fix: preserve bid drafts and align tender source data
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { quantityParts, bidRowTotal, validateBidRows } from './bidValidation.js';
|
||||
/**
|
||||
* İhale dosyası belgeleri — teklif mektubu, birim fiyat teklif cetveli ve
|
||||
* evrak kontrol listesi.
|
||||
@@ -53,12 +54,7 @@ export const paraTR = (n) =>
|
||||
(Number(n) || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
|
||||
/** "3 adet" → { miktar: 3, birim: "adet" } ; okunamazsa miktar null */
|
||||
export function miktarAyristir(q) {
|
||||
const m = /(-?[\d.,]+)\s*(.*)$/.exec(String(q || '').trim());
|
||||
if (!m) return { miktar: null, birim: String(q || '').trim() };
|
||||
const sayi = parseFloat(m[1].replace(/\./g, '').replace(',', '.'));
|
||||
return { miktar: isFinite(sayi) ? sayi : null, birim: (m[2] || '').trim() };
|
||||
}
|
||||
export const miktarAyristir = quantityParts;
|
||||
|
||||
const kacir = (s) =>
|
||||
String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
||||
@@ -73,6 +69,8 @@ const STIL = `
|
||||
.ust div { margin: .6mm 0; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 3mm 0; font-size: 9.5pt; }
|
||||
th, td { border: 1px solid #444; padding: 1.6mm 2mm; text-align: left; vertical-align: top; }
|
||||
thead { display: table-header-group; }
|
||||
tr { break-inside: avoid; }
|
||||
th { background: #eee; font-weight: bold; }
|
||||
td.s, th.s { text-align: right; white-space: nowrap; }
|
||||
.toplam td { font-weight: bold; background: #f4f4f4; }
|
||||
@@ -160,10 +158,12 @@ ihale dokümanındaki standart form metniyle karşılaştırınız.</p>`;
|
||||
|
||||
/** Birim fiyat teklif cetveli. */
|
||||
export function teklifCetveliHtml(f, satirlar, toplam) {
|
||||
const errors = validateBidRows(satirlar);
|
||||
if (errors.length) throw new Error(errors.join(" "));
|
||||
const govde = satirlar.map((r, i) => {
|
||||
const { miktar, birim } = miktarAyristir(r.quantity);
|
||||
const bf = Number(r.birimFiyat) || 0;
|
||||
const tutar = (miktar ?? 0) * bf;
|
||||
const tutar = bidRowTotal(r);
|
||||
return `<tr>
|
||||
<td class="s">${i + 1}</td>
|
||||
<td>${kacir(r.name)}</td>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
export function quantityParts(value) {
|
||||
if (typeof value === 'number') return {miktar:Number.isFinite(value) ? value : null,birim:''};
|
||||
const match = /^\s*([+-]?[\d]+(?:[.,]\d+)*)\s+(.+?)\s*$/.exec(String(value || ''));
|
||||
if (!match) return {miktar:null,birim:''};
|
||||
const raw = match[1];
|
||||
const normalized = raw.includes(',') ? raw.replace(/\./g,'').replace(',','.')
|
||||
: /^\d{1,3}(?:\.\d{3})+$/.test(raw) ? raw.replace(/\./g,'') : raw;
|
||||
const number = Number(normalized);
|
||||
return {miktar:Number.isFinite(number) ? number : null,birim:match[2]};
|
||||
}
|
||||
export const roundMoney = n => Math.round((n + Number.EPSILON) * 100) / 100;
|
||||
export function bidRowTotal(row) {
|
||||
const {miktar} = quantityParts(row.quantity);
|
||||
const price = Number(row.birimFiyat);
|
||||
return miktar > 0 && Number.isFinite(price) && price > 0 ? roundMoney(miktar * price) : null;
|
||||
}
|
||||
export function validateBidRows(rows) {
|
||||
if (!rows.length) return ['Resmi teklif cetvelindeki kalemler eksik.'];
|
||||
return rows.flatMap((row,i) => {
|
||||
const {miktar,birim} = quantityParts(row.quantity);
|
||||
return (!row.name?.trim() || !(miktar > 0) || !birim || bidRowTotal(row) === null)
|
||||
? [`${i+1}. kalemin adı, miktarı, birimi veya fiyatı eksik/geçersiz.`] : [];
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user