diff --git a/app/Http/Controllers/ClientCaseContoller.php b/app/Http/Controllers/ClientCaseContoller.php index 4a90271..01889f0 100644 --- a/app/Http/Controllers/ClientCaseContoller.php +++ b/app/Http/Controllers/ClientCaseContoller.php @@ -182,7 +182,6 @@ public function updateContract(ClientCase $clientCase, string $uuid, UpdateContr $accountData = []; // Track old balance before applying changes $currentAccount = $contract->account; // newest (latestOfMany) - $oldBalance = (float) optional($currentAccount)->balance_amount; if (! is_null($initial)) { $accountData['initial_amount'] = $initial; } @@ -213,56 +212,6 @@ public function updateContract(ClientCase $clientCase, string $uuid, UpdateContr $created = $contract->account()->create($accountData); $freshBal = (float) optional($created->fresh())->balance_amount; } - // If multiple historical accounts exist, log them and optionally propagate update to all to keep consistent - $allAccounts = \DB::table('accounts')->where('contract_id', $contract->id)->orderBy('id')->get(['id','balance_amount','initial_amount']); - if ($allAccounts->count() > 1 && array_key_exists('balance_amount', $accountData)) { - // Propagate balance to all for consistency (comment out if not desired) - \DB::table('accounts')->where('contract_id', $contract->id)->update(['balance_amount' => $accountData['balance_amount'], 'updated_at' => now()]); - $freshBal = (float) \DB::table('accounts')->where('contract_id', $contract->id)->latest('id')->value('balance_amount'); - } - try { - $accountCount = $allAccounts->count(); - logger()->info('Contract account upsert', [ - 'contract_id' => $contract->id, - 'request_initial' => $initial, - 'request_balance_present' => $balanceFieldPresent, - 'request_balance' => $balance, - 'request_account_type_id' => $request->input('account_type_id'), - 'account_data_applied' => $accountData, - 'old_balance' => $oldBalance, - 'new_balance_after_update' => $freshBal, - 'accounts_for_contract' => $accountCount, - 'accounts_snapshot' => $allAccounts, - ]); - } catch (\Throwable $e) { - // ignore logging errors - } - - // After update/create, if balance_amount changed (and not through a payment), log an activity with before/after - if (array_key_exists('balance_amount', $accountData)) { - // Guard against null account (e.g., if creation failed silently earlier) - $newAccount = $contract->account; // refreshed latest - $newBalance = $newAccount ? (float) optional($newAccount->fresh())->balance_amount : $oldBalance; - if ($newAccount && $newBalance !== $oldBalance) { - try { - $currency = optional(\App\Models\PaymentSetting::query()->first())->default_currency ?? 'EUR'; - $beforeStr = number_format($oldBalance, 2, ',', '.').' '.$currency; - $afterStr = number_format($newBalance, 2, ',', '.').' '.$currency; - $note = 'Sprememba stanja (Stanje pred: '.$beforeStr.', Stanje po: '.$afterStr.'; Izvor: sprememba)'; - \App\Models\Activity::create([ - 'due_date' => null, - 'amount' => null, - 'note' => $note, - 'action_id' => null, - 'decision_id' => null, - 'client_case_id' => $contract->client_case_id, - 'contract_id' => $contract->id, - ]); - } catch (\Throwable $e) { - // non-fatal - } - } - } } }); @@ -1376,6 +1325,8 @@ public function archiveContract(ClientCase $clientCase, string $uuid, Request $r $hasReactivateRule = false; } + + $executor = app(\App\Services\Archiving\ArchiveExecutor::class); $context = [ 'contract_id' => $contract->id, @@ -1388,6 +1339,7 @@ public function archiveContract(ClientCase $clientCase, string $uuid, Request $r $overall = []; $hadAnyEffect = false; foreach ($settings as $setting) { + dd($setting); $res = $executor->executeSetting($setting, $context, optional($request->user())->id); foreach ($res as $table => $count) { $overall[$table] = ($overall[$table] ?? 0) + $count; diff --git a/app/Services/ImportProcessor.php b/app/Services/ImportProcessor.php index 0873c16..c4d8eaf 100644 --- a/app/Services/ImportProcessor.php +++ b/app/Services/ImportProcessor.php @@ -213,6 +213,14 @@ public function process(Import $import, ?Authenticatable $user = null): array $importRow = null; try { $rawAssoc = $this->buildRowAssoc($row, $header); + // Skip entirely empty rows (all raw values blank/null after trimming) without creating an ImportRow + if ($this->rowIsEffectivelyEmpty($rawAssoc)) { + $skipped++; + if ($isPg) { + // No DB changes were made for this row; nothing to roll back explicitly. + } + continue; // proceed to next CSV row + } [$recordType, $mapped] = $this->applyMappings($rawAssoc, $mappings); // Determine row-level reactivation intent: precedence row > import > template @@ -1083,7 +1091,11 @@ private function upsertAccount(Import $import, array $mapped, $mappings): array ]; foreach (['start_date', 'end_date', 'description', 'type_id'] as $k) { if (array_key_exists($k, $contractFields) && ! is_null($contractFields[$k])) { - $newContractData[$k] = $contractFields[$k]; + $val = $contractFields[$k]; + if (in_array($k, ['start_date', 'end_date'], true)) { + $val = $this->normalizeDate(is_scalar($val) ? (string) $val : null); + } + $newContractData[$k] = $val; } } $newContractData['start_date'] = $newContractData['start_date'] ?? now()->toDateString(); @@ -1422,6 +1434,12 @@ private function upsertContractChain(Import $import, array $mapped, $mappings): return ['action' => 'skipped', 'message' => 'No contract fields marked for insert']; } $data = array_filter($applyInsert, fn ($v) => ! is_null($v)); + if (array_key_exists('start_date', $data)) { + $data['start_date'] = $this->normalizeDate(is_scalar($data['start_date']) ? (string) $data['start_date'] : null) ?? $data['start_date']; + } + if (array_key_exists('end_date', $data)) { + $data['end_date'] = $this->normalizeDate(is_scalar($data['end_date']) ? (string) $data['end_date'] : null) ?? $data['end_date']; + } $data['client_case_id'] = $clientCaseId; $data['reference'] = $reference; // ensure required defaults @@ -1441,6 +1459,39 @@ private function sanitizeHeaderName(string $v): string return trim($v); } + /** + * Normalize a raw date string coming from import sources to Y-m-d or null. + * Accepts common European formats like d.m.Y / d.m.y / d/m/Y / d/m/y and ISO. + * Falls back to strtotime parsing; returns null on failure instead of throwing. + */ + private function normalizeDate(?string $raw): ?string + { + if ($raw === null) { + return null; + } + $raw = trim($raw); + if ($raw === '') { + return null; + } + $candidates = ['d.m.Y', 'd.m.y', 'd/m/Y', 'd/m/y', 'Y-m-d']; + foreach ($candidates as $fmt) { + $dt = \DateTime::createFromFormat($fmt, $raw); + if ($dt instanceof \DateTime) { + // Reject invalid (createFromFormat returns false on mismatch; partial matches handled by checking errors) + $errors = \DateTime::getLastErrors(); + if (($errors['warning_count'] ?? 0) === 0 && ($errors['error_count'] ?? 0) === 0) { + return $dt->format('Y-m-d'); + } + } + } + // Fallback: strtotime (very permissive); if fails return null + $ts = @strtotime($raw); + if ($ts === false) { + return null; + } + return date('Y-m-d', $ts); + } + private function findSourceColumnFor($mappings, string $targetField): ?string { foreach ($mappings as $map) { @@ -1627,6 +1678,29 @@ private function collectPaymentAppliedFields(array $payload, \App\Models\Payment return $fields; } + /** + * Determine if a raw CSV row is "effectively" empty: all scalar values are null or blank after trimming. + * Non-scalar values (arrays/objects) will cause the row to be treated as non-empty. + */ + private function rowIsEffectivelyEmpty(array $rawAssoc): bool + { + if (empty($rawAssoc)) { + return true; // no columns at all + } + foreach ($rawAssoc as $v) { + if (is_array($v) || is_object($v)) { + return false; // treat structured data as content + } + if (! is_null($v)) { + $s = trim((string) $v); + if ($s !== '') { + return false; + } + } + } + return true; + } + /** * Ensure mapping roots are recognized; fail fast if unknown roots found. */