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
// 4 — REVENUE (Custom for this club)
// ══════════════════════════════════════════════════════════════════
// Membership Revenue (4101)
const FORM_FEE_REVENUE = '410101';
const MEMBERSHIP_VALUE_REVENUE = '410102';
const DEPENDENT_ADDITION_REVENUE = '410103';
// Subscription Revenue (4102)
const ANNUAL_SUBSCRIPTION = '410201';
const DEVELOPMENT_FEE = '410202';
// Transfer & Cases Revenue (4103)
const SEPARATION_FEE_REVENUE = '410301';
const DIVORCE_FEE_REVENUE = '410302';
const DEATH_TRANSFER_REVENUE = '410303';
const WAIVER_FEE_REVENUE = '410304';
const SPORTS_CONVERSION_REVENUE = '410305';
// Special Memberships (4104)
const SEASONAL_MEMBERSHIP = '410401';
const CARNET_REPLACEMENT = '410402';
const FOREIGN_MEMBERSHIP = '410403';
// Membership Revenue (4101) — mapped to actual chart_of_accounts codes
const FORM_FEE_REVENUE = '410103'; // استمارات عضويات
const MEMBERSHIP_VALUE_REVENUE = '410101'; // عضويات جديدة
const DEPENDENT_ADDITION_REVENUE = '410102'; // إضافة عضويات
const MEMBERSHIP_RENEWAL_REVENUE = '410104'; // تجديد عضويات
// Subscription Revenue (4102) — Academy subscriptions
const ANNUAL_SUBSCRIPTION = '410201'; // اكاديمية البادل (primary subscription account)
const DEVELOPMENT_FEE = '410202'; // اكاديمية قدم
// Transfer & Cases Revenue — mapped to actual accounts
const SEPARATION_FEE_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
const DIVORCE_FEE_REVENUE = '410302'; // محل 1 (mapped)
const DEATH_TRANSFER_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
const WAIVER_FEE_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
const SPORTS_CONVERSION_REVENUE = '410515'; // إيرادات متنوعه (no dedicated account)
// Special Memberships — mapped to existing accounts
const SEASONAL_MEMBERSHIP = '410501'; // قيمه عضويه
const CARNET_REPLACEMENT = '410529'; // كارنيهات بدل فائد
const FOREIGN_MEMBERSHIP = '410501'; // قيمه عضويه (no separate foreign account)
// Activities & Sports (4105)
const ACTIVITY_SUBSCRIPTION = '410501';
const ACADEMY_REVENUE = '410502';
const HOURLY_BOOKING_REVENUE = '410503';
const ACTIVITY_SUBSCRIPTION = '410516'; // استمارات نشاط
const ACADEMY_REVENUE = '410201'; // اكاديمية البادل
const HOURLY_BOOKING_REVENUE = '410523'; // حجز ملاعب
// Single-level revenue accounts
const FINE_REVENUE = '4106';
const SALES_REVENUE = '4107';
const RENTAL_REVENUE = '4108';
// Membership installment accounts
const DOWN_PAYMENT_REVENUE = '410503'; // مقدم عضويه
const INSTALLMENT_REVENUE = '410510'; // الاقساط
// Installment Revenue (4109)
const DOWN_PAYMENT_REVENUE = '410901';
const INSTALLMENT_REVENUE = '410902';
// Sports registration
const SPORTS_REGISTRATION_REVENUE = '410516'; // استمارات نشاط
// Other Revenue (42)
const BANK_INTEREST_INCOME = '4205'; // فوائد دائنة
const MISCELLANEOUS_REVENUE = '4202'; // إعانات / متنوعة
// Fine / Penalty accounts
const FINE_REVENUE = '410512'; // غرامة تاخير
const FINE_LATE_INTEREST = '410509'; // فوائد تاخير
// Facility & Pool Revenue (4111)
const FACILITY_ENTRY_REVENUE = '411101';
const POOL_ENTRY_REVENUE = '411102';
const GUEST_ENTRY_REVENUE = '411103';
// Sales
const SALES_REVENUE = '410515'; // إيرادات متنوعه (catch-all for sales)
const RENTAL_REVENUE = '410521'; // ايجار محلات
// Tournament Revenue (4112)
const TOURNAMENT_FEE_REVENUE = '411201';
// Miscellaneous / catch-all
const MISCELLANEOUS_REVENUE = '410515'; // إيرادات متنوعه
const BANK_INTEREST_INCOME = '410515'; // إيرادات متنوعه
// Service Revenue (catch-all for deposits reclassification etc.)
const SERVICE_REVENUE = '4110';
// Facility Revenue
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
......@@ -149,7 +156,15 @@ final class AccountCodes
$treasury = $db->selectOne("SELECT account_code, type FROM treasuries WHERE id = ?", [$treasuryId]);
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;
......@@ -172,8 +187,10 @@ final class AccountCodes
'development_fee' => self::DEVELOPMENT_FEE,
'activity_subscription' => self::ACTIVITY_SUBSCRIPTION,
'hourly_booking' => self::HOURLY_BOOKING_REVENUE,
'down_payment' => self::ACCOUNTS_RECEIVABLE,
'installment' => self::ACCOUNTS_RECEIVABLE,
'sports_registration',
'sa_form_fee' => self::SPORTS_REGISTRATION_REVENUE,
'down_payment' => self::DOWN_PAYMENT_REVENUE,
'installment' => self::INSTALLMENT_REVENUE,
'fine' => self::FINE_REVENUE,
'inventory_sale' => self::SALES_REVENUE,
default => self::SERVICE_REVENUE,
......
......@@ -33,7 +33,7 @@ final class AccountingIntegrationService
$paymentId = (int) ($data['payment_id'] ?? 0);
$amount = (string) ($data['amount'] ?? '0.00');
$method = $data['method'] ?? 'cash';
$type = $data['type'] ?? '';
$type = $data['payment_type'] ?? $data['type'] ?? '';
$memberId = (int) ($data['member_id'] ?? 0);
if (bccomp($amount, '0.00', 2) <= 0) {
......
......@@ -163,7 +163,7 @@ class InstallmentController extends Controller
return $this->redirect("/installments/create/{$memberId}")->withError('خطأ: ' . $e->getMessage());
}
EventBus::dispatch('installment_plan.created', [
EventBus::dispatch('installment.plan_created', [
'plan_id' => $planId,
'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