All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good
- Introduced a new test suite for stock quantity recalculation in `stockQuantity.recalculate.test.js`, ensuring comprehensive coverage of listing variant recalculation logic. - Enhanced the `filamentstock`, `partstock`, and `productstock` schemas by adding new rollup configurations for tracking unconsumed, used, and consumed states. - Implemented a pre-validation hook in the `partstock` and `productstock` schemas to automatically set the `part` field based on the associated `partSku`. - Updated the `stockevent` schema to include new rollup configurations and statistics methods for better inventory management. - Improved the `listing` and `listingvarient` schemas with additional recalculation logic and statistics methods to enhance data integrity and reporting capabilities.
150 lines
4.6 KiB
JavaScript
150 lines
4.6 KiB
JavaScript
import mongoose from 'mongoose';
|
|
import { generateId } from '../../utils.js';
|
|
import { aggregateRollups, editObject } from '../../database.js';
|
|
const { Schema } = mongoose;
|
|
|
|
const toId = (value) => {
|
|
if (value == null) return null;
|
|
if (typeof value === 'object' && value._id) return String(value._id);
|
|
return String(value);
|
|
};
|
|
|
|
const listingVarientSchema = new Schema(
|
|
{
|
|
_reference: { type: String, default: () => generateId()() },
|
|
listing: { type: Schema.Types.ObjectId, ref: 'listing', required: true },
|
|
product: { type: Schema.Types.ObjectId, ref: 'product', required: false },
|
|
productSku: { type: Schema.Types.ObjectId, ref: 'productSku', required: false },
|
|
state: {
|
|
type: {
|
|
type: String,
|
|
enum: ['draft', 'active', 'inactive', 'deleted', 'suspended', 'syncing'],
|
|
default: 'draft',
|
|
},
|
|
message: { type: String, required: false },
|
|
},
|
|
externalReference: { type: String, required: false },
|
|
price: { type: Number, required: false },
|
|
currency: { type: String, required: false },
|
|
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
|
priceWithTax: { type: Number, required: false },
|
|
lastSyncedAt: { type: Date, required: false },
|
|
stockQuantity: { type: Number, required: false, default: 0 },
|
|
},
|
|
{ timestamps: true }
|
|
);
|
|
|
|
listingVarientSchema.index({ currency: 'text', 'state.type': 'text' });
|
|
listingVarientSchema.index(
|
|
{ listing: 1, externalReference: 1 },
|
|
{
|
|
unique: true,
|
|
name: 'listing_1_externalReference_1',
|
|
partialFilterExpression: { externalReference: { $type: 'string', $gt: '' } },
|
|
}
|
|
);
|
|
|
|
listingVarientSchema.virtual('id').get(function () {
|
|
return this._id;
|
|
});
|
|
|
|
listingVarientSchema.set('toJSON', {
|
|
virtuals: true,
|
|
transform(doc, ret) {
|
|
if (!ret.state && ret.status) {
|
|
ret.state = { type: ret.status, message: null };
|
|
}
|
|
if (ret.status) delete ret.status;
|
|
return ret;
|
|
},
|
|
});
|
|
|
|
listingVarientSchema.statics.recalculate = async function (listingVarient, user) {
|
|
const listingId = listingVarient?.listing?._id || listingVarient?.listing;
|
|
const varientId = listingVarient?._id;
|
|
const productSkuId = toId(listingVarient?.productSku);
|
|
|
|
if (varientId && productSkuId && (await this.exists({ _id: varientId }))) {
|
|
let listing = listingVarient.listing;
|
|
if (!listing?.stockLocation) {
|
|
listing = await mongoose
|
|
.model('listing')
|
|
.findById(listingId)
|
|
.select('stockLocation product')
|
|
.lean();
|
|
}
|
|
const stockLocationId = toId(listing?.stockLocation);
|
|
if (stockLocationId) {
|
|
const stockRollup = await aggregateRollups({
|
|
model: mongoose.model('productStock'),
|
|
baseFilter: {
|
|
productSku: new mongoose.Types.ObjectId(productSkuId),
|
|
stockLocation: new mongoose.Types.ObjectId(stockLocationId),
|
|
},
|
|
rollupConfigs: [
|
|
{
|
|
name: 'stockQuantity',
|
|
rollups: [{ name: 'stockQuantity', property: 'currentQuantity', operation: 'sum' }],
|
|
},
|
|
],
|
|
});
|
|
const stockQuantity = stockRollup.stockQuantity?.sum || 0;
|
|
if (listingVarient.stockQuantity !== stockQuantity) {
|
|
await editObject({
|
|
model: this,
|
|
id: varientId,
|
|
updateData: { stockQuantity },
|
|
user,
|
|
recalculate: false,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!listingId) {
|
|
return;
|
|
}
|
|
|
|
const rollupResults = await aggregateRollups({
|
|
model: this,
|
|
baseFilter: { listing: new mongoose.Types.ObjectId(listingId) },
|
|
rollupConfigs: [
|
|
{
|
|
name: 'stockQuantity',
|
|
rollups: [{ name: 'stockQuantity', property: 'stockQuantity', operation: 'sum' }],
|
|
},
|
|
],
|
|
});
|
|
|
|
await editObject({
|
|
model: mongoose.model('listing'),
|
|
id: listingId,
|
|
updateData: {
|
|
stockQuantity: rollupResults.stockQuantity?.sum || 0,
|
|
},
|
|
user,
|
|
recalculate: false,
|
|
});
|
|
};
|
|
|
|
export const listingVarientModel = mongoose.model('listingVarient', listingVarientSchema);
|
|
|
|
async function replaceSparseExternalReferenceIndex() {
|
|
try {
|
|
const indexes = await listingVarientModel.collection.indexes();
|
|
const current = indexes.find((idx) => idx.name === 'listing_1_externalReference_1');
|
|
if (current && (current.sparse || !current.partialFilterExpression)) {
|
|
await listingVarientModel.collection.dropIndex('listing_1_externalReference_1');
|
|
}
|
|
await listingVarientModel.createIndexes();
|
|
} catch {
|
|
// Collection/index may not exist until Mongo is connected.
|
|
}
|
|
}
|
|
|
|
if (mongoose.connection.readyState === 1) {
|
|
replaceSparseExternalReferenceIndex();
|
|
} else {
|
|
mongoose.connection.once('open', replaceSparseExternalReferenceIndex);
|
|
}
|