feat(shopping): alışveriş listeleri, stok eşiği önerileri ve akıllı mağaza önerisi motoru
- shoppingLists koleksiyonu (ürün tipi / marka / serbest metin kalemleri), migration 015 - planShopping saf fonksiyonu: paket yuvarlama, zahmet ve hacimli cezası, teslimat, yemek kartı önceliği, ek mağaza eşiği - eşik önerileri onay/red (red stok yenilenene kadar), ürün tipine hacimli işareti - fiyat servisinde ortak loadTypeOffers; seed listeleri ve testler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
|
||||
import {
|
||||
SHOPPING_ITEM_KINDS,
|
||||
SHOPPING_ITEM_SOURCES,
|
||||
SHOPPING_LIST_STATUSES,
|
||||
} from "@/shared/shopping";
|
||||
|
||||
/**
|
||||
* Alışveriş kalemi (Bölüm 6.12): ürün tipi (genel), belirli ürün (marka/paket) ya da serbest metin.
|
||||
* Miktar binde bir: ürün tipinde tipin biriminde, üründe paket adedi, serbest metinde `unit` ile.
|
||||
* `checked`: alışveriş modunda sepete alındı.
|
||||
*/
|
||||
const shoppingItemSchema = new Schema(
|
||||
{
|
||||
kind: { type: String, enum: SHOPPING_ITEM_KINDS, required: true },
|
||||
productTypeId: { type: Schema.Types.ObjectId, default: null },
|
||||
productId: { type: Schema.Types.ObjectId, default: null },
|
||||
text: { type: String, default: "", trim: true, maxlength: 120 },
|
||||
/** Boşsa "1 paket" kabul edilir. */
|
||||
quantity: { type: Number, default: null, min: 0 },
|
||||
/** Serbest metin kaleminin birimi. */
|
||||
unit: { type: String, default: "", trim: true, maxlength: 20 },
|
||||
note: { type: String, default: "", trim: true, maxlength: 200 },
|
||||
source: { type: String, enum: SHOPPING_ITEM_SOURCES, default: "manual" },
|
||||
checked: { type: Boolean, default: false },
|
||||
checkedBy: { type: Schema.Types.ObjectId, default: null },
|
||||
checkedAt: { type: Date, default: null },
|
||||
addedBy: { type: Schema.Types.ObjectId, required: true },
|
||||
addedAt: { type: Date, default: () => new Date() },
|
||||
},
|
||||
{ _id: true },
|
||||
);
|
||||
|
||||
/** Alışveriş listesi; hane içinde ortak. Birden çok aktif liste olabilir. Silme: soft delete. */
|
||||
const shoppingListSchema = new Schema(
|
||||
{
|
||||
householdId: { type: Schema.Types.ObjectId, required: true },
|
||||
name: { type: String, required: true, trim: true, maxlength: 80 },
|
||||
status: { type: String, enum: SHOPPING_LIST_STATUSES, default: "active" },
|
||||
note: { type: String, default: "", trim: true, maxlength: 300 },
|
||||
items: { type: [shoppingItemSchema], default: [] },
|
||||
completedAt: { type: Date, default: null },
|
||||
deletedAt: { type: Date, default: null },
|
||||
seedRef: { type: String, default: null },
|
||||
createdBy: { type: Schema.Types.ObjectId, required: true },
|
||||
updatedBy: { type: Schema.Types.ObjectId, required: true },
|
||||
},
|
||||
{ timestamps: true, collection: "shoppingLists" },
|
||||
);
|
||||
|
||||
shoppingListSchema.index({ householdId: 1, deletedAt: 1, status: 1, updatedAt: -1 });
|
||||
shoppingListSchema.index(
|
||||
{ householdId: 1, seedRef: 1 },
|
||||
{ unique: true, partialFilterExpression: { seedRef: { $type: "string" } } },
|
||||
);
|
||||
|
||||
export type ShoppingList = InferSchemaType<typeof shoppingListSchema>;
|
||||
|
||||
export const ShoppingListModel: Model<ShoppingList> =
|
||||
(models.ShoppingList as Model<ShoppingList> | undefined) ??
|
||||
model<ShoppingList>("ShoppingList", shoppingListSchema);
|
||||
Reference in New Issue
Block a user