Kimi chinh sua

This commit is contained in:
2026-04-24 08:58:53 +00:00
parent 91ff4a5e4d
commit 86216ef872
43 changed files with 2868 additions and 597 deletions

View File

@@ -4,15 +4,13 @@ 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 App\Services\ContractScheduleService;
use Filament\Schemas\Schema;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use App\Filament\Resources\Contracts\ContractResource\RelationManagers\ScheduleItemsRelationManager;
use App\Filament\Resources\Contracts\Schemas\ContractForm;
class ContractResource extends Resource
@@ -37,6 +35,27 @@ class ContractResource extends Resource
Tables\Columns\TextColumn::make('contract_number')->label('Số HĐ')->searchable(),
Tables\Columns\TextColumn::make('product.code')->label('Sản phẩm'),
Tables\Columns\TextColumn::make('total_value')->label('Giá trị')->money('VND'),
Tables\Columns\TextColumn::make('paid_amount')->label('Đã thu')->money('VND'),
Tables\Columns\TextColumn::make('remaining_amount')->label('Còn lại')->money('VND'),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('generateSchedule')
->label('Tạo lịch TT')
->icon('heroicon-o-calendar-days')
->color('warning')
->requiresConfirmation()
->modalHeading('Tạo lịch thanh toán')
->modalDescription('Hành động này sẽ xóa lịch thanh toán cũ (nếu có) và tạo lại từ mẫu của dự án.')
->action(function (Contract $record) {
try {
ContractScheduleService::generateFromTemplate($record);
} catch (\InvalidArgumentException $e) {
// Filament sẽ tự động hiển thị lỗi nếu throw ra trong action
throw $e;
}
})
->visible(fn (Contract $record) => $record->signing_date !== null),
]);
}

View File

@@ -3,11 +3,8 @@
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 App\Services\ContractScheduleService;
use Filament\Resources\Pages\CreateRecord;
use Carbon\Carbon;
class CreateContract extends CreateRecord
{
@@ -19,42 +16,9 @@ class CreateContract extends CreateRecord
$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;
}
$template = \App\Models\PaymentTemplate::find($templateId);
if ($template) {
ContractScheduleService::generateFromTemplate($contract, $template);
}
}
}

View File

@@ -8,9 +8,12 @@ use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\DatePicker;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Placeholder;
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Support\HtmlString;
class ContractForm
{
@@ -18,63 +21,154 @@ class ContractForm
{
return $schema
->components([
Section::make('Liên kết & Mẫu thanh toán')
->columns(2)
Grid::make(3)
->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);
}
Section::make('Thông tin định danh')
->columnSpan(2)
->columns(2)
->schema([
Select::make('product_id')
->label('Sản phẩm (Lô đất)')
->relationship('product', 'code')
->searchable()
->preload()
->required()
->live()
->afterStateUpdated(function (Set $set, $state) {
if ($state) {
$product = Product::find($state);
if ($product) {
$set('total_value', $product->total_price);
$set('land_value', $product->qsdd_value);
$set('foundation_value', $product->foundation_temp_value);
}
}
}),
TextInput::make('contract_number')
->label('Số HĐMB')
->required()
->unique(ignoreRecord: true),
Select::make('contract_type')
->label('Loại hợp đồng')
->options([
'HĐMB' => 'Hợp đồng mua bán',
'HĐGV' => 'Hợp đồng góp vốn',
'HĐDC' => 'Hợp đồng đặt cọc',
])
->default('HĐMB')
->required(),
TextInput::make('transfer_order')
->label('Thứ tự chuyển nhượng')
->numeric()
->default(0)
->helperText('0 là chủ hiện tại, 1 là F0, 2 là F1...'),
]),
Section::make('Trạng thái')
->columnSpan(1)
->schema([
Select::make('status')
->label('Trạng thái pháp lý')
->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(),
DatePicker::make('signing_date')
->label('Ngày ký HĐ')
->required(),
DatePicker::make('sale_date')
->label('Ngày bán thực tế'),
]),
]),
Section::make('Chi tiết Tài chính & Chiết khấu')
->columns(3)
->schema([
TextInput::make('land_value')
->label('Giá trị QSDĐ')
->numeric()
->prefix('VND')
->live(onBlur: true)
->afterStateUpdated(fn ($state, $get, $set) => $set('total_value', (float)$state + (float)$get('foundation_value'))),
TextInput::make('foundation_value')
->label('Giá trị Móng')
->numeric()
->prefix('VND')
->live(onBlur: true)
->afterStateUpdated(fn ($state, $get, $set) => $set('total_value', (float)$get('land_value') + (float)$state)),
TextInput::make('total_value')
->label('Tổng giá trị niêm yết')
->numeric()
->prefix('VND')
->readOnly(),
Placeholder::make('discount_overview')
->label('Tổng quan chiết khấu (Dữ liệu từ Excel)')
->columnSpanFull()
->content(function ($record) {
if (!$record || !$record->discount_details) return 'Không có chiết khấu';
$details = $record->discount_details;
$html = '<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; background: #f9fafb; padding: 15px; border-radius: 8px; border: 1px solid #e5e7eb;">';
foreach ($details as $key => $val) {
if (empty($val)) continue;
$label = match($key) {
'open_sale' => 'Mở bán',
'multi_lot' => 'Số nhiều',
'wholesale' => 'Mua sỉ',
'ctv' => 'Cộng tác viên',
'full_payment' => 'Trả 1 lần',
'total_amount' => 'Tổng tiền CK',
'total_percentage' => 'Tổng % CK',
default => $key
};
$style = str_contains($key, 'total') ? 'font-weight: bold; color: #16a34a;' : 'color: #4b5563;';
$html .= "<div>
<div style='font-size: 0.7rem; color: #9ca3af; text-transform: uppercase; margin-bottom: 4px;'>{$label}</div>
<div style='{$style} font-size: 0.9rem;'>{$val}</div>
</div>";
}
$html .= '</div>';
return new HtmlString($html);
}),
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()
KeyValue::make('discount_details')
->label('Bảng chi tiết chiết khấu (Dạng Key-Value)')
->columnSpanFull(),
]),
Section::make('Chi tiết Hợp đồng')
Section::make('Thông tin quản lý & Khách hà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()
Select::make('customers')
->label('Khách hàng đứng tên')
->multiple()
->relationship('customers', 'full_name')
->preload()
->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(),
])
->columnSpanFull(),
TextInput::make('brokerage_name')
->label('Đơn vị môi giới'),
DatePicker::make('hql_confirmation_date')
->label('Ngày HQL xác nhận'),
TextInput::make('stored_contract_count')
->label('Số lượng HĐ lưu')
->numeric()
->default(0),
TextInput::make('filing_note')
->label('Ghi chú hồ sơ')
->columnSpanFull(),
Select::make('payment_template_id')
->label('Áp dụng mẫu thanh toán')
->placeholder('Chọn mẫu để tự động tạo lịch trình...')
->options(PaymentTemplate::pluck('name', 'id'))
->searchable()
->dehydrated(false)
->helperText('Lưu ý: Chỉ chọn nếu bạn muốn khởi tạo lại lịch trình thanh toán.'),
]),
]);
}
}

View File

@@ -5,6 +5,8 @@ namespace App\Filament\Resources\Contracts\Tables;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\BadgeColumn;
use Filament\Tables\Table;
class ContractsTable
@@ -13,18 +15,70 @@ class ContractsTable
{
return $table
->columns([
//
TextColumn::make('contract_number')
->label('Số HĐMB')
->searchable()
->sortable()
->copyable()
->description(fn ($record) => "Lô: {$record->product?->code}"),
TextColumn::make('customers.full_name')
->label('Khách hàng')
->searchable()
->listWithLineBreaks()
->bulleted(),
TextColumn::make('signing_date')
->label('Ngày ký')
->date('d/m/Y')
->sortable(),
TextColumn::make('total_value')
->label('Giá trị HĐ')
->money('VND')
->sortable()
->summarize(\Filament\Tables\Columns\Summarizers\Sum::make()->label('Tổng doanh thu')->money('VND')),
TextColumn::make('transfer_order')
->label('Đời CN')
->badge()
->color(fn ($state) => $state == 0 ? 'success' : 'gray')
->formatStateUsing(fn ($state) => $state == 0 ? 'Hiện tại' : "F{$state}")
->alignCenter(),
TextColumn::make('status')
->label('Trạng thái')
->badge()
->color(fn (string $state): string => match ($state) {
'Đang hiệu lực' => 'success',
'Đã hoàn thành' => 'primary',
'Đã hủy' => 'danger',
default => 'gray',
}),
])
->filters([
//
\Filament\Tables\Filters\SelectFilter::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',
]),
\Filament\Tables\Filters\TernaryFilter::make('is_current')
->label('Chủ sở hữu hiện tại')
->queries(
true: fn ($query) => $query->where('transfer_order', 0),
false: fn ($query) => $query->where('transfer_order', '>', 0),
)
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
->bulkActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
])
->defaultSort('created_at', 'desc');
}
}