fix: persist tender preparation and correct calendar export
This commit is contained in:
@@ -102,3 +102,7 @@ export const telegramSil = () => iste('/api/me/telegram', { method: 'DELETE' });
|
||||
// --- Takip bildirimleri (zeyilname / sonuç) ---
|
||||
export const bildirimleriGetir = () => iste('/api/me/bildirimler').then((d) => d.bildirimler || []);
|
||||
export const bildirimOkundu = (idler) => iste('/api/me/bildirimler', { method: 'POST', body: JSON.stringify({ idler }) });
|
||||
|
||||
export const getTenderStates = () => iste('/api/me/tender-state').then(d => d.records || {});
|
||||
export const saveTenderState = (key, patch, snapshot, version) =>
|
||||
iste('/api/me/tender-state', {method:'PUT',body:JSON.stringify({key,patch,snapshot,version})}).then(d => d.record);
|
||||
|
||||
@@ -27,8 +27,9 @@ export function yaziylaTL(n) {
|
||||
if (!isFinite(sayi)) return '';
|
||||
const negatif = sayi < 0;
|
||||
const mutlak = Math.abs(sayi);
|
||||
const tam = Math.floor(mutlak);
|
||||
const kurus = Math.round((mutlak - tam) * 100);
|
||||
const toplamKurus = Math.round((mutlak + Number.EPSILON) * 100);
|
||||
const tam = Math.floor(toplamKurus / 100);
|
||||
const kurus = toplamKurus % 100;
|
||||
|
||||
const gruplar = [];
|
||||
let kalan = tam;
|
||||
@@ -126,7 +127,7 @@ sınırlama yapılmadan bütün koşullarıyla kabul edilmiştir.</p>
|
||||
<p>İhale dokümanında yer alan bütün şartları kabul ederek, söz konusu ${bedelCumle}
|
||||
kabul ve taahhüt ediyoruz.</p>
|
||||
|
||||
<p>Teklifimizin, ihale tarihinden itibaren <strong>${kacir(f.gecerlilikGun || '60')}</strong>
|
||||
<p>Teklifimizin, ihale tarihinden itibaren <strong>${kacir(f.gecerlilikGun || '[DOĞRULAYIN]')}</strong>
|
||||
takvim günü süreyle geçerli olduğunu beyan ediyoruz.</p>
|
||||
|
||||
${f.kisim ? `<p>Teklifimiz <strong>${kacir(f.kisim)}</strong> kısmına/kısımlarına ilişkindir.</p>` : ''}
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function fetchTenderDetail(tenderId) {
|
||||
}
|
||||
|
||||
// Kullanıcının ihale üzerindeki durumunu (kanban, takip vb.) canlı veriyle birleştirir.
|
||||
const USER_FIELDS = ['kanbanStatus', 'takipte', 'incelendi', 'atananPersonel'];
|
||||
const USER_FIELDS = ['kanbanStatus', 'takipte', 'incelendi', 'atananPersonel', 'gorev', 'notes', 'checklist', 'bidDraft'];
|
||||
|
||||
export function mergeUserState(liveTenders, previousTenders) {
|
||||
const prevById = new Map((previousTenders || []).map((t) => [t.id, t]));
|
||||
@@ -30,7 +30,7 @@ export function mergeUserState(liveTenders, previousTenders) {
|
||||
if (!prev) return tender;
|
||||
const merged = { ...tender };
|
||||
for (const f of USER_FIELDS) {
|
||||
if (prev[f] !== undefined && prev[f] !== null && prev[f] !== false) merged[f] = prev[f];
|
||||
if (prev[f] !== undefined) merged[f] = prev[f];
|
||||
}
|
||||
if (prev.aciklama && !merged.aciklama) merged.aciklama = prev.aciklama;
|
||||
return merged;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Calendar dates in Turkish procurement announcements are Europe/Istanbul (+03:00).
|
||||
// Never manufacture a deadline when the source omitted its time.
|
||||
export function tenderDeadline(t) {
|
||||
const source = String(t.aciklama || '');
|
||||
const date = /^(\d{2})\.(\d{2})\.(\d{4})/.exec(String(t.tarih || ''));
|
||||
if (!date) throw new Error('İhale tarihi doğrulanamadı; takvime eklenemedi.');
|
||||
const [,day,month,year] = date;
|
||||
const time = /^(\d{2}):(\d{2})/.exec(String(t.saat || '').trim())
|
||||
|| new RegExp(`${day}\\.${month}\\.${year}\\s*[-–]?\\s*(\\d{2}):(\\d{2})`).exec(source);
|
||||
if (!time) throw new Error('İhale saati kaynakta bulunamadı; önce tarih ve saati doğrulayın.');
|
||||
const hour = Number(time[1]), minute = Number(time[2]);
|
||||
const calendarDate = new Date(Date.UTC(Number(year), Number(month)-1, Number(day)));
|
||||
if (hour > 23 || minute > 59 || calendarDate.getUTCFullYear() !== Number(year)
|
||||
|| calendarDate.getUTCMonth()+1 !== Number(month) || calendarDate.getUTCDate() !== Number(day))
|
||||
throw new Error('Geçersiz ihale tarihi veya saati.');
|
||||
return new Date(Date.UTC(Number(year),Number(month)-1,Number(day),hour-3,minute));
|
||||
}
|
||||
const escapeICS = v => String(v || '').replace(/\\/g,'\\\\').replace(/\r?\n/g,'\\n').replace(/;/g,'\\;').replace(/,/g,'\\,');
|
||||
const stamp = date => date.toISOString().replace(/[-:]/g,'').replace(/\.\d{3}Z$/,'Z');
|
||||
function fold(line) {
|
||||
const result = []; let current = '';
|
||||
for (const char of line) {
|
||||
if (new TextEncoder().encode(current + char).length > 75) { result.push(current); current = ' '; }
|
||||
current += char;
|
||||
}
|
||||
result.push(current); return result.join('\r\n');
|
||||
}
|
||||
export function createTenderICS(t, now = new Date()) {
|
||||
const start = tenderDeadline(t);
|
||||
const uid = encodeURIComponent(String(t.ikn || t.id || '').trim());
|
||||
if (!uid) throw new Error('İhale kimliği gerekli.');
|
||||
return ['BEGIN:VCALENDAR','VERSION:2.0','PRODID:-//Ihaleist//Tender Calendar//TR','CALSCALE:GREGORIAN',
|
||||
'BEGIN:VEVENT',`UID:${uid}@ihaleist.com`,`DTSTAMP:${stamp(now)}`,
|
||||
`DTSTART:${stamp(start)}`,`DTEND:${stamp(start)}`,
|
||||
`SUMMARY:${escapeICS('İhale son teklif: '+t.baslik)}`,
|
||||
`DESCRIPTION:${escapeICS('İKN: '+t.ikn+' — '+t.kurum)}`,
|
||||
'END:VEVENT','END:VCALENDAR',''].map(fold).join('\r\n');
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export const tenderStateKey = t => String(t?.ikn || t?.id || '').trim();
|
||||
export const USER_STATE_FIELDS = ['takipte', 'incelendi', 'kanbanStatus', 'atananPersonel', 'gorev', 'notes', 'checklist', 'bidDraft'];
|
||||
export function applyTenderStates(tenders, records = {}) {
|
||||
const seen = new Set();
|
||||
const result = tenders.map(t => {
|
||||
const key = tenderStateKey(t); seen.add(key);
|
||||
const record = records[key];
|
||||
return record ? { ...t, ...record.data } : t;
|
||||
});
|
||||
for (const [key, record] of Object.entries(records)) {
|
||||
if (!seen.has(key) && record.snapshot && (record.data.takipte || record.data.gorev))
|
||||
result.push({ ...record.snapshot, ...record.data });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export function tenderSnapshot(t) {
|
||||
return Object.fromEntries(['id','ikn','baslik','kurum','tarih','saat','sehir','alimTuru'].map(k => [k,t[k] ?? '']));
|
||||
}
|
||||
Reference in New Issue
Block a user