Add gold credit feature gated by SuperAdmin enable_gold_credit.
Port tables, APIs, and overdue cron from goldali, default the flag off, and keep customer routes behind auth. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('gold_credits', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('customer_id');
|
||||
$table->decimal('gold_amount', 12, 4);
|
||||
$table->unsignedSmallInteger('months');
|
||||
$table->enum('interest_type', ['none', 'percent', 'fixed_gram'])->default('none');
|
||||
$table->decimal('interest_value', 12, 4)->default(0);
|
||||
$table->enum('delivery_method', ['auto', 'manual', 'in_person'])->default('manual');
|
||||
$table->enum('status', ['pending', 'active', 'completed', 'cancelled'])->default('active');
|
||||
$table->enum('source', ['admin_direct', 'user_request'])->default('admin_direct');
|
||||
$table->decimal('total_interest_gold', 12, 4)->default(0);
|
||||
$table->decimal('total_payable_gold', 12, 4)->default(0);
|
||||
$table->decimal('paid_gold', 12, 4)->default(0);
|
||||
$table->unsignedBigInteger('paid_rial')->default(0);
|
||||
$table->unsignedBigInteger('created_by')->nullable();
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('customer_id', 'gc_credit_customer_fk')
|
||||
->references('id')->on('customers')->cascadeOnDelete();
|
||||
$table->index(['customer_id', 'status'], 'gc_credit_cust_st_idx');
|
||||
});
|
||||
|
||||
Schema::create('gold_credit_installments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('gold_credit_id');
|
||||
$table->unsignedSmallInteger('installment_number');
|
||||
$table->date('due_date');
|
||||
$table->unsignedInteger('amount_soot');
|
||||
$table->decimal('principal_gold', 12, 6)->default(0);
|
||||
$table->decimal('interest_gold', 12, 6)->default(0);
|
||||
$table->decimal('total_gold', 12, 6)->default(0);
|
||||
$table->decimal('paid_gold', 12, 6)->default(0);
|
||||
$table->unsignedBigInteger('paid_rial')->default(0);
|
||||
$table->enum('status', ['pending', 'paid', 'partial', 'overdue', 'prepaid'])->default('pending');
|
||||
$table->timestamp('paid_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('gold_credit_id', 'gc_inst_credit_fk')
|
||||
->references('id')->on('gold_credits')->cascadeOnDelete();
|
||||
$table->unique(['gold_credit_id', 'installment_number'], 'gc_inst_num_uniq');
|
||||
});
|
||||
|
||||
Schema::create('gold_credit_requests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('customer_id');
|
||||
$table->decimal('requested_gold', 12, 4);
|
||||
$table->unsignedSmallInteger('requested_months')->nullable();
|
||||
$table->text('note')->nullable();
|
||||
$table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
|
||||
$table->text('admin_note')->nullable();
|
||||
$table->unsignedBigInteger('gold_credit_id')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('customer_id', 'gc_req_customer_fk')
|
||||
->references('id')->on('customers')->cascadeOnDelete();
|
||||
$table->foreign('gold_credit_id', 'gc_req_credit_fk')
|
||||
->references('id')->on('gold_credits')->nullOnDelete();
|
||||
$table->index(['customer_id', 'status'], 'gc_req_cust_st_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('gold_credit_requests');
|
||||
Schema::dropIfExists('gold_credit_installments');
|
||||
Schema::dropIfExists('gold_credits');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('gold_credits', function (Blueprint $table) {
|
||||
$table->boolean('has_check')->default(false)->after('notes');
|
||||
$table->boolean('has_contract')->default(false)->after('has_check');
|
||||
$table->string('check_note')->nullable()->after('has_contract');
|
||||
$table->string('contract_note')->nullable()->after('check_note');
|
||||
$table->enum('penalty_type', ['rial', 'gold'])->nullable()->after('contract_note');
|
||||
$table->decimal('penalty_amount', 14, 4)->nullable()->after('penalty_type');
|
||||
$table->unsignedSmallInteger('grace_hours')->nullable()->after('penalty_amount');
|
||||
});
|
||||
|
||||
Schema::table('gold_credit_installments', function (Blueprint $table) {
|
||||
$table->timestamp('warning_sms_sent_at')->nullable()->after('paid_at');
|
||||
$table->timestamp('penalty_started_at')->nullable()->after('warning_sms_sent_at');
|
||||
$table->timestamp('last_penalty_at')->nullable()->after('penalty_started_at');
|
||||
$table->unsignedInteger('penalty_days')->default(0)->after('last_penalty_at');
|
||||
$table->unsignedBigInteger('penalty_rial')->default(0)->after('penalty_days');
|
||||
$table->decimal('penalty_gold', 12, 6)->default(0)->after('penalty_rial');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('gold_credits', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'has_check',
|
||||
'has_contract',
|
||||
'check_note',
|
||||
'contract_note',
|
||||
'penalty_type',
|
||||
'penalty_amount',
|
||||
'grace_hours',
|
||||
]);
|
||||
});
|
||||
|
||||
Schema::table('gold_credit_installments', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'warning_sms_sent_at',
|
||||
'penalty_started_at',
|
||||
'last_penalty_at',
|
||||
'penalty_days',
|
||||
'penalty_rial',
|
||||
'penalty_gold',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('gold_credit_installments', function (Blueprint $table) {
|
||||
$table->timestamp('reminder_3d_sent_at')->nullable()->after('warning_sms_sent_at');
|
||||
$table->timestamp('reminder_1d_sent_at')->nullable()->after('reminder_3d_sent_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('gold_credit_installments', function (Blueprint $table) {
|
||||
$table->dropColumn(['reminder_3d_sent_at', 'reminder_1d_sent_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('settings')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = [
|
||||
// SuperAdmin-owned feature flag (default off). Local copy; overlay may overwrite.
|
||||
[
|
||||
'key' => 'enable_gold_credit',
|
||||
'value' => 'false',
|
||||
'title_fa' => 'اعتبار طلایی',
|
||||
'description' => 'روشن: منوی اعتبار در ادمین و صفحه اعتبار در اپ مشتری فعال میشود. خاموش: غیرفعال. فقط از سوپرادمین قابل تغییر است.',
|
||||
'can_edit' => 0,
|
||||
'is_active' => 1,
|
||||
'type' => 'switch',
|
||||
],
|
||||
['key' => 'gold_credit_penalty_type', 'value' => 'rial', 'title_fa' => 'نوع جریمه اعتبار طلایی', 'description' => 'ریالی یا طلایی|rial,gold', 'can_edit' => 1, 'is_active' => 1, 'type' => 'select'],
|
||||
['key' => 'gold_credit_penalty_amount', 'value' => '1000000', 'title_fa' => 'مبلغ جریمه روزانه اعتبار طلایی', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'number'],
|
||||
['key' => 'gold_credit_grace_hours', 'value' => '24', 'title_fa' => 'مهلت پرداخت بعد از سررسید (ساعت)', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'number'],
|
||||
['key' => 'gold_credit_default_months', 'value' => '12', 'title_fa' => 'تعداد ماه پیشفرض اعتبار طلایی', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'number'],
|
||||
['key' => 'gold_credit_min_balance_gram', 'value' => '0.5', 'title_fa' => 'حداقل موجودی طلا برای درخواست (گرم)', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'number'],
|
||||
['key' => 'gold_credit_overdue_sms_enabled', 'value' => 'true', 'title_fa' => 'ارسال SMS هشدار سررسید اعتبار طلایی', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'switch'],
|
||||
['key' => 'gold_credit_admin_sms_enabled', 'value' => 'true', 'title_fa' => 'پیامک ادمین برای درخواست و پرداخت اعتبار طلایی', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'switch'],
|
||||
['key' => 'gold_credit_upcoming_sms_enabled', 'value' => 'true', 'title_fa' => 'یادآوری ۱ و ۳ روز مانده به سررسید قسط', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'switch'],
|
||||
['key' => 'gold_credit_pay_gold_enabled', 'value' => 'true', 'title_fa' => 'پرداخت اقساط با طلا', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'switch'],
|
||||
['key' => 'gold_credit_pay_rial_enabled', 'value' => 'false', 'title_fa' => 'پرداخت اقساط با ریال', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'switch'],
|
||||
['key' => 'gold_credit_pay_mixed_enabled', 'value' => 'false', 'title_fa' => 'پرداخت اقساط ترکیبی', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'switch'],
|
||||
['key' => 'gold_credit_overdue_sms_text', 'value' => 'مشتری گرامی، قسط اعتبار طلایی شما سررسید شده است.', 'title_fa' => 'متن SMS سررسید اعتبار طلایی', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'longtext'],
|
||||
['key' => 'gold_credit_info_text', 'value' => '', 'title_fa' => 'متن راهنمای اعتبار طلایی', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'longtext'],
|
||||
['key' => 'gold_credit_request_success_text', 'value' => 'درخواست شما ثبت شد و حداکثر تا ۲۴ ساعت بررسی میشود.', 'title_fa' => 'متن پس از ثبت درخواست اعتبار', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'longtext'],
|
||||
['key' => 'gold_credit_tiers', 'value' => '[]', 'title_fa' => 'سطوح اعتبار طلایی (JSON)', 'description' => '', 'can_edit' => 1, 'is_active' => 1, 'type' => 'longtext'],
|
||||
];
|
||||
|
||||
$hasType = Schema::hasColumn('settings', 'type');
|
||||
$hasShow = Schema::hasColumn('settings', 'show_to_user');
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$exists = DB::table('settings')->where('key', $row['key'])->exists();
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
$insert = [
|
||||
'key' => $row['key'],
|
||||
'value' => $row['value'],
|
||||
'title_fa' => $row['title_fa'],
|
||||
'description' => $row['description'],
|
||||
'can_edit' => $row['can_edit'],
|
||||
'is_active' => $row['is_active'],
|
||||
];
|
||||
if ($hasType) {
|
||||
$insert['type'] = $row['type'];
|
||||
}
|
||||
if ($hasShow && $row['key'] === 'enable_gold_credit') {
|
||||
$insert['show_to_user'] = 1;
|
||||
}
|
||||
DB::table('settings')->insert($insert);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// keep data
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user