95f2d065b4
What changed
- jobs.teeth (FDI string[]). memberCount becomes a derived field (teeth.length).
A new TeethChart component renders the full permanent dentition as a
16-column grid for each arch with click-toggle selection.
- /jobs/new: removed the price + currency inputs and the manual memberCount
field. Clinics now pick teeth via the chart; the form blocks submission
until at least one tooth is selected.
- createJobAction calls a new calculateJobPrice() helper that walks the
pricing cascade and writes price + currency on the job server-side. A
clinic-supplied price hidden field would now be ignored — the field
isn't even in the schema.
Pricing cascade (calculateJobPrice, lib/appwrite/pricing.ts)
1. clinic_pricing row matching (lab, clinic, type) with customUnitPrice
→ use that flat unit price.
2. clinic_pricing row with discountPercent → catalog unitPrice × (1-d).
3. lab's prosthetics catalog row matching type (not archived).
4. nothing → price stays null; lab can still set it manually later.
Clinic-specific overrides (clinic_pricing table)
- Unique on (labTenantId, clinicTenantId, prostheticType) so each
combination has at most one rule.
- Row permissions: read by both teams (transparency for clinic), write
only by lab — clinic can see the discount they're getting but cannot
edit it.
- setClinicPricingAction validates an approved connection exists before
creating/updating, and rejects requests where neither customUnitPrice
nor discountPercent is set.
- clearClinicPricingAction wipes a rule (catalog price re-applies).
UI
- /connections 'Bağlantılarım' table gets a new column showing the active
pricing rules per counterpart. Lab side has a 'Fiyatlandırma' button
that opens a dialog (PROSTHETIC_TYPE × customPrice|discountPercent form
+ list of active rules with delete). Clinic side is read-only.
- Job detail: 'Fiyat' field now shows 'Lab tarafından belirlenecek' when
null, instead of a literal —. Adds a 'Dişler' info block listing the
selected FDI numbers.
212 lines
4.6 KiB
TypeScript
212 lines
4.6 KiB
TypeScript
export const DATABASE_ID = "lab";
|
|
|
|
export const BUCKETS = {
|
|
tenantLogos: "tenant-logos",
|
|
jobFiles: "job-files",
|
|
} as const;
|
|
|
|
export const TABLES = {
|
|
tenantSettings: "tenant_settings",
|
|
profiles: "profiles",
|
|
connections: "connections",
|
|
patients: "patients",
|
|
clinicPricing: "clinic_pricing",
|
|
jobs: "jobs",
|
|
jobFiles: "job_files",
|
|
jobStatusHistory: "job_status_history",
|
|
prosthetics: "prosthetics",
|
|
financeEntries: "finance_entries",
|
|
notifications: "notifications",
|
|
auditLogs: "audit_logs",
|
|
inviteLinks: "invite_links",
|
|
passwordResets: "password_resets",
|
|
userPreferences: "user_preferences",
|
|
} as const;
|
|
|
|
export type TableId = (typeof TABLES)[keyof typeof TABLES];
|
|
|
|
export type SystemRow = {
|
|
$id: string;
|
|
$createdAt: string;
|
|
$updatedAt: string;
|
|
$permissions: string[];
|
|
$databaseId?: string;
|
|
$tableId?: string;
|
|
$sequence?: number;
|
|
};
|
|
|
|
type Row = SystemRow;
|
|
|
|
export type TenantRole = "owner" | "admin" | "member";
|
|
export type TenantKind = "lab" | "clinic";
|
|
|
|
export interface TenantSettings extends Row {
|
|
tenantId: string;
|
|
kind: TenantKind;
|
|
memberNumber: string;
|
|
companyName: string;
|
|
companyTaxId?: string;
|
|
companyAddress?: string;
|
|
companyEmail?: string;
|
|
companyPhone?: string;
|
|
logo?: string;
|
|
defaultCurrency?: string;
|
|
}
|
|
|
|
export interface Profile extends Row {
|
|
tenantId: string;
|
|
userId: string;
|
|
displayName?: string;
|
|
phone?: string;
|
|
title?: string;
|
|
}
|
|
|
|
export type ConnectionStatus = "pending" | "approved" | "rejected";
|
|
|
|
export interface Connection extends Row {
|
|
clinicTenantId: string;
|
|
labTenantId: string;
|
|
status: ConnectionStatus;
|
|
requestedBy: string;
|
|
requestedAt: string;
|
|
approvedAt?: string;
|
|
rejectedAt?: string;
|
|
}
|
|
|
|
export type JobStatus = "pending" | "in_progress" | "sent" | "delivered" | "cancelled";
|
|
export type JobStep = "olcu" | "alt_yapi_prova" | "ust_yapi_prova" | "cila_bitim";
|
|
export type ProstheticType =
|
|
| "metal_porselen"
|
|
| "zirkonyum"
|
|
| "implant_ustu_zirkonyum"
|
|
| "gecici"
|
|
| "e_max"
|
|
| "diger";
|
|
|
|
export interface Patient extends Row {
|
|
clinicTenantId: string;
|
|
createdBy: string;
|
|
patientCode: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
phone?: string;
|
|
dateOfBirth?: string;
|
|
notes?: string;
|
|
archived?: boolean;
|
|
}
|
|
|
|
export interface Job extends Row {
|
|
clinicTenantId: string;
|
|
labTenantId: string;
|
|
createdBy: string;
|
|
patientCode: string;
|
|
patientId?: string;
|
|
prostheticType: ProstheticType;
|
|
memberCount: number;
|
|
teeth?: string[];
|
|
color?: string;
|
|
description?: string;
|
|
price?: number;
|
|
currency?: string;
|
|
status: JobStatus;
|
|
currentStep?: JobStep;
|
|
dueDate?: string;
|
|
}
|
|
|
|
export interface ClinicPricing extends Row {
|
|
labTenantId: string;
|
|
clinicTenantId: string;
|
|
prostheticType: ProstheticType;
|
|
customUnitPrice?: number;
|
|
discountPercent?: number;
|
|
currency?: string;
|
|
createdBy: string;
|
|
}
|
|
|
|
export type JobFileKind = "scan" | "image" | "document";
|
|
|
|
export interface JobFile extends Row {
|
|
jobId: string;
|
|
clinicTenantId: string;
|
|
labTenantId: string;
|
|
uploadedBy: string;
|
|
kind: JobFileKind;
|
|
fileId: string;
|
|
name: string;
|
|
size: number;
|
|
mimeType?: string;
|
|
}
|
|
|
|
export interface JobStatusHistory extends Row {
|
|
jobId: string;
|
|
clinicTenantId: string;
|
|
labTenantId: string;
|
|
step: JobStep;
|
|
completedBy: string;
|
|
completedAt: string;
|
|
note?: string;
|
|
}
|
|
|
|
export interface Prosthetic extends Row {
|
|
tenantId: string;
|
|
createdBy: string;
|
|
name: string;
|
|
type: ProstheticType;
|
|
unitPrice: number;
|
|
currency?: string;
|
|
archived?: boolean;
|
|
}
|
|
|
|
export type FinanceType = "income" | "expense" | "receivable" | "payable";
|
|
export type FinanceStatus = "pending" | "paid" | "cancelled";
|
|
|
|
export interface FinanceEntry extends Row {
|
|
tenantId: string;
|
|
createdBy: string;
|
|
jobId?: string;
|
|
counterpartTenantId?: string;
|
|
type: FinanceType;
|
|
amount: number;
|
|
currency?: string;
|
|
status: FinanceStatus;
|
|
date: string;
|
|
description?: string;
|
|
}
|
|
|
|
export interface Notification extends Row {
|
|
tenantId: string;
|
|
userId?: string;
|
|
jobId?: string;
|
|
connectionId?: string;
|
|
message: string;
|
|
read: boolean;
|
|
}
|
|
|
|
export type AuditAction = "create" | "update" | "delete";
|
|
|
|
export interface AuditLog extends Row {
|
|
tenantId: string;
|
|
userId: string;
|
|
action: AuditAction;
|
|
entityType: string;
|
|
entityId: string;
|
|
changes?: string;
|
|
ipAddress?: string;
|
|
userAgent?: string;
|
|
}
|
|
|
|
export type InviteRole = "admin" | "member";
|
|
export type InviteStatus = "pending" | "accepted" | "cancelled" | "expired";
|
|
|
|
export interface InviteLink extends Row {
|
|
tenantId: string;
|
|
code: string;
|
|
email: string;
|
|
role?: InviteRole;
|
|
status?: InviteStatus;
|
|
invitedBy: string;
|
|
expiresAt?: string;
|
|
acceptedAt?: string;
|
|
acceptedBy?: string;
|
|
}
|