bd5a20ce64
- 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>
129 lines
4.8 KiB
TypeScript
129 lines
4.8 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
import { tr } from "@/i18n/tr";
|
|
import { fail, type ActionResult } from "@/lib/action-result";
|
|
import { getHouseholdActionContext } from "@/lib/dal";
|
|
import {
|
|
shoppingCompletionSchema,
|
|
shoppingItemFormSchema,
|
|
shoppingItemUpdateSchema,
|
|
shoppingListFormSchema,
|
|
} from "@/shared/schemas/shopping";
|
|
import { completeShopping } from "./completion";
|
|
import {
|
|
addShoppingItem,
|
|
approveSuggestions,
|
|
createShoppingList,
|
|
deleteShoppingList,
|
|
dismissSuggestion,
|
|
moveShoppingItem,
|
|
removeShoppingItem,
|
|
setShoppingItemChecked,
|
|
updateShoppingItem,
|
|
updateShoppingList,
|
|
} from "./service";
|
|
|
|
const idSchema = z.string().regex(/^[a-f0-9]{24}$/i);
|
|
|
|
function fieldErrors(error: z.ZodError) {
|
|
const out: Record<string, string> = {};
|
|
for (const issue of error.issues) {
|
|
const key = issue.path.join(".");
|
|
if (key && !out[key]) out[key] = issue.message;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
async function run<T, R>(
|
|
schema: z.ZodType<T>,
|
|
input: unknown,
|
|
fn: (userId: string, householdId: string, data: T) => Promise<ActionResult<R>>,
|
|
): Promise<ActionResult<R>> {
|
|
const ctx = await getHouseholdActionContext("member");
|
|
if (!ctx) return fail(tr.common.unauthorized);
|
|
const parsed = schema.safeParse(input);
|
|
if (!parsed.success) return fail(tr.adminUsers.errors.checkFields, fieldErrors(parsed.error));
|
|
const result = await fn(ctx.user.id, ctx.household.id, parsed.data);
|
|
if (result.ok) revalidatePath("/", "layout");
|
|
return result;
|
|
}
|
|
|
|
const ids = (...keys: string[]) => z.object(Object.fromEntries(keys.map((k) => [k, idSchema])));
|
|
|
|
export async function saveShoppingListAction(listId: string | null, input: unknown) {
|
|
if (listId === null)
|
|
return run(shoppingListFormSchema, input, (u, h, d) => createShoppingList(u, h, d));
|
|
const id = idSchema.safeParse(listId);
|
|
if (!id.success) return fail(tr.common.notFound);
|
|
return run(shoppingListFormSchema, input, (u, h, d) => updateShoppingList(u, h, id.data, d));
|
|
}
|
|
|
|
export async function deleteShoppingListAction(listId: unknown) {
|
|
const id = idSchema.safeParse(listId);
|
|
if (!id.success) return fail(tr.common.notFound);
|
|
return run(z.unknown(), null, (u, h) => deleteShoppingList(u, h, id.data));
|
|
}
|
|
|
|
export async function addShoppingItemAction(listId: unknown, input: unknown) {
|
|
const id = idSchema.safeParse(listId);
|
|
if (!id.success) return fail(tr.common.notFound);
|
|
return run(shoppingItemFormSchema, input, (u, h, d) => addShoppingItem(u, h, id.data, d));
|
|
}
|
|
|
|
export async function updateShoppingItemAction(listId: unknown, itemId: unknown, input: unknown) {
|
|
const ref = ids("listId", "itemId").safeParse({ listId, itemId });
|
|
if (!ref.success) return fail(tr.common.notFound);
|
|
return run(shoppingItemUpdateSchema, input, (u, h, d) =>
|
|
updateShoppingItem(u, h, ref.data.listId!, ref.data.itemId!, d),
|
|
);
|
|
}
|
|
|
|
export async function removeShoppingItemAction(listId: unknown, itemId: unknown) {
|
|
const ref = ids("listId", "itemId").safeParse({ listId, itemId });
|
|
if (!ref.success) return fail(tr.common.notFound);
|
|
return run(z.unknown(), null, (u, h) =>
|
|
removeShoppingItem(u, h, ref.data.listId!, ref.data.itemId!),
|
|
);
|
|
}
|
|
|
|
export async function setShoppingItemCheckedAction(
|
|
listId: unknown,
|
|
itemId: unknown,
|
|
checked: unknown,
|
|
) {
|
|
const ref = ids("listId", "itemId").safeParse({ listId, itemId });
|
|
if (!ref.success || typeof checked !== "boolean") return fail(tr.common.notFound);
|
|
return run(z.unknown(), null, (u, h) =>
|
|
setShoppingItemChecked(u, h, ref.data.listId!, ref.data.itemId!, checked),
|
|
);
|
|
}
|
|
|
|
export async function moveShoppingItemAction(listId: unknown, itemId: unknown, target: unknown) {
|
|
const ref = ids("listId", "itemId", "target").safeParse({ listId, itemId, target });
|
|
if (!ref.success) return fail(tr.common.notFound);
|
|
return run(z.unknown(), null, (u, h) =>
|
|
moveShoppingItem(u, h, ref.data.listId!, ref.data.itemId!, ref.data.target!),
|
|
);
|
|
}
|
|
|
|
export async function approveSuggestionsAction(listId: unknown, productTypeIds: unknown) {
|
|
const id = idSchema.safeParse(listId);
|
|
const types = z.array(idSchema).min(1).max(200).safeParse(productTypeIds);
|
|
if (!id.success || !types.success) return fail(tr.common.notFound);
|
|
return run(z.unknown(), null, (u, h) => approveSuggestions(u, h, id.data, types.data));
|
|
}
|
|
|
|
export async function dismissSuggestionAction(productTypeId: unknown) {
|
|
const id = idSchema.safeParse(productTypeId);
|
|
if (!id.success) return fail(tr.common.notFound);
|
|
return run(z.unknown(), null, (u, h) => dismissSuggestion(u, h, id.data));
|
|
}
|
|
|
|
export async function completeShoppingAction(listId: unknown, input: unknown) {
|
|
const id = idSchema.safeParse(listId);
|
|
if (!id.success) return fail(tr.common.notFound);
|
|
return run(shoppingCompletionSchema, input, (u, h, d) => completeShopping(u, h, id.data, d));
|
|
}
|