forked from admin/services_core
Giant Update
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?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('customers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('customer_code');
|
||||
$table->string('name');
|
||||
$table->string('identity_number', 100)->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('whatsapp_number', 50)->nullable();
|
||||
$table->text('installation_address')->nullable();
|
||||
$table->foreignId('provinsi_id')->nullable()->constrained('wilayah_provinsi')->nullOnDelete();
|
||||
$table->foreignId('kabupaten_id')->nullable()->constrained('wilayah_kabupaten')->nullOnDelete();
|
||||
$table->foreignId('kecamatan_id')->nullable()->constrained('wilayah_kecamatan')->nullOnDelete();
|
||||
$table->foreignId('desa_id')->nullable()->constrained('wilayah_desa')->nullOnDelete();
|
||||
$table->decimal('latitude', 10, 7)->nullable();
|
||||
$table->decimal('longitude', 10, 7)->nullable();
|
||||
$table->foreignId('package_profile_id')->nullable()->constrained('nas_package_profiles')->nullOnDelete();
|
||||
$table->foreignId('nas_mikrotik_id')->nullable()->constrained('nas_mikrotiks')->nullOnDelete();
|
||||
$table->unsignedBigInteger('billing_profile_id')->nullable()->index();
|
||||
$table->unsignedBigInteger('topology_id')->nullable()->index();
|
||||
$table->string('external_username')->nullable();
|
||||
$table->string('source', 20)->default('manual');
|
||||
$table->string('status', 20)->default('order');
|
||||
$table->string('order_stage', 30)->default('registration');
|
||||
$table->timestamp('activated_at')->nullable();
|
||||
$table->timestamp('inactivated_at')->nullable();
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->unique(['tenant_id', 'customer_code']);
|
||||
$table->index(['tenant_id', 'status']);
|
||||
$table->index(['tenant_id', 'source', 'external_username']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('customers');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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('customer_images', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('customer_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('stored_file_id')->constrained('stored_files')->cascadeOnDelete();
|
||||
$table->string('category', 30)->default('other');
|
||||
$table->string('caption')->nullable();
|
||||
$table->boolean('is_cover')->default(false);
|
||||
$table->unsignedSmallInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
$table->unique(['customer_id', 'stored_file_id']);
|
||||
$table->index(['customer_id', 'is_cover']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('customer_images');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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('billing_profiles', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name');
|
||||
$table->unsignedTinyInteger('invoice_day');
|
||||
$table->unsignedTinyInteger('send_day');
|
||||
$table->unsignedTinyInteger('warning_day');
|
||||
$table->unsignedTinyInteger('isolation_day');
|
||||
$table->string('tax_type', 20)->default('non_tax');
|
||||
$table->decimal('tax_rate', 5, 2)->default(0);
|
||||
$table->string('status', 20)->default('active');
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_id', 'name']);
|
||||
$table->index(['tenant_id', 'status']);
|
||||
});
|
||||
|
||||
Schema::table('customers', function (Blueprint $table) {
|
||||
$table->foreign('billing_profile_id')
|
||||
->references('id')
|
||||
->on('billing_profiles')
|
||||
->nullOnDelete();
|
||||
});
|
||||
|
||||
Schema::create('invoices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('customer_id')->constrained()->restrictOnDelete();
|
||||
$table->foreignId('billing_profile_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('paid_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('invoice_number');
|
||||
$table->date('period_start');
|
||||
$table->date('period_end');
|
||||
$table->date('issue_date');
|
||||
$table->date('send_date')->nullable();
|
||||
$table->date('warning_date')->nullable();
|
||||
$table->date('isolation_date')->nullable();
|
||||
$table->decimal('subtotal', 15, 2);
|
||||
$table->string('tax_type', 20)->default('non_tax');
|
||||
$table->decimal('tax_rate', 5, 2)->default(0);
|
||||
$table->decimal('tax_amount', 15, 2)->default(0);
|
||||
$table->decimal('total', 15, 2);
|
||||
$table->decimal('paid_amount', 15, 2)->default(0);
|
||||
$table->decimal('balance', 15, 2);
|
||||
$table->string('status', 20)->default('draft');
|
||||
$table->string('payment_status', 20)->default('unpaid');
|
||||
$table->timestamp('paid_at')->nullable();
|
||||
$table->string('payment_method', 50)->nullable();
|
||||
$table->string('payment_reference')->nullable();
|
||||
$table->string('external_payment_id')->nullable()->index();
|
||||
$table->string('finance_reference')->nullable()->index();
|
||||
$table->string('notification_status', 30)->default('pending');
|
||||
$table->string('customer_code_snapshot');
|
||||
$table->string('customer_name_snapshot');
|
||||
$table->text('customer_address_snapshot')->nullable();
|
||||
$table->string('package_name_snapshot')->nullable();
|
||||
$table->json('profile_snapshot')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->unique(['tenant_id', 'invoice_number']);
|
||||
$table->index(['tenant_id', 'status', 'issue_date']);
|
||||
$table->index(['tenant_id', 'payment_status', 'isolation_date']);
|
||||
$table->index(['customer_id', 'period_start', 'period_end']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('invoices');
|
||||
Schema::table('customers', function (Blueprint $table) {
|
||||
$table->dropForeign(['billing_profile_id']);
|
||||
});
|
||||
Schema::dropIfExists('billing_profiles');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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('billing_profiles', function (Blueprint $table) {
|
||||
$table->string('schedule_type', 30)->default('fixed_date')->after('name');
|
||||
$table->unsignedTinyInteger('invoice_days_before')->nullable()->after('isolation_day');
|
||||
$table->unsignedTinyInteger('send_days_before')->nullable()->after('invoice_days_before');
|
||||
$table->unsignedTinyInteger('warning_days_before')->nullable()->after('send_days_before');
|
||||
$table->unsignedTinyInteger('isolation_days_after')->nullable()->after('warning_days_before');
|
||||
$table->index(['tenant_id', 'schedule_type']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('billing_profiles', function (Blueprint $table) {
|
||||
$table->dropIndex(['tenant_id', 'schedule_type']);
|
||||
$table->dropColumn([
|
||||
'schedule_type',
|
||||
'invoice_days_before',
|
||||
'send_days_before',
|
||||
'warning_days_before',
|
||||
'isolation_days_after',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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('invoices', function (Blueprint $table) {
|
||||
$table->unique(
|
||||
['customer_id', 'period_start', 'period_end'],
|
||||
'invoices_customer_period_unique',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->dropUnique('invoices_customer_period_unique');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
<?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('topology_device_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->string('code', 50);
|
||||
$table->string('name');
|
||||
$table->string('category', 50);
|
||||
$table->boolean('can_have_ports')->default(true);
|
||||
$table->boolean('can_have_splitters')->default(false);
|
||||
$table->boolean('is_system')->default(false);
|
||||
$table->string('icon')->nullable();
|
||||
$table->string('color', 20)->default('#2f74c0');
|
||||
$table->string('status', 20)->default('active');
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_id', 'code']);
|
||||
$table->index(['tenant_id', 'status']);
|
||||
});
|
||||
|
||||
Schema::create('topology_nodes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('device_type_id')->constrained('topology_device_types')->restrictOnDelete();
|
||||
$table->foreignId('parent_node_id')->nullable()->constrained('topology_nodes')->nullOnDelete();
|
||||
$table->foreignId('provinsi_id')->nullable()->constrained('wilayah_provinsi')->nullOnDelete();
|
||||
$table->foreignId('kabupaten_id')->nullable()->constrained('wilayah_kabupaten')->nullOnDelete();
|
||||
$table->foreignId('kecamatan_id')->nullable()->constrained('wilayah_kecamatan')->nullOnDelete();
|
||||
$table->foreignId('desa_id')->nullable()->constrained('wilayah_desa')->nullOnDelete();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('code');
|
||||
$table->string('name');
|
||||
$table->text('description')->nullable();
|
||||
$table->string('status', 30)->default('active');
|
||||
$table->decimal('latitude', 10, 7);
|
||||
$table->decimal('longitude', 10, 7);
|
||||
$table->text('address')->nullable();
|
||||
$table->unsignedInteger('capacity')->nullable();
|
||||
$table->date('installed_at')->nullable();
|
||||
$table->decimal('diagram_x', 12, 2)->nullable();
|
||||
$table->decimal('diagram_y', 12, 2)->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->unique(['tenant_id', 'code']);
|
||||
$table->index(['tenant_id', 'device_type_id', 'status']);
|
||||
$table->index(['tenant_id', 'latitude', 'longitude']);
|
||||
});
|
||||
|
||||
Schema::create('topology_ports', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('node_id')->constrained('topology_nodes')->cascadeOnDelete();
|
||||
$table->string('name');
|
||||
$table->unsignedInteger('port_number');
|
||||
$table->string('port_type', 50)->default('fiber');
|
||||
$table->string('direction', 20)->default('bidirectional');
|
||||
$table->unsignedInteger('capacity')->default(1);
|
||||
$table->string('status', 30)->default('available');
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['node_id', 'port_number']);
|
||||
$table->index(['tenant_id', 'status']);
|
||||
});
|
||||
|
||||
Schema::create('topology_links', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('source_node_id')->constrained('topology_nodes')->restrictOnDelete();
|
||||
$table->foreignId('source_port_id')->nullable()->constrained('topology_ports')->restrictOnDelete();
|
||||
$table->foreignId('target_node_id')->constrained('topology_nodes')->restrictOnDelete();
|
||||
$table->foreignId('target_port_id')->nullable()->constrained('topology_ports')->restrictOnDelete();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('code');
|
||||
$table->string('name');
|
||||
$table->string('link_type', 30)->default('fiber');
|
||||
$table->string('fiber_type', 50)->nullable();
|
||||
$table->unsignedInteger('core_count')->nullable();
|
||||
$table->decimal('cable_length', 12, 2)->nullable();
|
||||
$table->decimal('measured_length', 12, 2)->nullable();
|
||||
$table->json('path_geojson')->nullable();
|
||||
$table->string('status', 30)->default('active');
|
||||
$table->date('installed_at')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
$table->unique(['tenant_id', 'code']);
|
||||
$table->unique(['source_node_id', 'target_node_id', 'source_port_id', 'target_port_id'], 'topology_links_endpoints_unique');
|
||||
$table->index(['tenant_id', 'status', 'link_type']);
|
||||
});
|
||||
|
||||
Schema::create('topology_connection_rules', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('source_device_type_id')->constrained('topology_device_types')->cascadeOnDelete();
|
||||
$table->foreignId('target_device_type_id')->constrained('topology_device_types')->cascadeOnDelete();
|
||||
$table->boolean('allowed')->default(true);
|
||||
$table->unsignedInteger('max_connections')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_id', 'source_device_type_id', 'target_device_type_id'], 'topology_rules_unique');
|
||||
});
|
||||
|
||||
Schema::create('topology_splitters', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('node_id')->constrained('topology_nodes')->cascadeOnDelete();
|
||||
$table->foreignId('input_port_id')->nullable()->constrained('topology_ports')->nullOnDelete();
|
||||
$table->string('ratio', 20);
|
||||
$table->unsignedTinyInteger('level')->default(1);
|
||||
$table->decimal('insertion_loss_db', 6, 2)->nullable();
|
||||
$table->string('status', 30)->default('active');
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['tenant_id', 'node_id']);
|
||||
});
|
||||
|
||||
Schema::create('topology_customer_connections', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('customer_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('odp_node_id')->constrained('topology_nodes')->restrictOnDelete();
|
||||
$table->foreignId('odp_port_id')->nullable()->constrained('topology_ports')->restrictOnDelete();
|
||||
$table->foreignId('drop_cable_link_id')->nullable()->constrained('topology_links')->nullOnDelete();
|
||||
$table->timestamp('connected_at')->nullable();
|
||||
$table->timestamp('disconnected_at')->nullable();
|
||||
$table->string('status', 30)->default('active');
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['customer_id', 'status']);
|
||||
$table->index(['tenant_id', 'odp_node_id', 'status']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('topology_customer_connections');
|
||||
Schema::dropIfExists('topology_splitters');
|
||||
Schema::dropIfExists('topology_connection_rules');
|
||||
Schema::dropIfExists('topology_links');
|
||||
Schema::dropIfExists('topology_ports');
|
||||
Schema::dropIfExists('topology_nodes');
|
||||
Schema::dropIfExists('topology_device_types');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,260 @@
|
||||
<?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::create('notification_events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code')->unique();
|
||||
$table->string('name');
|
||||
$table->string('category', 50);
|
||||
$table->text('description')->nullable();
|
||||
$table->json('available_channels');
|
||||
$table->json('variables')->nullable();
|
||||
$table->boolean('is_system')->default(true);
|
||||
$table->boolean('enabled')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('notification_channels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('channel', 30);
|
||||
$table->string('provider', 50);
|
||||
$table->string('name');
|
||||
$table->boolean('enabled')->default(false);
|
||||
$table->boolean('is_default')->default(false);
|
||||
$table->text('credentials')->nullable();
|
||||
$table->json('settings')->nullable();
|
||||
$table->string('last_health_status', 30)->nullable();
|
||||
$table->text('last_health_message')->nullable();
|
||||
$table->timestamp('last_health_check_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_id', 'channel', 'name']);
|
||||
$table->index(['tenant_id', 'channel', 'enabled']);
|
||||
});
|
||||
|
||||
Schema::create('notification_templates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_event_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_channel_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('channel', 30);
|
||||
$table->string('name');
|
||||
$table->string('subject')->nullable();
|
||||
$table->text('body');
|
||||
$table->string('provider_template_name')->nullable();
|
||||
$table->string('provider_template_id')->nullable();
|
||||
$table->string('language', 10)->default('id');
|
||||
$table->string('approval_status', 30)->default('not_required');
|
||||
$table->boolean('enabled')->default(true);
|
||||
$table->unsignedInteger('version')->default(1);
|
||||
$table->json('variables')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_id', 'notification_event_id', 'channel', 'language', 'version'], 'notification_templates_version_unique');
|
||||
$table->index(['tenant_id', 'channel', 'enabled']);
|
||||
});
|
||||
|
||||
Schema::create('notification_preferences', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_event_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_channel_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('channel', 30);
|
||||
$table->boolean('enabled')->default(true);
|
||||
$table->unsignedTinyInteger('priority')->default(1);
|
||||
$table->unsignedInteger('send_delay_minutes')->default(0);
|
||||
$table->time('quiet_hours_start')->nullable();
|
||||
$table->time('quiet_hours_end')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_id', 'notification_event_id', 'channel']);
|
||||
});
|
||||
|
||||
Schema::create('customer_contacts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('customer_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('type', 30);
|
||||
$table->string('value');
|
||||
$table->string('label')->nullable();
|
||||
$table->boolean('is_primary')->default(false);
|
||||
$table->boolean('is_verified')->default(false);
|
||||
$table->boolean('can_receive_transactional')->default(true);
|
||||
$table->boolean('can_receive_broadcast')->default(false);
|
||||
$table->timestamp('verified_at')->nullable();
|
||||
$table->timestamp('opted_out_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['customer_id', 'type', 'value']);
|
||||
$table->index(['tenant_id', 'type', 'is_verified']);
|
||||
});
|
||||
|
||||
Schema::create('notification_broadcasts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_template_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('notification_channel_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('approved_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('channel', 30);
|
||||
$table->string('subject')->nullable();
|
||||
$table->text('body')->nullable();
|
||||
$table->string('status', 30)->default('draft');
|
||||
$table->json('filter_snapshot');
|
||||
$table->timestamp('scheduled_at')->nullable();
|
||||
$table->unsignedInteger('estimated_recipients')->default(0);
|
||||
$table->unsignedInteger('processed_recipients')->default(0);
|
||||
$table->unsignedInteger('success_count')->default(0);
|
||||
$table->unsignedInteger('failed_count')->default(0);
|
||||
$table->timestamps();
|
||||
$table->index(['tenant_id', 'status', 'scheduled_at']);
|
||||
});
|
||||
|
||||
Schema::create('notification_messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_event_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('notification_template_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('notification_broadcast_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->nullableMorphs('recipient');
|
||||
$table->nullableMorphs('subjectable');
|
||||
$table->string('subject')->nullable();
|
||||
$table->json('payload');
|
||||
$table->json('context')->nullable();
|
||||
$table->unsignedTinyInteger('priority')->default(5);
|
||||
$table->timestamp('scheduled_at')->nullable();
|
||||
$table->string('status', 30)->default('pending');
|
||||
$table->timestamps();
|
||||
$table->index(['tenant_id', 'status', 'scheduled_at']);
|
||||
});
|
||||
|
||||
Schema::create('notification_deliveries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('notification_message_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_channel_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('channel', 30);
|
||||
$table->string('destination')->nullable();
|
||||
$table->string('rendered_subject')->nullable();
|
||||
$table->text('rendered_body');
|
||||
$table->string('provider_message_id')->nullable()->index();
|
||||
$table->string('status', 30)->default('pending');
|
||||
$table->unsignedTinyInteger('attempt_count')->default(0);
|
||||
$table->timestamp('queued_at')->nullable();
|
||||
$table->timestamp('sent_at')->nullable();
|
||||
$table->timestamp('delivered_at')->nullable();
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamp('failed_at')->nullable();
|
||||
$table->timestamp('next_retry_at')->nullable();
|
||||
$table->string('error_code')->nullable();
|
||||
$table->text('error_message')->nullable();
|
||||
$table->json('provider_response')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['channel', 'status', 'next_retry_at']);
|
||||
});
|
||||
|
||||
Schema::create('notification_broadcast_recipients', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('notification_broadcast_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('customer_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_message_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('destination')->nullable();
|
||||
$table->string('status', 30)->default('pending');
|
||||
$table->text('reason')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['notification_broadcast_id', 'customer_id']);
|
||||
});
|
||||
|
||||
Schema::create('user_notifications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_message_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('title');
|
||||
$table->text('body');
|
||||
$table->string('action_url')->nullable();
|
||||
$table->string('icon')->nullable();
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['user_id', 'read_at', 'created_at']);
|
||||
});
|
||||
|
||||
Schema::create('notification_event_occurrences', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('notification_event_id')->constrained()->cascadeOnDelete();
|
||||
$table->morphs('subjectable');
|
||||
$table->string('occurrence_key');
|
||||
$table->timestamp('processed_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['notification_event_id', 'subjectable_type', 'subjectable_id', 'occurrence_key'], 'notification_occurrences_unique');
|
||||
});
|
||||
|
||||
Schema::create('notification_webhook_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('notification_channel_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('provider', 50);
|
||||
$table->string('event_type')->nullable();
|
||||
$table->string('provider_message_id')->nullable()->index();
|
||||
$table->boolean('signature_valid')->default(false);
|
||||
$table->json('payload');
|
||||
$table->timestamp('processed_at')->nullable();
|
||||
$table->text('error')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
DB::table('customers')
|
||||
->select(['id', 'tenant_id', 'email', 'whatsapp_number'])
|
||||
->orderBy('id')
|
||||
->chunk(500, function ($customers) {
|
||||
$rows = [];
|
||||
foreach ($customers as $customer) {
|
||||
foreach (['email' => $customer->email, 'whatsapp' => $customer->whatsapp_number] as $type => $value) {
|
||||
if ($value) {
|
||||
$rows[] = [
|
||||
'tenant_id' => $customer->tenant_id,
|
||||
'customer_id' => $customer->id,
|
||||
'type' => $type,
|
||||
'value' => $value,
|
||||
'label' => 'Kontak utama',
|
||||
'is_primary' => true,
|
||||
'is_verified' => false,
|
||||
'can_receive_transactional' => true,
|
||||
'can_receive_broadcast' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($rows) {
|
||||
DB::table('customer_contacts')->insertOrIgnore($rows);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('notification_webhook_logs');
|
||||
Schema::dropIfExists('notification_event_occurrences');
|
||||
Schema::dropIfExists('user_notifications');
|
||||
Schema::dropIfExists('notification_broadcast_recipients');
|
||||
Schema::dropIfExists('notification_deliveries');
|
||||
Schema::dropIfExists('notification_messages');
|
||||
Schema::dropIfExists('notification_broadcasts');
|
||||
Schema::dropIfExists('customer_contacts');
|
||||
Schema::dropIfExists('notification_preferences');
|
||||
Schema::dropIfExists('notification_templates');
|
||||
Schema::dropIfExists('notification_channels');
|
||||
Schema::dropIfExists('notification_events');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,203 @@
|
||||
<?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('payment_providers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code', 50)->unique();
|
||||
$table->string('name');
|
||||
$table->string('scope', 20)->default('both');
|
||||
$table->json('capabilities')->nullable();
|
||||
$table->json('credential_schema')->nullable();
|
||||
$table->boolean('enabled')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('payment_gateway_accounts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('payment_provider_id')->constrained()->restrictOnDelete();
|
||||
$table->foreignId('tenant_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->string('scope', 20);
|
||||
$table->string('name');
|
||||
$table->string('environment', 20)->default('sandbox');
|
||||
$table->text('credentials')->nullable();
|
||||
$table->text('webhook_secret')->nullable();
|
||||
$table->json('settings')->nullable();
|
||||
$table->boolean('enabled')->default(false);
|
||||
$table->boolean('is_default')->default(false);
|
||||
$table->string('last_health_status', 30)->nullable();
|
||||
$table->text('last_health_message')->nullable();
|
||||
$table->timestamp('last_health_check_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['scope', 'tenant_id', 'enabled']);
|
||||
});
|
||||
|
||||
Schema::create('tenant_payment_settings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->unique()->constrained()->cascadeOnDelete();
|
||||
$table->string('payment_mode', 30)->default('disabled');
|
||||
$table->foreignId('tenant_gateway_account_id')->nullable()->constrained('payment_gateway_accounts')->nullOnDelete();
|
||||
$table->foreignId('platform_gateway_account_id')->nullable()->constrained('payment_gateway_accounts')->nullOnDelete();
|
||||
$table->string('fee_bearer', 20)->default('customer');
|
||||
$table->unsignedInteger('default_expiry_minutes')->default(1440);
|
||||
$table->json('enabled_methods')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('payment_transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('transaction_number')->unique();
|
||||
$table->foreignId('tenant_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('invoice_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('payment_gateway_account_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('purpose', 30);
|
||||
$table->string('gateway_scope', 20);
|
||||
$table->string('provider_code', 50);
|
||||
$table->string('provider_reference')->nullable()->index();
|
||||
$table->string('method', 50)->nullable();
|
||||
$table->string('currency', 3)->default('IDR');
|
||||
$table->decimal('amount', 18, 2);
|
||||
$table->decimal('provider_fee', 18, 2)->default(0);
|
||||
$table->decimal('platform_fee', 18, 2)->default(0);
|
||||
$table->decimal('total_amount', 18, 2);
|
||||
$table->string('status', 30)->default('created');
|
||||
$table->string('provider_status', 50)->nullable();
|
||||
$table->string('idempotency_key')->unique();
|
||||
$table->text('checkout_url')->nullable();
|
||||
$table->text('qr_content')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamp('paid_at')->nullable();
|
||||
$table->timestamp('failed_at')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['tenant_id', 'status', 'created_at']);
|
||||
$table->index(['user_id', 'purpose', 'status']);
|
||||
$table->index(['invoice_id', 'status']);
|
||||
});
|
||||
|
||||
Schema::create('payment_attempts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('payment_transaction_id')->constrained()->cascadeOnDelete();
|
||||
$table->unsignedSmallInteger('attempt_number');
|
||||
$table->string('action', 30);
|
||||
$table->string('status', 30);
|
||||
$table->json('request_payload')->nullable();
|
||||
$table->json('response_payload')->nullable();
|
||||
$table->string('error_code')->nullable();
|
||||
$table->text('error_message')->nullable();
|
||||
$table->unsignedInteger('duration_ms')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['payment_transaction_id', 'attempt_number', 'action'], 'payment_attempt_unique');
|
||||
});
|
||||
|
||||
Schema::create('payment_webhook_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('payment_gateway_account_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('payment_transaction_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('provider_code', 50);
|
||||
$table->string('provider_event_id')->nullable();
|
||||
$table->boolean('signature_valid')->default(false);
|
||||
$table->json('headers')->nullable();
|
||||
$table->json('payload');
|
||||
$table->string('status', 30)->default('received');
|
||||
$table->text('error_message')->nullable();
|
||||
$table->timestamp('processed_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['provider_code', 'status', 'created_at']);
|
||||
$table->unique(['payment_gateway_account_id', 'provider_event_id']);
|
||||
});
|
||||
|
||||
Schema::create('wallets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->nullableMorphs('owner');
|
||||
$table->string('wallet_type', 30);
|
||||
$table->string('currency', 3)->default('IDR');
|
||||
$table->decimal('available_balance', 18, 2)->default(0);
|
||||
$table->decimal('pending_balance', 18, 2)->default(0);
|
||||
$table->decimal('locked_balance', 18, 2)->default(0);
|
||||
$table->string('status', 20)->default('active');
|
||||
$table->unsignedBigInteger('version')->default(0);
|
||||
$table->timestamps();
|
||||
$table->unique(['owner_type', 'owner_id', 'wallet_type', 'currency'], 'wallet_owner_type_unique');
|
||||
});
|
||||
|
||||
Schema::create('wallet_transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('transaction_number')->unique();
|
||||
$table->string('type', 30);
|
||||
$table->string('status', 20)->default('pending');
|
||||
$table->foreignId('payment_transaction_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('initiated_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->decimal('amount', 18, 2);
|
||||
$table->decimal('fee', 18, 2)->default(0);
|
||||
$table->string('currency', 3)->default('IDR');
|
||||
$table->string('idempotency_key')->unique();
|
||||
$table->text('description')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['type', 'status', 'created_at']);
|
||||
});
|
||||
|
||||
Schema::create('wallet_ledger_entries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('wallet_id')->constrained()->restrictOnDelete();
|
||||
$table->foreignId('wallet_transaction_id')->constrained()->restrictOnDelete();
|
||||
$table->string('entry_type', 10);
|
||||
$table->string('balance_bucket', 20)->default('available');
|
||||
$table->decimal('amount', 18, 2);
|
||||
$table->decimal('balance_before', 18, 2);
|
||||
$table->decimal('balance_after', 18, 2);
|
||||
$table->text('description')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['wallet_id', 'wallet_transaction_id', 'entry_type', 'balance_bucket'], 'wallet_ledger_unique');
|
||||
$table->index(['wallet_id', 'created_at']);
|
||||
});
|
||||
|
||||
Schema::create('wallet_withdrawals', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->foreignId('wallet_id')->constrained()->restrictOnDelete();
|
||||
$table->foreignId('wallet_transaction_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('requested_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('approved_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->decimal('amount', 18, 2);
|
||||
$table->decimal('fee', 18, 2)->default(0);
|
||||
$table->string('bank_code', 30);
|
||||
$table->string('account_number');
|
||||
$table->string('account_name');
|
||||
$table->string('status', 30)->default('pending_approval');
|
||||
$table->string('provider_reference')->nullable();
|
||||
$table->text('rejection_reason')->nullable();
|
||||
$table->timestamp('processed_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['wallet_id', 'status', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('wallet_withdrawals');
|
||||
Schema::dropIfExists('wallet_ledger_entries');
|
||||
Schema::dropIfExists('wallet_transactions');
|
||||
Schema::dropIfExists('wallets');
|
||||
Schema::dropIfExists('payment_webhook_logs');
|
||||
Schema::dropIfExists('payment_attempts');
|
||||
Schema::dropIfExists('payment_transactions');
|
||||
Schema::dropIfExists('tenant_payment_settings');
|
||||
Schema::dropIfExists('payment_gateway_accounts');
|
||||
Schema::dropIfExists('payment_providers');
|
||||
}
|
||||
};
|
||||
@@ -17,6 +17,9 @@ class DatabaseSeeder extends Seeder
|
||||
$this->call([
|
||||
TicketTypeSeeder::class,
|
||||
MenuListSeeder::class,
|
||||
TopologySeeder::class,
|
||||
NotificationSeeder::class,
|
||||
PaymentProviderSeeder::class,
|
||||
]);
|
||||
|
||||
User::query()->updateOrCreate(
|
||||
|
||||
@@ -188,6 +188,28 @@ class MenuListSeeder extends Seeder
|
||||
);
|
||||
}
|
||||
|
||||
$paymentGateway = MenuList::updateOrCreate(
|
||||
['slug' => 'payment-gateway'],
|
||||
[
|
||||
'parent_id' => null, 'name' => 'Payment Gateway', 'url' => null,
|
||||
'icon' => 'cil-credit-card', 'component' => null, 'sort_order' => 80,
|
||||
]
|
||||
);
|
||||
foreach ([
|
||||
['slug' => 'payment-gateway-settings', 'name' => 'Setting', 'url' => '/payment-gateway/settings', 'sort_order' => 1],
|
||||
['slug' => 'payment-gateway-logs', 'name' => 'Log Pembayaran', 'url' => '/payment-gateway/logs', 'sort_order' => 2],
|
||||
] as $item) {
|
||||
MenuList::updateOrCreate(['slug' => $item['slug']], [
|
||||
'parent_id' => $paymentGateway->id, 'name' => $item['name'], 'url' => $item['url'],
|
||||
'icon' => 'cil-credit-card', 'component' => null, 'sort_order' => $item['sort_order'],
|
||||
]);
|
||||
}
|
||||
|
||||
MenuList::updateOrCreate(['slug' => 'wallet'], [
|
||||
'parent_id' => null, 'name' => 'Dompet', 'url' => '/wallet',
|
||||
'icon' => 'cil-wallet', 'component' => null, 'sort_order' => 81,
|
||||
]);
|
||||
|
||||
$nas = MenuList::updateOrCreate(
|
||||
['slug' => 'nas'],
|
||||
[
|
||||
@@ -220,5 +242,141 @@ class MenuListSeeder extends Seeder
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$customers = MenuList::updateOrCreate(
|
||||
['slug' => 'customers'],
|
||||
[
|
||||
'parent_id' => null,
|
||||
'name' => 'Customer',
|
||||
'url' => null,
|
||||
'icon' => 'cil-people',
|
||||
'component' => null,
|
||||
'sort_order' => 6,
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
foreach ([
|
||||
['slug' => 'customers-orders', 'name' => 'List Order', 'url' => '/customers/orders', 'sort_order' => 1],
|
||||
['slug' => 'customers-active', 'name' => 'Aktif', 'url' => '/customers/active', 'sort_order' => 2],
|
||||
['slug' => 'customers-inactive', 'name' => 'Tidak Aktif', 'url' => '/customers/inactive', 'sort_order' => 3],
|
||||
['slug' => 'customers-unmanaged', 'name' => 'Unmanage', 'url' => '/customers/unmanaged', 'sort_order' => 4],
|
||||
['slug' => 'customers-trash', 'name' => 'Sampah', 'url' => '/customers/trash', 'sort_order' => 5],
|
||||
] as $item) {
|
||||
MenuList::updateOrCreate(
|
||||
['slug' => $item['slug']],
|
||||
[
|
||||
'parent_id' => $customers->id,
|
||||
'name' => $item['name'],
|
||||
'url' => $item['url'],
|
||||
'icon' => 'cil-user',
|
||||
'component' => null,
|
||||
'sort_order' => $item['sort_order'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$billing = MenuList::updateOrCreate(
|
||||
['slug' => 'billing'],
|
||||
[
|
||||
'parent_id' => null,
|
||||
'name' => 'Tagihan',
|
||||
'url' => null,
|
||||
'icon' => 'cil-description',
|
||||
'component' => null,
|
||||
'sort_order' => 7,
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
foreach ([
|
||||
['slug' => 'billing-profiles', 'name' => 'Profile Tagihan', 'url' => '/billing/profiles', 'sort_order' => 1],
|
||||
['slug' => 'billing-running', 'name' => 'Tagihan Berjalan', 'url' => '/billing/running', 'sort_order' => 2],
|
||||
['slug' => 'billing-overdue', 'name' => 'Tunggakan', 'url' => '/billing/overdue', 'sort_order' => 3],
|
||||
['slug' => 'billing-paid', 'name' => 'Lunas', 'url' => '/billing/paid', 'sort_order' => 4],
|
||||
] as $item) {
|
||||
MenuList::updateOrCreate(
|
||||
['slug' => $item['slug']],
|
||||
[
|
||||
'parent_id' => $billing->id,
|
||||
'name' => $item['name'],
|
||||
'url' => $item['url'],
|
||||
'icon' => 'cil-dollar',
|
||||
'component' => null,
|
||||
'sort_order' => $item['sort_order'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$topology = MenuList::updateOrCreate(
|
||||
['slug' => 'topology'],
|
||||
[
|
||||
'parent_id' => null,
|
||||
'name' => 'Topologi',
|
||||
'url' => null,
|
||||
'icon' => 'cil-layers',
|
||||
'component' => null,
|
||||
'sort_order' => 8,
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
foreach ([
|
||||
['slug' => 'topology-map', 'name' => 'Peta Jaringan', 'url' => '/topology/map', 'icon' => 'cil-location-pin', 'sort_order' => 1],
|
||||
['slug' => 'topology-devices', 'name' => 'Perangkat', 'url' => '/topology/devices', 'icon' => 'cil-settings', 'sort_order' => 2],
|
||||
['slug' => 'topology-links', 'name' => 'Jalur Kabel', 'url' => '/topology/links', 'icon' => 'cil-layers', 'sort_order' => 3],
|
||||
['slug' => 'topology-network', 'name' => 'Topologi Jaringan', 'url' => '/topology/network', 'icon' => 'cil-grid', 'sort_order' => 4],
|
||||
['slug' => 'topology-device-types', 'name' => 'Kategori Perangkat', 'url' => '/topology/device-types', 'icon' => 'cil-list', 'sort_order' => 5],
|
||||
] as $item) {
|
||||
MenuList::updateOrCreate(
|
||||
['slug' => $item['slug']],
|
||||
[
|
||||
'parent_id' => $topology->id,
|
||||
'name' => $item['name'],
|
||||
'url' => $item['url'],
|
||||
'icon' => $item['icon'],
|
||||
'component' => null,
|
||||
'sort_order' => $item['sort_order'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$notifications = MenuList::updateOrCreate(
|
||||
['slug' => 'notifications-service'],
|
||||
[
|
||||
'parent_id' => null,
|
||||
'name' => 'Notifikasi',
|
||||
'url' => null,
|
||||
'icon' => 'cil-bell',
|
||||
'component' => null,
|
||||
'sort_order' => 9,
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
foreach ([
|
||||
['slug' => 'notifications-broadcasts', 'name' => 'Pesan Siaran', 'url' => '/notifications-service/broadcasts', 'icon' => 'cil-speech', 'sort_order' => 1],
|
||||
['slug' => 'notifications-system', 'name' => 'Notifikasi Aplikasi', 'url' => '/notifications-service/system', 'icon' => 'cil-bell', 'sort_order' => 2],
|
||||
['slug' => 'notifications-wa-official', 'name' => 'WhatsApp Official', 'url' => '/notifications-service/whatsapp-official', 'icon' => 'cil-comment-square', 'sort_order' => 3],
|
||||
['slug' => 'notifications-wa-unofficial', 'name' => 'WhatsApp Unofficial', 'url' => '/notifications-service/whatsapp-unofficial', 'icon' => 'cil-comment-square', 'sort_order' => 4],
|
||||
['slug' => 'notifications-email', 'name' => 'Email', 'url' => '/notifications-service/email', 'icon' => 'cil-envelope-open', 'sort_order' => 5],
|
||||
['slug' => 'notifications-telegram', 'name' => 'Telegram', 'url' => '/notifications-service/telegram', 'icon' => 'cil-arrow-right', 'sort_order' => 6],
|
||||
] as $item) {
|
||||
MenuList::updateOrCreate(
|
||||
['slug' => $item['slug']],
|
||||
[
|
||||
'parent_id' => $notifications->id,
|
||||
'name' => $item['name'],
|
||||
'url' => $item['url'],
|
||||
'icon' => $item['icon'],
|
||||
'component' => null,
|
||||
'sort_order' => $item['sort_order'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\NotificationEvent;
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class NotificationSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$events = [
|
||||
'customer.registration' => ['Registrasi Customer', 'customer', 'Halo {{customer.name}}, registrasi Anda di {{tenant.name}} berhasil diterima.'],
|
||||
'customer.activation' => ['Aktivasi Customer', 'customer', 'Halo {{customer.name}}, layanan Anda telah aktif. ID pelanggan: {{customer.customer_code}}.'],
|
||||
'billing.invoice_sent' => ['Kirim Tagihan', 'billing', 'Tagihan {{invoice.invoice_number}} sebesar {{invoice.total}} telah diterbitkan. Batas isolir: {{invoice.isolation_date}}.'],
|
||||
'billing.warning' => ['Peringatan Tagihan', 'billing', 'Pengingat: tagihan {{invoice.invoice_number}} sebesar {{invoice.balance}} belum dibayar.'],
|
||||
'billing.isolated' => ['Isolir Pelanggan', 'billing', 'Layanan {{customer.customer_code}} memasuki masa isolir karena tagihan belum dibayar.'],
|
||||
'voucher_agent.registration' => ['Registrasi Agen Voucher', 'voucher', 'Registrasi agen voucher {{agent.name}} berhasil.'],
|
||||
'voucher_agent.balance_added' => ['Tambah Saldo Agen Voucher', 'voucher', 'Saldo agen {{agent.name}} bertambah {{balance.amount}}. Saldo saat ini {{balance.current}}.'],
|
||||
];
|
||||
$channels = ['system', 'whatsapp_official', 'whatsapp_unofficial', 'email', 'telegram'];
|
||||
|
||||
foreach ($events as $code => [$name, $category, $body]) {
|
||||
$event = NotificationEvent::updateOrCreate(
|
||||
['code' => $code],
|
||||
[
|
||||
'name' => $name,
|
||||
'category' => $category,
|
||||
'description' => "Pesan otomatis untuk {$name}",
|
||||
'available_channels' => $channels,
|
||||
'variables' => $this->variables($body),
|
||||
'is_system' => true,
|
||||
'enabled' => true,
|
||||
],
|
||||
);
|
||||
foreach ($channels as $channel) {
|
||||
NotificationTemplate::updateOrCreate(
|
||||
[
|
||||
'tenant_id' => null,
|
||||
'notification_event_id' => $event->id,
|
||||
'channel' => $channel,
|
||||
'language' => 'id',
|
||||
'version' => 1,
|
||||
],
|
||||
[
|
||||
'name' => "{$name} - {$this->channelName($channel)}",
|
||||
'subject' => $channel === 'email' || $channel === 'system' ? $name : null,
|
||||
'body' => $body,
|
||||
'approval_status' => $channel === 'whatsapp_official' ? 'pending' : 'not_required',
|
||||
'enabled' => true,
|
||||
'variables' => $this->variables($body),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function variables(string $body): array
|
||||
{
|
||||
preg_match_all('/{{\s*([a-zA-Z0-9_.]+)\s*}}/', $body, $matches);
|
||||
|
||||
return array_values(array_unique($matches[1]));
|
||||
}
|
||||
|
||||
private function channelName(string $channel): string
|
||||
{
|
||||
return match ($channel) {
|
||||
'system' => 'Aplikasi',
|
||||
'whatsapp_official' => 'WhatsApp Official',
|
||||
'whatsapp_unofficial' => 'WhatsApp Unofficial',
|
||||
'email' => 'Email',
|
||||
'telegram' => 'Telegram',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\PaymentProvider;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class PaymentProviderSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
foreach ([
|
||||
['paydisini', 'Paydisini', 'tenant', ['api_key']],
|
||||
['winpay', 'Winpay', 'tenant', ['merchant_id', 'api_key', 'private_key']],
|
||||
['doku', 'DOKU', 'both', ['client_id', 'secret_key']],
|
||||
['flip_business', 'Flip Business', 'tenant', ['token', 'validation_token']],
|
||||
['duitku', 'Duitku', 'both', ['merchant_code', 'api_key']],
|
||||
['midtrans', 'Midtrans', 'platform', ['merchant_id', 'client_key', 'server_key']],
|
||||
] as [$code, $name, $scope, $credentials]) {
|
||||
PaymentProvider::updateOrCreate(['code' => $code], [
|
||||
'name' => $name, 'scope' => $scope, 'enabled' => true,
|
||||
'capabilities' => ['payment' => true, 'inquiry' => true, 'webhook' => true, 'refund' => in_array($code, ['doku', 'midtrans', 'duitku'])],
|
||||
'credential_schema' => collect($credentials)->map(fn ($key) => ['key' => $key, 'label' => str($key)->replace('_', ' ')->title(), 'secret' => ! str_contains($key, 'merchant_id') && ! str_contains($key, 'client_id')])->values(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\TopologyConnectionRule;
|
||||
use App\Models\TopologyDeviceType;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class TopologySeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$definitions = [
|
||||
['code' => 'POP', 'name' => 'Point of Presence', 'category' => 'site', 'color' => '#dc3545'],
|
||||
['code' => 'OTB', 'name' => 'Optical Termination Box', 'category' => 'termination', 'color' => '#fd7e14'],
|
||||
['code' => 'JB', 'name' => 'Joint Box', 'category' => 'closure', 'color' => '#6f42c1'],
|
||||
['code' => 'ODC', 'name' => 'Optical Distribution Cabinet', 'category' => 'distribution', 'color' => '#0d6efd'],
|
||||
['code' => 'ODP', 'name' => 'Optical Distribution Point', 'category' => 'distribution', 'color' => '#198754'],
|
||||
['code' => 'SPLITTER', 'name' => 'Splitter / Ratio', 'category' => 'splitter', 'color' => '#20c997', 'can_have_splitters' => true],
|
||||
];
|
||||
|
||||
$types = collect($definitions)->mapWithKeys(function ($definition) {
|
||||
$type = TopologyDeviceType::updateOrCreate(
|
||||
['tenant_id' => null, 'code' => $definition['code']],
|
||||
[
|
||||
...$definition,
|
||||
'tenant_id' => null,
|
||||
'can_have_ports' => true,
|
||||
'can_have_splitters' => $definition['can_have_splitters'] ?? false,
|
||||
'is_system' => true,
|
||||
'icon' => 'cil-location-pin',
|
||||
'status' => 'active',
|
||||
],
|
||||
);
|
||||
|
||||
return [$type->code => $type];
|
||||
});
|
||||
|
||||
foreach ([
|
||||
['POP', 'POP'], ['POP', 'OTB'], ['OTB', 'JB'], ['OTB', 'ODC'],
|
||||
['OTB', 'SPLITTER'], ['JB', 'ODC'], ['JB', 'ODP'], ['ODC', 'ODP'],
|
||||
['ODC', 'SPLITTER'], ['SPLITTER', 'ODP'],
|
||||
] as [$source, $target]) {
|
||||
TopologyConnectionRule::updateOrCreate(
|
||||
[
|
||||
'tenant_id' => null,
|
||||
'source_device_type_id' => $types[$source]->id,
|
||||
'target_device_type_id' => $types[$target]->id,
|
||||
],
|
||||
['allowed' => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user