Dişler ({job.teeth?.length ?? job.memberCount})
diff --git a/src/lib/appwrite/job-actions.ts b/src/lib/appwrite/job-actions.ts
index 64cf535..ebea74c 100644
--- a/src/lib/appwrite/job-actions.ts
+++ b/src/lib/appwrite/job-actions.ts
@@ -297,9 +297,12 @@ export async function acceptJobAction(
try {
const { tablesDB } = createAdminClient();
+ // Accepting the job = lab took the impression, started substructure work.
+ // Step jumps straight to alt_yapi_prova; location flips to at_lab.
await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
status: "in_progress",
- currentStep: "olcu",
+ currentStep: "alt_yapi_prova",
+ location: "at_lab",
});
await appendJobHistory({ job, step: "olcu", completedBy: ctx.user.id });
void logAudit({
@@ -308,12 +311,16 @@ export async function acceptJobAction(
action: "update",
entityType: "job",
entityId: jobId,
- changes: { status: "in_progress", currentStep: "olcu" },
+ changes: {
+ status: "in_progress",
+ currentStep: "alt_yapi_prova",
+ location: "at_lab",
+ },
});
void createNotification({
tenantId: job.clinicTenantId,
jobId,
- message: `${ctx.settings?.companyName ?? "Lab"} hasta ${job.patientCode} işini işleme aldı.`,
+ message: `${ctx.settings?.companyName ?? "Lab"} hasta ${job.patientCode} işini işleme aldı, alt yapı üretiminde.`,
});
} catch (e) {
return { ok: false, error: appwriteError(e, "Kabul edilemedi.") };
@@ -325,7 +332,14 @@ export async function acceptJobAction(
return { ok: true };
}
-export async function advanceStepAction(
+/**
+ * Lab hands the work back to the clinic for the next physical step
+ * (prova or final delivery). The current step stays the same — only the
+ * location flips at_lab → at_clinic. If the lab is finishing the last
+ * production step (cila_bitim), that's the final delivery and the job
+ * status becomes "sent".
+ */
+export async function handToClinicAction(
_prev: JobActionState,
formData: FormData,
): Promise {
@@ -339,7 +353,7 @@ export async function advanceStepAction(
requireRole(ctx, ["owner", "admin", "member"]);
requireTenantKind(ctx, ["lab"]);
} catch {
- return { ok: false, error: "Sadece laboratuvar aşama ilerletebilir." };
+ return { ok: false, error: "Sadece laboratuvar kliniğe gönderebilir." };
}
const job = await loadJobForTenant(jobId, ctx.tenantId);
@@ -347,36 +361,28 @@ export async function advanceStepAction(
return { ok: false, error: "İş bulunamadı." };
}
if (job.status !== "in_progress") {
- return { ok: false, error: "Yalnızca işleme alınmış işler ilerletilebilir." };
+ return { ok: false, error: "Sadece işlemdeki işler kliniğe gönderilebilir." };
+ }
+ if (job.location !== "at_lab") {
+ return { ok: false, error: "İş zaten kliniğe gönderilmiş." };
+ }
+ if (!job.currentStep) {
+ return { ok: false, error: "Mevcut aşama bilinmiyor." };
}
- const currentIdx = job.currentStep ? JOB_STEP_ORDER.indexOf(job.currentStep) : -1;
- if (currentIdx < 0) return { ok: false, error: "Mevcut aşama bilinmiyor." };
- const nextIdx = currentIdx + 1;
- const isFinalStepComplete = currentIdx === JOB_STEP_ORDER.length - 1;
+ const isFinalStep = job.currentStep === "cila_bitim";
try {
const { tablesDB } = createAdminClient();
- if (isFinalStepComplete) {
+ if (isFinalStep) {
+ // Final delivery — production is done, status moves to sent.
await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
status: "sent",
- });
- void logAudit({
- tenantId: ctx.tenantId,
- userId: ctx.user.id,
- action: "update",
- entityType: "job",
- entityId: jobId,
- changes: { status: "sent" },
- });
- } else {
- const nextStep = JOB_STEP_ORDER[nextIdx];
- await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
- currentStep: nextStep,
+ location: "at_clinic",
});
await appendJobHistory({
job,
- step: job.currentStep!,
+ step: "cila_bitim",
completedBy: ctx.user.id,
note,
});
@@ -386,27 +392,43 @@ export async function advanceStepAction(
action: "update",
entityType: "job",
entityId: jobId,
- changes: { currentStep: nextStep, completedStep: job.currentStep },
+ changes: { status: "sent", location: "at_clinic" },
+ });
+ void syncFinanceForJob({ ...job, status: "sent" });
+ void createNotification({
+ tenantId: job.clinicTenantId,
+ jobId,
+ message: `Hasta ${job.patientCode} cila/bitim tamamlandı, nihai teslime gönderildi.`,
+ });
+ } else {
+ // Prova için klinike geçici teslim — step aynı, location değişti.
+ await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
+ location: "at_clinic",
+ });
+ await appendJobHistory({
+ job,
+ step: job.currentStep,
+ completedBy: ctx.user.id,
+ note,
+ });
+ void logAudit({
+ tenantId: ctx.tenantId,
+ userId: ctx.user.id,
+ action: "update",
+ entityType: "job",
+ entityId: jobId,
+ changes: { location: "at_clinic", handedOffStep: job.currentStep },
+ });
+ const stepLabel =
+ job.currentStep === "alt_yapi_prova" ? "alt yapı" : "üst yapı";
+ void createNotification({
+ tenantId: job.clinicTenantId,
+ jobId,
+ message: `Hasta ${job.patientCode} ${stepLabel} provasına hazır, kliniğe gönderildi.`,
});
}
} catch (e) {
- return { ok: false, error: appwriteError(e, "İlerletilemedi.") };
- }
-
- if (isFinalStepComplete) {
- // Record completion of the last step too, then mark sent.
- await appendJobHistory({
- job,
- step: job.currentStep!,
- completedBy: ctx.user.id,
- note,
- });
- void syncFinanceForJob({ ...job, status: "sent" });
- void createNotification({
- tenantId: job.clinicTenantId,
- jobId,
- message: `Hasta ${job.patientCode} işi gönderildi. Teslim alındığında onaylayın.`,
- });
+ return { ok: false, error: appwriteError(e, "Gönderilemedi.") };
}
revalidatePath(`/jobs/${jobId}`);
@@ -416,6 +438,89 @@ export async function advanceStepAction(
return { ok: true };
}
+/**
+ * Clinic confirms the prova was successful. Step advances to the next
+ * production stage and location flips back at_clinic → at_lab so the
+ * lab can pick the work back up.
+ */
+export async function approveAtClinicAction(
+ _prev: JobActionState,
+ formData: FormData,
+): Promise {
+ const jobId = String(formData.get("jobId") ?? "").trim();
+ if (!jobId) return { ok: false, error: "İş bulunamadı." };
+ const note = String(formData.get("note") ?? "").trim() || undefined;
+
+ let ctx;
+ try {
+ ctx = await requireTenant();
+ requireRole(ctx, ["owner", "admin", "member"]);
+ requireTenantKind(ctx, ["clinic"]);
+ } catch {
+ return { ok: false, error: "Sadece klinik provayı onaylayabilir." };
+ }
+
+ const job = await loadJobForTenant(jobId, ctx.tenantId);
+ if (!job || job.clinicTenantId !== ctx.tenantId) {
+ return { ok: false, error: "İş bulunamadı." };
+ }
+ if (job.status !== "in_progress") {
+ return { ok: false, error: "Yalnızca işlemdeki provalar onaylanabilir." };
+ }
+ if (job.location !== "at_clinic") {
+ return { ok: false, error: "İş şu an klinikte değil." };
+ }
+ if (!job.currentStep) {
+ return { ok: false, error: "Mevcut aşama bilinmiyor." };
+ }
+
+ const currentIdx = JOB_STEP_ORDER.indexOf(job.currentStep);
+ const nextStep = JOB_STEP_ORDER[currentIdx + 1];
+ if (!nextStep) {
+ return { ok: false, error: "Bu aşamadan ileri gidilemez." };
+ }
+
+ try {
+ const { tablesDB } = createAdminClient();
+ await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
+ currentStep: nextStep,
+ location: "at_lab",
+ });
+ await appendJobHistory({
+ job,
+ step: job.currentStep,
+ completedBy: ctx.user.id,
+ note,
+ });
+ void logAudit({
+ tenantId: ctx.tenantId,
+ userId: ctx.user.id,
+ action: "update",
+ entityType: "job",
+ entityId: jobId,
+ changes: {
+ currentStep: nextStep,
+ location: "at_lab",
+ completedStep: job.currentStep,
+ },
+ });
+ const stepLabel =
+ job.currentStep === "alt_yapi_prova" ? "alt yapı" : "üst yapı";
+ void createNotification({
+ tenantId: job.labTenantId,
+ jobId,
+ message: `Hasta ${job.patientCode} ${stepLabel} provası onaylandı, lab tarafına geri döndü.`,
+ });
+ } catch (e) {
+ return { ok: false, error: appwriteError(e, "Onaylanamadı.") };
+ }
+
+ revalidatePath(`/jobs/${jobId}`);
+ revalidatePath("/jobs/inbound");
+ revalidatePath("/jobs/outbound");
+ return { ok: true };
+}
+
export async function markDeliveredAction(
_prev: JobActionState,
formData: FormData,
diff --git a/src/lib/appwrite/job-types.ts b/src/lib/appwrite/job-types.ts
index 7191d36..4dcbdbe 100644
--- a/src/lib/appwrite/job-types.ts
+++ b/src/lib/appwrite/job-types.ts
@@ -1,4 +1,4 @@
-import type { JobStatus, JobStep, ProstheticType } from "./schema";
+import type { JobLocation, JobStatus, JobStep, ProstheticType } from "./schema";
export type JobFormState = {
ok: boolean;
@@ -38,6 +38,11 @@ export const JOB_STEP_ORDER: JobStep[] = [
"cila_bitim",
];
+export const JOB_LOCATION_LABELS: Record = {
+ at_clinic: "Klinikte",
+ at_lab: "Laboratuvarda",
+};
+
export const PROSTHETIC_TYPE_LABELS: Record = {
metal_porselen: "Metal Porselen",
zirkonyum: "Zirkonyum",
diff --git a/src/lib/appwrite/schema.ts b/src/lib/appwrite/schema.ts
index 94ad14b..42cfc05 100644
--- a/src/lib/appwrite/schema.ts
+++ b/src/lib/appwrite/schema.ts
@@ -75,6 +75,7 @@ export interface Connection extends Row {
export type JobStatus = "pending" | "in_progress" | "sent" | "delivered" | "cancelled";
export type JobStep = "olcu" | "alt_yapi_prova" | "ust_yapi_prova" | "cila_bitim";
+export type JobLocation = "at_clinic" | "at_lab";
export type ProstheticType =
| "metal_porselen"
| "zirkonyum"
@@ -109,6 +110,7 @@ export interface Job extends Row {
currency?: string;
status: JobStatus;
currentStep?: JobStep;
+ location?: JobLocation;
dueDate?: string;
}