feat(assets): dönemsel rapor, net varlık ve aynî kayıt bağlantısı
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,9 @@ import { toObjectId } from "@/lib/object-id";
|
||||
import { AssetPriceModel } from "@/models/asset-price";
|
||||
import { InKindDebtModel } from "@/models/in-kind-debt";
|
||||
import { TransactionModel } from "@/models/transaction";
|
||||
import { addMovement } from "@/features/assets/service";
|
||||
import { AssetMovementModel } from "@/models/asset-movement";
|
||||
import { priceOn, type PriceQuote } from "./prices";
|
||||
import {
|
||||
ASSET_KEYS,
|
||||
formatAssetQuantity,
|
||||
@@ -38,19 +41,7 @@ const t = tr.inKind;
|
||||
|
||||
// --- Fiyat tablosu ---
|
||||
|
||||
export type PriceQuote = { buyMinor: number; sellMinor: number; date: string };
|
||||
|
||||
/** Varlığın verilen tarihteki (veya öncesindeki en yakın) fiyatı. */
|
||||
export async function priceOn(
|
||||
householdId: Types.ObjectId,
|
||||
assetKey: AssetKey,
|
||||
date: string,
|
||||
): Promise<PriceQuote | null> {
|
||||
const p = await AssetPriceModel.findOne({ householdId, assetKey, date: { $lte: date } })
|
||||
.sort({ date: -1 })
|
||||
.lean();
|
||||
return p ? { buyMinor: p.buyMinor, sellMinor: p.sellMinor, date: p.date } : null;
|
||||
}
|
||||
export { priceOn, type PriceQuote } from "./prices";
|
||||
|
||||
export type PriceRow = { assetKey: AssetKey; latest: PriceQuote | null; history: PriceQuote[] };
|
||||
|
||||
@@ -381,6 +372,11 @@ export async function deleteInKind(
|
||||
{ _id: d._id },
|
||||
{ $set: { deletedAt: new Date(), updatedBy: actor } },
|
||||
);
|
||||
// Bağlı varlık hareketleri serbest kalır (Birikim sayfasından yönetilebilir).
|
||||
await AssetMovementModel.updateMany(
|
||||
{ householdId: ctx.m.householdId, inKindDebtId: d._id },
|
||||
{ $set: { inKindDebtId: null } },
|
||||
);
|
||||
await writeAudit({
|
||||
action: "inkind.delete",
|
||||
actorId: actor,
|
||||
@@ -491,7 +487,15 @@ export async function recordPayment(
|
||||
contactName,
|
||||
);
|
||||
|
||||
await mongoose.connection.transaction(async (session) => {
|
||||
if (input.assetId) {
|
||||
const asset = ctx.assetMap.get(input.assetId);
|
||||
if (!asset || asset.hidden || asset.deleted)
|
||||
return fail(tr.common.notFound, { assetId: tr.common.notFound });
|
||||
if (asset.assetKey !== d.assetKey)
|
||||
return fail(t.link.assetMismatch, { assetId: t.link.assetMismatch });
|
||||
}
|
||||
|
||||
const writePayment = async (session: mongoose.ClientSession) => {
|
||||
let transactionId: Types.ObjectId | null = null;
|
||||
if (amountMinor > 0 && account) {
|
||||
const out = d.direction === "debt";
|
||||
@@ -549,10 +553,50 @@ export async function recordPayment(
|
||||
},
|
||||
session,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
await mongoose.connection.transaction(async (session) => {
|
||||
// Varlık bağlantısı: borç ödemesi varlıktan çıkış, alacak tahsilatı varlığa giriş.
|
||||
if (input.assetId) {
|
||||
const r = await addMovement(
|
||||
actorId,
|
||||
householdId,
|
||||
input.assetId,
|
||||
{
|
||||
kind: d.direction === "debt" ? "gift_out" : "gift_in",
|
||||
date: input.date,
|
||||
quantity,
|
||||
unitPrice: d.direction === "debt" ? null : (d.unitPriceMinor ?? null),
|
||||
amount: null,
|
||||
accountId: null,
|
||||
storage: "",
|
||||
note: t.link.movementNote(contactName),
|
||||
},
|
||||
{ inKindDebtId: d._id, session },
|
||||
);
|
||||
if (!r.ok) throw new LinkError(r.error, r.fieldErrors?.quantity);
|
||||
}
|
||||
await writePayment(session);
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof LinkError)
|
||||
return fail(error.message, error.quantity ? { quantity: error.quantity } : undefined);
|
||||
throw error;
|
||||
}
|
||||
return ok({ closed });
|
||||
}
|
||||
|
||||
/** Varlık hareketi eklenemedi: transaction geri alınır, hata kullanıcıya döner. */
|
||||
class LinkError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly quantity?: string,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Cari listesi için açık aynî kayıt özetleri (cari kimliği → etiketler). */
|
||||
export async function openInKindByContact(
|
||||
userId: string,
|
||||
|
||||
Reference in New Issue
Block a user