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:
@@ -0,0 +1,29 @@
|
||||
import { model, models, Schema, type InferSchemaType, type Model } from "mongoose";
|
||||
import { ASSET_KEYS } from "@/shared/assets";
|
||||
|
||||
/**
|
||||
* Fiyat tablosu (Bölüm 6.6): varlık × tarih × alış/satış (birim başına kuruş). İlk aşamada elle
|
||||
* giriş; otomatik kaynaklar Faz 17'de `source` ile eklenir. Gün başına tek kayıt (yeniden girişte
|
||||
* güncellenir).
|
||||
*/
|
||||
const assetPriceSchema = new Schema(
|
||||
{
|
||||
householdId: { type: Schema.Types.ObjectId, required: true },
|
||||
assetKey: { type: String, enum: ASSET_KEYS, required: true },
|
||||
/** "YYYY-MM-DD" (İstanbul). */
|
||||
date: { type: String, required: true },
|
||||
buyMinor: { type: Number, required: true, min: 0 },
|
||||
sellMinor: { type: Number, required: true, min: 0 },
|
||||
source: { type: String, enum: ["manual"], default: "manual" },
|
||||
createdBy: { type: Schema.Types.ObjectId, required: true },
|
||||
},
|
||||
{ timestamps: true, collection: "assetPrices" },
|
||||
);
|
||||
|
||||
assetPriceSchema.index({ householdId: 1, assetKey: 1, date: -1 }, { unique: true });
|
||||
|
||||
export type AssetPrice = InferSchemaType<typeof assetPriceSchema>;
|
||||
|
||||
export const AssetPriceModel: Model<AssetPrice> =
|
||||
(models.AssetPrice as Model<AssetPrice> | undefined) ??
|
||||
model<AssetPrice>("AssetPrice", assetPriceSchema);
|
||||
@@ -30,6 +30,9 @@ export const AUDIT_ACTIONS = [
|
||||
"recurring.delete",
|
||||
"contact.archive",
|
||||
"contact.unarchive",
|
||||
"inkind.payment",
|
||||
"inkind.close",
|
||||
"inkind.delete",
|
||||
] as const;
|
||||
export type AuditAction = (typeof AUDIT_ACTIONS)[number];
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -58,6 +58,12 @@ const transactionSchema = new Schema(
|
||||
/** Cari transferi: karşı taraf kişi (hesabın biri boş). */
|
||||
contactId: { type: Schema.Types.ObjectId, default: null },
|
||||
contactKind: { type: String, enum: [...CONTACT_ENTRY_KINDS, null], default: null },
|
||||
/**
|
||||
* Aynî borca bağlı para hareketi: birikim katkısı (hesap → birikim hesabı) veya ödeme.
|
||||
* `contactId` görünürlük için doludur ama `contactKind` boştur: cari TL bakiyesine girmez.
|
||||
*/
|
||||
inKindDebtId: { type: Schema.Types.ObjectId, default: null },
|
||||
inKindRole: { type: String, enum: ["contribution", "payment", null], 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.
|
||||
|
||||
Reference in New Issue
Block a user