index.blade.php 21 KB

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