feat(cards): kart/KMH dönem hesabı, taksitli harcama, kart ve KMH özeti servisleri, 006 migration

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 11:19:17 +03:00
parent 57067b0fd0
commit cc624af69f
22 changed files with 1113 additions and 19 deletions
+2
View File
@@ -68,6 +68,8 @@ export const accountFormSchema = z
cardDueKind: z.enum(DUE_RULE_KINDS),
cardDueValue: day(31),
cardPaymentAccountId: objectIdOrEmpty,
/** Boş → varsayılan kural (limite göre %20 / %40). */
cardMinimumRate: z.string().default("").pipe(percentToBp),
})
.superRefine((a, ctx) => {
if (a.type === "meal_card" && !a.mealCardProvider) {
+12
View File
@@ -2,6 +2,7 @@ import { z } from "zod";
import { tr } from "../../i18n/tr";
import { parseMoney } from "../money";
import { MAX_ATTACHMENTS_PER_RECORD } from "../attachments";
import { MAX_INSTALLMENTS } from "../card-periods";
import { MAX_SPLITS, normalizeTags, TRANSACTION_TYPES } from "../transactions";
const v = tr.validation;
@@ -53,6 +54,14 @@ export const transactionFormSchema = z
.union([z.string(), z.array(z.string())])
.default("")
.transform((x) => normalizeTags(x)),
/**
* Taksit sayısı (yalnızca kredi kartı gideri, oluştururken). 1 = peşin. Her taksit ayrı işlem
* olur; vadesi gelmeyenler planlıdır ve kendi döneminde otomatik gerçekleşir.
*/
installments: z.preprocess(
(v) => (v === "" || v === null || v === undefined ? 1 : v),
z.coerce.number().int().min(1).max(MAX_INSTALLMENTS, t.installmentsMax),
),
/** Bağlı ek dosyalar (sıralı). Yeni ekler önce /api/files ile yüklenir. */
attachmentIds: z
.array(z.string().regex(/^[a-f0-9]{24}$/i))
@@ -67,6 +76,9 @@ export const transactionFormSchema = z
ctx.addIssue({ code: "custom", path: ["toAccountId"], message: t.sameAccount });
return;
}
if (x.installments > 1 && (x.type !== "expense" || x.splits.length > 0)) {
ctx.addIssue({ code: "custom", path: ["installments"], message: t.installmentsExpenseOnly });
}
if (x.splits.length === 1) {
ctx.addIssue({ code: "custom", path: ["splits"], message: t.splitMin });
} else if (x.splits.length >= 2) {