with([ 'manager', 'items.catalogItem.documents', 'items.passportFile', 'documents', ]) ->findOrFail($orderId); return match ($type) { self::TYPE_ITEMS => $this->exportItems($order, $userId), self::TYPE_DELIVERY => $this->deliveryRequest($order, (int) $relatedId, $userId), self::TYPE_INSTALLATION => $this->installationPack($order, (int) $relatedId, $userId), self::TYPE_TECHNICAL_DOCUMENTS => $this->technicalDocuments($order, $itemIds, $userId), default => throw new RuntimeException('Неизвестный тип документа заказа.'), }; } private function exportItems(ProductionOrder $order, int $userId): File { $spreadsheet = new Spreadsheet; $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('МАФ'); $sheet->mergeCells('A1:F1'); $sheet->setCellValue('A1', 'Оборудование заказа №'.$order->order_number); $sheet->mergeCells('A2:F2'); $sheet->setCellValue('A2', sprintf( 'Заказчик: %s; адрес: %s; счёт №%s', $order->customer_name, $order->object_address, $order->invoice_number, )); $sheet->fromArray([ 'Артикул', 'Наименование', 'Номер заказа МАФ', 'Заводской номер', 'Дата производства', 'Паспорт', ], null, 'A4'); $row = 5; foreach ($order->items as $item) { $sheet->setCellValueExplicit("A{$row}", (string) $item->catalogItem?->article, DataType::TYPE_STRING); $sheet->setCellValue("B{$row}", $item->catalogItem?->calculator_name ?: $item->catalogItem?->print_name); $sheet->setCellValueExplicit("C{$row}", (string) $item->order_item_number, DataType::TYPE_STRING); $sheet->setCellValueExplicit("D{$row}", (string) $item->factory_number, DataType::TYPE_STRING); $sheet->setCellValue("E{$row}", $item->manufacture_date?->format('d.m.Y')); $sheet->setCellValue("F{$row}", $item->passport_file_id ? 'Да' : 'Нет'); $row++; } $lastRow = max(4, $row - 1); $sheet->getStyle("A4:F{$lastRow}")->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN); $sheet->getStyle("A1:F{$lastRow}")->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_TOP); foreach (['A' => 18, 'B' => 42, 'C' => 24, 'D' => 22, 'E' => 20, 'F' => 14] as $column => $width) { $sheet->getColumnDimension($column)->setWidth($width); } $filename = $this->safeFilename("МАФ_заказа_{$order->order_number}.xlsx"); $file = $this->writeSpreadsheet($spreadsheet, $filename, $userId, new Xlsx($spreadsheet)); $spreadsheet->disconnectWorksheets(); return $file; } private function deliveryRequest(ProductionOrder $order, int $deliveryId, int $userId): File { $delivery = $order->deliveries()->with('driver')->findOrFail($deliveryId); $template = base_path('templates/DeliveryRequest.xls'); if (! is_file($template)) { throw new RuntimeException('Шаблон заявки на доставку не найден.'); } $spreadsheet = IOFactory::load($template); $sheet = $spreadsheet->getActiveSheet(); $sheet->getPageSetup()->setPrintArea('B1:AG94'); $sheet->setCellValue('B2', 'Заявка на доставку/самовывоз оборудования от '.now()->format('d.m.Y')); $sheet->setCellValue('G4', $order->invoice_number); $sheet->setCellValue('N4', $order->invoice_date?->format('d.m.Y')); $sheet->setCellValue('G5', $order->contract_number); $sheet->setCellValue('N5', $order->contract_date?->format('d.m.Y')); $sheet->setCellValue('G6', $order->order_number); $sheet->setCellValue('F8', $order->manager?->name); $sheet->setCellValue('V8', $order->manager?->phone); $sheet->setCellValue('B11', $delivery->carrier ?: $delivery->driver?->name); $sheet->setCellValue('O11', trim(implode(' / ', array_filter([ $delivery->request_number, $delivery->request_date?->format('d.m.Y'), ])))); $sheet->setCellValue('S11', $this->shortTime($delivery->delivery_time)); $sheet->setCellValue('W11', $delivery->carrier_note); $sheet->setCellValue('L12', $order->customer_name); $sheet->setCellValue('C13', $delivery->contact_position); $sheet->setCellValue('L13', $delivery->contact_name); $sheet->setCellValue('Y13', $delivery->contact_phone); $sheet->setCellValue('L16', $order->execution_type === ProductionOrderExecutionType::Pickup ? 'Самовывоз' : 'Доставка'); $sheet->setCellValue('P16', $order->execution_type === ProductionOrderExecutionType::Pickup ? '' : 'Х'); $sheet->setCellValue('Q16', $order->execution_type === ProductionOrderExecutionType::Pickup ? 'Х' : ''); $sheet->setCellValue('L17', $order->object_address); $sheet->setCellValue('L18', $delivery->delivery_date->format('d.m.Y')); $sheet->setCellValue('L19', $this->shortTime($delivery->desired_time ?: $delivery->delivery_time)); $sheet->setCellValue('L20', $delivery->access_system); $sheet->setCellValue('L21', $delivery->unloading_place); $sheet->setCellValue('L22', $delivery->advance_notice); $sheet->setCellValue('L23', $delivery->document_signing_method); $sheet->setCellValue('L24', $delivery->has_passports_and_certificates ? 'Да' : 'Нет'); $sheet->setCellValue('G25', $this->equipmentSummary($order->items)); $sheet->setCellValue('G26', $delivery->note); $sheet->setCellValue('B27', $order->manager?->name); $sheet->setCellValue('S28', now()->format('d.m.Y')); $filename = $this->safeFilename("Заявка_на_доставку_{$order->order_number}_{$delivery->delivery_date->format('Y-m-d')}.xls"); $file = $this->writeSpreadsheet($spreadsheet, $filename, $userId, new Xls($spreadsheet)); $spreadsheet->disconnectWorksheets(); return $file; } private function installationPack(ProductionOrder $order, int $installationId, int $userId): File { $installation = $order->installations()->with('brigadier')->findOrFail($installationId); $template = base_path('templates/OrderForMount.xlsx'); if (! is_file($template)) { throw new RuntimeException('Шаблон заявки на монтаж не найден.'); } $temporaryDirectory = $this->temporaryDirectory(); Storage::disk('local')->makeDirectory($temporaryDirectory); $requestFilename = $this->safeFilename("Заявка_на_монтаж_{$order->order_number}.xlsx"); $requestPath = $temporaryDirectory.'/'.$requestFilename; $spreadsheet = IOFactory::load($template); $sheet = $spreadsheet->getActiveSheet(); $sheet->setCellValue('F8', $order->manager?->name); $sheet->setCellValue('X8', $order->manager?->phone); $sheet->setCellValue('C12', $order->customer_name); $sheet->setCellValue('L14', $order->object_address); $sheet->setCellValue('L15', $installation->installation_date->format('d.m.Y')); $sheet->setCellValue('G33', $this->equipmentSummary($order->items)); (new Xlsx($spreadsheet))->save(Storage::disk('local')->path($requestPath)); $spreadsheet->disconnectWorksheets(); $filename = $this->safeFilename("Монтажный_пакет_{$order->order_number}.zip"); try { return $this->zipFiles( $order, $order->items, $userId, $filename, [$requestPath => $requestFilename], includeOrderDocuments: true, ); } finally { Storage::disk('local')->deleteDirectory($temporaryDirectory); } } private function technicalDocuments(ProductionOrder $order, array $itemIds, int $userId): File { $ids = collect($itemIds)->map(static fn ($id): int => (int) $id)->filter()->unique(); $items = $order->items->whereIn('id', $ids); if ($items->isEmpty() || $items->count() !== $ids->count()) { throw new RuntimeException('Выбранные МАФ не найдены в заказе.'); } return $this->zipFiles( $order, $items, $userId, $this->safeFilename("Техдокументация_{$order->order_number}.zip"), ); } /** * @param Collection $items * @param array $localFiles */ private function zipFiles( ProductionOrder $order, Collection $items, int $userId, string $filename, array $localFiles = [], bool $includeOrderDocuments = false, ): File { $path = $this->generatedPath($userId, $filename); Storage::disk('local')->makeDirectory(dirname($path)); $zip = new ZipArchive; if ($zip->open(Storage::disk('local')->path($path), ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { throw new RuntimeException('Не удалось создать ZIP-архив.'); } $added = 0; foreach ($localFiles as $filePath => $archiveName) { if (Storage::disk('local')->exists($filePath)) { $zip->addFile(Storage::disk('local')->path($filePath), $archiveName); $added++; } } foreach ($items->pluck('catalogItem')->filter()->unique('id') as $catalogItem) { foreach ($catalogItem->documents as $document) { if (! $document->path || ! Storage::disk('public')->exists($document->path)) { continue; } $archiveName = 'Техдокументация/'.$this->safeFilename((string) $catalogItem->article).'/'.$this->safeFilename($document->original_name); $zip->addFile(Storage::disk('public')->path($document->path), $this->uniqueArchiveName($zip, $archiveName)); $added++; } } if ($includeOrderDocuments) { foreach ($order->documents as $document) { if (! $document->path || ! Storage::disk('public')->exists($document->path)) { continue; } $archiveName = 'Документы заказа/'.$this->safeFilename($document->original_name); $zip->addFile(Storage::disk('public')->path($document->path), $this->uniqueArchiveName($zip, $archiveName)); $added++; } } $zip->close(); if ($added === 0) { Storage::disk('local')->delete($path); throw new RuntimeException('Для выбранных МАФ нет технической документации.'); } return $this->registerFile($path, $filename, 'application/zip', $userId); } private function writeSpreadsheet(Spreadsheet $spreadsheet, string $filename, int $userId, object $writer): File { $path = $this->generatedPath($userId, $filename); Storage::disk('local')->makeDirectory(dirname($path)); $writer->save(Storage::disk('local')->path($path)); $mimeType = Str::endsWith($filename, '.xls') ? 'application/vnd.ms-excel' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; return $this->registerFile($path, $filename, $mimeType, $userId); } private function registerFile(string $path, string $filename, string $mimeType, int $userId): File { $file = File::query()->create([ 'link' => '', 'path' => $path, 'user_id' => $userId, 'original_name' => $filename, 'mime_type' => $mimeType, 'is_generated' => true, ]); $file->update(['link' => route('schedule.orders.files.download', $file)]); return $file; } private function equipmentSummary(Collection $items): string { return $items ->groupBy(static fn ($item): string => implode('|', [ $item->catalogItem?->article, $item->catalogItem?->calculator_name ?: $item->catalogItem?->print_name, ])) ->map(static function (Collection $group): string { $item = $group->first(); $name = $item->catalogItem?->calculator_name ?: $item->catalogItem?->print_name; return trim((string) $item->catalogItem?->article.' — '.$name).' — '.$group->count().' шт.'; }) ->implode("\n"); } private function shortTime(mixed $value): string { return $value ? substr((string) $value, 0, 5) : ''; } private function generatedPath(int $userId, string $filename): string { return "generated/production-orders/{$userId}/".Str::uuid()."/{$filename}"; } private function temporaryDirectory(): string { return 'generated/production-orders/tmp/'.Str::uuid(); } private function safeFilename(string $value): string { $value = preg_replace('/[^\pL\pN ._()-]+/u', '_', trim($value)) ?? ''; return mb_substr($value === '' ? 'файл' : $value, 0, 180); } private function uniqueArchiveName(ZipArchive $zip, string $name): string { if ($zip->locateName($name) === false) { return $name; } $extension = pathinfo($name, PATHINFO_EXTENSION); $base = $extension === '' ? $name : substr($name, 0, -strlen($extension) - 1); for ($index = 2; $index < 1000; $index++) { $candidate = $base." ({$index})".($extension === '' ? '' : ".{$extension}"); if ($zip->locateName($candidate) === false) { return $candidate; } } return $name; } }