false, 'report' => ["فایل پیدا نشد: $filePath"]]; } @set_time_limit(600); @ini_set('memory_limit', '512M'); try { $report[] = "خواندن فایل: $filePath"; [$patients, $oldIdToIcno] = $this->parsePatients($filePath); $report[] = 'بیماران خوانده‌شده: ' . count($patients); $visits = $this->parseVisits($filePath); $report[] = 'ویزیت‌های خوانده‌شده: ' . count($visits); DB::beginTransaction(); try { [$pCreated, $pUpdated, $icnoToNewId] = $this->importPatients($patients); $report[] = "بیماران — جدید: $pCreated | به‌روزرسانی: $pUpdated"; $oldIntIdToNewId = $this->buildOldIntIdMap($oldIdToIcno, $icnoToNewId); [$vCreated, $vSkipped] = $this->importVisits($visits, $oldIntIdToNewId, $icnoToNewId); $report[] = "ویزیت‌ها — جدید: $vCreated | رد‌شده: $vSkipped"; DB::commit(); } catch (\Throwable $e) { DB::rollBack(); throw $e; } return ['success' => true, 'report' => $report]; } catch (\Throwable $e) { return ['success' => false, 'report' => array_merge($report, ['خطا: ' . $e->getMessage()])]; } } private function parsePatients(string $filePath): array { $rows = []; $oldIdToIcno = []; foreach ($this->streamInsertRows($filePath, 'patients') as [$columns, $values]) { $row = array_combine($columns, $values); $oldId = (string) ($row['ID'] ?? ''); $icno = $row['icno'] ?? ''; if ($icno !== '' && $oldId !== '') { $oldIdToIcno[$oldId] = $icno; } $rows[] = $row; } return [$rows, $oldIdToIcno]; } private function parseVisits(string $filePath): array { $rows = []; foreach ($this->streamInsertRows($filePath, 'visits') as [$columns, $values]) { $rows[] = array_combine($columns, $values); } return $rows; } private function importPatients(array $patients): array { $created = 0; $updated = 0; $icnoToNewId = []; $colMap = [ 'icno' => 'icno', 'first_name' => 'first_name', 'last_name' => 'last_name', 'father_name' => 'father_name', 'gender' => 'gender', 'age' => 'age', 'job' => 'job', 'marital_status' => 'marital_status', 'education' => 'education', 'hand_phone' => 'hand_phone', 'home_phone' => 'home_phone', 'work_phone' => 'work_phone', 'other_phone' => 'other_phone', 'home_address' => 'home_address', 'work_address' => 'work_address', 'refered_by' => 'refered_by', 'referal_reason' => 'referal_reason', 'doc_id' => 'doc_id', 'insurance' => 'insurance', 'insurance_no' => 'insurance_no', 'is_undercare' => 'is_undercare', 'undercare_reason' => 'undercare_reason', 'is_usingdrug' => 'is_usingdrug', 'underdrug_reason' => 'underdrug_reason', 'has_alergyto' => 'has_alergyto', 'alergy_reason' => 'alergy_reason', 'description' => 'description', 'birth_date' => 'birth_date', 'blood_pressure' => 'blood_pressure', 'blood_sugar' => 'blood_sugar', 'surgery_before' => 'surgery_before', 'sergery_after' => 'sergery_after', 'photos_before' => 'photos_before', 'photos_after' => 'photos_after', 'videos' => 'videos', 'current_illness_1' => 'current_illness_1', 'current_illness_2' => 'current_illness_2', ]; $jsonArrayFields = [ 'photos_before', 'photos_after', 'videos', 'has_alergyto', 'current_illness_1', 'current_illness_2', ]; foreach ($patients as $raw) { $icno = trim($raw['icno'] ?? ''); if ($icno === '' || $icno === 'NULL') { continue; } $row = []; foreach ($colMap as $oldCol => $newCol) { $val = $raw[$oldCol] ?? null; $val = ($val === 'NULL' || $val === null) ? null : $val; if ($val !== null && in_array($newCol, $jsonArrayFields, true)) { $val = $this->normaliseJsonArray($val); } $row[$newCol] = $val; } $row['audio_files'] = null; $row['icno'] = $icno; $createdAt = $this->toGregorianDatetime($raw['log_datetime'] ?? null); $updatedAt = $this->toGregorianDatetime($raw['modify_datetime'] ?? null); $row['created_at'] = $createdAt ?? now(); $row['updated_at'] = $updatedAt ?? $createdAt ?? now(); $row['created_by'] = null; $row['updated_by'] = null; $existing = DB::table('patients')->where('icno', $icno)->first(); if ($existing) { DB::table('patients')->where('icno', $icno)->update($row); $icnoToNewId[$icno] = $existing->id; $updated++; } else { $newId = DB::table('patients')->insertGetId($row); $icnoToNewId[$icno] = $newId; $created++; } } return [$created, $updated, $icnoToNewId]; } private function importVisits(array $visits, array $oldIntIdToNewId, array $icnoToNewId): array { $created = 0; $skipped = 0; $colMap = [ 'visit_date' => 'visit_date', 'visit_time' => 'visit_time', 'treatment' => 'treatment', 'treatment_cost' => 'treatment_cost', 'payment_type' => 'payment_type', 'paid_amount' => 'paid_amount', 'pos_ul' => 'pos_ul', 'pos_ll' => 'pos_ll', 'pos_ur' => 'pos_ur', 'pos_lr' => 'pos_lr', 'treatment_description'=> 'treatment_description', 'doctor' => 'doctor', ]; $intFields = ['treatment', 'treatment_cost', 'payment_type', 'paid_amount', 'pos_ul', 'pos_ll', 'pos_ur', 'pos_lr', 'doctor']; foreach ($visits as $raw) { $oldPatientRef = (string) ($raw['patient'] ?? ''); $newPatientId = $this->resolvePatientId($oldPatientRef, $oldIntIdToNewId, $icnoToNewId); if ($newPatientId === null) { $skipped++; continue; } $row = ['patient' => $newPatientId]; foreach ($colMap as $oldCol => $newCol) { $val = $raw[$oldCol] ?? null; $val = ($val === 'NULL' || $val === null || $val === '') ? null : $val; $row[$newCol] = $val; } if (! empty($row['visit_date'])) { $row['visit_date'] = $this->jalaliToGregorian($row['visit_date']); } $row['visit_time'] = null; foreach ($intFields as $f) { if (isset($row[$f])) { $row[$f] = (int) $row[$f]; } } $row['created_at'] = now(); $row['updated_at'] = now(); $row['created_by'] = null; $row['updated_by'] = null; DB::table('visits')->insert($row); $created++; } return [$created, $skipped]; } private function jalaliToGregorian(?string $jalali): ?string { if (empty($jalali) || $jalali === 'NULL') { return null; } try { $normalised = str_replace('-', '/', trim($jalali)); return \Morilog\Jalali\Jalalian::fromFormat('Y/m/d', $normalised) ->toCarbon() ->format('Y-m-d'); } catch (\Throwable) { return null; } } private function toGregorianDatetime(?string $value): ?string { if (empty($value) || $value === 'NULL') { return null; } return $value; } private function buildOldIntIdMap(array $oldIdToIcno, array $icnoToNewId): array { $map = []; foreach ($oldIdToIcno as $oldId => $icno) { if (isset($icnoToNewId[$icno])) { $map[$oldId] = $icnoToNewId[$icno]; } } return $map; } private function resolvePatientId(string $ref, array $oldIntIdToNewId, array $icnoToNewId): ?int { if ($ref === '' || $ref === 'NULL') { return null; } if (isset($oldIntIdToNewId[$ref])) { return $oldIntIdToNewId[$ref]; } if (isset($icnoToNewId[$ref])) { return $icnoToNewId[$ref]; } // Live DB fallback $p = Patient::where('icno', $ref)->first(); return $p?->id; } private function normaliseJsonArray(?string $value): ?string { if ($value === null || $value === '') { return null; } $decoded = json_decode($value, true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { $paths = array_values(array_filter(array_map(function ($item) { if (is_string($item)) { return $item; } if (is_array($item) && isset($item['name'])) { return $item['name']; } return null; }, $decoded))); return json_encode($paths, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } // plain string — wrap it return json_encode([$value], JSON_UNESCAPED_UNICODE); } private function streamInsertRows(string $filePath, string $table): \Generator { $handle = fopen($filePath, 'r'); $pattern = '/^INSERT INTO `' . preg_quote($table, '/') . '` \((.+?)\) VALUES\s*$/i'; $columns = []; $inInsert = false; $rowBuffer = ''; while (($raw = fgets($handle)) !== false) { $line = rtrim($raw); if (preg_match($pattern, $line, $m)) { $inInsert = true; $columns = $this->parseColumnList($m[1]); $rowBuffer = ''; continue; } if ($inInsert && preg_match('/^INSERT INTO `([^`]+)`/i', $line, $m2) && strtolower($m2[1]) !== strtolower($table)) { $inInsert = false; $rowBuffer = ''; continue; } if (! $inInsert) { continue; } if ($line === ';') { $inInsert = false; $rowBuffer = ''; continue; } if ($line === '' || str_starts_with($line, '--')) { continue; } $rowBuffer .= ($rowBuffer !== '' ? "\n" : '') . $line; if ($this->isCompleteValueRow($rowBuffer)) { $values = $this->parseValueRow($rowBuffer); if (count($values) === count($columns)) { yield [$columns, $values]; } $rowBuffer = ''; } } fclose($handle); } private function parseColumnList(string $colStr): array { $cols = []; foreach (explode(',', $colStr) as $c) { $cols[] = trim(trim($c), '`'); } return $cols; } private function isCompleteValueRow(string $buf): bool { if (! str_starts_with($buf, '(')) { return false; } $count = 0; $escaped = false; $len = strlen($buf); for ($i = 0; $i < $len; $i++) { if ($escaped) { $escaped = false; continue; } if ($buf[$i] === '\\') { $escaped = true; continue; } if ($buf[$i] === "'") { $count++; } } return ($count % 2 === 0) && (bool) preg_match('/\),?;?\s*$/', $buf); } private function parseValueRow(string $row): array { $row = trim($row); $row = preg_replace('/[,;]\s*$/', '', $row); if (str_starts_with($row, '(')) { $row = substr($row, 1); } if (str_ends_with($row, ')')) { $row = substr($row, 0, -1); } $values = []; $current = ''; $inStr = false; $escaped = false; $len = strlen($row); for ($i = 0; $i < $len; $i++) { $ch = $row[$i]; if ($escaped) { $current .= $ch; $escaped = false; continue; } if ($ch === '\\') { $current .= $ch; $escaped = true; continue; } if ($ch === "'" && ! $inStr) { $inStr = true; continue; } if ($ch === "'" && $inStr) { $inStr = false; continue; } if ($ch === ',' && ! $inStr) { $values[] = $this->unescapeSqlString($current); $current = ''; continue; } $current .= $ch; } $values[] = $this->unescapeSqlString($current); return $values; } private function unescapeSqlString(string $val): string { $val = trim($val); if ($val === 'NULL') { return 'NULL'; } return str_replace( ['\\n', '\\r', '\\t', "\\'", '\\\\', '\\"'], ["\n", "\r", "\t", "'", '\\', '"'], $val ); } }