update tender form
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
-- Convert leave start/end columns from DATE to TIMESTAMP(3)
|
||||
-- so that HOURLY leaves can store the actual start/end time of day.
|
||||
-- Without this, Postgres truncates the time and every hourly leave
|
||||
-- renders at the company timezone offset (e.g. 03:00) instead of the
|
||||
-- chosen time.
|
||||
|
||||
ALTER TABLE "leaves"
|
||||
ALTER COLUMN "startDate" TYPE TIMESTAMP(3) USING "startDate"::timestamp(3),
|
||||
ALTER COLUMN "endDate" TYPE TIMESTAMP(3) USING "endDate"::timestamp(3);
|
||||
@@ -81,11 +81,11 @@ router.post(
|
||||
[
|
||||
body('issuingBodyName').optional().trim(),
|
||||
body('title').optional().trim(),
|
||||
body('tenderNumber').optional().trim(),
|
||||
body('termsValue').optional().isNumeric(),
|
||||
body('bondValue').optional().isNumeric(),
|
||||
body('announcementDate').optional().isISO8601(),
|
||||
body('closingDate').optional().isISO8601(),
|
||||
body('tenderNumber').optional({ checkFalsy: true }).trim(),
|
||||
body('termsValue').optional({ checkFalsy: false }).isNumeric(),
|
||||
body('bondValue').optional({ checkFalsy: false }).isNumeric(),
|
||||
body('announcementDate').optional({ checkFalsy: true }).isISO8601(),
|
||||
body('closingDate').optional({ checkFalsy: true }).isISO8601(),
|
||||
],
|
||||
validate,
|
||||
tendersController.checkDuplicates
|
||||
@@ -101,14 +101,14 @@ router.post(
|
||||
'/',
|
||||
authorize('tenders', 'tenders', 'create'),
|
||||
[
|
||||
body('tenderNumber').notEmpty().trim(),
|
||||
body('tenderNumber').optional({ checkFalsy: true }).trim(),
|
||||
body('issueNumber').optional().trim(),
|
||||
body('issuingBodyName').notEmpty().trim(),
|
||||
body('title').notEmpty().trim(),
|
||||
body('termsValue').isNumeric(),
|
||||
body('bondValue').isNumeric(),
|
||||
body('announcementDate').isISO8601(),
|
||||
body('closingDate').isISO8601(),
|
||||
body('termsValue').optional({ checkFalsy: false }).isNumeric(),
|
||||
body('bondValue').optional({ checkFalsy: false }).isNumeric(),
|
||||
body('announcementDate').optional({ checkFalsy: true }).isISO8601(),
|
||||
body('closingDate').optional({ checkFalsy: true }).isISO8601(),
|
||||
body('source').notEmpty(),
|
||||
body('announcementType').notEmpty(),
|
||||
],
|
||||
|
||||
@@ -35,7 +35,7 @@ const DIRECTIVE_TYPE_VALUES = [
|
||||
export interface CreateTenderData {
|
||||
issuingBodyName: string;
|
||||
title: string;
|
||||
tenderNumber: string;
|
||||
tenderNumber?: string;
|
||||
issueNumber?: string;
|
||||
|
||||
termsValue: number;
|
||||
@@ -49,8 +49,8 @@ export interface CreateTenderData {
|
||||
siteVisitLocation?: string;
|
||||
termsPickupProvince?: string;
|
||||
|
||||
announcementDate: string;
|
||||
closingDate: string;
|
||||
announcementDate?: string;
|
||||
closingDate?: string;
|
||||
announcementLink?: string;
|
||||
|
||||
source: string;
|
||||
@@ -327,16 +327,33 @@ private getEffectiveTenderStatus(tender: {
|
||||
async create(data: CreateTenderData, userId: string): Promise<TenderWithDuplicates> {
|
||||
const possibleDuplicates = await this.findPossibleDuplicates(data);
|
||||
|
||||
const existing = await prisma.tender.findUnique({
|
||||
where: { tenderNumber: data.tenderNumber.trim() },
|
||||
});
|
||||
if (existing) {
|
||||
throw new AppError(400, 'Tender number already exists - رقم المناقصة موجود مسبقاً');
|
||||
// رقم المناقصة اختياري في الواجهة، لكن العمود في قاعدة البيانات مطلوب وفريد.
|
||||
// إذا تُرك فاضياً نولّد رقماً تلقائياً حتى لا يفشل الحفظ ولا نحتاج migration.
|
||||
let tenderNumber = data.tenderNumber?.trim() || '';
|
||||
if (tenderNumber) {
|
||||
const existing = await prisma.tender.findUnique({
|
||||
where: { tenderNumber },
|
||||
});
|
||||
if (existing) {
|
||||
throw new AppError(400, 'Tender number already exists - رقم المناقصة موجود مسبقاً');
|
||||
}
|
||||
} else {
|
||||
// توليد رقم تلقائي فريد مع تفادي التعارض النادر
|
||||
tenderNumber = await this.generateTenderNumber();
|
||||
let guard = 0;
|
||||
while (await prisma.tender.findUnique({ where: { tenderNumber } })) {
|
||||
tenderNumber = `${await this.generateTenderNumber()}-${++guard}`;
|
||||
if (guard > 5) {
|
||||
tenderNumber = `TND-${Date.now()}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tenderNumber = data.tenderNumber.trim();
|
||||
const announcementDate = new Date(data.announcementDate);
|
||||
const closingDate = new Date(data.closingDate);
|
||||
// تاريخ الإعلان/الإغلاق اختياريان في الواجهة، والعمودان مطلوبان في قاعدة البيانات.
|
||||
// إذا تُركا فاضيين نستخدم تاريخ اليوم افتراضياً.
|
||||
const announcementDate = data.announcementDate ? new Date(data.announcementDate) : new Date();
|
||||
const closingDate = data.closingDate ? new Date(data.closingDate) : new Date();
|
||||
|
||||
if (data.siteVisitRequired && !data.siteVisitLocation?.trim()) {
|
||||
throw new AppError(400, 'مكان زيارة الموقع مطلوب عند اختيار زيارة موقع إجبارية');
|
||||
@@ -357,7 +374,7 @@ private getEffectiveTenderStatus(tender: {
|
||||
issueNumber: data.issueNumber?.trim() || null,
|
||||
issuingBodyName: data.issuingBodyName.trim(),
|
||||
title: data.title.trim(),
|
||||
termsValue: data.termsValue,
|
||||
termsValue: Number(data.termsValue ?? 0),
|
||||
bondValue: Number(data.initialBondValue ?? data.bondValue ?? 0),
|
||||
announcementDate,
|
||||
closingDate,
|
||||
@@ -507,8 +524,16 @@ private getEffectiveTenderStatus(tender: {
|
||||
if (data.bondValue !== undefined || data.initialBondValue !== undefined) {
|
||||
updateData.bondValue = Number(data.initialBondValue ?? data.bondValue ?? existing.bondValue);
|
||||
}
|
||||
if (data.announcementDate !== undefined) updateData.announcementDate = new Date(data.announcementDate);
|
||||
if (data.closingDate !== undefined) updateData.closingDate = new Date(data.closingDate);
|
||||
// العمود مطلوب في قاعدة البيانات: نتجاهل القيمة الفاضية ونبقي على القيمة الحالية بدل كتابة null.
|
||||
if (data.tenderNumber !== undefined && data.tenderNumber?.trim()) {
|
||||
updateData.tenderNumber = data.tenderNumber.trim();
|
||||
}
|
||||
if (data.announcementDate !== undefined && data.announcementDate) {
|
||||
updateData.announcementDate = new Date(data.announcementDate);
|
||||
}
|
||||
if (data.closingDate !== undefined && data.closingDate) {
|
||||
updateData.closingDate = new Date(data.closingDate);
|
||||
}
|
||||
if (data.announcementLink !== undefined) updateData.announcementLink = data.announcementLink?.trim() || null;
|
||||
if (data.source !== undefined) updateData.source = data.source;
|
||||
if (data.sourceOther !== undefined) updateData.sourceOther = data.sourceOther?.trim() || null;
|
||||
|
||||
Reference in New Issue
Block a user