import(storage_path('app/public/csv/Fixed_EJ.csv')); // $collection = (new FastExcel)->import(storage_path('app/public/csv/Fixed_HJ.csv')); // $collection = (new FastExcel)->import(storage_path('app/public/csv/Fixed_K.csv')); $array = $collection->toArray(); $linkedInvoices = []; $linkedInvoiceTargets = $this->linkedInvoiceTargets($array); foreach ($this->groupRowsByInvoice($array) as $invoiceNo => $invoiceRows) { $invoiceClient = null; $linkedInvoiceNo = null; $payments = []; $totalSemAmount = 0.0; $totalNetAmount = 0.0; foreach ($invoiceRows as $row) { $customerId = str_replace('-', '', (string) $this->rowValue($row, 'customer_id', '')); $startDate = $this->date($this->rowValue($row, 'start_date')); $endDate = $this->date($this->rowValue($row, 'end_date')); $client = Client::where('customer_id', $customerId)->first(); if (! $client) { Log::warning('Client not found for customer_id: '.$customerId, [ 'invoice_no' => $invoiceNo, ]); continue; } if ($invoiceClient !== null && $invoiceClient->isNot($client)) { Log::warning('Invoice rows resolve to different clients; row skipped.', [ 'invoice_no' => $invoiceNo, 'expected_client_id' => $invoiceClient->id, 'row_client_id' => $client->id, ]); continue; } $invoiceClient ??= $client; $client->update([ 'industry' => $this->rowValue($row, 'industry'), ]); $salesUser = User::where('name', $this->rowValue($row, 'sales'))->first(); $pic = User::where('name', $this->rowValue($row, 'pic'))->first(); if ($pic) { ClientUserAssignation::updateOrCreate( [ 'client_id' => $client->id, 'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON, ], [ 'user_id' => $pic->id, ] ); } if ($salesUser) { ClientUserAssignation::updateOrCreate( [ 'client_id' => $client->id, 'role' => ClientUserAssignation::ROLE_SALES_PERSON, ], [ 'user_id' => $salesUser->id, ] ); } $rowLinkedInvoiceNo = $this->linkedInvoiceNo($this->rowValue($row, 'linked_invoice_no', '')); $managementFee = $this->amount($this->rowValue($row, 'management_fee', 0)); $mediaFee = $this->amount($this->rowValue($row, 'media_fee', 0)); $paymentNettAmount = $mediaFee + $managementFee; $tax = $this->tax($this->rowValue($row, 'tax', 0), $paymentNettAmount, $startDate); $paymentTotalAmount = $paymentNettAmount + $tax['amount']; $isCreditCard = $mediaFee == 0 && ! in_array($invoiceNo, $linkedInvoiceTargets, true); $payments[] = [ 'payment_no' => $this->nullableString($this->rowValue($row, 'payment_no')), 'payment_total_amount' => $paymentTotalAmount, 'payment_nett_amount' => $paymentNettAmount, 'items' => $this->paymentItems( $mediaFee, $managementFee, $tax['percentage'], $startDate, $endDate, $isCreditCard, ), ]; $totalSemAmount += $paymentTotalAmount; $totalNetAmount += $paymentNettAmount; if ($rowLinkedInvoiceNo !== null) { $linkedInvoiceNo ??= $rowLinkedInvoiceNo; } } if ($invoiceClient === null || $payments === []) { continue; } $invoice = ClientInvoice::updateOrCreate( ['invoice_no' => $invoiceNo], [ 'client_id' => $invoiceClient->id, 'approved_at' => now(), 'total_sem_amount' => $totalSemAmount, 'total_net_amount' => $totalNetAmount, ] ); $paymentSyncService->sync($invoice, $payments); if ($linkedInvoiceNo !== null) { $linkedInvoices[$invoiceNo] = $linkedInvoiceNo; } } foreach ($linkedInvoices as $invoiceNo => $linkedInvoiceNo) { $invoice = ClientInvoice::where('invoice_no', $invoiceNo)->first(); $linkedInvoice = ClientInvoice::where('invoice_no', $linkedInvoiceNo)->first(); if ($invoice && $linkedInvoice) { $invoice->update([ 'linked_invoice_id' => $linkedInvoice->id, ]); } } DB::commit(); } catch (\Exception $e) { DB::rollBack(); Log::error('Error project linkage : '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); return 1; } return 0; } private function rowValue(array $row, string $key, mixed $default = null): mixed { $normalizedKey = $this->normalizeHeader($key); foreach ($row as $rowKey => $value) { if ($this->normalizeHeader((string) $rowKey) === $normalizedKey) { return $value; } } return $default; } private function normalizeHeader(string $header): string { return trim(preg_replace('/[^a-z0-9]+/', '_', strtolower($header)), '_'); } private function nullableString(mixed $value): ?string { $value = trim((string) $value); return $value === '' ? null : $value; } private function amount(mixed $value): float { $normalized = preg_replace('/[^0-9.\-]/', '', (string) $value); return is_numeric($normalized) ? (float) $normalized : 0.0; } private function invoiceNo(mixed $value): string { $invoiceNo = trim((string) $value); if (preg_match('/^\s*([A-Za-z0-9]+)/', $invoiceNo, $matches)) { return $matches[1]; } return $invoiceNo; } private function linkedInvoiceNo(mixed $value): ?string { $linkedInvoiceNo = $this->invoiceNo($value); if ($linkedInvoiceNo === '' || $linkedInvoiceNo === '0') { return null; } return $linkedInvoiceNo; } /** * @param array> $rows * @return array>> */ private function groupRowsByInvoice(array $rows): array { $groupedRows = []; foreach ($rows as $row) { $invoiceNo = $this->invoiceNo($this->rowValue($row, 'invoice_no')); if ($invoiceNo === '') { Log::warning('Invoice row skipped because invoice_no is empty.'); continue; } $groupedRows[$invoiceNo][] = $row; } return $groupedRows; } /** * @param array> $rows * @return array */ private function linkedInvoiceTargets(array $rows): array { $invoiceNumbers = []; foreach ($rows as $row) { $linkedInvoiceNo = $this->linkedInvoiceNo($this->rowValue($row, 'linked_invoice_no', '')); if ($linkedInvoiceNo !== null) { $invoiceNumbers[] = $linkedInvoiceNo; } } return array_values(array_unique($invoiceNumbers)); } private function date(mixed $value): ?Carbon { if (empty($value)) { return null; } $date = trim((string) $value); foreach (['d/m/y', 'd/m/Y', 'Y-m-d', 'd-m-y', 'd-m-Y'] as $format) { try { return Carbon::createFromFormat($format, $date)->startOfDay(); } catch (\Throwable) { continue; } } return Carbon::parse($date)->startOfDay(); } /** * @return array{percentage: float, amount: float} */ private function tax(mixed $value, float $amount, ?Carbon $startDate): array { $tax = $this->amount($value); if ($tax > 100 && $amount > 0) { return [ 'percentage' => ($tax / $amount) * 100, 'amount' => $tax, ]; } if ($tax > 0) { return [ 'percentage' => $tax, 'amount' => $this->taxAmount($amount, $tax), ]; } if ($startDate !== null && $startDate->greaterThanOrEqualTo(Carbon::parse('2024-03-01'))) { return [ 'percentage' => 8, 'amount' => $this->taxAmount($amount, 8), ]; } return [ 'percentage' => 6, 'amount' => $this->taxAmount($amount, 6), ]; } private function taxAmount(float $amount, float $taxPercentage): float { return $amount * ($taxPercentage / 100); } /** * @return array> */ private function paymentItems( float $mediaFee, float $managementFee, float $taxPercentage, ?Carbon $startDate, ?Carbon $endDate, bool $isCreditCard, ): array { $items = []; if ($mediaFee > 0 || $isCreditCard) { $mediaTax = $this->taxAmount($mediaFee, $taxPercentage); $items[] = [ 'billing_item_types_id' => 1, 'start_date' => $startDate?->format('Y-m-d'), 'end_date' => $endDate?->format('Y-m-d'), 'payment_item_amount' => $mediaFee + $mediaTax, 'tax_percentage' => $taxPercentage, 'net_amount' => $mediaFee, 'withholding_tax' => $taxPercentage, 'final_net_amount' => $this->finalNetAmount($mediaFee, $taxPercentage), 'spending' => 0, 'is_creditcard' => $isCreditCard, ]; } if ($managementFee > 0) { $managementTax = $this->taxAmount($managementFee, $taxPercentage); $items[] = [ 'billing_item_types_id' => 2, 'start_date' => null, 'end_date' => null, 'payment_item_amount' => $managementFee + $managementTax, 'tax_percentage' => $taxPercentage, 'net_amount' => $managementFee, 'withholding_tax' => 0, 'final_net_amount' => $this->finalNetAmount($managementFee, 0), 'spending' => 0, 'is_creditcard' => false, ]; } return $items; } private function finalNetAmount(float $netAmount, float $withholdingTax): float { return $netAmount / (1 + ($withholdingTax / 100)); } }