Xu ly giao dien San Pham
This commit is contained in:
@@ -7,17 +7,14 @@ use App\Models\Contract;
|
||||
use App\Models\Product;
|
||||
use App\Models\PaymentTemplate;
|
||||
use App\Enums\NavigationGroup;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use App\Filament\Resources\Contracts\ContractResource\RelationManagers\ScheduleItemsRelationManager;
|
||||
|
||||
use App\Filament\Resources\Contracts\Schemas\ContractForm;
|
||||
|
||||
class ContractResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Contract::class;
|
||||
@@ -30,24 +27,7 @@ class ContractResource extends Resource
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Liên kết & Mẫu thanh toán')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('product_id')->label('Sản phẩm')->relationship('product', 'code')->required()->live(),
|
||||
Select::make('payment_template_id')->label('Mẫu thanh toán')
|
||||
->options(fn (Get $get) => PaymentTemplate::pluck('name', 'id'))
|
||||
->required()->dehydrated(false),
|
||||
Select::make('customers')->label('Khách hàng')->relationship('customers', 'full_name')->multiple()->required()->columnSpanFull(),
|
||||
]),
|
||||
Section::make('Chi tiết Hợp đồng')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('contract_number')->label('Số HĐ')->required(),
|
||||
DatePicker::make('signing_date')->label('Ngày ký')->required(),
|
||||
])
|
||||
]);
|
||||
return ContractForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
namespace App\Filament\Resources\Contracts\Schemas;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\PaymentTemplate;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
|
||||
class ContractForm
|
||||
{
|
||||
@@ -10,7 +18,63 @@ class ContractForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
//
|
||||
Section::make('Liên kết & Mẫu thanh toán')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('product_id')
|
||||
->label('Sản phẩm')
|
||||
->relationship('product', 'code')
|
||||
->required()
|
||||
->live()
|
||||
->afterStateUpdated(function (Set $set, $state) {
|
||||
if ($state) {
|
||||
$product = Product::find($state);
|
||||
if ($product) {
|
||||
$set('total_value', $product->total_price);
|
||||
}
|
||||
}
|
||||
}),
|
||||
Select::make('payment_template_id')
|
||||
->label('Mẫu thanh toán')
|
||||
->options(fn () => PaymentTemplate::pluck('name', 'id'))
|
||||
->required()
|
||||
->dehydrated(false),
|
||||
Select::make('customers')
|
||||
->label('Khách hàng')
|
||||
->relationship('customers', 'full_name')
|
||||
->multiple()
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Section::make('Chi tiết Hợp đồng')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('contract_number')->label('Số HĐ')->required(),
|
||||
Select::make('contract_type')
|
||||
->label('Loại HĐ')
|
||||
->options([
|
||||
'HĐMB' => 'HĐMB',
|
||||
'HĐGV' => 'HĐGV',
|
||||
'HĐDC' => 'HĐDC',
|
||||
])
|
||||
->default('HĐMB')
|
||||
->required(),
|
||||
DatePicker::make('signing_date')->label('Ngày ký')->required(),
|
||||
TextInput::make('total_value')
|
||||
->label('Giá trị HĐ')
|
||||
->numeric()
|
||||
->required()
|
||||
->prefix('VND'),
|
||||
Select::make('status')
|
||||
->label('Trạng thái')
|
||||
->options([
|
||||
'Đang hiệu lực' => 'Đang hiệu lực',
|
||||
'Đã hoàn thành' => 'Đã hoàn thành',
|
||||
'Đã hủy' => 'Đã hủy',
|
||||
])
|
||||
->default('Đang hiệu lực')
|
||||
->required(),
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ class PaymentTemplateResource extends Resource
|
||||
TextInput::make('installment_no')->label('Đợt')->numeric()->required(),
|
||||
Select::make('type')->label('Loại')->options(PaymentType::class)->required(),
|
||||
TextInput::make('percentage')->label('%')->numeric()->required(),
|
||||
TextInput::make('days_after_signing')->label('Ngày sau ký (F0)')->numeric(),
|
||||
TextInput::make('days_after_previous')->label('Ngày sau đợt trước')->numeric(),
|
||||
\Filament\Forms\Components\DatePicker::make('due_date')->label('Ngày cố định'),
|
||||
])->columns(3),
|
||||
])
|
||||
]);
|
||||
|
||||
@@ -4,19 +4,13 @@ namespace App\Filament\Resources\Products;
|
||||
|
||||
use App\Filament\Resources\Products\Pages;
|
||||
use App\Models\Product;
|
||||
use App\Enums\ProductType;
|
||||
use App\Enums\NavigationGroup;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use App\Filament\Resources\Products\ProductResource\RelationManagers\ContractsRelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Resources\Products\ProductResource\RelationManagers\ContractsRelationManager;
|
||||
|
||||
use App\Filament\Resources\Products\Schemas\ProductForm;
|
||||
|
||||
class ProductResource extends Resource
|
||||
{
|
||||
@@ -30,43 +24,12 @@ class ProductResource extends Resource
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make('ProductDetails')
|
||||
->tabs([
|
||||
Tabs\Tab::make('Thông tin chung')
|
||||
->icon('heroicon-o-information-circle')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('project_id')->label('Dự án')->relationship('project', 'name')->required(),
|
||||
Select::make('product_type')->label('Loại sản phẩm')->options(ProductType::class)->required(),
|
||||
TextInput::make('code')->label('Mã SP')->required()->unique(ignoreRecord: true),
|
||||
Select::make('status')
|
||||
->label('Trạng thái')
|
||||
->options(['Đang mở bán' => 'Đang mở bán', 'Đã cọc' => 'Đã cọc', 'Đã bán' => 'Đã bán'])
|
||||
->required(),
|
||||
]),
|
||||
Tabs\Tab::make('Tài chính')
|
||||
->icon('heroicon-o-currency-dollar')
|
||||
->schema([
|
||||
TextInput::make('area')->label('Diện tích (m2)')->numeric()->required(),
|
||||
TextInput::make('price_per_unit')->label('Đơn giá')->numeric()->required(),
|
||||
TextInput::make('total_price')->label('Tổng giá')->numeric()->required(),
|
||||
]),
|
||||
])->columnSpanFull()
|
||||
]);
|
||||
return ProductForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('project.code')->label('Dự án'),
|
||||
Tables\Columns\TextColumn::make('code')->label('Mã SP')->searchable(),
|
||||
Tables\Columns\TextColumn::make('product_type')->label('Loại')->badge(),
|
||||
Tables\Columns\TextColumn::make('total_price')->label('Giá')->money('VND'),
|
||||
Tables\Columns\TextColumn::make('status')->label('Trạng thái')->badge(),
|
||||
]);
|
||||
return \App\Filament\Resources\Products\Tables\ProductsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array { return [ContractsRelationManager::class]; }
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
namespace App\Filament\Resources\Products\Schemas;
|
||||
|
||||
use App\Enums\ProductType;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ProductForm
|
||||
@@ -10,7 +16,76 @@ class ProductForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
//
|
||||
Tabs::make('ProductDetails')
|
||||
->tabs([
|
||||
Tabs\Tab::make('Thông tin chung')
|
||||
->icon('heroicon-o-information-circle')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('project_id')->label('Dự án')->relationship('project', 'name')->required(),
|
||||
Select::make('product_type')->label('Loại sản phẩm')->options(ProductType::class)->required(),
|
||||
TextInput::make('code')->label('Mã SP')->required(),
|
||||
Select::make('status')
|
||||
->label('Trạng thái')
|
||||
->options([
|
||||
'Đang mở bán' => 'Đang mở bán',
|
||||
'Đã cọc' => 'Đã cọc',
|
||||
'Đã bán' => 'Đã bán'
|
||||
])->required(),
|
||||
]),
|
||||
Tabs\Tab::make('Tài chính')
|
||||
->icon('heroicon-o-currency-dollar')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('area')->label('Diện tích (m2)')->numeric()->required(),
|
||||
TextInput::make('price_per_unit')->label('Đơn giá')->numeric()->required()->prefix('VND'),
|
||||
TextInput::make('total_price')->label('Tổng giá')->numeric()->required()->prefix('VND'),
|
||||
Section::make('Bổ sung')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('qsdd_value')->label('Giá trị QSDĐ')->numeric()->prefix('VND'),
|
||||
TextInput::make('foundation_temp_value')->label('Giá trị móng (tạm tính)')->numeric()->prefix('VND'),
|
||||
])
|
||||
]),
|
||||
Tabs\Tab::make('Kỹ thuật & Hạ tầng')
|
||||
->icon('heroicon-o-cog')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('adjacent_road')->label('Đường tiếp giáp'),
|
||||
TextInput::make('frontage_count')->label('Số mặt tiền')->numeric(),
|
||||
TextInput::make('max_floors')->label('Số tầng tối đa')->numeric(),
|
||||
TextInput::make('construction_status')->label('Trạng thái XD'),
|
||||
KeyValue::make('infrastructure_status')
|
||||
->label('Trạng thái hạ tầng')
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Tabs\Tab::make('Tiến độ thanh toán')
|
||||
->icon('heroicon-o-calendar-days')
|
||||
->schema([
|
||||
\Filament\Forms\Components\Repeater::make('payment_schedule_preview')
|
||||
->label('Lịch trình dự kiến / Thực tế')
|
||||
->relationship(function ($record) {
|
||||
// Nếu sản phẩm đã có hợp đồng, lấy lịch trình từ hợp đồng đầu tiên
|
||||
if ($record && $record->contracts()->exists()) {
|
||||
return $record->contracts()->first()->scheduleItems();
|
||||
}
|
||||
// Nếu chưa có, chúng ta sẽ hiển thị mẫu từ Project (phần này cần logic state giả lập hoặc view)
|
||||
return null;
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('installment_no')->label('Đợt')->disabled(),
|
||||
TextInput::make('type')->label('Loại')->disabled(),
|
||||
TextInput::make('percentage')->label('%')->disabled(),
|
||||
TextInput::make('amount')->label('Số tiền (VNĐ)')->numeric()->disabled(),
|
||||
DatePicker::make('due_date')->label('Ngày đến hạn')->disabled(),
|
||||
])
|
||||
->columns(5)
|
||||
->addable(false)
|
||||
->deletable(false)
|
||||
->reorderable(false)
|
||||
->placeholder('Sản phẩm chưa có hợp đồng hoặc Dự án chưa gán mẫu thanh toán mặc định.')
|
||||
]),
|
||||
])->columnSpanFull()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ namespace App\Filament\Resources\Products\Tables;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use App\Models\Project;
|
||||
|
||||
class ProductsTable
|
||||
{
|
||||
@@ -13,10 +16,45 @@ class ProductsTable
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
//
|
||||
TextColumn::make('project.code')
|
||||
->label('Dự án')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('code')
|
||||
->label('Mã SP')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('product_type')
|
||||
->label('Loại')
|
||||
->badge(),
|
||||
TextColumn::make('total_price')
|
||||
->label('Giá')
|
||||
->money('VND')
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label('Trạng thái')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'Đang mở bán' => 'success',
|
||||
'Đã cọc' => 'warning',
|
||||
'Đã bán' => 'danger',
|
||||
default => 'gray',
|
||||
}),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
SelectFilter::make('project_id')
|
||||
->label('Dự án')
|
||||
->relationship('project', 'name'),
|
||||
SelectFilter::make('status')
|
||||
->label('Trạng thái')
|
||||
->options([
|
||||
'Đang mở bán' => 'Đang mở bán',
|
||||
'Đã cọc' => 'Đã cọc',
|
||||
'Đã bán' => 'Đã bán',
|
||||
]),
|
||||
SelectFilter::make('product_type')
|
||||
->label('Loại sản phẩm')
|
||||
->options(\App\Enums\ProductType::class),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
|
||||
@@ -32,6 +32,11 @@ class ProjectResource extends Resource
|
||||
->schema([
|
||||
TextInput::make('code')->label('Mã Dự án')->required()->unique(ignoreRecord: true),
|
||||
TextInput::make('name')->label('Tên Dự án')->required(),
|
||||
Select::make('payment_template_id')
|
||||
->label('Mẫu thanh toán mặc định')
|
||||
->relationship('paymentTemplate', 'name')
|
||||
->placeholder('Chọn mẫu thanh toán cho toàn dự án')
|
||||
->columnSpanFull(),
|
||||
Select::make('type')
|
||||
->label('Loại hình')
|
||||
->options([
|
||||
|
||||
@@ -30,7 +30,8 @@ class Contract extends Model
|
||||
public function customers()
|
||||
{
|
||||
return $this->belongsToMany(Customer::class, 'contract_customers')
|
||||
->withPivot('role', 'transfer_order')
|
||||
->using(ContractCustomer::class)
|
||||
->withPivot('id', 'role', 'transfer_order')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
@@ -68,4 +69,20 @@ class Contract extends Model
|
||||
{
|
||||
return $this->hasMany(PaymentFine::class);
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($contract) {
|
||||
// Tự động lấy giá trị từ sản phẩm nếu chưa có
|
||||
if (empty($contract->total_value) && !empty($contract->product_id)) {
|
||||
$product = Product::find($contract->product_id);
|
||||
if ($product) {
|
||||
$contract->total_value = $product->total_price;
|
||||
}
|
||||
}
|
||||
|
||||
// Tính toán số tiền còn lại
|
||||
$contract->remaining_amount = ($contract->total_value ?? 0) - ($contract->paid_amount ?? 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
19
app/Models/ContractCustomer.php
Normal file
19
app/Models/ContractCustomer.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
class ContractCustomer extends Pivot
|
||||
{
|
||||
use HasUuids;
|
||||
|
||||
protected $table = 'contract_customers';
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -21,7 +21,8 @@ class Customer extends Model
|
||||
public function contracts()
|
||||
{
|
||||
return $this->belongsToMany(Contract::class, 'contract_customers')
|
||||
->withPivot('role', 'transfer_order')
|
||||
->using(ContractCustomer::class)
|
||||
->withPivot('id', 'role', 'transfer_order')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ class Project extends Model
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
|
||||
public function paymentTemplate()
|
||||
{
|
||||
return $this->belongsTo(PaymentTemplate::class);
|
||||
}
|
||||
|
||||
public function paymentTemplates()
|
||||
{
|
||||
return $this->hasMany(PaymentTemplate::class);
|
||||
|
||||
@@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Schema;
|
||||
return new class extends Migration {
|
||||
public function up(): void {
|
||||
Schema::create('contract_customers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('id')->primary();
|
||||
$table->foreignUuid('contract_id')->constrained('contracts')->cascadeOnDelete();
|
||||
$table->foreignUuid('customer_id')->constrained('customers')->cascadeOnDelete();
|
||||
$table->string('role')->default('CHỦ SH 1'); // Đồng sở hữu
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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('projects', function (Blueprint $table) {
|
||||
$table->string('type')->nullable(); // Thêm cột loại hình dự án
|
||||
$table->foreignUuid('payment_template_id')->nullable()->constrained('payment_templates')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
$table->dropForeign(['payment_template_id']);
|
||||
$table->dropColumn(['payment_template_id', 'type']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\User;
|
||||
use App\Models\Project;
|
||||
use App\Models\Product;
|
||||
use App\Models\Customer;
|
||||
@@ -9,144 +11,124 @@ use App\Models\Contract;
|
||||
use App\Models\PaymentTemplate;
|
||||
use App\Models\PaymentSchedule;
|
||||
use App\Models\PaymentScheduleItem;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Appendix;
|
||||
use App\Models\Settlement;
|
||||
use App\Enums\ProductType;
|
||||
use App\Enums\PaymentType;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TestDataSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
DB::transaction(function () {
|
||||
// 1. PROJECT
|
||||
$sth03 = Project::factory()->create(['code' => 'STH03', 'name' => 'Khu Riverside STH03']);
|
||||
$diamond = Project::factory()->create(['code' => 'DIA21', 'name' => 'Diamond Luxury Suites']);
|
||||
// 1. Tạo Tài khoản Admin
|
||||
User::updateOrCreate(
|
||||
['email' => 'admin@phuongtc.com'],
|
||||
[
|
||||
'name' => 'chanphuong',
|
||||
'password' => Hash::make('1Qazxsw2@!321'),
|
||||
]
|
||||
);
|
||||
|
||||
// 2. PAYMENT TEMPLATES
|
||||
$templateStandard = PaymentTemplate::create([
|
||||
'project_id' => $sth03->id,
|
||||
'name' => 'Thanh toán chuẩn STH03',
|
||||
'is_default' => true
|
||||
]);
|
||||
// 2. Tạo Dự án
|
||||
$project = Project::updateOrCreate(
|
||||
['code' => 'STH03'],
|
||||
[
|
||||
'name' => 'Khu đô thị Mỹ Gia - Gói 3',
|
||||
'type' => 'Khu đô thị'
|
||||
]
|
||||
);
|
||||
|
||||
$this->createTemplateItems($templateStandard);
|
||||
|
||||
// 3. PRODUCTS
|
||||
$this->seedProducts($sth03, $diamond);
|
||||
|
||||
// 4. CUSTOMERS
|
||||
$customers = Customer::factory(15)->create();
|
||||
|
||||
// 5. CASE STUDY: LỊCH SỬ CHUYỂN NHƯỢNG
|
||||
$this->seedTransferHistory($sth03, $customers);
|
||||
|
||||
// 6. CASE STUDY: DÒNG TIỀN PHỨC TẠP
|
||||
$this->seedComplexPayments($sth03, $customers, $templateStandard);
|
||||
});
|
||||
}
|
||||
|
||||
private function createTemplateItems($template)
|
||||
{
|
||||
PaymentScheduleItem::create(['template_id' => $template->id, 'installment_no' => 1, 'percentage' => 30, 'days_after_signing' => 7, 'type' => PaymentType::QSDD]);
|
||||
PaymentScheduleItem::create(['template_id' => $template->id, 'installment_no' => 2, 'percentage' => 40, 'days_after_previous' => 60, 'type' => PaymentType::MONG]);
|
||||
PaymentScheduleItem::create(['template_id' => $template->id, 'installment_no' => 3, 'percentage' => 30, 'days_after_previous' => 90, 'type' => PaymentType::THAN]);
|
||||
}
|
||||
|
||||
private function seedProducts($project1, $project2)
|
||||
{
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
Product::create([
|
||||
'project_id' => $project1->id,
|
||||
'product_type' => ProductType::LAND,
|
||||
'code' => $project1->code . '.' . sprintf('%02d', $i),
|
||||
'area' => 100 + $i,
|
||||
'price_per_unit' => 50000000,
|
||||
'total_price' => (100 + $i) * 50000000,
|
||||
'qsdd_value' => ((100 + $i) * 50000000) * 0.4,
|
||||
'adjacent_road' => 'Đường 16m',
|
||||
'frontage_count' => 1,
|
||||
'infrastructure_status' => [
|
||||
'dien' => ['status' => 'Hoàn thiện', 'child' => ['tram_bien_ap' => 'Đã nghiệm thu']],
|
||||
'nuoc' => ['status' => 'Đang thi công']
|
||||
],
|
||||
'custom_data' => ['so_lo' => 'Lô '.$i, 'huong' => 'Đông Nam'],
|
||||
'status' => 'Đang mở bán'
|
||||
]);
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
Product::create([
|
||||
'project_id' => $project2->id,
|
||||
'product_type' => ProductType::APARTMENT,
|
||||
'code' => $project2->code . '-P' . sprintf('%03d', $i),
|
||||
'area' => 75,
|
||||
'price_per_unit' => 40000000,
|
||||
'total_price' => 75 * 40000000,
|
||||
'infrastructure_status' => ['thang_may' => 'Schindler', 'pccc' => 'Đạt chuẩn'],
|
||||
'custom_data' => ['block' => 'A', 'floor' => 10 + $i, 'view' => 'City View'],
|
||||
'status' => 'Đang mở bán'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function seedTransferHistory($project, $customers)
|
||||
{
|
||||
$product = Product::where('code', 'STH03.01')->first();
|
||||
|
||||
$c1 = Contract::create([
|
||||
'product_id' => $product->id, 'transfer_order' => 1, 'contract_type' => 'HĐMB', 'contract_number' => 'HĐMB-GOC-88',
|
||||
'signing_date' => Carbon::now()->subYears(2), 'total_value' => $product->total_price, 'paid_amount' => $product->total_price, 'status' => 'Đã chuyển nhượng'
|
||||
]);
|
||||
$customers[0]->contracts()->attach($c1->id, ['role' => 'CHỦ CŨ', 'transfer_order' => 1]);
|
||||
|
||||
// SỬA TỪ customData THÀNH custom_data
|
||||
Appendix::create([
|
||||
'contract_id' => $c1->id, 'product_id' => $product->id, 'type' => 'Thay đổi diện tích', 'apply_from_order' => 1, 'signing_date' => Carbon::now()->subYears(1),
|
||||
'custom_data' => ['area_new' => 105]
|
||||
// 3. Tạo Mẫu thanh toán
|
||||
$template = PaymentTemplate::create([
|
||||
'project_id' => $project->id,
|
||||
'name' => 'Mẫu chuẩn Đất nền 30-40-30'
|
||||
]);
|
||||
|
||||
$c2 = Contract::create([
|
||||
'product_id' => $product->id, 'transfer_order' => 0, 'contract_type' => 'VBCN', 'contract_number' => 'VBCN-F1-99',
|
||||
'signing_date' => Carbon::now(), 'total_value' => $product->total_price * 1.15, 'paid_amount' => 500000000, 'status' => 'Đang hiệu lực'
|
||||
$project->update(['payment_template_id' => $template->id]);
|
||||
|
||||
// 4. Tạo các đợt mẫu
|
||||
PaymentScheduleItem::create([
|
||||
'template_id' => $template->id,
|
||||
'installment_no' => 1,
|
||||
'type' => PaymentType::MONG,
|
||||
'percentage' => 30,
|
||||
'days_after_signing' => 0,
|
||||
]);
|
||||
$customers[1]->contracts()->attach($c2->id, ['role' => 'CHỦ SỞ HỮU', 'transfer_order' => 0]);
|
||||
|
||||
$product->update(['status' => 'Đã bán']);
|
||||
}
|
||||
|
||||
private function seedComplexPayments($project, $customers, $template)
|
||||
{
|
||||
$product = Product::where('code', 'STH03.02')->first();
|
||||
$contract = Contract::create([
|
||||
'product_id' => $product->id, 'transfer_order' => 0, 'contract_type' => 'HĐMB', 'contract_number' => 'HD-PAY-DEBUG',
|
||||
'signing_date' => Carbon::now()->subDays(10), 'total_value' => $product->total_price, 'status' => 'Đang hiệu lực'
|
||||
PaymentScheduleItem::create([
|
||||
'template_id' => $template->id,
|
||||
'installment_no' => 2,
|
||||
'type' => PaymentType::THAN,
|
||||
'percentage' => 40,
|
||||
'days_after_previous' => 60,
|
||||
]);
|
||||
PaymentScheduleItem::create([
|
||||
'template_id' => $template->id,
|
||||
'installment_no' => 3,
|
||||
'type' => PaymentType::OTHER,
|
||||
'percentage' => 30,
|
||||
'days_after_previous' => 90,
|
||||
]);
|
||||
$customers[5]->contracts()->attach($contract->id, ['role' => 'CHỦ SỞ HỮU', 'transfer_order' => 0]);
|
||||
|
||||
$schedule = PaymentSchedule::create(['contract_id' => $contract->id, 'template_id' => $template->id]);
|
||||
$items = $template->items;
|
||||
foreach($items as $item) {
|
||||
$si = PaymentScheduleItem::create([
|
||||
'schedule_id' => $schedule->id, 'installment_no' => $item->installment_no, 'percentage' => $item->percentage,
|
||||
'amount' => $contract->total_value * ($item->percentage / 100), 'type' => $item->type, 'due_date' => Carbon::now()->addDays(30 * $item->installment_no)
|
||||
]);
|
||||
// 5. Tạo Sản phẩm
|
||||
Product::create([
|
||||
'project_id' => $project->id,
|
||||
'product_type' => ProductType::LAND,
|
||||
'code' => 'STH03.01',
|
||||
'area' => 100,
|
||||
'price_per_unit' => 25000000,
|
||||
'total_price' => 2500000000,
|
||||
'status' => 'Đang mở bán',
|
||||
]);
|
||||
|
||||
if ($item->installment_no == 1) {
|
||||
$required = $si->amount;
|
||||
$paid = $required * 1.2;
|
||||
Payment::create(['contract_id' => $contract->id, 'schedule_item_id' => $si->id, 'amount' => $paid, 'paid_date' => Carbon::now(), 'method' => 'Chuyển khoản']);
|
||||
$productSold = Product::create([
|
||||
'project_id' => $project->id,
|
||||
'product_type' => ProductType::LAND,
|
||||
'code' => 'STH03.02',
|
||||
'area' => 100,
|
||||
'price_per_unit' => 25000000,
|
||||
'total_price' => 2500000000,
|
||||
'status' => 'Đã bán',
|
||||
]);
|
||||
|
||||
$contract->update([
|
||||
'paid_amount' => $paid,
|
||||
'excess_amount' => $paid - $required,
|
||||
'remaining_amount' => $contract->total_value - $paid
|
||||
]);
|
||||
}
|
||||
}
|
||||
// 6. Khách hàng & Hợp đồng chuyển nhượng
|
||||
// Sử dụng cột cmnd_cccd thay cho id_number
|
||||
$customerA = Customer::create(['full_name' => 'Nguyễn Văn A', 'phone' => '0901234567', 'cmnd_cccd' => '123456789']);
|
||||
$customerB = Customer::create(['full_name' => 'Trần Thị B', 'phone' => '0907654321', 'cmnd_cccd' => '987654321']);
|
||||
|
||||
// F1
|
||||
$contractF1 = Contract::create([
|
||||
'product_id' => $productSold->id,
|
||||
'contract_number' => 'HĐMB-STH03.02-F1',
|
||||
'signing_date' => Carbon::now()->subMonths(6),
|
||||
'total_value' => 2500000000,
|
||||
'transfer_order' => 1,
|
||||
'status' => 'Đã thanh lý (Chuyển nhượng)',
|
||||
]);
|
||||
$contractF1->customers()->attach($customerA->id);
|
||||
|
||||
// F2
|
||||
$contractF2 = Contract::create([
|
||||
'product_id' => $productSold->id,
|
||||
'contract_number' => 'HĐMB-STH03.02-F2',
|
||||
'signing_date' => Carbon::now()->subDays(10),
|
||||
'total_value' => 2600000000,
|
||||
'transfer_order' => 2,
|
||||
'status' => 'Đang hiệu lực',
|
||||
]);
|
||||
$contractF2->customers()->attach($customerB->id);
|
||||
|
||||
$schedule = PaymentSchedule::create([
|
||||
'contract_id' => $contractF2->id,
|
||||
'template_id' => $template->id,
|
||||
]);
|
||||
|
||||
PaymentScheduleItem::create([
|
||||
'schedule_id' => $schedule->id,
|
||||
'installment_no' => 1,
|
||||
'type' => PaymentType::MONG,
|
||||
'percentage' => 30,
|
||||
'amount' => 780000000,
|
||||
'due_date' => Carbon::now()->subDays(10),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
28
moduledesign.md
Normal file
28
moduledesign.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Mô tả giao diện tính năng của tường module
|
||||
|
||||
1. **Module sản phẩm.**
|
||||
- Giao diện chính cần có thêm các mục để có thể lọc dữ liệu như sau:
|
||||
|
||||
- lọc theo Project
|
||||
|
||||
- lọc theo trạng thái (đã bán, chưa bán....)
|
||||
|
||||
- lọc theo đợt mở bán (hiện chưa có module này, sẽ bổ sung sau)
|
||||
|
||||
- Lọc theo các khu vực/block (kiểm tra lại xem đã có thể thực hiện hay chưa)
|
||||
|
||||
- Giao diện chi tiết:
|
||||
|
||||
- Hiển thị các thông tin chi tiết bắt buộc trong database chi thành các tab hợp lý
|
||||
|
||||
- Hiển thị các lịch sử liên quan (theo tab) như :
|
||||
|
||||
- lịch sử giao dịch
|
||||
|
||||
- lịch sử chỉnh sửa, cập nhật
|
||||
|
||||
- tiến độ thanh toán
|
||||
|
||||
- Tài liệu liên quan (sẽ bổ sung module sau)
|
||||
|
||||
|
||||
241
prisma2.md
Normal file
241
prisma2.md
Normal file
@@ -0,0 +1,241 @@
|
||||
// =============================================
|
||||
// PRISMA SCHEMA - HQLAND MANAGEMENT SYSTEM
|
||||
// Phiên bản: 2.4 (Cập nhật từ Database thực tế)
|
||||
// Ngày: 18/04/2026
|
||||
// Ghi chú: Đồng bộ hóa 100% với Migrations hiện tại.
|
||||
// =============================================
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 1. PROJECT (Dự án)
|
||||
// =============================================
|
||||
model Project {
|
||||
id String @id @default(uuid())
|
||||
code String @unique // ví dụ: STH03
|
||||
name String
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
products Product[]
|
||||
templates PaymentTemplate[]
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 2. PRODUCT (Sản phẩm)
|
||||
// =============================================
|
||||
model Product {
|
||||
id String @id @default(uuid())
|
||||
projectId String @map("project_id")
|
||||
|
||||
productType String @map("product_type") // LAND, APARTMENT...
|
||||
|
||||
code String @unique
|
||||
area Decimal @db.Decimal(12, 2)
|
||||
pricePerUnit Decimal @db.Decimal(15, 2) @map("price_per_unit")
|
||||
totalPrice Decimal @db.Decimal(15, 2) @map("total_price")
|
||||
|
||||
// Giá trị tài chính bổ sung
|
||||
qsddValue Decimal @default(0) @db.Decimal(15, 2) @map("qsdd_value")
|
||||
foundationTempValue Decimal @default(0) @db.Decimal(15, 2) @map("foundation_temp_value")
|
||||
contractTempValue Decimal @default(0) @db.Decimal(15, 2) @map("contract_temp_value")
|
||||
|
||||
// Thông số kỹ thuật
|
||||
adjacentRoad String? @map("adjacent_road")
|
||||
frontageCount Int? @map("frontage_count")
|
||||
maxFloors Int? @map("max_floors")
|
||||
buildingDensity Decimal? @db.Decimal(5, 2) @map("building_density")
|
||||
constructionStatus String? @map("construction_status")
|
||||
|
||||
// Hạ tầng & Dữ liệu động
|
||||
infrastructureRawText String? @map("infrastructure_raw_text")
|
||||
infrastructureStatus Json? @map("infrastructure_status")
|
||||
customData Json? @map("custom_data")
|
||||
|
||||
// Trạng thái
|
||||
status String @default("Đang mở bán")
|
||||
redBookStatus String @default("Chưa có dữ liệu") @map("red_book_status")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
contracts Contract[]
|
||||
settlements Settlement[]
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 3. CONTRACT & CUSTOMER
|
||||
// =============================================
|
||||
model Contract {
|
||||
id String @id @default(uuid())
|
||||
productId String @map("product_id")
|
||||
transferOrder Int @default(0) @map("transfer_order")
|
||||
contractType String @default("HĐMB") @map("contract_type")
|
||||
contractNumber String @unique @map("contract_number")
|
||||
signingDate DateTime? @map("signing_date")
|
||||
status String @default("Đang hiệu lực")
|
||||
|
||||
totalValue Decimal @db.Decimal(15, 2) @map("total_value")
|
||||
paidAmount Decimal @default(0) @db.Decimal(15, 2) @map("paid_amount")
|
||||
remainingAmount Decimal @default(0) @db.Decimal(15, 2) @map("remaining_amount")
|
||||
excessAmount Decimal @default(0) @db.Decimal(15, 2) @map("excess_amount") // Tiền dư
|
||||
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
customers ContractCustomer[]
|
||||
appendices Appendix[]
|
||||
payments Payment[]
|
||||
schedule PaymentSchedule?
|
||||
fines PaymentFine[]
|
||||
}
|
||||
|
||||
model Customer {
|
||||
id String @id @default(uuid())
|
||||
cmndCccd String @unique @map("cmnd_cccd")
|
||||
fullName String @map("full_name")
|
||||
phone String?
|
||||
email String?
|
||||
address Json?
|
||||
dob DateTime?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
contracts ContractCustomer[]
|
||||
}
|
||||
|
||||
model ContractCustomer {
|
||||
id BigInt @id @default(autoincrement())
|
||||
contractId String @map("contract_id")
|
||||
customerId String @map("customer_id")
|
||||
role String @default("CHỦ SH 1")
|
||||
transferOrder Int @default(0) @map("transfer_order")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
contract Contract @relation(fields: [contractId], references: [id])
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 4. FINANCE MODULE
|
||||
// =============================================
|
||||
model PaymentTemplate {
|
||||
id String @id @default(uuid())
|
||||
projectId String @map("project_id")
|
||||
name String
|
||||
isDefault Boolean @default(false) @map("is_default")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
items PaymentScheduleItem[]
|
||||
schedules PaymentSchedule[]
|
||||
}
|
||||
|
||||
model PaymentSchedule {
|
||||
id String @id @default(uuid())
|
||||
contractId String @unique @map("contract_id")
|
||||
templateId String @map("template_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
contract Contract @relation(fields: [contractId], references: [id])
|
||||
template PaymentTemplate @relation(fields: [templateId], references: [id])
|
||||
items PaymentScheduleItem[]
|
||||
}
|
||||
|
||||
model PaymentScheduleItem {
|
||||
id String @id @default(uuid())
|
||||
templateId String? @map("template_id")
|
||||
scheduleId String? @map("schedule_id")
|
||||
|
||||
installmentNo Int @map("installment_no")
|
||||
amount Decimal? @db.Decimal(15, 2)
|
||||
percentage Decimal? @db.Decimal(5, 2)
|
||||
|
||||
// Logic ngày đến hạn
|
||||
daysAfterSigning Int? @map("days_after_signing")
|
||||
daysAfterPrevious Int? @map("days_after_previous")
|
||||
dueDate DateTime? @map("due_date")
|
||||
|
||||
type String // QSDD, MONG, THAN...
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
template PaymentTemplate? @relation(fields: [templateId], references: [id])
|
||||
schedule PaymentSchedule? @relation(fields: [scheduleId], references: [id])
|
||||
payments Payment[]
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(uuid())
|
||||
contractId String @map("contract_id")
|
||||
scheduleItemId String? @map("schedule_item_id")
|
||||
|
||||
amount Decimal @db.Decimal(15, 2)
|
||||
paidDate DateTime @map("paid_date")
|
||||
receiptNumber String? @map("receipt_number")
|
||||
method String @default("Chuyển khoản")
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
contract Contract @relation(fields: [contractId], references: [id])
|
||||
scheduleItem PaymentScheduleItem? @relation(fields: [scheduleItemId], references: [id])
|
||||
}
|
||||
|
||||
model PaymentFine {
|
||||
id String @id @default(uuid())
|
||||
contractId String @map("contract_id")
|
||||
amount Decimal @db.Decimal(15, 2)
|
||||
reason String
|
||||
dueDate DateTime @map("due_date")
|
||||
paidDate DateTime? @map("paid_date")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
contract Contract @relation(fields: [contractId], references: [id])
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 5. APPENDIX & SETTLEMENT
|
||||
// =============================================
|
||||
model Appendix {
|
||||
id String @id @default(uuid())
|
||||
contractId String @map("contract_id")
|
||||
productId String @map("product_id")
|
||||
type String
|
||||
applyFromOrder Int @map("apply_from_order")
|
||||
signingDate DateTime @map("signing_date")
|
||||
customData Json? @map("custom_data")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
contract Contract @relation(fields: [contractId], references: [id])
|
||||
}
|
||||
|
||||
model Settlement {
|
||||
id String @id @default(uuid())
|
||||
productId String @map("product_id")
|
||||
type String // MONG, THAN, CP THI CONG
|
||||
tempValue Decimal @db.Decimal(15, 2) @map("temp_value")
|
||||
finalValue Decimal @db.Decimal(15, 2) @map("final_value")
|
||||
difference Decimal @db.Decimal(15, 2)
|
||||
redBookStatus String @map("red_book_status")
|
||||
issueDate DateTime? @map("issue_date")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
}
|
||||
106
tests/Feature/ContractFinanceFlowTest.php
Normal file
106
tests/Feature/ContractFinanceFlowTest.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Filament\Resources\Contracts\ContractResource;
|
||||
use App\Models\Contract;
|
||||
use App\Models\Customer;
|
||||
use App\Models\PaymentSchedule;
|
||||
use App\Models\PaymentScheduleItem;
|
||||
use App\Models\PaymentTemplate;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->actingAs(User::factory()->create());
|
||||
});
|
||||
|
||||
it('can create a contract and automatically generate a payment schedule from template', function () {
|
||||
// 1. Chuẩn bị dữ liệu mẫu
|
||||
$project = Project::factory()->create(['code' => 'TEST-PROJ', 'name' => 'Dự án Kiểm thử']);
|
||||
$product = Product::factory()->create([
|
||||
'project_id' => $project->id,
|
||||
'code' => 'STH03.TEST',
|
||||
'total_price' => 1000000000, // 1 tỷ
|
||||
]);
|
||||
$customer = Customer::factory()->create(['full_name' => 'Khách Hàng Test']);
|
||||
|
||||
// 2. Tạo Mẫu thanh toán (3 đợt: 30% - 30% - 40%)
|
||||
$template = PaymentTemplate::create([
|
||||
'project_id' => $project->id,
|
||||
'name' => 'Mẫu thanh toán 30-30-40',
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// Đợt 1: 30% ngay khi ký (0 ngày sau signing)
|
||||
PaymentScheduleItem::create([
|
||||
'template_id' => $template->id,
|
||||
'installment_no' => 1,
|
||||
'percentage' => 30,
|
||||
'days_after_signing' => 0,
|
||||
'type' => 'QSDD',
|
||||
]);
|
||||
|
||||
// Đợt 2: 30% sau 30 ngày kể từ đợt trước
|
||||
PaymentScheduleItem::create([
|
||||
'template_id' => $template->id,
|
||||
'installment_no' => 2,
|
||||
'percentage' => 30,
|
||||
'days_after_previous' => 30,
|
||||
'type' => 'MONG',
|
||||
]);
|
||||
|
||||
// 3. Thực hiện tạo Hợp đồng qua Livewire Page
|
||||
$signingDate = Carbon::now();
|
||||
|
||||
\Livewire\Livewire::test(\App\Filament\Resources\Contracts\Pages\CreateContract::class)
|
||||
->fillForm([
|
||||
'product_id' => $product->id,
|
||||
'contract_number' => 'HD-TEST-UUID',
|
||||
'contract_type' => 'HĐMB',
|
||||
'signing_date' => $signingDate->format('Y-m-d'),
|
||||
'total_value' => 1000000000,
|
||||
'payment_template_id' => $template->id, // Chọn mẫu thanh toán
|
||||
'customers' => [$customer->id], // Đồng sở hữu
|
||||
])
|
||||
->call('create')
|
||||
->assertHasNoFormErrors();
|
||||
|
||||
// 4. KIỂM TRA KẾT QUẢ TRONG DATABASE
|
||||
|
||||
// A. Hợp đồng phải tồn tại
|
||||
$contract = Contract::where('contract_number', 'HD-TEST-UUID')->first();
|
||||
expect($contract)->not->toBeNull();
|
||||
expect($contract->id)->toBeUuid(); // Kiểm tra khóa chính là UUID
|
||||
|
||||
// B. Kiểm tra bảng trung gian contract_customers (Phải có UUID)
|
||||
$this->assertDatabaseHas('contract_customers', [
|
||||
'contract_id' => $contract->id,
|
||||
'customer_id' => $customer->id,
|
||||
]);
|
||||
|
||||
$pivot = \DB::table('contract_customers')->where('contract_id', $contract->id)->first();
|
||||
expect($pivot->id)->toBeUuid(); // KIỂM TRA QUAN TRỌNG: ID bảng trung gian phải là UUID
|
||||
|
||||
// C. Kiểm tra Lịch trình thanh toán (Payment Schedule)
|
||||
$schedule = PaymentSchedule::where('contract_id', $contract->id)->first();
|
||||
expect($schedule)->not->toBeNull();
|
||||
expect($schedule->template_id)->toBe($template->id);
|
||||
|
||||
// D. Kiểm tra các đợt thanh toán con (Items)
|
||||
$scheduleItems = PaymentScheduleItem::where('schedule_id', $schedule->id)
|
||||
->orderBy('installment_no')
|
||||
->get();
|
||||
|
||||
expect($scheduleItems)->toHaveCount(2);
|
||||
|
||||
// Kiểm tra đợt 1 (30% = 300 triệu)
|
||||
expect((float)$scheduleItems[0]->amount)->toBe(300000000.0);
|
||||
expect($scheduleItems[0]->due_date->format('Y-m-d'))->toBe($signingDate->format('Y-m-d'));
|
||||
|
||||
// Kiểm tra đợt 2 (30% = 300 triệu, sau 30 ngày)
|
||||
expect((float)$scheduleItems[1]->amount)->toBe(300000000.0);
|
||||
expect($scheduleItems[1]->due_date->format('Y-m-d'))->toBe($signingDate->copy()->addDays(30)->format('Y-m-d'));
|
||||
});
|
||||
Reference in New Issue
Block a user