feat: add LOT creation, auto-save mappings, disable auto warehouse pricelist

- Add LOT creation functionality in pricing admin
  - New API endpoint POST /api/admin/pricing/lots
  - Modal form for creating new LOT with auto-category detection
  - Creates entries in both lot and qt_lot_metadata tables

- Implement auto-save for stock mappings
  - Auto-save on change for partnumber → LOT mappings
  - Visual feedback (orange during save, green on success, red on error)
  - Works in both main mappings table and import suggestions

- Improve stock import suggestions UI
  - Remove "Причина" column from suggestions table
  - Increase LOT and Partnumber column widths to 33% each
  - Better visual balance in the table layout

- Disable automatic warehouse pricelist creation on stock_log import
  - Import now completes at 100% after stock_log update
  - Manual pricelist creation available via UI when needed
  - Faster import process without auto-generation overhead

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 11:34:43 +03:00
parent 319400106c
commit 85062e007c
15 changed files with 474 additions and 594 deletions

View File

@@ -58,6 +58,15 @@
let canWrite = false;
let currentPage = 1;
function escapeHtml(value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
async function checkPricelistWritePermission() {
try {
const resp = await fetch('/api/pricelists/can-write');
@@ -88,7 +97,7 @@
document.getElementById('pricelists-body').innerHTML = `
<tr>
<td colspan="7" class="px-6 py-4 text-center text-red-500">
Ошибка загрузки: ${e.message}
Ошибка загрузки: ${escapeHtml(e.message)}
</td>
</tr>
`;
@@ -111,21 +120,28 @@
const date = new Date(pl.created_at).toLocaleDateString('ru-RU');
const statusClass = pl.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800';
const statusText = pl.is_active ? 'Активен' : 'Неактивен';
const id = Number(pl.id);
const safeID = Number.isFinite(id) ? id : 0;
const safeVersion = escapeHtml(pl.version || '');
const safeDate = escapeHtml(date);
const safeCreatedBy = escapeHtml(pl.created_by || '-');
const safeItemCount = Number.isFinite(Number(pl.item_count)) ? Number(pl.item_count) : 0;
const safeUsageCount = Number.isFinite(Number(pl.usage_count)) ? Number(pl.usage_count) : 0;
let actions = `<a href="/pricelists/${pl.id}" class="text-orange-600 hover:text-orange-800 text-sm">Просмотр</a>`;
if (canWrite && pl.usage_count === 0) {
actions += ` <button onclick="deletePricelist(${pl.id})" class="text-red-600 hover:text-red-800 text-sm ml-2">Удалить</button>`;
let actions = `<a href="/pricelists/${safeID}" class="text-orange-600 hover:text-orange-800 text-sm">Просмотр</a>`;
if (canWrite && safeUsageCount === 0 && safeID > 0) {
actions += ` <button onclick="deletePricelist(${safeID})" class="text-red-600 hover:text-red-800 text-sm ml-2">Удалить</button>`;
}
return `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<span class="font-mono text-sm">${pl.version}</span>
<span class="font-mono text-sm">${safeVersion}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${date}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${pl.created_by || '-'}</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm">${pl.item_count}</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm">${pl.usage_count}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${safeDate}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${safeCreatedBy}</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm">${safeItemCount}</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm">${safeUsageCount}</td>
<td class="px-6 py-4 whitespace-nowrap text-center">
<span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span>
</td>