edit.blade.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. @extends('layouts.app')
  2. @section('content')
  3. @php
  4. $canView = static fn (string $field): bool => (bool) ($readableFields[$field] ?? false);
  5. $canUpdate = static fn (string $field): bool => (bool) ($writableFields[$field] ?? false);
  6. $itemRows = old('items');
  7. if ($itemRows === null) {
  8. $itemRows = $order
  9. ? $order->items->map(static fn ($item): array => [
  10. 'id' => $item->id,
  11. 'common_catalog_item_id' => $item->common_catalog_item_id,
  12. 'order_item_number' => $item->order_item_number,
  13. 'factory_number' => $item->factory_number,
  14. 'manufacture_date' => $item->manufacture_date?->toDateString(),
  15. ])->values()->all()
  16. : [];
  17. }
  18. @endphp
  19. <div class="px-3">
  20. <div class="row mb-3">
  21. <div class="col-md-7 d-flex align-items-center">
  22. <h3 class="mb-0">{{ $order ? 'Заказ '.$order->order_number : 'Новый заказ' }}</h3>
  23. </div>
  24. @if($order && hasPermission('schedule-orders.delete'))
  25. <div class="col-md-5 text-md-end">
  26. <form action="{{ route('schedule.orders.destroy', $order) }}" method="POST"
  27. onsubmit="return confirm('Удалить заказ?')">
  28. @csrf
  29. @method('DELETE')
  30. <button type="submit" class="btn btn-sm btn-outline-danger">Удалить</button>
  31. </form>
  32. </div>
  33. @endif
  34. </div>
  35. <form action="{{ $order ? route('schedule.orders.update', $order) : route('schedule.orders.store') }}"
  36. method="POST">
  37. @csrf
  38. @if($order) @method('PUT') @endif
  39. <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
  40. <div class="card mb-3">
  41. <div class="card-header"><strong>Заказ</strong></div>
  42. <div class="card-body">
  43. <div class="row">
  44. <div class="col-xl-6">
  45. @if($canView('order_number'))
  46. @include('partials.input', ['name' => 'order_number', 'title' => 'Номер заказа', 'required' => true, 'value' => $order?->order_number, 'disabled' => !$canUpdate('order_number')])
  47. @endif
  48. @if($canView('order_year'))
  49. @include('partials.input', ['name' => 'order_year', 'title' => 'Год заказа', 'type' => 'number', 'min' => 2000, 'max' => 2100, 'required' => true, 'value' => $order?->order_year ?? now()->year, 'disabled' => !$canUpdate('order_year')])
  50. @endif
  51. @if($canView('customer_name'))
  52. @include('partials.input', ['name' => 'customer_name', 'title' => 'Заказчик', 'required' => true, 'value' => $order?->customer_name, 'disabled' => !$canUpdate('customer_name')])
  53. @endif
  54. @if($canView('object_address'))
  55. <div class="row mb-2">
  56. <label for="object_address" class="col-form-label small col-md-4 text-md-end">Адрес объекта <sup>*</sup></label>
  57. <div class="col-md-8">
  58. <textarea name="object_address" id="object_address" rows="3" required
  59. @disabled(!$canUpdate('object_address'))
  60. class="form-control form-control-sm @error('object_address') is-invalid @enderror">{{ old('object_address', $order?->object_address) }}</textarea>
  61. @error('object_address') <div class="invalid-feedback"><strong>{{ $message }}</strong></div> @enderror
  62. </div>
  63. </div>
  64. @endif
  65. @if($canView('manager_id'))
  66. @include('partials.select', ['name' => 'manager_id', 'title' => 'Менеджер', 'required' => true, 'first_empty' => true, 'options' => $managers, 'value' => old('manager_id', $order?->manager_id), 'disabled' => !$canUpdate('manager_id')])
  67. @endif
  68. @if($canView('status'))
  69. @include('partials.select', ['name' => 'status', 'title' => 'Статус', 'required' => true, 'options' => $statuses, 'value' => old('status', $order?->status?->value ?? \App\Enums\ProductionOrderStatus::Placed->value), 'disabled' => !$canUpdate('status')])
  70. @endif
  71. @if($canView('execution_type'))
  72. @include('partials.select', ['name' => 'execution_type', 'title' => 'Тип исполнения', 'required' => true, 'first_empty' => true, 'options' => $executionTypes, 'value' => old('execution_type', $order?->execution_type?->value), 'disabled' => !$canUpdate('execution_type')])
  73. @endif
  74. </div>
  75. <div class="col-xl-6">
  76. @if($canView('invoice_number'))
  77. @include('partials.input', ['name' => 'invoice_number', 'title' => '№ счёта', 'required' => true, 'value' => $order?->invoice_number, 'disabled' => !$canUpdate('invoice_number')])
  78. @endif
  79. @if($canView('invoice_date'))
  80. @include('partials.input', ['name' => 'invoice_date', 'title' => 'Дата счёта', 'type' => 'date', 'value' => $order?->invoice_date?->toDateString(), 'disabled' => !$canUpdate('invoice_date')])
  81. @endif
  82. @if($canView('contract_number'))
  83. @include('partials.input', ['name' => 'contract_number', 'title' => '№ договора', 'value' => $order?->contract_number, 'disabled' => !$canUpdate('contract_number')])
  84. @endif
  85. @if($canView('contract_date'))
  86. @include('partials.input', ['name' => 'contract_date', 'title' => 'Дата договора', 'type' => 'date', 'value' => $order?->contract_date?->toDateString(), 'disabled' => !$canUpdate('contract_date')])
  87. @endif
  88. @if($canView('payment_date'))
  89. @include('partials.input', ['name' => 'payment_date', 'title' => 'Дата оплаты', 'type' => 'date', 'required' => true, 'value' => $order?->payment_date?->toDateString(), 'disabled' => !$canUpdate('payment_date')])
  90. @endif
  91. @if($canView('supply_working_days'))
  92. @include('partials.input', ['name' => 'supply_working_days', 'title' => 'Срок поставки, раб. дней', 'type' => 'number', 'min' => 0, 'max' => 2000, 'required' => true, 'value' => $order?->supply_working_days, 'disabled' => !$canUpdate('supply_working_days')])
  93. @endif
  94. @if($order && $canView('contract_shipment_date'))
  95. @include('partials.input', ['name' => 'contract_shipment_date_display', 'title' => 'Дата отгрузки по договору', 'type' => 'date', 'value' => $order->contract_shipment_date?->toDateString(), 'disabled' => true])
  96. @endif
  97. @if($canView('application_shipment_date'))
  98. @include('partials.input', ['name' => 'application_shipment_date', 'title' => 'Дата отгрузки по заявке', 'type' => 'date', 'value' => $order?->application_shipment_date?->toDateString(), 'disabled' => !$canUpdate('application_shipment_date')])
  99. @endif
  100. @if($canView('note'))
  101. <div class="row mb-2">
  102. <label for="note" class="col-form-label small col-md-4 text-md-end">Примечание</label>
  103. <div class="col-md-8">
  104. <textarea name="note" id="note" rows="3" @disabled(!$canUpdate('note'))
  105. class="form-control form-control-sm @error('note') is-invalid @enderror">{{ old('note', $order?->note) }}</textarea>
  106. @error('note') <div class="invalid-feedback"><strong>{{ $message }}</strong></div> @enderror
  107. </div>
  108. </div>
  109. @endif
  110. </div>
  111. </div>
  112. </div>
  113. </div>
  114. <div class="card mb-3">
  115. <div class="card-header d-flex justify-content-between align-items-center">
  116. <strong>Оборудование</strong>
  117. </div>
  118. <div class="card-body {{ $canManageItems ? 'pb-2' : 'p-0' }}">
  119. @if($canManageItems)
  120. <div class="mb-3">
  121. <label for="production-order-catalog-search" class="form-label small mb-1">
  122. Добавить МАФ из общего каталога
  123. </label>
  124. <div class="row g-2 mb-2">
  125. <div class="col-md-9">
  126. <input type="search" id="production-order-catalog-search"
  127. class="form-control form-control-sm"
  128. placeholder="Введите артикул или наименование">
  129. </div>
  130. <div class="col-md-3">
  131. <div class="input-group input-group-sm">
  132. <span class="input-group-text">Количество</span>
  133. <input type="number" id="production-order-catalog-quantity"
  134. class="form-control" min="1" max="100" value="1">
  135. </div>
  136. </div>
  137. </div>
  138. <select id="production-order-catalog-results" class="form-select form-select-sm" size="6"
  139. aria-label="Результаты поиска МАФ"></select>
  140. <div class="form-text">
  141. Введите не менее двух символов и выберите позицию. Для каждой единицы будет создана отдельная строка.
  142. </div>
  143. </div>
  144. @endif
  145. <div class="table-responsive">
  146. <table class="table table-sm align-middle mb-0">
  147. <thead>
  148. <tr>
  149. <th>Позиция общего каталога</th>
  150. <th>Номер заказа МАФ</th>
  151. <th>Заводской номер</th>
  152. <th>Дата производства</th>
  153. @if($canManageItems)<th></th>@endif
  154. </tr>
  155. </thead>
  156. <tbody id="production-order-items">
  157. @foreach($itemRows as $index => $itemRow)
  158. @include('production_orders.partials.item-row', ['index' => $index, 'itemRow' => $itemRow])
  159. @endforeach
  160. </tbody>
  161. </table>
  162. </div>
  163. @error('items') <div class="text-danger small p-2"><strong>{{ $message }}</strong></div> @enderror
  164. </div>
  165. </div>
  166. <div class="d-flex flex-wrap gap-2">
  167. @if(($order && hasPermission('schedule-orders.update')) || (!$order && hasPermission('schedule-orders.create')))
  168. <button type="submit" class="btn btn-sm btn-primary">Сохранить</button>
  169. @endif
  170. <a href="{{ $back_url }}" class="btn btn-sm btn-outline-secondary">Назад</a>
  171. </div>
  172. </form>
  173. @if($canManageItems)
  174. <template id="production-order-item-template">
  175. @include('production_orders.partials.item-row', [
  176. 'index' => '__INDEX__',
  177. 'itemRow' => ['common_catalog_item_id' => '', 'order_item_number' => '', 'factory_number' => '', 'manufacture_date' => ''],
  178. ])
  179. </template>
  180. @endif
  181. </div>
  182. @if($canManageItems)
  183. @push('scripts')
  184. <script>
  185. document.addEventListener('DOMContentLoaded', () => {
  186. const rows = document.getElementById('production-order-items');
  187. const template = document.getElementById('production-order-item-template');
  188. const search = document.getElementById('production-order-catalog-search');
  189. const results = document.getElementById('production-order-catalog-results');
  190. const quantity = document.getElementById('production-order-catalog-quantity');
  191. let nextIndex = rows.querySelectorAll('tr').length;
  192. let searchTimer;
  193. let requestNumber = 0;
  194. search.addEventListener('input', () => {
  195. clearTimeout(searchTimer);
  196. const query = search.value.trim();
  197. results.replaceChildren();
  198. const currentRequest = ++requestNumber;
  199. if (query.length < 2) return;
  200. searchTimer = setTimeout(async () => {
  201. try {
  202. const url = new URL(@json(route('schedule.orders.catalog-search')), window.location.origin);
  203. url.searchParams.set('q', query);
  204. const response = await fetch(url, {headers: {'Accept': 'application/json'}});
  205. if (!response.ok || currentRequest !== requestNumber) return;
  206. const items = await response.json();
  207. results.replaceChildren(...items.map((item) => {
  208. const option = document.createElement('option');
  209. option.value = item.id;
  210. option.textContent = item.label;
  211. return option;
  212. }));
  213. } catch (error) {
  214. if (currentRequest === requestNumber) results.replaceChildren();
  215. }
  216. }, 250);
  217. });
  218. results.addEventListener('change', () => {
  219. const option = results.selectedOptions[0];
  220. if (!option) return;
  221. const count = Math.min(100, Math.max(1, Number.parseInt(quantity.value, 10) || 1));
  222. for (let i = 0; i < count; i++) {
  223. const fragment = template.content.cloneNode(true);
  224. const row = fragment.querySelector('tr');
  225. row.innerHTML = row.innerHTML.replaceAll('__INDEX__', nextIndex++);
  226. row.querySelector('.production-order-item-catalog-id').value = option.value;
  227. row.querySelector('.production-order-item-catalog-label').textContent = option.textContent;
  228. rows.appendChild(fragment);
  229. }
  230. search.value = '';
  231. quantity.value = '1';
  232. results.replaceChildren();
  233. });
  234. rows.addEventListener('click', (event) => {
  235. const button = event.target.closest('.remove-production-order-item');
  236. if (!button) return;
  237. button.closest('tr').remove();
  238. });
  239. });
  240. </script>
  241. @endpush
  242. @endif
  243. @endsection