Hoan thien core finance v2 - Calculation Pipeline, Form Templates
This commit is contained in:
@@ -138,22 +138,33 @@ class ContractForm
|
||||
->label('Bảng chi tiết chiết khấu (Dạng Key-Value)')
|
||||
->columnSpanFull(),
|
||||
|
||||
Placeholder::make('final_value_display')
|
||||
->label('Giá trị sau chiết khấu')
|
||||
Placeholder::make('price_sheet')
|
||||
->label('Phiếu tính giá')
|
||||
->columnSpanFull()
|
||||
->content(function ($record, $get) {
|
||||
$totalValue = $record ? (float) $record->total_value : (float) ($get('total_value') ?? 0);
|
||||
$discountDetails = $record ? $record->discount_details : ($get('discount_details') ?? []);
|
||||
|
||||
if ($totalValue <= 0) {
|
||||
return new HtmlString("<div style='font-size: 0.9rem; color: #9ca3af;'>Chưa có giá trị hợp đồng để tính chiết khấu.</div>");
|
||||
->content(function ($record) {
|
||||
if (! $record || ! $record->calculation_log) {
|
||||
return new HtmlString("<div style='font-size: 0.9rem; color: #9ca3af;'>Chưa có dữ liệu tính toán. Vui lòng lưu hợp đồng để tạo phiếu tính giá.</div>");
|
||||
}
|
||||
|
||||
$result = \App\Services\DiscountEngine::calculate($totalValue, $discountDetails);
|
||||
$final = number_format($result['final_value']);
|
||||
$discount = number_format($result['discount_amount']);
|
||||
$steps = $record->calculation_log['price_sheet'] ?? [];
|
||||
if (empty($steps)) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return new HtmlString("<div style='font-size: 1.1rem; font-weight: bold; color: #16a34a;'>{$final} VNĐ</div><div style='font-size: 0.8rem; color: #9ca3af;'>Đã chiết khấu: {$discount} VNĐ</div>");
|
||||
$html = '<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px;">';
|
||||
$html .= '<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem;">';
|
||||
$html .= '<thead><tr style="border-bottom: 2px solid #e5e7eb;"><th style="text-align: left; padding: 8px;">Diễn giải</th><th style="text-align: right; padding: 8px;">Giá trị (VNĐ)</th></tr></thead><tbody>';
|
||||
|
||||
foreach ($steps as $step) {
|
||||
$desc = $step['description'];
|
||||
$value = number_format($step['value']);
|
||||
$isOverride = $step['is_overridden'] ? ' <span style="color: #f59e0b; font-size: 0.75rem;">(ghi đè)</span>' : '';
|
||||
$style = str_contains(strtolower($desc), 'tổng') ? 'font-weight: bold; border-top: 1px solid #e5e7eb;' : '';
|
||||
$html .= "<tr style='{$style}'><td style='padding: 8px;'>{$desc}{$isOverride}</td><td style='text-align: right; padding: 8px;'>{$value}</td></tr>";
|
||||
}
|
||||
|
||||
$html .= '</tbody></table></div>';
|
||||
return new HtmlString($html);
|
||||
}),
|
||||
]),
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\FormTemplates;
|
||||
|
||||
use App\Filament\Resources\FormTemplates\Pages;
|
||||
use App\Models\FormTemplate;
|
||||
use App\Enums\NavigationGroup;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Resources\FormTemplates\Schemas\FormTemplateForm;
|
||||
use App\Filament\Resources\FormTemplates\Tables\FormTemplatesTable;
|
||||
|
||||
class FormTemplateResource extends Resource
|
||||
{
|
||||
protected static ?string $model = FormTemplate::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-document-duplicate';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::SETTING->value;
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
protected static ?string $modelLabel = 'Biểu mẫu';
|
||||
protected static ?string $pluralModelLabel = 'Biểu mẫu in ấn';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return FormTemplateForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return FormTemplatesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListFormTemplates::route('/'),
|
||||
'create' => Pages\CreateFormTemplate::route('/create'),
|
||||
'edit' => Pages\EditFormTemplate::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\FormTemplates\Pages;
|
||||
|
||||
use App\Filament\Resources\FormTemplates\FormTemplateResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateFormTemplate extends CreateRecord
|
||||
{
|
||||
protected static string $resource = FormTemplateResource::class;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\FormTemplates\Pages;
|
||||
|
||||
use App\Filament\Resources\FormTemplates\FormTemplateResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditFormTemplate extends EditRecord
|
||||
{
|
||||
protected static string $resource = FormTemplateResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\FormTemplates\Pages;
|
||||
|
||||
use App\Filament\Resources\FormTemplates\FormTemplateResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListFormTemplates extends ListRecords
|
||||
{
|
||||
protected static string $resource = FormTemplateResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\FormTemplates\Schemas;
|
||||
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class FormTemplateForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
// BLOCK 1: Thông tin cơ bản
|
||||
Section::make('Thông tin biểu mẫu')
|
||||
->schema([
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Tên biểu mẫu')
|
||||
->required(),
|
||||
|
||||
TextInput::make('code')
|
||||
->label('Mã biểu mẫu')
|
||||
->required()
|
||||
->unique(ignoreRecord: true),
|
||||
|
||||
Select::make('target_model')
|
||||
->label('Áp dụng cho')
|
||||
->options([
|
||||
'App\Models\Contract' => 'Hợp đồng',
|
||||
'App\Models\Product' => 'Sản phẩm',
|
||||
'App\Models\Customer' => 'Khách hàng',
|
||||
])
|
||||
->required(),
|
||||
]),
|
||||
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Select::make('paper_size')
|
||||
->label('Khổ giấy')
|
||||
->options([
|
||||
'A4' => 'A4',
|
||||
'A5' => 'A5',
|
||||
'Letter' => 'Letter',
|
||||
])
|
||||
->default('A4')
|
||||
->required(),
|
||||
]),
|
||||
]),
|
||||
|
||||
// BLOCK 2: Danh sách trường dữ liệu
|
||||
Section::make('Danh sách trường dữ liệu (Merge Fields)')
|
||||
->schema([
|
||||
Repeater::make('fields')
|
||||
->relationship('fields')
|
||||
->schema([
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
TextInput::make('code')
|
||||
->label('Mã trường')
|
||||
->required()
|
||||
->placeholder('ten_khach_hang'),
|
||||
|
||||
TextInput::make('label')
|
||||
->label('Tên hiển thị')
|
||||
->required()
|
||||
->placeholder('Tên khách hàng'),
|
||||
|
||||
Select::make('source_type')
|
||||
->label('Nguồn dữ liệu')
|
||||
->options([
|
||||
'db_column' => 'Cột trong DB',
|
||||
'db_relation' => 'Quan hệ (relation)',
|
||||
'formula' => 'Công thức tính toán',
|
||||
'input' => 'Nhập tay khi in',
|
||||
'static' => 'Giá trị cố định',
|
||||
])
|
||||
->required()
|
||||
->live(),
|
||||
]),
|
||||
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
KeyValue::make('source_config')
|
||||
->label('Cấu hình nguồn')
|
||||
->keyLabel('Tham số')
|
||||
->valueLabel('Giá trị')
|
||||
->helperText(fn ($get) => match ($get('source_type')) {
|
||||
'db_column' => 'Ví dụ: column => land_value',
|
||||
'db_relation' => 'Ví dụ: relation => customers, column => full_name, index => 0',
|
||||
'formula' => 'Ví dụ: expression => land_value + foundation_value',
|
||||
'input' => 'Ví dụ: default => (giá trị mặc định)',
|
||||
'static' => 'Ví dụ: value => Hà Nội',
|
||||
default => 'Nhập cấu hình phù hợp với loại nguồn dữ liệu',
|
||||
}),
|
||||
|
||||
Select::make('format')
|
||||
->label('Định dạng hiển thị')
|
||||
->options([
|
||||
'text' => 'Văn bản',
|
||||
'number' => 'Số',
|
||||
'currency' => 'Tiền tệ (VNĐ)',
|
||||
'date' => 'Ngày tháng',
|
||||
'percent' => 'Phần trăm',
|
||||
])
|
||||
->default('text'),
|
||||
|
||||
TextInput::make('decimal_places')
|
||||
->label('Số thập phân')
|
||||
->numeric()
|
||||
->default(0)
|
||||
->visible(fn ($get) => in_array($get('format'), ['number', 'currency', 'percent'])),
|
||||
]),
|
||||
|
||||
TextInput::make('display_order')
|
||||
->label('Thứ tự')
|
||||
->numeric()
|
||||
->default(0)
|
||||
->hidden(),
|
||||
])
|
||||
->addActionLabel('Thêm trường dữ liệu')
|
||||
->reorderable()
|
||||
->orderColumn('display_order')
|
||||
->defaultItems(0)
|
||||
->collapsible()
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
|
||||
// BLOCK 3: Nội dung mẫu in - FULL WIDTH, TO RỘNG
|
||||
Section::make('Nội dung mẫu in')
|
||||
->schema([
|
||||
RichEditor::make('html_template')
|
||||
->label('')
|
||||
->required()
|
||||
->placeholder('Soạn thảo nội dung biểu mẫu...')
|
||||
->helperText('Chèn trường dữ liệu bằng cú pháp {{ma_truong}}. Ví dụ: Tên khách hàng: {{ten_khach_hang}}')
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\FormTemplates\Tables;
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class FormTemplatesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label('Tên biểu mẫu')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('code')
|
||||
->label('Mã')
|
||||
->searchable()
|
||||
->copyable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('target_model')
|
||||
->label('Áp dụng cho')
|
||||
->formatStateUsing(fn ($state) => match ($state) {
|
||||
'App\Models\Contract' => 'Hợp đồng',
|
||||
'App\Models\Product' => 'Sản phẩm',
|
||||
'App\Models\Customer' => 'Khách hàng',
|
||||
default => $state,
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('paper_size')
|
||||
->label('Khổ giấy')
|
||||
->badge(),
|
||||
|
||||
Tables\Columns\IconColumn::make('is_active')
|
||||
->label('Hoạt động')
|
||||
->boolean(),
|
||||
|
||||
Tables\Columns\TextColumn::make('fields_count')
|
||||
->label('Số trường')
|
||||
->counts('fields'),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('target_model')
|
||||
->label('Áp dụng cho')
|
||||
->options([
|
||||
'App\Models\Contract' => 'Hợp đồng',
|
||||
'App\Models\Product' => 'Sản phẩm',
|
||||
'App\Models\Customer' => 'Khách hàng',
|
||||
]),
|
||||
])
|
||||
->defaultSort('name');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user