feat(260519-0oz-01): add QboPaymentCreatePayload, QboDepositCreatePayload types and createPayment/createDeposit methods to QboClient
- Add QboPaymentCreatePayload + QboDepositCreatePayload interfaces to lib/types/qbo.ts - Add createPayment(payload) and createDeposit(payload) public methods to QboClient - Both methods use existing private this.request<T>() with POST + minorversion=65 - Both methods throw descriptively if QBO returns no Id in response
This commit is contained in:
parent
5f4ccb9c56
commit
ef9b31e7c2
2 changed files with 93 additions and 0 deletions
|
|
@ -14,6 +14,8 @@ import {
|
|||
QboJournalEntry,
|
||||
QboReport,
|
||||
QboQueryResponse,
|
||||
QboPaymentCreatePayload,
|
||||
QboDepositCreatePayload,
|
||||
} from '@/lib/types/qbo';
|
||||
|
||||
const QBO_PRODUCTION_URL = 'https://quickbooks.api.intuit.com';
|
||||
|
|
@ -225,6 +227,45 @@ export class QboClient {
|
|||
return this.queryAll<QboDeposit>('Deposit', where);
|
||||
}
|
||||
|
||||
// ─── Entity Creators ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Create a Receive Payment in QBO and apply it to one or more invoices.
|
||||
* Returns the created Payment as QBO echoes it back (includes Id, SyncToken).
|
||||
*/
|
||||
async createPayment(payload: QboPaymentCreatePayload): Promise<QboPayment> {
|
||||
const data = await this.request<{ Payment: QboPayment }>(
|
||||
`/payment?minorversion=65`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
if (!data?.Payment?.Id) {
|
||||
throw new Error(`QBO createPayment returned no Payment: ${JSON.stringify(data).slice(0, 500)}`);
|
||||
}
|
||||
return data.Payment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Deposit in QBO that groups one or more existing Payments into a
|
||||
* single bank-deposit line (so QBO's bank-feed reconciliation matches the
|
||||
* bank's actual deposit slip).
|
||||
*/
|
||||
async createDeposit(payload: QboDepositCreatePayload): Promise<QboDeposit> {
|
||||
const data = await this.request<{ Deposit: QboDeposit }>(
|
||||
`/deposit?minorversion=65`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
if (!data?.Deposit?.Id) {
|
||||
throw new Error(`QBO createDeposit returned no Deposit: ${JSON.stringify(data).slice(0, 500)}`);
|
||||
}
|
||||
return data.Deposit;
|
||||
}
|
||||
|
||||
async getPurchases(updatedSince?: Date): Promise<QboPurchase[]> {
|
||||
const where = updatedSince
|
||||
? `MetaData.LastUpdatedTime > '${updatedSince.toISOString()}'`
|
||||
|
|
@ -278,6 +319,26 @@ export class QboClient {
|
|||
});
|
||||
return this.request<QboReport>(`/reports/CashFlow?${params.toString()}`);
|
||||
}
|
||||
|
||||
// A/R Aging Detail — one row per open invoice, with customer + balance + days
|
||||
// past due. Used to reconcile Pulse's qbo_invoices against QBO's view of A/R.
|
||||
async getAgedReceivableDetail(asOfDate?: string): Promise<QboReport> {
|
||||
const params = new URLSearchParams({
|
||||
report_date: asOfDate ?? new Date().toISOString().slice(0, 10),
|
||||
aging_method: 'Report_Date',
|
||||
minorversion: '65',
|
||||
});
|
||||
return this.request<QboReport>(`/reports/AgedReceivableDetail?${params.toString()}`);
|
||||
}
|
||||
|
||||
async getAgedReceivableSummary(asOfDate?: string): Promise<QboReport> {
|
||||
const params = new URLSearchParams({
|
||||
report_date: asOfDate ?? new Date().toISOString().slice(0, 10),
|
||||
aging_method: 'Report_Date',
|
||||
minorversion: '65',
|
||||
});
|
||||
return this.request<QboReport>(`/reports/AgedReceivables?${params.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
let _instance: QboClient | null = null;
|
||||
|
|
|
|||
|
|
@ -87,6 +87,37 @@ export interface QboDeposit {
|
|||
MetaData?: QboMetaData;
|
||||
}
|
||||
|
||||
// Payload for creating a Payment via POST /v3/company/{realmId}/payment
|
||||
// Only the fields we actually send — QBO accepts many more but we keep the
|
||||
// surface area small and explicit.
|
||||
export interface QboPaymentCreatePayload {
|
||||
CustomerRef: QboRef; // { value: customer_ref_id }
|
||||
TotalAmt: number;
|
||||
TxnDate?: string; // YYYY-MM-DD
|
||||
DepositToAccountRef?: QboRef; // { value: account id }
|
||||
PaymentRefNum?: string; // check number (max 21 chars per QBO)
|
||||
PrivateNote?: string;
|
||||
Line?: Array<{
|
||||
Amount: number;
|
||||
LinkedTxn?: QboLinkedTxn[]; // one entry per invoice being paid
|
||||
}>;
|
||||
}
|
||||
|
||||
// Payload for creating a Deposit via POST /v3/company/{realmId}/deposit
|
||||
// Each Line links to a previously-created Payment so QBO groups them as a
|
||||
// single bank deposit (matches the bank slip).
|
||||
export interface QboDepositCreatePayload {
|
||||
TxnDate?: string;
|
||||
DepositToAccountRef: QboRef; // required for deposit
|
||||
PrivateNote?: string;
|
||||
Line: Array<{
|
||||
Amount: number;
|
||||
DetailType: 'DepositLineDetail';
|
||||
LinkedTxn?: QboLinkedTxn[]; // [{ TxnId: payment_id, TxnType: 'Payment' }]
|
||||
DepositLineDetail?: Record<string, unknown>;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Purchase (expense/credit card)
|
||||
export interface QboPurchase {
|
||||
Id: string;
|
||||
|
|
@ -156,6 +187,7 @@ export interface QboEntitySyncResult {
|
|||
entity: string;
|
||||
success: boolean;
|
||||
recordsUpserted: number;
|
||||
recordsDeleted?: number;
|
||||
duration: number;
|
||||
error?: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue