ImportProductionOrdersServiceTest.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. declare(strict_types=1);
  3. namespace Tests\Unit\Services\Import;
  4. use App\Enums\ProductionOrderExecutionType;
  5. use App\Enums\ProductionOrderSource;
  6. use App\Enums\ProductionOrderStatus;
  7. use App\Models\CommonCatalogItem;
  8. use App\Models\Import;
  9. use App\Models\ProductionOrder;
  10. use App\Models\ProductionOrderItem;
  11. use App\Models\User;
  12. use App\Services\Export\ExportProductionOrdersService;
  13. use App\Services\Import\ImportProductionOrdersService;
  14. use Illuminate\Foundation\Testing\RefreshDatabase;
  15. use Illuminate\Support\Facades\Storage;
  16. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  17. use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  18. use RuntimeException;
  19. use Tests\TestCase;
  20. final class ImportProductionOrdersServiceTest extends TestCase
  21. {
  22. use RefreshDatabase;
  23. public function test_empty_order_id_creates_order_and_imports_items_from_separate_sheet(): void
  24. {
  25. Storage::fake('upload');
  26. $actor = User::factory()->admin()->create();
  27. $manager = User::factory()->manager()->create(['name' => 'Менеджер Импорта']);
  28. $catalogItem = CommonCatalogItem::factory()->create([
  29. 'article' => 'IMP-001',
  30. 'calculator_name' => 'Импортируемый комплекс',
  31. ]);
  32. $import = $this->createImport($actor, [
  33. $this->orderRow(null, 'НОВЫЙ-001', $manager->name),
  34. ], [[
  35. 2,
  36. null,
  37. $catalogItem->id,
  38. $catalogItem->article,
  39. $catalogItem->calculator_name,
  40. 'МАФ-НОВЫЙ-1',
  41. 'ЗН-001',
  42. '03.09.2026',
  43. ]]);
  44. $this->service($import, $actor)->handle();
  45. $order = ProductionOrder::query()->where('order_number', 'НОВЫЙ-001')->firstOrFail();
  46. $this->assertSame(ProductionOrderSource::Spreadsheet, $order->source);
  47. $this->assertSame('ООО Новый заказчик', $order->customer_name);
  48. $this->assertSame($manager->id, $order->manager_id);
  49. $this->assertSame('2026-09-09', $order->contract_shipment_date->toDateString());
  50. $this->assertDatabaseHas('production_order_items', [
  51. 'production_order_id' => $order->id,
  52. 'common_catalog_item_id' => $catalogItem->id,
  53. 'order_item_number' => 'МАФ-НОВЫЙ-1',
  54. 'factory_number' => 'ЗН-001',
  55. 'manufacture_date' => '2026-09-03',
  56. ]);
  57. $this->assertSame('DONE', $import->refresh()->status);
  58. }
  59. public function test_known_order_id_updates_order_and_synchronizes_items(): void
  60. {
  61. Storage::fake('upload');
  62. $actor = User::factory()->admin()->create();
  63. $manager = User::factory()->manager()->create(['name' => 'Менеджер Обновления']);
  64. $catalogItem = CommonCatalogItem::factory()->create([
  65. 'article' => 'IMP-UPDATE',
  66. 'calculator_name' => 'Обновляемый комплекс',
  67. ]);
  68. $order = ProductionOrder::factory()->create([
  69. 'order_number' => 'ОБНОВИТЬ-001',
  70. 'order_year' => 2026,
  71. 'customer_name' => 'Старый заказчик',
  72. 'manager_id' => $manager->id,
  73. 'source' => ProductionOrderSource::Manual,
  74. ]);
  75. $item = ProductionOrderItem::factory()->create([
  76. 'production_order_id' => $order->id,
  77. 'common_catalog_item_id' => $catalogItem->id,
  78. 'order_item_number' => 'МАФ-СТАРЫЙ',
  79. ]);
  80. $row = $this->orderRow($order->id, $order->order_number, $manager->name);
  81. $row[3] = 'ООО Обновлённый заказчик';
  82. $row[13] = ProductionOrderStatus::InStock->label();
  83. $import = $this->createImport($actor, [$row], [[
  84. 2,
  85. $item->id,
  86. $catalogItem->id,
  87. $catalogItem->article,
  88. $catalogItem->calculator_name,
  89. 'МАФ-ОБНОВЛЁННЫЙ',
  90. 'ЗН-UPDATED',
  91. '04.09.2026',
  92. ]]);
  93. $this->service($import, $actor)->handle();
  94. $order->refresh();
  95. $item->refresh();
  96. $this->assertSame('ООО Обновлённый заказчик', $order->customer_name);
  97. $this->assertSame(ProductionOrderStatus::InStock, $order->status);
  98. $this->assertSame(ProductionOrderSource::Manual, $order->source);
  99. $this->assertSame('МАФ-ОБНОВЛЁННЫЙ', $item->order_item_number);
  100. $this->assertSame('ЗН-UPDATED', $item->factory_number);
  101. $this->assertSame('2026-09-04', $item->manufacture_date?->toDateString());
  102. $this->assertSame('DONE', $import->refresh()->status);
  103. }
  104. public function test_unknown_order_id_marks_import_as_error_without_partial_changes(): void
  105. {
  106. Storage::fake('upload');
  107. $actor = User::factory()->admin()->create();
  108. $manager = User::factory()->manager()->create(['name' => 'Менеджер Ошибки']);
  109. $order = ProductionOrder::factory()->create([
  110. 'order_number' => 'БЕЗ-ИЗМЕНЕНИЙ',
  111. 'order_year' => 2026,
  112. 'customer_name' => 'Исходный заказчик',
  113. 'manager_id' => $manager->id,
  114. ]);
  115. $validRow = $this->orderRow($order->id, $order->order_number, $manager->name);
  116. $validRow[3] = 'Не должно сохраниться';
  117. $import = $this->createImport($actor, [
  118. $validRow,
  119. $this->orderRow(999999, 'НЕИЗВЕСТНЫЙ-ID', $manager->name),
  120. ], []);
  121. try {
  122. $this->service($import, $actor)->handle();
  123. $this->fail('Импорт с неизвестным ID должен завершиться ошибкой.');
  124. } catch (RuntimeException $exception) {
  125. $this->assertStringContainsString('заказ с ID 999999 не найден', $exception->getMessage());
  126. }
  127. $this->assertSame('Исходный заказчик', $order->refresh()->customer_name);
  128. $this->assertSame('ERROR', $import->refresh()->status);
  129. $this->assertStringContainsString('ID 999999', (string) $import->result);
  130. }
  131. /**
  132. * @param list<list<mixed>> $orderRows
  133. * @param list<list<mixed>> $itemRows
  134. */
  135. private function createImport(User $actor, array $orderRows, array $itemRows): Import
  136. {
  137. $path = 'production_orders/test-'.uniqid().'.xlsx';
  138. Storage::disk('upload')->makeDirectory(dirname($path));
  139. $spreadsheet = new Spreadsheet;
  140. $ordersSheet = $spreadsheet->getActiveSheet();
  141. $ordersSheet->setTitle(ExportProductionOrdersService::ORDERS_SHEET);
  142. $ordersSheet->fromArray(ExportProductionOrdersService::HEADERS, null, 'A1');
  143. if ($orderRows !== []) {
  144. $ordersSheet->fromArray($orderRows, null, 'A2');
  145. }
  146. $itemsSheet = $spreadsheet->createSheet();
  147. $itemsSheet->setTitle(ExportProductionOrdersService::ITEMS_SHEET);
  148. $itemsSheet->fromArray(ExportProductionOrdersService::ITEM_HEADERS, null, 'A1');
  149. if ($itemRows !== []) {
  150. $itemsSheet->fromArray($itemRows, null, 'A2');
  151. }
  152. (new Xlsx($spreadsheet))->save(Storage::disk('upload')->path($path));
  153. $spreadsheet->disconnectWorksheets();
  154. return Import::factory()->create([
  155. 'type' => 'production_orders',
  156. 'year' => null,
  157. 'filename' => $path,
  158. 'status' => 'new',
  159. 'user_id' => $actor->id,
  160. ]);
  161. }
  162. /** @return list<mixed> */
  163. private function orderRow(?int $id, string $number, string $managerName): array
  164. {
  165. return [
  166. $id,
  167. $number,
  168. 2026,
  169. 'ООО Новый заказчик',
  170. 'г. Москва, ул. Тестовая, 1',
  171. 'СЧ-001',
  172. '01.09.2026',
  173. 'Д-001',
  174. '31.08.2026',
  175. '02.09.2026',
  176. 5,
  177. null,
  178. '15.09.2026',
  179. ProductionOrderStatus::Placed->label(),
  180. ProductionOrderExecutionType::Delivery->label(),
  181. $managerName,
  182. 'Сводка оборудования не импортируется',
  183. 'Примечание импорта',
  184. ];
  185. }
  186. private function service(Import $import, User $actor): ImportProductionOrdersService
  187. {
  188. return app(ImportProductionOrdersService::class, [
  189. 'import' => $import,
  190. 'userId' => $actor->id,
  191. ]);
  192. }
  193. }