diff --git a/.changeset/eleven-geese-agree.md b/.changeset/eleven-geese-agree.md new file mode 100644 index 0000000..cd121fb --- /dev/null +++ b/.changeset/eleven-geese-agree.md @@ -0,0 +1,5 @@ +--- +"saleor-app-taxes": minor +--- + +Added "document recording" toggle. It is turned on by default. When turned off, the document type of all Avatax transactions change to "SalesOrder", making them not record. Read more about document recording [here](https://developer.avalara.com/ecommerce-integration-guide/sales-tax-badge/designing/disable-document-recording/). diff --git a/apps/taxes/src/modules/app/get-app-config.test.ts b/apps/taxes/src/modules/app/get-app-config.test.ts index 21bcdbd..67949f6 100644 --- a/apps/taxes/src/modules/app/get-app-config.test.ts +++ b/apps/taxes/src/modules/app/get-app-config.test.ts @@ -14,6 +14,7 @@ const mockedProviders: ProviderConnections = [ companyCode: "DEFAULT", isAutocommit: false, isSandbox: true, + isDocumentRecordingEnabled: true, name: "avatax-1", shippingTaxCode: "FR000000", credentials: { diff --git a/apps/taxes/src/modules/avatax/avatax-config-mock-generator.ts b/apps/taxes/src/modules/avatax/avatax-config-mock-generator.ts index 602bd48..934a5bd 100644 --- a/apps/taxes/src/modules/avatax/avatax-config-mock-generator.ts +++ b/apps/taxes/src/modules/avatax/avatax-config-mock-generator.ts @@ -6,6 +6,7 @@ const defaultAvataxConfig: AvataxConfig = { isSandbox: true, name: "Avatax-1", shippingTaxCode: "FR000000", + isDocumentRecordingEnabled: true, address: { country: "US", zip: "95008", diff --git a/apps/taxes/src/modules/avatax/avatax-connection-schema.ts b/apps/taxes/src/modules/avatax/avatax-connection-schema.ts index 3a09240..b9cb731 100644 --- a/apps/taxes/src/modules/avatax/avatax-connection-schema.ts +++ b/apps/taxes/src/modules/avatax/avatax-connection-schema.ts @@ -27,6 +27,7 @@ export const avataxConfigSchema = z companyCode: z.string().optional(), isAutocommit: z.boolean(), shippingTaxCode: z.string().optional(), + isDocumentRecordingEnabled: z.boolean().default(true), address: addressSchema, }) .merge(baseAvataxConfigSchema); @@ -38,6 +39,7 @@ export const defaultAvataxConfig: AvataxConfig = { companyCode: "", isSandbox: false, isAutocommit: false, + isDocumentRecordingEnabled: true, shippingTaxCode: "", credentials: { username: "", diff --git a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.test.ts b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.test.ts index 878d64d..542073d 100644 --- a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.test.ts +++ b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.test.ts @@ -1,11 +1,25 @@ import { describe, expect, it } from "vitest"; import { AvataxCalculateTaxesMockGenerator } from "./avatax-calculate-taxes-mock-generator"; import { AvataxCalculateTaxesPayloadTransformer } from "./avatax-calculate-taxes-payload-transformer"; +import { DocumentType } from "avatax/lib/enums/DocumentType"; + +const mockGenerator = new AvataxCalculateTaxesMockGenerator(); +const avataxConfigMock = mockGenerator.generateAvataxConfig(); describe("AvataxCalculateTaxesPayloadTransformer", () => { + it("returns document type of SalesInvoice", () => { + const taxBaseMock = mockGenerator.generateTaxBase(); + const matchesMock = mockGenerator.generateTaxCodeMatches(); + + const payload = new AvataxCalculateTaxesPayloadTransformer().transform( + taxBaseMock, + avataxConfigMock, + matchesMock + ); + + expect(payload.model.type).toBe(DocumentType.SalesOrder); + }); it("when discounts, calculates the sum of discounts", () => { - const mockGenerator = new AvataxCalculateTaxesMockGenerator(); - const avataxConfigMock = mockGenerator.generateAvataxConfig(); const taxBaseMock = mockGenerator.generateTaxBase({ discounts: [{ amount: { amount: 10 } }] }); const matchesMock = mockGenerator.generateTaxCodeMatches(); @@ -18,8 +32,6 @@ describe("AvataxCalculateTaxesPayloadTransformer", () => { expect(payload.model.discount).toEqual(10); }); it("when no discounts, the sum of discount is 0", () => { - const mockGenerator = new AvataxCalculateTaxesMockGenerator(); - const avataxConfigMock = mockGenerator.generateAvataxConfig(); const taxBaseMock = mockGenerator.generateTaxBase(); const matchesMock = mockGenerator.generateTaxCodeMatches(); diff --git a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts index 90bc9a0..6e9066c 100644 --- a/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts +++ b/apps/taxes/src/modules/avatax/calculate-taxes/avatax-calculate-taxes-payload-transformer.ts @@ -8,6 +8,16 @@ import { AvataxTaxCodeMatches } from "../tax-code/avatax-tax-code-match-reposito import { AvataxCalculateTaxesPayloadLinesTransformer } from "./avatax-calculate-taxes-payload-lines-transformer"; export class AvataxCalculateTaxesPayloadTransformer { + private matchDocumentType(config: AvataxConfig): DocumentType { + /* + * * For calculating taxes, we always use DocumentType.SalesOrder because it doesn't cause transaction recording. + * * The full flow is described here: https://developer.avalara.com/ecommerce-integration-guide/sales-tax-badge/design-document-workflow/should-i-commit/ + * * config.isDocumentRecordingEnabledEnabled is used to determine if the transaction should be recorded (hence if the document type should be SalesOrder). + * * Given that we never want to record the transaction in calculate taxes, we always return DocumentType.SalesOrder. + */ + return DocumentType.SalesOrder; + } + transform( taxBase: TaxBaseFragment, avataxConfig: AvataxConfig, @@ -17,7 +27,7 @@ export class AvataxCalculateTaxesPayloadTransformer { return { model: { - type: DocumentType.SalesOrder, + type: this.matchDocumentType(avataxConfig), customerCode: taxBase.sourceObject.user?.id ?? "", companyCode: avataxConfig.companyCode, // * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit diff --git a/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.test.ts b/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.test.ts index c88be2d..8123f7c 100644 --- a/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.test.ts +++ b/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { AvataxOrderCreatedMockGenerator } from "./avatax-order-created-mock-generator"; import { AvataxOrderCreatedPayloadTransformer } from "./avatax-order-created-payload-transformer"; +import { DocumentType } from "avatax/lib/enums/DocumentType"; const mockGenerator = new AvataxOrderCreatedMockGenerator(); @@ -16,12 +17,29 @@ const discountedOrderMock = mockGenerator.generateOrder({ ], }); +const transformer = new AvataxOrderCreatedPayloadTransformer(); + export const avataxConfigMock = mockGenerator.generateAvataxConfig(); describe("AvataxOrderCreatedPayloadTransformer", () => { - it("returns lines with discounted: true when there are discounts", () => { - const transformer = new AvataxOrderCreatedPayloadTransformer(); + it("returns document type of SalesInvoice when isDocumentRecordingEnabled is true", () => { + const payload = transformer.transform(orderMock, avataxConfigMock, []); + expect(payload.model.type).toBe(DocumentType.SalesInvoice); + }), + it("returns document type of SalesOrder when isDocumentRecordingEnabled is false", () => { + const payload = transformer.transform( + orderMock, + { + ...avataxConfigMock, + isDocumentRecordingEnabled: false, + }, + [] + ); + + expect(payload.model.type).toBe(DocumentType.SalesOrder); + }); + it("returns lines with discounted: true when there are discounts", () => { const payload = transformer.transform(discountedOrderMock, avataxConfigMock, []); const linesWithoutShipping = payload.model.lines.slice(0, -1); @@ -30,7 +48,6 @@ describe("AvataxOrderCreatedPayloadTransformer", () => { expect(check).toBe(true); }); it("returns lines with discounted: false when there are no discounts", () => { - const transformer = new AvataxOrderCreatedPayloadTransformer(); const payload = transformer.transform(orderMock, avataxConfigMock, []); const linesWithoutShipping = payload.model.lines.slice(0, -1); diff --git a/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.ts b/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.ts index 3307b5d..2ffe4fa 100644 --- a/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.ts +++ b/apps/taxes/src/modules/avatax/order-created/avatax-order-created-payload-transformer.ts @@ -10,6 +10,14 @@ import { AvataxOrderCreatedPayloadLinesTransformer } from "./avatax-order-create export const SHIPPING_ITEM_CODE = "Shipping"; export class AvataxOrderCreatedPayloadTransformer { + private matchDocumentType(config: AvataxConfig): DocumentType { + if (!config.isDocumentRecordingEnabled) { + // isDocumentRecordingEnabled = false changes all the DocTypes within your AvaTax requests to SalesOrder. This will stop any transaction from being recorded within AvaTax. + return DocumentType.SalesOrder; + } + + return DocumentType.SalesInvoice; + } transform( order: OrderCreatedSubscriptionFragment, avataxConfig: AvataxConfig, @@ -19,7 +27,7 @@ export class AvataxOrderCreatedPayloadTransformer { return { model: { - type: DocumentType.SalesInvoice, + type: this.matchDocumentType(avataxConfig), customerCode: order.user?.id ?? "" /* In Saleor Avatax plugin, the customer code is 0. In Taxes App, we set it to the user id. */, diff --git a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.test.ts b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.test.ts index 2a7392d..9964a8e 100644 --- a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.test.ts +++ b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.test.ts @@ -12,6 +12,7 @@ import { const MOCK_AVATAX_CONFIG: AvataxConfig = { companyCode: "DEFAULT", + isDocumentRecordingEnabled: true, isAutocommit: false, isSandbox: true, name: "Avatax-1", @@ -129,8 +130,6 @@ describe("getTransactionCodeFromMetadata", () => { }); }); -const transformer = new AvataxOrderFulfilledPayloadTransformer(MOCK_AVATAX_CONFIG); - const MOCKED_ORDER_FULFILLED_PAYLOAD: { order: OrderFulfilledSubscriptionFragment; } = { @@ -138,16 +137,35 @@ const MOCKED_ORDER_FULFILLED_PAYLOAD: { }; describe("AvataxOrderFulfilledPayloadTransformer", () => { - it("returns transformed payload", () => { - const mappedPayload = transformer.transform(MOCKED_ORDER_FULFILLED_PAYLOAD); - - expect(mappedPayload).toEqual({ - transactionCode: "transaction-code", - companyCode: "DEFAULT", - documentType: DocumentType.SalesInvoice, - model: { - commit: true, - }, + it("returns document type of SalesOrder when isDocumentRecordingEnabled is false", () => { + const transformer = new AvataxOrderFulfilledPayloadTransformer({ + ...MOCK_AVATAX_CONFIG, + isDocumentRecordingEnabled: false, + }); + + const payload = transformer.transform(MOCKED_ORDER_FULFILLED_PAYLOAD); + + expect(payload.documentType).toBe(DocumentType.SalesOrder); + }), + it("returns document type of SalesInvoice when isDocumentRecordingEnabled is true", () => { + const transformer = new AvataxOrderFulfilledPayloadTransformer(MOCK_AVATAX_CONFIG); + + const payload = transformer.transform(MOCKED_ORDER_FULFILLED_PAYLOAD); + + expect(payload.documentType).toBe(DocumentType.SalesInvoice); + }), + it("returns transformed payload", () => { + const transformer = new AvataxOrderFulfilledPayloadTransformer(MOCK_AVATAX_CONFIG); + + const mappedPayload = transformer.transform(MOCKED_ORDER_FULFILLED_PAYLOAD); + + expect(mappedPayload).toEqual({ + transactionCode: "transaction-code", + companyCode: "DEFAULT", + documentType: DocumentType.SalesInvoice, + model: { + commit: true, + }, + }); }); - }); }); diff --git a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.ts b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.ts index 98bed4b..f7a2b6b 100644 --- a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.ts +++ b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.ts @@ -24,13 +24,20 @@ export function getTransactionCodeFromMetadata( export class AvataxOrderFulfilledPayloadTransformer { constructor(private readonly config: AvataxConfig) {} + private matchDocumentType(config: AvataxConfig): DocumentType { + if (!config.isDocumentRecordingEnabled) { + return DocumentType.SalesOrder; + } + + return DocumentType.SalesInvoice; + } transform({ order }: AvataxOrderFulfilledPayload): AvataxOrderFulfilledTarget { const transactionCode = getTransactionCodeFromMetadata(order.privateMetadata); return { transactionCode, companyCode: this.config.companyCode ?? "", - documentType: DocumentType.SalesInvoice, + documentType: this.matchDocumentType(this.config), model: { commit: true, }, diff --git a/apps/taxes/src/modules/avatax/ui/avatax-configuration-credentials-fragment.tsx b/apps/taxes/src/modules/avatax/ui/avatax-configuration-credentials-fragment.tsx index a1793f1..51092dc 100644 --- a/apps/taxes/src/modules/avatax/ui/avatax-configuration-credentials-fragment.tsx +++ b/apps/taxes/src/modules/avatax/ui/avatax-configuration-credentials-fragment.tsx @@ -121,7 +121,24 @@ export const AvataxConfigurationCredentialsFragment = ( } name="isSandbox" /> - + + When turned off, the document type will always be set to SalesOrder. This + means the transactions will not be recorded in Avatax. Read more{" "} + + here + + . + + } + name="isDocumentRecordingEnabled" + /> {children} diff --git a/apps/taxes/src/modules/taxes/get-active-connection-service.test.ts b/apps/taxes/src/modules/taxes/get-active-connection-service.test.ts index 2c40793..38b7634 100644 --- a/apps/taxes/src/modules/taxes/get-active-connection-service.test.ts +++ b/apps/taxes/src/modules/taxes/get-active-connection-service.test.ts @@ -21,6 +21,7 @@ const mockedProviders: ProviderConnections = [ provider: "avatax", id: "1", config: { + isDocumentRecordingEnabled: true, companyCode: "DEFAULT", isAutocommit: false, isSandbox: true,