From 8401d22d717ad7818ab80abf95cd98dcca0fb214 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 10 Sep 2025 07:44:28 +0000 Subject: [PATCH] Add functionality to comment on delivery reconciliations Implement CRUD operations for reconciliation comments, including fetching comments with related data like author, group, and delivery details, and creating new comments. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 95e8aab4-f39f-4b48-aa57-a759ec15ba3d Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/95e8aab4-f39f-4b48-aa57-a759ec15ba3d/BOWXK9F --- server/storage.ts | 154 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/server/storage.ts b/server/storage.ts index 8df1c08..7be5426 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -14,6 +14,7 @@ import { announcements, nocodbConfig, invoiceVerificationCache, + reconciliationComments, savTickets, savTicketHistory, weatherData, @@ -52,6 +53,9 @@ import { type Announcement, type InsertAnnouncement, type AnnouncementWithRelations, + type ReconciliationComment, + type InsertReconciliationComment, + type ReconciliationCommentWithRelations, type NocodbConfig, type InsertNocodbConfig, type InvoiceVerificationCache, @@ -248,6 +252,12 @@ export interface IStorage { getWebhookBapConfig(): Promise; createWebhookBapConfig(config: InsertWebhookBapConfig): Promise; updateWebhookBapConfig(id: number, config: Partial): Promise; + + // Reconciliation Comments operations + getReconciliationComments(deliveryId: number): Promise; + createReconciliationComment(comment: InsertReconciliationComment): Promise; + updateReconciliationComment(id: number, comment: Partial): Promise; + deleteReconciliationComment(id: number): Promise; } export class DatabaseStorage implements IStorage { @@ -2566,6 +2576,83 @@ export class DatabaseStorage implements IStorage { .returning(); return config; } + + // Reconciliation Comments operations + async getReconciliationComments(deliveryId: number): Promise { + const comments = await db + .select({ + id: reconciliationComments.id, + deliveryId: reconciliationComments.deliveryId, + groupId: reconciliationComments.groupId, + content: reconciliationComments.content, + type: reconciliationComments.type, + authorId: reconciliationComments.authorId, + createdAt: reconciliationComments.createdAt, + updatedAt: reconciliationComments.updatedAt, + author: users, + group: groups, + delivery: { + id: deliveries.id, + orderId: deliveries.orderId, + supplierId: deliveries.supplierId, + groupId: deliveries.groupId, + scheduledDate: deliveries.scheduledDate, + deliveredDate: deliveries.deliveredDate, + quantity: deliveries.quantity, + unit: deliveries.unit, + status: deliveries.status, + notes: deliveries.notes, + blNumber: deliveries.blNumber, + blAmount: deliveries.blAmount, + invoiceReference: deliveries.invoiceReference, + invoiceAmount: deliveries.invoiceAmount, + reconciled: deliveries.reconciled, + validatedAt: deliveries.validatedAt, + createdBy: deliveries.createdBy, + createdAt: deliveries.createdAt, + updatedAt: deliveries.updatedAt, + } + }) + .from(reconciliationComments) + .leftJoin(users, eq(reconciliationComments.authorId, users.id)) + .leftJoin(groups, eq(reconciliationComments.groupId, groups.id)) + .leftJoin(deliveries, eq(reconciliationComments.deliveryId, deliveries.id)) + .where(eq(reconciliationComments.deliveryId, deliveryId)) + .orderBy(desc(reconciliationComments.createdAt)); + + return comments.map(comment => ({ + ...comment, + delivery: { + ...comment.delivery, + supplier: {} as any, // Will be filled by relations if needed + group: comment.group!, + creator: {} as any, // Will be filled by relations if needed + } + })) as ReconciliationCommentWithRelations[]; + } + + async createReconciliationComment(commentData: InsertReconciliationComment): Promise { + const [comment] = await db + .insert(reconciliationComments) + .values(commentData) + .returning(); + return comment; + } + + async updateReconciliationComment(id: number, commentData: Partial): Promise { + const [comment] = await db + .update(reconciliationComments) + .set({ ...commentData, updatedAt: new Date() }) + .where(eq(reconciliationComments.id, id)) + .returning(); + return comment; + } + + async deleteReconciliationComment(id: number): Promise { + await db + .delete(reconciliationComments) + .where(eq(reconciliationComments.id, id)); + } } // MemStorage class for development @@ -2581,6 +2668,7 @@ export class MemStorage implements IStorage { private customerOrders = new Map(); private dlcProducts = new Map(); private tasks = new Map(); + private reconciliationComments = new Map(); private announcements = new Map(); private avoirs = new Map(); private savTickets = new Map(); @@ -2600,6 +2688,7 @@ export class MemStorage implements IStorage { avoir: 1, savTicket: 1, savTicketHistory: 1, + reconciliationComment: 1, }; constructor() { @@ -4391,6 +4480,71 @@ export class MemStorage implements IStorage { this.avoirs.set(id, avoir); } } + + // Reconciliation Comments operations + async getReconciliationComments(deliveryId: number): Promise { + const comments = Array.from(this.reconciliationComments.values()) + .filter(comment => comment.deliveryId === deliveryId) + .sort((a, b) => new Date(b.createdAt!).getTime() - new Date(a.createdAt!).getTime()); + + return comments.map(comment => { + const delivery = this.deliveries.get(comment.deliveryId); + const author = this.users.get(comment.authorId); + const group = this.groups.get(comment.groupId); + + if (!delivery || !author || !group) { + throw new Error('Missing related data for reconciliation comment'); + } + + const supplier = this.suppliers.get(delivery.supplierId); + const creator = this.users.get(delivery.createdBy); + + if (!supplier || !creator) { + throw new Error('Missing delivery related data for reconciliation comment'); + } + + return { + ...comment, + delivery: { + ...delivery, + supplier, + group, + creator, + }, + group, + author, + } as ReconciliationCommentWithRelations; + }); + } + + async createReconciliationComment(commentData: InsertReconciliationComment): Promise { + const id = this.idCounters.reconciliationComment++; + const comment: ReconciliationComment = { + id, + ...commentData, + createdAt: new Date(), + updatedAt: new Date(), + }; + this.reconciliationComments.set(id, comment); + return comment; + } + + async updateReconciliationComment(id: number, commentData: Partial): Promise { + const existingComment = this.reconciliationComments.get(id); + if (!existingComment) throw new Error('Reconciliation comment not found'); + + const updatedComment = { + ...existingComment, + ...commentData, + updatedAt: new Date(), + }; + this.reconciliationComments.set(id, updatedComment); + return updatedComment; + } + + async deleteReconciliationComment(id: number): Promise { + this.reconciliationComments.delete(id); + } } // Use MemStorage in development, DatabaseStorage in production