Make workflow migrations idempotent for partial deploys.
Deploy Ladill Care / deploy (push) Successful in 2m41s
Deploy Ladill Care / deploy (push) Successful in 2m41s
Production already had care_facility_workflows from a prior attempt, so recreate checks let migrate finish and record the batch. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,112 +11,123 @@ use Illuminate\Support\Facades\Schema;
|
||||
* VisitStageAdvance → a visit's position + history through those stages.
|
||||
* FinancialObligation → charge intent created before/after a stage; a stage's
|
||||
* queue stays gated until its obligation clears (paid / authorized / waived).
|
||||
*
|
||||
* Creation is idempotent so a prior partial deploy (tables present, migration
|
||||
* row missing) can finish cleanly.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('care_facility_workflows', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('organization_id')->constrained('care_organizations')->cascadeOnDelete();
|
||||
$table->foreignId('branch_id')->nullable()->constrained('care_branches')->nullOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('template_key')->nullable();
|
||||
$table->string('category')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->json('settings')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
if (! Schema::hasTable('care_facility_workflows')) {
|
||||
Schema::create('care_facility_workflows', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('organization_id')->constrained('care_organizations')->cascadeOnDelete();
|
||||
$table->foreignId('branch_id')->nullable()->constrained('care_branches')->nullOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('template_key')->nullable();
|
||||
$table->string('category')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->json('settings')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['organization_id', 'branch_id', 'is_active']);
|
||||
});
|
||||
$table->index(['organization_id', 'branch_id', 'is_active']);
|
||||
});
|
||||
}
|
||||
|
||||
Schema::create('care_workflow_stages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('workflow_id')->constrained('care_facility_workflows')->cascadeOnDelete();
|
||||
$table->string('code');
|
||||
$table->string('name');
|
||||
$table->string('type')->default('custom');
|
||||
$table->string('queue_context')->nullable();
|
||||
$table->string('department_type')->nullable();
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
if (! Schema::hasTable('care_workflow_stages')) {
|
||||
Schema::create('care_workflow_stages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('workflow_id')->constrained('care_facility_workflows')->cascadeOnDelete();
|
||||
$table->string('code');
|
||||
$table->string('name');
|
||||
$table->string('type')->default('custom');
|
||||
$table->string('queue_context')->nullable();
|
||||
$table->string('department_type')->nullable();
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
|
||||
// Financial gate configuration
|
||||
$table->boolean('requires_payment')->default(false);
|
||||
$table->string('payment_timing')->default('none'); // before, after, none
|
||||
$table->json('payment_modes')->nullable(); // internal_cashier, external_bank, digital
|
||||
$table->boolean('insurance_eligible')->default(false);
|
||||
$table->boolean('credit_allowed')->default(false);
|
||||
$table->boolean('allow_override')->default(true);
|
||||
$table->string('charge_code')->nullable();
|
||||
$table->string('charge_label')->nullable();
|
||||
$table->unsignedInteger('default_amount_minor')->nullable();
|
||||
// Financial gate configuration
|
||||
$table->boolean('requires_payment')->default(false);
|
||||
$table->string('payment_timing')->default('none'); // before, after, none
|
||||
$table->json('payment_modes')->nullable(); // internal_cashier, external_bank, digital
|
||||
$table->boolean('insurance_eligible')->default(false);
|
||||
$table->boolean('credit_allowed')->default(false);
|
||||
$table->boolean('allow_override')->default(true);
|
||||
$table->string('charge_code')->nullable();
|
||||
$table->string('charge_label')->nullable();
|
||||
$table->unsignedInteger('default_amount_minor')->nullable();
|
||||
|
||||
// Flow configuration
|
||||
$table->string('default_next')->nullable();
|
||||
$table->boolean('can_return')->default(false);
|
||||
$table->boolean('can_skip')->default(false);
|
||||
$table->boolean('optional')->default(false);
|
||||
$table->boolean('creates_child')->default(false);
|
||||
// Flow configuration
|
||||
$table->string('default_next')->nullable();
|
||||
$table->boolean('can_return')->default(false);
|
||||
$table->boolean('can_skip')->default(false);
|
||||
$table->boolean('optional')->default(false);
|
||||
$table->boolean('creates_child')->default(false);
|
||||
|
||||
$table->json('meta')->nullable();
|
||||
$table->timestamps();
|
||||
$table->json('meta')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['workflow_id', 'code']);
|
||||
$table->index(['workflow_id', 'sort_order']);
|
||||
});
|
||||
$table->unique(['workflow_id', 'code']);
|
||||
$table->index(['workflow_id', 'sort_order']);
|
||||
});
|
||||
}
|
||||
|
||||
Schema::create('care_visit_stage_advances', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('visit_id')->constrained('care_visits')->cascadeOnDelete();
|
||||
$table->foreignId('workflow_id')->nullable()->constrained('care_facility_workflows')->nullOnDelete();
|
||||
$table->foreignId('workflow_stage_id')->nullable()->constrained('care_workflow_stages')->nullOnDelete();
|
||||
$table->string('stage_code')->nullable();
|
||||
$table->string('status')->default('active'); // active, blocked, completed, skipped
|
||||
$table->string('blocked_reason')->nullable(); // awaiting_payment, awaiting_bank_receipt, awaiting_authorization
|
||||
$table->timestamp('entered_at')->nullable();
|
||||
$table->timestamp('cleared_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->json('meta')->nullable();
|
||||
$table->timestamps();
|
||||
if (! Schema::hasTable('care_visit_stage_advances')) {
|
||||
Schema::create('care_visit_stage_advances', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('visit_id')->constrained('care_visits')->cascadeOnDelete();
|
||||
$table->foreignId('workflow_id')->nullable()->constrained('care_facility_workflows')->nullOnDelete();
|
||||
$table->foreignId('workflow_stage_id')->nullable()->constrained('care_workflow_stages')->nullOnDelete();
|
||||
$table->string('stage_code')->nullable();
|
||||
$table->string('status')->default('active'); // active, blocked, completed, skipped
|
||||
$table->string('blocked_reason')->nullable(); // awaiting_payment, awaiting_bank_receipt, awaiting_authorization
|
||||
$table->timestamp('entered_at')->nullable();
|
||||
$table->timestamp('cleared_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->json('meta')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['visit_id', 'status']);
|
||||
$table->index(['visit_id', 'stage_code']);
|
||||
});
|
||||
$table->index(['visit_id', 'status']);
|
||||
$table->index(['visit_id', 'stage_code']);
|
||||
});
|
||||
}
|
||||
|
||||
Schema::create('care_financial_obligations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('organization_id')->constrained('care_organizations')->cascadeOnDelete();
|
||||
$table->foreignId('branch_id')->nullable()->constrained('care_branches')->nullOnDelete();
|
||||
$table->foreignId('visit_id')->nullable()->constrained('care_visits')->nullOnDelete();
|
||||
$table->foreignId('patient_id')->nullable()->constrained('care_patients')->nullOnDelete();
|
||||
$table->foreignId('workflow_stage_id')->nullable()->constrained('care_workflow_stages')->nullOnDelete();
|
||||
$table->foreignId('bill_id')->nullable()->constrained('care_bills')->nullOnDelete();
|
||||
$table->string('stage_code')->nullable();
|
||||
$table->string('charge_code')->nullable();
|
||||
$table->string('label')->nullable();
|
||||
$table->unsignedInteger('amount_minor')->default(0);
|
||||
$table->string('status')->default('pending'); // pending, authorized, paid, waived, deferred, void
|
||||
$table->string('clearance_method')->nullable(); // payment, insurance, waiver, credit, bank_receipt, override
|
||||
$table->string('payment_mode')->nullable(); // internal_cashier, external_bank, digital
|
||||
$table->string('external_reference')->nullable(); // bank slip / receipt no
|
||||
$table->string('cleared_by')->nullable();
|
||||
$table->timestamp('cleared_at')->nullable();
|
||||
$table->json('meta')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
if (! Schema::hasTable('care_financial_obligations')) {
|
||||
Schema::create('care_financial_obligations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('owner_ref')->index();
|
||||
$table->foreignId('organization_id')->constrained('care_organizations')->cascadeOnDelete();
|
||||
$table->foreignId('branch_id')->nullable()->constrained('care_branches')->nullOnDelete();
|
||||
$table->foreignId('visit_id')->nullable()->constrained('care_visits')->nullOnDelete();
|
||||
$table->foreignId('patient_id')->nullable()->constrained('care_patients')->nullOnDelete();
|
||||
$table->foreignId('workflow_stage_id')->nullable()->constrained('care_workflow_stages')->nullOnDelete();
|
||||
$table->foreignId('bill_id')->nullable()->constrained('care_bills')->nullOnDelete();
|
||||
$table->string('stage_code')->nullable();
|
||||
$table->string('charge_code')->nullable();
|
||||
$table->string('label')->nullable();
|
||||
$table->unsignedInteger('amount_minor')->default(0);
|
||||
$table->string('status')->default('pending'); // pending, authorized, paid, waived, deferred, void
|
||||
$table->string('clearance_method')->nullable(); // payment, insurance, waiver, credit, bank_receipt, override
|
||||
$table->string('payment_mode')->nullable(); // internal_cashier, external_bank, digital
|
||||
$table->string('external_reference')->nullable(); // bank slip / receipt no
|
||||
$table->string('cleared_by')->nullable();
|
||||
$table->timestamp('cleared_at')->nullable();
|
||||
$table->json('meta')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['visit_id', 'status']);
|
||||
$table->index(['organization_id', 'status']);
|
||||
$table->index(['stage_code', 'status']);
|
||||
});
|
||||
$table->index(['visit_id', 'status']);
|
||||
$table->index(['organization_id', 'status']);
|
||||
$table->index(['stage_code', 'status']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
|
||||
@@ -8,6 +8,17 @@ return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('care_financial_obligations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hasUnique = collect(Schema::getIndexes('care_financial_obligations'))
|
||||
->contains(fn (array $index) => ($index['name'] ?? '') === 'care_obligations_visit_stage_unique');
|
||||
|
||||
if ($hasUnique) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('care_financial_obligations', function (Blueprint $table) {
|
||||
$table->unique(
|
||||
['visit_id', 'workflow_stage_id'],
|
||||
@@ -18,6 +29,17 @@ return new class extends Migration
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('care_financial_obligations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hasUnique = collect(Schema::getIndexes('care_financial_obligations'))
|
||||
->contains(fn (array $index) => ($index['name'] ?? '') === 'care_obligations_visit_stage_unique');
|
||||
|
||||
if (! $hasUnique) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('care_financial_obligations', function (Blueprint $table) {
|
||||
$table->dropUnique('care_obligations_visit_stage_unique');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user