Unify and validate bid totals across screen and exports

This commit is contained in:
2026-09-06 13:16:49 +03:00
parent 6fd6ac04f4
commit 3096ca45cd
6 changed files with 72 additions and 8 deletions
+7 -1
View File
@@ -1,4 +1,4 @@
import { quantityParts, parseBidPrice, bidRowTotal, validateBidRows } from './bidValidation.js';
import { quantityParts, parseBidPrice, bidRowTotal, validateBidRows, bidTotal, bidAmount } from './bidValidation.js';
/**
* İhale dosyası belgeleri — teklif mektubu, birim fiyat teklif cetveli ve
* evrak kontrol listesi.
@@ -112,6 +112,9 @@ function ustBilgi(f) {
/** KİK birim fiyat / götürü bedel teklif mektubu metni. */
export function teklifMektubuHtml(f, toplam) {
const amount = bidAmount(toplam);
if (amount === null) throw new Error('Teklif toplamı geçersiz veya güvenli hesaplama sınırını aşıyor.');
toplam = amount;
const goturu = f.teklifTuru === 'goturu';
const tur = goturu ? 'GÖTÜRÜ BEDEL TEKLİF MEKTUBU' : 'BİRİM FİYAT TEKLİF MEKTUBU';
const bedelCumle = goturu
@@ -162,6 +165,9 @@ ihale dokümanındaki standart form metniyle karşılaştırınız.</p>`;
export function teklifCetveliHtml(f, satirlar, toplam) {
const errors = validateBidRows(satirlar);
if (errors.length) throw new Error(errors.join(" "));
const calculated = bidTotal(satirlar);
if (calculated === null) throw new Error('Teklif toplamı geçersiz veya güvenli hesaplama sınırını aşıyor.');
if (toplam !== calculated) throw new Error('Teklif toplamı satır tutarlarıyla uyuşmuyor. Cetveli yeniden hesaplayın.');
const govde = satirlar.map((r, i) => {
const { miktar, birim } = miktarAyristir(r.quantity);
const bf = parseBidPrice(r.birimFiyat);
+18
View File
@@ -34,3 +34,21 @@ export function validateBidRows(rows) {
? [`${i+1}. kalemin adı, miktarı, birimi veya fiyatı eksik/geçersiz.`] : [];
});
}
// Sum rounded row amounts in integer kuruş so every output uses the same total.
export function bidTotal(rows) {
if (validateBidRows(rows).length) return null;
let cents = 0;
for (const row of rows) {
const rowCents = Math.round(bidRowTotal(row) * 100);
if (!Number.isSafeInteger(rowCents) || !Number.isSafeInteger(cents + rowCents)) return null;
cents += rowCents;
}
return cents > 0 ? cents / 100 : null;
}
export function bidAmount(value) {
const parsed = parseBidPrice(value);
if (parsed === null) return null;
const cents = Math.round((parsed + Number.EPSILON) * 100);
return Number.isSafeInteger(cents) && cents > 0 ? cents / 100 : null;
}
+3 -2
View File
@@ -1,7 +1,9 @@
import {validateBidRows,quantityParts,parseBidPrice,bidRowTotal,roundMoney} from './bidValidation.js';
import {validateBidRows,quantityParts,parseBidPrice,bidRowTotal,bidTotal} from './bidValidation.js';
export async function createBidWorkbook(form,rows) {
const errors=validateBidRows(rows);
if(errors.length) throw new Error(errors.join(' '));
const total=bidTotal(rows);
if(total === null) throw new Error('Teklif toplamı geçersiz veya güvenli hesaplama sınırını aşıyor.');
const {default:ExcelJS}=await import('exceljs');
const book=new ExcelJS.Workbook();
book.creator='İhaleist';
@@ -18,7 +20,6 @@ export async function createBidWorkbook(form,rows) {
const number=7+i;
sheet.addRow([i+1,r.name,miktar,birim,parseBidPrice(r.birimFiyat),{formula:`ROUND(C${number}*E${number},2)`,result:bidRowTotal(r)}]);
});
const total=roundMoney(rows.reduce((sum,row)=>sum+bidRowTotal(row),0));
sheet.addRow(['','GENEL TOPLAM (KDV hariç)','','','',{formula:`SUM(F7:F${rows.length+6})`,result:total}]);
sheet.getColumn(3).numFmt='#,##0.########';sheet.getColumn(5).numFmt='#,##0.00######';sheet.getColumn(6).numFmt='#,##0.00';
sheet.views=[{state:'frozen',ySplit:6}];