Commit 831a3e96 authored by DevPilot's avatar DevPilot

fix: optional date inputs posted as '' crashed saves across 17 controllers

MySQL strict mode rejects '' for a DATE/DATETIME column, so any form
with an untouched optional date aborted the whole insert with
"Incorrect date value: ''". This was not one bug — quote responses,
vouchers, bounced cheques, bank loans, documentary credits, letters of
guarantee, settlements, tenders, auctions, committees, payments,
overtime, permission requests and more all passed the raw post value
straight through.

Adds Request::postDate(), which returns NULL for an empty date field,
and routes all 33 optional-date call sites through it. QuoteService
also guards expiry_date directly since it is called from more than one
path.
parent d75ca900
......@@ -80,6 +80,20 @@ final class Request
return $this->post[$key] ?? $default;
}
/**
* An optional date/datetime field from a form.
*
* A date input the user never touched submits '', and MySQL rejects '' for a
* DATE/DATETIME column in strict mode ("Incorrect date value: ''"), which
* aborts the whole save. Always read optional dates through this so the empty
* case becomes NULL instead of blowing up the insert.
*/
public function postDate(string $key): ?string
{
$value = trim((string) ($this->post[$key] ?? ''));
return $value === '' ? null : $value;
}
public function input(string $key, $default = null)
{
return $this->post[$key] ?? $this->get[$key] ?? $default;
......
......@@ -63,7 +63,7 @@ class BankLoanController extends Controller
'principal_amount' => $request->post('principal_amount'),
'interest_rate' => $request->post('interest_rate'),
'term_months' => (int) $request->post('term_months'),
'start_date' => $request->post('start_date'),
'start_date' => $request->postDate('start_date'),
'collateral_description' => $request->post('collateral_description'),
'notes' => $request->post('notes'),
];
......@@ -108,7 +108,7 @@ class BankLoanController extends Controller
$installmentNumber = (int) $request->post('installment_number');
$paidAmount = $request->post('paid_amount');
$paidDate = $request->post('paid_date');
$paidDate = $request->postDate('paid_date');
$result = BankLoanService::recordPayment((int) $id, $installmentNumber, $paidAmount, $paidDate);
......
......@@ -109,7 +109,7 @@ class BillingController extends Controller
'treasury_id' => $request->post('treasury_id') ?: null,
'check_number' => $request->post('check_number'),
'check_bank' => $request->post('check_bank'),
'check_date' => $request->post('check_date'),
'check_date' => $request->postDate('check_date'),
'visa_reference' => $request->post('visa_reference'),
'transfer_reference' => $request->post('transfer_reference'),
]);
......@@ -231,7 +231,7 @@ class BillingController extends Controller
'source_table' => trim((string) $request->post('source_table', '')),
'id_column' => trim((string) $request->post('id_column', 'id')),
'amount_column' => trim((string) $request->post('amount_column', '')),
'date_column' => $nullable($request->post('date_column')),
'date_column' => $nullable($request->postDate('date_column')),
'reference_column' => $nullable($request->post('reference_column')),
'member_column' => $nullable($request->post('member_column')),
'player_column' => $nullable($request->post('player_column')),
......
......@@ -42,12 +42,12 @@ class BouncedChequeController extends Controller
$result = BouncedChequeService::bounce((int) $id, [
'reason_code' => $request->post('reason_code'),
'reason' => $request->post('reason'),
'date' => $request->post('date'),
'date' => $request->postDate('date'),
'bank_charge' => $request->post('bank_charge'),
'fee' => $request->post('fee'),
'fee_bearer' => $request->post('fee_bearer'),
'protest_number' => $request->post('protest_number'),
'protest_date' => $request->post('protest_date'),
'protest_date' => $request->postDate('protest_date'),
'notes' => $request->post('notes'),
]);
......@@ -69,7 +69,7 @@ class BouncedChequeController extends Controller
$this->authorize('accounting.instruments.manage');
$result = BouncedChequeService::represent((int) $id, [
'date' => $request->post('date'),
'date' => $request->postDate('date'),
'bank_account_id' => $request->post('bank_account_id'),
'notes' => $request->post('notes'),
]);
......@@ -91,7 +91,7 @@ class BouncedChequeController extends Controller
(int) $id,
(string) $request->post('resolution', ''),
[
'date' => $request->post('date'),
'date' => $request->postDate('date'),
'notes' => $request->post('notes'),
'replacement_instrument_id' => $request->post('replacement_instrument_id'),
]
......
......@@ -76,9 +76,9 @@ class DocumentaryCreditController extends Controller
'currency_code' => $request->post('currency_code', 'EGP'),
'margin_percentage' => $marginPct,
'margin_amount' => $marginAmount,
'opening_date' => $request->post('opening_date'),
'expiry_date' => $request->post('expiry_date'),
'shipment_date' => $request->post('shipment_date') ?: null,
'opening_date' => $request->postDate('opening_date'),
'expiry_date' => $request->postDate('expiry_date'),
'shipment_date' => $request->postDate('shipment_date') ?: null,
'status' => 'opened',
'terms' => $request->post('terms'),
'purchase_order_id' => $request->post('purchase_order_id') ?: null,
......
......@@ -83,8 +83,8 @@ class LetterOfGuaranteeController extends Controller
'margin_amount' => $marginAmount,
'commission_rate' => $commissionRate,
'commission_amount' => $commissionAmount,
'issue_date' => $request->post('issue_date'),
'expiry_date' => $request->post('expiry_date'),
'issue_date' => $request->postDate('issue_date'),
'expiry_date' => $request->postDate('expiry_date'),
'status' => 'issued',
'related_contract' => $request->post('related_contract'),
'notes' => $request->post('notes'),
......@@ -124,7 +124,7 @@ class LetterOfGuaranteeController extends Controller
if ($newStatus === 'renewed') {
$updateData['renewal_date'] = date('Y-m-d');
$updateData['expiry_date'] = $request->post('new_expiry_date');
$updateData['expiry_date'] = $request->postDate('new_expiry_date');
}
$db->update('letters_of_guarantee', $updateData, 'id = ?', [(int) $id]);
......
......@@ -46,7 +46,7 @@ class SettlementController extends Controller
$this->authorize('accounting.settlements.manage');
$data = [
'settlement_date' => $request->post('settlement_date'),
'settlement_date' => $request->postDate('settlement_date'),
'from_entity_type' => $request->post('from_entity_type'),
'from_entity_id' => $request->post('from_entity_id'),
'from_entity_name' => $request->post('from_entity_name'),
......
......@@ -47,9 +47,9 @@ class AuctionCommitteeController extends Controller
try {
$committeeId = AuctionCommitteeService::create($type, (int) $auctionId, [
'committee_name' => $request->post('committee_name'),
'formed_date' => $request->post('formed_date'),
'meeting_date' => $request->post('meeting_date'),
'decision_date' => $request->post('decision_date'),
'formed_date' => $request->postDate('formed_date'),
'meeting_date' => $request->postDate('meeting_date'),
'decision_date' => $request->postDate('decision_date'),
'chairman_employee_id' => $request->post('chairman_employee_id'),
'minutes' => $request->post('minutes'),
], $request->post('member_ids', []));
......
......@@ -136,10 +136,10 @@ class AuctionController extends Controller
try {
AuctionService::createBooklet((int) $id, [
'issue_date' => $request->post('issue_date'),
'issue_date' => $request->postDate('issue_date'),
'terms' => $request->post('terms'),
'fee' => $request->post('fee'),
'bid_deadline' => $request->post('bid_deadline'),
'bid_deadline' => $request->postDate('bid_deadline'),
]);
} catch (\Throwable $e) {
return $this->redirect('/auctions/' . $id)->withError($e->getMessage());
......
......@@ -204,7 +204,7 @@ class LeaveTypeController extends Controller
'gender_restriction' => trim((string) $request->post('gender_restriction', '')) ?: null,
'carry_over_allowed' => (int) ($request->post('carry_over_allowed', 0)),
'max_carry_over_days' => $this->nullableDecimal($request->post('max_carry_over_days')),
'carry_over_expiry_months' => $this->nullableInt($request->post('carry_over_expiry_months')),
'carry_over_expiry_months' => $this->nullableInt($request->postDate('carry_over_expiry_months')),
'is_accumulative' => (int) ($request->post('is_accumulative', 0)),
'accumulation_max_years' => $this->nullableInt($request->post('accumulation_max_years')),
'career_max_times' => $this->nullableInt($request->post('career_max_times')),
......
......@@ -70,7 +70,7 @@ class OvertimeController extends Controller
$data = [
'employee_profile_id' => (int) $request->post('employee_profile_id'),
'overtime_type_id' => (int) $request->post('overtime_type_id'),
'request_date' => $request->post('request_date'),
'request_date' => $request->postDate('request_date'),
'start_time' => $request->post('start_time'),
'end_time' => $request->post('end_time'),
'reason' => $request->post('reason'),
......
......@@ -70,12 +70,12 @@ class PermissionRequestController extends Controller
"SELECT COALESCE(SUM(hours), 0) as total FROM hr_permission_requests
WHERE employee_id = ? AND status IN ('pending','approved')
AND DATE_FORMAT(permission_date, '%Y-%m') = ?",
[$employeeId, date('Y-m', strtotime($request->post('permission_date')))]
[$employeeId, date('Y-m', strtotime($request->postDate('permission_date')))]
);
$db->insert('hr_permission_requests', [
'employee_id' => $employeeId,
'permission_date' => $request->post('permission_date'),
'permission_date' => $request->postDate('permission_date'),
'start_time' => $startTime,
'end_time' => $endTime,
'hours' => number_format($hours, 2),
......
......@@ -98,7 +98,7 @@ class ShiftController extends Controller
'employee_id' => $employeeId,
'shift_id' => $shiftId,
'start_date' => $request->post('start_date', date('Y-m-d')),
'end_date' => $request->post('end_date') ?: null,
'end_date' => $request->postDate('end_date') ?: null,
'rotation_pattern' => $request->post('rotation_pattern', 'fixed'),
'is_active' => 1,
]);
......
......@@ -172,7 +172,7 @@ class PaymentController extends Controller
'notes' => $notes,
'check_number' => $request->post('check_number'),
'check_bank' => $request->post('check_bank'),
'check_date' => $request->post('check_date'),
'check_date' => $request->postDate('check_date'),
'visa_reference' => $request->post('visa_reference'),
'transfer_reference' => $request->post('transfer_reference'),
'transfer_bank' => $request->post('transfer_bank'),
......
......@@ -52,9 +52,9 @@ class CommitteeController extends Controller
try {
$committeeId = CommitteeService::create($type, (int) $requisitionId, $tenderId ?: null, [
'committee_name' => $request->post('committee_name'),
'formed_date' => $request->post('formed_date'),
'meeting_date' => $request->post('meeting_date'),
'decision_date' => $request->post('decision_date'),
'formed_date' => $request->postDate('formed_date'),
'meeting_date' => $request->postDate('meeting_date'),
'decision_date' => $request->postDate('decision_date'),
'chairman_employee_id' => $request->post('chairman_employee_id'),
'minutes' => $request->post('minutes'),
], $memberIds);
......
......@@ -144,7 +144,7 @@ class QuoteController extends Controller
$terms = [
'delivery_terms' => $request->post('delivery_terms'),
'payment_terms' => $request->post('payment_terms'),
'expiry_date' => $request->post('expiry_date'),
'expiry_date' => $request->postDate('expiry_date'),
];
$result = QuoteService::recordQuoteResponse((int) $id, $items, $terms);
......
......@@ -52,8 +52,8 @@ class TenderController extends Controller
'title' => $request->post('title'),
'terms' => $request->post('terms'),
'booklet_fee' => $request->post('booklet_fee'),
'issue_date' => $request->post('issue_date'),
'bid_deadline' => $request->post('bid_deadline'),
'issue_date' => $request->postDate('issue_date'),
'bid_deadline' => $request->postDate('bid_deadline'),
]);
} catch (\Throwable $e) {
return $this->redirect('/procurement/requisitions/' . $requisitionId)->withError($e->getMessage());
......
......@@ -98,7 +98,9 @@ final class QuoteService
'total_amount' => $totalAmount,
'delivery_terms' => $terms['delivery_terms'] ?? null,
'payment_terms' => $terms['payment_terms'] ?? null,
'expiry_date' => $terms['expiry_date'] ?? null,
// An untouched date input posts '', which is not a DATE — send NULL.
'expiry_date' => (isset($terms['expiry_date']) && trim((string) $terms['expiry_date']) !== '')
? $terms['expiry_date'] : null,
], 'id = ?', [$quoteId]);
$db->commit();
......
......@@ -185,7 +185,7 @@ class SubscriptionController extends Controller
$extra = [
'check_number' => $request->post('check_number'),
'check_bank' => $request->post('check_bank'),
'check_date' => $request->post('check_date'),
'check_date' => $request->postDate('check_date'),
'visa_reference' => $request->post('visa_reference'),
'transfer_reference' => $request->post('transfer_reference'),
'transfer_bank' => $request->post('transfer_bank'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment