feat(prices): fiyat kayıtları, birim fiyat motoru ve analizde aylık maliyet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 12:27:51 +03:00
parent 78503b1787
commit e67a1d5411
16 changed files with 1212 additions and 6 deletions
+18 -4
View File
@@ -12,6 +12,7 @@ import {
} from "@/features/categories/tree";
import { hasHouseholdRole } from "@/features/households/roles";
import { resolveMembership, type Membership } from "@/features/households/service";
import { latestTypeUnitPrices } from "@/features/prices/service";
import { tr } from "@/i18n/tr";
import { fail, ok, type ActionResult } from "@/lib/action-result";
import { writeAudit } from "@/lib/audit";
@@ -24,6 +25,7 @@ import { StockCountModel } from "@/models/stock-count";
import { StockMovementModel } from "@/models/stock-movement";
import { todayIstanbul } from "@/shared/dates";
import { shiftMonth } from "@/shared/budget";
import { QUANTITY_SCALE } from "@/shared/quantity";
import type {
ProductTypeForm,
StockCategoryForm,
@@ -1242,6 +1244,10 @@ export type StockAnalysisRow = {
avgMonthlyWasted: number | null;
/** Fire / (kullanım + fire), %. */
wastePct: number | null;
/** Son bilinen birim fiyat (ürün tipi biriminde, kesirli kuruş) ve tarihi. */
unitPrice: { minor: number; date: string } | null;
/** Aylık ortalama maliyet tahmini: ortalama tüketim × son birim fiyat (kuruş). */
avgMonthlyCostMinor: number | null;
/** Bu ay (bugüne kadar). */
thisMonth: MonthUsage;
};
@@ -1264,10 +1270,11 @@ export async function getStockAnalysis(
shiftMonth(thisMonth, i - ANALYSIS_MONTHS),
);
const m = (await resolveMembership(userId, householdId, "viewer"))!;
const moves = await loadMoves(
m.householdId,
data.items.map((i) => new Types.ObjectId(i.id)),
);
const ids = data.items.map((i) => new Types.ObjectId(i.id));
const [moves, prices] = await Promise.all([
loadMoves(m.householdId, ids),
latestTypeUnitPrices(m.householdId, ids, data.today),
]);
const rows = data.items.map((item): StockAnalysisRow => {
const list = moves.get(item.id) ?? [];
const [usage, current] = [monthlyUsage(list, months), monthlyUsage(list, [thisMonth])[0]!];
@@ -1277,6 +1284,8 @@ export async function getStockAnalysis(
const sum = (k: "used" | "wasted") => full.reduce((s, u) => s + u[k], 0);
const used = sum("used");
const wasted = sum("wasted");
const price = prices.get(item.id) ?? null;
const avgUsed = full.length ? used / full.length : null;
return {
id: item.id,
name: item.name,
@@ -1287,6 +1296,11 @@ export async function getStockAnalysis(
avgMonthlyUsed: full.length ? roundForDisplay(used / full.length) : null,
avgMonthlyWasted: full.length ? roundForDisplay(wasted / full.length) : null,
wastePct: used + wasted > 0 ? Math.round((wasted * 100) / (used + wasted)) : null,
unitPrice: price ? { minor: price.unitPriceMinor, date: price.date } : null,
avgMonthlyCostMinor:
price && avgUsed !== null
? Math.round((avgUsed * price.unitPriceMinor) / QUANTITY_SCALE)
: null,
thisMonth: current,
};
});