Files
butcem/src/models/transaction.ts
T

93 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
import { TRANSACTION_STATUSES, TRANSACTION_TYPES } from "@/shared/transactions";
/**
* İşlem (Bölüm 6.3). `amountMinor` her zaman pozitif; işaret türden gelir (transactionEffects).
* Bakiyenin tek doğruluk kaynağıdır. Silme yumuşaktır (`deletedAt`).
*/
const splitSchema = new Schema(
{
categoryId: { type: Schema.Types.ObjectId, required: true },
amountMinor: { type: Number, required: true, min: 1 },
note: { type: String, default: "", maxlength: 120 },
},
{ _id: true },
);
const transactionSchema = new Schema(
{
householdId: { type: Schema.Types.ObjectId, required: true },
type: { type: String, enum: TRANSACTION_TYPES, required: true },
status: { type: String, enum: TRANSACTION_STATUSES, default: "posted" },
/** İstanbul gününün başlangıç anı (UTC). */
date: { type: Date, required: true },
accountId: { type: Schema.Types.ObjectId, required: true },
toAccountId: { type: Schema.Types.ObjectId, default: null },
amountMinor: { type: Number, required: true, min: 1 },
/** Bölünmemiş gelir/giderde kategori; bölünmüşte null ve kalemler `splits`te. */
categoryId: { type: Schema.Types.ObjectId, default: null },
splits: { type: [splitSchema], default: [] },
/** Kategori filtresi/raporlar için: categoryId veya kalem kategorileri (tekilleştirilmiş). */
effectiveCategoryIds: { type: [Schema.Types.ObjectId], default: [] },
payeeId: { type: Schema.Types.ObjectId, default: null },
payeeName: { type: String, default: "", maxlength: 80 },
description: { type: String, default: "", maxlength: 500 },
/** Arama için normalize metin (açıklama + satıcı + kalem notları). */
searchText: { type: String, default: "" },
tags: { type: [String], default: [] },
/** Bağlı (silinmemiş) ek dosya sayısı; liste ikonu için (asıl kayıt attachments'ta). */
attachmentCount: { type: Number, default: 0 },
// İleri fazlar: planlı işlem kuralı (7), taksit (7), kart dönemi (7), cari (8).
recurringRuleId: { type: Schema.Types.ObjectId, default: null },
/** Serideki sıra (0'dan); kuralla birlikte benzersiz (tekrar üretimi idempotent). */
occurrenceIndex: { type: Number, default: null },
/** Tek başına düzenlendi/ertelendi: "tümü" güncellemesinde korunur. */
detached: { type: Boolean, default: false },
/** Planlı tutar tahmini (ör. fatura); onaylarken kesinleşir. */
isEstimate: { type: Boolean, default: false },
installment: {
type: new Schema(
{ planId: Schema.Types.ObjectId, index: Number, count: Number },
{ _id: false },
),
default: null,
},
statementId: { type: Schema.Types.ObjectId, default: null },
contactId: { type: Schema.Types.ObjectId, default: null },
/**
* Mutabakatı yapılmış hesaplar (Bölüm 6.1). Transfer iki hesaba dokunduğu için hesap bazında
* tutulur. Tutar, tarih, tür veya hesap değişirse işaret kalkar.
*/
reconciledAccountIds: { type: [Schema.Types.ObjectId], default: [] },
/** Mutabakat düzeltme işlemi: kategorisiz; düzenlenemez, yalnızca silinebilir. */
isAdjustment: { type: Boolean, default: false },
deletedAt: { type: Date, default: null },
/** Seed verisinin idempotent yüklenmesi için sabit anahtar. */
seedRef: { type: String, default: null },
createdBy: { type: Schema.Types.ObjectId, required: true },
updatedBy: { type: Schema.Types.ObjectId, required: true },
},
{ timestamps: true, collection: "transactions" },
);
transactionSchema.index({ householdId: 1, deletedAt: 1, date: -1, createdAt: -1 });
transactionSchema.index({ householdId: 1, status: 1, date: 1 });
transactionSchema.index({ householdId: 1, accountId: 1, date: -1 });
transactionSchema.index({ householdId: 1, toAccountId: 1, date: -1 });
transactionSchema.index({ householdId: 1, effectiveCategoryIds: 1, date: -1 });
transactionSchema.index({ householdId: 1, tags: 1 });
transactionSchema.index(
{ recurringRuleId: 1, occurrenceIndex: 1 },
{ unique: true, partialFilterExpression: { recurringRuleId: { $type: "objectId" } } },
);
transactionSchema.index(
{ householdId: 1, seedRef: 1 },
{ unique: true, partialFilterExpression: { seedRef: { $type: "string" } } },
);
export type Transaction = InferSchemaType<typeof transactionSchema>;
export const TransactionModel: Model<Transaction> =
(models.Transaction as Model<Transaction> | undefined) ??
model<Transaction>("Transaction", transactionSchema);