Initial commit
This commit is contained in:
32
database/migrations/2014_10_12_000000_create_users_table.php
Normal file
32
database/migrations/2014_10_12_000000_create_users_table.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('document_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name'); // 協會辦法, 法規, 會議記錄, 表格
|
||||
$table->string('slug')->unique();
|
||||
$table->text('description')->nullable();
|
||||
$table->string('icon')->nullable(); // emoji or FontAwesome class
|
||||
$table->integer('sort_order')->default(0);
|
||||
|
||||
// Default access level for documents in this category
|
||||
$table->enum('default_access_level', ['public', 'members', 'admin', 'board'])->default('members');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_categories');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('documents', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('document_category_id')->constrained()->onDelete('cascade');
|
||||
|
||||
// Document metadata
|
||||
$table->string('title');
|
||||
$table->string('document_number')->unique()->nullable(); // e.g., BYL-2024-001
|
||||
$table->text('description')->nullable();
|
||||
$table->uuid('public_uuid')->unique(); // For public sharing links
|
||||
|
||||
// Access control
|
||||
$table->enum('access_level', ['public', 'members', 'admin', 'board'])->default('members');
|
||||
|
||||
// Current version pointer (set after first version is created)
|
||||
$table->foreignId('current_version_id')->nullable()->constrained('document_versions')->onDelete('set null');
|
||||
|
||||
// Status
|
||||
$table->enum('status', ['active', 'archived'])->default('active');
|
||||
$table->timestamp('archived_at')->nullable();
|
||||
|
||||
// User tracking
|
||||
$table->foreignId('created_by_user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->foreignId('last_updated_by_user_id')->nullable()->constrained('users')->onDelete('set null');
|
||||
|
||||
// Statistics
|
||||
$table->integer('view_count')->default(0);
|
||||
$table->integer('download_count')->default(0);
|
||||
$table->integer('version_count')->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// Indexes
|
||||
$table->index('document_category_id');
|
||||
$table->index('access_level');
|
||||
$table->index('status');
|
||||
$table->index('public_uuid');
|
||||
$table->index('created_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('documents');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('document_versions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('document_id')->constrained()->onDelete('cascade');
|
||||
|
||||
// Version information
|
||||
$table->string('version_number'); // 1.0, 1.1, 2.0, etc.
|
||||
$table->text('version_notes')->nullable(); // What changed in this version
|
||||
$table->boolean('is_current')->default(false); // Is this the current published version
|
||||
|
||||
// File information
|
||||
$table->string('file_path'); // storage/documents/...
|
||||
$table->string('original_filename');
|
||||
$table->string('mime_type');
|
||||
$table->unsignedBigInteger('file_size'); // in bytes
|
||||
$table->string('file_hash')->nullable(); // SHA-256 hash for integrity verification
|
||||
|
||||
// User tracking
|
||||
$table->foreignId('uploaded_by_user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->timestamp('uploaded_at');
|
||||
|
||||
// Make version immutable after creation (no updated_at)
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
$table->index('document_id');
|
||||
$table->index('version_number');
|
||||
$table->index('is_current');
|
||||
$table->index('uploaded_at');
|
||||
|
||||
// Unique constraint: only one current version per document
|
||||
$table->unique(['document_id', 'is_current'], 'unique_current_version');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_versions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('document_access_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('document_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('document_version_id')->nullable()->constrained()->onDelete('set null');
|
||||
|
||||
// Access information
|
||||
$table->enum('action', ['view', 'download']); // What action was performed
|
||||
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null'); // null if anonymous/public access
|
||||
$table->string('ip_address')->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
|
||||
// Timestamps
|
||||
$table->timestamp('accessed_at');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
$table->index('document_id');
|
||||
$table->index('user_id');
|
||||
$table->index('action');
|
||||
$table->index('accessed_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_access_logs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('members', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('full_name');
|
||||
$table->string('email')->index();
|
||||
$table->string('phone')->nullable();
|
||||
$table->string('national_id_encrypted')->nullable();
|
||||
$table->string('national_id_hash')->nullable()->index();
|
||||
$table->date('membership_started_at')->nullable();
|
||||
$table->date('membership_expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('members');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('membership_payments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('member_id')->constrained()->cascadeOnDelete();
|
||||
$table->date('paid_at');
|
||||
$table->decimal('amount', 10, 2);
|
||||
$table->string('method')->nullable();
|
||||
$table->string('reference')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('membership_payments');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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('users', function (Blueprint $table) {
|
||||
$table->boolean('is_admin')->default(false)->after('email');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('is_admin');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // permission id
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
|
||||
Schema::drop($tableNames['role_has_permissions']);
|
||||
Schema::drop($tableNames['model_has_roles']);
|
||||
Schema::drop($tableNames['model_has_permissions']);
|
||||
Schema::drop($tableNames['roles']);
|
||||
Schema::drop($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
if (! class_exists(\Spatie\Permission\Models\Role::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$roleClass = \Spatie\Permission\Models\Role::class;
|
||||
|
||||
$adminRole = $roleClass::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']);
|
||||
|
||||
User::where('is_admin', true)->each(function (User $user) use ($adminRole) {
|
||||
$user->assignRole($adminRole);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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('members', function (Blueprint $table) {
|
||||
$table->timestamp('last_expiry_reminder_sent_at')->nullable()->after('membership_expires_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->dropColumn('last_expiry_reminder_sent_at');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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('audit_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('action');
|
||||
$table->string('auditable_type')->nullable();
|
||||
$table->unsignedBigInteger('auditable_id')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('audit_logs');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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('finance_documents', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('member_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('submitted_by_user_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('title');
|
||||
$table->decimal('amount', 10, 2)->nullable();
|
||||
$table->string('status')->default('pending');
|
||||
$table->text('description')->nullable();
|
||||
$table->timestamp('submitted_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('finance_documents');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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('members', function (Blueprint $table) {
|
||||
$table->string('address_line_1')->nullable()->after('phone');
|
||||
$table->string('address_line_2')->nullable()->after('address_line_1');
|
||||
$table->string('city')->nullable()->after('address_line_2');
|
||||
$table->string('postal_code')->nullable()->after('city');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->dropColumn(['address_line_1', 'address_line_2', 'city', 'postal_code']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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('roles', function (Blueprint $table) {
|
||||
$table->string('description')->nullable()->after('guard_name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropColumn('description');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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('members', function (Blueprint $table) {
|
||||
$table->string('emergency_contact_name')->nullable()->after('postal_code');
|
||||
$table->string('emergency_contact_phone')->nullable()->after('emergency_contact_name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->dropColumn(['emergency_contact_name', 'emergency_contact_phone']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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('users', function (Blueprint $table) {
|
||||
$table->string('profile_photo_path')->nullable()->after('is_admin');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('profile_photo_path');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
// File attachment
|
||||
$table->string('attachment_path')->nullable()->after('description');
|
||||
|
||||
// Cashier approval
|
||||
$table->foreignId('approved_by_cashier_id')->nullable()->constrained('users')->nullOnDelete()->after('attachment_path');
|
||||
$table->timestamp('cashier_approved_at')->nullable()->after('approved_by_cashier_id');
|
||||
|
||||
// Accountant approval
|
||||
$table->foreignId('approved_by_accountant_id')->nullable()->constrained('users')->nullOnDelete()->after('cashier_approved_at');
|
||||
$table->timestamp('accountant_approved_at')->nullable()->after('approved_by_accountant_id');
|
||||
|
||||
// Chair approval
|
||||
$table->foreignId('approved_by_chair_id')->nullable()->constrained('users')->nullOnDelete()->after('accountant_approved_at');
|
||||
$table->timestamp('chair_approved_at')->nullable()->after('approved_by_chair_id');
|
||||
|
||||
// Rejection fields
|
||||
$table->foreignId('rejected_by_user_id')->nullable()->constrained('users')->nullOnDelete()->after('chair_approved_at');
|
||||
$table->timestamp('rejected_at')->nullable()->after('rejected_by_user_id');
|
||||
$table->text('rejection_reason')->nullable()->after('rejected_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
$table->dropForeign(['approved_by_cashier_id']);
|
||||
$table->dropForeign(['approved_by_accountant_id']);
|
||||
$table->dropForeign(['approved_by_chair_id']);
|
||||
$table->dropForeign(['rejected_by_user_id']);
|
||||
|
||||
$table->dropColumn([
|
||||
'attachment_path',
|
||||
'approved_by_cashier_id',
|
||||
'cashier_approved_at',
|
||||
'approved_by_accountant_id',
|
||||
'accountant_approved_at',
|
||||
'approved_by_chair_id',
|
||||
'chair_approved_at',
|
||||
'rejected_by_user_id',
|
||||
'rejected_at',
|
||||
'rejection_reason',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('chart_of_accounts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('account_code', 10)->unique()->comment('Account code (e.g., 4101)');
|
||||
$table->string('account_name_zh')->comment('Chinese account name');
|
||||
$table->string('account_name_en')->nullable()->comment('English account name');
|
||||
$table->enum('account_type', ['asset', 'liability', 'net_asset', 'income', 'expense'])->comment('Account type');
|
||||
$table->string('category')->nullable()->comment('Detailed category');
|
||||
$table->foreignId('parent_account_id')->nullable()->constrained('chart_of_accounts')->nullOnDelete()->comment('Parent account for hierarchical structure');
|
||||
$table->boolean('is_active')->default(true)->comment('Active status');
|
||||
$table->integer('display_order')->default(0)->comment('Display order');
|
||||
$table->text('description')->nullable()->comment('Account description');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('account_type');
|
||||
$table->index('is_active');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('chart_of_accounts');
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('budget_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('budget_id')->constrained()->cascadeOnDelete()->comment('Budget reference');
|
||||
$table->foreignId('chart_of_account_id')->constrained()->cascadeOnDelete()->comment('Chart of account reference');
|
||||
$table->decimal('budgeted_amount', 15, 2)->default(0)->comment('Budgeted amount');
|
||||
$table->decimal('actual_amount', 15, 2)->default(0)->comment('Actual amount (calculated)');
|
||||
$table->text('notes')->nullable()->comment('Item notes');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['budget_id', 'chart_of_account_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('budget_items');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('budgets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('fiscal_year')->comment('Fiscal year (e.g., 2025)');
|
||||
$table->string('name')->comment('Budget name');
|
||||
$table->enum('period_type', ['annual', 'quarterly', 'monthly'])->default('annual')->comment('Budget period type');
|
||||
$table->date('period_start')->comment('Period start date');
|
||||
$table->date('period_end')->comment('Period end date');
|
||||
$table->enum('status', ['draft', 'submitted', 'approved', 'active', 'closed'])->default('draft')->comment('Budget status');
|
||||
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete()->comment('Created by user');
|
||||
$table->foreignId('approved_by_user_id')->nullable()->constrained('users')->nullOnDelete()->comment('Approved by user');
|
||||
$table->timestamp('approved_at')->nullable()->comment('Approval timestamp');
|
||||
$table->text('notes')->nullable()->comment('Budget notes');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('fiscal_year');
|
||||
$table->index('status');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('budgets');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('transactions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('budget_item_id')->nullable()->constrained()->nullOnDelete()->comment('Budget item reference');
|
||||
$table->foreignId('chart_of_account_id')->constrained()->cascadeOnDelete()->comment('Chart of account reference');
|
||||
$table->date('transaction_date')->comment('Transaction date');
|
||||
$table->decimal('amount', 15, 2)->comment('Transaction amount');
|
||||
$table->enum('transaction_type', ['income', 'expense'])->comment('Transaction type');
|
||||
$table->string('description')->comment('Transaction description');
|
||||
$table->string('reference_number')->nullable()->comment('Reference/receipt number');
|
||||
$table->foreignId('finance_document_id')->nullable()->constrained()->nullOnDelete()->comment('Related finance document');
|
||||
$table->foreignId('membership_payment_id')->nullable()->constrained()->nullOnDelete()->comment('Related membership payment');
|
||||
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete()->comment('Created by user');
|
||||
$table->text('notes')->nullable()->comment('Additional notes');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('transaction_date');
|
||||
$table->index('transaction_type');
|
||||
$table->index(['budget_item_id', 'transaction_date']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('transactions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('financial_reports', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->enum('report_type', ['revenue_expenditure', 'balance_sheet', 'property_inventory', 'internal_management'])->comment('Report type');
|
||||
$table->integer('fiscal_year')->comment('Fiscal year');
|
||||
$table->date('period_start')->comment('Period start date');
|
||||
$table->date('period_end')->comment('Period end date');
|
||||
$table->enum('status', ['draft', 'finalized', 'approved', 'submitted'])->default('draft')->comment('Report status');
|
||||
$table->foreignId('budget_id')->nullable()->constrained()->nullOnDelete()->comment('Related budget');
|
||||
$table->foreignId('generated_by_user_id')->constrained('users')->cascadeOnDelete()->comment('Generated by user');
|
||||
$table->foreignId('approved_by_user_id')->nullable()->constrained('users')->nullOnDelete()->comment('Approved by user');
|
||||
$table->timestamp('approved_at')->nullable()->comment('Approval timestamp');
|
||||
$table->string('file_path')->nullable()->comment('PDF/Excel file path');
|
||||
$table->text('notes')->nullable()->comment('Report notes');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['report_type', 'fiscal_year']);
|
||||
$table->index('status');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('financial_reports');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issues', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('issue_number')->unique()->comment('Auto-generated issue number (e.g., ISS-2025-001)');
|
||||
$table->string('title');
|
||||
$table->text('description')->nullable();
|
||||
|
||||
// Issue categorization
|
||||
$table->enum('issue_type', ['work_item', 'project_task', 'maintenance', 'member_request'])
|
||||
->default('work_item')
|
||||
->comment('Type of issue');
|
||||
$table->enum('status', ['new', 'assigned', 'in_progress', 'review', 'closed'])
|
||||
->default('new')
|
||||
->comment('Current workflow status');
|
||||
$table->enum('priority', ['low', 'medium', 'high', 'urgent'])
|
||||
->default('medium')
|
||||
->comment('Priority level');
|
||||
|
||||
// User relationships
|
||||
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete()->comment('User who created the issue');
|
||||
$table->foreignId('assigned_to_user_id')->nullable()->constrained('users')->nullOnDelete()->comment('User assigned to work on this');
|
||||
$table->foreignId('reviewer_id')->nullable()->constrained('users')->nullOnDelete()->comment('User assigned to review');
|
||||
|
||||
// Related entities
|
||||
$table->foreignId('member_id')->nullable()->constrained('members')->nullOnDelete()->comment('Related member (for member requests)');
|
||||
$table->foreignId('parent_issue_id')->nullable()->constrained('issues')->nullOnDelete()->comment('Parent issue for sub-tasks');
|
||||
|
||||
// Dates and time tracking
|
||||
$table->date('due_date')->nullable()->comment('Deadline for completion');
|
||||
$table->timestamp('closed_at')->nullable()->comment('When issue was closed');
|
||||
$table->decimal('estimated_hours', 8, 2)->nullable()->comment('Estimated time to complete');
|
||||
$table->decimal('actual_hours', 8, 2)->default(0)->comment('Actual time spent (sum of time logs)');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// Indexes for common queries
|
||||
$table->index('issue_type');
|
||||
$table->index('status');
|
||||
$table->index('priority');
|
||||
$table->index('assigned_to_user_id');
|
||||
$table->index('created_by_user_id');
|
||||
$table->index('due_date');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issues');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issue_comments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->text('comment_text');
|
||||
$table->boolean('is_internal')->default(false)->comment('Hide from members if true');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
$table->index('issue_id');
|
||||
$table->index('user_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issue_comments');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issue_attachments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete()->comment('User who uploaded');
|
||||
$table->string('file_name');
|
||||
$table->string('file_path');
|
||||
$table->unsignedBigInteger('file_size')->comment('File size in bytes');
|
||||
$table->string('mime_type');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
$table->index('issue_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issue_attachments');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('custom_field_values', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('custom_field_id')->constrained('custom_fields')->cascadeOnDelete();
|
||||
$table->morphs('customizable'); // customizable_type and customizable_id (for issues)
|
||||
$table->json('value')->comment('Stored value (JSON for flexibility)');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
$table->index('custom_field_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('custom_field_values');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('custom_fields', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique();
|
||||
$table->enum('field_type', ['text', 'number', 'date', 'select'])->comment('Data type');
|
||||
$table->json('options')->nullable()->comment('Options for select type fields');
|
||||
$table->json('applies_to_issue_types')->comment('Which issue types can use this field');
|
||||
$table->boolean('is_required')->default(false);
|
||||
$table->integer('display_order')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('custom_fields');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issue_label_pivot', function (Blueprint $table) {
|
||||
$table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete();
|
||||
$table->foreignId('issue_label_id')->constrained('issue_labels')->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
// Composite primary key
|
||||
$table->primary(['issue_id', 'issue_label_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issue_label_pivot');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issue_labels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique();
|
||||
$table->string('color', 7)->default('#6B7280')->comment('Hex color code');
|
||||
$table->text('description')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issue_labels');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issue_relationships', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete();
|
||||
$table->foreignId('related_issue_id')->constrained('issues')->cascadeOnDelete();
|
||||
$table->enum('relationship_type', ['blocks', 'blocked_by', 'related_to', 'duplicate_of'])
|
||||
->comment('Type of relationship');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
$table->index('issue_id');
|
||||
$table->index('related_issue_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issue_relationships');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issue_time_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->decimal('hours', 8, 2)->comment('Hours worked');
|
||||
$table->text('description')->nullable()->comment('What was done');
|
||||
$table->timestamp('logged_at')->comment('When the work was performed');
|
||||
$table->timestamps();
|
||||
|
||||
// Indexes
|
||||
$table->index('issue_id');
|
||||
$table->index('user_id');
|
||||
$table->index('logged_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issue_time_logs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('issue_watchers', function (Blueprint $table) {
|
||||
$table->foreignId('issue_id')->constrained('issues')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
// Composite primary key to prevent duplicate watchers
|
||||
$table->primary(['issue_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('issue_watchers');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('membership_payments', function (Blueprint $table) {
|
||||
// Payment verification workflow status
|
||||
$table->enum('status', ['pending', 'approved_cashier', 'approved_accountant', 'approved_chair', 'rejected'])
|
||||
->default('pending')
|
||||
->after('reference');
|
||||
|
||||
// Payment method
|
||||
$table->enum('payment_method', ['bank_transfer', 'convenience_store', 'cash', 'credit_card'])
|
||||
->nullable()
|
||||
->after('status');
|
||||
|
||||
// Receipt file upload
|
||||
$table->string('receipt_path')->nullable()->after('payment_method');
|
||||
|
||||
// Submitted by (member self-submission)
|
||||
$table->foreignId('submitted_by_user_id')->nullable()->after('receipt_path')
|
||||
->constrained('users')->nullOnDelete();
|
||||
|
||||
// Cashier verification (Tier 1)
|
||||
$table->foreignId('verified_by_cashier_id')->nullable()->after('submitted_by_user_id')
|
||||
->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('cashier_verified_at')->nullable()->after('verified_by_cashier_id');
|
||||
|
||||
// Accountant verification (Tier 2)
|
||||
$table->foreignId('verified_by_accountant_id')->nullable()->after('cashier_verified_at')
|
||||
->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('accountant_verified_at')->nullable()->after('verified_by_accountant_id');
|
||||
|
||||
// Chair verification (Tier 3)
|
||||
$table->foreignId('verified_by_chair_id')->nullable()->after('accountant_verified_at')
|
||||
->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('chair_verified_at')->nullable()->after('verified_by_chair_id');
|
||||
|
||||
// Rejection tracking
|
||||
$table->foreignId('rejected_by_user_id')->nullable()->after('chair_verified_at')
|
||||
->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('rejected_at')->nullable()->after('rejected_by_user_id');
|
||||
$table->text('rejection_reason')->nullable()->after('rejected_at');
|
||||
|
||||
// Admin notes
|
||||
$table->text('notes')->nullable()->after('rejection_reason');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('membership_payments', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'status',
|
||||
'payment_method',
|
||||
'receipt_path',
|
||||
'submitted_by_user_id',
|
||||
'verified_by_cashier_id',
|
||||
'cashier_verified_at',
|
||||
'verified_by_accountant_id',
|
||||
'accountant_verified_at',
|
||||
'verified_by_chair_id',
|
||||
'chair_verified_at',
|
||||
'rejected_by_user_id',
|
||||
'rejected_at',
|
||||
'rejection_reason',
|
||||
'notes',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
// Membership status - distinguishes paid vs unpaid members
|
||||
$table->enum('membership_status', ['pending', 'active', 'expired', 'suspended'])
|
||||
->default('pending')
|
||||
->after('membership_expires_at')
|
||||
->comment('Payment verification status: pending (not paid), active (paid & activated), expired, suspended');
|
||||
|
||||
// Membership type - for different membership tiers
|
||||
$table->enum('membership_type', ['regular', 'honorary', 'lifetime', 'student'])
|
||||
->default('regular')
|
||||
->after('membership_status')
|
||||
->comment('Type of membership: regular (annual fee), honorary (no fee), lifetime (one-time), student (discounted)');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('members', function (Blueprint $table) {
|
||||
$table->dropColumn(['membership_status', 'membership_type']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* The unique constraint on (document_id, is_current) was too restrictive.
|
||||
* It prevented having multiple versions with is_current = false.
|
||||
* We only need to ensure ONE version has is_current = true, which is
|
||||
* enforced in the application logic.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('document_versions', function (Blueprint $table) {
|
||||
$table->dropUnique('unique_current_version');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('document_versions', function (Blueprint $table) {
|
||||
$table->unique(['document_id', 'is_current'], 'unique_current_version');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Create tags table
|
||||
Schema::create('document_tags', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->string('color')->default('#6366f1'); // Indigo color
|
||||
$table->text('description')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Create pivot table for document-tag relationship
|
||||
Schema::create('document_document_tag', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('document_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('document_tag_id')->constrained()->onDelete('cascade');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['document_id', 'document_tag_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_document_tag');
|
||||
Schema::dropIfExists('document_tags');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('documents', function (Blueprint $table) {
|
||||
$table->date('expires_at')->nullable()->after('status');
|
||||
$table->boolean('auto_archive_on_expiry')->default(false)->after('expires_at');
|
||||
$table->text('expiry_notice')->nullable()->after('auto_archive_on_expiry');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('documents', function (Blueprint $table) {
|
||||
$table->dropColumn(['expires_at', 'auto_archive_on_expiry', 'expiry_notice']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('system_settings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('key')->unique()->comment('Setting key (e.g., documents.qr_enabled)');
|
||||
$table->text('value')->nullable()->comment('Setting value (JSON for complex values)');
|
||||
$table->enum('type', ['string', 'integer', 'boolean', 'json', 'array'])->default('string')->comment('Value type for casting');
|
||||
$table->string('group')->nullable()->index()->comment('Settings group (e.g., documents, security, notifications)');
|
||||
$table->text('description')->nullable()->comment('Human-readable description of this setting');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('system_settings');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
// 申請類型和金額分級
|
||||
$table->enum('request_type', ['expense_reimbursement', 'advance_payment', 'purchase_request', 'petty_cash'])
|
||||
->default('expense_reimbursement')
|
||||
->after('status')
|
||||
->comment('申請類型:費用報銷/預支借款/採購申請/零用金');
|
||||
|
||||
$table->enum('amount_tier', ['small', 'medium', 'large'])
|
||||
->nullable()
|
||||
->after('request_type')
|
||||
->comment('金額層級:小額(<5000)/中額(5000-50000)/大額(>50000)');
|
||||
|
||||
// 會計科目分配(會計審核時填寫)
|
||||
$table->foreignId('chart_of_account_id')->nullable()->after('amount_tier')->constrained('chart_of_accounts')->nullOnDelete();
|
||||
$table->foreignId('budget_item_id')->nullable()->after('chart_of_account_id')->constrained('budget_items')->nullOnDelete();
|
||||
|
||||
// 理監事會議核准(大額)
|
||||
$table->boolean('requires_board_meeting')->default(false)->after('chair_approved_at');
|
||||
$table->date('board_meeting_date')->nullable()->after('requires_board_meeting');
|
||||
$table->text('board_meeting_decision')->nullable()->after('board_meeting_date');
|
||||
$table->foreignId('approved_by_board_meeting_id')->nullable()->after('board_meeting_decision')->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('board_meeting_approved_at')->nullable()->after('approved_by_board_meeting_id');
|
||||
|
||||
// 付款單製作(會計)
|
||||
$table->foreignId('payment_order_created_by_accountant_id')->nullable()->after('board_meeting_approved_at')->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('payment_order_created_at')->nullable()->after('payment_order_created_by_accountant_id');
|
||||
$table->enum('payment_method', ['bank_transfer', 'check', 'cash'])->nullable()->after('payment_order_created_at');
|
||||
$table->string('payee_name', 100)->nullable()->after('payment_method');
|
||||
$table->string('payee_bank_code', 10)->nullable()->after('payee_name');
|
||||
$table->string('payee_account_number', 30)->nullable()->after('payee_bank_code');
|
||||
$table->text('payment_notes')->nullable()->after('payee_account_number');
|
||||
|
||||
// 出納覆核付款單
|
||||
$table->foreignId('payment_verified_by_cashier_id')->nullable()->after('payment_notes')->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('payment_verified_at')->nullable()->after('payment_verified_by_cashier_id');
|
||||
$table->text('payment_verification_notes')->nullable()->after('payment_verified_at');
|
||||
|
||||
// 實際付款執行
|
||||
$table->foreignId('payment_executed_by_cashier_id')->nullable()->after('payment_verification_notes')->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('payment_executed_at')->nullable()->after('payment_executed_by_cashier_id');
|
||||
$table->string('payment_transaction_id', 50)->nullable()->after('payment_executed_at')->comment('銀行交易編號');
|
||||
$table->string('payment_receipt_path')->nullable()->after('payment_transaction_id')->comment('付款憑證路徑');
|
||||
$table->decimal('actual_payment_amount', 10, 2)->nullable()->after('payment_receipt_path')->comment('實付金額');
|
||||
|
||||
// 記帳階段 (外鍵稍後加上,因為相關表還不存在)
|
||||
$table->unsignedBigInteger('cashier_ledger_entry_id')->nullable()->after('actual_payment_amount');
|
||||
$table->timestamp('cashier_recorded_at')->nullable()->after('cashier_ledger_entry_id');
|
||||
$table->unsignedBigInteger('accounting_transaction_id')->nullable()->after('cashier_recorded_at');
|
||||
$table->timestamp('accountant_recorded_at')->nullable()->after('accounting_transaction_id');
|
||||
|
||||
// 月底核對
|
||||
$table->enum('reconciliation_status', ['pending', 'matched', 'discrepancy', 'resolved'])->default('pending')->after('accountant_recorded_at');
|
||||
$table->text('reconciliation_notes')->nullable()->after('reconciliation_status');
|
||||
$table->timestamp('reconciled_at')->nullable()->after('reconciliation_notes');
|
||||
$table->foreignId('reconciled_by_user_id')->nullable()->after('reconciled_at')->constrained('users')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('finance_documents', function (Blueprint $table) {
|
||||
// 移除外鍵約束
|
||||
$table->dropForeign(['chart_of_account_id']);
|
||||
$table->dropForeign(['budget_item_id']);
|
||||
$table->dropForeign(['approved_by_board_meeting_id']);
|
||||
$table->dropForeign(['payment_order_created_by_accountant_id']);
|
||||
$table->dropForeign(['payment_verified_by_cashier_id']);
|
||||
$table->dropForeign(['payment_executed_by_cashier_id']);
|
||||
$table->dropForeign(['reconciled_by_user_id']);
|
||||
|
||||
// 移除欄位
|
||||
$table->dropColumn([
|
||||
'request_type',
|
||||
'amount_tier',
|
||||
'chart_of_account_id',
|
||||
'budget_item_id',
|
||||
'requires_board_meeting',
|
||||
'board_meeting_date',
|
||||
'board_meeting_decision',
|
||||
'approved_by_board_meeting_id',
|
||||
'board_meeting_approved_at',
|
||||
'payment_order_created_by_accountant_id',
|
||||
'payment_order_created_at',
|
||||
'payment_method',
|
||||
'payee_name',
|
||||
'payee_bank_code',
|
||||
'payee_account_number',
|
||||
'payment_notes',
|
||||
'payment_verified_by_cashier_id',
|
||||
'payment_verified_at',
|
||||
'payment_verification_notes',
|
||||
'payment_executed_by_cashier_id',
|
||||
'payment_executed_at',
|
||||
'payment_transaction_id',
|
||||
'payment_receipt_path',
|
||||
'actual_payment_amount',
|
||||
'cashier_ledger_entry_id',
|
||||
'cashier_recorded_at',
|
||||
'accounting_transaction_id',
|
||||
'accountant_recorded_at',
|
||||
'reconciliation_status',
|
||||
'reconciliation_notes',
|
||||
'reconciled_at',
|
||||
'reconciled_by_user_id',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('payment_orders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('finance_document_id')->constrained('finance_documents')->cascadeOnDelete();
|
||||
|
||||
// 付款資訊
|
||||
$table->string('payee_name', 100)->comment('收款人姓名');
|
||||
$table->string('payee_bank_code', 10)->nullable()->comment('銀行代碼');
|
||||
$table->string('payee_account_number', 30)->nullable()->comment('銀行帳號');
|
||||
$table->string('payee_bank_name', 100)->nullable()->comment('銀行名稱');
|
||||
$table->decimal('payment_amount', 10, 2)->comment('付款金額');
|
||||
$table->enum('payment_method', ['bank_transfer', 'check', 'cash'])->comment('付款方式');
|
||||
|
||||
// 會計製單
|
||||
$table->foreignId('created_by_accountant_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->string('payment_order_number', 50)->unique()->comment('付款單號');
|
||||
$table->text('notes')->nullable();
|
||||
|
||||
// 出納覆核
|
||||
$table->foreignId('verified_by_cashier_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('verified_at')->nullable();
|
||||
$table->enum('verification_status', ['pending', 'approved', 'rejected'])->default('pending');
|
||||
$table->text('verification_notes')->nullable();
|
||||
|
||||
// 執行付款
|
||||
$table->foreignId('executed_by_cashier_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('executed_at')->nullable();
|
||||
$table->enum('execution_status', ['pending', 'completed', 'failed'])->default('pending');
|
||||
$table->string('transaction_reference', 100)->nullable()->comment('交易參考號');
|
||||
|
||||
// 憑證
|
||||
$table->string('payment_receipt_path')->nullable()->comment('付款憑證路徑');
|
||||
|
||||
$table->enum('status', ['draft', 'pending_verification', 'verified', 'executed', 'cancelled'])->default('draft');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('finance_document_id');
|
||||
$table->index('status');
|
||||
$table->index('verification_status');
|
||||
$table->index('execution_status');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('payment_orders');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cashier_ledger_entries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('finance_document_id')->nullable()->constrained('finance_documents')->cascadeOnDelete();
|
||||
$table->date('entry_date')->comment('記帳日期');
|
||||
$table->enum('entry_type', ['receipt', 'payment'])->comment('類型:收入/支出');
|
||||
|
||||
// 付款資訊
|
||||
$table->enum('payment_method', ['bank_transfer', 'check', 'cash'])->comment('付款方式');
|
||||
$table->string('bank_account', 100)->nullable()->comment('使用的銀行帳戶');
|
||||
$table->decimal('amount', 10, 2)->comment('金額');
|
||||
|
||||
// 餘額追蹤
|
||||
$table->decimal('balance_before', 10, 2)->comment('交易前餘額');
|
||||
$table->decimal('balance_after', 10, 2)->comment('交易後餘額');
|
||||
|
||||
// 憑證資訊
|
||||
$table->string('receipt_number', 50)->nullable()->comment('收據/憑證編號');
|
||||
$table->string('transaction_reference', 100)->nullable()->comment('交易參考號');
|
||||
|
||||
// 記錄人員
|
||||
$table->foreignId('recorded_by_cashier_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->timestamp('recorded_at')->useCurrent();
|
||||
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('finance_document_id');
|
||||
$table->index('entry_date');
|
||||
$table->index('entry_type');
|
||||
$table->index('recorded_by_cashier_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cashier_ledger_entries');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('bank_reconciliations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->date('reconciliation_month')->comment('調節月份');
|
||||
|
||||
// 銀行對帳單
|
||||
$table->decimal('bank_statement_balance', 10, 2)->comment('銀行對帳單餘額');
|
||||
$table->date('bank_statement_date')->comment('對帳單日期');
|
||||
$table->string('bank_statement_file_path')->nullable()->comment('對帳單檔案');
|
||||
|
||||
// 系統帳面
|
||||
$table->decimal('system_book_balance', 10, 2)->comment('系統帳面餘額');
|
||||
|
||||
// 未達帳項(JSON 格式)
|
||||
$table->json('outstanding_checks')->nullable()->comment('未兌現支票');
|
||||
$table->json('deposits_in_transit')->nullable()->comment('在途存款');
|
||||
$table->json('bank_charges')->nullable()->comment('銀行手續費');
|
||||
|
||||
// 調節結果
|
||||
$table->decimal('adjusted_balance', 10, 2)->comment('調整後餘額');
|
||||
$table->decimal('discrepancy_amount', 10, 2)->default(0)->comment('差異金額');
|
||||
$table->enum('reconciliation_status', ['pending', 'completed', 'discrepancy'])->default('pending');
|
||||
|
||||
// 執行人員
|
||||
$table->foreignId('prepared_by_cashier_id')->constrained('users')->cascadeOnDelete();
|
||||
$table->foreignId('reviewed_by_accountant_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('approved_by_manager_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
|
||||
$table->timestamp('prepared_at')->useCurrent();
|
||||
$table->timestamp('reviewed_at')->nullable();
|
||||
$table->timestamp('approved_at')->nullable();
|
||||
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('reconciliation_month');
|
||||
$table->index('reconciliation_status');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('bank_reconciliations');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user