-
Notifications
You must be signed in to change notification settings - Fork 0
OUT-3686: paginate-and-filter QBO customer email lookup #241
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
Changes from all commits
770c1a1
1119d4b
6cf76e4
119d5c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -296,20 +296,44 @@ export default class IntuitAPI { | |||||||
| return CustomerQueryResponseSchema.parse(qbCustomers.Customer[0]) | ||||||||
| } | ||||||||
|
|
||||||||
| // QBO's parser mishandles special chars on PrimaryEmailAddr filters, so we | ||||||||
| // page and match client-side. sanitizedCompanyName disambiguates the same | ||||||||
| // email across companies; normalisation matches customer.service.ts. | ||||||||
| // ORDERBY Id ASC pins a stable cursor — QBO's default (LastUpdatedTime DESC) | ||||||||
| // lets a mid-walk update shift a row past STARTPOSITION (false negative). | ||||||||
| async _getCustomerByEmail( | ||||||||
| email: string, | ||||||||
| sanitizedCompanyName?: string, | ||||||||
| ): Promise<CustomerQueryResponseType | undefined> { | ||||||||
| const needle = email.trim().toLowerCase() | ||||||||
| if (!needle) return | ||||||||
|
|
||||||||
| CustomLogger.info({ | ||||||||
| obj: { email }, | ||||||||
| obj: { email, sanitizedCompanyName }, | ||||||||
| message: `IntuitAPI#getCustomerByEmail | Customer query start for realmId: ${this.tokens.intuitRealmId}. Email: ${email}`, | ||||||||
| }) | ||||||||
| const customerQuery = `SELECT Id, SyncToken, Active, CompanyName, PrimaryEmailAddr FROM Customer WHERE PrimaryEmailAddr = '${escapeForQBQuery(email)}' AND Active in (true, false)` | ||||||||
| const qbCustomers = await this.customQuery(customerQuery) | ||||||||
|
|
||||||||
| if (!qbCustomers) return | ||||||||
| const pageSize = 1000 | ||||||||
| let startPosition = 1 | ||||||||
|
|
||||||||
| if (!qbCustomers.Customer) return | ||||||||
| return CustomerQueryResponseSchema.parse(qbCustomers.Customer[0]) | ||||||||
| while (true) { | ||||||||
| const customerQuery = `SELECT Id, SyncToken, Active, CompanyName, PrimaryEmailAddr FROM Customer WHERE Active IN (true, false) ORDERBY Id ASC STARTPOSITION ${startPosition} MAXRESULTS ${pageSize}` | ||||||||
| const qbCustomers = await this.customQuery(customerQuery) | ||||||||
| const customers = qbCustomers?.Customer ?? [] | ||||||||
| if (customers.length === 0) return | ||||||||
|
|
||||||||
| const match = customers.find((c: CustomerQueryResponseType) => { | ||||||||
| const addr = c.PrimaryEmailAddr?.Address | ||||||||
| if (typeof addr !== 'string') return false | ||||||||
| if (addr.trim().toLowerCase() !== needle) return false | ||||||||
| if ((c.CompanyName || undefined) !== sanitizedCompanyName) return false | ||||||||
| return true | ||||||||
|
Comment on lines
+329
to
+330
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CompanyName can be empty string when returned from QBO. Since the value for sanitizedCompanyName is either string or optional, comparing CompanyName with empty string is never equivalent to undefined sanitizedCompanyName. That is the reason for such implementation.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is confusing tbh. So if both are undefined === undefined or "" === "" vayo vane ni true nai janu parne haina? Your check right now can have following scenarios: So only thing you want to avoid is
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I want to be true is "" === undefined. Left hand side is never undefined without |
||||||||
| }) | ||||||||
| if (match) return CustomerQueryResponseSchema.parse(match) | ||||||||
|
|
||||||||
| if (customers.length < pageSize) return | ||||||||
| startPosition += pageSize | ||||||||
| } | ||||||||
|
SandipBajracharya marked this conversation as resolved.
|
||||||||
| } | ||||||||
|
SandipBajracharya marked this conversation as resolved.
|
||||||||
|
|
||||||||
| /** | ||||||||
|
|
@@ -892,7 +916,10 @@ export default class IntuitAPI { | |||||||
| includeInactive?: boolean, | ||||||||
| ): Promise<CustomerQueryResponseType> | ||||||||
| } = this.wrapWithRetry(this._getACustomer) as any | ||||||||
| getCustomerByEmail = this.wrapWithRetry(this._getCustomerByEmail) | ||||||||
| // Intentionally NOT wrapped in wrapWithRetry — a transient 429 mid-walk would | ||||||||
| // replay from page 1 and amplify rate-limit pressure. The inner customQuery | ||||||||
| // calls already retry on 429 (same reasoning as resolveUniqueCustomerName). | ||||||||
| getCustomerByEmail = this._getCustomerByEmail.bind(this) | ||||||||
| getAnItem: { | ||||||||
| ( | ||||||||
| name: string, | ||||||||
|
|
||||||||
Uh oh!
There was an error while loading. Please reload this page.