feat(modules): connections, products, jobs (list/form/detail-placeholder)
Connections (clinic ↔ lab)
- request via member number, approve/reject (counterparty), cancel pending,
delete approved
- permission rows opened to both teams; audit log for every mutation
- /connections page: own code card, request form, pending inbound/outbound
tables, approved connections table with delete confirm
Products (lab catalog)
- createProstheticAction + update + archive/restore + delete (lab-only)
- zod validation, dev-mode error surfacing
- /products page: catalog table + add form + edit dialog. Hidden from
clinic accounts via requireTenantKind.
Jobs (work orders)
- createJobAction (clinic-only) — checks approved connection before write,
permissions opened to both clinic and lab teams
- listInboundJobs (lab perspective), listOutboundJobs (clinic perspective),
listApprovedLabsForClinic for the new-job form
- /jobs/inbound + /jobs/outbound tables with role-aware copy
- /jobs/new full form (lab select, patient code, prosthetic type, member
count, color, due date, price/currency, description)
- /jobs/[jobId] placeholder detail page with stepper visualisation;
status/step updates and file upload come next session
All new mutations follow the memory rules: schema-checked row payloads,
admin client behind requireTenant + requireRole/requireTenantKind, audit
log calls best-effort, no empty-string Radix Select values.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const PROSTHETIC_TYPES = [
|
||||
"metal_porselen",
|
||||
"zirkonyum",
|
||||
"implant_ustu_zirkonyum",
|
||||
"gecici",
|
||||
"e_max",
|
||||
"diger",
|
||||
] as const;
|
||||
|
||||
export const createJobSchema = z.object({
|
||||
labTenantId: z.string().min(1, "Laboratuvar seçin."),
|
||||
patientCode: z.string().trim().min(1, "Hasta kodu zorunlu.").max(50),
|
||||
prostheticType: z.enum(PROSTHETIC_TYPES, { message: "Protez türü seçin." }),
|
||||
memberCount: z
|
||||
.union([z.string(), z.number()])
|
||||
.transform((v) => {
|
||||
if (typeof v === "number") return v;
|
||||
const n = parseInt(v, 10);
|
||||
return Number.isFinite(n) ? n : NaN;
|
||||
})
|
||||
.pipe(z.number().int().min(1, "En az 1 üye.").max(32, "En fazla 32 üye.")),
|
||||
color: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(20)
|
||||
.optional()
|
||||
.transform((v) => (v ? v.toUpperCase() : undefined)),
|
||||
description: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(2000)
|
||||
.optional()
|
||||
.transform((v) => (v ? v : undefined)),
|
||||
price: z
|
||||
.union([z.string(), z.number()])
|
||||
.optional()
|
||||
.transform((v) => {
|
||||
if (v === undefined || v === "") return undefined;
|
||||
const n = typeof v === "number" ? v : Number(String(v).replace(",", "."));
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}),
|
||||
currency: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(8)
|
||||
.optional()
|
||||
.transform((v) => (v ? v.toUpperCase() : "TRY")),
|
||||
dueDate: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((v) => (v ? new Date(v).toISOString() : undefined)),
|
||||
});
|
||||
|
||||
export type CreateJobInput = z.infer<typeof createJobSchema>;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const PROSTHETIC_TYPES = [
|
||||
"metal_porselen",
|
||||
"zirkonyum",
|
||||
"implant_ustu_zirkonyum",
|
||||
"gecici",
|
||||
"e_max",
|
||||
"diger",
|
||||
] as const;
|
||||
|
||||
export const prostheticSchema = z.object({
|
||||
name: z.string().trim().min(1, "Ürün adı zorunlu.").max(255),
|
||||
type: z.enum(PROSTHETIC_TYPES, { message: "Protez türü seçin." }),
|
||||
unitPrice: z
|
||||
.union([z.string(), z.number()])
|
||||
.transform((v) => {
|
||||
if (typeof v === "number") return v;
|
||||
const n = Number(v.replace(",", "."));
|
||||
return Number.isFinite(n) ? n : NaN;
|
||||
})
|
||||
.pipe(z.number().min(0, "Negatif olamaz.")),
|
||||
currency: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(8)
|
||||
.optional()
|
||||
.transform((v) => (v ? v.toUpperCase() : "TRY")),
|
||||
});
|
||||
|
||||
export type ProstheticInput = z.infer<typeof prostheticSchema>;
|
||||
Reference in New Issue
Block a user