From 3a3c309a5b450459d20e388415d5419b5b1efc9e Mon Sep 17 00:00:00 2001 From: Nathnael Date: Sun, 2 Aug 2026 18:33:38 +0000 Subject: [PATCH] fix: etrade inconsistencies --- .../modules/companies/companies.service.ts | 36 ++++++++----------- .../companies/dto/update-profile.dto.ts | 5 ++- .../companies/services/etrade.service.ts | 4 ++- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/apps/edr-freight-api/src/modules/companies/companies.service.ts b/apps/edr-freight-api/src/modules/companies/companies.service.ts index d9798af5d..e0af07e0b 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.service.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.service.ts @@ -928,7 +928,7 @@ export class CompaniesService { ): Promise { const { profile, company } = await this.getCompanyInfoByUserId(userId); - await this.assertEtradeFieldsAuthentic(company, dto); + await this.applyEtradeSourcedFields(company, dto); // Naming (or renaming) a Power of Attorney is one of the writes that can // leave the company with a representative and nothing evidencing them, so @@ -3216,22 +3216,24 @@ export class CompaniesService { /** * An eTrade-sourced field can only ever hold what a fresh eTrade lookup for * this TIN actually returns — the portal never lets the customer type these - * once eTrade has supplied them, so a mismatch here means either stale - * client state or a hand-crafted request, and either way the write is - * refused rather than silently trusting it. + * once eTrade has supplied them. Rather than trust the client's copy (stale + * cache, hand-crafted request, or just a formatting mismatch) and reject it, + * refetch eTrade ourselves and overwrite the touched fields with whatever it + * says now — the client's submitted values for these keys only matter as a + * "this field is part of the save" flag, never as data we persist. */ - private async assertEtradeFieldsAuthentic( + private async applyEtradeSourcedFields( company: Company, dto: UpdateProfileDto, ): Promise { const touched = ETRADE_SOURCED_FIELDS.some( - (key) => dto[key] !== undefined, + (key) => key !== "tin" && dto[key] !== undefined, ); if (!touched) return; const tin = dto.tin ?? company.tin; const registration = await this.resolveEtradeRegistration(tin); - const expected: Partial> = { + const fresh: Partial> = { companyName: registration.companyName, licenceNumber: registration.licenceNumber, statusDescription: registration.statusDescription, @@ -3251,21 +3253,11 @@ export class CompaniesService { }; for (const key of ETRADE_SOURCED_FIELDS) { - const submitted = dto[key]; - if (submitted === undefined) continue; - const source = expected[key]; - // eTrade left this field blank — the onboarding/settings card falls back - // to letting the customer type it directly, so nothing to check against. - if (!source) continue; - const same = - key === "etradePhone" - ? normalizeE164(String(submitted)) === normalizeE164(source) - : submitted === source; - if (!same) { - throw new BadRequestException( - `${key} doesn't match eTrade's current record for this TIN. Re-verify with eTrade to pick up the latest details.`, - ); - } + if (key === "tin" || dto[key] === undefined) continue; + const value = fresh[key]; + // eTrade left this field blank — fall back to whatever the client sent + // (the onboarding/settings card lets the customer type it directly then). + if (value) (dto as Record)[key] = value; } } } diff --git a/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts b/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts index 9f7d1ed39..596644fab 100644 --- a/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts +++ b/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts @@ -179,9 +179,12 @@ export class UpdateProfileDto { @MaxLength(100) houseNo?: string; + // Not validated as a phone number: eTrade-sourced, so a fresh eTrade lookup + // overwrites whatever the client sends here — see + // CompaniesService.applyEtradeSourcedFields. Presence just flags "this save + // touches an eTrade-owned field." @IsOptional() @IsString() @MaxLength(20) - @IsValidPhone() etradePhone?: string; } diff --git a/apps/edr-freight-api/src/modules/companies/services/etrade.service.ts b/apps/edr-freight-api/src/modules/companies/services/etrade.service.ts index 51bdb2df6..3292ee2d3 100644 --- a/apps/edr-freight-api/src/modules/companies/services/etrade.service.ts +++ b/apps/edr-freight-api/src/modules/companies/services/etrade.service.ts @@ -108,7 +108,9 @@ export class ETradeService { dateRegistered: businessInfo.DateRegistered, renewedFrom: businessInfo.RenewedFrom, renewalDate: businessInfo.RenewalDate, - renewedTo: businessInfo.RenewedTo, + // RenewedTo is ISO ("2018-07-07T00:00:00"); RenewedToDateString matches + // RenewedFrom/RenewalDate's "M/D/YYYY" format — use that for consistency. + renewedTo: businessInfo.RenewedToDateString, // eTrade returns uncoded uppercase text and sometimes a zone name in the // Region slot. Map it onto the canonical list; an unresolved value yields // "" so the form asks the user to pick rather than failing validation on