ProductionOrderController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Http\Controllers;
  4. use App\Enums\ProductionOrderExecutionType;
  5. use App\Enums\ProductionOrderStatus;
  6. use App\Http\Requests\SaveProductionOrderRequest;
  7. use App\Models\CommonCatalogItem;
  8. use App\Models\ProductionOrder;
  9. use App\Models\Role;
  10. use App\Models\User;
  11. use App\Services\Access\FieldAccessService;
  12. use App\Services\ProductionOrderService;
  13. use Illuminate\Contracts\View\View;
  14. use Illuminate\Http\JsonResponse;
  15. use Illuminate\Http\RedirectResponse;
  16. use Illuminate\Http\Request;
  17. use Illuminate\Support\Arr;
  18. class ProductionOrderController extends Controller
  19. {
  20. private const FIELD_MAP = [
  21. 'status_name' => 'status',
  22. 'execution_type_name' => 'execution_type',
  23. 'items_count' => 'id',
  24. ];
  25. protected array $data = [
  26. 'active' => 'schedule_orders',
  27. 'title' => 'График заказов',
  28. 'id' => 'schedule_orders',
  29. 'header' => [
  30. 'id' => 'ID',
  31. 'order_number' => 'Номер заказа',
  32. 'customer_name' => 'Заказчик',
  33. 'object_address' => 'Адрес объекта',
  34. 'invoice_number' => '№ счёта',
  35. 'payment_date' => 'Дата оплаты',
  36. 'supply_working_days' => 'Срок поставки',
  37. 'contract_shipment_date' => 'Дата отгрузки по договору',
  38. 'application_shipment_date' => 'Дата отгрузки по заявке',
  39. 'status_name' => 'Статус',
  40. 'execution_type_name' => 'Тип',
  41. 'manager_id' => 'Менеджер',
  42. 'items_count' => 'МАФ',
  43. 'note' => 'Примечание',
  44. ],
  45. 'searchFields' => [
  46. 'order_number',
  47. 'customer_name',
  48. 'object_address',
  49. 'invoice_number',
  50. 'contract_number',
  51. 'note',
  52. ],
  53. ];
  54. public function index(Request $request): View
  55. {
  56. session(['gp_schedule_orders' => $request->query()]);
  57. $nav = $this->startNavigationContext($request);
  58. $model = new ProductionOrder;
  59. $this->data['header'] = array_filter(
  60. $this->data['header'],
  61. fn (string $field): bool => $request->user()->canViewField(
  62. 'schedule-orders',
  63. self::FIELD_MAP[$field] ?? $field,
  64. ),
  65. ARRAY_FILTER_USE_KEY,
  66. );
  67. $this->data['searchFields'] = array_values(array_filter(
  68. $this->data['searchFields'],
  69. fn (string $field): bool => $request->user()->canViewField('schedule-orders', $field),
  70. ));
  71. $this->createDateFilters(
  72. $model,
  73. ...array_values(array_intersect([
  74. 'payment_date',
  75. 'contract_shipment_date',
  76. 'application_shipment_date',
  77. ], array_keys($this->data['header']))),
  78. );
  79. if (isset($this->data['header']['supply_working_days'])) {
  80. $this->createRangeFilters($model, 'supply_working_days');
  81. }
  82. foreach (['status_name', 'execution_type_name'] as $field) {
  83. if (isset($this->data['header'][$field])) {
  84. $this->data['filters'][$field] = [
  85. 'title' => $this->data['header'][$field],
  86. 'values' => [],
  87. ];
  88. }
  89. }
  90. $query = ProductionOrder::query()->with('manager')->withCount('items');
  91. $this->acceptFilters($query, $request);
  92. $this->acceptSearch($query, $request);
  93. $requestedSort = $request->string('sortBy')->toString();
  94. $sortMap = [
  95. 'status_name' => 'status',
  96. 'execution_type_name' => 'execution_type',
  97. 'items_count' => 'items_count',
  98. ];
  99. if (isset($sortMap[$requestedSort])) {
  100. $request->merge(['sortBy' => $sortMap[$requestedSort]]);
  101. }
  102. $this->setSortAndOrderBy($model, $request);
  103. if ($requestedSort !== '' && isset($sortMap[$requestedSort])) {
  104. $this->data['sortBy'] = $requestedSort;
  105. $query->orderBy($sortMap[$requestedSort], $this->data['orderBy']);
  106. $query->orderBy('id', $this->data['orderBy']);
  107. } else {
  108. $this->applyStableSorting($query);
  109. }
  110. $this->data['orders'] = $query->paginate($this->data['per_page'])->withQueryString();
  111. $this->data['nav'] = $nav;
  112. return view('production_orders.index', $this->data);
  113. }
  114. public function create(Request $request, FieldAccessService $fieldAccess): View
  115. {
  116. return $this->orderView($request, $fieldAccess);
  117. }
  118. public function show(
  119. Request $request,
  120. ProductionOrder $productionOrder,
  121. FieldAccessService $fieldAccess,
  122. ): View {
  123. return $this->orderView(
  124. $request,
  125. $fieldAccess,
  126. $productionOrder->load([
  127. 'manager',
  128. 'items.catalogItem.imageFile',
  129. 'deliveries.driver',
  130. 'installations.brigadier',
  131. 'activities.user',
  132. ]),
  133. );
  134. }
  135. public function store(
  136. SaveProductionOrderRequest $request,
  137. FieldAccessService $fieldAccess,
  138. ProductionOrderService $service,
  139. ): RedirectResponse {
  140. $validated = $request->validated();
  141. $items = $validated['items'];
  142. $fields = array_keys(config('access.schedule-orders.fields', []));
  143. $payload = $fieldAccess->filterValidatedPayload(
  144. $request->user(),
  145. 'schedule-orders',
  146. Arr::only($validated, $fields),
  147. );
  148. $payload['items'] = $items;
  149. $order = $service->create($payload, $request->user());
  150. return redirect()->route('schedule.orders.show', $this->withNav(
  151. ['productionOrder' => $order],
  152. $this->resolveNavToken($request),
  153. ))->with('success', 'Заказ создан.');
  154. }
  155. public function update(
  156. SaveProductionOrderRequest $request,
  157. ProductionOrder $productionOrder,
  158. FieldAccessService $fieldAccess,
  159. ProductionOrderService $service,
  160. ): RedirectResponse {
  161. $validated = $request->validated();
  162. $fields = array_keys(config('access.schedule-orders.fields', []));
  163. $payload = $fieldAccess->filterValidatedPayload(
  164. $request->user(),
  165. 'schedule-orders',
  166. Arr::only($validated, $fields),
  167. );
  168. $canManageItems = $request->user()->hasPermission('schedule-orders.items.manage');
  169. $editableItemFields = array_values(array_filter(
  170. ['factory_number', 'manufacture_date'],
  171. fn (string $field): bool => $request->user()->canUpdateField('schedule-orders', $field),
  172. ));
  173. if (($canManageItems || $editableItemFields !== []) && isset($validated['items'])) {
  174. $payload['items'] = $validated['items'];
  175. }
  176. $service->update(
  177. $productionOrder,
  178. $payload,
  179. $request->user(),
  180. $canManageItems,
  181. $editableItemFields,
  182. );
  183. return redirect()->route('schedule.orders.show', $this->withNav(
  184. ['productionOrder' => $productionOrder],
  185. $this->resolveNavToken($request),
  186. ))->with('success', 'Заказ обновлён.');
  187. }
  188. public function destroy(Request $request, ProductionOrder $productionOrder): RedirectResponse
  189. {
  190. $productionOrder->delete();
  191. return redirect()
  192. ->route('schedule.orders', session('gp_schedule_orders'))
  193. ->with('success', 'Заказ удалён.');
  194. }
  195. public function searchCatalogItems(Request $request): JsonResponse
  196. {
  197. $validated = $request->validate([
  198. 'q' => ['nullable', 'string', 'max:200'],
  199. ]);
  200. $search = trim((string) ($validated['q'] ?? ''));
  201. if (mb_strlen($search) < 2) {
  202. return response()->json([]);
  203. }
  204. $items = CommonCatalogItem::query()
  205. ->where(function ($query) use ($search): void {
  206. $query
  207. ->where('article', 'like', "%{$search}%")
  208. ->orWhere('calculator_name', 'like', "%{$search}%")
  209. ->orWhere('print_name', 'like', "%{$search}%");
  210. })
  211. ->orderBy('article')
  212. ->limit(50)
  213. ->get(['id', 'article', 'calculator_name', 'print_name'])
  214. ->map(static fn (CommonCatalogItem $item): array => [
  215. 'id' => $item->id,
  216. 'label' => $item->article.' — '.($item->calculator_name ?: $item->print_name ?: 'Без наименования'),
  217. ])
  218. ->values();
  219. return response()->json($items);
  220. }
  221. private function orderView(
  222. Request $request,
  223. FieldAccessService $fieldAccess,
  224. ?ProductionOrder $order = null,
  225. ): View {
  226. $nav = $this->resolveNavToken($request);
  227. $this->rememberNavigation($request, $nav);
  228. $fields = array_keys(config('access.schedule-orders.fields', []));
  229. $catalogItemIds = collect($order?->items ?? [])
  230. ->pluck('common_catalog_item_id')
  231. ->merge(collect($request->old('items', []))->pluck('common_catalog_item_id'))
  232. ->filter()
  233. ->unique()
  234. ->values();
  235. return view('production_orders.edit', [
  236. 'active' => 'schedule_orders',
  237. 'title' => $order ? 'Заказ '.$order->order_number : 'Новый заказ',
  238. 'order' => $order,
  239. 'nav' => $nav,
  240. 'back_url' => $this->navigationBackUrl(
  241. $request,
  242. $nav,
  243. route('schedule.orders', session('gp_schedule_orders')),
  244. ),
  245. 'readableFields' => array_fill_keys(
  246. $fieldAccess->filterReadableFields($request->user(), 'schedule-orders', $fields),
  247. true,
  248. ),
  249. 'writableFields' => collect($fields)->mapWithKeys(fn (string $field): array => [
  250. $field => $request->user()->canUpdateField('schedule-orders', $field),
  251. ])->all(),
  252. 'canManageItems' => $request->user()->hasPermission('schedule-orders.items.manage'),
  253. 'canManageDeliveries' => $request->user()->hasPermission('schedule-orders.deliveries.manage'),
  254. 'canManageInstallations' => $request->user()->hasPermission('schedule-orders.installations.manage'),
  255. 'canUpdateFactoryNumber' => $request->user()->canUpdateField('schedule-orders', 'factory_number'),
  256. 'canUpdateManufactureDate' => $request->user()->canUpdateField('schedule-orders', 'manufacture_date'),
  257. 'managers' => User::query()
  258. ->where('role', Role::MANAGER)
  259. ->orderBy('name')
  260. ->pluck('name', 'id'),
  261. 'drivers' => User::query()
  262. ->whereIn('role', [Role::DRIVER, Role::BRIGADIER])
  263. ->orderBy('name')
  264. ->pluck('name', 'id'),
  265. 'brigadiers' => User::query()
  266. ->where('role', Role::BRIGADIER)
  267. ->orderBy('name')
  268. ->pluck('name', 'id'),
  269. 'catalogItems' => CommonCatalogItem::query()
  270. ->whereKey($catalogItemIds)
  271. ->get(['id', 'article', 'calculator_name', 'print_name'])
  272. ->keyBy('id'),
  273. 'statuses' => ProductionOrderStatus::options(),
  274. 'executionTypes' => ProductionOrderExecutionType::options(),
  275. ]);
  276. }
  277. }