feat(shopping): mağazalar, zahmet skoru ve ürünler (marka/paket, dönüşüm, görseller)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { model, models, Schema, type InferSchemaType, type Model } from "mongoos
|
||||
import { ATTACHMENT_KINDS } from "@/shared/attachments";
|
||||
|
||||
/** Ek dosyanın bağlanabileceği kayıt türleri (ileride: alışveriş fişi, cari hareket…). */
|
||||
export const ATTACHMENT_OWNER_TYPES = ["transaction"] as const;
|
||||
export const ATTACHMENT_OWNER_TYPES = ["transaction", "product"] as const;
|
||||
export type AttachmentOwnerType = (typeof ATTACHMENT_OWNER_TYPES)[number];
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,10 @@ export const AUDIT_ACTIONS = [
|
||||
"stock.product.archive",
|
||||
"stock.product.unarchive",
|
||||
"stock.movement.delete",
|
||||
"store.archive",
|
||||
"store.unarchive",
|
||||
"product.archive",
|
||||
"product.unarchive",
|
||||
] as const;
|
||||
export type AuditAction = (typeof AUDIT_ACTIONS)[number];
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
|
||||
|
||||
/**
|
||||
* Ürün (marka/varyant, Bölüm 6.11): "Ariel Matik 5 kg / 50 yıkama". Bir ürün tipine bağlıdır;
|
||||
* fiyatlar (Faz 12b) bu seviyede tutulur. Paket miktarı binde bir. `typeQuantity`: paket birimi
|
||||
* ürün tipinin birimine kendiliğinden çevrilemiyorsa bir paketin ürün tipi birimindeki karşılığı
|
||||
* (zorunlu); çevrilebiliyorsa null. Görseller `attachments` (ownerType "product").
|
||||
*/
|
||||
const productSchema = new Schema(
|
||||
{
|
||||
householdId: { type: Schema.Types.ObjectId, required: true },
|
||||
productTypeId: { type: Schema.Types.ObjectId, required: true },
|
||||
brand: { type: String, required: true, trim: true, maxlength: 60 },
|
||||
/** Tanım (ör. "Matik Dağ Esintisi"). */
|
||||
name: { type: String, default: "", trim: true, maxlength: 120 },
|
||||
/** Arama anahtarı: marka + tanım (Türkçe katlanmış). */
|
||||
searchKey: { type: String, required: true },
|
||||
barcode: { type: String, default: null },
|
||||
packageQuantity: { type: Number, required: true, min: 1 },
|
||||
packageUnit: { type: String, required: true, trim: true, maxlength: 20 },
|
||||
typeQuantity: { type: Number, default: null, min: 1 },
|
||||
note: { type: String, default: "", maxlength: 300 },
|
||||
imageCount: { type: Number, default: 0 },
|
||||
archivedAt: { 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: "products" },
|
||||
);
|
||||
|
||||
productSchema.index({ householdId: 1, productTypeId: 1, archivedAt: 1 });
|
||||
productSchema.index(
|
||||
{ householdId: 1, barcode: 1 },
|
||||
{ partialFilterExpression: { barcode: { $type: "string" } } },
|
||||
);
|
||||
productSchema.index(
|
||||
{ householdId: 1, seedRef: 1 },
|
||||
{ unique: true, partialFilterExpression: { seedRef: { $type: "string" } } },
|
||||
);
|
||||
|
||||
export type Product = InferSchemaType<typeof productSchema>;
|
||||
|
||||
export const ProductModel: Model<Product> =
|
||||
(models.Product as Model<Product> | undefined) ?? model<Product>("Product", productSchema);
|
||||
@@ -0,0 +1,43 @@
|
||||
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
|
||||
import { MEAL_CARD_PROVIDERS } from "@/shared/accounts";
|
||||
import { STORE_KINDS } from "@/shared/stores";
|
||||
|
||||
/**
|
||||
* Mağaza (Bölüm 6.11): fiziksel market, online platform veya pazar. Hane bazlı (hane içinde
|
||||
* ortak). Uzaklık metre; teslimat ücretleri kuruş. Zahmet skoru okuma sırasında hesaplanır.
|
||||
*/
|
||||
const storeSchema = new Schema(
|
||||
{
|
||||
householdId: { type: Schema.Types.ObjectId, required: true },
|
||||
name: { type: String, required: true, trim: true, maxlength: 80 },
|
||||
nameKey: { type: String, required: true },
|
||||
kind: { type: String, enum: STORE_KINDS, required: true },
|
||||
branch: { type: String, default: "", maxlength: 80 },
|
||||
/** Eve uzaklık (metre); online mağazada null. */
|
||||
distanceM: { type: Number, default: null, min: 0 },
|
||||
/** Taşıma güçlüğü 1 (kolay) – 5 (çok zor). */
|
||||
carryDifficulty: { type: Number, default: 1, min: 1, max: 5 },
|
||||
/** Online: teslimat ücreti ve ücretsiz teslimat alt limiti (kuruş). */
|
||||
deliveryFeeMinor: { type: Number, default: null, min: 0 },
|
||||
freeDeliveryMinMinor: { type: Number, default: null, min: 0 },
|
||||
mealCardAccepted: { type: Boolean, default: false },
|
||||
mealCardProviders: { type: [{ type: String, enum: MEAL_CARD_PROVIDERS }], default: [] },
|
||||
note: { type: String, default: "", maxlength: 300 },
|
||||
archivedAt: { 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: "stores" },
|
||||
);
|
||||
|
||||
storeSchema.index({ householdId: 1, archivedAt: 1, nameKey: 1 });
|
||||
storeSchema.index(
|
||||
{ householdId: 1, seedRef: 1 },
|
||||
{ unique: true, partialFilterExpression: { seedRef: { $type: "string" } } },
|
||||
);
|
||||
|
||||
export type Store = InferSchemaType<typeof storeSchema>;
|
||||
|
||||
export const StoreModel: Model<Store> =
|
||||
(models.Store as Model<Store> | undefined) ?? model<Store>("Store", storeSchema);
|
||||
Reference in New Issue
Block a user