update api register

This commit is contained in:
Wian Drs
2026-08-01 11:42:42 +07:00
parent b0d08211ec
commit 75342dc7b7
55 changed files with 2254 additions and 35 deletions
@@ -0,0 +1,100 @@
<?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('voucher_agents', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->restrictOnDelete();
$table->string('agent_code');
$table->string('payment_type', 20);
$table->string('price_mode', 20)->default('discount');
$table->decimal('discount_percent', 5, 2)->default(0);
$table->decimal('commission_percent', 5, 2)->default(0);
$table->decimal('credit_limit', 18, 2)->default(0);
$table->decimal('outstanding_balance', 18, 2)->default(0);
$table->string('status', 20)->default('active');
$table->text('notes')->nullable();
$table->timestamps();
$table->unique(['tenant_id', 'user_id']);
$table->unique(['tenant_id', 'agent_code']);
$table->index(['tenant_id', 'payment_type', 'status']);
});
Schema::create('voucher_sales', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->string('sale_number')->unique();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('voucher_agent_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('nas_package_profile_id')->constrained()->restrictOnDelete();
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
$table->string('source', 30)->default('dashboard');
$table->unsignedInteger('quantity');
$table->decimal('unit_price', 18, 2);
$table->decimal('discount_amount', 18, 2)->default(0);
$table->decimal('total_amount', 18, 2);
$table->string('payment_status', 30)->default('paid');
$table->string('status', 30)->default('completed');
$table->json('pricing_snapshot')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
$table->index(['tenant_id', 'status', 'created_at']);
$table->index(['voucher_agent_id', 'payment_status']);
});
Schema::create('hotspot_vouchers', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('voucher_sale_id')->constrained()->cascadeOnDelete();
$table->foreignId('nas_package_profile_id')->constrained()->restrictOnDelete();
$table->foreignId('nas_mikrotik_id')->nullable()->constrained()->nullOnDelete();
$table->string('username');
$table->text('password');
$table->string('status', 30)->default('generated');
$table->string('sync_status', 30)->default('pending');
$table->string('external_reference')->nullable();
$table->timestamp('valid_until')->nullable();
$table->timestamp('activated_at')->nullable();
$table->timestamp('used_at')->nullable();
$table->timestamp('synced_at')->nullable();
$table->text('sync_error')->nullable();
$table->json('profile_snapshot')->nullable();
$table->timestamps();
$table->unique(['tenant_id', 'username']);
$table->index(['tenant_id', 'status', 'created_at']);
$table->index(['nas_mikrotik_id', 'sync_status']);
});
Schema::create('voucher_login_page_settings', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('tenant_id')->unique()->constrained()->cascadeOnDelete();
$table->foreignId('payment_gateway_account_id')->nullable()->constrained()->nullOnDelete();
$table->string('public_key')->unique();
$table->boolean('enabled')->default(false);
$table->string('title')->default('Beli Voucher Hotspot');
$table->string('theme_color', 20)->default('#ff8c00');
$table->json('allowed_package_profile_ids')->nullable();
$table->json('settings')->nullable();
$table->timestamp('enabled_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('voucher_login_page_settings');
Schema::dropIfExists('hotspot_vouchers');
Schema::dropIfExists('voucher_sales');
Schema::dropIfExists('voucher_agents');
}
};
@@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('wallets', function (Blueprint $table) {
$table->foreignId('tenant_id')->nullable()->after('uuid')->constrained()->cascadeOnDelete();
$table->dropUnique('wallet_owner_type_unique');
});
DB::table('wallets')->where('owner_type', 'App\\Models\\Tenant')->orderBy('id')->each(function ($wallet) {
DB::table('wallets')->where('id', $wallet->id)->update([
'tenant_id' => $wallet->owner_id,
'wallet_type' => 'tenant_settlement',
]);
});
DB::table('wallets')->where('owner_type', 'App\\Models\\User')->orderBy('id')->each(function ($wallet) {
$tenantId = DB::table('user_tenants')->where('user_id', $wallet->owner_id)
->orderByDesc('is_default')->orderBy('id')->value('tenant_id');
if ($tenantId) {
DB::table('wallets')->where('id', $wallet->id)->update([
'tenant_id' => $tenantId,
'wallet_type' => 'deposit',
]);
} elseif ((float) $wallet->available_balance === 0.0 && (float) $wallet->pending_balance === 0.0 && (float) $wallet->locked_balance === 0.0) {
DB::table('wallets')->where('id', $wallet->id)->delete();
}
});
Schema::table('wallets', function (Blueprint $table) {
$table->unique(['owner_type', 'owner_id', 'tenant_id', 'wallet_type', 'currency'], 'wallet_owner_tenant_type_unique');
$table->index(['tenant_id', 'wallet_type', 'status']);
});
}
public function down(): void
{
Schema::table('wallets', function (Blueprint $table) {
$table->dropUnique('wallet_owner_tenant_type_unique');
$table->dropIndex(['tenant_id', 'wallet_type', 'status']);
});
DB::table('wallets')->where('wallet_type', 'deposit')->update(['wallet_type' => 'user']);
DB::table('wallets')->where('wallet_type', 'tenant_settlement')->update(['wallet_type' => 'tenant']);
Schema::table('wallets', function (Blueprint $table) {
$table->dropConstrainedForeignId('tenant_id');
$table->unique(['owner_type', 'owner_id', 'wallet_type', 'currency'], 'wallet_owner_type_unique');
});
}
};
@@ -0,0 +1,61 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_platform_staff')->default(false)->after('is_master')->index();
});
Schema::create('role_applications', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('applicant_user_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('tenant_id')->nullable()->constrained()->cascadeOnDelete();
$table->string('application_type', 20);
$table->string('status', 30)->default('pending');
$table->json('payload');
$table->foreignId('reviewed_by')->nullable()->constrained('users')->nullOnDelete();
$table->text('review_notes')->nullable();
$table->timestamp('submitted_at');
$table->timestamp('reviewed_at')->nullable();
$table->timestamps();
$table->index(['applicant_user_id', 'status', 'submitted_at']);
$table->index(['application_type', 'tenant_id', 'status']);
});
Schema::create('customer_user_links', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('customer_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('linked_by')->nullable()->constrained('users')->nullOnDelete();
$table->string('relationship', 30)->default('owner');
$table->boolean('is_primary')->default(true);
$table->timestamps();
$table->unique(['customer_id', 'user_id']);
$table->index(['user_id', 'tenant_id']);
});
DB::table('customers')->whereNotNull('user_id')->orderBy('id')->each(function ($customer) {
DB::table('customer_user_links')->insertOrIgnore([
'tenant_id' => $customer->tenant_id, 'customer_id' => $customer->id,
'user_id' => $customer->user_id, 'relationship' => 'owner', 'is_primary' => true,
'created_at' => now(), 'updated_at' => now(),
]);
});
}
public function down(): void
{
Schema::dropIfExists('customer_user_links');
Schema::dropIfExists('role_applications');
Schema::table('users', fn (Blueprint $table) => $table->dropColumn('is_platform_staff'));
}
};
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('notification_channels', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
});
Schema::table('notification_channels', function (Blueprint $table) {
$table->unsignedBigInteger('tenant_id')->nullable()->change();
$table->foreign('tenant_id')->references('id')->on('tenants')->cascadeOnDelete();
});
}
public function down(): void
{
DB::table('notification_channels')->whereNull('tenant_id')->delete();
Schema::table('notification_channels', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
});
Schema::table('notification_channels', function (Blueprint $table) {
$table->unsignedBigInteger('tenant_id')->nullable(false)->change();
$table->foreign('tenant_id')->references('id')->on('tenants')->cascadeOnDelete();
});
}
};
@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('user_tenants', function (Blueprint $table) {
$table->string('role', 30)->default('customer')->after('tenant_id');
$table->index(['tenant_id', 'role']);
});
DB::table('user_tenants')
->whereIn('user_id', DB::table('users')->where('access_level', 'staff')->select('id'))
->update(['role' => 'staff']);
$ownerUsers = DB::table('users')->where('access_level', 'tenant_owner')->pluck('id');
foreach ($ownerUsers as $userId) {
$ownerTenantId = DB::table('user_tenants')
->where('user_id', $userId)
->orderByDesc('is_default')
->orderBy('id')
->value('tenant_id');
if ($ownerTenantId) {
DB::table('user_tenants')
->where('user_id', $userId)
->where('tenant_id', $ownerTenantId)
->update(['role' => 'tenant_owner']);
}
}
DB::statement("CREATE UNIQUE INDEX user_tenants_one_owner_per_user ON user_tenants (user_id) WHERE role = 'tenant_owner'");
}
public function down(): void
{
DB::statement('DROP INDEX IF EXISTS user_tenants_one_owner_per_user');
Schema::table('user_tenants', function (Blueprint $table) {
$table->dropIndex(['tenant_id', 'role']);
$table->dropColumn('role');
});
}
};
@@ -0,0 +1,53 @@
<?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('notification_messages', function (Blueprint $table) {
$table->foreignId('tenant_id')->nullable()->change();
});
Schema::table('user_notifications', function (Blueprint $table) {
$table->foreignId('tenant_id')->nullable()->change();
});
Schema::create('system_notification_campaigns', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('tenant_id')->nullable()->constrained()->cascadeOnDelete();
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
$table->string('title');
$table->text('body');
$table->string('action_url')->nullable();
$table->string('icon')->default('cil-bell');
$table->json('audience');
$table->string('status', 30)->default('queued');
$table->unsignedInteger('estimated_recipients')->default(0);
$table->unsignedInteger('processed_recipients')->default(0);
$table->timestamp('completed_at')->nullable();
$table->timestamps();
$table->index(['tenant_id', 'status', 'created_at']);
});
Schema::table('user_notifications', function (Blueprint $table) {
$table->foreignId('system_notification_campaign_id')->nullable()
->after('notification_message_id')->constrained()->cascadeOnDelete();
$table->unique(['system_notification_campaign_id', 'user_id'], 'user_notifications_campaign_user_unique');
});
}
public function down(): void
{
Schema::table('user_notifications', function (Blueprint $table) {
$table->dropUnique('user_notifications_campaign_user_unique');
$table->dropConstrainedForeignId('system_notification_campaign_id');
});
Schema::dropIfExists('system_notification_campaigns');
Schema::table('user_notifications', fn (Blueprint $table) => $table->foreignId('tenant_id')->nullable(false)->change());
Schema::table('notification_messages', fn (Blueprint $table) => $table->foreignId('tenant_id')->nullable(false)->change());
}
};
+54 -1
View File
@@ -2,6 +2,7 @@
namespace Database\Seeders;
use App\Models\MenuGroup;
use App\Models\MenuList;
use Illuminate\Database\Seeder;
@@ -188,6 +189,40 @@ class MenuListSeeder extends Seeder
);
}
$accessApplications = MenuList::updateOrCreate(['slug' => 'role-applications'], [
'parent_id' => null, 'name' => 'Permintaan Akses', 'url' => null,
'icon' => 'cil-user-follow', 'component' => null, 'sort_order' => 83,
]);
foreach ([
['slug' => 'role-applications-tenants', 'name' => 'Pengajuan Tenant', 'url' => '/access-applications/tenants', 'sort_order' => 1],
['slug' => 'role-applications-agents', 'name' => 'Pengajuan Agen', 'url' => '/access-applications/agents', 'sort_order' => 2],
['slug' => 'role-applications-staff', 'name' => 'Pengajuan Staff', 'url' => '/access-applications/staff', 'sort_order' => 3],
] as $item) {
MenuList::updateOrCreate(['slug' => $item['slug']], [
'parent_id' => $accessApplications->id, 'name' => $item['name'], 'url' => $item['url'],
'icon' => 'cil-user-follow', 'component' => null, 'sort_order' => $item['sort_order'],
]);
}
$staffApplicationMenu = MenuList::where('slug', 'role-applications-staff')->firstOrFail();
$groupManagementMenu = MenuList::where('slug', 'users-group-menus')->first();
if ($groupManagementMenu) {
MenuGroup::query()
->whereNull('tenant_id')
->where('is_system', true)
->whereHas('menus', fn ($query) => $query->whereKey($groupManagementMenu->id))
->each(function ($group) use ($accessApplications, $staffApplicationMenu) {
$permissions = [
'can_view' => true, 'can_create' => false, 'can_update' => false,
'can_delete' => false, 'can_approve' => true, 'can_export' => false,
];
$group->menus()->syncWithoutDetaching([
$accessApplications->id => $permissions,
$staffApplicationMenu->id => $permissions,
]);
});
}
$paymentGateway = MenuList::updateOrCreate(
['slug' => 'payment-gateway'],
[
@@ -206,10 +241,28 @@ class MenuListSeeder extends Seeder
}
MenuList::updateOrCreate(['slug' => 'wallet'], [
'parent_id' => null, 'name' => 'Dompet', 'url' => '/wallet',
'parent_id' => null, 'name' => 'Saldo Deposit', 'url' => '/deposit-balance',
'icon' => 'cil-wallet', 'component' => null, 'sort_order' => 81,
]);
$voucherHotspot = MenuList::updateOrCreate(
['slug' => 'voucher-hotspot'],
[
'parent_id' => null, 'name' => 'Voucher Hotspot', 'url' => null,
'icon' => 'cil-barcode', 'component' => null, 'sort_order' => 82,
]
);
foreach ([
['slug' => 'voucher-hotspot-sales', 'name' => 'Jual Voucher', 'url' => '/voucher-hotspot/sales', 'sort_order' => 1],
['slug' => 'voucher-hotspot-agents', 'name' => 'Agen Voucher', 'url' => '/voucher-hotspot/agents', 'sort_order' => 2],
['slug' => 'voucher-hotspot-login-page', 'name' => 'Voucher LoginPage', 'url' => '/voucher-hotspot/login-page', 'sort_order' => 3],
] as $item) {
MenuList::updateOrCreate(['slug' => $item['slug']], [
'parent_id' => $voucherHotspot->id, 'name' => $item['name'], 'url' => $item['url'],
'icon' => 'cil-barcode', 'component' => null, 'sort_order' => $item['sort_order'],
]);
}
$nas = MenuList::updateOrCreate(
['slug' => 'nas'],
[