components([ Grid::make(3) ->schema([ Section::make('Thông tin phiếu thu') ->columnSpan(2) ->columns(2) ->schema([ Select::make('contract_id') ->label('Hợp đồng') ->relationship('contract', 'contract_number') ->searchable() ->preload() ->required() ->live() ->afterStateUpdated(function (Set $set) { $set('schedule_item_id', null); }), Select::make('schedule_item_id') ->label('Đợt thanh toán') ->placeholder('Để trống nếu là tạm ứng / không đối soát đợt') ->options(function (callable $get) { $contractId = $get('contract_id'); if (! $contractId) { return []; } return PaymentScheduleItem::query() ->whereHas('schedule', fn ($q) => $q->where('contract_id', $contractId)) ->get() ->mapWithKeys(function ($item) { $label = 'Đợt '.$item->installment_no.' - '.$item->type; if ($item->amount) { $label .= ' ('.number_format($item->amount).' VNĐ)'; } return [$item->id => $label]; }); }) ->searchable(), TextInput::make('amount') ->label('Số tiền thu') ->numeric() ->prefix('VND') ->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') ->required() ->default(now()), TextInput::make('receipt_number') ->label('Số phiếu thu / Mã giao dịch'), Select::make('method') ->label('Phương thức thanh toán') ->options([ 'Chuyển khoản' => 'Chuyển khoản', 'Tiền mặt' => 'Tiền mặt', 'Thẻ' => 'Thẻ', 'Khác' => 'Khác', ]) ->default('Chuyển khoản') ->required(), Select::make('collected_by') ->label('Ngườ thu') ->relationship('collector', 'name') ->searchable() ->preload() ->default(auth()->id()) ->required(), ]), Section::make('Bổ sung') ->columnSpan(1) ->schema([ KeyValue::make('metadata') ->label('Dữ liệu bổ sung (nếu có)') ->keyLabel('Thông tin') ->valueLabel('Giá trị'), ]), ]), ]); } }