feat: add shipping tax code (#424)

* feat:  add shippingTaxCode

* build: 👷 add changeset
This commit is contained in:
Adrian Pilarczyk 2023-04-26 14:40:46 +02:00 committed by GitHub
parent 9eacc88b53
commit 3347a305cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 76 additions and 21 deletions

View file

@ -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.

View file

@ -17,6 +17,7 @@ const mockedProviders: ProvidersConfig = [
name: "avatax-1",
password: "avatax-password",
username: "avatax-username",
shippingTaxCode: "FR000000",
},
},
{

View file

@ -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<typeof avataxConfigSchema>;
@ -19,6 +20,7 @@ export const defaultAvataxConfig: AvataxConfig = {
companyCode: "",
isSandbox: true,
isAutocommit: false,
shippingTaxCode: "",
};
export const avataxInstanceConfigSchema = z.object({

View file

@ -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,
});
});
});

View file

@ -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(),
},
};

View file

@ -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);
});

View file

@ -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),
},

View file

@ -95,6 +95,7 @@ const MOCKED_MAP_PAYLOAD_ARGS: CommitTransactionMapPayloadArgs = {
name: "Avatax-1",
password: "user-password",
username: "user-name",
shippingTaxCode: "FR000000",
},
};

View file

@ -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 }) => (
<TextField type="text" {...field} label="Instance name" {...textFieldProps} />
<TextField
required
type="text"
{...field}
label="Instance name"
{...textFieldProps}
/>
)}
/>
{formState.errors.name && (
@ -227,7 +234,7 @@ export const AvataxConfigurationForm = () => {
control={control}
defaultValue=""
render={({ field }) => (
<TextField type="text" {...field} label="Username" {...textFieldProps} />
<TextField required type="text" {...field} label="Username" {...textFieldProps} />
)}
/>
{formState.errors.username && (
@ -239,7 +246,9 @@ export const AvataxConfigurationForm = () => {
name="password"
control={control}
defaultValue={defaultValues.password}
render={({ field }) => <TextField label="Password" {...field} {...textFieldProps} />}
render={({ field }) => (
<TextField required label="Password" {...field} {...textFieldProps} />
)}
/>
{formState.errors.password && (
<FormHelperText error>{formState.errors.password.message}</FormHelperText>
@ -251,13 +260,38 @@ export const AvataxConfigurationForm = () => {
control={control}
defaultValue={defaultValues.companyCode}
render={({ field }) => (
<TextField type="text" {...field} label="Company code" {...textFieldProps} />
<TextField
required
type="text"
{...field}
label="Company code"
{...textFieldProps}
/>
)}
/>
{formState.errors.companyCode && (
<FormHelperText error>{formState.errors.companyCode.message}</FormHelperText>
)}
</Grid>
<Grid item xs={12}>
<Controller
name="shippingTaxCode"
control={control}
defaultValue={defaultValues.shippingTaxCode}
render={({ field }) => (
<TextField type="text" {...field} label="Shipping tax code" {...textFieldProps} />
)}
/>
<FormHelperText>
{"Tax code that for the shipping line sent to Avatax. "}
<AppLink href="https://taxcode.avatax.avalara.com">
Must match Avatax tax codes format.
</AppLink>
</FormHelperText>
{formState.errors.shippingTaxCode && (
<FormHelperText error>{formState.errors.shippingTaxCode.message}</FormHelperText>
)}
</Grid>
</Grid>
<br />
<div className={styles.reverseRow}>

View file

@ -26,6 +26,7 @@ const mockedProviders: ProvidersConfig = [
name: "avatax-1",
password: "avatax-password",
username: "avatax-username",
shippingTaxCode: "FR000000",
},
},
{

View file

@ -162,7 +162,13 @@ export const TaxJarConfigurationForm = () => {
control={control}
defaultValue={defaultValues.name}
render={({ field }) => (
<TextField type="text" {...field} label="Instance name" {...textFieldProps} />
<TextField
required
type="text"
{...field}
label="Instance name"
{...textFieldProps}
/>
)}
/>
{formState.errors.name && (
@ -174,7 +180,9 @@ export const TaxJarConfigurationForm = () => {
name="apiKey"
control={control}
defaultValue={defaultValues.apiKey}
render={({ field }) => <TextField label="API Key" {...field} {...textFieldProps} />}
render={({ field }) => (
<TextField required label="API Key" {...field} {...textFieldProps} />
)}
/>
{formState.errors?.apiKey && (
<FormHelperText error>{formState.errors?.apiKey.message}</FormHelperText>