From 6106e1d5fa9124bae7bedec747b6aaa6ecb6fe51 Mon Sep 17 00:00:00 2001 From: Adrian Pilarczyk Date: Thu, 3 Aug 2023 11:48:43 +0200 Subject: [PATCH] feat: Avatax metadata tax calculation date (#843) * feat: :sparkles: add metadata tax calculation date * build: :construction_worker: add changeset --- .changeset/ninety-lobsters-design.md | 5 +++ .../subscriptions/OrderConfirmed.graphql | 1 + ...onfirmed-calculation-date-resolver.test.ts | 31 +++++++++++++++++ ...der-confirmed-calculation-date-resolver.ts | 33 +++++++++++++++++++ ...tax-order-confirmed-payload-transformer.ts | 10 ++++-- 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 .changeset/ninety-lobsters-design.md create mode 100644 apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.test.ts create mode 100644 apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.ts diff --git a/.changeset/ninety-lobsters-design.md b/.changeset/ninety-lobsters-design.md new file mode 100644 index 0000000..21beda3 --- /dev/null +++ b/.changeset/ninety-lobsters-design.md @@ -0,0 +1,5 @@ +--- +"saleor-app-taxes": minor +--- + +Added support for reading the tax calculation date from metadata field `avataxTaxCalculationDate`. The value has to be valid UTC datetime string (e.g. "2021-08-31T13:00:00.000Z"). diff --git a/apps/taxes/graphql/subscriptions/OrderConfirmed.graphql b/apps/taxes/graphql/subscriptions/OrderConfirmed.graphql index d605029..c0d7eb6 100644 --- a/apps/taxes/graphql/subscriptions/OrderConfirmed.graphql +++ b/apps/taxes/graphql/subscriptions/OrderConfirmed.graphql @@ -65,6 +65,7 @@ fragment OrderConfirmedSubscription on Order { } } avataxEntityCode: metafield(key: "avataxEntityCode") + avataxTaxCalculationDate: metafield(key: "avataxTaxCalculationDate") } fragment OrderConfirmedEventSubscription on Event { __typename diff --git a/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.test.ts b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.test.ts new file mode 100644 index 0000000..e4c5ba8 --- /dev/null +++ b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; +import { OrderConfirmedSubscriptionFragment } from "../../../../generated/graphql"; +import { AvataxOrderConfirmedCalculationDateResolver } from "./avatax-order-confirmed-calculation-date-resolver"; + +const resolver = new AvataxOrderConfirmedCalculationDateResolver(); + +describe("AvataxOrderConfirmedCalculationDateResolver", () => { + it("should return the metadata tax calculation date if it is set", () => { + const order = { + avataxTaxCalculationDate: "2021-01-01T00:00:00.000Z", + created: "2021-01-02T00:00:00.000Z", + } as any as OrderConfirmedSubscriptionFragment; + + expect(resolver.resolve(order)).toEqual(new Date("2021-01-01T00:00:00.000Z")); + }); + it("should fallback to order created when metadata tax calculation date is not a string datetime", () => { + const order = { + avataxTaxCalculationDate: "not-a-datetime", + created: "2021-01-02T00:00:00.000Z", + } as any as OrderConfirmedSubscriptionFragment; + + expect(resolver.resolve(order)).toEqual(new Date("2021-01-02T00:00:00.000Z")); + }); + it("should return the order creation date if the metadata tax calculation date is not set", () => { + const order = { + created: "2021-01-02T00:00:00.000Z", + } as any as OrderConfirmedSubscriptionFragment; + + expect(resolver.resolve(order)).toEqual(new Date("2021-01-02T00:00:00.000Z")); + }); +}); diff --git a/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.ts b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.ts new file mode 100644 index 0000000..37deefc --- /dev/null +++ b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-calculation-date-resolver.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import { OrderConfirmedSubscriptionFragment } from "../../../../generated/graphql"; +import { createLogger } from "../../../lib/logger"; + +export class AvataxOrderConfirmedCalculationDateResolver { + private logger = createLogger({ + name: "AvataxOrderConfirmedCalculationDateResolver", + }); + + resolve(order: OrderConfirmedSubscriptionFragment): Date { + if (!order.avataxTaxCalculationDate) { + this.logger.info("No tax calculation date provided. Falling back to order created date."); + return new Date(order.created); + } + + // UTC datetime string, e.g. "2021-08-31T13:00:00.000Z" + const taxCalculationParse = z.string().datetime().safeParse(order.avataxTaxCalculationDate); + + if (taxCalculationParse.success) { + // The user is able to pass other tax calculation date than the order creation date. + this.logger.info( + "Valid UTC tax calculation date found in metadata. Using it for tax calculation." + ); + return new Date(taxCalculationParse.data); + } else { + this.logger.warn( + `The tax calculation date ${order.avataxTaxCalculationDate} is not a valid UTC datetime. Falling back to order created date.` + ); + + return new Date(order.created); + } + } +} diff --git a/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-payload-transformer.ts b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-payload-transformer.ts index cdc3bbc..fac7f82 100644 --- a/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-payload-transformer.ts +++ b/apps/taxes/src/modules/avatax/order-confirmed/avatax-order-confirmed-payload-transformer.ts @@ -7,6 +7,7 @@ import { AvataxConfig } from "../avatax-connection-schema"; import { AvataxTaxCodeMatches } from "../tax-code/avatax-tax-code-match-repository"; import { AvataxOrderConfirmedPayloadLinesTransformer } from "./avatax-order-confirmed-payload-lines-transformer"; import { AvataxEntityTypeMatcher } from "../avatax-entity-type-matcher"; +import { AvataxOrderConfirmedCalculationDateResolver } from "./avatax-order-confirmed-calculation-date-resolver"; export const SHIPPING_ITEM_CODE = "Shipping"; @@ -19,15 +20,20 @@ export class AvataxOrderConfirmedPayloadTransformer { return DocumentType.SalesInvoice; } + async transform( order: OrderConfirmedSubscriptionFragment, avataxConfig: AvataxConfig, matches: AvataxTaxCodeMatches ): Promise { - const linesTransformer = new AvataxOrderConfirmedPayloadLinesTransformer(); const avataxClient = new AvataxClient(avataxConfig); + + const linesTransformer = new AvataxOrderConfirmedPayloadLinesTransformer(); + const dateResolver = new AvataxOrderConfirmedCalculationDateResolver(); const entityTypeMatcher = new AvataxEntityTypeMatcher({ client: avataxClient }); + const entityUseCode = await entityTypeMatcher.match(order.avataxEntityCode); + const date = dateResolver.resolve(order); return { model: { @@ -47,7 +53,7 @@ export class AvataxOrderConfirmedPayloadTransformer { currencyCode: order.total.currency, email: order.user?.email ?? "", lines: linesTransformer.transform(order, avataxConfig, matches), - date: new Date(order.created), + date, discount: discountUtils.sumDiscounts( order.discounts.map((discount) => discount.amount.amount) ),