feat(cards): kart/KMH dönem hesabı, taksitli harcama, kart ve KMH özeti servisleri, 006 migration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,11 @@ const creditCardSchema = new Schema(
|
||||
kind: { type: String, enum: DUE_RULE_KINDS, default: "daysAfter" },
|
||||
value: { type: Number, min: 1, max: 31, default: 10 },
|
||||
},
|
||||
/**
|
||||
* Asgari ödeme oranı, baz puan (2000 = %20). null → varsayılan kural: limit 50.000 TL altı %20,
|
||||
* üstü %40 (Faz 7 kararı).
|
||||
*/
|
||||
minimumPaymentRateBp: { type: Number, min: 0, max: 10_000, default: null },
|
||||
/** Kart borcunun ödeneceği varsayılan hesap. */
|
||||
paymentAccountId: { type: Schema.Types.ObjectId, default: null },
|
||||
},
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
|
||||
|
||||
/**
|
||||
* Kart/KMH dönem kaydı (Bölüm 6.8, `statements`). Tutarlar işlemlerden hesaplanır ve kart özeti
|
||||
* okunurken güncellenir (geriye dönük girilen işlem dönem borcunu doğru değiştirir); bu koleksiyon
|
||||
* raporlar ve mobil API için son hesaplanan değerleri tutar.
|
||||
*/
|
||||
const cardStatementSchema = new Schema(
|
||||
{
|
||||
householdId: { type: Schema.Types.ObjectId, required: true },
|
||||
accountId: { type: Schema.Types.ObjectId, required: true },
|
||||
kind: { type: String, enum: ["credit_card", "overdraft"], required: true },
|
||||
/** "YYYY-MM-DD" (İstanbul), ikisi de dahil. */
|
||||
periodStart: { type: String, required: true },
|
||||
periodEnd: { type: String, required: true },
|
||||
dueDate: { type: String, default: null },
|
||||
/** Kesim günü sonundaki borç (pozitif). */
|
||||
statementDebtMinor: { type: Number, default: 0 },
|
||||
minimumPaymentMinor: { type: Number, default: 0 },
|
||||
/** Kesimden sonra (bir sonraki kesime kadar) yapılan ödemeler. */
|
||||
paidMinor: { type: Number, default: 0 },
|
||||
/** Dönemdeki harcamalar (çıkışlar). */
|
||||
spentMinor: { type: Number, default: 0 },
|
||||
/** KMH: tahmini faiz. */
|
||||
interestMinor: { type: Number, default: null },
|
||||
computedAt: { type: Date, required: true },
|
||||
},
|
||||
{ timestamps: true, collection: "statements" },
|
||||
);
|
||||
|
||||
cardStatementSchema.index({ accountId: 1, periodEnd: 1 }, { unique: true });
|
||||
cardStatementSchema.index({ householdId: 1, accountId: 1, periodEnd: -1 });
|
||||
|
||||
export type CardStatement = InferSchemaType<typeof cardStatementSchema>;
|
||||
|
||||
export const CardStatementModel: Model<CardStatement> =
|
||||
(models.CardStatement as Model<CardStatement> | undefined) ??
|
||||
model<CardStatement>("CardStatement", cardStatementSchema);
|
||||
@@ -76,6 +76,7 @@ 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({ "installment.planId": 1 }, { sparse: true });
|
||||
transactionSchema.index(
|
||||
{ recurringRuleId: 1, occurrenceIndex: 1 },
|
||||
{ unique: true, partialFilterExpression: { recurringRuleId: { $type: "objectId" } } },
|
||||
|
||||
Reference in New Issue
Block a user