Commit c0419dbd authored by Mahmoud Aglan's avatar Mahmoud Aglan

test

parent cbf7de3f
......@@ -609,6 +609,207 @@ final class AccountingIntegrationService
}
}
// ────────────────────────────────────────────────────────────
// INSTALLMENT PAYMENTS
// ────────────────────────────────────────────────────────────
/**
* Update AR when an installment is paid.
* The cash/revenue side is already handled by payment.completed.
* This reduces the AR balance for the installment plan.
*/
public static function onInstallmentPaid(array $data): void
{
$db = App::getInstance()->db();
$planId = (int) ($data['plan_id'] ?? 0);
$scheduleId = (int) ($data['schedule_id'] ?? 0);
$memberId = (int) ($data['member_id'] ?? 0);
if ($planId <= 0 || $scheduleId <= 0) {
return;
}
$scheduleItem = $db->selectOne("SELECT amount FROM installment_schedule WHERE id = ?", [$scheduleId]);
$amount = (string) ($scheduleItem['amount'] ?? '0.00');
if (bccomp($amount, '0.00', 2) <= 0) {
return;
}
$ar = \App\Modules\Accounting\Models\AccountReceivable::findByDocument('installment', $planId);
if ($ar) {
$newPaid = bcadd((string) $ar->paid_amount, $amount, 2);
$newBalance = bcsub((string) $ar->total_amount, $newPaid, 2);
if (bccomp($newBalance, '0.00', 2) < 0) $newBalance = '0.00';
$status = bccomp($newBalance, '0.00', 2) <= 0 ? 'paid' : 'partial';
$ar->update([
'paid_amount' => $newPaid,
'balance' => $newBalance,
'status' => $status,
]);
}
}
// ────────────────────────────────────────────────────────────
// REFUNDS
// ────────────────────────────────────────────────────────────
/**
* Post GL entry when a sale is refunded.
* Dr. Sales Revenue (reduce revenue) | Cr. Cash/Bank (money out)
*/
public static function onSaleRefunded(array $data): void
{
$db = App::getInstance()->db();
$saleId = (int) ($data['sale_id'] ?? 0);
$refundId = (int) ($data['refund_id'] ?? 0);
$refundNumber = $data['refund_number'] ?? '';
$amount = (string) ($data['amount'] ?? '0.00');
if ($saleId <= 0 || bccomp($amount, '0.00', 2) <= 0) {
return;
}
$sale = $db->selectOne("SELECT * FROM sales WHERE id = ?", [$saleId]);
if (!$sale) {
return;
}
$salesRevenue = self::getAccountByCode('4103');
$cashAccount = self::getAccountByCode('1101');
if (!$salesRevenue || !$cashAccount) {
Logger::error("Refund auto-post failed: accounts not found", ['sale_id' => $saleId]);
return;
}
$description = 'مرتجع مبيعات — فاتورة ' . ($sale['invoice_number'] ?? '') . ' — إشعار ' . $refundNumber;
$result = JournalService::createEntry([
'entry_date' => date('Y-m-d'),
'description_ar' => $description,
'description_en' => 'Sales refund — ' . $refundNumber,
'reference_type' => 'sale_refund',
'reference_id' => $refundId,
'reference_number' => $refundNumber,
'source_module' => 'sales',
'is_auto_generated' => 1,
], [
[
'account_id' => (int) $salesRevenue['id'],
'debit' => $amount,
'credit' => '0.00',
'description_ar' => 'مرتجع مبيعات — تخفيض إيراد',
],
[
'account_id' => (int) $cashAccount['id'],
'debit' => '0.00',
'credit' => $amount,
'description_ar' => 'مرتجع مبيعات — صرف نقدي',
],
], true);
if (!$result['success']) {
Logger::error("Refund auto-post failed", ['sale_id' => $saleId, 'error' => $result['error'] ?? '']);
}
}
// ────────────────────────────────────────────────────────────
// RENTAL DEPOSITS
// ────────────────────────────────────────────────────────────
/**
* Record deposit liability when collected.
* The cash side is handled by payment.completed.
* This creates a Dr. Cash, Cr. Deposit Liability (2105 Deferred Revenue).
* Since payment.completed already handles Dr. Cash → Cr. Revenue,
* we only need to reclassify from revenue to deposit liability.
*/
public static function onRentalDepositCollected(array $data): void
{
$db = App::getInstance()->db();
$contractId = (int) ($data['contract_id'] ?? 0);
$paymentId = (int) ($data['payment_id'] ?? 0);
if ($contractId <= 0 || $paymentId <= 0) {
return;
}
$payment = $db->selectOne("SELECT * FROM payments WHERE id = ?", [$paymentId]);
if (!$payment) {
return;
}
$amount = (string) ($payment['amount'] ?? '0.00');
if (bccomp($amount, '0.00', 2) <= 0) {
return;
}
// Reclassify: Dr. Service Revenue (4105), Cr. Deferred Revenue / Deposits (2105)
$serviceRevenue = self::getAccountByCode('4105');
$deferredRevenue = self::getAccountByCode('2105');
if (!$serviceRevenue || !$deferredRevenue) {
return;
}
$contract = $db->selectOne("SELECT * FROM rental_contracts WHERE id = ?", [$contractId]);
$contractNum = $contract['contract_number'] ?? $contractId;
$result = JournalService::createEntry([
'entry_date' => date('Y-m-d'),
'description_ar' => 'إعادة تصنيف تأمين إيجار — عقد ' . $contractNum,
'description_en' => 'Rental deposit reclassification — contract ' . $contractNum,
'reference_type' => 'rental_deposit',
'reference_id' => $contractId,
'source_module' => 'rentals',
'is_auto_generated' => 1,
], [
[
'account_id' => (int) $serviceRevenue['id'],
'debit' => $amount,
'credit' => '0.00',
'description_ar' => 'إعادة تصنيف من إيراد خدمات إلى تأمينات',
],
[
'account_id' => (int) $deferredRevenue['id'],
'debit' => '0.00',
'credit' => $amount,
'description_ar' => 'تأمين إيجار مؤجل — عقد ' . $contractNum,
],
], true);
if (!$result['success']) {
Logger::error("Rental deposit reclassification failed", ['contract_id' => $contractId, 'error' => $result['error'] ?? '']);
}
}
/**
* Reverse deposit liability when refunded.
* Dr. Deferred Revenue (2105), Cr. Cash (1101)
*/
public static function onRentalDepositRefunded(array $data): void
{
$db = App::getInstance()->db();
$contractId = (int) ($data['contract_id'] ?? 0);
$paymentId = (int) ($data['payment_id'] ?? 0);
if ($contractId <= 0) {
return;
}
// Find the original deposit reclassification entry and reverse it
$entry = \App\Modules\Accounting\Models\JournalEntry::findByReference('rental_deposit', $contractId);
if ($entry && $entry->isPosted()) {
JournalService::reverseEntry((int) $entry->id, 'رد تأمين إيجار — عقد ' . $contractId);
}
}
// ────────────────────────────────────────────────────────────
// HELPERS
// ────────────────────────────────────────────────────────────
......
......@@ -160,11 +160,11 @@ EventBus::listen('fine.imposed', function (array $data): void {
}, 50);
// When a fine is collected, update AR
EventBus::listen('fine.collected', function (array $data): void {
EventBus::listen('fine.paid', function (array $data): void {
try {
AccountingIntegrationService::onFineCollected($data);
} catch (\Throwable $e) {
\App\Core\Logger::error('Accounting auto-post failed (fine.collected): ' . $e->getMessage());
\App\Core\Logger::error('Accounting auto-post failed (fine.paid): ' . $e->getMessage());
}
}, 50);
......@@ -177,3 +177,41 @@ EventBus::listen('installment.plan_created', function (array $data): void {
\App\Core\Logger::error('Accounting auto-post failed (installment.plan_created): ' . $e->getMessage());
}
}, 50);
// When an installment is paid, update AR
EventBus::listen('installment.paid', function (array $data): void {
try {
AccountingIntegrationService::onInstallmentPaid($data);
} catch (\Throwable $e) {
\App\Core\Logger::error('Accounting auto-post failed (installment.paid): ' . $e->getMessage());
}
}, 50);
// ── Refunds ─────────────────────────────────────────────────
// When a sale is refunded, post reversal GL entry
EventBus::listen('sale.refunded', function (array $data): void {
try {
AccountingIntegrationService::onSaleRefunded($data);
} catch (\Throwable $e) {
\App\Core\Logger::error('Accounting auto-post failed (sale.refunded): ' . $e->getMessage());
}
}, 50);
// ── Rentals ─────────────────────────────────────────────────
// When a rental deposit is collected, record deposit liability
EventBus::listen('rental.deposit_collected', function (array $data): void {
try {
AccountingIntegrationService::onRentalDepositCollected($data);
} catch (\Throwable $e) {
\App\Core\Logger::error('Accounting auto-post failed (rental.deposit_collected): ' . $e->getMessage());
}
}, 50);
// When a rental deposit is refunded, reverse deposit liability
EventBus::listen('rental.deposit_refunded', function (array $data): void {
try {
AccountingIntegrationService::onRentalDepositRefunded($data);
} catch (\Throwable $e) {
\App\Core\Logger::error('Accounting auto-post failed (rental.deposit_refunded): ' . $e->getMessage());
}
}, 50);
......@@ -12,6 +12,7 @@ use App\Modules\ActivitySubscriptions\Models\ActivityPricing;
use App\Modules\ActivitySubscriptions\Services\ActivitySubscriptionService;
use App\Modules\ActivitySubscriptions\Services\ActivityPricingService;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\Sales\Services\InventoryPaymentService;
use App\Modules\Disciplines\Models\SportDiscipline;
use App\Modules\Academies\Models\Academy;
......@@ -107,18 +108,34 @@ class ActivitySubscriptionController extends Controller
}
$amount = (string) ($sub['total_amount'] ?? '0.00');
$data = $request->all();
$data['member_id'] = null;
$data['player_id'] = (int) $sub['player_id'];
$data['amount'] = $amount;
$data['payment_type'] = 'activity_subscription';
$data['payment_method'] = $data['payment_method'] ?? 'cash';
$data['related_entity_type'] = 'activity_subscriptions';
$data['related_entity_id'] = (int) $id;
$data['description'] = 'اشتراك نشاط ' . ($sub['subscription_month'] ?? '') . ' — ' . ($sub['player_name'] ?? '');
$result = PaymentService::processPayment($data);
$paymentMethod = $request->post('payment_method', 'cash');
$description = 'اشتراك نشاط ' . ($sub['subscription_month'] ?? '') . ' — ' . ($sub['player_name'] ?? '');
// Look up player's member_id — member players go through PaymentService,
// non-member players go through InventoryPaymentService (guest path)
$player = $db->selectOne("SELECT member_id FROM players WHERE id = ?", [(int) $sub['player_id']]);
$playerMemberId = $player ? (int) ($player['member_id'] ?? 0) : 0;
if ($playerMemberId > 0) {
$result = PaymentService::processPayment([
'member_id' => $playerMemberId,
'amount' => $amount,
'payment_type' => 'activity_subscription',
'payment_method' => $paymentMethod,
'related_entity_type' => 'activity_subscriptions',
'related_entity_id' => (int) $id,
'description' => $description,
]);
} else {
$result = InventoryPaymentService::processGuestPayment([
'amount' => $amount,
'payment_method' => $paymentMethod,
'guest_name' => $sub['player_name'] ?? 'لاعب',
'related_entity_type' => 'activity_subscriptions',
'related_entity_id' => (int) $id,
'description' => $description,
]);
}
if (!$result['success']) {
return $this->redirect('/activity-subscriptions/' . $id)->withError($result['error']);
}
......
......@@ -12,6 +12,7 @@ use App\Modules\Death\Models\DeathCase;
use App\Modules\Archive\Services\ArchiveService;
use App\Modules\Rules\Services\RuleEngine;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\ServiceCatalog\Models\ServicePrice;
class DeathController extends Controller
{
......@@ -31,9 +32,11 @@ class DeathController extends Controller
$spouses = $db->select("SELECT * FROM spouses WHERE member_id = ? AND is_archived = 0 AND status = 'active'", [(int) $memberId]);
// Pre-calculate fees to show on form
$formFeeData = RuleEngine::get('FORM_TRANSFER_FEE');
$formFee = $formFeeData['amount'] ?? '570.00';
$annualSub = '527.00'; // 492 + 35 development
$formFee = ServicePrice::getPrice('SVC_TRANSFER_FORM', '570.00');
$annualSubBase = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00';
$annualSub = bcadd($annualSubBase, $devFee, 2);
$totalFee = bcadd($formFee, $annualSub, 2);
return $this->view('Death.Views.create', [
......@@ -63,9 +66,11 @@ class DeathController extends Controller
}
// Calculate fee (form fee + annual renewal)
$formFeeData = RuleEngine::get('FORM_TRANSFER_FEE');
$formFee = $formFeeData['amount'] ?? '570.00';
$annualSub = '527.00'; // 492 + 35 development
$formFee = ServicePrice::getPrice('SVC_TRANSFER_FORM', '570.00');
$annualSubBase = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00';
$annualSub = bcadd($annualSubBase, $devFee, 2);
$totalFee = bcadd($formFee, $annualSub, 2);
$case = DeathCase::create([
......
......@@ -14,6 +14,7 @@ use App\Modules\Members\Services\MemberNumberGenerator;
use App\Modules\Members\Services\MemberSearchService;
use App\Modules\Members\Services\BillingService;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\Rules\Services\RuleEngine;
class MemberController extends Controller
{
......@@ -178,7 +179,8 @@ class MemberController extends Controller
$remaining = bcsub($membershipValue, $amount, 2);
if (bccomp($remaining, '0', 2) > 0) {
$months = min(30, max(1, (int) $request->post('installment_months', 30)));
$interestRate = '22.00';
$interestRateData = RuleEngine::get('INSTALLMENT_INTEREST_RATE');
$interestRate = $interestRateData['percentage'] ?? '22.00';
$totalInterest = bcdiv(bcmul($remaining, $interestRate, 4), '100', 2);
$totalWithInterest = bcadd($remaining, $totalInterest, 2);
$monthlyPayment = bcdiv($totalWithInterest, (string) $months, 2);
......@@ -208,6 +210,12 @@ class MemberController extends Controller
'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
]);
}
EventBus::dispatch('installment.plan_created', [
'plan_id' => $planId,
'member_id' => (int) $id,
'total_amount' => $totalWithInterest,
]);
}
}
......
......@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Modules\Members\Services;
use App\Core\App;
use App\Modules\ServiceCatalog\Models\ServicePrice;
/**
* Calculates the TOTAL bill for a member including all additions.
......@@ -30,10 +31,12 @@ final class BillingService
$formFeePaid = ($ff !== null);
} catch (\Throwable $e) {}
$formFeeAmount = ServicePrice::getPrice('SVC_NEW_FORM', '505.00');
$items[] = [
'type' => 'form_fee',
'label' => 'رسوم استمارة عضوية (500 استمارة + 5 طابع شهداء)',
'amount' => '505.00',
'amount' => $formFeeAmount,
'paid' => $formFeePaid,
'included' => false,
'category' => 'required',
......
......@@ -12,6 +12,7 @@ use App\Modules\Payments\Models\Payment;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\Payments\Services\BalanceCalculator;
use App\Modules\Members\Services\MemberNumberGenerator;
use App\Modules\Rules\Services\RuleEngine;
class PaymentController extends Controller
{
......@@ -226,7 +227,8 @@ class PaymentController extends Controller
if (bccomp($remaining, '0', 2) > 0) {
// Create installment plan
$interestRate = '22.00';
$interestRateData = RuleEngine::get('INSTALLMENT_INTEREST_RATE');
$interestRate = $interestRateData['percentage'] ?? '22.00';
$months = 30; // Max default, can be overridden
$totalInterest = bcdiv(bcmul($remaining, $interestRate, 4), '100', 2);
$totalWithInterest = bcadd($remaining, $totalInterest, 2);
......@@ -276,6 +278,12 @@ class PaymentController extends Controller
'updated_at' => date('Y-m-d H:i:s'),
]);
}
EventBus::dispatch('installment.plan_created', [
'plan_id' => $planId,
'member_id' => $memberId,
'total_amount' => $totalWithInterest,
]);
}
// Assign number and activate
......
......@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Modules\Payments\Services;
use App\Core\App;
use App\Modules\ServiceCatalog\Models\ServicePrice;
/**
* Calculates all financial obligations and balances for a member.
......@@ -27,7 +28,7 @@ final class BalanceCalculator
// ── Form Fee ──
$formFeePaid = PaymentService::hasPaid($memberId, 'form_fee');
$formFee = '505.00';
$formFee = ServicePrice::getPrice('SVC_NEW_FORM', '505.00');
// ── Membership Fee ──
$membershipFeePaid = PaymentService::hasPaid($memberId, 'membership_fee');
......
......@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Modules\Sales\Services;
use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
/**
......@@ -79,6 +80,17 @@ final class InventoryPaymentService
$db->commit();
// Fire payment.completed so Accounting module auto-posts GL entries
EventBus::dispatch('payment.completed', [
'payment_id' => $paymentId,
'receipt_id' => $receiptId,
'receipt_number' => $receiptNumber,
'member_id' => 0,
'type' => 'inventory_sale',
'amount' => $amount,
'method' => $paymentMethod,
]);
Logger::info("Guest payment #{$paymentId} processed — receipt: {$receiptNumber}");
return [
......
......@@ -28,4 +28,51 @@ class ServicePrice extends Model
ORDER BY sc.service_code
");
}
/**
* Get the current price for a service by code.
* Returns base_amount for fixed prices, or the full row for percentage-based.
* Uses effective_from/effective_to to find the currently valid price.
* Falls back to $default if the service code is not found.
*/
public static function getPrice(string $serviceCode, ?string $default = null, ?int $branchId = null): ?string
{
$db = App::getInstance()->db();
$today = date('Y-m-d');
// Try branch-specific first, then global
$sql = "SELECT base_amount FROM service_catalog
WHERE service_code = ? AND is_active = 1
AND effective_from <= ?
AND (effective_to IS NULL OR effective_to >= ?)";
if ($branchId !== null) {
$row = $db->selectOne($sql . " AND branch_id = ? ORDER BY effective_from DESC LIMIT 1", [$serviceCode, $today, $today, $branchId]);
if ($row) return $row['base_amount'];
}
$row = $db->selectOne($sql . " AND branch_id IS NULL ORDER BY effective_from DESC LIMIT 1", [$serviceCode, $today, $today]);
return $row ? $row['base_amount'] : $default;
}
/**
* Get the full service catalog row (for percentage-based services).
*/
public static function getServiceRow(string $serviceCode, ?int $branchId = null): ?array
{
$db = App::getInstance()->db();
$today = date('Y-m-d');
$sql = "SELECT * FROM service_catalog
WHERE service_code = ? AND is_active = 1
AND effective_from <= ?
AND (effective_to IS NULL OR effective_to >= ?)";
if ($branchId !== null) {
$row = $db->selectOne($sql . " AND branch_id = ? ORDER BY effective_from DESC LIMIT 1", [$serviceCode, $today, $today, $branchId]);
if ($row) return $row;
}
return $db->selectOne($sql . " AND branch_id IS NULL ORDER BY effective_from DESC LIMIT 1", [$serviceCode, $today, $today]);
}
}
\ No newline at end of file
......@@ -75,6 +75,14 @@ class Spouse extends Model
return null;
}
public function getQualificationName(): string
{
if (!$this->qualification_id) return '—';
$db = App::getInstance()->db();
$row = $db->selectOne("SELECT name_ar FROM qualifications WHERE id = ?", [$this->qualification_id]);
return $row['name_ar'] ?? '—';
}
public function getClassificationLabel(): string
{
return match ($this->classification) {
......
......@@ -5,6 +5,7 @@ namespace App\Modules\Spouses\Services;
use App\Core\App;
use App\Modules\Spouses\Models\Spouse;
use App\Modules\ServiceCatalog\Models\ServicePrice;
/**
* Spouse Fee Calculator
......@@ -55,7 +56,7 @@ final class SpouseFeeCalculator
// Late addition = member is already active/accepted = must pay form fee
$isLateAddition = !in_array($member['status'] ?? '', ['potential', 'under_review']);
$formFee = $isLateAddition ? '570.00' : '0.00';
$formFee = $isLateAddition ? ServicePrice::getPrice('SVC_ADDITION_FORM', '570.00') : '0.00';
// ── Is the member acquired (مكتسب)? ──
$isAcquiredMember = self::isAcquiredMember($memberId);
......
......@@ -6,6 +6,7 @@ namespace App\Modules\Transfers\Services;
use App\Core\App;
use App\Modules\Rules\Services\RuleEngine;
use App\Modules\Pricing\Services\PricingEngine;
use App\Modules\ServiceCatalog\Models\ServicePrice;
final class SeparationFeeCalculator
{
......@@ -46,7 +47,7 @@ final class SeparationFeeCalculator
$formFee = $formFeeData['amount'] ?? '570.00';
// Annual subscription (basic member subscription)
$annualSub = '492.00'; // current year rate
$annualSub = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00';
$annualSubscriptionFee = bcadd($annualSub, $devFee, 2);
......
......@@ -5,6 +5,8 @@ namespace CronJobs;
use App\Core\Database;
use App\Core\Logger;
use App\Modules\ServiceCatalog\Models\ServicePrice;
use App\Modules\Rules\Services\RuleEngine;
class SubscriptionGeneratorJob
{
......@@ -31,12 +33,15 @@ class SubscriptionGeneratorJob
// Get all active members
$members = $this->db->select("SELECT id, full_name_ar, membership_type FROM members WHERE status = 'active' AND is_archived = 0");
$devFee = '35.00';
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00';
$memberRate = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
$spouseRate = ServicePrice::getPrice('SVC_ANNUAL_SPOUSE', '492.00');
$childRate = ServicePrice::getPrice('SVC_ANNUAL_CHILD', '222.00');
$tempRate = ServicePrice::getPrice('SVC_ANNUAL_TEMP', '222.00');
$ts = date('Y-m-d H:i:s');
foreach ($members as $m) {
$memberRate = '492.00'; // Default 2025/2026
// Generate for member
$this->db->insert('subscriptions', [
'member_id' => (int) $m['id'],
......@@ -62,9 +67,9 @@ class SubscriptionGeneratorJob
'person_type' => 'spouse',
'person_id' => (int) $sp['id'],
'person_name' => $sp['full_name_ar'],
'base_amount' => $memberRate,
'base_amount' => $spouseRate,
'development_fee'=> $devFee,
'total_amount' => bcadd($memberRate, $devFee, 2),
'total_amount' => bcadd($spouseRate, $devFee, 2),
'status' => 'pending',
'created_at' => $ts,
'updated_at' => $ts,
......@@ -74,7 +79,6 @@ class SubscriptionGeneratorJob
// Generate for children
$children = $this->db->select("SELECT id, full_name_ar FROM children WHERE member_id = ? AND is_archived = 0 AND status = 'active'", [(int) $m['id']]);
$childRate = '222.00';
foreach ($children as $ch) {
$this->db->insert('subscriptions', [
'member_id' => (int) $m['id'],
......@@ -101,9 +105,9 @@ class SubscriptionGeneratorJob
'person_type' => 'temporary',
'person_id' => (int) $tmp['id'],
'person_name' => $tmp['full_name_ar'],
'base_amount' => $childRate,
'base_amount' => $tempRate,
'development_fee'=> $devFee,
'total_amount' => bcadd($childRate, $devFee, 2),
'total_amount' => bcadd($tempRate, $devFee, 2),
'status' => 'pending',
'created_at' => $ts,
'updated_at' => $ts,
......
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