Files
isletmem-kovakcrm/src/lib/validation/software.ts
T
kovakmedya 113988273f feat(software): catalog + customer assignments (M2M)
Software catalog with per-customer assignments via the customer_software
join table. Two tabs in one /software page:

Catalog tab:
- Software CRUD: name, version, description, defaultFee (TRY).
- Deleting a software cascades and removes all its assignments first
  (best-effort loop, then the catalog row), all wrapped in audit logs.

Assignments tab:
- M2M between customer and software with own fee (overrides defaultFee),
  billingPeriod (monthly default), startDate/endDate, notes.
- Form auto-fills fee from selected software's defaultFee.
- Both Sheet forms localized; date inputs round-tripped via toIsoDate
  (Appwrite expects ISO 8601 with TZ; HTML date input gives YYYY-MM-DD).
- Delete dialogs differentiated for catalog ('siliniyor') vs assignment
  ('kaldırılıyor').

New files:
- lib/validation/software.ts (softwareSchema + customerSoftwareSchema)
- lib/appwrite/software-actions.ts (6 server actions)
- lib/appwrite/software-queries.ts (listSoftware, listAssignments)
- lib/appwrite/software-types.ts (form state)
- /software route with SoftwareClient (Tabs), SoftwareFormSheet,
  AssignmentFormSheet, inline delete dialogs.

Empty states surface the right next-step CTA: 'önce müşteri ekleyin', or
'önce yazılım ekleyin', as appropriate.
2026-04-30 05:50:33 +03:00

42 lines
1.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod";
export const softwareSchema = z.object({
name: z.string().trim().min(1, "Yazılım adı zorunlu.").max(255),
version: z.string().trim().max(50).optional().transform((v) => (v ? v : undefined)),
description: z
.string()
.trim()
.max(2000)
.optional()
.transform((v) => (v ? v : undefined)),
defaultFee: z
.union([z.number(), z.string()])
.optional()
.transform((v) => {
if (v === undefined || v === "") return undefined;
const n = typeof v === "string" ? Number(v.replace(",", ".")) : v;
return Number.isFinite(n) ? n : undefined;
}),
});
export type SoftwareInput = z.infer<typeof softwareSchema>;
export const customerSoftwareSchema = z.object({
customerId: z.string().min(1, "Müşteri seçin."),
softwareId: z.string().min(1, "Yazılım seçin."),
startDate: z.string().optional().transform((v) => (v ? v : undefined)),
endDate: z.string().optional().transform((v) => (v ? v : undefined)),
fee: z
.union([z.number(), z.string()])
.optional()
.transform((v) => {
if (v === undefined || v === "") return undefined;
const n = typeof v === "string" ? Number(v.replace(",", ".")) : v;
return Number.isFinite(n) ? n : undefined;
}),
billingPeriod: z.enum(["monthly", "yearly", "onetime"]).optional().default("monthly"),
notes: z.string().trim().max(1000).optional().transform((v) => (v ? v : undefined)),
});
export type CustomerSoftwareInput = z.infer<typeof customerSoftwareSchema>;