feat(stock): sayım oturumları, fire ayarlı öneri motoru ve tüketim analizi

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 10:58:06 +03:00
parent 5e0f1b5901
commit 814bdbbc2a
12 changed files with 1219 additions and 69 deletions
+40
View File
@@ -118,3 +118,43 @@ export const stockCountFormSchema = z.object({
});
export type StockCountFormInput = z.input<typeof stockCountFormSchema>;
export type StockCountForm = z.output<typeof stockCountFormSchema>;
/**
* Toplu sayım satırı: sayılan miktar boşsa ürün sayılmamış sayılır (atlanır). Eksiğin bir kısmı
* fire olarak işaretlenebilir (neden zorunlu).
*/
const stockCountRowSchema = z
.object({
productTypeId: z.string().regex(/^[a-f0-9]{24}$/i),
actualQuantity: optionalQuantity,
wasteQuantity: optionalQuantity.default(null),
wasteReason: z.preprocess(
(x) => (x === "" || x === undefined ? null : x),
z.enum(WASTE_REASONS).nullable(),
),
})
.superRefine((x, ctx) => {
if (x.wasteQuantity && !x.wasteReason)
ctx.addIssue({ code: "custom", path: ["wasteReason"], message: s.wasteReason });
if (x.wasteQuantity && x.actualQuantity === null)
ctx.addIssue({ code: "custom", path: ["actualQuantity"], message: s.quantity });
})
.transform((x) => ({
...x,
wasteQuantity: x.wasteQuantity || null,
wasteReason: x.wasteQuantity ? x.wasteReason : null,
}));
export const stockCountSheetSchema = z
.object({
date: ymd,
categoryId: optionalId,
note: z.string().trim().max(300).default(""),
rows: z.array(stockCountRowSchema).max(500),
})
.superRefine((x, ctx) => {
if (!x.rows.some((r) => r.actualQuantity !== null))
ctx.addIssue({ code: "custom", path: ["rows"], message: s.nothingCounted });
});
export type StockCountSheetInput = z.input<typeof stockCountSheetSchema>;
export type StockCountSheet = z.output<typeof stockCountSheetSchema>;