Skip to content
This repository has been archived by the owner on May 28, 2023. It is now read-only.

Bugfix/2918 #289

Merged
merged 12 commits into from
Jul 4, 2019
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.11.0-rc.1] - UNRELEASED

### Fixed
- The `product.price_*` fields have been normalized with the backward compatibility support (see `config.tax.deprecatedPriceFieldsSupport` which is by default true) - @pkarw (#289)
- The `product.final_price` field is now being taken into product price calcualtion. Moreover, we've added the `config.tax.finalPriceIncludesTax` - which is set to `true` by default. All the `price`, `original_price` and `special_price` fields are calculated accordingly. It was required as Magento2 uses `final_price` to set the catalog pricing rules after-prices - @pkarw (#289)

## [1.10.0-rc.1] - UNRELEASED

### Added
Expand Down
4 changes: 3 additions & 1 deletion config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@
"tax": {
"defaultCountry": "DE",
"defaultRegion": "",
"deprecatedPriceFieldsSupport": true,
"calculateServerSide": true,
"sourcePriceIncludesTax": false
"sourcePriceIncludesTax": false,
"finalPriceIncludesTax": false
},
"i18n": {
"fullCountryName": "Germany",
Expand Down
220 changes: 160 additions & 60 deletions src/lib/taxcalc.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
function isSpecialPriceActive(fromDate, toDate) {
function isSpecialPriceActive (fromDate, toDate) {
const now = new Date()
fromDate = fromDate ? new Date(fromDate) : false
toDate = toDate ? new Date(toDate) : false

if (!fromDate && !toDate) {
return true
}

if (fromDate && toDate) {
return fromDate < now && toDate > now
}
Expand All @@ -20,40 +20,81 @@ function isSpecialPriceActive(fromDate, toDate) {
}
}

export function updateProductPrices (product, rate, sourcePriceInclTax = false) {
const rateFactor = parseFloat(rate.rate) / 100
export function updateProductPrices (product, rate, sourcePriceInclTax = false, deprecatedPriceFieldsSupport = false, finalPriceInclTax = true) {
const rate_factor = parseFloat(rate.rate) / 100
if (finalPriceInclTax) {
product.final_price_incl_tax = parseFloat(product.final_price) // final price does include tax
product.final_price = product.final_price_incl_tax / (1 + rate_factor)
product.final_price_tax = product.final_price_incl_tax - product.final_price
} else {
product.final_price = parseFloat(product.final_price) // final price does include tax
product.final_price_tax = product.final_price * rate_factor
product.final_price_incl_tax = product.final_price + product.final_price_tax
}
product.price = parseFloat(product.price)
product.special_price = parseFloat(product.special_price)

let priceExclTax = product.price
if (product.final_price) {
if (product.final_price < product.price) { // compare the prices with the product final price if provided; final prices is used in case of active catalog promo rules for example
if (product.final_price < product.special_price) { // for VS - special_price is any price lowered than regular price (`price`); in Magento there is a separate mechanism for setting the `special_prices`
product.price = product.special_price // if the `final_price` is lower than the original `special_price` - it means some catalog rules were applied over it
}
product.special_to_date = null
product.special_from_date = null
product.special_price = product.final_price
} else {
product.price = product.final_price
}
}

let price_excl_tax = product.price
if (sourcePriceInclTax) {
priceExclTax = product.price / (1 + rateFactor)
product.price = priceExclTax
price_excl_tax = product.price / (1 + rate_factor)
product.price = price_excl_tax
}

product.priceTax = priceExclTax * rateFactor
product.priceInclTax = priceExclTax + product.priceTax
product.price_tax = price_excl_tax * rate_factor
product.price_incl_tax = price_excl_tax + product.price_tax

let specialPriceExclTax = product.special_price
let special_price_excl_tax = product.special_price
if (sourcePriceInclTax) {
specialPriceExclTax = product.special_price / (1 + rateFactor)
product.special_price = specialPriceExclTax
special_price_excl_tax = product.special_price / (1 + rate_factor)
product.special_price = special_price_excl_tax
}

product.specialPriceTax = specialPriceExclTax * rateFactor
product.specialPriceInclTax = specialPriceExclTax + product.specialPriceTax
product.special_price_tax = special_price_excl_tax * rate_factor
product.special_price_incl_tax = special_price_excl_tax + product.special_price_tax

if (deprecatedPriceFieldsSupport) {
/** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
product.priceTax = product.price_tax
product.priceInclTax = product.price_incl_tax
product.specialPriceTax = product.special_price_tax
product.specialPriceInclTax = product.special_price_incl_tax
/** END */
}

if (product.special_price && (product.special_price < product.price)) {
if (!isSpecialPriceActive(product.special_from_date, product.special_to_date)) {
product.special_price = 0 // out of the dates period
} else {
product.originalPrice = priceExclTax
product.originalPriceInclTax = product.priceInclTax
product.originalPriceTax = product.priceTax
product.original_price = price_excl_tax
product.original_price_incl_tax = product.price_incl_tax
product.original_price_tax = product.price_tax

product.price = special_price_excl_tax
product.price_incl_tax = product.special_price_incl_tax
product.price_tax = product.special_price_tax

product.price = specialPriceExclTax
product.priceInclTax = product.specialPriceInclTax
product.priceTax = product.specialPriceTax
if (deprecatedPriceFieldsSupport) {
/** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
product.priceInclTax = product.price_incl_tax
product.priceTax = product.price_tax
product.originalPrice = product.original_price
product.originalPriceInclTax = product.original_price_incl_tax
product.originalPriceTax = product.original_price_tax
/** END */
}
}
} else {
product.special_price = 0 // the same price as original; it's not a promotion
Expand All @@ -68,90 +109,149 @@ export function updateProductPrices (product, rate, sourcePriceInclTax = false)
}
configurableChild.price = parseFloat(configurableChild.price)
configurableChild.special_price = parseFloat(configurableChild.special_price)
configurableChild.final_price_incl_tax = parseFloat(configurableChild.final_price) // final price does include tax
configurableChild.final_price = configurableChild.final_price_incl_tax / (1 + rate_factor)

let priceExclTax = configurableChild.price
if (configurableChild.final_price) {
if (configurableChild.final_price < configurableChild.price) { // compare the prices with the product final price if provided; final prices is used in case of active catalog promo rules for example
if (configurableChild.final_price < configurableChild.special_price) { // for VS - special_price is any price lowered than regular price (`price`); in Magento there is a separate mechanism for setting the `special_prices`
configurableChild.price = configurableChild.special_price // if the `final_price` is lower than the original `special_price` - it means some catalog rules were applied over it
}
configurableChild.special_to_date = null
configurableChild.special_from_date = null
configurableChild.special_price = product.final_price
} else {
configurableChild.price = configurableChild.final_price
}
}

let price_excl_tax = configurableChild.price
if (sourcePriceInclTax) {
priceExclTax = configurableChild.price / (1 + rateFactor)
configurableChild.price = priceExclTax
price_excl_tax = configurableChild.price / (1 + rate_factor)
configurableChild.price = price_excl_tax
}

configurableChild.priceTax = priceExclTax * rateFactor
configurableChild.priceInclTax = priceExclTax + configurableChild.priceTax
configurableChild.price_tax = price_excl_tax * rate_factor
configurableChild.price_incl_tax = price_excl_tax + configurableChild.price_tax

let specialPriceExclTax = parseFloat(configurableChild.special_price)
let special_price_excl_tax = parseFloat(configurableChild.special_price)

if (sourcePriceInclTax) {
specialPriceExclTax = configurableChild.special_price / (1 + rateFactor)
configurableChild.special_price = specialPriceExclTax
special_price_excl_tax = configurableChild.special_price / (1 + rate_factor)
configurableChild.special_price = special_price_excl_tax
}

configurableChild.specialPriceTax = specialPriceExclTax * rateFactor
configurableChild.specialPriceInclTax = specialPriceExclTax + configurableChild.specialPriceTax
configurableChild.special_price_tax = special_price_excl_tax * rate_factor
configurableChild.special_price_incl_tax = special_price_excl_tax + configurableChild.special_price_tax

if (deprecatedPriceFieldsSupport) {
/** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
configurableChild.priceTax = configurableChild.price_tax
configurableChild.priceInclTax = configurableChild.price_incl_tax
configurableChild.specialPriceTax = configurableChild.special_price_tax
configurableChild.specialPriceInclTax = configurableChild.special_price_incl_tax
/** END */
}

if (configurableChild.special_price && (configurableChild.special_price < configurableChild.price)) {
if (!isSpecialPriceActive(configurableChild.special_from_date, configurableChild.special_to_date)) {
configurableChild.special_price = 0 // out of the dates period
} else {
configurableChild.originalPrice = priceExclTax
configurableChild.originalPriceInclTax = configurableChild.priceInclTax
configurableChild.originalPriceTax = configurableChild.priceTax
configurableChild.original_price = price_excl_tax
configurableChild.original_price_incl_tax = configurableChild.price_incl_tax
configurableChild.original_price_tax = configurableChild.price_tax

configurableChild.price = specialPriceExclTax
configurableChild.priceInclTax = configurableChild.specialPriceInclTax
configurableChild.priceTax = configurableChild.specialPriceTax
configurableChild.price = special_price_excl_tax
configurableChild.price_incl_tax = configurableChild.special_price_incl_tax
configurableChild.price_tax = configurableChild.special_price_tax

if (deprecatedPriceFieldsSupport) {
/** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
configurableChild.originalPrice = configurableChild.original_price
configurableChild.originalPriceInclTax = configurableChild.original_price_incl_tax
configurableChild.originalPriceTax = configurableChild.original_price_tax
configurableChild.priceInclTax = configurableChild.price_incl_tax
configurableChild.priceTax = configurableChild.price_tax
/** END */
}
}
} else {
configurableChild.special_price = 0
}

if (configurableChild.priceInclTax < product.priceInclTax || product.price === 0) { // always show the lowest price
product.priceInclTax = configurableChild.priceInclTax
product.priceTax = configurableChild.priceTax
if ((configurableChild.price_incl_tax <= product.price_incl_tax) || product.price === 0) { // always show the lowest price
product.price_incl_tax = configurableChild.price_incl_tax
product.price_tax = configurableChild.price_tax
product.price = configurableChild.price
product.special_price = configurableChild.special_price
product.specialPriceInclTax = configurableChild.specialPriceInclTax
product.specialPriceTax = configurableChild.specialPriceTax
product.originalPrice = configurableChild.originalPrice
product.originalPriceInclTax = configurableChild.originalPriceInclTax
product.originalPriceTax = configurableChild.originalPriceTax
product.special_price_incl_tax = configurableChild.special_price_incl_tax
product.special_price_tax = configurableChild.special_price_tax
product.original_price = configurableChild.original_price
product.original_price_incl_tax = configurableChild.original_price_incl_tax
product.original_price_tax = configurableChild.original_price_tax

if (deprecatedPriceFieldsSupport) {
/** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
product.priceInclTax = product.price_incl_tax
product.priceTax = product.price_tax
product.specialPriceInclTax = product.special_price_incl_tax
product.specialPriceTax = product.special_price_tax
product.originalPrice = product.original_price
product.originalPriceInclTax = product.original_price_incl_tax
product.originalPriceTax = product.original_price_tax
/** END */
}
}
}
}
}

export function calculateProductTax (product, taxClasses, taxCountry = 'PL', taxRegion = '', sourcePriceInclTax = false) {
export function calculateProductTax (product, taxClasses, taxCountry = 'PL', taxRegion = '', sourcePriceInclTax = false, deprecatedPriceFieldsSupport = false, finalPriceInclTax = true) {
let rateFound = false
if (product.tax_class_id > 0) {
let taxClass = taxClasses.find((el) => el.product_tax_class_ids.indexOf(parseInt(product.tax_class_id) >= 0))
if (taxClass) {
for (let rate of taxClass.rates) { // TODO: add check for zip code ranges (!)
if (rate.tax_country_id === taxCountry && (rate.region_name === taxRegion || rate.tax_region_id === 0 || !rate.region_name)) {
updateProductPrices(product, rate, sourcePriceInclTax)
updateProductPrices(product, rate, sourcePriceInclTax, deprecatedPriceFieldsSupport)
rateFound = true
console.debug('Tax rate ' + rate.code + ' = ' + rate.rate + '% found for ' + taxCountry + ' / ' + taxRegion)
break
}
}
} else {
console.debug('No such tax class id: ' + product.tax_class_id)
}
} else {
console.debug('No tax class set for: ' + product.sku)
}
if (!rateFound) {
console.log('No such tax class id: ' + product.tax_class_id + ' or rate not found for ' + taxCountry + ' / ' + taxRegion)
updateProductPrices(product, {rate: 0})

product.priceInclTax = product.price
product.priceTax = 0
product.specialPriceInclTax = 0
product.specialPriceTax = 0
product.price_incl_tax = product.price
product.price_tax = 0
product.special_price_incl_tax = 0
product.special_price_tax = 0

if (deprecatedPriceFieldsSupport) {
/** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
product.priceInclTax = product.price
product.priceTax = 0
product.specialPriceInclTax = 0
product.specialPriceTax = 0
/** END */
}

if (product.configurable_children) {
for (let configurableChildren of product.configurable_children) {
configurableChildren.priceInclTax = configurableChildren.price
configurableChildren.priceTax = 0
configurableChildren.specialPriceInclTax = 0
configurableChildren.specialPriceTax = 0
configurableChildren.price_incl_tax = configurableChildren.price
configurableChildren.price_tax = 0
configurableChildren.special_price_incl_tax = 0
configurableChildren.special_price_tax = 0

if (deprecatedPriceFieldsSupport) {
/** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
configurableChildren.priceInclTax = configurableChildren.price
configurableChildren.priceTax = 0
configurableChildren.specialPriceInclTax = 0
configurableChildren.specialPriceTax = 0
/** END */
}
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/platform/magento1/tax.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ const bodybuilder = require('bodybuilder')
import TierHelper from '../../helpers/priceTiers'

class TaxProxy extends AbstractTaxProxy {
constructor (config, entityType, indexName, taxCountry, taxRegion = '', sourcePriceInclTax = null) {
constructor (config, entityType, indexName, taxCountry, taxRegion = '', sourcePriceInclTax = null, finalPriceInclTax = null) {
super(config)
this._entityType = entityType
this._indexName = indexName
this._sourcePriceInclTax = sourcePriceInclTax
this._finalPriceInclTax = finalPriceInclTax

if (this._config.storeViews && this._config.storeViews.multistore) {
for (let storeCode in this._config.storeViews){
Expand All @@ -21,6 +22,7 @@ class TaxProxy extends AbstractTaxProxy {
taxRegion = store.tax.defaultRegion
taxCountry = store.tax.defaultCountry
sourcePriceInclTax = store.tax.sourcePriceIncludesTax
finalPriceInclTax = store.tax.finalPriceIncludesTax
break;
}
}
Expand All @@ -37,15 +39,20 @@ class TaxProxy extends AbstractTaxProxy {
if (sourcePriceInclTax === null) {
sourcePriceInclTax = this._config.tax.sourcePriceIncludesTax
}
if (finalPriceInclTax === null) {
finalPriceInclTax = this._config.tax.finalPriceIncludesTax
}
this._deprecatedPriceFieldsSupport = this._config.tax.deprecatedPriceFieldsSupport
this._taxCountry = taxCountry
this._taxRegion = taxRegion
this._sourcePriceInclTax = sourcePriceInclTax
this._finalPriceInclTax = finalPriceInclTax
console.log('Taxes will be calculated for', taxCountry, taxRegion, sourcePriceInclTax)
this.taxFor = this.taxFor.bind(this)
}

taxFor (product) {
return calculateProductTax(product, this._taxClasses, this._taxCountry, this._taxRegion, this._sourcePriceInclTax)
return calculateProductTax(product, this._taxClasses, this._taxCountry, this._taxRegion, this._sourcePriceInclTax, this._deprecatedPriceFieldsSupport, this._finalPriceInclTax)
}

applyTierPrices (productList, groupId) {
Expand Down
Loading