add supplier management module
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import prisma from '../../config/database';
|
||||
import { AppError } from '../../shared/middleware/errorHandler';
|
||||
import { AuditLogger } from '../../shared/utils/auditLogger';
|
||||
import { notificationsService } from '../notifications/notifications.service';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import path from 'path';
|
||||
import fs from 'fs'
|
||||
@@ -209,25 +208,11 @@ class TendersService {
|
||||
return cleanedNotes ? `${cleanedNotes}\n${metaBlock}` : metaBlock;
|
||||
}
|
||||
|
||||
private getComputedTenderStatus<T extends { status?: string | null; closingDate?: Date | string | null }>(tender: T) {
|
||||
if (tender.status === 'CONVERTED_TO_DEAL' || tender.status === 'CANCELLED') {
|
||||
return tender.status;
|
||||
}
|
||||
|
||||
if (tender.closingDate && new Date(tender.closingDate) < new Date()) {
|
||||
return 'EXPIRED';
|
||||
}
|
||||
|
||||
return tender.status || 'ACTIVE';
|
||||
}
|
||||
|
||||
private mapTenderExtraFields<T extends { notes?: string | null; bondValue?: any; status?: string | null; closingDate?: Date | string | null }>(tender: T) {
|
||||
private mapTenderExtraFields<T extends { notes?: string | null; bondValue?: any }>(tender: T) {
|
||||
const { cleanNotes, meta } = this.extractTenderExtraMeta(tender.notes);
|
||||
|
||||
return {
|
||||
...tender,
|
||||
status: this.getComputedTenderStatus(tender),
|
||||
originalStatus: tender.status,
|
||||
notes: cleanNotes || null,
|
||||
initialBondValue: meta.initialBondValue ?? Number(tender.bondValue ?? 0),
|
||||
finalBondValue: meta.finalBondValue ?? null,
|
||||
@@ -360,9 +345,7 @@ class TendersService {
|
||||
{ issuingBodyName: { contains: filters.search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
if (filters.status && filters.status !== 'EXPIRED') {
|
||||
where.status = filters.status;
|
||||
}
|
||||
if (filters.status) where.status = filters.status;
|
||||
if (filters.source) where.source = filters.source;
|
||||
if (filters.announcementType) where.announcementType = filters.announcementType;
|
||||
|
||||
@@ -378,15 +361,9 @@ class TendersService {
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
const mappedTenders = tenders.map((t) => this.mapTenderExtraFields(t));
|
||||
const filteredTenders =
|
||||
filters.status === 'EXPIRED'
|
||||
? mappedTenders.filter((t: any) => t.status === 'EXPIRED')
|
||||
: mappedTenders;
|
||||
|
||||
return {
|
||||
tenders: filteredTenders,
|
||||
total: filters.status === 'EXPIRED' ? filteredTenders.length : total,
|
||||
return {
|
||||
tenders: tenders.map((t) => this.mapTenderExtraFields(t)),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
};
|
||||
@@ -540,20 +517,20 @@ class TendersService {
|
||||
},
|
||||
});
|
||||
|
||||
const assignedUser = directive.assignedToEmployee?.user;
|
||||
if (assignedUser?.id) {
|
||||
const typeLabel = this.getDirectiveTypeLabel(data.type);
|
||||
|
||||
await notificationsService.notifyMany({
|
||||
userIds: [assignedUser.id],
|
||||
type: 'TENDER_DIRECTIVE_ASSIGNED',
|
||||
title: 'تم إسناد توجيه مناقصة جديد',
|
||||
message: `تم إسناد توجيه "${typeLabel}" لك في المناقصة ${tender.tenderNumber}${data.notes?.trim() ? ` - ${data.notes.trim()}` : ''}`,
|
||||
entityType: 'TENDER',
|
||||
entityId: tender.id,
|
||||
excludeUserIds: [userId],
|
||||
});
|
||||
}
|
||||
const assignedUser = directive.assignedToEmployee?.user;
|
||||
if (assignedUser?.id) {
|
||||
const typeLabel = this.getDirectiveTypeLabel(data.type);
|
||||
await prisma.notification.create({
|
||||
data: {
|
||||
userId: assignedUser.id,
|
||||
type: 'TENDER_DIRECTIVE_ASSIGNED',
|
||||
title: `مهمة مناقصة - Tender task: ${tender.tenderNumber}`,
|
||||
message: `${tender.title} (${tender.tenderNumber}): ${typeLabel}. ${data.notes || ''}`,
|
||||
entityType: 'TENDER_DIRECTIVE',
|
||||
entityId: directive.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'TENDER_DIRECTIVE',
|
||||
@@ -701,17 +678,7 @@ class TendersService {
|
||||
return deal;
|
||||
}
|
||||
|
||||
private decodeUploadedFileName(fileName: string) {
|
||||
if (!fileName) return 'file';
|
||||
|
||||
try {
|
||||
return Buffer.from(fileName, 'latin1').toString('utf8');
|
||||
} catch {
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
|
||||
async uploadTenderAttachment(
|
||||
async uploadTenderAttachment(
|
||||
tenderId: string,
|
||||
file: { path: string; originalname: string; mimetype: string; size: number },
|
||||
userId: string,
|
||||
@@ -719,17 +686,14 @@ class TendersService {
|
||||
) {
|
||||
const tender = await prisma.tender.findUnique({ where: { id: tenderId } });
|
||||
if (!tender) throw new AppError(404, 'Tender not found');
|
||||
|
||||
const fileName = path.basename(file.path);
|
||||
const originalName = this.decodeUploadedFileName(file.originalname);
|
||||
|
||||
const attachment = await prisma.attachment.create({
|
||||
data: {
|
||||
entityType: 'TENDER',
|
||||
entityId: tenderId,
|
||||
tenderId,
|
||||
fileName,
|
||||
originalName,
|
||||
originalName: file.originalname,
|
||||
mimeType: file.mimetype,
|
||||
size: file.size,
|
||||
path: file.path,
|
||||
@@ -737,7 +701,6 @@ class TendersService {
|
||||
uploadedBy: userId,
|
||||
},
|
||||
});
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'TENDER',
|
||||
entityId: tenderId,
|
||||
@@ -745,7 +708,6 @@ class TendersService {
|
||||
userId,
|
||||
changes: { attachmentUploaded: attachment.id },
|
||||
});
|
||||
|
||||
return attachment;
|
||||
}
|
||||
|
||||
@@ -759,12 +721,8 @@ class TendersService {
|
||||
where: { id: directiveId },
|
||||
select: { id: true, tenderId: true },
|
||||
});
|
||||
|
||||
if (!directive) throw new AppError(404, 'Directive not found');
|
||||
|
||||
const fileName = path.basename(file.path);
|
||||
const originalName = this.decodeUploadedFileName(file.originalname);
|
||||
|
||||
const attachment = await prisma.attachment.create({
|
||||
data: {
|
||||
entityType: 'TENDER_DIRECTIVE',
|
||||
@@ -772,7 +730,7 @@ class TendersService {
|
||||
tenderDirectiveId: directiveId,
|
||||
tenderId: directive.tenderId,
|
||||
fileName,
|
||||
originalName,
|
||||
originalName: file.originalname,
|
||||
mimeType: file.mimetype,
|
||||
size: file.size,
|
||||
path: file.path,
|
||||
@@ -780,7 +738,6 @@ class TendersService {
|
||||
uploadedBy: userId,
|
||||
},
|
||||
});
|
||||
|
||||
await AuditLogger.log({
|
||||
entityType: 'TENDER_DIRECTIVE',
|
||||
entityId: directiveId,
|
||||
@@ -788,9 +745,9 @@ class TendersService {
|
||||
userId,
|
||||
changes: { attachmentUploaded: attachment.id },
|
||||
});
|
||||
|
||||
return attachment;
|
||||
}
|
||||
|
||||
async getAttachmentFile(attachmentId: string): Promise<string> {
|
||||
const attachment = await prisma.attachment.findUnique({
|
||||
where: { id: attachmentId },
|
||||
@@ -808,10 +765,12 @@ class TendersService {
|
||||
|
||||
if (!attachment) throw new AppError(404, 'File not found')
|
||||
|
||||
// حذف من الديسك
|
||||
if (attachment.path && fs.existsSync(attachment.path)) {
|
||||
fs.unlinkSync(attachment.path)
|
||||
}
|
||||
|
||||
// حذف من DB
|
||||
await prisma.attachment.delete({
|
||||
where: { id: attachmentId },
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user