Skip to content

Commit

Permalink
feat(core): OrderItem.unitPrice now _always_ excludes tax
Browse files Browse the repository at this point in the history
Relates to #467. This change means that storefronts do not need to do any tax calculations and can
reliably list `unitPrice` and `unitPriceWithTax` and know that the actual net/gross prices
are being listed.

BREAKING CHANGE: The `OrderItem.unitPrice` is now _always_ given as the net (without tax) price
of the related ProductVariant. Formerly, it was either the net or gross price, depending on
the `pricesIncludeTax` setting of the Channel. If you have existing Orders where
`unitPriceIncludesTax = true`, you will need to manually update the `unitPrice` value *before*
running any other migrations for this release. The query will look like:

    `UPDATE order_item SET unitPrice = ROUND(unitPrice / ((taxRate + 100) / 100)) WHERE unitPriceIncludesTax = 1`
  • Loading branch information
michaelbromley committed Oct 30, 2020
1 parent a53f27e commit 6e2d490
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 185 deletions.
2 changes: 1 addition & 1 deletion packages/core/e2e/shipping-method.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const calculatorWithMetadata = new ShippingCalculator({
code: 'calculator-with-metadata',
description: [{ languageCode: LanguageCode.en, value: 'Has metadata' }],
args: {},
calculate: order => {
calculate: () => {
return {
price: 100,
priceWithTax: 100,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/api/schema/type/order.type.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type OrderItem implements Node {
cancelled: Boolean!
unitPrice: Int!
unitPriceWithTax: Int!
unitPriceIncludesTax: Boolean!
unitPriceIncludesTax: Boolean! @deprecated(reason: "`unitPrice` is now always without tax")
taxRate: Float!
adjustments: [Adjustment!]!
fulfillment: Fulfillment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('FSM validateTransitionDefinition()', () => {
},
};

const result = validateTransitionDefinition(orderStateTransitions, 'AddingItems');
const result = validateTransitionDefinition(orderStateTransitions, 'Created');

expect(result.valid).toBe(true);
});
Expand Down
58 changes: 22 additions & 36 deletions packages/core/src/entity/order-item/order-item.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Adjustment, AdjustmentType } from '@vendure/common/lib/generated-types';
import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
import { Column, Entity, ManyToOne, OneToOne, RelationId } from 'typeorm';
import { Column, Entity, ManyToOne, OneToOne } from 'typeorm';

import { Calculated } from '../../common/calculated-decorator';
import { VendureEntity } from '../base/base.entity';
Expand Down Expand Up @@ -28,7 +28,11 @@ export class OrderItem extends VendureEntity {

@Column() readonly unitPrice: number;

@Column() unitPriceIncludesTax: boolean;
/**
* @deprecated
* TODO: remove once the field has been removed from the GraphQL type
*/
unitPriceIncludesTax = false;

@Column({ type: 'decimal', precision: 5, scale: 2, transformer: new DecimalTransformer() })
taxRate: number;
Expand All @@ -55,11 +59,7 @@ export class OrderItem extends VendureEntity {

@Calculated()
get unitPriceWithTax(): number {
if (this.unitPriceIncludesTax) {
return this.unitPrice;
} else {
return Math.round(this.unitPrice * ((100 + this.taxRate) / 100));
}
return Math.round(this.unitPrice * ((100 + this.taxRate) / 100));
}

/**
Expand All @@ -70,44 +70,30 @@ export class OrderItem extends VendureEntity {
if (!this.pendingAdjustments) {
return [];
}
if (this.unitPriceIncludesTax) {
return this.pendingAdjustments;
} else {
return this.pendingAdjustments.map(a => {
if (a.type === AdjustmentType.PROMOTION) {
// Add the tax that would have been payable on the discount so that the numbers add up
// for the end-user.
const adjustmentWithTax = Math.round(a.amount * ((100 + this.taxRate) / 100));
return {
...a,
amount: adjustmentWithTax,
};
}
return a;
});
}
return this.pendingAdjustments.map(a => {
if (a.type === AdjustmentType.PROMOTION) {
// Add the tax that would have been payable on the discount so that the numbers add up
// for the end-user.
const adjustmentWithTax = Math.round(a.amount * ((100 + this.taxRate) / 100));
return {
...a,
amount: adjustmentWithTax,
};
}
return a;
});
}

/**
* This is the actual, final price of the OrderItem payable by the customer.
*/
get unitPriceWithPromotionsAndTax(): number {
if (this.unitPriceIncludesTax) {
return this.unitPriceWithPromotions;
} else {
return this.unitPriceWithPromotions + this.unitTax;
}
return this.unitPriceWithPromotions + this.unitTax;
}

get unitTax(): number {
if (this.unitPriceIncludesTax) {
return Math.round(
this.unitPriceWithPromotions - this.unitPriceWithPromotions / ((100 + this.taxRate) / 100),
);
} else {
const taxAdjustment = this.adjustments.find(a => a.type === AdjustmentType.TAX);
return taxAdjustment ? taxAdjustment.amount : 0;
}
const taxAdjustment = this.adjustments.find(a => a.type === AdjustmentType.TAX);
return taxAdjustment ? taxAdjustment.amount : 0;
}

get promotionAdjustmentsTotal(): number {
Expand Down
18 changes: 0 additions & 18 deletions packages/core/src/entity/order-line/order-line.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,6 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
return (this.items || []).filter(i => !i.cancelled);
}

/**
* Sets whether the unitPrice of each OrderItem in the line includes tax.
*/
setUnitPriceIncludesTax(includesTax: boolean) {
this.activeItems.forEach(item => {
item.unitPriceIncludesTax = includesTax;
});
}

/**
* Sets the tax rate being applied to each Orderitem in this line.
*/
setTaxRate(taxRate: number) {
this.activeItems.forEach(item => {
item.taxRate = taxRate;
});
}

/**
* Clears Adjustments from all OrderItems of the given type. If no type
* is specified, then all adjustments are removed.
Expand Down
Loading

0 comments on commit 6e2d490

Please sign in to comment.