Add article generation and pricelist categories
This commit is contained in:
@@ -249,10 +249,23 @@ function renderConfigs(configs) {
|
||||
html += '<td class="px-4 py-3 text-sm text-gray-700">' + escapeHtml(projectName) + '</td>';
|
||||
}
|
||||
}
|
||||
const article = c.article ? escapeHtml(c.article) : '';
|
||||
const serverModel = c.server_model ? escapeHtml(c.server_model) : '';
|
||||
const subtitle = article || serverModel;
|
||||
if (configStatusMode === 'archived') {
|
||||
html += '<td class="px-4 py-3 text-sm font-medium text-gray-700">' + escapeHtml(c.name) + '</td>';
|
||||
html += '<td class="px-4 py-3 text-sm font-medium text-gray-700">';
|
||||
html += '<div>' + escapeHtml(c.name) + '</div>';
|
||||
if (subtitle) {
|
||||
html += '<div class="text-xs text-gray-500">' + subtitle + '</div>';
|
||||
}
|
||||
html += '</td>';
|
||||
} else {
|
||||
html += '<td class="px-4 py-3 text-sm font-medium"><a href="/configurator?uuid=' + c.uuid + '" class="text-blue-600 hover:text-blue-800 hover:underline">' + escapeHtml(c.name) + '</a></td>';
|
||||
html += '<td class="px-4 py-3 text-sm font-medium">';
|
||||
html += '<a href="/configurator?uuid=' + c.uuid + '" class="text-blue-600 hover:text-blue-800 hover:underline">' + escapeHtml(c.name) + '</a>';
|
||||
if (subtitle) {
|
||||
html += '<div class="text-xs text-gray-500">' + subtitle + '</div>';
|
||||
}
|
||||
html += '</td>';
|
||||
}
|
||||
html += '<td class="px-4 py-3 text-sm text-gray-500">' + escapeHtml(author) + '</td>';
|
||||
html += '<td class="px-4 py-3 text-sm text-gray-500">' + pricePerUnit + '</td>';
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<div id="cart-summary-content" class="p-4">
|
||||
<div id="article-display" class="text-sm text-gray-700 mb-3 font-mono"></div>
|
||||
<div id="cart-items" class="space-y-2 mb-4"></div>
|
||||
<div class="border-t pt-3 flex justify-between items-center">
|
||||
<div class="text-lg font-bold">
|
||||
@@ -334,6 +335,10 @@ let cart = [];
|
||||
let categoryOrderMap = {}; // Category code -> display_order mapping
|
||||
let autoSaveTimeout = null; // Timeout for debounced autosave
|
||||
let serverCount = 1; // Server count for the configuration
|
||||
let serverModelForQuote = '';
|
||||
let supportCode = '';
|
||||
let currentArticle = '';
|
||||
let articlePreviewTimeout = null;
|
||||
let selectedPricelistIds = {
|
||||
estimate: null,
|
||||
warehouse: null,
|
||||
@@ -634,6 +639,9 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
category: item.category || getCategoryFromLotName(item.lot_name)
|
||||
}));
|
||||
}
|
||||
serverModelForQuote = config.server_model || '';
|
||||
supportCode = config.support_code || '';
|
||||
currentArticle = config.article || '';
|
||||
|
||||
// Restore custom price if saved
|
||||
if (config.custom_price) {
|
||||
@@ -948,7 +956,32 @@ function renderTab() {
|
||||
}
|
||||
|
||||
function renderSingleSelectTab(categories) {
|
||||
let html = `
|
||||
let html = '';
|
||||
if (currentTab === 'base') {
|
||||
html += `
|
||||
<div class="mb-1 grid grid-cols-1 md:grid-cols-[1fr,16rem] gap-3 items-start">
|
||||
<label for="server-model-input" class="block text-sm font-medium text-gray-700">Модель сервера для КП:</label>
|
||||
<label for="support-code-select" class="block text-sm font-medium text-gray-700">Уровень техподдержки:</label>
|
||||
</div>
|
||||
<div class="mb-3 grid grid-cols-1 md:grid-cols-[1fr,16rem] gap-3 items-start">
|
||||
<input type="text"
|
||||
id="server-model-input"
|
||||
value="${escapeHtml(serverModelForQuote)}"
|
||||
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500"
|
||||
oninput="updateServerModelForQuote(this.value)">
|
||||
<select id="support-code-select"
|
||||
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500"
|
||||
onchange="updateSupportCode(this.value)">
|
||||
<option value="">—</option>
|
||||
<option value="1yW" ${supportCode === '1yW' ? 'selected' : ''}>1yW</option>
|
||||
<option value="1yB" ${supportCode === '1yB' ? 'selected' : ''}>1yB</option>
|
||||
<option value="1yS" ${supportCode === '1yS' ? 'selected' : ''}>1yS</option>
|
||||
<option value="1yP" ${supportCode === '1yP' ? 'selected' : ''}>1yP</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
html += `
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
@@ -1636,6 +1669,8 @@ function updateCartUI() {
|
||||
calculateCustomPrice();
|
||||
renderSalePriceTable();
|
||||
|
||||
scheduleArticlePreview();
|
||||
|
||||
if (cart.length === 0) {
|
||||
document.getElementById('cart-items').innerHTML =
|
||||
'<div class="text-gray-500 text-center py-2">Конфигурация пуста</div>';
|
||||
@@ -1711,6 +1746,69 @@ function escapeHtml(text) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function updateServerModelForQuote(value) {
|
||||
serverModelForQuote = value || '';
|
||||
scheduleArticlePreview();
|
||||
}
|
||||
|
||||
function updateSupportCode(value) {
|
||||
supportCode = value || '';
|
||||
scheduleArticlePreview();
|
||||
}
|
||||
|
||||
function scheduleArticlePreview() {
|
||||
if (articlePreviewTimeout) {
|
||||
clearTimeout(articlePreviewTimeout);
|
||||
}
|
||||
articlePreviewTimeout = setTimeout(() => {
|
||||
previewArticle();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
async function previewArticle() {
|
||||
const el = document.getElementById('article-display');
|
||||
if (!el) return;
|
||||
|
||||
const model = serverModelForQuote.trim();
|
||||
if (!model || !selectedPricelistIds.estimate || cart.length === 0) {
|
||||
currentArticle = '';
|
||||
el.textContent = 'Артикул: —';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/configs/preview-article', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
server_model: serverModelForQuote,
|
||||
support_code: supportCode,
|
||||
pricelist_id: selectedPricelistIds.estimate,
|
||||
items: cart.map(item => ({
|
||||
lot_name: item.lot_name,
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price || 0
|
||||
}))
|
||||
})
|
||||
});
|
||||
if (!resp.ok) {
|
||||
currentArticle = '';
|
||||
el.textContent = 'Артикул: —';
|
||||
return;
|
||||
}
|
||||
const data = await resp.json();
|
||||
currentArticle = data.article || '';
|
||||
el.textContent = currentArticle ? ('Артикул: ' + currentArticle) : 'Артикул: —';
|
||||
} catch(e) {
|
||||
currentArticle = '';
|
||||
el.textContent = 'Артикул: —';
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentArticle() {
|
||||
return currentArticle || '';
|
||||
}
|
||||
|
||||
function triggerAutoSave() {
|
||||
// Debounce autosave - wait 1 second after last change
|
||||
if (autoSaveTimeout) {
|
||||
@@ -1751,6 +1849,9 @@ async function saveConfig(showNotification = true) {
|
||||
custom_price: customPrice,
|
||||
notes: '',
|
||||
server_count: serverCountValue,
|
||||
server_model: serverModelForQuote,
|
||||
support_code: supportCode,
|
||||
article: getCurrentArticle(),
|
||||
pricelist_id: selectedPricelistIds.estimate,
|
||||
only_in_stock: onlyInStock
|
||||
})
|
||||
@@ -1795,17 +1896,19 @@ async function exportCSV() {
|
||||
...item,
|
||||
unit_price: getDisplayPrice(item),
|
||||
}));
|
||||
const article = getCurrentArticle();
|
||||
const resp = await fetch('/api/export/csv', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({items: exportItems, name: configName, project_uuid: projectUUID})
|
||||
body: JSON.stringify({items: exportItems, name: configName, project_uuid: projectUUID, article: article})
|
||||
});
|
||||
|
||||
const blob = await resp.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = getFilenameFromResponse(resp) || (configName || 'config') + '.csv';
|
||||
const articleForName = article || 'BOM';
|
||||
a.download = getFilenameFromResponse(resp) || ((configName || 'config') + ' ' + articleForName + '.csv');
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch(e) {
|
||||
|
||||
Reference in New Issue
Block a user