Browse Source

fix maf export

Alexander Musikhin 2 tuần trước cách đây
mục cha
commit
57aeb54b24

+ 1 - 1
app/Http/Controllers/ImportController.php

@@ -66,7 +66,7 @@ class ImportController extends Controller
 
         $import = Import::query()->create([
             'type' => $request->type,
-            'year' => in_array($request->type, ['catalog', 'maf_orders']) ? year() : null,
+            'year' => in_array($request->type, ['catalog', 'maf_orders', 'mafs']) ? year() : null,
             'status' => 'new',
             'filename' => $path,
         ]);

+ 1 - 1
app/Jobs/ExportMafJob.php

@@ -29,7 +29,7 @@ class ExportMafJob implements ShouldQueue
     public function handle(): void
     {
         try {
-            $file = (new ExportMafService())->handle($this->userId);
+            $file = (new ExportMafService())->handle($this->userId, $this->filters['year'] ?? null);
             Log::info('ExportMaf job done!');
             Log::info($file);
             event(new SendWebSocketMessageEvent('Экспорт завершён!', $this->userId, ['download' => $file]));

+ 1 - 1
app/Jobs/Import/ImportJob.php

@@ -40,7 +40,7 @@ class ImportJob implements ShouldQueue
                     (new ImportOrdersService($this->import))->handle();
                     break;
                 case 'mafs':
-                    (new ImportMafsService($this->import))->handle();
+                    (new ImportMafsService($this->import, $this->import->year))->handle();
                     break;
                 case 'reclamations':
                     (new ImportReclamationsService($this->import))->handle();

+ 7 - 3
app/Services/ExportMafService.php

@@ -15,7 +15,7 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 
 class ExportMafService
 {
-    public function handle(int $userId): string
+    public function handle(int $userId, ?int $year = null): string
     {
 
         $inputFileType = 'Xlsx'; // Xlsx - Xml - Ods - Slk - Gnumeric - Csv
@@ -27,7 +27,11 @@ class ExportMafService
         $sheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
 
 
-        $mafs = MafView::query()->get();
+        $targetYear = $year ?? year();
+        $mafs = MafView::query()
+            ->withoutGlobalScopes()
+            ->where('year', $targetYear)
+            ->get();
         $i = 2;
         foreach ($mafs as $maf) {
             $sheet->setCellValue('A' . $i, $maf->id);
@@ -82,4 +86,4 @@ class ExportMafService
         return $fileName;
 
     }
-}
+}

+ 6 - 2
app/Services/ImportMafsService.php

@@ -12,6 +12,8 @@ use Exception;
 
 class ImportMafsService extends ImportBaseService
 {
+    private ?int $year;
+
     const HEADERS = [
         'ID'                            => 'products_sku.id',
         'Год'                           => 'products_sku.year',
@@ -38,9 +40,10 @@ class ImportMafsService extends ImportBaseService
     ];
 
 
-    public function __construct(Import $import)
+    public function __construct(Import $import, ?int $year = null)
     {
         parent::__construct($import);
+        $this->year = $year;
         $this->headers = self::HEADERS;
     }
 
@@ -79,6 +82,7 @@ class ImportMafsService extends ImportBaseService
                 $productSKU = ProductSKU::query()
                     ->withoutGlobalScopes()
                     ->where('id', $id)
+                    ->when($this->year, fn ($q) => $q->where('year', $this->year))
                     ->first();
                 if ($productSKU) {
                     $logMessage .= 'Found and updated product with id: ' . $productSKU->id;
@@ -100,4 +104,4 @@ class ImportMafsService extends ImportBaseService
 
         return true;
     }
-}
+}

+ 167 - 0
docs/dkr-zip.md

@@ -0,0 +1,167 @@
+Цель: Создать веб-модуль для управления каталогом запчастей, учёта их наличия на складе, обработки заказов на пополнение и автоматического списания при использовании в рекламациях.
+
+1. Общее описание системы
+
+В системе три роли пользователей:
+
+Администратор: Полный доступ ко всем функциям (добавление, редактирование, удаление, импорт/экспорт).
+
+Менеджер: Только просмотр каталога и списка заказов. Не видит цену закупки.
+
+Бригадир: Полностью не имеет доступа к данному модулю (не видит ни каталог, ни заказы).
+
+Основные сущности: Каталог запчастей, Заказы деталей, Контроль наличия.
+
+Данные тесно связаны: остатки в каталоге автоматически считаются из заказов, а списание происходит при добавлении детали в рекламации.
+
+Важное правило: Экспорт и импорт данных (каталога, заказов) должны сохранять и восстанавливать все связанные данные, включая связи (например, между артикулом и его картинкой, кодами и их расшифровками), чтобы обеспечить целостность системы после переноса данных.
+
+2. Модуль 1: Каталог запчастей
+
+Назначение: Единый справочник всех запчастей.
+
+Структура записи (столбцы таблицы):
+
+Картинка: Автоподгрузка с сервера по артикулу или ручная загрузка.
+
+Артикул детали: Уникальный идентификатор. Заполняется через форму или импорт.
+
+Где используется (Артикул МАФ): Связь с основной номенклатурой. Может быть пустым.
+
+Кол-во Общее: Вычисляемое поле = Кол-во без документов + Кол-во с документами. Кликабельно. При клике открывает вкладку "Заказы деталей" с фильтром по этому артикулу и статусом "На складе".
+
+Кол-во без документов: Вычисляемое поле. Сумма остатков (Остаток > 0) из вкладки "Заказы деталей" для этого артикула, где С документами = "НЕТ" и Статус = "На складе". Кликабельно. При клике открывает "Заказы деталей" с фильтром: артикул, Остаток > 0, С документами = "НЕТ", Статус = "На складе".
+
+Кол-во с документами: Вычисляемое поле. Сумма остатков (Остаток > 0) из вкладки "Заказы деталей" для этого артикула, где С документами = "ДА" и Статус = "На складе". Кликабельно. При клике открывает "Заказы деталей" с фильтром: артикул, Остаток > 0, С документами = "ДА", Статус = "На складе".
+
+Примечание: Руководящее поле.
+
+Цена закупки: Только для Администратора. Скрыта для других ролей.
+
+Цена для заказчика: Руководящее/импортируемое поле.
+
+Цена экспертизы: Руководящее/импортируемое поле.
+
+№ по ТСН: Руководящее/импортируемое поле.
+
+Шифр расценки и коды ресурсов: Может содержать несколько кодов (например, "11.2457.24/14"). Руководящее/импортируемое поле.
+
+Функционал:
+
+Редактирование: При клике на строку открывается форма редактирования, где можно менять только поля: Примечание, Где используется, Цена закупки, Цена для заказчика, Цена экспертизы, № по ТСН, Шифр расценки.
+
+Импорт/Экспорт: Аналогично существующему модулю "Каталог МАФ" (ДКР). Ключевое требование: При импорте данных (например, из файла предыдущего экспорта) система должна корректно обрабатывать и сохранять все связанные данные (ссылки на картинки, связи артикулов, записи в справочнике расшифровок кодов). При экспорте — выгружать данные в формате, пригодном для последующего целостного импорта.
+
+Расшифровка кодов (для полей 11 и 12):
+
+Создать отдельный справочник "Расшифровка расценок" с полями: КОД, Расшифровка.
+
+Реализовать подсказку (tooltip) при наведении курсора на код в каталоге, отображающую его расшифровку из справочника.
+
+Связь с импортом/экспортом: При импорте каталога, если в файле встречаются новые коды, отсутствующие в справочнике, система должна предлагать пользователю (Администратору) добавить их расшифровку или создать записи с пустым полем "Расшифровка".
+
+3. Модуль 2: Список заказов деталей
+
+Назначение: Учёт партий запчастей, поступивших на склад или ожидаемых.
+
+Структура записи:
+
+Картинка: Берётся из Каталога по артикулу.
+
+Артикул детали: Ссылка на запись в Каталоге.
+
+Источник заказа (площадка/рекламация):
+
+Цель: Указать, для какого объекта или случая сделан заказ. Связь с сущностями "Площадки" и "Рекламации" НЕОБЯЗАТЕЛЬНА.
+
+Реализация: Поле ввода с возможностью:
+
+Свободного ввода текста (например, "Складской запас", "Договор №XXX").
+
+Поиска и выбора существующей Площадки из системы (через выпадающий список с автодополнением).
+
+Поиска и выбора существующей Рекламации из системы (через выпадающий список с автодополнением).
+
+В таблице отображается текстовое представление выбранного объекта (название площадки, номер рекламации) или введённый вручную текст.
+
+Статус: Выбор из трёх значений: Заказано, На складе, Отгружено.
+
+Логика статусов: При создании - Заказано. При ручном изменении на На складе - детали учитываются в остатках каталога. Когда Остаток становится 0 - статус автоматически меняется на Отгружено.
+
+Заказано: Исходное количество в заказе.
+
+Остаток: Текущее количество, оставшееся в этом заказе (уменьшается при списании в рекламации).
+
+С документами: Чекбокс "ДА"/"НЕТ".
+
+Примечание: Руководящее поле.
+
+Детальная карточка заказа:
+
+Открывается по клику на строку. Позволяет редактировать все поля.
+
+История списаний: Блок, отображающий, сколько, когда и куда (примечание из окна списания, например, "Рекламация №ХХХ") было отгружено деталей из этого заказа.
+
+Кнопка "Отгрузить":
+
+При нажатии открывает модальное окно с полями: Количество (для списания) и Примечание (куда/для чего забирается, например, номер рекламации).
+
+После подтверждения:
+
+Значение Остаток в заказе уменьшается на указанное количество.
+
+В блоке истории списаний добавляется запись с указанным количеством и примечанием.
+
+4. Интеграция с рекламациями (модификация существующего функционала)
+
+Добавление детали в рекламацию:
+
+Поле для ввода детали должно быть с автодополнением (typeahead). При вводе артикула предлагаются варианты только из Каталога запчастей.
+
+Добавить чекбокс "С документами" рядом с полем выбора детали.
+
+Логика списания при сохранении рекламации:
+
+Система получает артикул детали, необходимое количество и признак "С документами".
+
+Поиск заказа для списания: В модуле "Заказы деталей" ищется заказ с данным артикулом, где С документами соответствует запрошенному признаку, Статус = "На складе" и Остаток > 0. Списание происходит из найденного заказа (уменьшается Остаток).
+
+Важно: В историю списаний этого заказа автоматически добавляется запись: "Автоматическое списание для рекламации: [НОМЕР_ТЕКУЩЕЙ_РЕКЛАМАЦИИ]".
+
+Если подходящего заказа не найдено:
+
+В Каталоге в соответствующей колонке (Кол-во с документами или Кол-во без документов) значение становится отрицательным (например, -1 или -запрашиваемое_количество).
+
+Данная позиция автоматически попадает во вкладку "Контроль наличия".
+
+5. Модуль 3: Контроль наличия
+
+Назначение: Панель мониторинга проблем с наличием.
+
+Данные формируются автоматически, редактирование записей недоступно.
+
+Источники данных и логика формирования:
+
+Правило 1 (Критический недостаток): Вывести все детали, у которых в Каталоге значение в колонках Кол-во без документов ИЛИ Кол-во с документами меньше 0. Выделить строку красным. В столбце "Примечание" системы написать: "Отсутствие детали".
+
+Правило 2 (Минимальный остаток):
+
+В Каталоге добавить скрытое (или отображаемое) поле "Минимальный остаток" для каждой детали (по умолчанию 0).
+
+Если в Каталоге Кол-во Общее (или одна из детализированных колонок, в зависимости от политики) станет меньше значения "Минимальный остаток" - вывести деталь в этот модуль. Выделить строку желтым. В примечании: "Достигнут лимит минимального остатка (X шт)", где X - значение минимального остатка.
+
+Колонки: Картинка, Артикул, Кол-во для заказа (сколько не хватает, т.е. модуль отрицательного значения из Каталога), Примечание (системное).
+
+Логика снятия уведомления: Уведомление из этого модуля автоматически снимается, когда условие, вызвавшее его, перестаёт выполняться. Например:
+
+Для "отсутствия" (-N) — когда появляется заказ с этой деталью и статусом На складе или Заказано, который может покрыть недостачу (остаток в заказе >= недостающему количеству). После этого отрицательное значение в каталоге обнуляется или становится положительным.
+
+Для "минимального остатка" — когда Кол-во Общее становится >= значению "Минимальный остаток".
+
+6. Требования к интерфейсу
+
+Вкладки: "Каталог запчастей", "Заказы деталей", "Контроль наличия".
+
+Таблицы с сортировкой, фильтрацией и кликабельными ячейками, как указано выше.
+
+Единый стиль с существующей платформой (логика работы кнопок, модальных окон, импорта/экспорта).

+ 289 - 0
docs/flex_roles_plan.md

@@ -0,0 +1,289 @@
+# План внедрения гибкой ролевой модели (RBAC)
+
+## Обзор
+
+Внедрение системы RBAC (Role-Based Access Control) для Stroyprofit CRM с возможностью создания новых ролей и назначения им прав через UI.
+
+## Текущее состояние
+
+| Компонент | Описание |
+|-----------|----------|
+| Хранение ролей | Поле `role` (string) в таблице `users` |
+| Класс Role | Статический класс с константами `app/Models/Role.php` |
+| Проверка ролей | Helper функция `hasRole()` в `app/Helpers/roles.php` |
+| Middleware | `EnsureUserHasRole` - проверка через `in_array()` |
+| Точки проверки | 155+ вызовов `hasRole()` в Blade, контроллерах, FormRequest |
+
+**Текущие роли:** admin, manager, brigadier
+
+---
+
+## Фаза 1: База данных
+
+### Миграции
+
+**1.1 Таблица `roles`**
+```
+database/migrations/2026_02_03_000001_create_roles_table.php
+```
+- `id`, `slug` (unique), `name`, `description`, `is_system` (boolean), `timestamps`
+
+**1.2 Таблица `permissions`**
+```
+database/migrations/2026_02_03_000002_create_permissions_table.php
+```
+- `id`, `slug` (unique), `name`, `module`, `action`, `description`, `timestamps`
+
+**1.3 Связь ролей и прав `role_has_permissions`**
+```
+database/migrations/2026_02_03_000003_create_role_has_permissions_table.php
+```
+- `role_id` (FK), `permission_id` (FK), составной PK
+
+**1.4 Добавление `role_id` в `users`**
+```
+database/migrations/2026_02_03_000004_add_role_id_to_users_table.php
+```
+- `role_id` (FK nullable) - связь с таблицей roles
+
+---
+
+## Фаза 2: Модели
+
+### 2.1 Обновление `app/Models/Role.php`
+
+Преобразовать из статического класса в Eloquent модель:
+- Сохранить константы для обратной совместимости (ADMIN, MANAGER, BRIGADIER)
+- Добавить связь `permissions()` (belongsToMany)
+- Добавить связь `users()` (hasMany)
+- Методы: `hasPermission()`, `givePermissions()`, `syncPermissions()`
+
+### 2.2 Новая модель `app/Models/Permission.php`
+
+- Связь `roles()` (belongsToMany)
+- Статический метод `getGroupedByModule()`
+
+### 2.3 Обновление `app/Models/User.php`
+
+Добавить:
+- Поле `role_id` в `$fillable`
+- Связь `roleModel()` (belongsTo Role)
+- Метод `hasRole()` - сначала проверяет `role_id`, fallback на `role`
+- Метод `hasPermission()` - админ имеет все права
+- Метод `hasAnyPermission()`
+- Метод `getAllPermissions()`
+
+---
+
+## Фаза 3: Helper функции
+
+### Обновление `app/Helpers/roles.php`
+
+- `getRoles()` - сначала из БД, fallback на константы
+- `hasRole()` - использовать метод модели User
+- `hasPermission()` - новая функция
+- `hasAnyPermission()` - новая функция
+- `roleName()` - сначала из БД, fallback на константы
+
+---
+
+## Фаза 4: Middleware
+
+### 4.1 Обновить `app/Http/Middleware/EnsureUserHasRole.php`
+- Использовать метод `$user->hasRole()` вместо прямой проверки
+
+### 4.2 Новый `app/Http/Middleware/EnsureUserHasPermission.php`
+- Использование: `middleware('permission:orders.create')`
+
+### 4.3 Новый `app/Http/Middleware/EnsureUserHasAnyPermission.php`
+- Использование: `middleware('permission.any:orders.create,orders.update')`
+
+### 4.4 Регистрация в `bootstrap/app.php`
+```php
+$middleware->alias([
+    'role' => EnsureUserHasRole::class,
+    'permission' => EnsureUserHasPermission::class,
+    'permission.any' => EnsureUserHasAnyPermission::class,
+]);
+```
+
+---
+
+## Фаза 5: Blade директивы
+
+### Добавить в `app/Providers/AppServiceProvider.php`
+
+```php
+Blade::if('role', fn($roles) => hasRole($roles));
+Blade::if('permission', fn($permission) => hasPermission($permission));
+Blade::if('anypermission', fn($permissions) => hasAnyPermission($permissions));
+```
+
+**Использование:**
+```blade
+@role('admin')...@endrole
+@permission('orders.delete')...@endpermission
+```
+
+---
+
+## Фаза 6: Сидер данных
+
+### `database/seeders/RbacSeeder.php`
+
+1. Создать 73 права (permissions) по модулям
+2. Создать системные роли (admin, manager, brigadier) с `is_system=true`
+3. Назначить права ролям
+4. Мигрировать существующих пользователей: заполнить `role_id` на основе `role`
+
+---
+
+## Фаза 7: UI управления ролями
+
+### 7.1 Контроллер `app/Http/Controllers/Admin/RoleController.php`
+- CRUD для ролей
+- Назначение прав через чекбоксы
+
+### 7.2 Маршруты (добавить в `routes/web.php`)
+```php
+Route::prefix('admin/roles')->middleware('role:admin')->group(function(){
+    Route::get('', [RoleController::class, 'index'])->name('admin.roles.index');
+    Route::get('create', [RoleController::class, 'create'])->name('admin.roles.create');
+    Route::post('', [RoleController::class, 'store'])->name('admin.roles.store');
+    Route::get('{role}', [RoleController::class, 'show'])->name('admin.roles.show');
+    Route::put('{role}', [RoleController::class, 'update'])->name('admin.roles.update');
+    Route::delete('{role}', [RoleController::class, 'destroy'])->name('admin.roles.destroy');
+});
+```
+
+### 7.3 Views
+- `resources/views/admin/roles/index.blade.php` - список ролей
+- `resources/views/admin/roles/edit.blade.php` - создание/редактирование с чекбоксами прав
+
+### 7.4 Меню
+Добавить в `resources/views/layouts/menu.blade.php`:
+```blade
+<li><a href="{{ route('admin.roles.index') }}">Роли и права</a></li>
+```
+
+---
+
+## Фаза 8: Обновление формы пользователя
+
+### Обновить `app/Http/Requests/User/StoreUser.php`
+- Заменить `role` на `role_id`
+
+### Обновить view редактирования пользователя
+- Выпадающий список ролей из БД вместо констант
+
+---
+
+## Список прав (73 права)
+
+| Модуль | Права |
+|--------|-------|
+| orders | view, create, update, delete, export, documents, photos, maf, generate |
+| reclamations | view, create, update, delete, documents, photos, spare_parts |
+| maf | view, update, import, export, passports |
+| maf_orders | view, create, update, delete |
+| catalog | view, create, update, delete, import, export, certificates |
+| schedule | view, create, update, delete, export |
+| spare_parts | view, create, update, delete, import, export |
+| spare_part_orders | view, create, update, delete, ship |
+| spare_part_reservations | view, manage |
+| spare_part_inventory | view |
+| contracts | view, create, update, delete |
+| users | view, create, update, delete |
+| responsibles | view, create, update, delete |
+| reports | view |
+| import | view, create |
+| districts | view, manage |
+| areas | view, manage |
+| pricing_codes | view, manage |
+| admin | clear_data, year_data, roles |
+
+---
+
+## Права по ролям (по умолчанию)
+
+### Admin
+Все права (*)
+
+### Manager
+- orders: view, create, update, documents, photos, generate
+- reclamations: view, create, update, documents, photos, spare_parts
+- maf: view, update
+- catalog: view
+- schedule: view, create, update
+- spare_parts: view
+- spare_part_orders: view, create, update
+- spare_part_reservations: view, manage
+- spare_part_inventory: view
+- contracts: view, create, update
+- responsibles: view, create, update
+- reports: view
+
+### Brigadier
+- orders: view, photos
+- reclamations: view, photos
+- schedule: view
+
+---
+
+## Порядок выполнения
+
+1. Создать миграции (4 файла)
+2. Создать модель Permission
+3. Обновить модель Role (сохранить константы)
+4. Обновить модель User
+5. Обновить helpers/roles.php
+6. Обновить EnsureUserHasRole middleware
+7. Создать новые middleware (permission, permission.any)
+8. Зарегистрировать middleware в bootstrap/app.php
+9. Добавить Blade директивы в AppServiceProvider
+10. Создать RbacSeeder
+11. Запустить: `php artisan migrate && php artisan db:seed --class=RbacSeeder`
+12. Создать RoleController
+13. Создать views для ролей
+14. Обновить меню
+15. Обновить форму пользователя (role → role_id)
+
+---
+
+## Обратная совместимость
+
+- Функция `hasRole()` продолжает работать
+- Middleware `role:admin,manager` работает без изменений
+- Blade шаблоны с `@if(hasRole('admin'))` работают
+- Постепенная миграция на `hasPermission()` опциональна
+
+---
+
+## Критические файлы для изменения
+
+| Файл | Действие |
+|------|----------|
+| `app/Models/Role.php` | Переработать в Eloquent модель |
+| `app/Models/User.php` | Добавить role_id, методы hasPermission |
+| `app/Models/Permission.php` | Создать новую модель |
+| `app/Helpers/roles.php` | Обновить с поддержкой БД |
+| `app/Http/Middleware/EnsureUserHasRole.php` | Использовать метод модели |
+| `bootstrap/app.php` | Зарегистрировать новые middleware |
+| `app/Providers/AppServiceProvider.php` | Добавить Blade директивы |
+| `database/seeders/RbacSeeder.php` | Создать сидер |
+| `app/Http/Controllers/Admin/RoleController.php` | Создать контроллер |
+| `resources/views/admin/roles/*.blade.php` | Создать views |
+| `resources/views/layouts/menu.blade.php` | Добавить пункт меню |
+| `routes/web.php` | Добавить маршруты ролей |
+
+---
+
+## Верификация
+
+После внедрения:
+1. Проверить авторизацию существующих пользователей
+2. Создать новую роль через UI
+3. Назначить права новой роли
+4. Создать пользователя с новой ролью
+5. Проверить доступ к разделам согласно правам
+6. Проверить что системные роли нельзя удалить