saleor-apps-redis_apl/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts
Adrian Pilarczyk 453baf78a1
feat: finalize order process (#355)
* feat:  add dummy order-created

* refactor: 🔥 unused private-providers-configuration-service

* feat:  add dummy order-fulfilled

* refactor: 🚚 move provider-config

* refactor: 🚚 crudSettingsConfigurator -> crudSettingsManager

* refactor: ♻️ [tax-provider].ts -> [tax-provider]-webhook.service.ts

* feat:  add dummy createOrder

* refactor: ♻️ distinguish between salesOrder and salesInvoice in avatax

* refactor: 🚚 [provider]-calculate.ts to [provider]-transform.ts

* refactor: 🚚 ResponseTaxPayload to tax-provider-webhook.ts

* refactor: 🚚 ResponseTaxPayload -> CalculateTaxesResponse

* refactor: ♻️ webhooks with active-tax-provider.service.ts

* feat:  add skeleton orderCreate functionality

* refactor: ♻️ [provider]-transform.ts -> [provider]-[webhook]-transform.ts

* feat:  add order-fulfilled with avatax call

* refactor: ♻️ move getActiveTaxProvider to active-tax-provider

* refactor: 🏷️ export types for [provider]-client function args

* refactor: 🚚 UpdateAppMetadata -> UpdateMetadata

* feat:  fulfill order with id from metadata

* build: ⬆️ upgrade avatax

* feat:  commit transaction on fulfill in avatax

* fix: 🐛 return of webhooks to ensure valid retry behavior

* refactor: 🚚 [provider]-[webhook]-transform -> [provider]-[webhook]-map

* refactor: 🏷️ export types of avatax-calculate-taxes mapPayload

* refactor: ♻️ extract address-map to separate function

* refactor: ♻️ remove schema.ts

* refactor: ♻️ move addressSchema to channels-config.ts

* feat:  add tests & placeholder tests for avatax & taxjar maps

* refactor: ♻️ throw error if no metadata

* refactor: ♻️ change EXTERNAL_ID_KEY to PROVIDER_ORDER_ID_KEY

add comments

* refactor: ♻️ comments -> it.todo in tests

* refactor: 💡 add comment about shipping_item_code

* refactor:  add todo items for tests

* refactor: ♻️ remove export and add sumLines to taxJarOrderCreated

* refactor: ♻️ address-map with avatarAddressFactory

* docs: 💡 add comment about MOCKED_SALEOR_PAYLOAD

* refactor: ♻️ remove export of mapLines and add to avataxCalculateTaxes

* style: 🎨 add newline-after-var warn to eslint-config-saleor

* style: 🎨 autofix newline-after-var in taxes

* test:  restructure tests according to new naming in address-map

* refactor: ♻️ add shippingItemCode to avataxCalculateTaxes wrapper object

* refactor: 🚚 payloadProps -> payloadArgs

* refactor: ♻️ add Maps suffix to map wrapper objects

* refactor: ♻️ remove data: null from ActiveTaxProviderResult

* refactor: ♻️ maintain the object hierarchy in tests

* refactor: ♻️ refactor webhook responses with WebhookResponseFactory

* build: ⬆️ vitest

* test:  add tests for get-app-config-test

* test:  add tests for getActiveTaxProvider

* refactor: ♻️ use address fragment for taxBase and order

* refactor: ♻️ rename WebhookResponseFactory -> WebhookResponse

* style: 👷 add multiline-comment-style

* fix: 🐛 dummy test in get-app-config.test.ts

* refactor: ♻️ rename AddressFragment -> Address

* refactor: ♻️ use debug instead of error in webhook-response noRetry

* refactor: ♻️ refactor as variables in mutation

* build: 👷 add changeset

* refactor: ♻️ split changesets in two

* build: ⬆️ vite

* build: ⬆️ vite && vitest in all apps
2023-04-17 13:58:21 +02:00

112 lines
3.9 KiB
TypeScript

import { LineItemModel } from "avatax/lib/models/LineItemModel";
import { TransactionModel } from "avatax/lib/models/TransactionModel";
import { TaxBaseFragment } from "../../../../generated/graphql";
import { DocumentType } from "avatax/lib/enums/DocumentType";
import { ChannelConfig } from "../../channels-configuration/channels-config";
import { taxLineResolver } from "../../taxes/tax-line-resolver";
import { CalculateTaxesResponse } from "../../taxes/tax-provider-webhook";
import { CreateTransactionArgs } from "../avatax-client";
import { AvataxConfig } from "../avatax-config";
import { avataxAddressFactory } from "./address-factory";
import { numbers } from "../../taxes/numbers";
/**
* * Shipping is a regular line item in Avatax
* https://developer.avalara.com/avatax/dev-guide/shipping-and-handling/taxability-of-shipping-charges/
*/
const SHIPPING_ITEM_CODE = "Shipping";
function mapLines(taxBase: TaxBaseFragment): LineItemModel[] {
const productLines = taxBase.lines.map((line) => ({
amount: line.unitPrice.amount,
taxIncluded: line.chargeTaxes,
// todo: get from tax code matcher
taxCode: taxLineResolver.getLineTaxCode(line),
quantity: line.quantity,
}));
if (taxBase.shippingPrice.amount !== 0) {
// * In Avatax, shipping is a regular line
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/\
*/
quantity: 1,
};
return [...productLines, shippingLine];
}
return productLines;
}
export type AvataxCalculateTaxesMapPayloadArgs = {
taxBase: TaxBaseFragment;
channel: ChannelConfig;
config: AvataxConfig;
};
const mapPayload = (props: AvataxCalculateTaxesMapPayloadArgs): CreateTransactionArgs => {
const { taxBase, channel, config } = props;
return {
model: {
type: DocumentType.SalesOrder,
customerCode: taxBase.sourceObject.user?.id ?? "",
companyCode: config.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
commit: config.isAutocommit,
addresses: {
shipFrom: avataxAddressFactory.fromChannelAddress(channel.address),
shipTo: avataxAddressFactory.fromSaleorAddress(taxBase.address!),
},
currencyCode: taxBase.currency,
lines: mapLines(taxBase),
date: new Date(),
},
};
};
const mapResponse = (transaction: TransactionModel): CalculateTaxesResponse => {
const shippingLine = transaction.lines?.find((line) => line.itemCode === SHIPPING_ITEM_CODE);
const productLines = transaction.lines?.filter((line) => line.itemCode !== SHIPPING_ITEM_CODE);
const shippingGrossAmount = shippingLine?.taxableAmount ?? 0;
const shippingTaxCalculated = shippingLine?.taxCalculated ?? 0;
const shippingNetAmount = numbers.roundFloatToTwoDecimals(
shippingGrossAmount - shippingTaxCalculated
);
return {
shipping_price_gross_amount: shippingGrossAmount,
shipping_price_net_amount: shippingNetAmount,
// todo: add shipping tax rate
shipping_tax_rate: 0,
lines:
productLines?.map((line) => {
const lineTaxCalculated = line.taxCalculated ?? 0;
const lineTotalNetAmount = line.taxableAmount ?? 0;
const lineTotalGrossAmount = numbers.roundFloatToTwoDecimals(
lineTotalNetAmount + lineTaxCalculated
);
return {
total_gross_amount: lineTotalGrossAmount,
total_net_amount: lineTotalNetAmount,
// todo: add tax rate
tax_rate: 0,
};
}) ?? [],
};
};
export const avataxCalculateTaxesMaps = {
mapPayload,
mapResponse,
mapLines,
shippingItemCode: SHIPPING_ITEM_CODE,
};