Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/app/api/quickbooks/invoice/invoice.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions src/app/api/quickbooks/payment/payment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
27 changes: 17 additions & 10 deletions src/app/api/quickbooks/product/product.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ 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'
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'
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
{
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/quickbooks/sync/sync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import CustomLogger from '@/utils/logger'
import { QBSyncLog, QBSyncLogSelectSchemaType } from '@/db/schema/qbSyncLogs'
import { z } from 'zod'
import { and, eq, inArray, max } from 'drizzle-orm'

Check warning on line 20 in src/app/api/quickbooks/sync/sync.service.ts

View workflow job for this annotation

GitHub Actions / Run linters

'max' is defined but never used. Allowed unused vars must match /^_/u
import { WhereClause } from '@/type/common'
import { TokenService } from '@/app/api/quickbooks/token/token.service'
import { QBPortalConnection } from '@/db/schema/qbPortalConnections'
Expand Down Expand Up @@ -410,7 +410,7 @@
})
// 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({
Expand Down Expand Up @@ -479,6 +479,7 @@
eq(QBPortalConnection.portalId, this.user.workspaceId),
['id'],
)

const deleteLogs = this.syncLogService.updateQBSyncLog(
{
deletedAt: new Date(),
Expand Down
38 changes: 25 additions & 13 deletions src/app/api/quickbooks/syncLog/syncLog.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
QBSyncLogSelectSchemaType,
QBSyncLogUpdateSchema,
QBSyncLogUpdateSchemaType,
QBSyncLogWithEntityType,
} from '@/db/schema/qbSyncLogs'
import { WhereClause } from '@/type/common'
import { orderMap } from '@/utils/drizzle'
Expand Down Expand Up @@ -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),
Expand All @@ -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) {
Expand All @@ -129,7 +141,7 @@ export class SyncLogService extends BaseService {
/**
* Get all failed sync logs
*/
async getFailedSyncLogsByEntityType(
async getAllFailedLogsForWorkspace(
includeDeleted: boolean,
): Promise<QBSyncLogSelectSchemaType[] | []> {
return await this.db.query.QBSyncLog.findMany({
Expand Down
44 changes: 33 additions & 11 deletions src/app/api/quickbooks/webhook/webhook.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { ProductService } from '@/app/api/quickbooks/product/product.service'
import { SettingService } from '@/app/api/quickbooks/setting/setting.service'
import { SyncLogService } from '@/app/api/quickbooks/syncLog/syncLog.service'
import { AccountErrorCodes } from '@/constant/intuitErrorCode'

Check warning on line 11 in src/app/api/quickbooks/webhook/webhook.service.ts

View workflow job for this annotation

GitHub Actions / Run linters

'AccountErrorCodes' is defined but never used. Allowed unused vars must match /^_/u
import { QBSyncLog } from '@/db/schema/qbSyncLogs'
import {
InvoiceCreatedResponseSchema,
Expand All @@ -20,7 +20,7 @@
WebhookEventResponseSchema,
WebhookEventResponseType,
} from '@/type/dto/webhook.dto'
import { refreshTokenExpireMessage, validateAccessToken } from '@/utils/auth'

Check warning on line 23 in src/app/api/quickbooks/webhook/webhook.service.ts

View workflow job for this annotation

GitHub Actions / Run linters

'refreshTokenExpireMessage' is defined but never used. Allowed unused vars must match /^_/u
import { CopilotAPI } from '@/utils/copilotAPI'
import { ErrorMessageAndCode, getMessageAndCodeFromError } from '@/utils/error'
import { IntuitAPITokensType } from '@/utils/intuitAPI'
Expand Down Expand Up @@ -154,7 +154,10 @@
parsedInvoiceResource.data.total,
getMessageAndCodeFromError(error),
)
throw error
console.error(
`WebhookService#handleWebhookEvent#invoiceCreated :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${parsedInvoiceResource.data.id}`,
)
return
}
}

Expand Down Expand Up @@ -187,7 +190,10 @@
parsedVoidedInvoiceResource.data.total,
getMessageAndCodeFromError(error),
)
throw error
console.error(
`WebhookService#handleWebhookEvent#handleInvoiceVoided :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${parsedVoidedInvoiceResource.data.id}`,
)
return
}
}

Expand Down Expand Up @@ -216,7 +222,10 @@
deletePayload.total,
getMessageAndCodeFromError(error),
)
throw error
console.error(
`WebhookService#handleWebhookEvent#handleInvoiceDeleted :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${deletePayload.id}`,
)
return
}
}

Expand Down Expand Up @@ -257,7 +266,10 @@
deletedAt: getDeletedAtForAuthAccountCategoryLog(errorWithCode),
category: getCategory(errorWithCode),
})
throw error
console.error(
`WebhookService#handleWebhookEvent#handleInvoicePaid :: Error | Portal Id: ${this.user.workspaceId} | Invoice: ${parsedPaidInvoiceResource.data.id}`,
)
return
}
}

Expand Down Expand Up @@ -298,7 +310,10 @@
deletedAt: getDeletedAtForAuthAccountCategoryLog(errorWithCode),
category: getCategory(errorWithCode),
})
throw error
console.error(
`WebhookService#handleWebhookEvent#handleProductUpdated :: Error | Portal Id: ${this.user.workspaceId} | Product: ${parsedProductResource.data.id}`,
)
return
}
}

Expand Down Expand Up @@ -351,7 +366,10 @@
},
conditions,
)
throw error
console.error(
`WebhookService#handleWebhookEvent#handlePriceCreated :: Error | Portal Id: ${this.user.workspaceId} | PriceId: ${priceResource.id}`,
)
return
}
}

Expand Down Expand Up @@ -383,10 +401,11 @@
}

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',
Expand Down Expand Up @@ -432,7 +451,10 @@
deletedAt: getDeletedAtForAuthAccountCategoryLog(errorWithCode),
category: getCategory(errorWithCode),
})
throw error
console.error(
`WebhookService#handleWebhookEvent#handlePaymentSucceeded :: Error | Portal Id: ${this.user.workspaceId} | Payment: ${parsedPaymentSucceedResource.data.id}`,
)
return
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/db/schema/qbSyncLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ export const QBSyncLog = table('qb_sync_logs', {
export const QBSyncLogCreateSchema = createInsertSchema(QBSyncLog)
export type QBSyncLogCreateSchemaType = z.infer<typeof QBSyncLogCreateSchema>

export type QBSyncLogWithEntityType = Omit<
QBSyncLogCreateSchemaType,
'entityType'
> & {
entityType: EntityType
eventType: EventType
}

export const QBSyncLogSelectSchema = createSelectSchema(QBSyncLog)
export type QBSyncLogSelectSchemaType = z.infer<typeof QBSyncLogSelectSchema>

Expand Down
Loading