This commit is contained in:
Simon Pocrnjič
2025-10-08 22:36:54 +02:00
parent f40c3d0f2e
commit 39dd3d4d8f
2 changed files with 78 additions and 52 deletions
+75 -1
View File
@@ -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.
*/