-
Notifications
You must be signed in to change notification settings - Fork 0
OUT-3528: notify IUs when QBO sync fails with manual-fix errors #238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
9994762
feat(OUT-3528): add error_code column to qb_sync_logs
SandipBajracharya d9a0629
feat(OUT-3528): plumb QBO error codes into sync log writes
SandipBajracharya 46e64aa
feat(OUT-3528): IU notifications for user-actionable QBO errors
SandipBajracharya c740e1a
feat(OUT-3528): dispatch sync-failure notifications via SyncErrorNoti…
SandipBajracharya 92c4aff
test(OUT-3528): unit coverage for notifier and helper
SandipBajracharya a85c990
test(OUT-3528): derive coverage loops from enum + registry
SandipBajracharya da0b10a
fix(OUT-3528): guard buildEntityReference against partial context
SandipBajracharya 31b0693
feat(OUT-3528): describe payment.succeeded as "invoice fees creation"
SandipBajracharya e74f9b6
feat(OUT-3528): add QB_INVALID_ACCOUNT_TYPE for QBO error 6430
SandipBajracharya b6d3d0b
chore(OUT-3528): migration file to add error code in sync logs table
SandipBajracharya 3216a6a
fix(OUT-3528): extract QBO Fault code from HttpFetchError
SandipBajracharya c3834dd
refactor(OUT-3528): drop 6140 from notification registry; use QBOErro…
SandipBajracharya 6b0a48b
fix(OUT-3528): casing typo Quickbooks → QuickBooks in CLOSED_PERIOD copy
SandipBajracharya 0b4f64d
fix(OUT-3528): narrow 5010 suppression to PRODUCT only
SandipBajracharya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,32 @@ | ||
| export enum NotificationActions { | ||
| AUTH_RECONNECT = 'auth_reconnect', | ||
| QB_DUPLICATE_NAME = 'qb_duplicate_name', | ||
| QB_CLOSED_PERIOD = 'qb_closed_period', | ||
| QB_DEPOSITED_TXN_LOCKED = 'qb_deposited_txn_locked', | ||
| QB_INACTIVE_REFERENCE = 'qb_inactive_reference', | ||
| QB_SUBSCRIPTION_INVALID = 'qb_subscription_invalid', | ||
| QB_VALIDATION_FAILED = 'qb_validation_failed', | ||
| QB_STALE_OBJECT = 'qb_stale_object', | ||
| QB_TXN_LINK_FAILED = 'qb_txn_link_failed', | ||
| QB_ITEM_INCOME_ACCOUNT_MISSING = 'qb_item_income_account_missing', | ||
| QB_INVALID_ACCOUNT_TYPE = 'qb_invalid_account_type', | ||
| } | ||
|
|
||
| /** | ||
| * Optional context passed alongside a NotificationActions value when dispatching | ||
| * a sync-failure notification. The notification helper uses these to interpolate | ||
| * a tailored title/body (e.g. naming the offending invoice number or QB item). | ||
| * | ||
| * All fields are optional so callers (like AUTH_RECONNECT) can still dispatch | ||
| * without context. | ||
| */ | ||
| export interface NotificationContext { | ||
| entityType?: string | ||
| eventType?: string | ||
| entityKey?: string | ||
| invoiceNumber?: string | ||
| customerName?: string | ||
| productName?: string | ||
| qbItemName?: string | ||
| errorMessage?: string | ||
| } |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { BaseService } from '@/app/api/core/services/base.service' | ||
| import { EntityType, LogStatus } from '@/app/api/core/types/log' | ||
| import { | ||
| NotificationActions, | ||
| NotificationContext, | ||
| } from '@/app/api/core/types/notification' | ||
| import { NotificationService } from '@/app/api/notification/notification.service' | ||
| import { UserActionableErrorCodes } from '@/constant/intuitErrorCode' | ||
| import { QBSyncLogSelectSchemaType } from '@/db/schema/qbSyncLogs' | ||
| import { getPortalConnection } from '@/db/service/token.service' | ||
|
|
||
| /** | ||
| * Looks up the user-actionable notification action for a given QBO error code. | ||
| * Returns null when the code is empty, unknown, or refers to a transient/auth | ||
| * error handled elsewhere (429, 5xx, invalid_grant, etc.). | ||
| */ | ||
| export function getActionForErrorCode( | ||
| errorCode: string | null | undefined, | ||
| ): NotificationActions | null { | ||
| if (!errorCode) return null | ||
| return UserActionableErrorCodes[errorCode] ?? null | ||
| } | ||
|
|
||
| /** | ||
| * Picks the strongest available identifier for the offending QBO entity so the | ||
| * notification body can reference a concrete record (invoice number, item, etc). | ||
| */ | ||
| export function getEntityKey(log: QBSyncLogSelectSchemaType): string { | ||
| return ( | ||
| log.quickbooksId || | ||
| log.invoiceNumber || | ||
| log.qbItemName || | ||
| log.copilotPriceId || | ||
| log.copilotId || | ||
| '' | ||
| ) | ||
| } | ||
|
|
||
| export class SyncErrorNotifier extends BaseService { | ||
| /** | ||
| * Dispatches an IU notification for a freshly written FAILED sync log row | ||
| * when its errorCode is in the user-actionable registry. One sync_log insert | ||
| * = one notification — natural dedup comes from sync_log being created once | ||
| * per failed entity. | ||
| * | ||
| * Errors here are caller-suppressed; a notification failure must not undo | ||
| * the sync log write. | ||
| */ | ||
| async notify(log: QBSyncLogSelectSchemaType): Promise<void> { | ||
| if (log.status !== LogStatus.FAILED) return | ||
|
|
||
| const action = getActionForErrorCode(log.errorCode) | ||
| if (!action) return | ||
|
|
||
| // PRODUCT 5010 auto-recovers via updateProductSyncToken on the next | ||
| // cron tick. INVOICE/PAYMENT have no equivalent refresh, so their | ||
| // 5010s stay user-actionable. | ||
| if ( | ||
| action === NotificationActions.QB_STALE_OBJECT && | ||
| log.entityType === EntityType.PRODUCT | ||
| ) { | ||
| return | ||
| } | ||
|
SandipBajracharya marked this conversation as resolved.
|
||
|
|
||
| const context: NotificationContext = { | ||
| entityType: log.entityType, | ||
| eventType: log.eventType, | ||
| entityKey: getEntityKey(log), | ||
| invoiceNumber: log.invoiceNumber ?? undefined, | ||
| customerName: log.customerName ?? undefined, | ||
| productName: log.productName ?? undefined, | ||
| qbItemName: log.qbItemName ?? undefined, | ||
| errorMessage: log.errorMessage ?? undefined, | ||
| } | ||
| const portal = await getPortalConnection(this.user.workspaceId) | ||
|
|
||
| const notificationService = new NotificationService(this.user) | ||
| // Webhook-driven failures have no calling IU. Empty senderId mirrors the | ||
| // existing AUTH_RECONNECT pattern (auth.service.ts) where `error.intiatedBy | ||
| // ?? ''` is passed to sendNotificationToIU. | ||
| await notificationService.sendNotificationToIU( | ||
| portal?.intiatedBy || '', | ||
| action, | ||
| context, | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.