diff --git a/.changeset/perfect-lies-float.md b/.changeset/perfect-lies-float.md new file mode 100644 index 0000000..fe58c4c --- /dev/null +++ b/.changeset/perfect-lies-float.md @@ -0,0 +1,5 @@ +--- +"saleor-app-taxes": minor +--- + +Add "Shipping tax code" field to Avatax configuration form. Allows to customize the tax code set on the "shipping" item. 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 421e9eb..0dad227 100644 --- a/apps/taxes/src/modules/app/get-app-config.test.ts +++ b/apps/taxes/src/modules/app/get-app-config.test.ts @@ -17,6 +17,7 @@ const mockedProviders: ProvidersConfig = [ name: "avatax-1", password: "avatax-password", username: "avatax-username", + shippingTaxCode: "FR000000", }, }, { diff --git a/apps/taxes/src/modules/avatax/avatax-config.ts b/apps/taxes/src/modules/avatax/avatax-config.ts index 16e8bad..6340e26 100644 --- a/apps/taxes/src/modules/avatax/avatax-config.ts +++ b/apps/taxes/src/modules/avatax/avatax-config.ts @@ -8,6 +8,7 @@ export const avataxConfigSchema = z.object({ isSandbox: z.boolean(), companyCode: z.string().min(1, { message: "Company code requires at least one character." }), isAutocommit: z.boolean(), + shippingTaxCode: z.string().optional(), }); export type AvataxConfig = z.infer; @@ -19,6 +20,7 @@ export const defaultAvataxConfig: AvataxConfig = { companyCode: "", isSandbox: true, isAutocommit: false, + shippingTaxCode: "", }; export const avataxInstanceConfigSchema = z.object({ diff --git a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts index 81a3583..b681da0 100644 --- a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts +++ b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts @@ -98,6 +98,7 @@ const MOCKED_CALCULATE_TAXES_ARGS: AvataxCalculateTaxesMapPayloadArgs = { name: "Avatax-1", password: "password", username: "username", + shippingTaxCode: "FR000000", }, }; @@ -114,12 +115,16 @@ describe("avataxCalculateTaxesMaps", () => { }); describe("mapLines", () => { it("includes shipping as a line", () => { - const lines = avataxCalculateTaxesMaps.mapLines(MOCKED_CALCULATE_TAXES_ARGS.taxBase); + const lines = avataxCalculateTaxesMaps.mapLines( + MOCKED_CALCULATE_TAXES_ARGS.taxBase, + MOCKED_CALCULATE_TAXES_ARGS.config + ); expect(lines).toContainEqual({ itemCode: avataxCalculateTaxesMaps.shippingItemCode, quantity: 1, amount: 48.33, + taxCode: MOCKED_CALCULATE_TAXES_ARGS.config.shippingTaxCode, }); }); }); diff --git a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts index 89f4ac1..904615a 100644 --- a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts +++ b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts @@ -16,7 +16,7 @@ import { avataxAddressFactory } from "./address-factory"; */ const SHIPPING_ITEM_CODE = "Shipping"; -function mapLines(taxBase: TaxBaseFragment): LineItemModel[] { +function mapLines(taxBase: TaxBaseFragment, config: AvataxConfig): LineItemModel[] { const productLines = taxBase.lines.map((line) => ({ amount: line.unitPrice.amount, taxIncluded: line.chargeTaxes, @@ -30,11 +30,7 @@ function mapLines(taxBase: TaxBaseFragment): LineItemModel[] { const shippingLine: LineItemModel = { amount: taxBase.shippingPrice.amount, itemCode: SHIPPING_ITEM_CODE, - /** - * todo: add taxCode - * * Different shipping methods can have different tax codes - * https://developer.avalara.com/ecommerce-integration-guide/sales-tax-badge/designing/non-standard-items/\ - */ + taxCode: config.shippingTaxCode, quantity: 1, }; @@ -65,7 +61,7 @@ const mapPayload = (props: AvataxCalculateTaxesMapPayloadArgs): CreateTransactio shipTo: avataxAddressFactory.fromSaleorAddress(taxBase.address!), }, currencyCode: taxBase.currency, - lines: mapLines(taxBase), + lines: mapLines(taxBase, config), date: new Date(), }, }; diff --git a/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts index 1d4fc4a..f43e1d7 100644 --- a/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts +++ b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts @@ -5,7 +5,7 @@ import { avataxOrderCreatedMaps, } from "./avatax-order-created-map"; -const MOCKED_ORDER: CreateTransactionMapPayloadArgs = { +const MOCKED_ARGS: CreateTransactionMapPayloadArgs = { order: { id: "T3JkZXI6OTU4MDA5YjQtNDUxZC00NmQ1LThhMWUtMTRkMWRmYjFhNzI5", created: "2023-04-11T11:03:09.304109+00:00", @@ -120,6 +120,7 @@ const MOCKED_ORDER: CreateTransactionMapPayloadArgs = { name: "Avatax-1", password: "user-password", username: "user-name", + shippingTaxCode: "FR000000", }, }; @@ -131,7 +132,7 @@ describe("avataxOrderCreatedMaps", () => { }); describe("mapPayload", () => { it("returns lines with discounted: true when there are discounts", () => { - const payload = avataxOrderCreatedMaps.mapPayload(MOCKED_ORDER); + const payload = avataxOrderCreatedMaps.mapPayload(MOCKED_ARGS); const linesWithoutShipping = payload.model.lines.slice(0, -1); const check = linesWithoutShipping.every((line) => line.discounted === true); @@ -143,7 +144,7 @@ describe("avataxOrderCreatedMaps", () => { it.todo("rounding of numbers"); }); describe("mapLines", () => { - const lines = avataxOrderCreatedMaps.mapLines(MOCKED_ORDER.order); + const lines = avataxOrderCreatedMaps.mapLines(MOCKED_ARGS.order, MOCKED_ARGS.config); it("returns the correct number of lines", () => { expect(lines).toHaveLength(3); @@ -152,6 +153,7 @@ describe("avataxOrderCreatedMaps", () => { it("includes shipping as a line", () => { expect(lines).toContainEqual({ itemCode: avataxOrderCreatedMaps.consts.shippingItemCode, + taxCode: MOCKED_ARGS.config.shippingTaxCode, quantity: 1, amount: 48.33, }); @@ -176,7 +178,7 @@ describe("avataxOrderCreatedMaps", () => { }); describe("mapDiscounts", () => { it("sums up all discounts", () => { - const discounts = avataxOrderCreatedMaps.mapDiscounts(MOCKED_ORDER.order.discounts); + const discounts = avataxOrderCreatedMaps.mapDiscounts(MOCKED_ARGS.order.discounts); expect(discounts).toEqual(31.45); }); diff --git a/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts index 327f3b6..c6ae38a 100644 --- a/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts +++ b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts @@ -14,7 +14,7 @@ import { avataxAddressFactory } from "./address-factory"; */ const SHIPPING_ITEM_CODE = "Shipping"; -function mapLines(order: OrderCreatedSubscriptionFragment): LineItemModel[] { +function mapLines(order: OrderCreatedSubscriptionFragment, config: AvataxConfig): LineItemModel[] { const productLines: LineItemModel[] = order.lines.map((line) => ({ amount: line.unitPrice.net.amount, // todo: get from tax code matcher @@ -32,10 +32,10 @@ function mapLines(order: OrderCreatedSubscriptionFragment): LineItemModel[] { taxIncluded: true, itemCode: SHIPPING_ITEM_CODE, /** - * todo: add taxCode * * Different shipping methods can have different tax codes. * https://developer.avalara.com/ecommerce-integration-guide/sales-tax-badge/designing/non-standard-items/\ */ + taxCode: config.shippingTaxCode, quantity: 1, }; @@ -76,7 +76,7 @@ const mapPayload = ({ }, currencyCode: order.total.currency, email: order.user?.email ?? "", - lines: mapLines(order), + lines: mapLines(order, config), date: new Date(order.created), discount: mapDiscounts(order.discounts), }, diff --git a/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts b/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts index 544d2b5..b611094 100644 --- a/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts +++ b/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts @@ -95,6 +95,7 @@ const MOCKED_MAP_PAYLOAD_ARGS: CommitTransactionMapPayloadArgs = { name: "Avatax-1", password: "user-password", username: "user-name", + shippingTaxCode: "FR000000", }, }; diff --git a/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx b/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx index 3b51020..ce5c6f8 100644 --- a/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx +++ b/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx @@ -37,6 +37,7 @@ const defaultValues: FormValues = { password: "", username: "", name: "", + shippingTaxCode: "", }; export const AvataxConfigurationForm = () => { @@ -159,7 +160,13 @@ export const AvataxConfigurationForm = () => { control={control} defaultValue={defaultValues.name} render={({ field }) => ( - + )} /> {formState.errors.name && ( @@ -227,7 +234,7 @@ export const AvataxConfigurationForm = () => { control={control} defaultValue="" render={({ field }) => ( - + )} /> {formState.errors.username && ( @@ -239,7 +246,9 @@ export const AvataxConfigurationForm = () => { name="password" control={control} defaultValue={defaultValues.password} - render={({ field }) => } + render={({ field }) => ( + + )} /> {formState.errors.password && ( {formState.errors.password.message} @@ -251,13 +260,38 @@ export const AvataxConfigurationForm = () => { control={control} defaultValue={defaultValues.companyCode} render={({ field }) => ( - + )} /> {formState.errors.companyCode && ( {formState.errors.companyCode.message} )} + + ( + + )} + /> + + {"Tax code that for the shipping line sent to Avatax. "} + + Must match Avatax tax codes format. + + + {formState.errors.shippingTaxCode && ( + {formState.errors.shippingTaxCode.message} + )} +
diff --git a/apps/taxes/src/modules/taxes/active-tax-provider.test.ts b/apps/taxes/src/modules/taxes/active-tax-provider.test.ts index 6384ea2..f31ccdc 100644 --- a/apps/taxes/src/modules/taxes/active-tax-provider.test.ts +++ b/apps/taxes/src/modules/taxes/active-tax-provider.test.ts @@ -26,6 +26,7 @@ const mockedProviders: ProvidersConfig = [ name: "avatax-1", password: "avatax-password", username: "avatax-username", + shippingTaxCode: "FR000000", }, }, { diff --git a/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx b/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx index d696289..b078d20 100644 --- a/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx +++ b/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx @@ -162,7 +162,13 @@ export const TaxJarConfigurationForm = () => { control={control} defaultValue={defaultValues.name} render={({ field }) => ( - + )} /> {formState.errors.name && ( @@ -174,7 +180,9 @@ export const TaxJarConfigurationForm = () => { name="apiKey" control={control} defaultValue={defaultValues.apiKey} - render={({ field }) => } + render={({ field }) => ( + + )} /> {formState.errors?.apiKey && ( {formState.errors?.apiKey.message}