9a60085d14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
import { z } from "zod";
|
||
import { tr } from "../../i18n/tr";
|
||
import { ASSET_KEYS } from "../assets";
|
||
import { parseMoney } from "../money";
|
||
import { parseQuantity } from "../quantity";
|
||
|
||
const v = tr.validation;
|
||
const k = tr.inKind.validation;
|
||
const ymd = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, v.date);
|
||
const objectId = z.string().regex(/^[a-f0-9]{24}$/i, tr.transactions.validation.selectAccount);
|
||
const emptyToNull = (x: unknown) => (x === "" || x === undefined ? null : x);
|
||
|
||
/** Boş → null; dolu → pozitif kuruş. */
|
||
const optionalMoney = z.preprocess(
|
||
emptyToNull,
|
||
z
|
||
.string()
|
||
.trim()
|
||
.transform((s, ctx) => {
|
||
const minor = parseMoney(s);
|
||
if (minor === null || minor < 0) {
|
||
ctx.addIssue({ code: "custom", message: v.money });
|
||
return z.NEVER;
|
||
}
|
||
return minor;
|
||
})
|
||
.nullable(),
|
||
);
|
||
|
||
const quantity = z
|
||
.string()
|
||
.trim()
|
||
.transform((s, ctx) => {
|
||
const q = parseQuantity(s);
|
||
if (q === null || q <= 0) {
|
||
ctx.addIssue({ code: "custom", message: k.quantity });
|
||
return z.NEVER;
|
||
}
|
||
return q;
|
||
});
|
||
|
||
/** Fiyat girişi: satış boşsa alışla aynı (tek fiyat girilirse ikisine de yazılır — Faz 0 kararı). */
|
||
export const assetPriceFormSchema = z
|
||
.object({
|
||
assetKey: z.enum(ASSET_KEYS),
|
||
date: ymd,
|
||
buy: optionalMoney,
|
||
sell: optionalMoney,
|
||
})
|
||
.superRefine((x, ctx) => {
|
||
if (x.buy === null && x.sell === null)
|
||
ctx.addIssue({ code: "custom", path: ["buy"], message: k.price });
|
||
})
|
||
.transform((x) => ({
|
||
assetKey: x.assetKey,
|
||
date: x.date,
|
||
buyMinor: (x.buy ?? x.sell)!,
|
||
sellMinor: (x.sell ?? x.buy)!,
|
||
}));
|
||
export type AssetPriceForm = z.output<typeof assetPriceFormSchema>;
|
||
|
||
/** Aynî borç/alacak kaydı. Birim fiyat boşsa fiyat tablosundan (o tarihteki son fiyat) alınır. */
|
||
export const inKindFormSchema = z.object({
|
||
direction: z.enum(["debt", "receivable"]),
|
||
assetKey: z.enum(ASSET_KEYS),
|
||
quantity,
|
||
date: ymd,
|
||
unitPrice: optionalMoney,
|
||
note: z.string().trim().max(500).default(""),
|
||
savingsAccountId: z.preprocess(emptyToNull, objectId.nullable()),
|
||
monthlyPlan: optionalMoney,
|
||
});
|
||
export type InKindForm = z.output<typeof inKindFormSchema>;
|
||
|
||
/** Birikim katkısı: hesaptan borcun birikim hesabına transfer. */
|
||
export const contributionSchema = z.object({
|
||
fromAccountId: objectId,
|
||
amount: z
|
||
.string()
|
||
.trim()
|
||
.transform((s, ctx) => {
|
||
const minor = parseMoney(s);
|
||
if (minor === null || minor <= 0) {
|
||
ctx.addIssue({ code: "custom", message: tr.transactions.validation.amountPositive });
|
||
return z.NEVER;
|
||
}
|
||
return minor;
|
||
}),
|
||
date: ymd,
|
||
});
|
||
export type ContributionForm = z.output<typeof contributionSchema>;
|
||
|
||
/**
|
||
* "Ödeme gerçekleşti". Aynı varlıkla: miktar zorunlu, TL (varlığı almak için harcanan) isteğe
|
||
* bağlı. TL karşılığıyla: tutar ve hesap zorunlu; miktar boşsa güncel fiyattan hesaplanır.
|
||
*/
|
||
export const paymentFormSchema = z
|
||
.object({
|
||
method: z.enum(["asset", "cash"]),
|
||
date: ymd,
|
||
quantity: z.preprocess(emptyToNull, quantity.nullable()),
|
||
amount: optionalMoney,
|
||
accountId: z.preprocess(emptyToNull, objectId.nullable()),
|
||
/** Aynı varlıkla ödemede bağlanacak varlık (Faz 9b): borçta varlıktan düşer, alacakta eklenir. */
|
||
assetId: z.preprocess(emptyToNull, objectId.nullable()).default(null),
|
||
note: z.string().trim().max(300).default(""),
|
||
})
|
||
.superRefine((x, ctx) => {
|
||
if (x.assetId && x.method !== "asset")
|
||
ctx.addIssue({ code: "custom", path: ["assetId"], message: k.assetOnlyWithAsset });
|
||
if (x.assetId && x.amount)
|
||
ctx.addIssue({ code: "custom", path: ["amount"], message: k.noAmountWithAsset });
|
||
if (x.method === "asset" && x.quantity === null)
|
||
ctx.addIssue({ code: "custom", path: ["quantity"], message: k.quantity });
|
||
if (x.method === "cash") {
|
||
if (!x.amount) ctx.addIssue({ code: "custom", path: ["amount"], message: k.amount });
|
||
if (!x.accountId) ctx.addIssue({ code: "custom", path: ["accountId"], message: k.account });
|
||
}
|
||
if (x.amount && !x.accountId)
|
||
ctx.addIssue({ code: "custom", path: ["accountId"], message: k.account });
|
||
});
|
||
export type PaymentForm = z.output<typeof paymentFormSchema>;
|