index.blade.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. @extends('layouts.app')
  2. @section('content')
  3. <div class="container-fluid">
  4. <div class="row">
  5. <div class="col-12">
  6. <h3>{{ $title }}</h3>
  7. {{-- Вкладки --}}
  8. <ul class="nav nav-tabs mb-3">
  9. <li class="nav-item">
  10. <a class="nav-link {{ ($tab ?? 'catalog') === 'catalog' ? 'active' : '' }}"
  11. href="{{ route('spare_parts.index', session('gp_spare_parts', [])) }}">
  12. Каталог
  13. </a>
  14. </li>
  15. @if(hasPermission('spare_part_orders.view'))
  16. <li class="nav-item">
  17. <a class="nav-link {{ ($tab ?? '') === 'orders' ? 'active' : '' }}"
  18. href="{{ route('spare_part_orders.index', session('gp_spare_part_orders', [])) }}">
  19. Заказы запчастей
  20. </a>
  21. </li>
  22. @endif
  23. @if(hasPermission('spare_part_inventory.view'))
  24. <li class="nav-item">
  25. <a class="nav-link {{ ($tab ?? '') === 'inventory' ? 'active' : '' }}"
  26. href="{{ route('spare_part_inventory.index') }}">
  27. Контроль наличия
  28. </a>
  29. </li>
  30. @endif
  31. <li class="nav-item">
  32. <a class="nav-link" href="{{ route('pricing_codes.index', session('gp_pricing_codes', [])) }}">
  33. Справочник расшифровок
  34. </a>
  35. </li>
  36. <li class="nav-item">
  37. <a class="nav-link {{ ($tab ?? '') === 'help' ? 'active' : '' }}"
  38. href="{{ route('spare_parts.help', session('gp_spare_parts', [])) }}">
  39. <i class="bi bi-question-circle"></i> Справка
  40. </a>
  41. </li>
  42. </ul>
  43. @if(($tab ?? 'catalog') === 'catalog')
  44. {{-- Кнопки управления --}}
  45. <div class="mb-3">
  46. @if(hasAnyPermission(['spare_parts.update', 'spare_part_orders.create', 'spare_part_orders.ship']))
  47. <a href="{{ route('spare_parts.create') }}" class="btn btn-sm btn-primary">Добавить запчасть</a>
  48. @endif
  49. @if(hasAnyPermission(['spare_parts.create', 'spare_parts.import', 'spare_parts.export', 'spare_parts.delete']))
  50. <form action="{{ route('spare_parts.export') }}" method="POST" class="d-inline">
  51. @csrf
  52. <button type="submit" class="btn btn-sm btn-success">Экспорт</button>
  53. </form>
  54. <button type="button" class="btn btn-sm btn-info" data-bs-toggle="modal" data-bs-target="#importModal">
  55. Импорт
  56. </button>
  57. @endif
  58. </div>
  59. {{-- Таблица каталога --}}
  60. @if(isset($spare_parts) && isset($strings))
  61. @include('partials.table', [
  62. 'id' => $id,
  63. 'header' => $header,
  64. 'strings' => $strings,
  65. 'filters' => $filters ?? [],
  66. 'ranges' => $ranges ?? [],
  67. 'dates' => $dates ?? [],
  68. 'searchFields' => $searchFields ?? [],
  69. 'sortBy' => $sortBy ?? 'article',
  70. 'orderBy' => $orderBy ?? 'asc',
  71. 'routeName' => $routeName ?? null,
  72. ])
  73. @include('partials.pagination', ['items' => $spare_parts])
  74. @endif
  75. @elseif($tab === 'orders')
  76. {{-- Таблица заказов --}}
  77. @if(isset($spare_part_orders))
  78. <div class="mb-3">
  79. @if(hasAnyPermission(['spare_parts.update', 'spare_part_orders.create', 'spare_part_orders.ship']))
  80. <a href="{{ route('spare_part_orders.create') }}" class="btn btn-sm btn-primary">Создать заказ</a>
  81. <button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#setOrderInStockModal">
  82. Отгрузить весь заказ
  83. </button>
  84. @endif
  85. </div>
  86. @include('partials.table', [
  87. 'id' => $id,
  88. 'header' => $header,
  89. 'strings' => $strings,
  90. 'filters' => $filters ?? [],
  91. 'ranges' => $ranges ?? [],
  92. 'dates' => $dates ?? [],
  93. 'searchFields' => $searchFields ?? [],
  94. 'sortBy' => $sortBy ?? 'id',
  95. 'orderBy' => $orderBy ?? 'desc',
  96. 'routeName' => $routeName ?? null,
  97. ])
  98. @include('partials.pagination', ['items' => $spare_part_orders])
  99. @endif
  100. @elseif($tab === 'inventory')
  101. {{-- Контроль наличия --}}
  102. {{-- Открытые дефициты --}}
  103. <h4 class="text-danger"><i class="bi bi-exclamation-triangle-fill"></i> Открытые дефициты
  104. @if(isset($open_shortages) && $open_shortages->count() > 0)
  105. <span class="badge bg-danger">{{ $open_shortages->count() }}</span>
  106. @endif
  107. </h4>
  108. @if(isset($open_shortages) && $open_shortages->count() > 0)
  109. <div class="table-responsive js-subtable-scroll">
  110. <table class="table table-danger table-striped">
  111. <thead>
  112. <tr>
  113. <th>Картинка</th>
  114. <th>Артикул</th>
  115. <th>Рекламация</th>
  116. <th class="text-center">Требуется</th>
  117. <th class="text-center">Зарезервировано</th>
  118. <th class="text-center">Не хватает</th>
  119. <th class="text-center">С док.</th>
  120. <th>Дата создания</th>
  121. </tr>
  122. </thead>
  123. <tbody>
  124. @foreach($open_shortages as $shortage)
  125. <tr>
  126. <td>
  127. @if($shortage->sparePart && $shortage->sparePart->image)
  128. <img src="{{ $shortage->sparePart->image }}" alt="{{ $shortage->sparePart->article }}" class="img-max-50">
  129. @endif
  130. </td>
  131. <td>
  132. @if($shortage->sparePart)
  133. <a href="{{ route('spare_parts.show', $shortage->sparePart->id) }}">{{ $shortage->sparePart->article }}</a>
  134. @if($shortage->sparePart->used_in_maf)
  135. <br><small class="text-muted">{{ $shortage->sparePart->used_in_maf }}</small>
  136. @endif
  137. @else
  138. -
  139. @endif
  140. </td>
  141. <td>
  142. @if($shortage->reclamation)
  143. <a href="{{ route('reclamations.show', $shortage->reclamation->id) }}">#{{ $shortage->reclamation->id }}</a>
  144. @else
  145. -
  146. @endif
  147. </td>
  148. <td class="text-center">{{ $shortage->required_qty }}</td>
  149. <td class="text-center">{{ $shortage->reserved_qty }}</td>
  150. <td class="text-center fw-bold">{{ $shortage->missing_qty }}</td>
  151. <td class="text-center">
  152. @if($shortage->with_documents)
  153. <i class="bi bi-check-circle text-success"></i> Да
  154. @else
  155. <i class="bi bi-x-circle text-muted"></i> Нет
  156. @endif
  157. </td>
  158. <td>{{ $shortage->created_at->format('d.m.Y H:i') }}</td>
  159. </tr>
  160. @endforeach
  161. </tbody>
  162. </table>
  163. </div>
  164. @else
  165. <p class="text-muted">Нет открытых дефицитов</p>
  166. @endif
  167. {{-- Запчасти с критическим недостатком --}}
  168. <h4 class="text-danger mt-4">Запчасти с дефицитами</h4>
  169. @if(isset($critical_shortages) && $critical_shortages->count() > 0)
  170. <div class="table-responsive js-subtable-scroll">
  171. <table class="table table-danger table-striped">
  172. <thead>
  173. <tr>
  174. <th>Картинка</th>
  175. <th>Артикул</th>
  176. <th class="text-center">Свободно без док</th>
  177. <th class="text-center">Свободно с док</th>
  178. <th>Дефициты</th>
  179. </tr>
  180. </thead>
  181. <tbody>
  182. @foreach($critical_shortages as $sp)
  183. <tr>
  184. <td>
  185. @if($sp->image)
  186. <img src="{{ $sp->image }}" alt="{{ $sp->article }}" class="img-max-50">
  187. @endif
  188. </td>
  189. <td>
  190. <a href="{{ route('spare_parts.show', $sp->id) }}">{{ $sp->article }}</a>
  191. @if($sp->used_in_maf)
  192. <br><small class="text-muted">{{ $sp->used_in_maf }}</small>
  193. @endif
  194. </td>
  195. <td class="text-center">{{ $sp->quantity_without_docs }}</td>
  196. <td class="text-center">{{ $sp->quantity_with_docs }}</td>
  197. <td>
  198. @if(isset($sp->shortage_details))
  199. @foreach($sp->shortage_details as $detail)
  200. <div class="small">
  201. <a href="{{ route('reclamations.show', $detail['reclamation_id']) }}">Рекл. #{{ $detail['reclamation_id'] }}</a>:
  202. не хватает <strong>{{ $detail['missing_qty'] }}</strong> шт.
  203. @if($detail['with_documents'])
  204. (с док.)
  205. @else
  206. (без док.)
  207. @endif
  208. </div>
  209. @endforeach
  210. @endif
  211. </td>
  212. </tr>
  213. @endforeach
  214. </tbody>
  215. </table>
  216. </div>
  217. @else
  218. <p class="text-muted">Нет запчастей с дефицитами</p>
  219. @endif
  220. <h4 class="text-warning mt-4"><i class="bi bi-exclamation-circle-fill"></i> Ниже минимального остатка</h4>
  221. @if(isset($below_min_stock) && $below_min_stock->count() > 0)
  222. <div class="table-responsive js-subtable-scroll">
  223. <table class="table table-warning table-striped">
  224. <thead>
  225. <tr>
  226. <th>Картинка</th>
  227. <th>Артикул</th>
  228. <th class="text-center">Текущий остаток</th>
  229. <th class="text-center">Минимальный остаток</th>
  230. <th class="text-center">Нужно заказать</th>
  231. </tr>
  232. </thead>
  233. <tbody>
  234. @foreach($below_min_stock as $sp)
  235. <tr>
  236. <td>
  237. @if($sp->image)
  238. <img src="{{ $sp->image }}" alt="{{ $sp->article }}" class="img-max-50">
  239. @endif
  240. </td>
  241. <td>
  242. <a href="{{ route('spare_parts.show', $sp->id) }}">{{ $sp->article }}</a>
  243. @if($sp->used_in_maf)
  244. <br><small class="text-muted">{{ $sp->used_in_maf }}</small>
  245. @endif
  246. </td>
  247. <td class="text-center">{{ $sp->total_quantity }}</td>
  248. <td class="text-center">{{ $sp->min_stock }}</td>
  249. <td class="text-center fw-bold">{{ max(0, $sp->min_stock - $sp->total_quantity) }}</td>
  250. </tr>
  251. @endforeach
  252. </tbody>
  253. </table>
  254. </div>
  255. @else
  256. <p class="text-muted">Нет запчастей ниже минимального остатка</p>
  257. @endif
  258. @elseif($tab === 'help')
  259. {{-- Справка --}}
  260. <div class="card">
  261. <div class="card-body markdown-content">
  262. {!! $helpContent !!}
  263. </div>
  264. </div>
  265. @endif
  266. </div>
  267. </div>
  268. </div>
  269. {{-- Модальное окно импорта --}}
  270. @if(hasAnyPermission(['spare_parts.create', 'spare_parts.import', 'spare_parts.export', 'spare_parts.delete']))
  271. <div class="modal fade" id="importModal" tabindex="-1" aria-labelledby="importSparePartsModalLabel" aria-hidden="true">
  272. <div class="modal-dialog">
  273. <div class="modal-content">
  274. <div class="modal-header">
  275. <h1 class="modal-title fs-5" id="importSparePartsModalLabel">Импорт каталога запчастей и справочника расшифровок</h1>
  276. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  277. </div>
  278. <form action="{{ route('spare_parts.import') }}" method="POST" enctype="multipart/form-data">
  279. @csrf
  280. <div class="modal-body">
  281. <div class="mb-3">
  282. <label for="import_file" class="form-label">Выберите файл Excel</label>
  283. <input type="file" class="form-control" id="import_file" name="file" accept=".xlsx,.xls" required>
  284. <div class="form-text">
  285. Файл должен содержать две вкладки:<br>
  286. 1. Каталог запчастей (колонки: ID, Артикул, Где используется, Кол-во без док, Кол-во с док, Кол-во общее, Примечание, Цена закупки, Цена для заказчика, Цена экспертизы, № по ТСН, Шифр расценки, Минимальный остаток)<br>
  287. 2. Справочник расшифровок (колонки: ID, Тип, Код, Расшифровка)
  288. </div>
  289. </div>
  290. </div>
  291. <div class="modal-footer">
  292. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
  293. <button type="submit" class="btn btn-primary">Импортировать</button>
  294. </div>
  295. </form>
  296. </div>
  297. </div>
  298. </div>
  299. @endif
  300. @if(hasAnyPermission(['spare_parts.update', 'spare_part_orders.create', 'spare_part_orders.ship']) && ($tab ?? '') === 'orders')
  301. <div class="modal fade" id="setOrderInStockModal" tabindex="-1" aria-labelledby="setOrderInStockModalLabel" aria-hidden="true">
  302. <div class="modal-dialog">
  303. <div class="modal-content">
  304. <form action="{{ route('spare_part_orders.set_order_in_stock') }}" method="POST">
  305. @csrf
  306. <div class="modal-header">
  307. <h1 class="modal-title fs-5" id="setOrderInStockModalLabel">Отгрузить весь заказ</h1>
  308. <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
  309. </div>
  310. <div class="modal-body">
  311. <label for="bulk_order_number" class="form-label">Номер заказа</label>
  312. <select class="form-select @error('bulk_order_number') is-invalid @enderror" id="bulk_order_number" name="bulk_order_number" required>
  313. <option value="">Выберите номер заказа</option>
  314. @foreach($order_numbers ?? [] as $order_number)
  315. <option value="{{ $order_number }}" @selected(old('bulk_order_number') === $order_number)>{{ $order_number }}</option>
  316. @endforeach
  317. </select>
  318. @error('bulk_order_number')
  319. <div class="invalid-feedback d-block">{{ $message }}</div>
  320. @enderror
  321. </div>
  322. <div class="modal-footer">
  323. <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Отмена</button>
  324. <button type="submit" class="btn btn-primary btn-sm">Отгрузить заказ</button>
  325. </div>
  326. </form>
  327. </div>
  328. </div>
  329. </div>
  330. @endif
  331. @push('scripts')
  332. <script type="module">
  333. function waitForJQuery(callback) {
  334. if (typeof window.$ !== 'undefined') {
  335. callback();
  336. } else {
  337. setTimeout(() => waitForJQuery(callback), 50);
  338. }
  339. }
  340. waitForJQuery(function() {
  341. $(document).on('click', '.clickable-quantity', function(e) {
  342. e.preventDefault();
  343. const sparePartId = $(this).data('spare-part-id');
  344. const withDocs = $(this).data('with-docs');
  345. let url = "{{ route('spare_part_orders.index') }}" +
  346. "?spare_part_id=" + sparePartId +
  347. "&status=in_stock&available_qty_min=1";
  348. if (withDocs !== 'all') {
  349. url += "&with_documents=" + withDocs;
  350. }
  351. window.location.href = url;
  352. });
  353. @if($errors->has('bulk_order_number') && hasAnyPermission(['spare_parts.update', 'spare_part_orders.create', 'spare_part_orders.ship']) && ($tab ?? '') === 'orders')
  354. const setOrderInStockModalElement = document.getElementById('setOrderInStockModal');
  355. if (setOrderInStockModalElement) {
  356. const setOrderInStockModal = new bootstrap.Modal(setOrderInStockModalElement);
  357. setOrderInStockModal.show();
  358. }
  359. @endif
  360. });
  361. </script>
  362. @endpush
  363. @endsection