feat: new tax api (#979)

* feat: add tax calculation strategy (#885)

* feat: add tax calculation strategy

* fix: adds strategy loader

* fix: eslint ignore

* chore: cleanup

* fix: allow plugin overwrites

* fix: allow plugin overwrites

* fix: fake region

* Update packages/medusa/src/loaders/strategies.ts

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>

* feat: adds tax related db entities + tax provider (#896)

* feat: adds tax related db entities + tax provider

* fix: add tax provider tests

* fix: add tax service unit tests

* fix: tests + migrations

* fix: add inherited tax lines

* chore: rm tax-line repo

* fix: test

* fix: pr comments

* fix: unit test

* feat: totals service to ts (#911)

* feat: adds tax related db entities + tax provider

* fix: add tax provider tests

* fix: add tax service unit tests

* fix: tests + migrations

* feat: totals service to ts

* fix: remove totals.js

* fix: add shipping methods

* fix: add inherited tax lines

* chore: rm tax-line repo

* fix: test

* fix: tests

* fix: tests

* fix: unit test

* fix: adds TotalsServiceProps

* feat: adds integration tests for automatic tax calculation + shipping tax rates (#945)

* feat: adds tax related db entities + tax provider

* fix: add tax provider tests

* fix: add tax service unit tests

* fix: tests + migrations

* feat: totals service to ts

* fix: remove totals.js

* fix: add shipping methods

* fix: add inherited tax lines

* chore: rm tax-line repo

* fix: test

* fix: tests

* fix: tests

* fix: unit test

* fix: integration test helpers

* fix: adds factories + tests automatic tax rates

* fix: remove verbose

* fix: adds TotalsServiceProps

* fix: add shipping tax lines

* fix: add migration for shipping taxes

* fix: integration tests for shipping taxes

* fix: integration tests for shipping taxes

* fix: jsdoc types

* Feat/manual taxes (#950)

* feat: adds tax related db entities + tax provider

* fix: add tax provider tests

* fix: add tax service unit tests

* fix: tests + migrations

* feat: totals service to ts

* fix: remove totals.js

* fix: add shipping methods

* fix: add inherited tax lines

* chore: rm tax-line repo

* fix: test

* fix: tests

* fix: tests

* fix: unit test

* fix: integration test helpers

* fix: adds factories + tests automatic tax rates

* fix: remove verbose

* fix: adds TotalsServiceProps

* fix: add shipping tax lines

* fix: add migration for shipping taxes

* fix: integration tests for shipping taxes

* fix: integration tests for shipping taxes

* fix: add integration tests for manual taxes

* fix: cart service - cleanup jsdoc

* feat: add /carts/id/taxes to manually calculate taxes

* feat: add integration tests for order tax calculations

* fix: unit tests

* fix: merge

* fix: rm verbose

* fix: unit tests

* fix: object -> cartOrOrder

* fix: rounding

* Feat/complete order w tax lines (#951)

* feat: adds tax related db entities + tax provider

* fix: add tax provider tests

* fix: add tax service unit tests

* fix: tests + migrations

* feat: totals service to ts

* fix: remove totals.js

* fix: add shipping methods

* fix: add inherited tax lines

* chore: rm tax-line repo

* fix: test

* fix: tests

* fix: tests

* fix: unit test

* fix: integration test helpers

* fix: adds factories + tests automatic tax rates

* fix: remove verbose

* fix: adds TotalsServiceProps

* fix: add shipping tax lines

* fix: add migration for shipping taxes

* fix: integration tests for shipping taxes

* fix: integration tests for shipping taxes

* fix: add integration tests for manual taxes

* fix: cart service - cleanup jsdoc

* feat: add /carts/id/taxes to manually calculate taxes

* feat: add integration tests for order tax calculations

* feat: adds cart completion strategy + create order w. tax lines

* fix: unit tests

* fix: merge

* fix: rm verbose

* fix: unit tests

* fix: unit tests

* fix: unit tests

* fix: ensure calculation for list orders

* fix: unit tests

* fix: integration tests

* fix: adds cart order type gaurds

* Docs/tax api (#954)

* feat: adds tax related db entities + tax provider

* fix: add tax provider tests

* fix: add tax service unit tests

* fix: tests + migrations

* feat: totals service to ts

* fix: remove totals.js

* fix: add shipping methods

* fix: add inherited tax lines

* chore: rm tax-line repo

* fix: test

* fix: tests

* fix: tests

* fix: unit test

* fix: integration test helpers

* fix: adds factories + tests automatic tax rates

* fix: remove verbose

* fix: adds TotalsServiceProps

* fix: add shipping tax lines

* fix: add migration for shipping taxes

* fix: integration tests for shipping taxes

* fix: integration tests for shipping taxes

* fix: add integration tests for manual taxes

* fix: cart service - cleanup jsdoc

* feat: add /carts/id/taxes to manually calculate taxes

* feat: add integration tests for order tax calculations

* feat: adds cart completion strategy + create order w. tax lines

* fix: unit tests

* fix: merge

* fix: rm verbose

* fix: unit tests

* fix: unit tests

* fix: unit tests

* fix: ensure calculation for list orders

* fix: unit tests

* fix: integration tests

* docs: documents tax related methods and types

* fix: require either item_id or shipping_method_id

* feat: product type tax rate (#969)

* feat: adds tax related db entities + tax provider

* fix: add tax provider tests

* fix: add tax service unit tests

* fix: tests + migrations

* feat: totals service to ts

* fix: remove totals.js

* fix: add shipping methods

* fix: add inherited tax lines

* chore: rm tax-line repo

* fix: test

* fix: tests

* fix: tests

* fix: unit test

* fix: integration test helpers

* fix: adds factories + tests automatic tax rates

* fix: remove verbose

* fix: adds TotalsServiceProps

* fix: add shipping tax lines

* fix: add migration for shipping taxes

* fix: integration tests for shipping taxes

* fix: integration tests for shipping taxes

* fix: add integration tests for manual taxes

* fix: cart service - cleanup jsdoc

* feat: add /carts/id/taxes to manually calculate taxes

* feat: add integration tests for order tax calculations

* feat: adds cart completion strategy + create order w. tax lines

* fix: unit tests

* fix: merge

* fix: rm verbose

* fix: unit tests

* fix: unit tests

* fix: unit tests

* fix: ensure calculation for list orders

* fix: unit tests

* fix: integration tests

* docs: documents tax related methods and types

* fix: require either item_id or shipping_method_id

* feat: adds returns tests for new tax system

* feat: adds return lines + integration tests for swaps

* feat: return integration tests

* feat: adds product type tax rates

* feat: add tax management endpoints

* fix: create single migration

* fix: adds tax rates to js client

* fix: strats

* Fix/plugin tests (#998)

* plugin testing setup

* fix: test sendgrid plugin

* fix: test sendgrid plugin

* chore: clean

* chore: clean

* fix: clean up tests

* fix: remove dirty import

* fix: sendgrid + brightpearl

* fix: plugin integration tests

* fix: klarna

* fix: shipping method tax

* fix: remove taxrates

* fix: unit tests

* fix: integration

* fix: integration

* fix: plugins tests

* fix: ignore plugins

* fix: tests

* fix: taxes (#1017)

* fix: taxes

* fix: taxes

* fix: faulty ref

* fix: create tax-lines with claim items

* fix: snapshot tax-liens

* fix: allows integration test teardown to force deleting tables

* fix: tests

* fix: merge

* fix: adds tax-rates to client

* fix: adds tax-rates to medusa-react

* fix: tests

* fix: tests

* fix: add product types

* fix: adds tax provider endpoint + cascaded deletes on tax rate relations

* fix: move errors to service layer

* fix: cleanup api

* fix: unit tests

* fix: error handler in base-service

* fix: Add order region to swap on createFulfillment (#1110)

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Sebastian Rindom
2022-02-24 20:14:09 +01:00
committed by olivermrbl
parent d80eaa172d
commit 47588e7a8d
223 changed files with 28546 additions and 2229 deletions

View File

@@ -349,7 +349,7 @@ class SendGridService extends NotificationService {
}
}
async orderPlacedData({ id }) {
async orderCanceledData({ id }) {
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
@@ -448,6 +448,131 @@ class SendGridService extends NotificationService {
}
}
async orderPlacedData({ id }) {
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
],
})
const { tax_total, shipping_total, gift_card_total, total } = order
const currencyCode = order.currency_code.toUpperCase()
const items = await Promise.all(
order.items.map(async (i) => {
i.totals = await this.totalsService_.getLineItemTotals(i, order, {
include_tax: true,
use_tax_lines: true,
})
i.thumbnail = this.normalizeThumbUrl_(i.thumbnail)
i.discounted_price = `${this.humanPrice_(
i.totals.total / i.quantity,
currencyCode
)} ${currencyCode}`
i.price = `${this.humanPrice_(
i.totals.original_total / i.quantity,
currencyCode
)} ${currencyCode}`
return i
})
)
let discounts = []
if (order.discounts) {
discounts = order.discounts.map((discount) => {
return {
is_giftcard: false,
code: discount.code,
descriptor: `${discount.rule.value}${
discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
}`,
}
})
}
let giftCards = []
if (order.gift_cards) {
giftCards = order.gift_cards.map((gc) => {
return {
is_giftcard: true,
code: gc.code,
descriptor: `${gc.value} ${currencyCode}`,
}
})
discounts.concat(giftCards)
}
const locale = await this.extractLocale(order)
// Includes taxes in discount amount
const discountTotal = items.reduce((acc, i) => {
return acc + i.totals.original_total - i.totals.total
}, 0)
const discounted_subtotal = items.reduce((acc, i) => {
return acc + i.totals.total
}, 0)
const subtotal = items.reduce((acc, i) => {
return acc + i.totals.original_total
}, 0)
const subtotal_ex_tax = items.reduce((total, i) => {
return total + i.totals.subtotal
}, 0)
return {
...order,
locale,
has_discounts: order.discounts.length,
has_gift_cards: order.gift_cards.length,
date: order.created_at.toDateString(),
items,
discounts,
subtotal_ex_tax: `${this.humanPrice_(
subtotal_ex_tax,
currencyCode
)} ${currencyCode}`,
subtotal: `${this.humanPrice_(subtotal, currencyCode)} ${currencyCode}`,
gift_card_total: `${this.humanPrice_(
gift_card_total,
currencyCode
)} ${currencyCode}`,
tax_total: `${this.humanPrice_(tax_total, currencyCode)} ${currencyCode}`,
discount_total: `${this.humanPrice_(
discountTotal,
currencyCode
)} ${currencyCode}`,
shipping_total: `${this.humanPrice_(
shipping_total,
currencyCode
)} ${currencyCode}`,
total: `${this.humanPrice_(total, currencyCode)} ${currencyCode}`,
}
}
async gcCreatedData({ id }) {
const giftCard = await this.giftCardService_.retrieve(id, {
relations: ["region", "order"],
@@ -475,68 +600,76 @@ class SendGridService extends NotificationService {
relations: [
"items",
"items.item",
"items.item.tax_lines",
"items.item.variant",
"items.item.variant.product",
"shipping_method",
"shipping_method.tax_lines",
"shipping_method.shipping_option",
],
})
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
})
returnRequest.items = returnRequest.items.map((item) => {
const found = items.find((i) => i.id === item.item_id)
return {
...item,
item: found,
}
})
const items = await this.lineItemService_.list(
{
id: returnRequest.items.map(({ item_id }) => item_id),
},
{ relations: ["tax_lines"] }
)
// Fetch the order
const order = await this.orderService_.retrieve(id, {
select: ["total"],
relations: [
"items",
"items.tax_lines",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_address",
"returns",
"swaps",
"swaps.additional_items",
],
})
let merged = [...order.items]
// merge items from order with items from order swaps
if (order.swaps && order.swaps.length) {
for (const s of order.swaps) {
merged = [...merged, ...s.additional_items]
}
}
// Calculate which items are in the return
const returnItems = returnRequest.items.map((i) => {
const found = merged.find((oi) => oi.id === i.item_id)
return {
...found,
quantity: i.quantity,
}
})
const taxRate = order.tax_rate / 100
const currencyCode = order.currency_code.toUpperCase()
// Calculate which items are in the return
const returnItems = await Promise.all(
returnRequest.items.map(async (i) => {
const found = items.find((oi) => oi.id === i.item_id)
found.quantity = i.quantity
found.thumbnail = this.normalizeThumbUrl_(found.thumbnail)
found.totals = await this.totalsService_.getLineItemTotals(
found,
order,
{
include_tax: true,
use_tax_lines: true,
}
)
found.price = `${this.humanPrice_(
found.totals.total,
currencyCode
)} ${currencyCode}`
found.tax_lines = found.totals.tax_lines
return found
})
)
// Get total of the returned products
const item_subtotal = this.totalsService_.getRefundTotal(order, returnItems)
const item_subtotal = returnItems.reduce(
(acc, next) => acc + next.totals.total,
0
)
// If the return has a shipping method get the price and any attachments
let shippingTotal = 0
if (returnRequest.shipping_method) {
shippingTotal = returnRequest.shipping_method.price * (1 + taxRate)
const base = returnRequest.shipping_method.price
shippingTotal =
base +
returnRequest.shipping_method.tax_lines.reduce((acc, next) => {
return Math.round(acc + base * (next.rate / 100))
}, 0)
}
const locale = await this.extractLocale(order)
@@ -545,7 +678,7 @@ class SendGridService extends NotificationService {
locale,
has_shipping: !!returnRequest.shipping_method,
email: order.email,
items: this.processItems_(returnItems, taxRate, currencyCode),
items: returnItems,
subtotal: `${this.humanPrice_(
item_subtotal,
currencyCode
@@ -570,11 +703,12 @@ class SendGridService extends NotificationService {
}
}
async swapCreatedData({ id }) {
async swapReceivedData({ id }) {
const store = await this.storeService_.retrieve()
const swap = await this.swapService_.retrieve(id, {
relations: [
"additional_items",
"additional_items.tax_lines",
"return_order",
"return_order.items",
"return_order.items.item",
@@ -585,9 +719,14 @@ class SendGridService extends NotificationService {
const returnRequest = swap.return_order
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
})
const items = await this.lineItemService_.list(
{
id: returnRequest.items.map(({ item_id }) => item_id),
},
{
relations: ["tax_lines"],
}
)
returnRequest.items = returnRequest.items.map((item) => {
const found = items.find((i) => i.id === item.item_id)
@@ -612,42 +751,51 @@ class SendGridService extends NotificationService {
"shipping_address",
"swaps",
"swaps.additional_items",
"swaps.additional_items.tax_lines",
],
})
const taxRate = order.tax_rate / 100
const cart = await this.cartService_.retrieve(swap.cart_id, {
select: [
"total",
"tax_total",
"discount_total",
"shipping_total",
"subtotal",
],
})
const currencyCode = order.currency_code.toUpperCase()
let merged = [...order.items]
const decoratedItems = await Promise.all(
cart.items.map(async (i) => {
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
include_tax: true,
})
// merge items from order with items from order swaps
if (order.swaps && order.swaps.length) {
for (const s of order.swaps) {
merged = [...merged, ...s.additional_items]
}
}
const returnItems = this.processItems_(
swap.return_order.items.map((i) => {
const found = merged.find((oi) => oi.id === i.item_id)
return {
...found,
quantity: i.quantity,
...i,
totals,
price: this.humanPrice_(
totals.subtotal + totals.tax_total,
currencyCode
),
}
}),
taxRate,
currencyCode
})
)
const returnTotal = this.totalsService_.getRefundTotal(order, returnItems)
const returnTotal = decoratedItems.reduce((acc, next) => {
if (next.is_return) {
return acc + -1 * (next.totals.subtotal + next.totals.tax_total)
}
return acc
}, 0)
const constructedOrder = {
...order,
shipping_methods: [],
items: swap.additional_items,
}
const additionalTotal = this.totalsService_.getTotal(constructedOrder)
const additionalTotal = decoratedItems.reduce((acc, next) => {
if (!next.is_return) {
return acc + next.totals.subtotal + next.totals.tax_total
}
return acc
}, 0)
const refundAmount = swap.return_order.refund_amount
@@ -661,8 +809,143 @@ class SendGridService extends NotificationService {
date: swap.updated_at.toDateString(),
swap_link: swapLink,
email: order.email,
items: this.processItems_(swap.additional_items, taxRate, currencyCode),
return_items: returnItems,
items: decoratedItems.filter((di) => !di.is_return),
return_items: decoratedItems.filter((di) => di.is_return),
return_total: `${this.humanPrice_(
returnTotal,
currencyCode
)} ${currencyCode}`,
tax_total: `${this.humanPrice_(
cart.total,
currencyCode
)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(
refundAmount,
currencyCode
)} ${currencyCode}`,
additional_total: `${this.humanPrice_(
additionalTotal,
currencyCode
)} ${currencyCode}`,
}
}
async swapCreatedData({ id }) {
const store = await this.storeService_.retrieve()
const swap = await this.swapService_.retrieve(id, {
relations: [
"additional_items",
"additional_items.tax_lines",
"return_order",
"return_order.items",
"return_order.items.item",
"return_order.shipping_method",
"return_order.shipping_method.shipping_option",
],
})
const returnRequest = swap.return_order
const items = await this.lineItemService_.list(
{
id: returnRequest.items.map(({ item_id }) => item_id),
},
{
relations: ["tax_lines"],
}
)
returnRequest.items = returnRequest.items.map((item) => {
const found = items.find((i) => i.id === item.item_id)
return {
...item,
item: found,
}
})
const swapLink = store.swap_link_template.replace(
/\{cart_id\}/,
swap.cart_id
)
const order = await this.orderService_.retrieve(swap.order_id, {
select: ["total"],
relations: [
"items",
"items.tax_lines",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_address",
"swaps",
"swaps.additional_items",
"swaps.additional_items.tax_lines",
],
})
const cart = await this.cartService_.retrieve(swap.cart_id, {
select: [
"total",
"tax_total",
"discount_total",
"shipping_total",
"subtotal",
],
})
const currencyCode = order.currency_code.toUpperCase()
const decoratedItems = await Promise.all(
cart.items.map(async (i) => {
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
include_tax: true,
})
return {
...i,
totals,
tax_lines: totals.tax_lines,
price: `${this.humanPrice_(
totals.original_total / i.quantity,
currencyCode
)} ${currencyCode}`,
discounted_price: `${this.humanPrice_(
totals.total / i.quantity,
currencyCode
)} ${currencyCode}`,
}
})
)
const returnTotal = decoratedItems.reduce((acc, next) => {
const { total } = next.totals
if (next.is_return && next.variant_id) {
return acc + -1 * total
}
return acc
}, 0)
const additionalTotal = decoratedItems.reduce((acc, next) => {
const { total } = next.totals
if (!next.is_return) {
return acc + total
}
return acc
}, 0)
const refundAmount = swap.return_order.refund_amount
const locale = await this.extractLocale(order)
return {
locale,
swap,
order,
return_request: returnRequest,
date: swap.updated_at.toDateString(),
swap_link: swapLink,
email: order.email,
items: decoratedItems.filter((di) => !di.is_return),
return_items: decoratedItems.filter((di) => di.is_return),
return_total: `${this.humanPrice_(
returnTotal,
currencyCode
@@ -687,7 +970,9 @@ class SendGridService extends NotificationService {
relations: [
"shipping_address",
"shipping_methods",
"shipping_methods.tax_lines",
"additional_items",
"additional_items.tax_lines",
"return_order",
"return_order.items",
],
@@ -695,40 +980,68 @@ class SendGridService extends NotificationService {
const order = await this.orderService_.retrieve(swap.order_id, {
relations: [
"region",
"items",
"items.tax_lines",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"swaps",
"swaps.additional_items",
"swaps.additional_items.tax_lines",
],
})
let merged = [...order.items]
const cart = await this.cartService_.retrieve(swap.cart_id, {
select: [
"total",
"tax_total",
"discount_total",
"shipping_total",
"subtotal",
],
})
// merge items from order with items from order swaps
if (order.swaps && order.swaps.length) {
for (const s of order.swaps) {
merged = [...merged, ...s.additional_items]
const returnRequest = swap.return_order
const items = await this.lineItemService_.list(
{
id: returnRequest.items.map(({ item_id }) => item_id),
},
{
relations: ["tax_lines"],
}
}
)
const taxRate = order.tax_rate / 100
const currencyCode = order.currency_code.toUpperCase()
const returnItems = this.processItems_(
swap.return_order.items.map((i) => {
const found = merged.find((oi) => oi.id === i.item_id)
const returnItems = await Promise.all(
swap.return_order.items.map(async (i) => {
const found = items.find((oi) => oi.id === i.item_id)
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
include_tax: true,
})
return {
...found,
thumbnail: this.normalizeThumbUrl_(found.thumbnail),
price: `${this.humanPrice_(
totals.original_total / i.quantity,
currencyCode
)} ${currencyCode}`,
discounted_price: `${this.humanPrice_(
totals.total / i.quantity,
currencyCode
)} ${currencyCode}`,
quantity: i.quantity,
}
}),
taxRate,
currencyCode
})
)
const returnTotal = this.totalsService_.getRefundTotal(order, returnItems)
const returnTotal = await this.totalsService_.getRefundTotal(
order,
returnItems
)
const constructedOrder = {
...order,
@@ -736,7 +1049,7 @@ class SendGridService extends NotificationService {
items: swap.additional_items,
}
const additionalTotal = this.totalsService_.getTotal(constructedOrder)
const additionalTotal = await this.totalsService_.getTotal(constructedOrder)
const refundAmount = swap.return_order.refund_amount
@@ -750,11 +1063,31 @@ class SendGridService extends NotificationService {
locale,
swap,
order,
items: this.processItems_(swap.additional_items, taxRate, currencyCode),
items: await Promise.all(
swap.additional_items.map(async (i) => {
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
include_tax: true,
})
return {
...i,
thumbnail: this.normalizeThumbUrl_(i.thumbnail),
price: `${this.humanPrice_(
totals.original_total / i.quantity,
currencyCode
)} ${currencyCode}`,
discounted_price: `${this.humanPrice_(
totals.total / i.quantity,
currencyCode
)} ${currencyCode}`,
quantity: i.quantity,
}
})
),
date: swap.updated_at.toDateString(),
email: order.email,
tax_amount: `${this.humanPrice_(
swap.difference_due * taxRate,
cart.tax_total,
currencyCode
)} ${currencyCode}`,
paid_total: `${this.humanPrice_(
@@ -871,7 +1204,7 @@ class SendGridService extends NotificationService {
if (fromOrder.cart_id) {
try {
const cart = await this.cartService_.retrieve(fromOrder.cart_id, {
select: ["context"],
select: ["id", "context"],
})
if (cart.context && cart.context.locale) {