feat(shopping): alışverişi tamamla transaction'ı ve karşılaştırma motoru
- tek transaction: stok alımı, fiş kaynaklı fiyat, mağaza başına bölünmüş gider, fiş ekleri, kalanların taşınması - muadil, markasız ürün tipi ve plansız ekstra satırları; stok → gider kategorisi eşlemesi ve yedek kategori - compareShopping saf fonksiyonu: aynı/muadil/alınmadı/ekstra, farkın nedenlere tam ayrışması - tamamlanan listede rapor görüntüsü; testler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,8 +29,10 @@ import {
|
||||
suggestionHidden,
|
||||
type ShoppingItemKind,
|
||||
type ShoppingItemSource,
|
||||
type ShoppingCompletion,
|
||||
type ShoppingListStatus,
|
||||
} from "@/shared/shopping";
|
||||
import { compareShopping, type ComparisonSummary } from "@/shared/shopping-compare";
|
||||
import { planShopping, type PlanResult } from "@/shared/shopping-plan";
|
||||
import { compareTr } from "@/shared/search";
|
||||
import { formatStock, type PurchaseSuggestion } from "@/shared/stock";
|
||||
@@ -44,7 +46,7 @@ import type { StoreKind } from "@/shared/stores";
|
||||
|
||||
const t = tr.shopping;
|
||||
|
||||
type ItemLean = {
|
||||
export type ItemLean = {
|
||||
_id: Types.ObjectId;
|
||||
kind: ShoppingItemKind;
|
||||
productTypeId: Types.ObjectId | null;
|
||||
@@ -59,13 +61,14 @@ type ItemLean = {
|
||||
addedAt: Date;
|
||||
};
|
||||
|
||||
type ListLean = {
|
||||
export type ListLean = {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
status: ShoppingListStatus;
|
||||
note: string;
|
||||
items: ItemLean[];
|
||||
completedAt: Date | null;
|
||||
completion: ShoppingCompletion | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
@@ -154,6 +157,8 @@ export type ShoppingListDetail = {
|
||||
/** Kalem taşımak için diğer aktif listeler. */
|
||||
otherLists: { id: string; name: string }[];
|
||||
canWrite: boolean;
|
||||
/** Tamamlanmış listede karşılaştırma raporu. */
|
||||
report: { completion: ShoppingCompletion; summary: ComparisonSummary } | null;
|
||||
};
|
||||
|
||||
// --- Yardımcılar ---
|
||||
@@ -401,7 +406,7 @@ function itemView(
|
||||
};
|
||||
}
|
||||
|
||||
async function buildPlan(
|
||||
export async function buildPlan(
|
||||
userId: string,
|
||||
m: Membership,
|
||||
items: ItemLean[],
|
||||
@@ -576,12 +581,27 @@ export async function getShoppingList(
|
||||
},
|
||||
otherLists: others.map((o) => ({ id: String(o._id), name: o.name })),
|
||||
canWrite,
|
||||
report: list.completion
|
||||
? {
|
||||
completion: list.completion,
|
||||
summary: compareShopping(
|
||||
list.completion.rows.map((r) => ({
|
||||
match: r.match,
|
||||
plan: r.plan,
|
||||
actual:
|
||||
r.amountMinor === null
|
||||
? null
|
||||
: { productId: r.productId, packages: r.packages, amountMinor: r.amountMinor },
|
||||
})),
|
||||
),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
// --- Yazma: listeler ---
|
||||
|
||||
async function writableList(actorId: string, householdId: string, listId: string) {
|
||||
export async function writableList(actorId: string, householdId: string, listId: string) {
|
||||
const m = await resolveMembership(actorId, householdId, "member");
|
||||
if (!m) return { error: fail(tr.common.unauthorized) } as const;
|
||||
const list = await loadList(m, listId);
|
||||
|
||||
Reference in New Issue
Block a user