usesUnifiedPosting()) { $this->accountingUtil->saveMap($type, $id, $userId, $businessId, $depositTo, $paymentAccount); return; } $context = $this->resolveContext($type, $id, $businessId); $holdingEntityId = $this->entityResolution->resolveFromLocation($businessId, $locationId ?? $context['location_id']); $this->postingService->post(new FinancialPostingRequest( businessId: $businessId, userId: $userId, sourceType: $type, sourceId: $id, note: $this->noteForType($type, $context), lines: [ [ 'accounting_account_id' => $depositTo, 'amount' => $context['amount'], 'type' => 'debit', 'map_type' => 'deposit_to', 'transaction_id' => $context['transaction_id'], 'transaction_payment_id' => $context['transaction_payment_id'], 'holding_entity_id' => $holdingEntityId, ], [ 'accounting_account_id' => $paymentAccount, 'amount' => $context['amount'], 'type' => 'credit', 'map_type' => 'payment_account', 'transaction_id' => $context['transaction_id'], 'transaction_payment_id' => $context['transaction_payment_id'], 'holding_entity_id' => $holdingEntityId, ], ], operationDate: $context['operation_date'], holdingEntityId: $holdingEntityId, transactionId: $context['transaction_id'], transactionPaymentId: $context['transaction_payment_id'], mappingType: $type, ), true); } public function delete(string $type, int $transactionId, ?int $paymentId = null, ?int $businessId = null): void { if ($this->usesUnifiedPosting() && $businessId) { $sourceId = in_array($type, ['sell_payment', 'purchase_payment'], true) ? (int) $paymentId : $transactionId; if ($sourceId) { $postingKey = $businessId.':'.$type.':'.$sourceId; $userId = (int) (request()->session()->get('user.id') ?: 1); $this->postingService->reverseByPostingKey($businessId, $postingKey, $userId); } } $this->accountingUtil->deleteMap($transactionId, $paymentId); } public function assessGaps(int $businessId, int $days = 90): array { $since = now()->subDays($days)->toDateString(); $unmappedSells = $this->countUnmappedTransactions($businessId, 'sell', $since); $unmappedPurchases = $this->countUnmappedTransactions($businessId, 'purchase', $since); $unmappedExpenses = $this->countUnmappedTransactions($businessId, 'expense', $since); $unmappedPayments = $this->countUnmappedPayments($businessId, $since); $total = $unmappedSells + $unmappedPurchases + $unmappedExpenses + $unmappedPayments; return [ 'unmapped_sells' => $unmappedSells, 'unmapped_purchases' => $unmappedPurchases, 'unmapped_expenses' => $unmappedExpenses, 'unmapped_payments' => $unmappedPayments, 'total_unmapped' => $total, 'ready' => $total === 0, 'unified_posting' => $this->usesUnifiedPosting(), ]; } protected function resolveContext(string $type, int $id, int $businessId): array { if (in_array($type, ['sell_payment', 'purchase_payment'], true)) { $payment = TransactionPayment::where('id', $id)->where('business_id', $businessId)->firstOrFail(); $transaction = Transaction::find($payment->transaction_id); return [ 'amount' => (float) $payment->amount, 'operation_date' => optional($payment->paid_on)->format('Y-m-d H:i:s') ?? now()->toDateTimeString(), 'transaction_id' => null, 'transaction_payment_id' => $payment->id, 'location_id' => $transaction?->location_id, 'reference' => $payment->payment_ref_no ?? (string) $payment->id, ]; } $transaction = Transaction::where('business_id', $businessId)->where('id', $id)->firstOrFail(); return [ 'amount' => (float) $transaction->final_total, 'operation_date' => optional($transaction->transaction_date)->format('Y-m-d H:i:s') ?? now()->toDateTimeString(), 'transaction_id' => $transaction->id, 'transaction_payment_id' => null, 'location_id' => $transaction->location_id, 'reference' => $transaction->invoice_no ?? (string) $transaction->id, ]; } protected function noteForType(string $type, array $context): string { return match ($type) { 'sell' => 'Sell invoice '.$context['reference'], 'purchase' => 'Purchase '.$context['reference'], 'expense' => 'Expense '.$context['reference'], 'sell_payment' => 'Sell payment '.$context['reference'], 'purchase_payment' => 'Purchase payment '.$context['reference'], default => ucfirst(str_replace('_', ' ', $type)).' '.$context['reference'], }; } protected function countUnmappedTransactions(int $businessId, string $type, string $since): int { return (int) Transaction::where('transactions.business_id', $businessId) ->where('transactions.type', $type) ->where('transactions.status', 'final') ->whereDate('transactions.transaction_date', '>=', $since) ->whereNotExists(function ($q) { $q->selectRaw('1') ->from('accounting_accounts_transactions as aat') ->whereColumn('aat.transaction_id', 'transactions.id'); }) ->count(); } protected function countUnmappedPayments(int $businessId, string $since): int { return (int) TransactionPayment::where('transaction_payments.business_id', $businessId) ->whereDate('transaction_payments.paid_on', '>=', $since) ->whereNotExists(function ($q) { $q->selectRaw('1') ->from('accounting_accounts_transactions as aat') ->whereColumn('aat.transaction_payment_id', 'transaction_payments.id'); }) ->count(); } }