feat(accounts): hesaplar (KMH, kredi kartı, yemek kartı, yalnızca ben)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 02:57:58 +03:00
parent 380ddaf303
commit 8dd5fd42e3
9 changed files with 1805 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
import { ACCOUNT_TYPES, DUE_RULE_KINDS, MEAL_CARD_PROVIDERS } from "@/shared/accounts";
import { ICON_KEYS, SWATCHES } from "@/shared/appearance";
/**
* Hesap (Bölüm 6.1). Tutarlar kuruş. Bakiye işareti: varlık pozitif, kart borcu negatif.
* Güncel bakiyenin tek doğruluk kaynağı işlemlerdir (Faz 6b); `balanceCacheMinor` yalnızca önbellek.
*/
const overdraftSchema = new Schema(
{
enabled: { type: Boolean, default: false },
limitMinor: { type: Number, min: 0, default: 0 },
/** Aylık faiz oranı, baz puan (4,25% → 425). */
monthlyRateBp: { type: Number, min: 0, max: 10_000, default: null },
statementDay: { type: Number, min: 1, max: 31, default: null },
},
{ _id: false },
);
const creditCardSchema = new Schema(
{
limitMinor: { type: Number, min: 0, default: 0 },
/** Hesap kesim günü; ayda yoksa (31) ayın son günü kullanılır (Faz 7). */
statementDay: { type: Number, min: 1, max: 31, default: 1 },
dueRule: {
kind: { type: String, enum: DUE_RULE_KINDS, default: "daysAfter" },
value: { type: Number, min: 1, max: 31, default: 10 },
},
/** Kart borcunun ödeneceği varsayılan hesap. */
paymentAccountId: { type: Schema.Types.ObjectId, default: null },
},
{ _id: false },
);
const accountSchema = new Schema(
{
householdId: { type: Schema.Types.ObjectId, required: true },
name: { type: String, required: true, trim: true, maxlength: 80 },
type: { type: String, enum: ACCOUNT_TYPES, required: true },
institution: { type: String, trim: true, maxlength: 80, default: "" },
mealCardProvider: { type: String, enum: [...MEAL_CARD_PROVIDERS, null], default: null },
/** İleride döviz hesabı için; şimdilik yalnızca TRY. */
currency: { type: String, enum: ["TRY"], default: "TRY" },
openingBalanceMinor: { type: Number, required: true, default: 0 },
openingDate: { type: Date, required: true },
color: { type: String, enum: SWATCHES, default: "blue" },
icon: { type: String, enum: ICON_KEYS, default: "wallet" },
order: { type: Number, default: 0 },
archivedAt: { type: Date, default: null },
/** Hesabın sahibi kişi (hane üyesi). "Yalnızca ben" hesaplarında bakiyeyi yalnızca o görür. */
ownerUserId: { type: Schema.Types.ObjectId, default: null },
isPrivate: { type: Boolean, default: false },
overdraft: { type: overdraftSchema, default: null },
creditCard: { type: creditCardSchema, default: null },
balanceCacheMinor: { type: Number, default: null },
createdBy: { type: Schema.Types.ObjectId, required: true },
updatedBy: { type: Schema.Types.ObjectId, required: true },
},
{ timestamps: true, collection: "accounts" },
);
accountSchema.index({ householdId: 1, archivedAt: 1, order: 1 });
export type Account = InferSchemaType<typeof accountSchema>;
export const AccountModel: Model<Account> =
(models.Account as Model<Account> | undefined) ?? model<Account>("Account", accountSchema);