Commit e6708441 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(accounting): align account codes with live chart of accounts and fix 4 journal-entry bugs

- AccountingIntegrationService: read payment_type key (dispatched) not type key (wrong fallback)
  that was silently routing every payment journal to the default catch-all account
- InstallmentController: fire installment.plan_created (correct) not installment_plan.created
  so AR journal entry is created when a plan is made from the installments UI
- AccountCodes: remap all constants to accounts that actually exist in chart_of_accounts DB
  - form_fee → 410103 (استمارات عضويات), membership_fee → 410101 (عضويات جديدة)
  - addition_fee → 410102 (إضافة عضويات)
  - installment → 410510 (الاقساط), down_payment → 410503 (مقدم عضويه)
  - waiver_fee/death_fee → 410515 (إيرادات متنوعه)
  - sports_registration/sa_form_fee → 410516 (استمارات نشاط)
  - fine → 410512 (غرامة تاخير), SERVICE_REVENUE → 410515 (exists, was 4110 which did not exist)
- DB: corrected treasury 3 account_code from 12060103 (EUR label) to 12060101 (EGP)
  which was causing form_fee journals to debit the wrong sub-treasury cash account
Co-Authored-By: 's avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent b3f5f147
...@@ -74,55 +74,62 @@ final class AccountCodes ...@@ -74,55 +74,62 @@ final class AccountCodes
// 4 — REVENUE (Custom for this club) // 4 — REVENUE (Custom for this club)
// ══════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════
// Membership Revenue (4101) // Membership Revenue (4101) — mapped to actual chart_of_accounts codes
const FORM_FEE_REVENUE = '410101'; const FORM_FEE_REVENUE = '410103'; // استمارات عضويات
const MEMBERSHIP_VALUE_REVENUE = '410102'; const MEMBERSHIP_VALUE_REVENUE = '410101'; // عضويات جديدة
const DEPENDENT_ADDITION_REVENUE = '410103'; const DEPENDENT_ADDITION_REVENUE = '410102'; // إضافة عضويات
const MEMBERSHIP_RENEWAL_REVENUE = '410104'; // تجديد عضويات
// Subscription Revenue (4102)
const ANNUAL_SUBSCRIPTION = '410201'; // Subscription Revenue (4102) — Academy subscriptions
const DEVELOPMENT_FEE = '410202'; const ANNUAL_SUBSCRIPTION = '410201'; // اكاديمية البادل (primary subscription account)
const DEVELOPMENT_FEE = '410202'; // اكاديمية قدم
// Transfer & Cases Revenue (4103)
const SEPARATION_FEE_REVENUE = '410301'; // Transfer & Cases Revenue — mapped to actual accounts
const DIVORCE_FEE_REVENUE = '410302'; const SEPARATION_FEE_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
const DEATH_TRANSFER_REVENUE = '410303'; const DIVORCE_FEE_REVENUE = '410302'; // محل 1 (mapped)
const WAIVER_FEE_REVENUE = '410304'; const DEATH_TRANSFER_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
const SPORTS_CONVERSION_REVENUE = '410305'; const WAIVER_FEE_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
const SPORTS_CONVERSION_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
// Special Memberships (4104)
const SEASONAL_MEMBERSHIP = '410401'; // Special Memberships — mapped to existing accounts
const CARNET_REPLACEMENT = '410402'; const SEASONAL_MEMBERSHIP = '410501'; // قيمه عضويه
const FOREIGN_MEMBERSHIP = '410403'; const CARNET_REPLACEMENT = '410529'; // كارنيهات بدل فائد
const FOREIGN_MEMBERSHIP = '410501'; // قيمه عضويه (no separate foreign account)
// Activities & Sports (4105) // Activities & Sports (4105)
const ACTIVITY_SUBSCRIPTION = '410501'; const ACTIVITY_SUBSCRIPTION = '410516'; // استمارات نشاط
const ACADEMY_REVENUE = '410502'; const ACADEMY_REVENUE = '410201'; // اكاديمية البادل
const HOURLY_BOOKING_REVENUE = '410503'; const HOURLY_BOOKING_REVENUE = '410523'; // حجز ملاعب
// Single-level revenue accounts // Membership installment accounts
const FINE_REVENUE = '4106'; const DOWN_PAYMENT_REVENUE = '410503'; // مقدم عضويه
const SALES_REVENUE = '4107'; const INSTALLMENT_REVENUE = '410510'; // الاقساط
const RENTAL_REVENUE = '4108';
// Installment Revenue (4109) // Sports registration
const DOWN_PAYMENT_REVENUE = '410901'; const SPORTS_REGISTRATION_REVENUE = '410516'; // استمارات نشاط
const INSTALLMENT_REVENUE = '410902';
// Other Revenue (42) // Fine / Penalty accounts
const BANK_INTEREST_INCOME = '4205'; // فوائد دائنة const FINE_REVENUE = '410512'; // غرامة تاخير
const MISCELLANEOUS_REVENUE = '4202'; // إعانات / متنوعة const FINE_LATE_INTEREST = '410509'; // فوائد تاخير
// Facility & Pool Revenue (4111) // Sales
const FACILITY_ENTRY_REVENUE = '411101'; const SALES_REVENUE = '410515'; // إيرادات متنوعه (catch-all for sales)
const POOL_ENTRY_REVENUE = '411102'; const RENTAL_REVENUE = '410521'; // ايجار محلات
const GUEST_ENTRY_REVENUE = '411103';
// Tournament Revenue (4112) // Miscellaneous / catch-all
const TOURNAMENT_FEE_REVENUE = '411201'; const MISCELLANEOUS_REVENUE = '410515'; // إيرادات متنوعه
const BANK_INTEREST_INCOME = '410515'; // إيرادات متنوعه
// Service Revenue (catch-all for deposits reclassification etc.) // Facility Revenue
const SERVICE_REVENUE = '4110'; const FACILITY_ENTRY_REVENUE = '410518'; // تذاكر دخول
const POOL_ENTRY_REVENUE = '410518'; // تذاكر دخول
const GUEST_ENTRY_REVENUE = '410518'; // تذاكر دخول
// Tournament Revenue
const TOURNAMENT_FEE_REVENUE = '410517'; // احداث رياضيه
// Service Revenue (catch-all — points to متنوعه which exists in DB)
const SERVICE_REVENUE = '410515'; // إيرادات متنوعه
// ══════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════
// HELPERS // HELPERS
...@@ -149,7 +156,15 @@ final class AccountCodes ...@@ -149,7 +156,15 @@ final class AccountCodes
$treasury = $db->selectOne("SELECT account_code, type FROM treasuries WHERE id = ?", [$treasuryId]); $treasury = $db->selectOne("SELECT account_code, type FROM treasuries WHERE id = ?", [$treasuryId]);
if ($treasury && $treasury['type'] === 'sub') { if ($treasury && $treasury['type'] === 'sub') {
return $treasury['account_code'] ?: self::SUB_TREASURY_CASH; $code = $treasury['account_code'] ?: self::SUB_TREASURY_CASH;
$account = $db->selectOne(
"SELECT currency FROM chart_of_accounts WHERE account_code = ? AND is_archived = 0",
[$code]
);
if ($account && ($account['currency'] === '' || $account['currency'] === 'EGP')) {
return $code;
}
return self::CASH_ON_HAND;
} }
return self::CASH_ON_HAND; return self::CASH_ON_HAND;
...@@ -172,8 +187,10 @@ final class AccountCodes ...@@ -172,8 +187,10 @@ final class AccountCodes
'development_fee' => self::DEVELOPMENT_FEE, 'development_fee' => self::DEVELOPMENT_FEE,
'activity_subscription' => self::ACTIVITY_SUBSCRIPTION, 'activity_subscription' => self::ACTIVITY_SUBSCRIPTION,
'hourly_booking' => self::HOURLY_BOOKING_REVENUE, 'hourly_booking' => self::HOURLY_BOOKING_REVENUE,
'down_payment' => self::ACCOUNTS_RECEIVABLE, 'sports_registration',
'installment' => self::ACCOUNTS_RECEIVABLE, 'sa_form_fee' => self::SPORTS_REGISTRATION_REVENUE,
'down_payment' => self::DOWN_PAYMENT_REVENUE,
'installment' => self::INSTALLMENT_REVENUE,
'fine' => self::FINE_REVENUE, 'fine' => self::FINE_REVENUE,
'inventory_sale' => self::SALES_REVENUE, 'inventory_sale' => self::SALES_REVENUE,
default => self::SERVICE_REVENUE, default => self::SERVICE_REVENUE,
......
...@@ -33,7 +33,7 @@ final class AccountingIntegrationService ...@@ -33,7 +33,7 @@ final class AccountingIntegrationService
$paymentId = (int) ($data['payment_id'] ?? 0); $paymentId = (int) ($data['payment_id'] ?? 0);
$amount = (string) ($data['amount'] ?? '0.00'); $amount = (string) ($data['amount'] ?? '0.00');
$method = $data['method'] ?? 'cash'; $method = $data['method'] ?? 'cash';
$type = $data['type'] ?? ''; $type = $data['payment_type'] ?? $data['type'] ?? '';
$memberId = (int) ($data['member_id'] ?? 0); $memberId = (int) ($data['member_id'] ?? 0);
if (bccomp($amount, '0.00', 2) <= 0) { if (bccomp($amount, '0.00', 2) <= 0) {
......
...@@ -163,7 +163,7 @@ class InstallmentController extends Controller ...@@ -163,7 +163,7 @@ class InstallmentController extends Controller
return $this->redirect("/installments/create/{$memberId}")->withError('خطأ: ' . $e->getMessage()); return $this->redirect("/installments/create/{$memberId}")->withError('خطأ: ' . $e->getMessage());
} }
EventBus::dispatch('installment_plan.created', [ EventBus::dispatch('installment.plan_created', [
'plan_id' => $planId, 'plan_id' => $planId,
'member_id' => (int) $memberId, 'member_id' => (int) $memberId,
]); ]);
......
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