feat(transactions): işlem servisi, bakiye hesaplama, satıcı kuralları ve izolasyon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import "server-only";
|
||||
import { Types } from "mongoose";
|
||||
import { getHouseholdDetail, resolveMembership } from "@/features/households/service";
|
||||
import { computeAccountDeltas } from "@/features/transactions/service";
|
||||
import { tr } from "@/i18n/tr";
|
||||
import { fail, ok, type ActionResult } from "@/lib/action-result";
|
||||
import { diffFields, writeAudit } from "@/lib/audit";
|
||||
@@ -94,12 +95,12 @@ type Lean = {
|
||||
const canSeeBalance = (a: Lean, viewerId: string) =>
|
||||
!a.isPrivate || String(a.ownerUserId) === viewerId;
|
||||
|
||||
/** FAZ 6b: açılış bakiyesi + işlemler. Şimdilik açılış bakiyesi. */
|
||||
function computeBalance(a: Lean): number {
|
||||
return a.openingBalanceMinor;
|
||||
/** Güncel bakiye = açılış bakiyesi + gerçekleşmiş işlemlerin etkisi (tek doğruluk kaynağı: işlemler). */
|
||||
function computeBalance(a: Lean, deltas: ReadonlyMap<string, number>): number {
|
||||
return a.openingBalanceMinor + (deltas.get(String(a._id)) ?? 0);
|
||||
}
|
||||
|
||||
function toView(a: Lean, viewerId: string): AccountView {
|
||||
function toView(a: Lean, viewerId: string, deltas: ReadonlyMap<string, number>): AccountView {
|
||||
const visible = canSeeBalance(a, viewerId);
|
||||
return {
|
||||
id: String(a._id),
|
||||
@@ -113,7 +114,7 @@ function toView(a: Lean, viewerId: string): AccountView {
|
||||
archived: Boolean(a.archivedAt),
|
||||
isPrivate: a.isPrivate,
|
||||
ownerUserId: a.ownerUserId ? String(a.ownerUserId) : null,
|
||||
balanceMinor: visible ? computeBalance(a) : null,
|
||||
balanceMinor: visible ? computeBalance(a, deltas) : null,
|
||||
openingBalanceMinor: visible ? a.openingBalanceMinor : null,
|
||||
openingDate: toIstanbulYmd(a.openingDate),
|
||||
overdraft: a.overdraft?.enabled ? a.overdraft : null,
|
||||
@@ -140,7 +141,10 @@ export async function listAccounts(
|
||||
const rows = (await AccountModel.find(filter).lean<Lean[]>()).sort(
|
||||
(a, b) => a.order - b.order || compareTr(a.name, b.name),
|
||||
);
|
||||
const household = await HouseholdModel.findById(m.householdId).select({ settings: 1 }).lean();
|
||||
const [household, deltas] = await Promise.all([
|
||||
HouseholdModel.findById(m.householdId).select({ settings: 1 }).lean(),
|
||||
computeAccountDeltas(m.householdId),
|
||||
]);
|
||||
const includePrivate = household?.settings?.includePrivateAccountsInTotals ?? true;
|
||||
|
||||
const totals: AccountTotals = {
|
||||
@@ -158,12 +162,12 @@ export async function listAccounts(
|
||||
continue;
|
||||
}
|
||||
if (!visible) totals.includesHidden = true;
|
||||
const balance = computeBalance(a);
|
||||
const balance = computeBalance(a, deltas);
|
||||
if (isLiabilityType(a.type)) totals.cardDebtMinor += balance;
|
||||
else totals.assetsMinor += balance;
|
||||
}
|
||||
totals.netMinor = totals.assetsMinor + totals.cardDebtMinor;
|
||||
return { accounts: rows.map((a) => toView(a, userId)), totals };
|
||||
return { accounts: rows.map((a) => toView(a, userId, deltas)), totals };
|
||||
}
|
||||
|
||||
/** Form → belge alanları. Kart için açılış borcu negatif saklanır. */
|
||||
|
||||
Reference in New Issue
Block a user