feat: импорт собственного CSV QuoteForge + fix обновления цен
Добавлен парсер собственного CSV-экспорта QuoteForge (IsQuoteForgeCSV / parseQuoteForgeCSV). Формат: UTF-8 BOM + заголовок Line;Type;p/n;..., блоки сервер → компоненты. DirectItems создаются напрямую без прохода через VendorSpecResolver. Модальное окно импорта принимает .csv/.txt/.xml. Fix кнопки «Обновить цены» на странице варианта: после синхронизации прайс-листов запрашивается актуальный estimate-прайслист и передаётся явным pricelist_id в каждый POST /api/configs/:uuid/refresh-prices. Ранее использовался устаревший ID, сохранённый в конфигурации. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,14 +108,14 @@
|
||||
|
||||
<div id="vendor-import-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
||||
<div class="bg-white rounded-lg shadow-xl w-full max-w-lg mx-4 p-6">
|
||||
<h2 class="text-xl font-semibold mb-4">Импорт выгрузки вендора</h2>
|
||||
<h2 class="text-xl font-semibold mb-4">Импорт конфигураций</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="text-sm text-gray-600">
|
||||
Загружает `CFXML`-выгрузку в текущий проект и создаёт несколько конфигураций, если они есть в файле.
|
||||
Поддерживаемые форматы: CFXML-выгрузка вендора (.xml), собственный CSV-экспорт QuoteForge (.csv), текстовый BOM Inspur (.txt).
|
||||
</div>
|
||||
<div>
|
||||
<label for="vendor-import-file" class="block text-sm font-medium text-gray-700 mb-1">Файл выгрузки</label>
|
||||
<input id="vendor-import-file" type="file" accept=".xml,text/xml,application/xml"
|
||||
<label for="vendor-import-file" class="block text-sm font-medium text-gray-700 mb-1">Файл</label>
|
||||
<input id="vendor-import-file" type="file" accept=".xml,.csv,.txt,text/xml,application/xml,text/csv,text/plain"
|
||||
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-amber-500 focus:border-amber-500">
|
||||
</div>
|
||||
<div id="vendor-import-status" class="hidden text-sm rounded border px-3 py-2"></div>
|
||||
@@ -911,7 +911,7 @@ async function importVendorWorkspace() {
|
||||
const input = document.getElementById('vendor-import-file');
|
||||
const submit = document.getElementById('vendor-import-submit');
|
||||
if (!input || !input.files || !input.files[0]) {
|
||||
setVendorImportStatus('Выберите XML-файл выгрузки', 'error');
|
||||
setVendorImportStatus('Выберите файл для импорта', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1611,11 +1611,30 @@ async function refreshAllPrices() {
|
||||
serverSyncSkipped = true;
|
||||
}
|
||||
|
||||
// Resolve latest estimate pricelist ID to pass explicitly, so each config
|
||||
// is updated to the newest pricelist rather than the one stored in the config.
|
||||
let latestEstimatePricelistId = null;
|
||||
try {
|
||||
const plResp = await fetch('/api/pricelists?active_only=true&source=estimate&per_page=1');
|
||||
if (plResp.ok) {
|
||||
const plData = await plResp.json();
|
||||
const list = plData.pricelists || plData.items || plData;
|
||||
if (Array.isArray(list) && list.length > 0 && list[0].id) {
|
||||
latestEstimatePricelistId = Number(list[0].id);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
let failed = 0;
|
||||
let newTotalSum = 0;
|
||||
for (const cfg of configs) {
|
||||
try {
|
||||
const resp = await fetch('/api/configs/' + cfg.uuid + '/refresh-prices', { method: 'POST' });
|
||||
const body = latestEstimatePricelistId ? JSON.stringify({ pricelist_id: latestEstimatePricelistId }) : undefined;
|
||||
const resp = await fetch('/api/configs/' + cfg.uuid + '/refresh-prices', {
|
||||
method: 'POST',
|
||||
headers: body ? { 'Content-Type': 'application/json' } : {},
|
||||
body,
|
||||
});
|
||||
if (!resp.ok) { failed++; continue; }
|
||||
const updated = await resp.json();
|
||||
if (updated && updated.total_price != null) {
|
||||
|
||||
Reference in New Issue
Block a user