Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/35439 integrate vat tax include in price config into service application #13

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ It follows the folder structure to ensure certification & deployment from commer
* insert commercetools credentials to `.env` file
* run `./bin/ngrok.sh` or `ngrok http 8080` to start ngrok and insert the dynamic url in the `.env` file as specified in post-deploy script
* run `yarn connector:post-deploy` to register the extension with the public ngrok url
* run `ỳarn start:dev` to build the application
* run `yarn start:dev` to build the application

## Architecture principles for building an connect application

* Connector solution should be lightweight in nature
* Connector solutions should follow test driven development. Unit , Integration (& E2E) tests should be included and successfully passed to be used
* No hardcoding of customer related config. If needed, values in an environment file which should not be maintained in repository
* Connector solution should be supported with detailed documentation
* Connectors should be point to point in nature, currently doesnt support any persistence capabilities apart from in memory persistence
* Connectors should be point to point in nature, currently doesn't support any persistence capabilities apart from in memory persistence
* Connector solution should use open source technologies, although connector itself can be private for specific customer(s)
* Code should not contain console.log statements, use [the included logger](https://github.com/commercetools/merchant-center-application-kit/tree/main/packages-backend/loggers#readme) instead.
12 changes: 7 additions & 5 deletions event/src/avalara/requests/actions/commit.transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import { AddressInfo } from 'avatax/lib/models/AddressInfo';

export async function commitTransaction(
order: Order,
creds: { [key: string]: string },
credentials: { [key: string]: string },
originAddress: AddressInfo,
config: any
config: any,
pricesIncludesTax: boolean
) {
if (!['US', 'CA'].includes(order?.shippingAddress?.country || 'none')) {
return undefined;
}
const client = new AvaTaxClient(config).withSecurity(creds);
const client = new AvaTaxClient(config).withSecurity(credentials);

const taxDocument = await processOrder(
'commit',
order,
creds?.companyCode,
originAddress
credentials?.companyCode,
originAddress,
pricesIncludesTax
);

const taxResponse = await client.createTransaction({ model: taxDocument });
Expand Down
15 changes: 9 additions & 6 deletions event/src/avalara/requests/actions/refund.transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,35 @@ import { TaxOverrideModel } from 'avatax/lib/models/TaxOverrideModel';
import { getOrder } from '../../../client/data.client';
import { AddressInfo } from 'avatax/lib/models/AddressInfo';
import { processOrder } from '../preprocess/preprocess.order';
import { TaxOverrideType } from 'avatax/lib/enums/TaxOverrideType';

export async function refundTransaction(
orderId: string,
creds: { [key: string]: string },
credentials: { [key: string]: string },
originAddress: AddressInfo,
config: any
config: any,
pricesIncludesTax: boolean
) {
const order = await getOrder(orderId);

if (!['US', 'CA'].includes(order?.shippingAddress?.country || 'none')) {
return undefined;
}
const client = new AvaTaxClient(config).withSecurity(creds);
const client = new AvaTaxClient(config).withSecurity(credentials);

const taxDocument = await processOrder(
'refund',
order,
creds?.companyCode,
originAddress
credentials?.companyCode,
originAddress,
pricesIncludesTax
);

taxDocument.referenceCode = 'Refund';

const taxModel = new TaxOverrideModel();
taxModel.taxDate = new Date(order.createdAt);
taxModel.type = 3;
taxModel.type = TaxOverrideType.TaxDate;
taxModel.reason = 'Refund';
taxDocument.taxOverride = taxModel;

Expand Down
12 changes: 7 additions & 5 deletions event/src/avalara/requests/actions/void.transaction.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import AvaTaxClient from 'avatax/lib/AvaTaxClient';
import { VoidTransactionModel } from 'avatax/lib/models/VoidTransactionModel';
import { getOrder } from '../../../client/data.client';
import { DocumentType } from 'avatax/lib/enums/DocumentType';
import { VoidReasonCode } from 'avatax/lib/enums/VoidReasonCode';

export async function voidTransaction(
orderId: string,
creds: { [key: string]: string },
credentials: { [key: string]: string },
config: any
) {
const order = await getOrder(orderId);

if (!['US', 'CA'].includes(order?.shippingAddress?.country || 'none')) {
return undefined;
}
const client = new AvaTaxClient(config).withSecurity(creds);
const client = new AvaTaxClient(config).withSecurity(credentials);

const voidModel = new VoidTransactionModel();
voidModel.code = 3;
voidModel.code = VoidReasonCode.DocVoided;
const voidBody = {
companyCode: creds.companyCode,
companyCode: credentials.companyCode,
transactionCode: order?.orderNumber || orderId,
documentType: 1,
documentType: DocumentType.SalesInvoice,
model: voidModel,
};

Expand Down
55 changes: 55 additions & 0 deletions event/src/avalara/requests/postprocess/postprocess.order.commit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
OrderSetCustomTypeAction,
OrderSetLineItemCustomTypeAction,
} from '@commercetools/platform-sdk';
import { TransactionModel } from 'avatax/lib/models/TransactionModel';
import { performOrderUpdateActions } from '../../../client/data.client';
import { logger } from '../../../utils/logger.utils';

function buildCustomTypeAction(
action: string,
customTypeId: string,
fields: object
) {
return {
action,
type: {
id: customTypeId,
typeId: 'type',
},
fields,
};
}

export async function postProcessing(
orderId: string,
orderVersion: number,
transaction: TransactionModel | undefined
) {
if (!transaction) {
return;
}
const actions: Array<
OrderSetLineItemCustomTypeAction | OrderSetCustomTypeAction
> = [];

for (const item of transaction?.lines || []) {
actions.push({
...buildCustomTypeAction('setLineItemCustomType', 'vat-code', {
vatCode: item.vatCode,
}),
lineItemId: item.itemCode,
} as OrderSetLineItemCustomTypeAction);
}

if (transaction.invoiceMessages) {
actions.push({
630N marked this conversation as resolved.
Show resolved Hide resolved
...buildCustomTypeAction('setCustomType', 'invoices-messages', {
invoiceMessages: transaction.invoiceMessages,
}),
} as OrderSetCustomTypeAction);
}
await performOrderUpdateActions(orderId, orderVersion, actions).catch(
(error) => logger.error(error)
);
}
29 changes: 29 additions & 0 deletions event/src/avalara/requests/postprocess/postprocess.order.refund.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { OrderSetCustomTypeAction } from '@commercetools/platform-sdk';
import { TransactionModel } from 'avatax/lib/models/TransactionModel';
import { performOrderUpdateActions } from '../../../client/data.client';
import { logger } from '../../../utils/logger.utils';

export async function postProcessing(
orderId: string,
orderVersion: number,
transaction: TransactionModel | undefined
) {
if (!transaction) {
return;
}
const actions: Array<OrderSetCustomTypeAction> = [];

if (transaction.invoiceMessages) {
actions.push({
action: 'setCustomType',
type: {
id: 'invoices-messages',
typeId: 'type',
},
fields: { invoiceMessages: transaction.invoiceMessages },
} as OrderSetCustomTypeAction);
}
await performOrderUpdateActions(orderId, orderVersion, actions).catch(
(error) => logger.error(error)
);
}
36 changes: 31 additions & 5 deletions event/src/avalara/requests/preprocess/preprocess.order.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { LineItem, Order } from '@commercetools/platform-sdk';
import { getCustomerEntityUseCode } from '../../../client/data.client';
import {
getCustomerEntityUseCode,
getCustomerVatId,
} from '../../../client/data.client';
import { CreateTransactionModel } from 'avatax/lib/models/CreateTransactionModel';
import { lineItem } from '../../utils/line.items';
import { shippingAddress } from '../../utils/shipping.address';
import { shipItem } from '../../utils/shipping.info';
import { AddressInfo } from 'avatax/lib/models/AddressInfo';
import { getCategoryTaxCodes } from './get.categories';
import { DocumentType } from 'avatax/lib/enums/DocumentType';
import { TransactionParameterModel } from 'avatax/lib/models/TransactionParameterModel';

// initialize and specify the tax document model of Avalara
export async function processOrder(
type: string,
order: Order,
companyCode: string,
originAddress: AddressInfo
originAddress: AddressInfo,
pricesIncludesTax: boolean
): Promise<CreateTransactionModel> {
const taxDocument = new CreateTransactionModel();

Expand All @@ -21,13 +27,18 @@ export async function processOrder(

const shipTo = shippingAddress(order?.shippingAddress);

const shippingInfo = await shipItem(type, order?.shippingInfo);
const shippingInfo = await shipItem(
type,
order?.shippingInfo,
pricesIncludesTax
);

const itemCategoryTaxCodes = await getCategoryTaxCodes(order?.lineItems);

const lines = await Promise.all(
order?.lineItems.map(
async (x: LineItem) => await lineItem(type, x, itemCategoryTaxCodes)
async (x: LineItem) =>
await lineItem(type, x, itemCategoryTaxCodes, pricesIncludesTax)
)
);

Expand All @@ -45,7 +56,10 @@ export async function processOrder(

taxDocument.companyCode = companyCode;

taxDocument.type = type === 'refund' ? 5 : 1;
taxDocument.type =
type === 'refund'
? DocumentType.ReturnInvoice
: DocumentType.SalesInvoice;

taxDocument.currencyCode = order?.totalPrice?.currencyCode;

Expand All @@ -57,6 +71,18 @@ export async function processOrder(
};
taxDocument.entityUseCode = customerInfo?.exemptCode;
taxDocument.lines = lines;

taxDocument.businessIdentificationNo = await getCustomerVatId(
order?.customerId
);
taxDocument.parameters = [
{
name: 'Transport',
value:
taxDocument.type === DocumentType.SalesInvoice ? 'Seller' : 'Buyer',
unit: '',
} as TransactionParameterModel,
];
}

return taxDocument;
Expand Down
5 changes: 3 additions & 2 deletions event/src/avalara/utils/line.items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ function itemTaxCode(item: LineItem) {
export async function lineItem(
type: string,
item: LineItem,
catTaxCodes: [{ [key: string]: string }]
catTaxCodes: [{ [key: string]: string }],
pricesIncludesTax: boolean
) {
const lineItem = new LineItemModel();

Expand All @@ -33,7 +34,7 @@ export async function lineItem(

lineItem.itemCode = item?.variant?.sku;

lineItem.taxIncluded = item?.taxRate?.includedInPrice;
lineItem.taxIncluded = pricesIncludesTax;
lineItem.taxCode =
itemTaxCode(item) ??
catTaxCodes?.find((x) => x?.sku === item?.variant?.sku)?.taxCode;
Expand Down
8 changes: 6 additions & 2 deletions event/src/avalara/utils/shipping.info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ import { LineItemModel } from 'avatax/lib/models/LineItemModel';
import { getShipTaxCode } from '../../client/data.client';

// Mapping CT LineItem Model to Avalara LineItem Model
export async function shipItem(type: string, item: ShippingInfo) {
export async function shipItem(
type: string,
item: ShippingInfo,
pricesIncludesTax: boolean
) {
const lineItem = new LineItemModel();
const taxCode = await getShipTaxCode(item.shippingMethod?.id || '');
lineItem.quantity = 1;
lineItem.amount =
((type === 'refund' ? -1 : 1) * item.price.centAmount) / 100;
lineItem.description = item.shippingMethodName;
lineItem.itemCode = 'Shipping';
lineItem.taxIncluded = item.taxRate?.includedInPrice;
lineItem.taxIncluded = pricesIncludesTax;
lineItem.taxCode = taxCode || 'FR010000';

return lineItem;
Expand Down
25 changes: 25 additions & 0 deletions event/src/client/data.client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createApiRoot } from './create.client';
import { OrderUpdateAction } from '@commercetools/platform-sdk';

export const getData = async (container: string) => {
const data = (
Expand Down Expand Up @@ -83,3 +84,27 @@ export const getOrder = async (id: string) => {
return (await createApiRoot().orders().withId({ ID: id }).get().execute())
.body;
};

export const performOrderUpdateActions = async (
id: string,
version: number,
actions: Array<OrderUpdateAction>
) => {
return (
await createApiRoot()
.orders()
.withId({ ID: id })
.post({ body: { version, actions } })
.execute()
).body;
};

export const getCustomerVatId = async (id?: string) => {
if (!id) {
return '';
}
return (
(await createApiRoot().customers().withId({ ID: id }).get().execute())?.body
?.vatId || ''
);
};
Loading