Compare commits

..

No commits in common. "2b660ec61d1bae4fac26cd488c20dbdf4b1aea78" and "0b39eef0458196aab68f998784da1d683f3f8478" have entirely different histories.

4 changed files with 113 additions and 246 deletions

View File

@ -1,7 +1,26 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { generateId } from '../../utils.js'; import { generateId } from '../../utils.js';
const { Schema } = mongoose; const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js';
import { stockEventModel } from './stockevent.schema.js';
const getStockEventTotal = async (stock, parentType) => {
const stockId = stock?._id;
if (!stockId) return null;
const parentId =
stockId instanceof mongoose.Types.ObjectId ? stockId : new mongoose.Types.ObjectId(stockId);
const [result] = await stockEventModel.aggregate([
{ $match: { parent: parentId, parentType } },
{ $group: { _id: null, total: { $sum: '$value' }, count: { $sum: 1 } } },
]);
return {
total: result?.total ?? 0,
count: result?.count ?? 0,
};
};
// Define the main filamentStock schema // Define the main filamentStock schema
const filamentStockSchema = new Schema( const filamentStockSchema = new Schema(
@ -19,15 +38,6 @@ const filamentStockSchema = new Schema(
net: { type: Number, required: true }, net: { type: Number, required: true },
gross: { type: Number, required: true }, gross: { type: Number, required: true },
}, },
history: [
{
currentWeight: {
net: { type: Number, required: true },
gross: { type: Number, required: true },
},
timestamp: { type: Date, default: Date.now },
},
],
filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament', required: true }, filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament', required: true },
filamentSku: { type: mongoose.Schema.Types.ObjectId, ref: 'filamentSku', required: true }, filamentSku: { type: mongoose.Schema.Types.ObjectId, ref: 'filamentSku', required: true },
stockLocation: { stockLocation: {
@ -81,6 +91,29 @@ filamentStockSchema.statics.history = async function (from, to) {
return results; return results;
}; };
filamentStockSchema.statics.recalculate = async function (filamentStock, user) {
const events = await getStockEventTotal(filamentStock, this.modelName);
if (!events?.count) return;
const net = events.total;
const startingNet = filamentStock.startingWeight?.net ?? 0;
const startingGross = filamentStock.startingWeight?.gross ?? 0;
const gross = startingNet > 0 ? (startingGross * net) / startingNet : net;
await editObject({
model: this,
id: filamentStock._id,
updateData: {
currentWeight: {
net,
gross,
},
},
user,
recalculate: false,
});
};
// Add virtual id getter // Add virtual id getter
filamentStockSchema.virtual('id').get(function () { filamentStockSchema.virtual('id').get(function () {
return this._id; return this._id;

View File

@ -1,7 +1,26 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { generateId } from '../../utils.js'; import { generateId } from '../../utils.js';
const { Schema } = mongoose; const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js';
import { stockEventModel } from './stockevent.schema.js';
const getStockEventTotal = async (stock, parentType) => {
const stockId = stock?._id;
if (!stockId) return null;
const parentId =
stockId instanceof mongoose.Types.ObjectId ? stockId : new mongoose.Types.ObjectId(stockId);
const [result] = await stockEventModel.aggregate([
{ $match: { parent: parentId, parentType } },
{ $group: { _id: null, total: { $sum: '$value' }, count: { $sum: 1 } } },
]);
return {
total: result?.total ?? 0,
count: result?.count ?? 0,
};
};
// Define the main partStock schema // Define the main partStock schema
const partStockSchema = new Schema( const partStockSchema = new Schema(
@ -18,12 +37,6 @@ const partStockSchema = new Schema(
required: false, required: false,
}, },
currentQuantity: { type: Number, required: true }, currentQuantity: { type: Number, required: true },
history: [
{
currentQuantity: { type: Number, required: true },
timestamp: { type: Date, default: Date.now },
},
],
sourceType: { type: String, required: true }, sourceType: { type: String, required: true },
source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true }, source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true },
}, },
@ -61,6 +74,21 @@ partStockSchema.statics.history = async function (from, to) {
return results; return results;
}; };
partStockSchema.statics.recalculate = async function (partStock, user) {
const events = await getStockEventTotal(partStock, this.modelName);
if (!events?.count) return;
await editObject({
model: this,
id: partStock._id,
updateData: {
currentQuantity: events.total,
},
user,
recalculate: false,
});
};
// Add virtual id getter // Add virtual id getter
partStockSchema.virtual('id').get(function () { partStockSchema.virtual('id').get(function () {
return this._id; return this._id;

View File

@ -1,7 +1,26 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { generateId } from '../../utils.js'; import { generateId } from '../../utils.js';
const { Schema } = mongoose; const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js';
import { stockEventModel } from './stockevent.schema.js';
const getStockEventTotal = async (stock, parentType) => {
const stockId = stock?._id;
if (!stockId) return null;
const parentId =
stockId instanceof mongoose.Types.ObjectId ? stockId : new mongoose.Types.ObjectId(stockId);
const [result] = await stockEventModel.aggregate([
{ $match: { parent: parentId, parentType } },
{ $group: { _id: null, total: { $sum: '$value' }, count: { $sum: 1 } } },
]);
return {
total: result?.total ?? 0,
count: result?.count ?? 0,
};
};
const partStockUsageSchema = new Schema({ const partStockUsageSchema = new Schema({
partStock: { type: Schema.Types.ObjectId, ref: 'partStock', required: false }, partStock: { type: Schema.Types.ObjectId, ref: 'partStock', required: false },
@ -25,12 +44,6 @@ const productStockSchema = new Schema(
required: false, required: false,
}, },
currentQuantity: { type: Number, required: true }, currentQuantity: { type: Number, required: true },
history: [
{
currentQuantity: { type: Number, required: true },
timestamp: { type: Date, default: Date.now },
},
],
partStocks: [partStockUsageSchema], partStocks: [partStockUsageSchema],
}, },
{ timestamps: true } { timestamps: true }
@ -76,6 +89,21 @@ productStockSchema.statics.history = async function (from, to) {
return results; return results;
}; };
productStockSchema.statics.recalculate = async function (productStock, user) {
const events = await getStockEventTotal(productStock, this.modelName);
if (!events?.count) return;
await editObject({
model: this,
id: productStock._id,
updateData: {
currentQuantity: events.total,
},
user,
recalculate: false,
});
};
// Add virtual id getter // Add virtual id getter
productStockSchema.virtual('id').get(function () { productStockSchema.virtual('id').get(function () {
return this._id; return this._id;

View File

@ -1,199 +1,7 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { generateId } from '../../utils.js'; import { generateId } from '../../utils.js';
import { getObject, editObject } from '../../database.js';
const { Schema } = mongoose; const { Schema } = mongoose;
const parentStockModelNames = {
filamentStock: 'filamentStock',
partStock: 'partStock',
productStock: 'productStock',
};
const initialStockStates = {
filamentStock: 'unconsumed',
partStock: 'new',
productStock: 'posted',
};
const getStartingAmount = (parentType, parentStock) => {
if (parentType === 'filamentStock') {
return parentStock.startingWeight?.net ?? 0;
}
return parentStock.startingQuantity ?? 0;
};
const buildParentState = (parentType, parentStock, currentAmount, startingAmount) => {
if (parentStock.state?.type === 'draft') {
return undefined;
}
const fullState = initialStockStates[parentType];
if (!fullState) {
return undefined;
}
if (currentAmount <= 0) {
return { ...parentStock.state, type: 'consumed', progress: 0 };
}
if (startingAmount <= 0) {
return undefined;
}
const progress = currentAmount / startingAmount;
console.log('progress', progress);
if (currentAmount === startingAmount) {
return { ...parentStock.state, type: fullState, progress: 1 };
}
if (currentAmount < startingAmount) {
return { ...parentStock.state, type: 'used', progress };
}
return { ...parentStock.state, type: fullState, progress: 1 };
};
const getStockEventTotal = async (parentId, parentType) => {
if (!parentId) return null;
const objectId =
parentId instanceof mongoose.Types.ObjectId ? parentId : new mongoose.Types.ObjectId(parentId);
const [result] = await mongoose
.model('stockEvent')
.aggregate([
{ $match: { parent: objectId, parentType } },
{ $group: { _id: null, total: { $sum: '$value' }, count: { $sum: 1 } } },
]);
return {
total: result?.total ?? 0,
count: result?.count ?? 0,
};
};
const buildParentUpdateData = (parentType, parentStock, events) => {
const updateData = {};
let currentAmount;
if (parentType === 'filamentStock') {
const net = events.total;
const startingNet = parentStock.startingWeight?.net ?? 0;
const startingGross = parentStock.startingWeight?.gross ?? 0;
const gross = startingNet > 0 ? (startingGross * net) / startingNet : net;
updateData.currentWeight = { net, gross };
currentAmount = net;
} else {
updateData.currentQuantity = events.total;
currentAmount = events.total;
}
const state = buildParentState(
parentType,
parentStock,
currentAmount,
getStartingAmount(parentType, parentStock)
);
if (state) {
updateData.state = state;
}
return updateData;
};
const HISTORY_RATE_LIMIT_MS = 3000;
const isWithinHistoryRateLimit = (lastEntry, timestamp = new Date()) => {
if (!lastEntry?.timestamp) return false;
const elapsed = new Date(timestamp).getTime() - new Date(lastEntry.timestamp).getTime();
return elapsed < HISTORY_RATE_LIMIT_MS;
};
const getLastParentHistoryValue = (parentType, history = []) => {
const lastEntry = history.at(-1);
if (!lastEntry) return undefined;
return parentType === 'filamentStock' ? lastEntry.currentWeight : lastEntry.currentQuantity;
};
const parentValuesEqual = (parentType, a, b) => {
if (a === b) return true;
if (a == null || b == null) return false;
if (parentType === 'filamentStock') {
return a.net === b.net && a.gross === b.gross;
}
return a === b;
};
const buildParentHistoryEntry = (parentType, currentValue, timestamp) => {
if (parentType === 'filamentStock') {
return { currentWeight: currentValue, timestamp };
}
return { currentQuantity: currentValue, timestamp };
};
const appendParentHistoryIfChanged = (
parentType,
parentStock,
updateData,
timestamp = new Date()
) => {
const history = parentStock.history || [];
const lastEntry = history.at(-1);
const currentValue =
parentType === 'filamentStock' ? updateData.currentWeight : updateData.currentQuantity;
const lastHistoryValue = getLastParentHistoryValue(parentType, history);
if (parentValuesEqual(parentType, currentValue, lastHistoryValue)) {
return updateData;
}
if (isWithinHistoryRateLimit(lastEntry, timestamp)) {
return updateData;
}
return {
...updateData,
history: [...history, buildParentHistoryEntry(parentType, currentValue, timestamp)],
};
};
const recalculateParentStock = async (parentType, parentId, user) => {
if (!parentType || !parentId) return;
const modelName = parentStockModelNames[parentType];
if (!modelName) return;
const parentModel = mongoose.model(modelName);
const parentStock = await getObject({
model: parentModel,
id: parentId,
cached: true,
});
if (!parentStock || parentStock.error) return;
const events = await getStockEventTotal(parentId, parentType);
if (!events?.count) return;
await editObject({
model: parentModel,
id: parentStock._id,
updateData: appendParentHistoryIfChanged(
parentType,
parentStock,
buildParentUpdateData(parentType, parentStock, events)
),
user,
recalculate: false,
});
};
const stockEventSchema = new Schema( const stockEventSchema = new Schema(
{ {
_reference: { type: String, default: () => generateId()() }, _reference: { type: String, default: () => generateId()() },
@ -219,12 +27,6 @@ const stockEventSchema = new Schema(
required: true, required: true,
enum: ['user', 'subJob', 'stockAudit', 'stockTransfer'], enum: ['user', 'subJob', 'stockAudit', 'stockTransfer'],
}, },
history: [
{
value: { type: Number, required: true },
timestamp: { type: Date, default: Date.now },
},
],
timestamp: { type: Date, default: Date.now }, timestamp: { type: Date, default: Date.now },
}, },
{ timestamps: true } { timestamps: true }
@ -232,30 +34,6 @@ const stockEventSchema = new Schema(
stockEventSchema.index({ parentType: 'text', ownerType: 'text', unit: 'text' }); stockEventSchema.index({ parentType: 'text', ownerType: 'text', unit: 'text' });
stockEventSchema.statics.recalculate = async function (stockEvent, user) {
const history = stockEvent.history || [];
const lastEntry = history.at(-1);
const lastHistoryValue = lastEntry?.value;
const currentValue = stockEvent.value;
const timestamp = stockEvent.timestamp || new Date();
if (currentValue !== lastHistoryValue && !isWithinHistoryRateLimit(lastEntry, timestamp)) {
await editObject({
model: this,
id: stockEvent._id,
updateData: {
history: [...history, { value: currentValue, timestamp }],
},
user,
recalculate: false,
});
}
const parentType = stockEvent.parentType;
const parentId = stockEvent.parent?._id || stockEvent.parent;
await recalculateParentStock(parentType, parentId, user);
};
// Add virtual id getter // Add virtual id getter
stockEventSchema.virtual('id').get(function () { stockEventSchema.virtual('id').get(function () {
return this._id; return this._id;