feat: QuickBooks Online integration
- Add QBO OAuth2 client with token refresh (lib/services/qbo-client.ts) - Add QBO sync service for invoices, payments, deposits, purchases, journal entries, reports (lib/services/qbo-sync-service.ts) - Add QBO types (lib/types/qbo.ts) - Add API routes: /api/qbo/auth, /api/qbo/sync, /api/qbo/disconnect - Add /admin/qbo status and sync management page - Add legal pages: /legal/eula, /legal/privacy (Intuit app assessment) - Add QBO nav link under Admin - Fix reports: remove invalid summarize_column_by, add accounting_method from Preferences API, add showrows=all&showcols=all - Add CashFlow report type alongside P&L and BalanceSheet - Add NoReportData check to skip empty report months - Add intuit_tid capture in error messages - Add redirect: follow for cluster routing - Migration 051: qbo_tokens, qbo_invoices, qbo_payments, qbo_deposits, qbo_transactions, qbo_reports tables Also includes earlier work: - Ping flap suppression pipeline step - Ticket digest reports with LLM analysis - Zabbix WAN monitor and gap analysis - Kiosk is_deleted filter fixes - Datto RMM ping target enrichment - Entity sync soft-delete detection
This commit is contained in:
parent
c518eefdb2
commit
b98c67482a
40 changed files with 6223 additions and 15 deletions
161
lib/types/qbo.ts
Normal file
161
lib/types/qbo.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
export interface QboTokenRecord {
|
||||
id: number;
|
||||
realm_id: string;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
access_token_expires_at: Date;
|
||||
refresh_token_expires_at: Date;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface QboTokenResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
expires_in: number;
|
||||
x_refresh_token_expires_in: number;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
export interface QboRef {
|
||||
value: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface QboMetaData {
|
||||
CreateTime: string;
|
||||
LastUpdatedTime: string;
|
||||
}
|
||||
|
||||
export interface QboLineItem {
|
||||
Id?: string;
|
||||
LineNum?: number;
|
||||
Description?: string;
|
||||
Amount: number;
|
||||
DetailType: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface QboLinkedTxn {
|
||||
TxnId: string;
|
||||
TxnType: string;
|
||||
TxnLineId?: string;
|
||||
}
|
||||
|
||||
// Invoice
|
||||
export interface QboInvoice {
|
||||
Id: string;
|
||||
SyncToken: string;
|
||||
DocNumber?: string;
|
||||
TxnDate?: string;
|
||||
DueDate?: string;
|
||||
CustomerRef?: QboRef;
|
||||
BillEmail?: { Address?: string };
|
||||
TotalAmt?: number;
|
||||
Balance?: number;
|
||||
EmailStatus?: string;
|
||||
PrintStatus?: string;
|
||||
CurrencyRef?: QboRef;
|
||||
Line?: QboLineItem[];
|
||||
LinkedTxn?: QboLinkedTxn[];
|
||||
MetaData?: QboMetaData;
|
||||
}
|
||||
|
||||
// Payment
|
||||
export interface QboPayment {
|
||||
Id: string;
|
||||
SyncToken: string;
|
||||
TxnDate?: string;
|
||||
CustomerRef?: QboRef;
|
||||
TotalAmt?: number;
|
||||
UnappliedAmt?: number;
|
||||
CurrencyRef?: QboRef;
|
||||
PaymentMethodRef?: QboRef;
|
||||
DepositToAccountRef?: QboRef;
|
||||
Line?: Array<{ Amount: number; LinkedTxn?: QboLinkedTxn[] }>;
|
||||
MetaData?: QboMetaData;
|
||||
}
|
||||
|
||||
// Deposit
|
||||
export interface QboDeposit {
|
||||
Id: string;
|
||||
SyncToken: string;
|
||||
TxnDate?: string;
|
||||
DepositToAccountRef?: QboRef;
|
||||
TotalAmt?: number;
|
||||
Line?: QboLineItem[];
|
||||
MetaData?: QboMetaData;
|
||||
}
|
||||
|
||||
// Purchase (expense/credit card)
|
||||
export interface QboPurchase {
|
||||
Id: string;
|
||||
SyncToken: string;
|
||||
TxnDate?: string;
|
||||
DocNumber?: string;
|
||||
EntityRef?: QboRef & { type?: string };
|
||||
AccountRef?: QboRef;
|
||||
TotalAmt?: number;
|
||||
CurrencyRef?: QboRef;
|
||||
PrivateNote?: string;
|
||||
Line?: QboLineItem[];
|
||||
MetaData?: QboMetaData;
|
||||
}
|
||||
|
||||
// Journal Entry
|
||||
export interface QboJournalEntry {
|
||||
Id: string;
|
||||
SyncToken: string;
|
||||
TxnDate?: string;
|
||||
DocNumber?: string;
|
||||
TotalAmt?: number;
|
||||
CurrencyRef?: QboRef;
|
||||
PrivateNote?: string;
|
||||
Line?: QboLineItem[];
|
||||
MetaData?: QboMetaData;
|
||||
}
|
||||
|
||||
// Report (P&L, Balance Sheet)
|
||||
export interface QboReport {
|
||||
Header?: {
|
||||
ReportName?: string;
|
||||
StartPeriod?: string;
|
||||
EndPeriod?: string;
|
||||
Time?: string;
|
||||
Currency?: string;
|
||||
ReportBasis?: string;
|
||||
NoReportData?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
Columns?: any;
|
||||
Rows?: any;
|
||||
}
|
||||
|
||||
export interface QboQueryResponse<T> {
|
||||
QueryResponse: {
|
||||
[key: string]: T[] | number | undefined;
|
||||
startPosition?: number;
|
||||
maxResults?: number;
|
||||
totalCount?: number;
|
||||
};
|
||||
time: string;
|
||||
}
|
||||
|
||||
export interface QboSyncResult {
|
||||
syncId: string;
|
||||
realmId: string;
|
||||
status: 'completed' | 'failed';
|
||||
startedAt: Date;
|
||||
completedAt: Date;
|
||||
duration: number;
|
||||
entities: QboEntitySyncResult[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export interface QboEntitySyncResult {
|
||||
entity: string;
|
||||
success: boolean;
|
||||
recordsUpserted: number;
|
||||
duration: number;
|
||||
error?: string;
|
||||
}
|
||||
|
|
@ -28,7 +28,8 @@ export enum WebhookEntityType {
|
|||
}
|
||||
|
||||
/**
|
||||
* Autotask entities that support webhooks via their REST API
|
||||
* Entities Autotask actually exposes a webhook REST endpoint for.
|
||||
* TimeEntries, Tasks, Projects, Contracts return 404 — not supported by Autotask.
|
||||
*/
|
||||
export const WEBHOOK_SUPPORTED_ENTITIES: WebhookEntityType[] = [
|
||||
WebhookEntityType.COMPANIES,
|
||||
|
|
@ -115,6 +116,10 @@ const ENTITY_TYPE_MAP: Record<string, WebhookEntityType> = {
|
|||
'InstalledProduct': WebhookEntityType.CONFIGURATION_ITEMS, // Autotask actual payload name
|
||||
'Ticket': WebhookEntityType.TICKETS,
|
||||
'TicketNote': WebhookEntityType.TICKET_NOTES,
|
||||
'TimeEntry': WebhookEntityType.TIME_ENTRIES,
|
||||
'Task': WebhookEntityType.TASKS,
|
||||
'Project': WebhookEntityType.PROJECTS,
|
||||
'Contract': WebhookEntityType.CONTRACTS,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue