fix: bind bid prices and review to source content
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { bidRowTotal, validateBidRows, roundMoney } from '../services/bidValidation';
|
||||
import { sourceRows, bidSourceSnapshot, restoreBidPrices } from '../services/bidSource';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import {
|
||||
FolderCheck, ChevronDown, ChevronUp, FileSignature, Table2, ListChecks,
|
||||
@@ -58,10 +59,12 @@ export const BidPackageBuilder = ({ results, tender }) => {
|
||||
const [kisim, setKisim] = useState(storedDraft.kisim || '');
|
||||
const [goturuBedel, setGoturuBedel] = useState(storedDraft.goturuBedel || '');
|
||||
|
||||
const kalemler = tender?.items?.length
|
||||
? tender.items.map(k => ({name:k.ad,quantity:`${k.miktar} ${k.birim || ''}`,keySpecs:[]}))
|
||||
: (Array.isArray(results?.itemsAndSpecs) ? results.itemsAndSpecs : []);
|
||||
const [fiyatlar, setFiyatlar] = useState(storedDraft.fiyatlar || {});
|
||||
const kalemler = sourceRows(tender, results);
|
||||
const sourceSnapshot = bidSourceSnapshot(tender, results, kalemler);
|
||||
const [reviewedSource, setReviewedSource] = useState(storedDraft.reviewedSource || null);
|
||||
const [fiyatlar, setFiyatlar] = useState(() => restoreBidPrices(storedDraft));
|
||||
const kaynakKontrolGerekli = reviewedSource !== sourceSnapshot;
|
||||
const eskiFiyatlar = storedDraft.priceSchema !== 1 && Object.keys(storedDraft.fiyatlar || {}).length > 0;
|
||||
|
||||
const alanYaz = (k, v) => {
|
||||
const yeni = { ...firma, [k]: v };
|
||||
@@ -70,13 +73,13 @@ export const BidPackageBuilder = ({ results, tender }) => {
|
||||
};
|
||||
|
||||
const satirlar = useMemo(
|
||||
() => kalemler.map((k, i) => ({ ...k, birimFiyat: fiyatlar[i] })),
|
||||
() => kalemler.map(k => ({ ...k, birimFiyat: fiyatlar[k.priceKey] })),
|
||||
[kalemler, fiyatlar],
|
||||
);
|
||||
|
||||
const kalemToplami = useMemo(() => roundMoney(satirlar.reduce((t,r) => t + (bidRowTotal(r) ?? 0),0)), [satirlar]);
|
||||
const saveDraft = async () => {
|
||||
const saved = await mutateTender(tender.id, {bidDraft:{firma,teklifTuru,gecerlilikGun,kisim,goturuBedel,fiyatlar}});
|
||||
const saved = await mutateTender(tender.id, {bidDraft:{firma,teklifTuru,gecerlilikGun,kisim,goturuBedel,priceSchema:1,pricesBySource:fiyatlar,sourceSnapshot,reviewedSource}});
|
||||
if (saved) { setDirty(false); setHata(null); }
|
||||
else setHata('Taslak kaydedilemedi. Tekrar deneyin.');
|
||||
};
|
||||
@@ -87,7 +90,7 @@ export const BidPackageBuilder = ({ results, tender }) => {
|
||||
const kalemEksik = teklifTuru === 'birim' && validateBidRows(satirlar).length > 0;
|
||||
const turCeliskisi = teklifTuru === 'goturu' && /birim fiyat/i.test(tender?.aciklama || '');
|
||||
const sureEksik = !(Number(gecerlilikGun) > 0) || (kaynakGecerlilik && Number(gecerlilikGun) < Number(kaynakGecerlilik[1]));
|
||||
const hazir = eksikler.length === 0 && toplam > 0 && !kalemEksik && !turCeliskisi && !sureEksik;
|
||||
const hazir = eksikler.length === 0 && toplam > 0 && !kalemEksik && !turCeliskisi && !sureEksik && !kaynakKontrolGerekli;
|
||||
|
||||
const formVerisi = () => ({
|
||||
...firma,
|
||||
@@ -142,6 +145,19 @@ export const BidPackageBuilder = ({ results, tender }) => {
|
||||
<span role="status" className="text-xs">{dirty ? 'Kaydedilmemiş değişiklikler var' : storedDraft.firma ? 'Sunucuda kayıtlı taslak' : 'Henüz kaydedilmedi'}</span>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-amber-300 bg-amber-50 p-3 space-y-2">
|
||||
{kaynakKontrolGerekli && <p role="alert" className="text-xs text-amber-900">
|
||||
{reviewedSource ? 'İlan, kalem veya belge bilgisi değişti. Önceki kontrol bu sürüm için geçerli değil.' : 'Kalemler ve teklif koşulları resmi dokümanla henüz karşılaştırılmadı.'}
|
||||
{' '}Kaynağı kontrol etmeden teklif çıktısı oluşturulamaz.
|
||||
</p>}
|
||||
{eskiFiyatlar && <p className="text-xs text-amber-900">Eski taslak fiyatları satır sırasına göre kaydedilmiş. Yanlış ürüne aktarılmaması için fiyatları yeniden girin.</p>}
|
||||
<label className="flex items-start gap-2 text-xs text-slate-800">
|
||||
<input type="checkbox" checked={!kaynakKontrolGerekli}
|
||||
onChange={e => setReviewedSource(e.target.checked ? sourceSnapshot : null)} />
|
||||
Bu sürümün kalemlerini, miktarlarını, teklif türünü ve geçerlilik süresini resmi dokümanla karşılaştırdım.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Sizden beklenen bilgiler */}
|
||||
{eksikler.length > 0 && (
|
||||
<div className="bg-amber-50 border border-amber-300 rounded-xl p-3.5 flex gap-2.5">
|
||||
@@ -245,16 +261,16 @@ export const BidPackageBuilder = ({ results, tender }) => {
|
||||
const { miktar, birim } = miktarAyristir(r.quantity);
|
||||
const tutar = bidRowTotal(r);
|
||||
return (
|
||||
<tr key={i} className="border-b border-slate-100">
|
||||
<tr key={r.priceKey} className="border-b border-slate-100">
|
||||
<td className="py-1.5 pr-2 text-slate-800">{r.name || '—'}</td>
|
||||
<td className="py-1.5 text-right text-slate-600 whitespace-nowrap">
|
||||
{miktar ?? '—'} {birim}
|
||||
</td>
|
||||
<td className="py-1.5 text-right">
|
||||
<input
|
||||
value={fiyatlar[i] ?? ''}
|
||||
value={fiyatlar[r.priceKey] ?? ''}
|
||||
onChange={(e) =>
|
||||
setFiyatlar((o) => ({ ...o, [i]: e.target.value.replace(/[^\d.]/g, '') }))}
|
||||
setFiyatlar((o) => ({ ...o, [r.priceKey]: e.target.value.replace(/[^\d.]/g, '') }))}
|
||||
placeholder={r.estimatedUnitPrice != null ? String(r.estimatedUnitPrice) : '0'}
|
||||
className="w-28 text-xs text-right px-2 py-1 rounded border border-slate-300"
|
||||
/>
|
||||
|
||||
@@ -323,7 +323,7 @@ export const DeepAnalysisReport = ({ results, tender }) => {
|
||||
)}
|
||||
|
||||
{/* Analiz ve yol haritası okunduktan sonraki adım: teklif zarfını hazırla */}
|
||||
<BidPackageBuilder results={results} tender={tender} />
|
||||
<BidPackageBuilder key={tender?.ikn || tender?.id} results={results} tender={tender} />
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user