findOrFail($this->userId); $spreadsheet = (new Xlsx)->load(Storage::disk('upload')->path((string) $this->import->filename)); $sheet = $spreadsheet->getActiveSheet(); $headers = $sheet->rangeToArray('A1:E1', null, true, true, false)[0]; if (array_map($this->normalize(...), $headers) !== array_map($this->normalize(...), self::HEADERS)) { throw new RuntimeException('Некорректные заголовки файла складских заказов.'); } $created = 0; $updated = 0; $errors = 0; for ($row = 2; $row <= $sheet->getHighestDataRow(); $row++) { $orderNumber = trim((string) $sheet->getCell("A{$row}")->getFormattedValue()); $article = trim((string) $sheet->getCell("B{$row}")->getFormattedValue()); if ($orderNumber === '' && $article === '') { continue; } try { if ($orderNumber === '') { throw new RuntimeException('Не указан номер заказа.'); } if ($article === '') { throw new RuntimeException('Не указан артикул.'); } $item = CommonCatalogItem::query()->where('article', $article)->firstOrFail(); $status = $this->status((string) $sheet->getCell("C{$row}")->getFormattedValue()); $quantity = filter_var( $sheet->getCell("D{$row}")->getCalculatedValue(), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]], ); if ($quantity === false) { throw new RuntimeException('Количество должно быть целым числом больше нуля.'); } $data = [ 'common_catalog_item_id' => $item->id, 'order_number' => $orderNumber, 'status' => $status, 'ordered_quantity' => $quantity, 'note' => $this->nullable((string) $sheet->getCell("E{$row}")->getValue()), ]; $order = StockOrder::withTrashed() ->where('order_number', $orderNumber) ->where('common_catalog_item_id', $item->id) ->first(); if ($order) { if ($order->trashed()) { $order->restore(); } $this->inventoryService->updateOrder($order, $data, $actor); $updated++; } else { $this->inventoryService->createOrder($data, $actor); $created++; } } catch (Throwable $exception) { $errors++; $this->import->log("Строка {$row}: {$exception->getMessage()}", 'WARNING'); } } $spreadsheet->disconnectWorksheets(); $this->import->log("Создано: {$created}; обновлено: {$updated}; ошибок: {$errors}."); $this->import->update(['status' => 'DONE']); return true; } catch (Throwable $exception) { $this->import->log($exception->getMessage(), 'ERROR'); $this->import->update(['status' => 'ERROR']); throw $exception; } } private function status(string $value): string { return match (mb_strtolower(trim($value))) { 'заказан', 'заказано', StockOrder::STATUS_ORDERED => StockOrder::STATUS_ORDERED, 'на складе', StockOrder::STATUS_IN_STOCK => StockOrder::STATUS_IN_STOCK, 'отгружено', 'отгружен', StockOrder::STATUS_SHIPPED => StockOrder::STATUS_SHIPPED, default => throw new RuntimeException("Неизвестный статус: {$value}."), }; } private function normalize(mixed $value): string { return mb_strtolower(trim((string) preg_replace('/\s+/u', ' ', (string) $value))); } private function nullable(string $value): ?string { $value = trim($value); return $value === '' ? null : $value; } }