option('business_id'); if ($this->option('posting-keys')) { $this->backfillPostingKeys($businessId); } if ($this->option('link-payment-accounts')) { $this->linkPaymentAccounts($businessId); } if ($this->option('entity-tags')) { $this->backfillEntityTags($businessId); } if ($this->option('auto-map')) { $this->autoMapTransactions($businessId); } if (! $this->option('posting-keys') && ! $this->option('link-payment-accounts') && ! $this->option('entity-tags') && ! $this->option('auto-map')) { $this->backfillPostingKeys($businessId); $this->linkPaymentAccounts($businessId); $this->backfillEntityTags($businessId); } return self::SUCCESS; } protected function backfillPostingKeys(?string $businessId): void { if (! Schema::hasTable('accounting_posting_logs') || ! Schema::hasColumn('accounting_accounts_transactions', 'posting_key')) { $this->warn('Posting log tables not available.'); return; } $logs = DB::table('accounting_posting_logs')->where('status', 'posted'); if ($businessId) { $logs->where('business_id', $businessId); } $count = 0; foreach ($logs->get() as $log) { if (! $log->acc_trans_mapping_id) { continue; } $updated = DB::table('accounting_accounts_transactions') ->where('acc_trans_mapping_id', $log->acc_trans_mapping_id) ->whereNull('posting_key') ->update([ 'posting_key' => $log->posting_key, 'source_type' => $log->source_type, 'source_id' => $log->source_id, ]); $count += $updated; } $this->info("Backfilled posting_key on {$count} GL line(s)."); } protected function linkPaymentAccounts(?string $businessId): void { if (! Schema::hasColumn('accounts', 'accounting_account_id')) { $this->warn('accounts.accounting_account_id column not available.'); return; } $query = DB::table('business'); if ($businessId) { $query->where('id', $businessId); } $service = app(\Modules\Accounting\Services\PaymentAccountGlLinkService::class); $linked = 0; foreach ($query->pluck('id') as $id) { $linked += $service->linkByNameMatch((int) $id); } $this->info("Linked {$linked} payment account(s) to GL by name match."); } protected function backfillEntityTags(?string $businessId): void { if (! class_exists(\Modules\Accounting\Services\HoldingEntityResolutionService::class)) { return; } $service = app(\Modules\Accounting\Services\HoldingEntityResolutionService::class); $query = DB::table('business'); if ($businessId) { $query->where('id', $businessId); } $total = 0; foreach ($query->pluck('id') as $id) { $total += $service->backfillUntagged((int) $id); } $this->info("Backfilled holding_entity_id on {$total} GL line(s)."); } protected function autoMapTransactions(?string $businessId): void { if (! class_exists(\Modules\Accounting\Services\OperationalTransactionPostingService::class)) { return; } $service = app(\Modules\Accounting\Services\OperationalTransactionPostingService::class); $query = DB::table('business'); if ($businessId) { $query->where('id', $businessId); } $mapped = 0; foreach ($query->pluck('id') as $business) { $mapped += $this->mapRecentTransactionsForBusiness((int) $business, $service); } $this->info("Auto-mapped {$mapped} operational transaction(s)."); } protected function mapRecentTransactionsForBusiness(int $businessId, \Modules\Accounting\Services\OperationalTransactionPostingService $service): int { $since = now()->subDays(90)->toDateString(); $count = 0; $userId = 1; $transactions = DB::table('transactions') ->where('business_id', $businessId) ->whereIn('type', ['sell', 'purchase', 'expense']) ->where('status', 'final') ->whereDate('transaction_date', '>=', $since) ->get(['id', 'type', 'location_id']); foreach ($transactions as $transaction) { if (DB::table('accounting_accounts_transactions')->where('transaction_id', $transaction->id)->exists()) { continue; } $location = DB::table('business_locations')->where('id', $transaction->location_id)->first(); if (! $location) { continue; } $map = json_decode($location->accounting_default_map ?? '{}', true); $scenario = $transaction->type === 'sell' ? 'sale' : ($transaction->type === 'purchase' ? 'purchases' : 'expense'); $depositTo = $map[$scenario]['deposit_to'] ?? null; $paymentAccount = $map[$scenario]['payment_account'] ?? null; if (! $depositTo || ! $paymentAccount) { continue; } $type = $transaction->type === 'sell' ? 'sell' : $transaction->type; $service->post($type, (int) $transaction->id, $userId, $businessId, (int) $depositTo, (int) $paymentAccount, (int) $transaction->location_id); $count++; } return $count; } }