ContractorController.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Models\Contractor;
  4. use App\Models\Product;
  5. use App\Models\Scopes\YearScope;
  6. use App\Services\ContractorPriceService;
  7. use Illuminate\Http\RedirectResponse;
  8. use Illuminate\Http\Request;
  9. use Illuminate\View\View;
  10. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  11. class ContractorController extends Controller
  12. {
  13. protected string $id = 'contractors';
  14. protected array $header = [
  15. 'id' => 'ID',
  16. 'name' => 'Наименование',
  17. 'legal_name' => 'Юридическое имя',
  18. 'contract_number' => '№ договора',
  19. 'contract_date' => 'Дата договора',
  20. 'organization_form_name' => 'Форма',
  21. 'tax_rate_name' => 'Налог',
  22. 'hidden_txt' => 'Скрыт',
  23. ];
  24. protected array $searchFields = ['name', 'legal_name', 'contract_number', 'director_name'];
  25. protected array $priceHeader = [
  26. 'image' => 'Картинка МАФ',
  27. 'article' => 'Артикул МАФ',
  28. 'nomenclature_number' => 'Номер номенклатуры',
  29. 'name_in_spec' => 'Наименование по спецификации',
  30. 'installation_price_txt' => 'Цена монтажа',
  31. 'status_name' => 'Статус',
  32. 'actions' => '',
  33. ];
  34. protected array $priceSearchFields = ['article', 'nomenclature_number', 'name_in_spec'];
  35. public function index(Request $request): View
  36. {
  37. session(['gp_contractors' => $request->query()]);
  38. $nav = $this->startNavigationContext($request);
  39. $allowedSortFields = ['id', 'name', 'legal_name', 'contract_number', 'contract_date', 'organization_form', 'tax_rate', 'hidden'];
  40. $sortBy = in_array($request->get('sortBy'), $allowedSortFields, true) ? $request->get('sortBy') : 'name';
  41. $orderBy = $request->get('order') === 'desc' ? 'desc' : 'asc';
  42. $contractors = Contractor::query();
  43. if ($request->filled('s')) {
  44. $search = $request->get('s');
  45. $contractors->where(function ($query) use ($search) {
  46. foreach ($this->searchFields as $field) {
  47. $query->orWhere($field, 'like', '%' . $search . '%');
  48. }
  49. });
  50. }
  51. $contractors = $contractors->orderBy($sortBy, $orderBy)->get();
  52. return view('contractors.index', [
  53. 'active' => 'contractors',
  54. 'id' => $this->id,
  55. 'header' => $this->header,
  56. 'sortBy' => $sortBy,
  57. 'orderBy' => $orderBy,
  58. 'searchFields' => $this->searchFields,
  59. 'contractors' => $contractors,
  60. 'organizationForms' => Contractor::ORGANIZATION_FORMS,
  61. 'taxRates' => Contractor::TAX_RATES,
  62. 'nav' => $nav,
  63. ]);
  64. }
  65. public function create(Request $request, ContractorPriceService $priceService): View
  66. {
  67. return $this->showForm($request, null, $priceService);
  68. }
  69. public function show(Request $request, Contractor $contractor, ContractorPriceService $priceService): View
  70. {
  71. return $this->showForm($request, $contractor, $priceService);
  72. }
  73. public function store(Request $request): RedirectResponse
  74. {
  75. $validated = $this->validatedContractor($request);
  76. $contractor = Contractor::query()->create($validated);
  77. return redirect()->route('contractors.show', $contractor)->with('success', 'Подрядчик создан');
  78. }
  79. public function update(Request $request, Contractor $contractor): RedirectResponse
  80. {
  81. $contractor->update($this->validatedContractor($request));
  82. return redirect()->route('contractors.show', $contractor)->with('success', 'Подрядчик сохранён');
  83. }
  84. public function updatePrice(Request $request, Contractor $contractor, ContractorPriceService $priceService): RedirectResponse
  85. {
  86. $validated = $request->validate([
  87. 'product_id' => ['required', 'integer', 'exists:products,id'],
  88. 'name_in_spec' => ['nullable', 'string', 'max:255'],
  89. 'price' => ['nullable', 'numeric', 'min:0'],
  90. 'nav' => ['nullable', 'string'],
  91. 'filters' => ['nullable', 'array'],
  92. 's' => ['nullable', 'string'],
  93. 'sortBy' => ['nullable', 'string'],
  94. 'order' => ['nullable', 'string'],
  95. ]);
  96. $product = Product::withoutGlobalScope(YearScope::class)->withTrashed()->findOrFail($validated['product_id']);
  97. $priceService->updatePrice(
  98. $contractor,
  99. $product,
  100. year(),
  101. $validated['name_in_spec'] ?? null,
  102. (float) ($validated['price'] ?? 0),
  103. );
  104. return redirect()
  105. ->route('contractors.show', array_filter([
  106. 'contractor' => $contractor,
  107. 'nav' => $validated['nav'] ?? null,
  108. 's' => $validated['s'] ?? null,
  109. 'sortBy' => $validated['sortBy'] ?? null,
  110. 'order' => $validated['order'] ?? null,
  111. ]) + $request->only('filters'))
  112. ->with('success', 'Цена монтажа сохранена');
  113. }
  114. public function importPrices(Request $request, Contractor $contractor, ContractorPriceService $priceService): RedirectResponse
  115. {
  116. $validated = $request->validate([
  117. 'import_file' => ['required', 'file', 'mimes:xlsx,xls'],
  118. ]);
  119. $result = $priceService->import($contractor, $validated['import_file'], year());
  120. $message = "Импорт завершён. Обновлено: {$result['updated']}. Без изменений: {$result['unchanged']}. Ошибок: " . count($result['errors']) . '.';
  121. return redirect()
  122. ->route('contractors.show', $contractor)
  123. ->with('success', $message)
  124. ->with('contractor_import_errors', $result['errors']);
  125. }
  126. public function exportPrices(Request $request, Contractor $contractor, ContractorPriceService $priceService): BinaryFileResponse
  127. {
  128. $catalogYear = year();
  129. $path = $priceService->export($contractor, $catalogYear);
  130. return response()
  131. ->download($path, 'Цены монтажа ' . $contractor->name . ' ' . $catalogYear . '.xlsx')
  132. ->deleteFileAfterSend();
  133. }
  134. private function showForm(Request $request, ?Contractor $contractor, ContractorPriceService $priceService): View
  135. {
  136. $nav = $this->resolveNavToken($request);
  137. $this->rememberNavigation($request, $nav);
  138. $year = year();
  139. $filters = $request->input('filters', []);
  140. if (!is_array($filters)) {
  141. $filters = [];
  142. }
  143. $allowedSortFields = array_keys($this->priceHeader);
  144. $priceSortBy = in_array($request->get('sortBy'), $allowedSortFields, true) ? $request->get('sortBy') : 'article';
  145. $priceOrderBy = $request->get('order') === 'desc' ? 'desc' : 'asc';
  146. $allPriceRows = $contractor ? $priceService->rowsForContractor($contractor, $year) : collect();
  147. $priceRows = $contractor
  148. ? $priceService->rowsForContractor($contractor, $year, $filters, $request->string('s')->toString(), $priceSortBy, $priceOrderBy)
  149. : collect();
  150. return view('contractors.edit', [
  151. 'active' => 'contractors',
  152. 'contractor' => $contractor,
  153. 'organizationForms' => Contractor::ORGANIZATION_FORMS,
  154. 'taxRates' => Contractor::TAX_RATES,
  155. 'priceRows' => $priceRows,
  156. 'catalogYear' => $year,
  157. 'priceHeader' => $this->priceHeader,
  158. 'priceSearchFields' => $this->priceSearchFields,
  159. 'priceSortBy' => $priceSortBy,
  160. 'priceOrderBy' => $priceOrderBy,
  161. 'priceFilters' => $priceService->filterOptionsForRows($allPriceRows, $this->priceHeader),
  162. 'priceRanges' => [],
  163. 'nav' => $nav,
  164. 'back_url' => $this->navigationBackUrl($request, $nav, route('contractors.index', session('gp_contractors'))),
  165. ]);
  166. }
  167. private function validatedContractor(Request $request): array
  168. {
  169. return $request->validate([
  170. 'name' => ['required', 'string', 'max:255'],
  171. 'legal_name' => ['required', 'string', 'max:255'],
  172. 'contract_number' => ['required', 'string', 'max:255'],
  173. 'contract_date' => ['required', 'date'],
  174. 'director_name' => ['required', 'string', 'max:255'],
  175. 'organization_form' => ['required', 'string', 'in:' . implode(',', array_keys(Contractor::ORGANIZATION_FORMS))],
  176. 'tax_rate' => ['required', 'string', 'in:' . implode(',', array_keys(Contractor::TAX_RATES))],
  177. 'contract_header' => ['required', 'string'],
  178. 'hidden' => ['nullable', 'boolean'],
  179. ]) + ['hidden' => false];
  180. }
  181. }