Compare commits

...

3 Commits

Author SHA1 Message Date
c38c1dee24 Implemented stock locations.
Some checks failed
farmcontrol/farmcontrol-ws/pipeline/head There was a failure building this commit
2026-05-17 16:55:05 +01:00
ca3f28fc74 Added stock location and stock transfer models, updated inventory schemas to include stockLocation references, and modified stock event schema to support stockTransfer owner type. 2026-03-21 23:14:14 +00:00
8a9c272de4 Implemented listing schemas. 2026-03-21 21:39:21 +00:00
14 changed files with 287 additions and 15 deletions

View File

@ -20,6 +20,11 @@ const filamentStockSchema = new Schema(
gross: { type: Number, required: true },
},
filamentSku: { type: mongoose.Schema.Types.ObjectId, ref: 'filamentSku', required: true },
stockLocation: {
type: mongoose.Schema.Types.ObjectId,
ref: 'stockLocation',
required: false,
},
},
{ timestamps: true }
);

View File

@ -12,6 +12,11 @@ const partStockSchema = new Schema(
progress: { type: Number, required: false },
},
partSku: { type: mongoose.Schema.Types.ObjectId, ref: 'partSku', required: true },
stockLocation: {
type: mongoose.Schema.Types.ObjectId,
ref: 'stockLocation',
required: false,
},
currentQuantity: { type: Number, required: true },
sourceType: { type: String, required: true },
source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true },

View File

@ -19,6 +19,11 @@ const productStockSchema = new Schema(
},
postedAt: { type: Date, required: false },
productSku: { type: mongoose.Schema.Types.ObjectId, ref: 'productSku', required: true },
stockLocation: {
type: mongoose.Schema.Types.ObjectId,
ref: 'stockLocation',
required: false,
},
currentQuantity: { type: Number, required: true },
partStocks: [partStockUsageSchema],
},

View File

@ -25,7 +25,7 @@ const stockEventSchema = new Schema(
ownerType: {
type: String,
required: true,
enum: ['user', 'subJob', 'stockAudit'],
enum: ['user', 'subJob', 'stockAudit', 'stockTransfer'],
},
timestamp: { type: Date, default: Date.now },
},

View File

@ -0,0 +1,39 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const addressSchema = new Schema({
building: { required: false, type: String },
addressLine1: { required: false, type: String },
addressLine2: { required: false, type: String },
city: { required: false, type: String },
state: { required: false, type: String },
postcode: { required: false, type: String },
country: { required: false, type: String },
});
const stockLocationSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
name: { type: String, required: true },
address: { required: false, type: addressSchema },
},
{ timestamps: true }
);
stockLocationSchema.statics.stats = async function () {
const total = await this.countDocuments({});
return { total: { count: total } };
};
stockLocationSchema.statics.history = async function () {
return [];
};
stockLocationSchema.virtual('id').get(function () {
return this._id;
});
stockLocationSchema.set('toJSON', { virtuals: true });
export const stockLocationModel = mongoose.model('stockLocation', stockLocationSchema);

View File

@ -0,0 +1,71 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const stockTransferLineSchema = new Schema(
{
fromStockType: {
type: String,
required: true,
enum: ['filamentStock', 'partStock', 'productStock'],
},
fromStock: {
type: Schema.Types.ObjectId,
refPath: 'fromStockType',
required: true,
},
quantity: { type: Number, required: true },
toStockLocation: {
type: Schema.Types.ObjectId,
ref: 'stockLocation',
required: true,
},
toStockType: {
type: String,
required: false,
enum: ['filamentStock', 'partStock', 'productStock'],
},
toStock: {
type: Schema.Types.ObjectId,
refPath: 'toStockType',
required: false,
},
},
{ _id: true }
);
const stockTransferSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
state: {
type: { type: String, required: true, default: 'draft' },
progress: { type: Number, required: false },
},
postedAt: { type: Date, required: false },
lines: { type: [stockTransferLineSchema], default: [] },
},
{ timestamps: true }
);
stockTransferSchema.statics.stats = async function () {
const [draft, posted] = await Promise.all([
this.countDocuments({ 'state.type': 'draft' }),
this.countDocuments({ 'state.type': 'posted' }),
]);
return {
draft: { count: draft },
posted: { count: posted },
};
};
stockTransferSchema.statics.history = async function () {
return [];
};
stockTransferSchema.virtual('id').get(function () {
return this._id;
});
stockTransferSchema.set('toJSON', { virtuals: true });
export const stockTransferModel = mongoose.model('stockTransfer', stockTransferSchema);

View File

@ -56,6 +56,8 @@ const hostSchema = new mongoose.Schema(
connectedAt: { required: false, type: Date },
authCode: { type: { required: false, type: String } },
deviceInfo: { deviceInfoSchema },
otp: { type: { required: false, type: String } },
otpExpiresAt: { required: false, type: Date },
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
},
{ timestamps: true }

View File

@ -31,19 +31,10 @@ partSchema.virtual('id').get(function () {
partSchema.set('toJSON', { virtuals: true });
partSchema.statics.recalculate = async function (part, user) {
const orderItemModel = mongoose.model('orderItem');
const itemId = part._id;
const draftOrderItems = await orderItemModel
.find({
'state.type': 'draft',
itemType: 'part',
item: itemId,
})
.populate('order')
.lean();
for (const orderItem of draftOrderItems) {
await orderItemModel.recalculate(orderItem, user);
const partSkuModel = mongoose.model('partSku');
const skus = await partSkuModel.find({ part: part._id }).select('_id').lean();
for (const sku of skus) {
await partSkuModel.recalculate(sku, user);
}
};

View File

@ -45,6 +45,7 @@ productSchema.statics.recalculate = async function (product, user) {
for (const orderItem of draftOrderItems) {
await orderItemModel.recalculate(orderItem, user);
}
};
// Create and export the model

View File

@ -17,6 +17,8 @@ import { stockEventModel } from './inventory/stockevent.schema.js';
import { stockAuditModel } from './inventory/stockaudit.schema.js';
import { partStockModel } from './inventory/partstock.schema.js';
import { productStockModel } from './inventory/productstock.schema.js';
import { stockLocationModel } from './inventory/stocklocation.schema.js';
import { stockTransferModel } from './inventory/stocktransfer.schema.js';
import { auditLogModel } from './management/auditlog.schema.js';
import { userModel } from './management/user.schema.js';
import { appPasswordModel } from './management/apppassword.schema.js';
@ -39,6 +41,8 @@ import { invoiceModel } from './finance/invoice.schema.js';
import { clientModel } from './sales/client.schema.js';
import { salesOrderModel } from './sales/salesorder.schema.js';
import { marketplaceModel } from './sales/marketplace.schema.js';
import { listingModel } from './sales/listing.schema.js';
import { listingVarientModel } from './sales/listingvarient.schema.js';
// Map prefixes to models and id fields
export const models = {
@ -155,6 +159,20 @@ export const models = {
referenceField: '_reference',
label: 'Product Stock',
},
SLN: {
model: stockLocationModel,
idField: '_id',
type: 'stockLocation',
referenceField: '_reference',
label: 'Stock Location',
},
STT: {
model: stockTransferModel,
idField: '_id',
type: 'stockTransfer',
referenceField: '_reference',
label: 'Stock Transfer',
},
ADL: {
model: auditLogModel,
idField: '_id',
@ -323,4 +341,18 @@ export const models = {
label: 'Marketplace',
referenceField: '_reference',
},
LST: {
model: listingModel,
idField: '_id',
type: 'listing',
label: 'Listing',
referenceField: '_reference',
},
LVR: {
model: listingVarientModel,
idField: '_id',
type: 'listingVarient',
label: 'Listing Varient',
referenceField: '_reference',
},
};

View File

@ -0,0 +1,44 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.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 },
price: { type: Number, required: false },
currency: { type: String, required: false },
lastSyncedAt: { type: Date, required: false },
},
{ timestamps: 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;
},
});
export const listingModel = mongoose.model('listing', listingSchema);

View File

@ -0,0 +1,43 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
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 },
},
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 },
},
{ timestamps: true }
);
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;
},
});
export const listingVarientModel = mongoose.model('listingVarient', listingVarientSchema);

View File

@ -1,4 +1,5 @@
import mongoose from 'mongoose';
import { editObject } from '../../database.js';
import { generateId } from '../../utils.js';
const marketplaceSchema = new mongoose.Schema(
@ -11,6 +12,16 @@ const marketplaceSchema = new mongoose.Schema(
enum: ['ebay', 'etsy', 'tiktokShop'],
},
active: { required: true, type: Boolean, default: true },
connected: { type: Boolean, required: true, default: false },
connectedAt: { type: Date, required: false },
state: {
type: {
type: String,
enum: ['active', 'inactive', 'suspended', 'ready', 'offline', 'syncing'],
default: 'offline',
},
message: { type: String, required: false },
},
// Provider-specific API configuration (flexible for eBay, Etsy, TikTok Shop)
config: { type: mongoose.Schema.Types.Mixed, default: {} },
},
@ -21,6 +32,26 @@ marketplaceSchema.virtual('id').get(function () {
return this._id;
});
marketplaceSchema.statics.recalculate = async function (marketplace, user) {
let stateType;
if (marketplace.active === false) {
stateType = 'inactive';
} else if (marketplace.connected === false) {
stateType = 'disconnected';
} else {
stateType = 'ready';
}
console.log('recalculating marketplace state', stateType);
marketplace.state = { type: stateType };
await editObject({
model: this,
id: marketplace._id,
updateData: { state: { type: stateType } },
user,
recalculate: false,
});
};
marketplaceSchema.set('toJSON', { virtuals: true });
export const marketplaceModel = mongoose.model('marketplace', marketplaceSchema);

View File

@ -23,9 +23,12 @@ export async function generateHostOTP(id) {
const otpHost = await editObject({
model: hostModel,
id: id,
updateData: { otp: otp, otpExpiresAt: expiresAt }
updateData: { otp: otp, otpExpiresAt: expiresAt },
auditLog: false
});
console.log('otpHost', otpHost);
return otpHost;
}