Tom Butcher 9435d5c64c
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
Add rollup statistics functionality to sales schemas
This commit enhances the `client`, `listing`, and `marketplace` schemas by introducing rollup statistics and history tracking capabilities. New rollup configurations are added to count various states, such as 'active', 'inactive', and others specific to each schema. The `stats` and `history` static methods are implemented to aggregate data using the newly introduced `aggregateRollups` and `aggregateRollupsHistory` functions, improving data analysis and reporting for sales entities.
2026-08-24 23:02:47 +01:00

228 lines
6.5 KiB
JavaScript

import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
import { editObject, newObject, deleteObject, aggregateRollups, aggregateRollupsHistory } from '../../database.js';
const { Schema } = mongoose;
const listingSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
product: { type: Schema.Types.ObjectId, ref: 'product', required: false },
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
stockLocation: { type: Schema.Types.ObjectId, ref: 'stockLocation', required: true },
marketplace: { type: Schema.Types.ObjectId, ref: 'marketplace', required: true },
title: { type: String, required: false },
state: {
type: {
type: String,
enum: ['draft', 'active', 'inactive', 'deleted', 'suspended', 'syncing'],
default: 'draft',
},
message: { type: String, required: false },
},
url: { type: String, required: false },
description: { type: String, required: false },
externalReference: { type: String, required: false },
price: { type: Number, required: false },
currency: { type: String, required: false },
lastSyncedAt: { type: Date, required: false },
stockQuantity: { type: Number, required: false, default: 0 },
condition: {
type: String,
enum: [
'new',
'likeNew',
'newOther',
'newWithDefects',
'manufacturerRefurbished',
'certifiedRefurbished',
'excellentRefurbished',
'veryGoodRefurbished',
'goodRefurbished',
'sellerRefurbished',
'usedExcellent',
'usedVeryGood',
'usedGood',
'usedAcceptable',
'forPartsOrNotWorking',
'preOwnedExcellent',
'preOwnedFair',
],
default: 'new',
required: false,
},
courierServices: [{ type: Schema.Types.ObjectId, ref: 'courierService', required: true }],
},
{ timestamps: true }
);
listingSchema.index({ title: 'text', url: 'text' });
listingSchema.index({ marketplace: 1, externalReference: 1 }, { unique: true, sparse: true });
listingSchema.virtual('id').get(function () {
return this._id;
});
listingSchema.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;
},
});
const refId = (value) => value?._id ?? value;
listingSchema.statics.recalculate = async function (listing, user) {
const listingId = refId(listing);
if (!listingId) {
return;
}
const listingVarientModel = mongoose.model('listingVarient');
const productSkuModel = mongoose.model('productSku');
const listingProductId = refId(listing.product);
const findVarients = () =>
listingVarientModel.find({ listing: listingId }).sort({ createdAt: 1 }).lean();
let varients = await findVarients();
if (listingProductId) {
const productSkus = await productSkuModel
.find({ product: listingProductId })
.sort({ createdAt: 1 })
.lean();
const varientsBySkuId = new Map();
const unmatchedVarients = [];
for (const varient of varients) {
const skuId = refId(varient.productSku);
if (skuId && !varientsBySkuId.has(String(skuId))) {
varientsBySkuId.set(String(skuId), varient);
} else {
unmatchedVarients.push(varient);
}
}
for (const sku of productSkus) {
const skuId = sku._id;
const varientUpdateData = {
product: listingProductId,
productSku: skuId,
};
const existingVarient = varientsBySkuId.get(String(skuId)) || unmatchedVarients.shift();
if (existingVarient) {
varientsBySkuId.delete(String(skuId));
const existingProductId = refId(existingVarient.product);
const existingSkuId = refId(existingVarient.productSku);
if (
String(existingProductId) === String(listingProductId) &&
String(existingSkuId) === String(skuId)
) {
continue;
}
const varientResult = await editObject({
model: listingVarientModel,
id: existingVarient._id,
updateData: varientUpdateData,
user,
recalculate: false,
});
if (varientResult.error) {
throw varientResult;
}
} else {
const varientResult = await newObject({
model: listingVarientModel,
newData: {
...varientUpdateData,
listing: listingId,
state: { type: listing.state?.type || 'draft' },
},
user,
recalculate: false,
});
if (varientResult.error) {
throw varientResult;
}
}
}
for (const extra of [...varientsBySkuId.values(), ...unmatchedVarients]) {
const deleteResult = await deleteObject({
model: listingVarientModel,
id: extra._id,
user,
});
if (deleteResult.error) {
throw deleteResult;
}
}
varients = await findVarients();
}
for (const varient of varients) {
await listingVarientModel.recalculate(varient, user);
}
};
const rollupConfigs = [
{
name: 'draft',
filter: { 'state.type': 'draft' },
rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }],
},
{
name: 'active',
filter: { 'state.type': 'active' },
rollups: [{ name: 'active', property: 'state.type', operation: 'count' }],
},
{
name: 'inactive',
filter: { 'state.type': 'inactive' },
rollups: [{ name: 'inactive', property: 'state.type', operation: 'count' }],
},
{
name: 'syncing',
filter: { 'state.type': 'syncing' },
rollups: [{ name: 'syncing', property: 'state.type', operation: 'count' }],
},
{
name: 'suspended',
filter: { 'state.type': 'suspended' },
rollups: [{ name: 'suspended', property: 'state.type', operation: 'count' }],
},
{
name: 'deleted',
filter: { 'state.type': 'deleted' },
rollups: [{ name: 'deleted', property: 'state.type', operation: 'count' }],
},
];
listingSchema.statics.stats = async function () {
const results = await aggregateRollups({
model: this,
rollupConfigs: rollupConfigs,
});
return results;
};
listingSchema.statics.history = async function (from, to) {
const results = await aggregateRollupsHistory({
model: this,
startDate: from,
endDate: to,
rollupConfigs: rollupConfigs,
});
return results;
};
export const listingModel = mongoose.model('listing', listingSchema);