feat: job status/step flow, file upload, finance sync, notifications
Job lifecycle
- acceptJobAction (lab): pending → in_progress + currentStep=olcu
- advanceStepAction (lab): step ilerletir, son adım sonrası status=sent
- markDeliveredAction (clinic): sent → delivered
- cancelJobAction: pending iş iptali (her iki taraf)
- job_status_history her step transition'da idempotent kayıt
- Detay sayfası interactive panel + Aşama Geçmişi kartı
Job files (Appwrite Storage job-files bucket, 30MB/file)
- uploadJobFilesAction: çoklu dosya, mimeType'tan kind sınıflandırma
(scan/image/document), her iki team'e read permission, partial-fail
rollback (storage + row temizliği)
- deleteJobFileAction: yetkilendirilmiş silme, file + row birlikte
- JobFilesPanel: client-side select + upload + liste + indir + sil
- next.config bodySizeLimit 3mb → 100mb (toplu yükleme için)
Finance sync (idempotent)
- syncFinanceForJob helper: sent/delivered transition'larında klinik
payable + lab receivable rows (jobId+tenantId+type unique kontrolü,
her tarafta tek satır garanti)
- markFinancePaidAction / reopenFinanceAction: manuel ödendi/geri al
- /finance sayfası: stat kartlar (bekleyen alacak/borç, aylık gelir/gider)
+ hareketler tablosu, role-aware kopyalar
- Memory rule [[feedback_cross_entity_sync_helpers]]: best-effort, never
re-throws
Notifications
- createNotification helper, connection (request/approve) ve job
(create/accept/sent/delivered) eventlerinde tetikleniyor
- /notifications sayfası + tek tek / hepsi okundu işaretle
- Header'a Bell ikonu + okunmamış count badge (layout SSR'de besler)
- Middleware PROTECTED_PREFIXES'e /notifications ekli
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import "server-only";
|
||||
|
||||
import { Query } from "node-appwrite";
|
||||
|
||||
import { BUCKETS, DATABASE_ID, TABLES, type JobFile } from "./schema";
|
||||
import { createAdminClient } from "./server";
|
||||
import { getFileViewUrl } from "./storage";
|
||||
|
||||
export type JobFileWithUrl = JobFile & {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export async function listJobFiles(jobId: string): Promise<JobFileWithUrl[]> {
|
||||
const { tablesDB } = createAdminClient();
|
||||
const result = await tablesDB.listRows({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.jobFiles,
|
||||
queries: [
|
||||
Query.equal("jobId", jobId),
|
||||
Query.orderDesc("$createdAt"),
|
||||
Query.limit(200),
|
||||
],
|
||||
});
|
||||
const rows = result.rows as unknown as JobFile[];
|
||||
return rows.map((r) => ({
|
||||
...r,
|
||||
url: getFileViewUrl(BUCKETS.jobFiles, r.fileId),
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user