From 31c59a3c9f9a82caf7ee57a472651da082a4b3dc Mon Sep 17 00:00:00 2001 From: Aya Date: Thu, 7 May 2026 16:16:31 +0300 Subject: [PATCH] edit status of tenders --- .../src/modules/tenders/tenders.service.ts | 69 +++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/backend/src/modules/tenders/tenders.service.ts b/backend/src/modules/tenders/tenders.service.ts index cfe3798..7dc3dc8 100644 --- a/backend/src/modules/tenders/tenders.service.ts +++ b/backend/src/modules/tenders/tenders.service.ts @@ -86,8 +86,52 @@ class TendersService { return `TND-${year}-${seq}`; } - private readonly EXTRA_META_START = '[EXTRA_TENDER_META]'; + private readonly EXTRA_META_START = '[EXTRA_TENDER_META]'; private readonly EXTRA_META_END = '[/EXTRA_TENDER_META]'; + private getCompanyTodayDate(): Date { + const parts = new Intl.DateTimeFormat('en-US', { + timeZone: 'Asia/Riyadh', + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).formatToParts(new Date()); + + const year = parts.find((p) => p.type === 'year')?.value; + const month = parts.find((p) => p.type === 'month')?.value; + const day = parts.find((p) => p.type === 'day')?.value; + + return new Date(`${year}-${month}-${day}T00:00:00.000Z`); +} + +private toDateOnly(value: Date | string | null | undefined): Date | null { + if (!value) return null; + + const date = value instanceof Date ? value : new Date(value); + + if (Number.isNaN(date.getTime())) return null; + + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, '0'); + const day = String(date.getUTCDate()).padStart(2, '0'); + + return new Date(`${year}-${month}-${day}T00:00:00.000Z`); +} + +private getEffectiveTenderStatus(tender: { + status?: string | null; + closingDate?: Date | string | null; +}) { + if (tender.status === 'ACTIVE') { + const closingDate = this.toDateOnly(tender.closingDate); + const today = this.getCompanyTodayDate(); + + if (closingDate && closingDate < today) { + return 'EXPIRED'; + } + } + + return tender.status || 'ACTIVE'; +} private extractTenderExtraMeta(notes?: string | null) { if (!notes) { @@ -208,11 +252,17 @@ class TendersService { return cleanedNotes ? `${cleanedNotes}\n${metaBlock}` : metaBlock; } - private mapTenderExtraFields(tender: T) { - const { cleanNotes, meta } = this.extractTenderExtraMeta(tender.notes); + private mapTenderExtraFields(tender: T) { + const { cleanNotes, meta } = this.extractTenderExtraMeta(tender.notes); return { ...tender, + status: this.getEffectiveTenderStatus(tender), notes: cleanNotes || null, initialBondValue: meta.initialBondValue ?? Number(tender.bondValue ?? 0), finalBondValue: meta.finalBondValue ?? null, @@ -345,7 +395,15 @@ class TendersService { { issuingBodyName: { contains: filters.search, mode: 'insensitive' } }, ]; } - if (filters.status) where.status = filters.status; + if (filters.status === 'EXPIRED') { + where.status = 'ACTIVE'; + where.closingDate = { lt: this.getCompanyTodayDate() }; + } else if (filters.status === 'ACTIVE') { + where.status = 'ACTIVE'; + where.closingDate = { gte: this.getCompanyTodayDate() }; + } else if (filters.status) { + where.status = filters.status; + } if (filters.source) where.source = filters.source; if (filters.announcementType) where.announcementType = filters.announcementType; @@ -624,6 +682,9 @@ class TendersService { if (tender.status === 'CONVERTED_TO_DEAL') { throw new AppError(400, 'Tender already converted to deal'); } + if (this.getEffectiveTenderStatus(tender) === 'EXPIRED') { + throw new AppError(400, 'Cannot convert expired tender to deal'); + } const pipeline = await prisma.pipeline.findUnique({ where: { id: data.pipelineId },