etsy-finance-tracker/server/src/models/Product.ts
dlawler489 b27ede4131 Add alias matching, cost snapshots, reference-based fee allocation; remove debug tooling
- Products store packing-slip title aliases; matcher checks exact title/alias
  first so previously-matched items skip fuzzy matching entirely
- Packing-slip imports snapshot printingCost/costOfGoods/productId onto order
  items; profit analysis reads stored costs so catalog edits don't rewrite history
- Etsy statement fees allocate to orders via Order # references in Title/Info
  instead of date proximity; shop-level fees (listings, ads) no longer leak
  into order fees
- Remove broken transaction-fee exclusion guard (order totals are gross, so
  all expenses count once)
- Remove debug buttons, date-fix banner, test files, and console.log noise;
  Clear All Orders now uses a bulk DELETE /orders endpoint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:39:59 +10:00

83 lines
No EOL
2.3 KiB
TypeScript

import mongoose, { Document, Schema } from 'mongoose';
export interface IProduct extends Document {
title: string;
description: string;
price: number;
costOfGoods: number;
printingCost: number;
sku: string;
category: string;
tags: string[];
aliases: string[];
images: string[];
variants: {
name: string;
options: string[];
price?: number;
sku?: string;
}[];
inventory: {
quantity: number;
lowStockAlert: number;
location?: string;
};
dimensions: {
length?: number;
width?: number;
height?: number;
weight?: number;
};
materials: string[];
isActive: boolean;
etsyListingId?: string;
userId: mongoose.Types.ObjectId;
dateCreated: Date;
dateUpdated: Date;
}
const ProductSchema: Schema = new Schema({
title: { type: String, required: true, trim: true },
description: { type: String, default: '' },
price: { type: Number, required: true, min: 0 },
costOfGoods: { type: Number, default: 0, min: 0 },
printingCost: { type: Number, default: 0, min: 0 },
sku: { type: String, trim: true, default: '' },
category: { type: String, default: 'Other' },
tags: [{ type: String, trim: true }],
// Packing-slip item titles previously matched to this product; used for
// deterministic matching on future imports
aliases: [{ type: String, trim: true }],
images: [{ type: String }],
variants: [{
name: { type: String, required: true },
options: [{ type: String, required: true }],
price: { type: Number, min: 0 },
sku: { type: String }
}],
inventory: {
quantity: { type: Number, required: true, min: 0 },
lowStockAlert: { type: Number, default: 5 },
location: { type: String }
},
dimensions: {
length: { type: Number, min: 0 },
width: { type: Number, min: 0 },
height: { type: Number, min: 0 },
weight: { type: Number, min: 0 }
},
materials: [{ type: String }],
isActive: { type: Boolean, default: true },
etsyListingId: { type: String },
userId: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
dateCreated: { type: Date, default: Date.now },
dateUpdated: { type: Date, default: Date.now }
});
// Update dateUpdated on save
ProductSchema.pre('save', function(next) {
this.dateUpdated = new Date();
next();
});
export default mongoose.model<IProduct>('Product', ProductSchema);