feat(in-kind): varlık türleri, fiyat tablosu, aynî borç/alacak, birikim planı ve ödeme akışı, 008 migration

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 12:37:48 +03:00
parent 1062b2d96c
commit 9781cd9ed2
15 changed files with 1354 additions and 3 deletions
+61
View File
@@ -0,0 +1,61 @@
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
import { ASSET_KEYS } from "@/shared/assets";
/**
* Aynî borç/alacak (Bölüm 6.5): bir cariyle altın, ziynet veya döviz cinsinden borç.
* Borç tarihi fiyatı ve TL karşılığı not olarak saklanır; güncel değer fiyat tablosundan.
* Kapanış yalnızca "Ödeme gerçekleşti" kayıtlarıyla (kısmi ödeme miktarı düşürür).
* Birikim planı: katkılar seçilen birikim hesabına transferdir (`transactions.inKindDebtId`).
*/
const paymentSchema = new Schema(
{
date: { type: String, required: true },
/** asset: aynı varlıkla · cash: TL karşılığıyla. */
method: { type: String, enum: ["asset", "cash"], required: true },
quantity: { type: Number, required: true, min: 1 },
/** Ödemede harcanan / tahsil edilen TL (aynı varlıkla ödemede varlığı almak için harcanan). */
amountMinor: { type: Number, default: 0, min: 0 },
accountId: { type: Schema.Types.ObjectId, default: null },
transactionId: { type: Schema.Types.ObjectId, default: null },
note: { type: String, default: "", maxlength: 300 },
createdBy: { type: Schema.Types.ObjectId, required: true },
},
{ _id: true, timestamps: { createdAt: true, updatedAt: false } },
);
const inKindDebtSchema = new Schema(
{
householdId: { type: Schema.Types.ObjectId, required: true },
contactId: { type: Schema.Types.ObjectId, required: true },
/** debt: ben borçluyum · receivable: bana borçlu. */
direction: { type: String, enum: ["debt", "receivable"], required: true },
assetKey: { type: String, enum: ASSET_KEYS, required: true },
/** Binde bir (5 gr → 5000). */
quantity: { type: Number, required: true, min: 1 },
date: { type: String, required: true },
unitPriceMinor: { type: Number, default: null },
valueMinor: { type: Number, default: null },
note: { type: String, default: "", maxlength: 500 },
savingsAccountId: { type: Schema.Types.ObjectId, default: null },
monthlyPlanMinor: { type: Number, default: null },
payments: { type: [paymentSchema], default: [] },
closedAt: { type: String, default: null },
deletedAt: { 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: "inKindDebts" },
);
inKindDebtSchema.index({ householdId: 1, contactId: 1, deletedAt: 1 });
inKindDebtSchema.index(
{ householdId: 1, seedRef: 1 },
{ unique: true, partialFilterExpression: { seedRef: { $type: "string" } } },
);
export type InKindDebt = InferSchemaType<typeof inKindDebtSchema>;
export const InKindDebtModel: Model<InKindDebt> =
(models.InKindDebt as Model<InKindDebt> | undefined) ??
model<InKindDebt>("InKindDebt", inKindDebtSchema);