feat: add shipping tax code (#424)
* feat: ✨ add shippingTaxCode * build: 👷 add changeset
This commit is contained in:
parent
9eacc88b53
commit
3347a305cd
11 changed files with 76 additions and 21 deletions
5
.changeset/perfect-lies-float.md
Normal file
5
.changeset/perfect-lies-float.md
Normal 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.
|
|
@ -17,6 +17,7 @@ const mockedProviders: ProvidersConfig = [
|
|||
name: "avatax-1",
|
||||
password: "avatax-password",
|
||||
username: "avatax-username",
|
||||
shippingTaxCode: "FR000000",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -95,6 +95,7 @@ const MOCKED_MAP_PAYLOAD_ARGS: CommitTransactionMapPayloadArgs = {
|
|||
name: "Avatax-1",
|
||||
password: "user-password",
|
||||
username: "user-name",
|
||||
shippingTaxCode: "FR000000",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -26,6 +26,7 @@ const mockedProviders: ProvidersConfig = [
|
|||
name: "avatax-1",
|
||||
password: "avatax-password",
|
||||
username: "avatax-username",
|
||||
shippingTaxCode: "FR000000",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue