feat: version tender calendar events

This commit is contained in:
2026-09-06 16:21:04 +03:00
parent fc4825e2f8
commit 835e66405a
3 changed files with 18 additions and 3 deletions
+6
View File
@@ -1264,3 +1264,9 @@ Taslak türü seçicisine erişilebilir ad eklendi. Üretim derlemesi geçti. Ö
- Eksiklere geçiş düğmeleri aynı veri ve olay işleyicilerini kullanıyor. Tablo/satır/hücre rolleri ve sütun başlıkları korunuyor; mobil görsel alan etiketleri ekran okuyucu için tekrar edilmiyor. Uzun kesintisiz metinler satıra bölünebiliyor.
- Build ve diff kontrolü başarılı. 390/768/1440 piksel tarayıcı görsel kabulü, klavye ve gerçek ekran okuyucu doğrulaması henüz yapılmadı. Bu düzenleme tüm mobil kabulün geçtiği anlamına gelmez. Genel plan açık.
- QA düzeneğine aynı özetin 390/768/1440 içerik genişliğiyle açılabildiği bir görünüm yolu eklendi ve uzun başlık/atanan sorumlu verisiyle hedef bağlantısı ölçüldü. Uygulama içi tarayıcı sekmesinin görünüm alanı yeniden boyutlandırılamadığı için bu ölçüm gerçek mobil viewport kabulü sayılmadı; mobil doğrulama hâlâ açık.
### 2026-09-06 — ICS kaynak sürümü
- D02 kapsamında ICS olayına `SEQUENCE` eklendi. Değer, doğrulanmış `icsSequence` veya doküman sürümünden güvenli bir negatif olmayan tamsayı olarak alınır; geçersiz değerler sıfıra düşer. İKN tabanlı UID sabit kalır, tarih değişiminde istemcilerin aynı etkinliği güncellemesi mümkün olur.
- Açıklamadaki “otomatik güncellenmez” ifadesi, kaynak sürümü ve resmi doğrulama gereğini belirten güncelleme yönlendirmesiyle değiştirildi. ICS’nin kendisi hâlâ resmi takvim servisine otomatik gönderim yapmaz.
- Regresyon testi UID sürekliliğini ve 3→4 sürüm artışını doğruluyor. 278 test ve build başarılı. Google/Outlook gerçek test takvimlerine aktarım henüz yapılmadı; genel plan açık.
+4 -2
View File
@@ -29,11 +29,13 @@ 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.');
const rawSequence=Number(t.icsSequence ?? t.documentVersion ?? 0);
const sequence=Number.isSafeInteger(rawSequence)&&rawSequence>=0 ? rawSequence : 0;
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)}`,'TRANSP:TRANSPARENT',
`SEQUENCE:${sequence}`,`DTSTART:${stamp(start)}`,'TRANSP:TRANSPARENT',
`SUMMARY:${escapeICS('İhale son teklif: '+t.baslik)}`,
`DESCRIPTION:${escapeICS('İKN: '+t.ikn+' — '+t.kurum+'\nBu tek seferlik takvim kaydı otomatik güncellenmez. İhale tarihini resmi kaynaktan doğrulayın; değişiklikte takviminizdeki eski kaydı güncelleyin.')}`,
`DESCRIPTION:${escapeICS('İKN: '+t.ikn+' — '+t.kurum+'\nKaynak sürümünü ve ihale tarihini resmi kaynaktan doğrulayın; zeyilname veya tarih değişikliğinde aynı UID ve artan sürümle takvimi güncelleyin.')}`,
'END:VEVENT','END:VCALENDAR',''].map(fold).join('\r\n');
}
+8 -1
View File
@@ -55,11 +55,18 @@ test('deadline ICS is a transparent instant, preserves UID across date changes a
assert.match(changed,/DTSTART:20260912T113000Z/);
assert.doesNotMatch(changed,/DTEND:|DURATION:/);
assert.match(changed,/TRANSP:TRANSPARENT/);
assert.match(changed.replace(/\r\n /g,''),/otomatik güncellenmez/);
assert.match(changed.replace(/\r\n /g,''),/artan sürümle takvimi güncelle/);
const hostile=createTenderICS({...t,baslik:'A\rEND:VEVENT\rBEGIN:VEVENT\nB'});
assert.equal(hostile.split('\r\n').filter(line=>line==='BEGIN:VEVENT').length,1);
assert.equal(hostile.split('\r\n').filter(line=>line==='END:VEVENT').length,1);
});
test('ICS exposes a stable event version for calendar updates',()=>{
const first=createTenderICS({...t,documentVersion:3},new Date('2026-09-01T00:00:00Z'));
const changed=createTenderICS({...t,tarih:'12.09.2026',saat:'14:30',documentVersion:4},new Date('2026-09-02T00:00:00Z'));
assert.match(first,/SEQUENCE:3/); assert.match(changed,/SEQUENCE:4/);
assert.equal(first.match(/UID:[^\r\n]+/)[0],changed.match(/UID:[^\r\n]+/)[0]);
assert.match(changed,/art%C4%B1?an|artan sürüm|artan/);
});
test('calendar agenda sorts known times before unknown and preserves undated records',async()=>{
const {calendarAgenda}=await import('../src/services/tenderCalendar.js');