diff --git a/src/app/api/quickbooks/invoice/invoice.service.ts b/src/app/api/quickbooks/invoice/invoice.service.ts index 28e98a0..889e8e5 100644 --- a/src/app/api/quickbooks/invoice/invoice.service.ts +++ b/src/app/api/quickbooks/invoice/invoice.service.ts @@ -858,10 +858,11 @@ export class InvoiceService extends BaseService { } // check if the entity invoice has successful event paid - const syncLog = await this.syncLogService.getOneByCopilotIdAndEventType( - payload.data.id, - EventType.PAID, - ) + const syncLog = await this.syncLogService.getOneByCopilotIdAndEventType({ + copilotId: payload.data.id, + eventType: EventType.PAID, + entityType: EntityType.INVOICE, + }) if (syncLog?.status === LogStatus.SUCCESS) { console.info('InvoiceService#webhookInvoicePaid | Invoice already paid') @@ -893,10 +894,11 @@ export class InvoiceService extends BaseService { } // get invoice sync log - const invoiceLog = await this.syncLogService.getOneByCopilotIdAndEventType( - payload.data.id, - EventType.CREATED, - ) + const invoiceLog = await this.syncLogService.getOneByCopilotIdAndEventType({ + copilotId: payload.data.id, + eventType: EventType.CREATED, + entityType: EntityType.INVOICE, + }) if (!invoiceLog) { console.error( @@ -986,10 +988,11 @@ export class InvoiceService extends BaseService { } // get invoice sync log - const invoiceLog = await this.syncLogService.getOneByCopilotIdAndEventType( - payload.id, - EventType.CREATED, - ) + const invoiceLog = await this.syncLogService.getOneByCopilotIdAndEventType({ + copilotId: payload.id, + eventType: EventType.CREATED, + entityType: EntityType.INVOICE, + }) if (!invoiceLog) { console.error( @@ -1073,10 +1076,11 @@ export class InvoiceService extends BaseService { } // get invoice sync log - const invoiceLog = await this.syncLogService.getOneByCopilotIdAndEventType( - payload.id, - EventType.CREATED, - ) + const invoiceLog = await this.syncLogService.getOneByCopilotIdAndEventType({ + copilotId: payload.id, + eventType: EventType.CREATED, + entityType: EntityType.INVOICE, + }) if (!invoiceLog) { console.error( diff --git a/src/app/api/quickbooks/payment/payment.service.ts b/src/app/api/quickbooks/payment/payment.service.ts index 2793cb0..9f7a868 100644 --- a/src/app/api/quickbooks/payment/payment.service.ts +++ b/src/app/api/quickbooks/payment/payment.service.ts @@ -169,6 +169,7 @@ export class PaymentService extends BaseService { feeAmount: (payload.Line[0].Amount * 100).toFixed(2), remark: 'Absorbed fees', qbItemName: 'Assembly Fees', + errorMessage: '', }, ) } catch (error: unknown) { diff --git a/src/app/api/quickbooks/product/product.service.ts b/src/app/api/quickbooks/product/product.service.ts index ff0d6a0..1633d64 100644 --- a/src/app/api/quickbooks/product/product.service.ts +++ b/src/app/api/quickbooks/product/product.service.ts @@ -22,7 +22,7 @@ import { } from '@/type/dto/webhook.dto' import { CopilotAPI } from '@/utils/copilotAPI' import IntuitAPI, { IntuitAPITokensType } from '@/utils/intuitAPI' -import { and, count, desc, eq, inArray, isNull, not, sql } from 'drizzle-orm' +import { and, count, desc, eq, inArray, isNull, not, SQL } from 'drizzle-orm' import { convert } from 'html-to-text' import { z } from 'zod' import { SyncLogService } from '@/app/api/quickbooks/syncLog/syncLog.service' @@ -30,7 +30,7 @@ import { EntityType, EventType, LogStatus } from '@/app/api/core/types/log' import dayjs from 'dayjs' import APIError from '@/app/api/core/exceptions/api' import httpStatus from 'http-status' -import { QBSyncLog, QBSyncLogCreateSchemaType } from '@/db/schema/qbSyncLogs' +import { QBSyncLog, QBSyncLogWithEntityType } from '@/db/schema/qbSyncLogs' import User from '@/app/api/core/models/User.model' import { SettingService } from '@/app/api/quickbooks/setting/setting.service' import { replaceBeforeParens, replaceSpecialCharsForQB } from '@/utils/string' @@ -605,7 +605,7 @@ export class ProductService extends BaseService { payload.map(async (item) => { const copilotId = item.id - const payload: QBSyncLogCreateSchemaType = { + const payload: QBSyncLogWithEntityType = { portalId: this.user.workspaceId, entityType: EntityType.PRODUCT, eventType: EventType.UNMAPPED, @@ -645,13 +645,20 @@ export class ProductService extends BaseService { productPrice?: string }, ) { - const conditions = [ - eq(QBSyncLog.portalId, this.user.workspaceId), - eventType === EventType.UPDATED - ? (eq(QBSyncLog.copilotId, copilotId), // product update should be reflected to all the products with multiple prices - eq(QBSyncLog.status, LogStatus.FAILED)) - : eq(QBSyncLog.copilotPriceId, z.string().parse(opts.copilotPriceId)), // product create should have different price Id, - ].filter(Boolean) + const conditions: SQL[] = [eq(QBSyncLog.portalId, this.user.workspaceId)] + + if (eventType === EventType.UPDATED) { + conditions.push( + eq(QBSyncLog.copilotId, copilotId), // product update should be reflected to all the products with multiple prices + eq(QBSyncLog.eventType, eventType), + eq(QBSyncLog.status, LogStatus.FAILED), + eq(QBSyncLog.quickbooksId, quickbooksId), + ) + } else { + conditions.push( + eq(QBSyncLog.copilotPriceId, z.string().parse(opts.copilotPriceId)), // product create should have different price Id, + ) + } await this.syncLogService.updateOrCreateQBSyncLog( { diff --git a/src/app/api/quickbooks/sync/sync.service.ts b/src/app/api/quickbooks/sync/sync.service.ts index 81054e6..515a71d 100644 --- a/src/app/api/quickbooks/sync/sync.service.ts +++ b/src/app/api/quickbooks/sync/sync.service.ts @@ -410,7 +410,7 @@ export class SyncService extends BaseService { }) // 1. get all failed sync logs group by the entity type const failedSyncLogs = - await this.syncLogService.getFailedSyncLogsByEntityType(includeDeleted) + await this.syncLogService.getAllFailedLogsForWorkspace(includeDeleted) if (failedSyncLogs.length === 0) { CustomLogger.info({ @@ -479,6 +479,7 @@ export class SyncService extends BaseService { eq(QBPortalConnection.portalId, this.user.workspaceId), ['id'], ) + const deleteLogs = this.syncLogService.updateQBSyncLog( { deletedAt: new Date(), diff --git a/src/app/api/quickbooks/syncLog/syncLog.service.ts b/src/app/api/quickbooks/syncLog/syncLog.service.ts index 1ce5a3a..b0c08b0 100644 --- a/src/app/api/quickbooks/syncLog/syncLog.service.ts +++ b/src/app/api/quickbooks/syncLog/syncLog.service.ts @@ -8,6 +8,7 @@ import { QBSyncLogSelectSchemaType, QBSyncLogUpdateSchema, QBSyncLogUpdateSchemaType, + QBSyncLogWithEntityType, } from '@/db/schema/qbSyncLogs' import { WhereClause } from '@/type/common' import { orderMap } from '@/utils/drizzle' @@ -66,15 +67,21 @@ export class SyncLogService extends BaseService { return log } - async getOneByCopilotIdAndEventType( - copilotId: string, - eventType?: EventType, - ) { + async getOneByCopilotIdAndEventType({ + copilotId, + eventType, + entityType, + }: { + copilotId: string + eventType: EventType + entityType: EntityType + }) { const conditions = [ eq(QBSyncLog.portalId, this.user.workspaceId), eq(QBSyncLog.copilotId, copilotId), - eventType ? eq(QBSyncLog.eventType, eventType) : undefined, - ].filter(Boolean) // removes undefined + eq(QBSyncLog.eventType, eventType), + eq(QBSyncLog.entityType, entityType), + ] const query = this.db.query.QBSyncLog.findFirst({ where: and(...conditions), @@ -94,18 +101,23 @@ export class SyncLogService extends BaseService { } async updateOrCreateQBSyncLog( - payload: QBSyncLogCreateSchemaType, + payload: QBSyncLogWithEntityType, conditions?: WhereClause, ) { let existingLog if (conditions) { - existingLog = await this.getOne(conditions) + const sqlConditions = and( + ...[conditions], + eq(QBSyncLog.entityType, payload.entityType), + ) as WhereClause + existingLog = await this.getOne(sqlConditions) } else { - existingLog = await this.getOneByCopilotIdAndEventType( - payload.copilotId, - payload.eventType, - ) + existingLog = await this.getOneByCopilotIdAndEventType({ + copilotId: payload.copilotId, + eventType: payload.eventType, + entityType: payload.entityType, + }) } if (existingLog) { @@ -129,7 +141,7 @@ export class SyncLogService extends BaseService { /** * Get all failed sync logs */ - async getFailedSyncLogsByEntityType( + async getAllFailedLogsForWorkspace( includeDeleted: boolean, ): Promise { return await this.db.query.QBSyncLog.findMany({ diff --git a/src/app/api/quickbooks/webhook/webhook.service.ts b/src/app/api/quickbooks/webhook/webhook.service.ts index a34e8cc..ab94f21 100644 --- a/src/app/api/quickbooks/webhook/webhook.service.ts +++ b/src/app/api/quickbooks/webhook/webhook.service.ts @@ -154,7 +154,10 @@ export class WebhookService extends BaseService { parsedInvoiceResource.data.total, getMessageAndCodeFromError(error), ) - throw error + console.error( + `WebhookService#handleWebhookEvent#invoiceCreated :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${parsedInvoiceResource.data.id}`, + ) + return } } @@ -187,7 +190,10 @@ export class WebhookService extends BaseService { parsedVoidedInvoiceResource.data.total, getMessageAndCodeFromError(error), ) - throw error + console.error( + `WebhookService#handleWebhookEvent#handleInvoiceVoided :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${parsedVoidedInvoiceResource.data.id}`, + ) + return } } @@ -216,7 +222,10 @@ export class WebhookService extends BaseService { deletePayload.total, getMessageAndCodeFromError(error), ) - throw error + console.error( + `WebhookService#handleWebhookEvent#handleInvoiceDeleted :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${deletePayload.id}`, + ) + return } } @@ -257,7 +266,10 @@ export class WebhookService extends BaseService { deletedAt: getDeletedAtForAuthAccountCategoryLog(errorWithCode), category: getCategory(errorWithCode), }) - throw error + console.error( + `WebhookService#handleWebhookEvent#handleInvoicePaid :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${parsedPaidInvoiceResource.data.id}`, + ) + return } } @@ -298,7 +310,10 @@ export class WebhookService extends BaseService { deletedAt: getDeletedAtForAuthAccountCategoryLog(errorWithCode), category: getCategory(errorWithCode), }) - throw error + console.error( + `WebhookService#handleWebhookEvent#handleProductUpdated :: Error | Portal Id: ${this.user.workspaceId} | Product: ${parsedProductResource.data.id}`, + ) + return } } @@ -351,7 +366,10 @@ export class WebhookService extends BaseService { }, conditions, ) - throw error + console.error( + `WebhookService#handleWebhookEvent#handlePriceCreated :: Error | Portal Id: ${this.user.workspaceId} | PriceId: ${priceResource.id}`, + ) + return } } @@ -383,10 +401,11 @@ export class WebhookService extends BaseService { } const syncLogService = new SyncLogService(this.user) - const syncLog = await syncLogService.getOneByCopilotIdAndEventType( - parsedPaymentSucceedResource.data.id, - EventType.SUCCEEDED, - ) + const syncLog = await syncLogService.getOneByCopilotIdAndEventType({ + copilotId: parsedPaymentSucceedResource.data.id, + eventType: EventType.SUCCEEDED, + entityType: EntityType.PAYMENT, + }) if (syncLog?.status === LogStatus.SUCCESS) { console.info( 'WebhookService#webhookPaymentSucceeded | Payment already succeeded', @@ -432,7 +451,10 @@ export class WebhookService extends BaseService { deletedAt: getDeletedAtForAuthAccountCategoryLog(errorWithCode), category: getCategory(errorWithCode), }) - throw error + console.error( + `WebhookService#handleWebhookEvent#handlePaymentSucceeded :: Error | Portal Id: ${this.user.workspaceId} | Payment: ${parsedPaymentSucceedResource.data.id}`, + ) + return } } } diff --git a/src/db/schema/qbSyncLogs.ts b/src/db/schema/qbSyncLogs.ts index aad024b..017d2ea 100644 --- a/src/db/schema/qbSyncLogs.ts +++ b/src/db/schema/qbSyncLogs.ts @@ -53,6 +53,14 @@ export const QBSyncLog = table('qb_sync_logs', { export const QBSyncLogCreateSchema = createInsertSchema(QBSyncLog) export type QBSyncLogCreateSchemaType = z.infer +export type QBSyncLogWithEntityType = Omit< + QBSyncLogCreateSchemaType, + 'entityType' +> & { + entityType: EntityType + eventType: EventType +} + export const QBSyncLogSelectSchema = createSelectSchema(QBSyncLog) export type QBSyncLogSelectSchemaType = z.infer