Hold stale tender notification retries for review

This commit is contained in:
2026-09-06 13:19:55 +03:00
parent 02625906f2
commit 1fbf77c0ac
6 changed files with 73 additions and 4 deletions
+23 -3
View File
@@ -9,7 +9,7 @@ import {deadlineInfo} from '../src/services/tenderCalendar.js';
* NOTIFY_MAX=25 → e-postada listelenecek en fazla ihale
*/
import { list, markBaseline, reserveDelivery, acceptDelivery, failDelivery, available } from './notify-store.mjs';
import { list, markBaseline, reserveDelivery, acceptDelivery, failDelivery, holdDelivery, available } from './notify-store.mjs';
import { emailConfigured, emailFrom, sendEmail, tenderDigestHtml, escapeHtml } from './email.mjs';
import { telegramConfigured, sendTelegram, tgKacir } from './telegram.mjs';
import { getData } from './user-store.mjs';
@@ -62,13 +62,30 @@ export function bildirilebilir(t,now=new Date()) {
return true;
}
// A retry must preserve its original provider request; stale content needs review,
// not a rewritten payload under the old idempotency key.
export function deliverySourceProblem(delivery,current,clock) {
const pinned = delivery.payload.digest;
if (!Array.isArray(pinned) || pinned.length !== delivery.tenderIds.length)
return 'Gönderim kaynağı doğrulanamadı; otomatik tekrar durduruldu. Yönetici incelemesi gerekli.';
const byId = new Map(current.map(t=>[t.id,t]));
for (let i=0;i<pinned.length;i++) {
const old = pinned[i], fresh = old && byId.get(old.id);
if (!old || old.id !== delivery.tenderIds[i] || !fresh || !bildirilebilir(fresh,clock)
|| !bildirilebilir(old,clock)
|| deadlineInfo(old,clock).deadline?.getTime() !== deadlineInfo(fresh,clock).deadline?.getTime())
return 'İhale kaynağı, son teklif zamanı veya başvuru durumu değişti/doğrulanamıyor. Eski e-posta yeniden gönderilmedi; sağlayıcı kaydı ve güncel ilan için yönetici incelemesi gerekli.';
}
return null;
}
/**
* Tüm aktif abonelikleri işler.
* @param {Function} tenderGetir () => Promise<{tenders: []}>
*/
let processing=false;
export async function bildirimleriIsle(tenderGetir, { zorla = false } = {}, dependencies={}) {
const {readSubscriptions=list,saveBaseline=markBaseline,reserve=reserveDelivery,accepted=acceptDelivery,failed=failDelivery,configured=emailConfigured,
const {readSubscriptions=list,saveBaseline=markBaseline,reserve=reserveDelivery,accepted=acceptDelivery,failed=failDelivery,hold=holdDelivery,configured=emailConfigured,
deliverEmail=sendEmail,telegramReady=telegramConfigured,deliverTelegram=sendTelegram,now=()=>new Date()}=dependencies;
if(processing)return {atlandi:'önceki tur sürüyor'};
processing=true;
@@ -81,7 +98,8 @@ export async function bildirimleriIsle(tenderGetir, { zorla = false } = {}, depe
if (!abonelikler.length) return { abonelik: 0 };
const veri = await tenderGetir();
const tumu = notificationSource(veri).filter(t=>bildirilebilir(t,now()));
const source = notificationSource(veri);
const tumu = source.filter(t=>bildirilebilir(t,now()));
const requested=Number(process.env.NOTIFY_MAX || 25);
const maxAdet=Number.isInteger(requested)&&requested>0?Math.min(requested,200):25;
@@ -125,6 +143,8 @@ export async function bildirimleriIsle(tenderGetir, { zorla = false } = {}, depe
},gonderilecek.map(t=>t.id),{force:zorla});
if(!delivery)continue;
if(delivery.blocked){hata++;continue;}
const sourceProblem=deliverySourceProblem(delivery,source,now());
if(sourceProblem){hold(delivery,sourceProblem);hata++;continue;}
try {
const result=await deliverEmail({...delivery.payload,idempotencyKey:'notify/'+delivery.id});
accepted(delivery,result?.id);