Przeglądaj źródła

справка по запчастям

Alexander Musikhin 2 dni temu
rodzic
commit
f2ebfbe703

+ 2 - 2
app/Http/Controllers/ReportController.php

@@ -169,7 +169,7 @@ class ReportController extends Controller
             ProductSKU::query()
             ProductSKU::query()
                 ->withSum('product', 'total_price')
                 ->withSum('product', 'total_price')
                 ->get()
                 ->get()
-                ->sum('product_sum_total_price') / 10
+                ->sum('product_sum_total_price') / 100
         );
         );
         // общая сумма done
         // общая сумма done
         $this->data['totalDoneSum'] = Price::format(
         $this->data['totalDoneSum'] = Price::format(
@@ -179,7 +179,7 @@ class ReportController extends Controller
             })
             })
                 ->withSum('product', 'total_price')
                 ->withSum('product', 'total_price')
                 ->get()
                 ->get()
-                ->sum('product_sum_total_price') / 10
+                ->sum('product_sum_total_price') / 100
         );
         );
 
 
         $districts = District::query()->get()->pluck('shortname', 'id')->toArray();
         $districts = District::query()->get()->pluck('shortname', 'id')->toArray();

+ 12 - 0
app/Http/Controllers/SparePartController.php

@@ -9,8 +9,10 @@ use App\Models\Import;
 use App\Models\SparePart;
 use App\Models\SparePart;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\File;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Str;
 
 
 class SparePartController extends Controller
 class SparePartController extends Controller
 {
 {
@@ -89,6 +91,16 @@ class SparePartController extends Controller
         return view('spare_parts.edit', $this->data);
         return view('spare_parts.edit', $this->data);
     }
     }
 
 
+    public function help()
+    {
+        $markdownPath = base_path('docs/spare-parts.md');
+        $markdown = File::exists($markdownPath) ? File::get($markdownPath) : '# Справка не найдена';
+        $this->data['helpContent'] = Str::markdown($markdown);
+        $this->data['tab'] = 'help';
+
+        return view('spare_parts.index', $this->data);
+    }
+
     public function create()
     public function create()
     {
     {
         $this->data['spare_part'] = null;
         $this->data['spare_part'] = null;

+ 382 - 0
docs/spare-parts.md

@@ -0,0 +1,382 @@
+# Модуль запчастей
+
+Модуль управления запчастями позволяет вести учёт запасных частей для обслуживания малых архитектурных форм (МАФ). Включает каталог, заказы, резервирование, списания и контроль дефицитов.
+
+## Содержание
+
+1. [Каталог запчастей](#1-каталог-запчастей)
+2. [Заказы запчастей (партии)](#2-заказы-запчастей-партии)
+3. [Резервирование и дефициты](#3-резервирование-и-дефициты)
+4. [Списание запчастей](#4-списание-запчастей)
+5. [Движения запасов](#5-движения-запасов)
+6. [Контроль наличия](#6-контроль-наличия)
+7. [Связь с рекламациями](#7-связь-с-рекламациями)
+
+---
+
+## 1. Каталог запчастей
+
+### Доступ
+Меню: **Запчасти** → вкладка **Каталог**
+
+### Описание
+Справочник всех доступных запчастей. Содержит информацию о наименовании, ценах и применении. **Важно:** каталог не хранит остатки напрямую — они рассчитываются автоматически на основе заказов и резервов.
+
+### Поля карточки запчасти
+
+| Поле | Описание |
+|------|----------|
+| Артикул | Уникальный идентификатор запчасти |
+| Где используется | Описание применения (какой МАФ) |
+| Цена закупки | Закупочная цена (только для админов) |
+| Цена для заказчика | Цена продажи |
+| Цена экспертизы | Цена для экспертных работ |
+| № по ТСН | Номер по территориальным сметным нормам |
+| Шифры расценки | Коды расценок (можно несколько) |
+| Мин. остаток | Минимальный остаток на складе |
+| Картинка | Фото запчасти |
+
+### Вычисляемые остатки
+
+Система автоматически рассчитывает:
+- **Физический остаток** — сколько реально есть на складе
+- **Зарезервировано** — сколько забронировано под рекламации
+- **Свободно** — физический минус зарезервировано
+
+Остатки ведутся раздельно:
+- **С документами** — запчасти с полным пакетом документов
+- **Без документов** — запчасти без документов
+
+### Действия
+
+- **Создать** — добавить новую запчасть (админ)
+- **Редактировать** — изменить данные запчасти
+- **Удалить** — удалить запчасть (только если нет заказов)
+- **Экспорт** — выгрузить каталог в Excel
+- **Импорт** — загрузить данные из Excel
+
+### Фильтрация
+
+- По применению (где используется)
+- По диапазону цен
+- По наличию ниже минимума
+
+---
+
+## 2. Заказы запчастей (партии)
+
+### Доступ
+Меню: **Запчасти** → вкладка **Заказы деталей**
+
+### Описание
+Каждый заказ — это партия запчастей с определённым количеством. Именно заказы формируют физические остатки на складе.
+
+### Жизненный цикл заказа
+
+```
+┌──────────┐      Поступило       ┌───────────┐      Всё списано      ┌───────────┐
+│ Заказано │  ──────────────────► │ На складе │  ────────────────────► │ Отгружено │
+│ (ordered)│                      │ (in_stock)│                        │ (shipped) │
+└──────────┘                      └───────────┘                        └───────────┘
+     ▲                                  │
+     │                                  │
+     │                                  ▼
+   Создание                      Доступно для
+   заказа                        резервирования
+```
+
+### Поля заказа
+
+| Поле | Описание |
+|------|----------|
+| Артикул | Какая запчасть |
+| Источник | Откуда заказ (текстовое описание) |
+| Заказано | Начальное количество |
+| Остаток | Текущий остаток в партии |
+| С документами | Флаг наличия документов |
+| Статус | Заказано / На складе / Отгружено |
+| Примечание | Дополнительная информация |
+
+### Действия
+
+- **Создать заказ** — оформить новый заказ запчасти
+- **Поступило на склад** — перевести заказ в статус "На складе" (активирует остаток)
+- **Прямое списание** — списать без резервирования (ручная операция)
+- **Коррекция** — исправить остаток по итогам инвентаризации (только админ)
+- **Удалить** — удалить заказ (только если нет активных резервов)
+
+### Автоматика при поступлении
+
+При переводе заказа в статус "На складе":
+1. Система проверяет открытые дефициты по этой запчасти
+2. Автоматически создаёт резервы под дефициты (FIFO — сначала старые)
+3. Закрывает дефициты, если они полностью покрыты
+
+---
+
+## 3. Резервирование и дефициты
+
+### Что такое резерв?
+
+Резерв — это бронирование запчасти под конкретную рекламацию. Резерв **уменьшает свободный остаток**, но **не уменьшает физический** до момента списания.
+
+### Статусы резерва
+
+| Статус | Описание |
+|--------|----------|
+| Активен | Запчасть забронирована |
+| Списан | Запчасть отгружена |
+| Отменён | Бронь отменена |
+
+### Что такое дефицит?
+
+Дефицит создаётся автоматически, когда при резервировании **не хватает свободных запчастей**. Дефицит фиксирует неудовлетворённую потребность.
+
+### Пример
+
+1. Требуется 10 шт запчасти для рекламации
+2. Свободно на складе только 3 шт
+3. Система создаёт:
+   - Резерв на 3 шт (из имеющихся)
+   - Дефицит на 7 шт (не хватает)
+
+### Автоматическое закрытие дефицита
+
+При поступлении новой партии запчасти:
+1. Система находит открытые дефициты (по FIFO — старые первыми)
+2. Автоматически резервирует новые запчасти под дефициты
+3. Закрывает дефициты при полном покрытии
+
+### Статусы дефицита
+
+| Статус | Описание |
+|--------|----------|
+| Открыт | Есть недостача, ожидает поступления |
+| Закрыт | Полностью покрыт резервами |
+
+---
+
+## 4. Списание запчастей
+
+### Способы списания
+
+#### 1. Списание через резерв (основной способ)
+
+Используется при работе с рекламациями:
+1. Запчасть резервируется под рекламацию
+2. При отгрузке нажимается кнопка "Списать"
+3. Резерв закрывается, остаток партии уменьшается
+
+**Где:** Карточка заказа → блок "Резервы" → кнопка "Списать"
+
+#### 2. Прямое списание (ручное)
+
+Для ситуаций без рекламации:
+1. Открыть карточку заказа
+2. Нажать "Прямое списание"
+3. Указать количество и причину
+
+**Где:** Карточка заказа → кнопка "Списать"
+
+#### 3. Списание через рекламацию
+
+При удалении запчасти из рекламации можно выбрать:
+- Отменить резерв (запчасть возвращается в свободный остаток)
+- Списать (запчасть списывается со склада)
+
+### Списание всех резервов рекламации
+
+В карточке рекламации можно списать все запчасти одной кнопкой:
+- **Списать все** — списывает все активные резервы рекламации
+
+---
+
+## 5. Движения запасов
+
+### Описание
+
+Каждая операция с запчастями записывается в журнал движений. Это обеспечивает полный аудит и историю.
+
+### Типы движений
+
+| Тип | Описание | Влияние на остаток |
+|-----|----------|-------------------|
+| Поступление | Партия пришла на склад | +остаток |
+| Резервирование | Забронировано под рекламацию | Нет (только свободный) |
+| Списание | Отгрузка со склада | −остаток |
+| Отмена резерва | Бронь отменена | Нет (освобождает свободный) |
+| Коррекция (+) | Увеличение при инвентаризации | +остаток |
+| Коррекция (−) | Уменьшение при инвентаризации | −остаток |
+
+### Источники операций
+
+- **Рекламация** — операция вызвана работой с рекламацией
+- **Ручная** — ручная операция пользователя
+- **Закрытие дефицита** — автоматическое резервирование при поступлении
+- **Инвентаризация** — коррекция остатков
+
+### Просмотр истории
+
+**Где:** Карточка заказа → блок "История движений"
+
+Показывает:
+- Дата и время операции
+- Тип операции
+- Количество
+- Примечание
+- Кто выполнил
+
+---
+
+## 6. Контроль наличия
+
+### Доступ
+Меню: **Запчасти** → вкладка **Контроль наличия**
+
+### Описание
+Дашборд для мониторинга запасов и управления дефицитами.
+
+### Блоки дашборда
+
+#### Критические дефициты
+Запчасти с открытыми дефицитами. Требуют срочного заказа.
+
+| Информация | Описание |
+|------------|----------|
+| Запчасть | Артикул и название |
+| Рекламация | Для какой рекламации |
+| Требуется | Сколько нужно всего |
+| Зарезервировано | Сколько уже есть |
+| Не хватает | Сколько нужно заказать |
+| Покрытие | Процент обеспеченности |
+
+#### Ниже минимума
+Запчасти, свободный остаток которых ниже установленного минимума.
+
+### Рекомендуемые действия
+
+1. **Открыть дефицит** → узнать для какой рекламации
+2. **Создать заказ** на недостающее количество
+3. **При поступлении** → дефицит закроется автоматически
+
+---
+
+## 7. Связь с рекламациями
+
+### Добавление запчасти в рекламацию
+
+1. Открыть карточку рекламации
+2. Перейти в раздел "Запчасти"
+3. Выбрать запчасть из каталога
+4. Указать количество и тип документов
+5. Сохранить
+
+При сохранении:
+- Система пытается зарезервировать запчасти
+- Если не хватает — создаётся дефицит
+- Показывается информация о статусе резервирования
+
+### Изменение количества
+
+При изменении количества запчасти:
+- Если увеличение → дорезервируется (или увеличивается дефицит)
+- Если уменьшение → освобождается часть резерва
+
+### Удаление запчасти из рекламации
+
+Варианты:
+1. **Отменить резерв** — запчасть возвращается в свободный остаток
+2. **Списать** — запчасть списывается со склада
+
+При удалении также закрывается связанный дефицит (если был).
+
+### Списание запчастей по рекламации
+
+В карточке рекламации:
+- **Списать запчасть** — списать конкретную запчасть
+- **Списать все** — списать все зарезервированные запчасти
+
+---
+
+## Приложение: Схема работы системы
+
+### FIFO-резервирование
+
+При резервировании система выбирает партии в порядке их создания (старые первыми):
+
+```
+Партия 1 (создана 01.01): 5 шт ──► Резервируется первой
+Партия 2 (создана 15.01): 3 шт ──► Резервируется второй
+Партия 3 (создана 01.02): 10 шт ──► Резервируется последней
+```
+
+### Разделение по типам документов
+
+Остатки "с документами" и "без документов" учитываются **раздельно**:
+
+```
+Запрос: 5 шт с документами
+
+Партия A (без документов): 10 шт ──► Не подходит
+Партия B (с документами): 3 шт  ──► Резерв 3 шт
+Партия C (с документами): 5 шт  ──► Резерв 2 шт
+
+Итого: Резерв 5 шт, дефицит 0
+```
+
+### Автозакрытие дефицитов
+
+```
+1. Дефицит открыт: нужно 7 шт с документами
+   ↓
+2. Поступает партия: 10 шт с документами, статус → "На складе"
+   ↓
+3. Observer срабатывает автоматически
+   ↓
+4. Создаётся резерв на 7 шт из новой партии
+   ↓
+5. Дефицит закрывается
+   ↓
+6. Остаток партии: 3 шт свободно
+```
+
+---
+
+## Права доступа
+
+| Действие | Админ | Менеджер | Бригадир |
+|----------|:-----:|:--------:|:--------:|
+| Просмотр каталога | ✓ | ✓ |    ✓     |
+| Создание/редактирование запчасти | ✓ | ✗ |    ✗     |
+| Удаление запчасти | ✓ | ✗ |    ✗     |
+| Импорт/экспорт каталога | ✓ | ✗ |    ✗     |
+| Создание заказа | ✓ | ✓ |    ✗     |
+| Списание | ✓ | ✓ |    ✗     |
+| Коррекция остатков | ✓ | ✗ |    ✗     |
+| Удаление заказа | ✓ | ✗ |    ✗     |
+| Просмотр цены закупки | ✓ | ✗ |    ✗     |
+
+---
+
+## Часто задаваемые вопросы
+
+### Почему не могу удалить запчасть?
+Запчасть нельзя удалить, если есть связанные заказы. Сначала нужно удалить все заказы.
+
+### Почему не могу удалить заказ?
+Заказ нельзя удалить, если есть активные резервы. Сначала отмените или спишите резервы.
+
+### Как исправить ошибку в остатке?
+Используйте функцию "Коррекция" в карточке заказа (доступно только админам).
+
+### Почему свободный остаток меньше физического?
+Часть запчастей зарезервирована под рекламации. Посмотрите блок "Резервы" в карточке заказа.
+
+### Как узнать, для каких рекламаций зарезервированы запчасти?
+В карточке заказа блок "Резервы" показывает список рекламаций с количеством.
+
+### Дефицит не закрылся автоматически при поступлении — почему?
+Проверьте:
+1. Тип документов должен совпадать (с документами / без документов)
+2. Заказ должен быть переведён в статус "На складе"
+3. Дефицит должен быть в статусе "Открыт"

+ 64 - 0
resources/sass/app.scss

@@ -133,3 +133,67 @@
 .svod-32, .svod-33, .svod-28, .svod-29, .svod-30, .svod-31{
 .svod-32, .svod-33, .svod-28, .svod-29, .svod-30, .svod-31{
   background-color: #ffc0c0 !important;
   background-color: #ffc0c0 !important;
 }
 }
+
+// Markdown content styles
+.markdown-content {
+  h1, h2, h3, h4, h5, h6 {
+    margin-top: 1.5rem;
+    margin-bottom: 0.75rem;
+  }
+
+  h1 { font-size: 1.75rem; }
+  h2 { font-size: 1.5rem; border-bottom: 1px solid #dee2e6; padding-bottom: 0.5rem; }
+  h3 { font-size: 1.25rem; }
+  h4 { font-size: 1.1rem; }
+
+  table {
+    width: 100%;
+    margin-bottom: 1rem;
+    border-collapse: collapse;
+
+    th, td {
+      padding: 0.5rem;
+      border: 1px solid #dee2e6;
+      cursor: default;
+    }
+
+    th {
+      background-color: var(--bs-primary-bg-subtle);
+    }
+  }
+
+  pre {
+    background-color: #f8f9fa;
+    padding: 1rem;
+    border-radius: 0.25rem;
+    overflow-x: auto;
+
+    code {
+      color: inherit;
+    }
+  }
+
+  code {
+    color: #d63384;
+    background-color: #f8f9fa;
+    padding: 0.2rem 0.4rem;
+    border-radius: 0.25rem;
+  }
+
+  hr {
+    margin: 2rem 0;
+    border-top: 1px solid #dee2e6;
+  }
+
+  ul, ol {
+    padding-left: 1.5rem;
+    margin-bottom: 1rem;
+  }
+
+  blockquote {
+    border-left: 4px solid #dee2e6;
+    padding-left: 1rem;
+    color: #6c757d;
+    margin: 1rem 0;
+  }
+}

+ 7 - 0
resources/views/partials/table.blade.php

@@ -148,6 +148,13 @@
                                   style="cursor: help; text-decoration: underline dotted;">
                                   style="cursor: help; text-decoration: underline dotted;">
                                 {{ $string->$headerName }}
                                 {{ $string->$headerName }}
                             </span>
                             </span>
+                        @elseif($headerName === 'pricing_codes_list' && $string->$headerName)
+                            <span data-bs-toggle="tooltip"
+                                  data-bs-placement="top"
+                                  title="{{ $string->pricing_code_description ?? 'Нет расшифровки' }}"
+                                  style="cursor: help; text-decoration: underline dotted;">
+                                {{ $string->$headerName }}
+                            </span>
                         @else
                         @else
                             <p title="{!! $string->$headerName !!}">
                             <p title="{!! $string->$headerName !!}">
                                 {!! \Illuminate\Support\Str::words($string->$headerName, config('app.words_in_table_cell_limit'), ' ...') !!}
                                 {!! \Illuminate\Support\Str::words($string->$headerName, config('app.words_in_table_cell_limit'), ' ...') !!}

+ 14 - 0
resources/views/spare_parts/index.blade.php

@@ -33,6 +33,12 @@
                     </a>
                     </a>
                 </li>
                 </li>
                 @endif
                 @endif
+                <li class="nav-item">
+                    <a class="nav-link {{ ($tab ?? '') === 'help' ? 'active' : '' }}"
+                       href="{{ route('spare_parts.help') }}">
+                        <i class="bi bi-question-circle"></i> Справка
+                    </a>
+                </li>
             </ul>
             </ul>
 
 
             @if(($tab ?? 'catalog') === 'catalog')
             @if(($tab ?? 'catalog') === 'catalog')
@@ -254,6 +260,14 @@
                 @else
                 @else
                     <p class="text-muted">Нет запчастей ниже минимального остатка</p>
                     <p class="text-muted">Нет запчастей ниже минимального остатка</p>
                 @endif
                 @endif
+
+            @elseif($tab === 'help')
+                {{-- Справка --}}
+                <div class="card">
+                    <div class="card-body markdown-content">
+                        {!! $helpContent !!}
+                    </div>
+                </div>
             @endif
             @endif
         </div>
         </div>
     </div>
     </div>

+ 1 - 0
routes/web.php

@@ -235,6 +235,7 @@ Route::middleware('auth:web')->group(function () {
     // Каталог запчастей
     // Каталог запчастей
     Route::prefix('spare-parts')->name('spare_parts.')->group(function () {
     Route::prefix('spare-parts')->name('spare_parts.')->group(function () {
         Route::get('/', [SparePartController::class, 'index'])->name('index');
         Route::get('/', [SparePartController::class, 'index'])->name('index');
+        Route::get('/help', [SparePartController::class, 'help'])->name('help');
         Route::get('/search', [SparePartController::class, 'search'])->name('search');
         Route::get('/search', [SparePartController::class, 'search'])->name('search');
         Route::get('/create', [SparePartController::class, 'create'])->name('create')->middleware('role:admin');
         Route::get('/create', [SparePartController::class, 'create'])->name('create')->middleware('role:admin');
         Route::get('/{sparePart}', [SparePartController::class, 'show'])->name('show');
         Route::get('/{sparePart}', [SparePartController::class, 'show'])->name('show');

BIN
templates/Orders.xlsx