feat(categories): kategori ağacı, varsayılan Türkçe set ve yönetim ekranı

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 02:57:58 +03:00
parent eaf80c6bb4
commit 380ddaf303
10 changed files with 1469 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
import { ICON_KEYS, SWATCHES } from "@/shared/appearance";
import { CATEGORY_KINDS } from "@/shared/schemas/category";
/** Gelir/gider kategori ağacı (Bölüm 6.2). Silme yok; arşivleme (alt ağaçla birlikte). */
const categorySchema = new Schema(
{
householdId: { type: Schema.Types.ObjectId, required: true },
kind: { type: String, enum: CATEGORY_KINDS, required: true },
name: { type: String, required: true, trim: true, maxlength: 80 },
/** Kardeşler arasında ad tekrarını önlemek için normalize ad (foldTr). */
nameKey: { type: String, required: true },
parentId: { type: Schema.Types.ObjectId, default: null },
/** Kökten üst kategoriye kadar kimlikler (alt ağaç sorguları ve toplama için). */
ancestors: { type: [Schema.Types.ObjectId], default: [] },
icon: { type: String, enum: ICON_KEYS, default: "tag" },
color: { type: String, enum: SWATCHES, default: "slate" },
order: { type: Number, default: 0 },
archivedAt: { type: Date, default: null },
/** Varsayılan setten geldiyse sabit anahtar (tekrar yüklemede kopya olmaz). */
defaultKey: { type: String, default: null },
createdBy: { type: Schema.Types.ObjectId, required: true },
updatedBy: { type: Schema.Types.ObjectId, required: true },
},
{ timestamps: true, collection: "categories" },
);
categorySchema.index({ householdId: 1, kind: 1, parentId: 1, order: 1 });
categorySchema.index({ householdId: 1, ancestors: 1 });
categorySchema.index(
{ householdId: 1, defaultKey: 1 },
{ unique: true, partialFilterExpression: { defaultKey: { $type: "string" } } },
);
export type Category = InferSchemaType<typeof categorySchema>;
export const CategoryModel: Model<Category> =
(models.Category as Model<Category> | undefined) ?? model<Category>("Category", categorySchema);