Hoan thien core finance v2
This commit is contained in:
42
app/Filament/Resources/Appendices/AppendixResource.php
Normal file
42
app/Filament/Resources/Appendices/AppendixResource.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Appendices;
|
||||
|
||||
use App\Filament\Resources\Appendices\Pages;
|
||||
use App\Models\Appendix;
|
||||
use App\Enums\NavigationGroup;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Resources\Appendices\Schemas\AppendixForm;
|
||||
use App\Filament\Resources\Appendices\Tables\AppendicesTable;
|
||||
|
||||
class AppendixResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Appendix::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-document-text';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::TRANSACTION->value;
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
protected static ?string $modelLabel = 'Phụ lục';
|
||||
protected static ?string $pluralModelLabel = 'Phụ lục HĐ';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return AppendixForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return AppendicesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListAppendices::route('/'),
|
||||
'create' => Pages\CreateAppendix::route('/create'),
|
||||
'edit' => Pages\EditAppendix::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Appendices/Pages/CreateAppendix.php
Normal file
11
app/Filament/Resources/Appendices/Pages/CreateAppendix.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Appendices\Pages;
|
||||
|
||||
use App\Filament\Resources\Appendices\AppendixResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateAppendix extends CreateRecord
|
||||
{
|
||||
protected static string $resource = AppendixResource::class;
|
||||
}
|
||||
11
app/Filament/Resources/Appendices/Pages/EditAppendix.php
Normal file
11
app/Filament/Resources/Appendices/Pages/EditAppendix.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Appendices\Pages;
|
||||
|
||||
use App\Filament\Resources\Appendices\AppendixResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditAppendix extends EditRecord
|
||||
{
|
||||
protected static string $resource = AppendixResource::class;
|
||||
}
|
||||
11
app/Filament/Resources/Appendices/Pages/ListAppendices.php
Normal file
11
app/Filament/Resources/Appendices/Pages/ListAppendices.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Appendices\Pages;
|
||||
|
||||
use App\Filament\Resources\Appendices\AppendixResource;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListAppendices extends ListRecords
|
||||
{
|
||||
protected static string $resource = AppendixResource::class;
|
||||
}
|
||||
65
app/Filament/Resources/Appendices/Schemas/AppendixForm.php
Normal file
65
app/Filament/Resources/Appendices/Schemas/AppendixForm.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Appendices\Schemas;
|
||||
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class AppendixForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Section::make('Thông tin phụ lục')
|
||||
->columnSpan(2)
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('contract_id')
|
||||
->label('Hợp đồng gốc')
|
||||
->relationship('contract', 'contract_number')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
|
||||
Select::make('product_id')
|
||||
->label('Sản phẩm')
|
||||
->relationship('product', 'code')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
|
||||
TextInput::make('type')
|
||||
->label('Loại phụ lục')
|
||||
->required(),
|
||||
|
||||
TextInput::make('apply_from_order')
|
||||
->label('Áp dụng từ CN thứ')
|
||||
->numeric()
|
||||
->default(0)
|
||||
->required(),
|
||||
|
||||
DatePicker::make('signing_date')
|
||||
->label('Ngày ký phụ lục')
|
||||
->required(),
|
||||
]),
|
||||
|
||||
Section::make('Dữ liệu bổ sung')
|
||||
->columnSpan(1)
|
||||
->schema([
|
||||
KeyValue::make('custom_data')
|
||||
->label('Thông tin bổ sung')
|
||||
->keyLabel('Thông tin')
|
||||
->valueLabel('Giá trị'),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
38
app/Filament/Resources/Appendices/Tables/AppendicesTable.php
Normal file
38
app/Filament/Resources/Appendices/Tables/AppendicesTable.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Appendices\Tables;
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class AppendicesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('contract.contract_number')
|
||||
->label('Hợp đồng')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('product.code')
|
||||
->label('Sản phẩm')
|
||||
->searchable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->label('Loại phụ lục')
|
||||
->badge(),
|
||||
|
||||
Tables\Columns\TextColumn::make('apply_from_order')
|
||||
->label('Từ CN')
|
||||
->alignCenter(),
|
||||
|
||||
Tables\Columns\TextColumn::make('signing_date')
|
||||
->label('Ngày ký')
|
||||
->date('d/m/Y')
|
||||
->sortable(),
|
||||
])
|
||||
->defaultSort('signing_date', 'desc');
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,12 @@ namespace App\Filament\Resources\Contracts;
|
||||
use App\Filament\Resources\Contracts\Pages;
|
||||
use App\Models\Contract;
|
||||
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;
|
||||
use App\Filament\Resources\Contracts\Tables\ContractsTable;
|
||||
|
||||
class ContractResource extends Resource
|
||||
{
|
||||
@@ -30,33 +29,7 @@ class ContractResource extends Resource
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
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),
|
||||
]);
|
||||
return ContractsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array { return [ScheduleItemsRelationManager::class]; }
|
||||
|
||||
@@ -13,10 +13,9 @@ class CreateContract extends CreateRecord
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
$contract = $this->record;
|
||||
$templateId = $this->data['payment_template_id'] ?? null;
|
||||
|
||||
if ($templateId) {
|
||||
$template = \App\Models\PaymentTemplate::find($templateId);
|
||||
if ($contract->payment_template_id) {
|
||||
$template = $contract->paymentTemplate;
|
||||
if ($template) {
|
||||
ContractScheduleService::generateFromTemplate($contract, $template);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,24 @@ class ContractForm
|
||||
KeyValue::make('discount_details')
|
||||
->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')
|
||||
->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>");
|
||||
}
|
||||
|
||||
$result = \App\Services\DiscountEngine::calculate($totalValue, $discountDetails);
|
||||
$final = number_format($result['final_value']);
|
||||
$discount = number_format($result['discount_amount']);
|
||||
|
||||
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>");
|
||||
}),
|
||||
]),
|
||||
|
||||
Section::make('Thông tin quản lý & Khách hàng')
|
||||
@@ -166,8 +184,8 @@ class ContractForm
|
||||
->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.'),
|
||||
->hiddenOn('edit')
|
||||
->helperText('Hệ thống sẽ tự động tạo lịch thanh toán sau khi lưu hợp đồng.'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Filament\Resources\Contracts\Tables;
|
||||
|
||||
use App\Models\Contract;
|
||||
use App\Services\ContractScheduleService;
|
||||
use Filament\Actions\Action;
|
||||
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
|
||||
@@ -55,6 +57,19 @@ class ContractsTable
|
||||
'Đã hủy' => 'danger',
|
||||
default => 'gray',
|
||||
}),
|
||||
|
||||
TextColumn::make('paid_amount')
|
||||
->label('Đã thu')
|
||||
->money('VND')
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('remaining_amount')
|
||||
->label('Còn lại')
|
||||
->money('VND')
|
||||
->sortable()
|
||||
->color('danger')
|
||||
->toggleable(),
|
||||
])
|
||||
->filters([
|
||||
\Filament\Tables\Filters\SelectFilter::make('status')
|
||||
@@ -73,6 +88,17 @@ class ContractsTable
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
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) {
|
||||
ContractScheduleService::generateFromTemplate($record);
|
||||
})
|
||||
->visible(fn (Contract $record) => $record->signing_date !== null),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentFines\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentFines\PaymentFineResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePaymentFine extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PaymentFineResource::class;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentFines\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentFines\PaymentFineResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPaymentFine extends EditRecord
|
||||
{
|
||||
protected static string $resource = PaymentFineResource::class;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentFines\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentFines\PaymentFineResource;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPaymentFines extends ListRecords
|
||||
{
|
||||
protected static string $resource = PaymentFineResource::class;
|
||||
}
|
||||
42
app/Filament/Resources/PaymentFines/PaymentFineResource.php
Normal file
42
app/Filament/Resources/PaymentFines/PaymentFineResource.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentFines;
|
||||
|
||||
use App\Filament\Resources\PaymentFines\Pages;
|
||||
use App\Models\PaymentFine;
|
||||
use App\Enums\NavigationGroup;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Resources\PaymentFines\Schemas\PaymentFineForm;
|
||||
use App\Filament\Resources\PaymentFines\Tables\PaymentFinesTable;
|
||||
|
||||
class PaymentFineResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PaymentFine::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-exclamation-triangle';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::FINANCE->value;
|
||||
protected static ?int $navigationSort = 6;
|
||||
|
||||
protected static ?string $modelLabel = 'Tiền phạt';
|
||||
protected static ?string $pluralModelLabel = 'Tiền phạt';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return PaymentFineForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return PaymentFinesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPaymentFines::route('/'),
|
||||
'create' => Pages\CreatePaymentFine::route('/create'),
|
||||
'edit' => Pages\EditPaymentFine::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentFines\Schemas;
|
||||
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class PaymentFineForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Section::make('Thông tin tiền phạt')
|
||||
->columnSpan(2)
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('contract_id')
|
||||
->label('Hợp đồng')
|
||||
->relationship('contract', 'contract_number')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
|
||||
TextInput::make('amount')
|
||||
->label('Số tiền phạt')
|
||||
->numeric()
|
||||
->prefix('VND')
|
||||
->required(),
|
||||
|
||||
TextInput::make('reason')
|
||||
->label('Lý do phạt')
|
||||
->required(),
|
||||
|
||||
DatePicker::make('due_date')
|
||||
->label('Ngày đến hạn nộp phạt')
|
||||
->required(),
|
||||
|
||||
DatePicker::make('paid_date')
|
||||
->label('Ngày thực nộp')
|
||||
->nullable(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PaymentFines\Tables;
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PaymentFinesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('contract.contract_number')
|
||||
->label('Hợp đồng')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('amount')
|
||||
->label('Số tiền phạt')
|
||||
->money('VND')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('reason')
|
||||
->label('Lý do')
|
||||
->searchable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('due_date')
|
||||
->label('Hạn nộp')
|
||||
->date('d/m/Y')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('paid_date')
|
||||
->label('Ngày nộp')
|
||||
->date('d/m/Y')
|
||||
->placeholder('Chưa nộp')
|
||||
->color(fn ($state) => $state ? 'success' : 'danger'),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('unpaid')
|
||||
->label('Chưa nộp')
|
||||
->query(fn ($query) => $query->whereNull('paid_date')),
|
||||
])
|
||||
->defaultSort('due_date', 'desc');
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,12 @@ class PaymentResource extends Resource
|
||||
return PaymentsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return parent::getEloquentQuery()
|
||||
->with(['scheduleItem.payments']);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -62,7 +62,74 @@ class PaymentForm
|
||||
->label('Số tiền thu')
|
||||
->numeric()
|
||||
->prefix('VND')
|
||||
->required(),
|
||||
->required()
|
||||
->live(onBlur: true)
|
||||
->helperText(function ($component) {
|
||||
$data = $component->getContainer()->getRawState();
|
||||
$contractId = $data['contract_id'] ?? null;
|
||||
$scheduleItemId = $data['schedule_item_id'] ?? null;
|
||||
|
||||
if (! $contractId) {
|
||||
return 'Vui lòng chọn hợp đồng trước.';
|
||||
}
|
||||
|
||||
$contract = \App\Models\Contract::find($contractId);
|
||||
if (! $contract) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($scheduleItemId) {
|
||||
$item = PaymentScheduleItem::find($scheduleItemId);
|
||||
if ($item) {
|
||||
$paid = $contract->payments()
|
||||
->where('schedule_item_id', $scheduleItemId)
|
||||
->when($component->getRecord() instanceof \App\Models\Payment, fn ($q, $r) => $q->where('id', '!=', $r->id))
|
||||
->sum('amount');
|
||||
$remaining = (float) $item->amount - (float) $paid;
|
||||
|
||||
return 'Công nợ đợt này: '.number_format($remaining).' VNĐ';
|
||||
}
|
||||
}
|
||||
|
||||
return 'Công nợ HĐ còn lại: '.number_format($contract->remaining_amount).' VNĐ';
|
||||
})
|
||||
->rules([
|
||||
function ($component) {
|
||||
return function (string $attribute, $value, \Closure $fail) use ($component) {
|
||||
$data = $component->getContainer()->getRawState();
|
||||
$contractId = $data['contract_id'] ?? null;
|
||||
$scheduleItemId = $data['schedule_item_id'] ?? null;
|
||||
|
||||
if (! $contractId || ! is_numeric($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contract = \App\Models\Contract::find($contractId);
|
||||
if (! $contract) {
|
||||
return;
|
||||
}
|
||||
|
||||
$maxAmount = null;
|
||||
|
||||
if ($scheduleItemId) {
|
||||
$item = PaymentScheduleItem::find($scheduleItemId);
|
||||
if ($item) {
|
||||
$paid = $contract->payments()
|
||||
->where('schedule_item_id', $scheduleItemId)
|
||||
->when($component->getRecord() instanceof \App\Models\Payment, fn ($q, $r) => $q->where('id', '!=', $r->id))
|
||||
->sum('amount');
|
||||
$maxAmount = (float) $item->amount - (float) $paid;
|
||||
}
|
||||
} else {
|
||||
$maxAmount = (float) $contract->remaining_amount;
|
||||
}
|
||||
|
||||
if ($maxAmount !== null && (float) $value > $maxAmount) {
|
||||
$fail('Số tiền thu không được vượt quá '.number_format($maxAmount).' VNĐ.');
|
||||
}
|
||||
};
|
||||
},
|
||||
]),
|
||||
|
||||
DatePicker::make('paid_date')
|
||||
->label('Ngày thu')
|
||||
|
||||
@@ -29,9 +29,50 @@ class PaymentsTable
|
||||
Tables\Columns\TextColumn::make('receipt_number')
|
||||
->label('Số phiếu thu')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('scheduleItem.type')
|
||||
->label('Loại đợt')
|
||||
->placeholder('Tạm ứng')
|
||||
->formatStateUsing(fn ($state) => $state?->getLabel()),
|
||||
|
||||
Tables\Columns\TextColumn::make('scheduleItem.installment_no')
|
||||
->label('Đợt TT')
|
||||
->placeholder('Tạm ứng'),
|
||||
|
||||
Tables\Columns\TextColumn::make('reconciliation_status')
|
||||
->label('Đối soát')
|
||||
->badge()
|
||||
->color(function ($record) {
|
||||
if (! $record->scheduleItem) {
|
||||
return 'gray';
|
||||
}
|
||||
$remaining = (float) $record->scheduleItem->remaining_amount;
|
||||
if ($remaining == 0) {
|
||||
return 'success';
|
||||
}
|
||||
if ($remaining > 0) {
|
||||
return 'warning';
|
||||
}
|
||||
return 'danger';
|
||||
})
|
||||
->state(function ($record) {
|
||||
if (! $record->scheduleItem) {
|
||||
return 'Tạm ứng';
|
||||
}
|
||||
$remaining = (float) $record->scheduleItem->remaining_amount;
|
||||
if ($remaining == 0) {
|
||||
return 'Đủ';
|
||||
}
|
||||
if ($remaining > 0) {
|
||||
return 'Thiếu';
|
||||
}
|
||||
return 'Thừa';
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('scheduleItem.remaining_amount')
|
||||
->label('Còn thiếu')
|
||||
->money('VND')
|
||||
->placeholder('-')
|
||||
->color('danger'),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('method')
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Settlements\Pages;
|
||||
|
||||
use App\Filament\Resources\Settlements\SettlementResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateSettlement extends CreateRecord
|
||||
{
|
||||
protected static string $resource = SettlementResource::class;
|
||||
}
|
||||
11
app/Filament/Resources/Settlements/Pages/EditSettlement.php
Normal file
11
app/Filament/Resources/Settlements/Pages/EditSettlement.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Settlements\Pages;
|
||||
|
||||
use App\Filament\Resources\Settlements\SettlementResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditSettlement extends EditRecord
|
||||
{
|
||||
protected static string $resource = SettlementResource::class;
|
||||
}
|
||||
11
app/Filament/Resources/Settlements/Pages/ListSettlements.php
Normal file
11
app/Filament/Resources/Settlements/Pages/ListSettlements.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Settlements\Pages;
|
||||
|
||||
use App\Filament\Resources\Settlements\SettlementResource;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListSettlements extends ListRecords
|
||||
{
|
||||
protected static string $resource = SettlementResource::class;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Settlements\Schemas;
|
||||
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class SettlementForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Section::make('Thông tin quyết toán')
|
||||
->columnSpan(2)
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('product_id')
|
||||
->label('Sản phẩm')
|
||||
->relationship('product', 'code')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
|
||||
TextInput::make('type')
|
||||
->label('Loại quyết toán')
|
||||
->required(),
|
||||
|
||||
TextInput::make('temp_value')
|
||||
->label('Giá trị tạm tính')
|
||||
->numeric()
|
||||
->prefix('VND')
|
||||
->required(),
|
||||
|
||||
TextInput::make('final_value')
|
||||
->label('Giá trị chốt')
|
||||
->numeric()
|
||||
->prefix('VND')
|
||||
->required(),
|
||||
|
||||
TextInput::make('difference')
|
||||
->label('Chênh lệch')
|
||||
->numeric()
|
||||
->prefix('VND')
|
||||
->required(),
|
||||
|
||||
TextInput::make('red_book_status')
|
||||
->label('Trạng thái sổ đỏ')
|
||||
->required(),
|
||||
|
||||
DatePicker::make('issue_date')
|
||||
->label('Ngày cấp sổ')
|
||||
->nullable(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
app/Filament/Resources/Settlements/SettlementResource.php
Normal file
42
app/Filament/Resources/Settlements/SettlementResource.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Settlements;
|
||||
|
||||
use App\Filament\Resources\Settlements\Pages;
|
||||
use App\Models\Settlement;
|
||||
use App\Enums\NavigationGroup;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Table;
|
||||
use App\Filament\Resources\Settlements\Schemas\SettlementForm;
|
||||
use App\Filament\Resources\Settlements\Tables\SettlementsTable;
|
||||
|
||||
class SettlementResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Settlement::class;
|
||||
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-clipboard-document-check';
|
||||
protected static string | \UnitEnum | null $navigationGroup = NavigationGroup::TRANSACTION->value;
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
protected static ?string $modelLabel = 'Quyết toán';
|
||||
protected static ?string $pluralModelLabel = 'Quyết toán & Sổ đỏ';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return SettlementForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return SettlementsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListSettlements::route('/'),
|
||||
'create' => Pages\CreateSettlement::route('/create'),
|
||||
'edit' => Pages\EditSettlement::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Settlements\Tables;
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class SettlementsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('product.code')
|
||||
->label('Sản phẩm')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->label('Loại QT')
|
||||
->badge(),
|
||||
|
||||
Tables\Columns\TextColumn::make('temp_value')
|
||||
->label('Tạm tính')
|
||||
->money('VND')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('final_value')
|
||||
->label('Chốt')
|
||||
->money('VND')
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('difference')
|
||||
->label('Chênh lệch')
|
||||
->money('VND')
|
||||
->color(fn ($state) => (float) $state > 0 ? 'danger' : 'success'),
|
||||
|
||||
Tables\Columns\TextColumn::make('red_book_status')
|
||||
->label('Trạng thái sổ')
|
||||
->badge(),
|
||||
|
||||
Tables\Columns\TextColumn::make('issue_date')
|
||||
->label('Ngày cấp')
|
||||
->date('d/m/Y')
|
||||
->placeholder('Chưa cấp'),
|
||||
])
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
}
|
||||
46
app/Filament/Widgets/ContractStatsOverview.php
Normal file
46
app/Filament/Widgets/ContractStatsOverview.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Contract;
|
||||
use App\Models\PaymentScheduleItem;
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
|
||||
class ContractStatsOverview extends BaseWidget
|
||||
{
|
||||
protected function getStats(): array
|
||||
{
|
||||
$totalRevenue = (float) Contract::sum('total_value');
|
||||
$totalPaid = (float) Contract::sum('paid_amount');
|
||||
$totalRemaining = (float) Contract::sum('remaining_amount');
|
||||
$activeContracts = Contract::where('status', 'Đang hiệu lực')->count();
|
||||
$upcomingPayments = PaymentScheduleItem::whereNull('schedule_id')
|
||||
->orWhereHas('schedule', fn ($q) => $q->whereHas('contract'))
|
||||
->whereDate('due_date', '<=', now()->addDays(30))
|
||||
->whereDate('due_date', '>=', now())
|
||||
->count();
|
||||
|
||||
return [
|
||||
Stat::make('Tổng doanh thu', number_format($totalRevenue) . ' VNĐ')
|
||||
->description('Tổng giá trị tất cả HĐ')
|
||||
->color('primary'),
|
||||
|
||||
Stat::make('Đã thu', number_format($totalPaid) . ' VNĐ')
|
||||
->description('Tổng tiền đã thanh toán')
|
||||
->color('success'),
|
||||
|
||||
Stat::make('Công nợ phải thu', number_format($totalRemaining) . ' VNĐ')
|
||||
->description('Tổng tiền chưa thu')
|
||||
->color('danger'),
|
||||
|
||||
Stat::make('HĐ hiệu lực', $activeContracts)
|
||||
->description('Số hợp đồng đang hiệu lực')
|
||||
->color('warning'),
|
||||
|
||||
Stat::make('Đợt TT sắp đến hạn', $upcomingPayments)
|
||||
->description('Trong 30 ngày tới')
|
||||
->color('info'),
|
||||
];
|
||||
}
|
||||
}
|
||||
47
app/Filament/Widgets/UpcomingPaymentsTable.php
Normal file
47
app/Filament/Widgets/UpcomingPaymentsTable.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\PaymentScheduleItem;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
|
||||
class UpcomingPaymentsTable extends BaseWidget
|
||||
{
|
||||
protected int | string | array $columnSpan = 'full';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
PaymentScheduleItem::query()
|
||||
->whereHas('schedule.contract')
|
||||
->whereDate('due_date', '>=', now())
|
||||
->whereDate('due_date', '<=', now()->addDays(30))
|
||||
->orderBy('due_date')
|
||||
)
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('schedule.contract.contract_number')
|
||||
->label('Số HĐ')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('installment_no')
|
||||
->label('Đợt')
|
||||
->alignCenter(),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->label('Loại')
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('amount')
|
||||
->label('Số tiền')
|
||||
->money('VND'),
|
||||
Tables\Columns\TextColumn::make('due_date')
|
||||
->label('Ngày đến hạn')
|
||||
->date('d/m/Y')
|
||||
->color('danger'),
|
||||
Tables\Columns\TextColumn::make('remaining_amount')
|
||||
->label('Còn thiếu')
|
||||
->money('VND'),
|
||||
])
|
||||
->paginated([10, 25, 50]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user