Đóng gói cuối tuần
This commit is contained in:
18
app/Enums/NavigationGroup.php
Normal file
18
app/Enums/NavigationGroup.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum NavigationGroup: string
|
||||
{
|
||||
case PROJECT = 'Quản lý Dự án';
|
||||
case WAREHOUSE = 'Quản lý Kho';
|
||||
case CUSTOMER = 'Quản lý Khách hàng';
|
||||
case TRANSACTION = 'Quản lý Giao dịch';
|
||||
case FINANCE = 'Quản lý Dòng tiền';
|
||||
case SETTING = 'Cấu hình Hệ thống';
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
27
app/Enums/PaymentType.php
Normal file
27
app/Enums/PaymentType.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum PaymentType: string
|
||||
{
|
||||
case QSDD = 'QSDD';
|
||||
case MONG = 'MONG';
|
||||
case THAN = 'THAN';
|
||||
case CHI_PHI_TC = 'CHI_PHI_TC';
|
||||
case CK = 'CK';
|
||||
case PHAT = 'PHAT';
|
||||
case OTHER = 'OTHER';
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match($this) {
|
||||
self::QSDD => 'Tiền QSDĐ',
|
||||
self::MONG => 'Tiền Móng',
|
||||
self::THAN => 'Tiền Thân',
|
||||
self::CHI_PHI_TC => 'Chi phí thi công',
|
||||
self::CK => 'Chiết khấu',
|
||||
self::PHAT => 'Tiền phạt',
|
||||
self::OTHER => 'Khác',
|
||||
};
|
||||
}
|
||||
}
|
||||
25
app/Enums/ProductType.php
Normal file
25
app/Enums/ProductType.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ProductType: string
|
||||
{
|
||||
case LAND = 'LAND';
|
||||
case APARTMENT = 'APARTMENT';
|
||||
case SHOPHOUSE = 'SHOPHOUSE';
|
||||
case OFFICE = 'OFFICE';
|
||||
case CONDOTEL = 'CONDOTEL';
|
||||
case VILLA = 'VILLA';
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match($this) {
|
||||
self::LAND => 'Đất nền',
|
||||
self::APARTMENT => 'Căn hộ',
|
||||
self::SHOPHOUSE => 'Shophouse',
|
||||
self::OFFICE => 'Văn phòng',
|
||||
self::CONDOTEL => 'Condotel',
|
||||
self::VILLA => 'Biệt thự',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ namespace App\Filament\Resources\Contracts;
|
||||
|
||||
use App\Filament\Resources\Contracts\Pages;
|
||||
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;
|
||||
@@ -12,40 +15,38 @@ 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;
|
||||
|
||||
class ContractResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Contract::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-document-text';
|
||||
protected static string | \UnitEnum | null $navigationGroup = 'Quản lý Giao dịch';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::TRANSACTION->value;
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
protected static ?string $modelLabel = 'Hợp đồng';
|
||||
protected static ?string $pluralModelLabel = 'Hợp đồng';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Thông tin Liên kết')
|
||||
Section::make('Liên kết & Mẫu thanh toán')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('product_id')->label('Mã Sản phẩm')->relationship('product', 'code')->searchable()->preload()->required(),
|
||||
Select::make('customers')->label('Khách hàng')->relationship('customers', 'full_name')->multiple()->searchable()->preload()->required(),
|
||||
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ợp đồng')->required()->unique(ignoreRecord: true),
|
||||
Select::make('contract_type')->label('Loại Hợp đồng')->options(['HĐMB' => 'HĐ Mua bán', 'HĐĐC' => 'HĐ Đặt cọc', 'HĐGV' => 'HĐ Góp vốn', 'VBCN' => 'VBCN'])->default('HĐMB'),
|
||||
DatePicker::make('signing_date')->label('Ngày ký')->displayFormat('d/m/Y'),
|
||||
TextInput::make('transfer_order')->label('Thứ tự chuyển nhượng')->numeric()->default(0),
|
||||
Select::make('status')->label('Trạng thái')
|
||||
->options(['Đang hiệu lực' => 'Đang hiệu lực', 'Đã thanh lý' => 'Đã thanh lý', 'Đã chuyển nhượng' => 'Đã chuyển nhượng'])
|
||||
->default('Đang hiệu lực'),
|
||||
]),
|
||||
Section::make('Tài chính')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('total_value')->label('Tổng giá trị (VND)')->numeric()->required(),
|
||||
TextInput::make('paid_amount')->label('Đã thanh toán (VND)')->numeric()->default(0),
|
||||
]),
|
||||
TextInput::make('contract_number')->label('Số HĐ')->required(),
|
||||
DatePicker::make('signing_date')->label('Ngày ký')->required(),
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -54,23 +55,13 @@ class ContractResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('contract_number')->label('Số HĐ')->searchable(),
|
||||
Tables\Columns\TextColumn::make('contract_type')->label('Loại HĐ')->badge(),
|
||||
Tables\Columns\TextColumn::make('product.code')->label('Mã SP')->searchable(),
|
||||
Tables\Columns\TextColumn::make('customers.full_name')->label('Khách hàng')->badge(),
|
||||
Tables\Columns\TextColumn::make('status')->label('Trạng thái')
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'Đang hiệu lực' => 'success',
|
||||
'Đã thanh lý' => 'warning',
|
||||
'Đã chuyển nhượng' => 'gray',
|
||||
default => 'gray',
|
||||
})->badge(),
|
||||
Tables\Columns\TextColumn::make('total_value')->label('Tổng giá trị')->money('VND'),
|
||||
Tables\Columns\TextColumn::make('paid_amount')->label('Đã TT')->money('VND'),
|
||||
Tables\Columns\TextColumn::make('remaining_amount')->label('Còn lại')->money('VND')->color('danger'),
|
||||
])
|
||||
->defaultSort('created_at', 'desc');
|
||||
Tables\Columns\TextColumn::make('product.code')->label('Sản phẩm'),
|
||||
Tables\Columns\TextColumn::make('total_value')->label('Giá trị')->money('VND'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array { return [ScheduleItemsRelationManager::class]; }
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Contracts\ContractResource\RelationManagers;
|
||||
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
|
||||
class ScheduleItemsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'scheduleItems';
|
||||
|
||||
protected static ?string $title = 'Lịch thanh toán thực tế';
|
||||
|
||||
protected static ?string $modelLabel = 'Đợt thanh toán';
|
||||
protected static ?string $pluralModelLabel = 'Đợt thanh toán';
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Chi tiết đợt thanh toán')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Placeholder::make('installment_no')->label('Đợt số')->content(fn ($record) => $record->installment_no),
|
||||
Placeholder::make('type')->label('Loại đợt')->content(fn ($record) => $record->type?->getLabel()),
|
||||
TextInput::make('amount')->label('Số tiền (VND)')->numeric()->required(),
|
||||
DatePicker::make('due_date')->label('Ngày đến hạn')->required(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('installment_no')->label('Đợt')->badge(),
|
||||
TextColumn::make('type')->label('Loại')->badge(),
|
||||
TextColumn::make('amount')->label('Số tiền dự kiến')->money('VND'),
|
||||
TextColumn::make('due_date')->label('Hạn thanh toán')->date('d/m/Y'),
|
||||
])
|
||||
->defaultSort('installment_no', 'asc')
|
||||
->actions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,59 @@
|
||||
namespace App\Filament\Resources\Contracts\Pages;
|
||||
|
||||
use App\Filament\Resources\Contracts\ContractResource;
|
||||
use App\Models\PaymentTemplate;
|
||||
use App\Models\PaymentSchedule;
|
||||
use App\Models\PaymentScheduleItem;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CreateContract extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ContractResource::class;
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
$contract = $this->record;
|
||||
$templateId = $this->data['payment_template_id'] ?? null;
|
||||
|
||||
if ($templateId) {
|
||||
$template = PaymentTemplate::find($templateId);
|
||||
|
||||
// 1. Tạo Schedule cho Hợp đồng
|
||||
$schedule = PaymentSchedule::create([
|
||||
'contract_id' => $contract->id,
|
||||
'template_id' => $template->id,
|
||||
]);
|
||||
|
||||
// 2. Clone từng Item từ Template sang Schedule thực tế
|
||||
$items = $template->items()->orderBy('installment_no')->get();
|
||||
$lastDueDate = Carbon::parse($contract->signing_date);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$dueDate = null;
|
||||
|
||||
// Logic tính ngày đến hạn chuẩn v5.5
|
||||
if ($item->days_after_signing !== null) {
|
||||
$dueDate = Carbon::parse($contract->signing_date)->addDays($item->days_after_signing);
|
||||
} elseif ($item->days_after_previous !== null) {
|
||||
$dueDate = $lastDueDate->copy()->addDays($item->days_after_previous);
|
||||
} elseif ($item->due_date !== null) {
|
||||
$dueDate = $item->due_date;
|
||||
}
|
||||
|
||||
PaymentScheduleItem::create([
|
||||
'schedule_id' => $schedule->id,
|
||||
'installment_no' => $item->installment_no,
|
||||
'type' => $item->type,
|
||||
'percentage' => $item->percentage,
|
||||
'amount' => $contract->total_value * ($item->percentage / 100),
|
||||
'due_date' => $dueDate,
|
||||
]);
|
||||
|
||||
if ($dueDate) {
|
||||
$lastDueDate = $dueDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
use App\Enums\NavigationGroup;
|
||||
|
||||
class CustomerResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Customer::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-users';
|
||||
protected static string | \UnitEnum | null $navigationGroup = 'Quản lý Khách hàng';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::CUSTOMER->value;
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
|
||||
70
app/Filament/Resources/PaymentTemplateResource.php
Normal file
70
app/Filament/Resources/PaymentTemplateResource.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\PaymentTemplateResource\Pages;
|
||||
use App\Models\PaymentTemplate;
|
||||
use App\Enums\NavigationGroup;
|
||||
use App\Enums\PaymentType;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PaymentTemplateResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PaymentTemplate::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-table-cells';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::FINANCE->value;
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
protected static ?string $modelLabel = 'Mẫu thanh toán';
|
||||
protected static ?string $pluralModelLabel = 'Mẫu thanh toán';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Thông tin mẫu')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('project_id')->label('Dự án')->relationship('project', 'name')->required(),
|
||||
TextInput::make('name')->label('Tên mẫu')->required(),
|
||||
]),
|
||||
Section::make('Chi tiết đợt')
|
||||
->schema([
|
||||
Repeater::make('items')
|
||||
->label('Danh sách đợt')
|
||||
->relationship('items')
|
||||
->schema([
|
||||
TextInput::make('installment_no')->label('Đợt')->numeric()->required(),
|
||||
Select::make('type')->label('Loại')->options(PaymentType::class)->required(),
|
||||
TextInput::make('percentage')->label('%')->numeric()->required(),
|
||||
])->columns(3),
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('project.name')->label('Dự án'),
|
||||
Tables\Columns\TextColumn::make('name')->label('Tên mẫu'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPaymentTemplates::route('/'),
|
||||
'create' => Pages\CreatePaymentTemplate::route('/create'),
|
||||
'edit' => Pages\EditPaymentTemplate::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentTemplateResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentTemplateResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePaymentTemplate extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PaymentTemplateResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentTemplateResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentTemplateResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPaymentTemplate extends EditRecord
|
||||
{
|
||||
protected static string $resource = PaymentTemplateResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentTemplateResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentTemplateResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPaymentTemplates extends ListRecords
|
||||
{
|
||||
protected static string $resource = PaymentTemplateResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,14 @@ 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;
|
||||
@@ -18,72 +22,38 @@ class ProductResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Product::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-squares-2x2';
|
||||
protected static string | \UnitEnum | null $navigationGroup = 'Quản lý kho';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::WAREHOUSE->value;
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
protected static ?string $modelLabel = 'Sản phẩm';
|
||||
protected static ?string $pluralModelLabel = 'Sản phẩm';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Liên kết Dự án & Phân loại')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('project_id')
|
||||
->label('Thuộc Dự án')
|
||||
->relationship('project', 'name')
|
||||
->searchable()->preload()->required(),
|
||||
Select::make('product_type')
|
||||
->label('Loại sản phẩm')
|
||||
->options([
|
||||
'LAND' => 'Đất nền',
|
||||
'APARTMENT' => 'Căn hộ chung cư',
|
||||
'SHOPHOUSE' => 'Shophouse',
|
||||
])->required()->live(),
|
||||
]),
|
||||
|
||||
Section::make('Thông tin định danh & Diện tích')
|
||||
->columns(3)
|
||||
->schema([
|
||||
TextInput::make('code')->label('Mã Sản Phẩm')->required()->unique(ignoreRecord: true),
|
||||
TextInput::make('area')->label('Diện tích (m2)')->numeric()->required()->live(onBlur: true)
|
||||
->afterStateUpdated(function (Get $get, $state, $set) {
|
||||
$price = (float) $get('price_per_unit');
|
||||
if ($state && $price) $set('total_price', (float) $state * $price);
|
||||
}),
|
||||
Select::make('red_book_status')->label('Sổ đỏ')
|
||||
->options(['Chưa có dữ liệu' => 'Chưa có dữ liệu', 'Đã có sổ' => 'Đã có sổ', 'Đang chờ sổ' => 'Đang chờ sổ'])
|
||||
->default('Chưa có dữ liệu'),
|
||||
]),
|
||||
|
||||
Section::make('Thông tin Tài chính')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('price_per_unit')->label('Đơn giá (VND/m2)')->numeric()->live(onBlur: true)
|
||||
->afterStateUpdated(function (Get $get, $state, $set) {
|
||||
$area = (float) $get('area');
|
||||
if ($state && $area) $set('total_price', (float) $state * $area);
|
||||
}),
|
||||
TextInput::make('total_price')->label('Tổng giá trị niêm yết')->numeric()->required(),
|
||||
]),
|
||||
|
||||
Section::make('Thông tin chi tiết')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('custom_data.block')->label('Block/Tòa')->visible(fn (Get $get) => $get('product_type') === 'APARTMENT'),
|
||||
TextInput::make('custom_data.floor')->label('Tầng')->numeric()->visible(fn (Get $get) => $get('product_type') === 'APARTMENT'),
|
||||
TextInput::make('custom_data.view')->label('Hướng View')->visible(fn (Get $get) => $get('product_type') === 'APARTMENT'),
|
||||
TextInput::make('custom_data.road_width')->label('Đường (m)')->visible(fn (Get $get) => in_array($get('product_type'), ['LAND', 'SHOPHOUSE'])),
|
||||
TextInput::make('custom_data.frontage')->label('Số mặt tiền')->numeric()->visible(fn (Get $get) => in_array($get('product_type'), ['LAND', 'SHOPHOUSE'])),
|
||||
]),
|
||||
|
||||
Section::make('Trạng thái kinh doanh')
|
||||
->schema([
|
||||
Select::make('status')
|
||||
->label('Tình trạng rổ hàng')
|
||||
->options(['Đang mở bán' => 'Đang mở bán', 'Đã giữ chỗ' => 'Đã giữ chỗ', 'Đã cọc' => 'Đã cọc', 'Đã bán' => 'Đã bán', 'Tạm khóa' => 'Tạm khóa'])
|
||||
->default('Đang mở bán')->required(),
|
||||
]),
|
||||
|
||||
// Đã loại bỏ phần Lịch sử Giao dịch nhúng thủ công tại đây để tránh trùng lặp
|
||||
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()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -91,26 +61,15 @@ class ProductResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('project.name')->label('Dự án')->sortable()->searchable(),
|
||||
Tables\Columns\TextColumn::make('code')->label('Mã SP')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('contracts_count')->label('Số HĐ')->counts('contracts')->badge()->color('info'),
|
||||
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á niêm yết')->money('VND')->sortable(),
|
||||
Tables\Columns\SelectColumn::make('status')->label('Trạng thái')
|
||||
->options(['Đang mở bán' => 'Đang mở bán', 'Đã giữ chỗ' => 'Đã giữ chỗ', 'Đã cọc' => 'Đã cọc', 'Đã bán' => 'Đã bán', 'Tạm khóa' => 'Tạm khóa']),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('project_id')->label('Dự án')->relationship('project', 'name'),
|
||||
Tables\Filters\SelectFilter::make('product_type')->options(['LAND' => 'Đất nền', 'APARTMENT' => 'Căn hộ']),
|
||||
Tables\Columns\TextColumn::make('total_price')->label('Giá')->money('VND'),
|
||||
Tables\Columns\TextColumn::make('status')->label('Trạng thái')->badge(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
ContractsRelationManager::class,
|
||||
];
|
||||
}
|
||||
public static function getRelations(): array { return [ContractsRelationManager::class]; }
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
|
||||
@@ -11,12 +11,17 @@ use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use App\Enums\NavigationGroup;
|
||||
|
||||
class ProjectResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Project::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-building-office-2';
|
||||
protected static string | \UnitEnum | null $navigationGroup = 'Quản lý Dự án';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::PROJECT->value;
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
protected static ?string $modelLabel = 'Dự án';
|
||||
protected static ?string $pluralModelLabel = 'Dự án';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
@@ -25,9 +30,16 @@ class ProjectResource extends Resource
|
||||
Section::make('Thông tin Dự án')
|
||||
->columns(2)
|
||||
->schema([
|
||||
TextInput::make('name')->label('Tên Dự án')->required()->maxLength(255),
|
||||
Select::make('type')->label('Loại hình')->options(['Khu đô thị' => 'Khu đô thị', 'Chung cư' => 'Chung cư', 'Đất nền phân lô' => 'Đất nền phân lô', 'Khu nghỉ dưỡng' => 'Khu nghỉ dưỡng'])->required(),
|
||||
TextInput::make('address')->label('Địa chỉ chi tiết')->columnSpanFull(),
|
||||
TextInput::make('code')->label('Mã Dự án')->required()->unique(ignoreRecord: true),
|
||||
TextInput::make('name')->label('Tên Dự án')->required(),
|
||||
Select::make('type')
|
||||
->label('Loại hình')
|
||||
->options([
|
||||
'Khu đô thị' => 'Khu đô thị',
|
||||
'Chung cư' => 'Chung cư',
|
||||
'Đất nền phân lô' => 'Đất nền phân lô',
|
||||
])->required(),
|
||||
TextInput::make('address')->label('Địa chỉ')->columnSpanFull(),
|
||||
])
|
||||
]);
|
||||
}
|
||||
@@ -36,12 +48,10 @@ class ProjectResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->label('Tên Dự án')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('type')->label('Loại hình')->badge()->sortable(),
|
||||
Tables\Columns\TextColumn::make('address')->label('Địa chỉ')->limit(50),
|
||||
Tables\Columns\TextColumn::make('created_at')->label('Ngày tạo')->dateTime('d/m/Y')->sortable()->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->defaultSort('created_at', 'desc');
|
||||
Tables\Columns\TextColumn::make('code')->label('Mã')->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')->label('Tên Dự án')->searchable(),
|
||||
Tables\Columns\TextColumn::make('type')->label('Loại hình')->badge(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
|
||||
31
app/Models/Appendix.php
Normal file
31
app/Models/Appendix.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Appendix extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $table = 'appendices'; // Đảm bảo khớp với migration
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'custom_data' => 'array',
|
||||
'signing_date' => 'date',
|
||||
];
|
||||
|
||||
public function contract()
|
||||
{
|
||||
return $this->belongsTo(Contract::class);
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
|
||||
class Contract extends Model
|
||||
{
|
||||
@@ -12,15 +13,59 @@ class Contract extends Model
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
// 1 Hợp đồng thuộc về 1 Sản phẩm
|
||||
protected $casts = [
|
||||
'metadata' => 'array',
|
||||
'total_value' => 'decimal:2',
|
||||
'paid_amount' => 'decimal:2',
|
||||
'remaining_amount' => 'decimal:2',
|
||||
'excess_amount' => 'decimal:2',
|
||||
'signing_date' => 'date',
|
||||
];
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
// 1 Hợp đồng có thể có nhiều Khách hàng (Đồng sở hữu)
|
||||
public function customers()
|
||||
{
|
||||
return $this->belongsToMany(Customer::class, 'contract_customers')->withPivot('role', 'transfer_order')->withTimestamps();
|
||||
return $this->belongsToMany(Customer::class, 'contract_customers')
|
||||
->withPivot('role', 'transfer_order')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
|
||||
public function appendices()
|
||||
{
|
||||
return $this->hasMany(Appendix::class);
|
||||
}
|
||||
|
||||
public function paymentSchedule()
|
||||
{
|
||||
return $this->hasOne(PaymentSchedule::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy trực tiếp các đợt thanh toán của hợp đồng này
|
||||
*/
|
||||
public function scheduleItems(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
PaymentScheduleItem::class,
|
||||
PaymentSchedule::class,
|
||||
'contract_id', // Khóa ngoại trên bảng PaymentSchedule
|
||||
'schedule_id', // Khóa ngoại trên bảng PaymentScheduleItem
|
||||
'id', // Khóa chính trên bảng Contract
|
||||
'id' // Khóa chính trên bảng PaymentSchedule
|
||||
);
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany(Payment::class);
|
||||
}
|
||||
|
||||
public function paymentFines()
|
||||
{
|
||||
return $this->hasMany(PaymentFine::class);
|
||||
}
|
||||
}
|
||||
|
||||
30
app/Models/Payment.php
Normal file
30
app/Models/Payment.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Payment extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'decimal:2',
|
||||
'paid_date' => 'date',
|
||||
'metadata' => 'array',
|
||||
];
|
||||
|
||||
public function contract()
|
||||
{
|
||||
return $this->belongsTo(Contract::class);
|
||||
}
|
||||
|
||||
public function scheduleItem()
|
||||
{
|
||||
return $this->belongsTo(PaymentScheduleItem::class, 'schedule_item_id');
|
||||
}
|
||||
}
|
||||
25
app/Models/PaymentFine.php
Normal file
25
app/Models/PaymentFine.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PaymentFine extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'decimal:2',
|
||||
'due_date' => 'date',
|
||||
'paid_date' => 'date',
|
||||
];
|
||||
|
||||
public function contract()
|
||||
{
|
||||
return $this->belongsTo(Contract::class);
|
||||
}
|
||||
}
|
||||
29
app/Models/PaymentSchedule.php
Normal file
29
app/Models/PaymentSchedule.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PaymentSchedule extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function contract()
|
||||
{
|
||||
return $this->belongsTo(Contract::class);
|
||||
}
|
||||
|
||||
public function template()
|
||||
{
|
||||
return $this->belongsTo(PaymentTemplate::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(PaymentScheduleItem::class, 'schedule_id');
|
||||
}
|
||||
}
|
||||
37
app/Models/PaymentScheduleItem.php
Normal file
37
app/Models/PaymentScheduleItem.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\PaymentType;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PaymentScheduleItem extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'type' => PaymentType::class,
|
||||
'amount' => 'decimal:2',
|
||||
'percentage' => 'decimal:2',
|
||||
'due_date' => 'date',
|
||||
];
|
||||
|
||||
public function template()
|
||||
{
|
||||
return $this->belongsTo(PaymentTemplate::class);
|
||||
}
|
||||
|
||||
public function schedule()
|
||||
{
|
||||
return $this->belongsTo(PaymentSchedule::class);
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany(Payment::class, 'schedule_item_id');
|
||||
}
|
||||
}
|
||||
28
app/Models/PaymentTemplate.php
Normal file
28
app/Models/PaymentTemplate.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PaymentTemplate extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'is_default' => 'boolean',
|
||||
];
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(PaymentScheduleItem::class, 'template_id');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ProductType;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -12,20 +13,33 @@ class Product extends Model
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
// Ép kiểu JSON để Filament có thể đọc/ghi dữ liệu linh hoạt (tầng, hướng, mặt tiền...)
|
||||
protected $casts = [
|
||||
'product_type' => ProductType::class,
|
||||
'custom_data' => 'array',
|
||||
'infrastructure_status' => 'array',
|
||||
'area' => 'decimal:2',
|
||||
'price_per_unit' => 'decimal:2',
|
||||
'total_price' => 'decimal:2',
|
||||
'building_density' => 'decimal:2',
|
||||
];
|
||||
|
||||
// Khai báo mối quan hệ V3: 1 Sản phẩm thuộc về 1 Dự án
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
// Đón đầu cho các bước tiếp theo: 1 Sản phẩm có nhiều Hợp đồng
|
||||
public function contracts()
|
||||
{
|
||||
return $this->hasMany(Contract::class);
|
||||
}
|
||||
}
|
||||
|
||||
public function settlements()
|
||||
{
|
||||
return $this->hasMany(Settlement::class);
|
||||
}
|
||||
|
||||
public function appendices()
|
||||
{
|
||||
return $this->hasMany(Appendix::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@ class Project extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $guarded = []; // Cho phép lưu tất cả các cột
|
||||
protected $guarded = [];
|
||||
|
||||
// Khai báo mối quan hệ: 1 Dự án có nhiều Sản phẩm
|
||||
public function products()
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
}
|
||||
|
||||
public function paymentTemplates()
|
||||
{
|
||||
return $this->hasMany(PaymentTemplate::class);
|
||||
}
|
||||
}
|
||||
|
||||
26
app/Models/Settlement.php
Normal file
26
app/Models/Settlement.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Settlement extends Model
|
||||
{
|
||||
use HasUuids, HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'temp_value' => 'decimal:2',
|
||||
'final_value' => 'decimal:2',
|
||||
'difference' => 'decimal:2',
|
||||
'issue_date' => 'date',
|
||||
];
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user