This commit introduces a new `tax.js` module containing functions for tax rate resolution and amount calculation with tax. The `amountWithTax` and `resolveTaxRate` functions are integrated into various schemas, including `invoice`, `orderitem`, `shipment`, `courierservice`, `filament`, `part`, and `product`, enhancing their ability to compute amounts with applicable taxes. Additionally, corresponding tests are added to ensure the correctness of the new tax-related functionalities.
120 lines
3.8 KiB
JavaScript
120 lines
3.8 KiB
JavaScript
import mongoose from 'mongoose';
|
|
import { generateId } from '../../utils.js';
|
|
import { productModel } from './product.schema.js';
|
|
import { taxRateModel } from './taxrate.schema.js';
|
|
import { editObject, getObject } from '../../database.js';
|
|
import {
|
|
amountWithTax,
|
|
effectiveMarginPrice,
|
|
resolveTaxRate,
|
|
} from '../../tax.js';
|
|
const { Schema } = mongoose;
|
|
|
|
const partSkuUsageSchema = new Schema({
|
|
part: { type: Schema.Types.ObjectId, ref: 'part', required: true },
|
|
partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true },
|
|
quantity: { type: Number, required: true },
|
|
});
|
|
|
|
// Define the main product SKU schema
|
|
const productSkuSchema = new Schema(
|
|
{
|
|
_reference: { type: String, default: () => generateId()() },
|
|
barcode: { type: String, required: false },
|
|
product: { type: Schema.Types.ObjectId, ref: 'product', required: true },
|
|
name: { type: String, required: true },
|
|
description: { type: String, required: false },
|
|
priceMode: { type: String, default: 'margin' },
|
|
price: { type: Number, required: false },
|
|
cost: { type: Number, required: false },
|
|
overrideCost: { type: Boolean, default: false },
|
|
overridePrice: { type: Boolean, default: false },
|
|
margin: { type: Number, required: false },
|
|
amount: { type: Number, required: false },
|
|
parts: { type: [partSkuUsageSchema], default: [] },
|
|
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
|
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
|
priceWithTax: { type: Number, required: false },
|
|
costWithTax: { type: Number, required: false },
|
|
},
|
|
{ timestamps: true }
|
|
);
|
|
|
|
productSkuSchema.index({ name: 'text', barcode: 'text', description: 'text' });
|
|
|
|
// Add virtual id getter
|
|
productSkuSchema.virtual('id').get(function () {
|
|
return this._id;
|
|
});
|
|
|
|
// Configure JSON serialization to include virtuals
|
|
productSkuSchema.set('toJSON', { virtuals: true });
|
|
|
|
productSkuSchema.statics.recalculate = async function (productSku, user) {
|
|
const parent = await getObject({
|
|
model: productModel,
|
|
id: productSku.product?._id || productSku.product,
|
|
cached: true,
|
|
});
|
|
|
|
const taxUpdateData = {};
|
|
|
|
if (productSku.overrideCost) {
|
|
const costTaxRate = await resolveTaxRate(productSku.costTaxRate, getObject, taxRateModel);
|
|
if (productSku.cost != null) {
|
|
taxUpdateData.costWithTax = amountWithTax(productSku.cost, costTaxRate);
|
|
}
|
|
} else if (parent?.costWithTax != null) {
|
|
taxUpdateData.costWithTax = parent.costWithTax;
|
|
}
|
|
|
|
if (productSku.overridePrice) {
|
|
const priceTaxRate = await resolveTaxRate(
|
|
productSku.priceTaxRate ?? parent?.priceTaxRate,
|
|
getObject,
|
|
taxRateModel
|
|
);
|
|
const cost = productSku.overrideCost ? productSku.cost : parent?.cost;
|
|
const price = effectiveMarginPrice({
|
|
priceMode: productSku.priceMode ?? parent?.priceMode,
|
|
price: productSku.price,
|
|
cost,
|
|
margin: productSku.margin ?? parent?.margin,
|
|
});
|
|
if (price != null) {
|
|
taxUpdateData.priceWithTax = amountWithTax(price, priceTaxRate);
|
|
}
|
|
} else if (parent?.priceWithTax != null) {
|
|
taxUpdateData.priceWithTax = parent.priceWithTax;
|
|
}
|
|
|
|
if (Object.keys(taxUpdateData).length > 0) {
|
|
await editObject({
|
|
model: this,
|
|
id: productSku._id,
|
|
updateData: taxUpdateData,
|
|
user,
|
|
recalculate: false,
|
|
});
|
|
Object.assign(productSku, taxUpdateData);
|
|
}
|
|
|
|
const orderItemModel = mongoose.model('orderItem');
|
|
const skuId = productSku._id;
|
|
const draftOrderItems = await orderItemModel
|
|
.find({
|
|
'state.type': 'draft',
|
|
itemType: 'product',
|
|
sku: skuId,
|
|
})
|
|
.populate('order')
|
|
.lean();
|
|
|
|
for (const orderItem of draftOrderItems) {
|
|
await orderItemModel.recalculate(orderItem, user);
|
|
}
|
|
};
|
|
|
|
// Create and export the model
|
|
export const productSkuModel = mongoose.model('productSku', productSkuSchema);
|