From 683c4cd20a227f3a4c3f0392dd2a27d873b1b4f3 Mon Sep 17 00:00:00 2001 From: Duradundi Date: Fri, 11 Sep 2026 14:08:18 +0300 Subject: [PATCH] Updated WF --- asm_app/index.html | 4 +- asm_app/src/components/Sidebar.tsx | 4 +- asm_app/src/config/defaults.ts | 2 +- asm_app/src/hooks/useDocTypeMeta.ts | 12 +- asm_app/src/pages/AssetDetail.tsx | 64 ++++---- asm_app/src/pages/AssetList.tsx | 3 + asm_app/src/pages/WorkOrderDetail.tsx | 158 +++++++++++++++++--- asm_app/src/services/assetService.ts | 4 + asm_app/src/services/workOrderService.ts | 3 + asm_app/src/utils/workOrderWorkflowUtils.ts | 96 ++++++++++++ 10 files changed, 288 insertions(+), 62 deletions(-) create mode 100644 asm_app/src/utils/workOrderWorkflowUtils.ts diff --git a/asm_app/index.html b/asm_app/index.html index bc215d2..8d798f1 100644 --- a/asm_app/index.html +++ b/asm_app/index.html @@ -2,8 +2,8 @@ - - + + Seera Arabia - Asset Management System diff --git a/asm_app/src/components/Sidebar.tsx b/asm_app/src/components/Sidebar.tsx index 9dee0c6..0cdc28d 100644 --- a/asm_app/src/components/Sidebar.tsx +++ b/asm_app/src/components/Sidebar.tsx @@ -70,10 +70,10 @@ const Sidebar: React.FC = ({ userEmail }) => { // Version is automatically updated by build script based on file modification time const imageVersion = import.meta.env.DEV ? `?v=${Date.now()}` - : `?v=1785757497`; // Auto-updated by build script + : `?v=1789058360`; // Auto-updated by build script const logoVersion = import.meta.env.DEV ? `?v=${Date.now()}` - : `?v=1785757497`; // Auto-updated by build script + : `?v=1789058360`; // Auto-updated by build script const backgroundImageUrl = baseUrl.endsWith('/') ? `${baseUrl}sidebar-background.jpg${imageVersion}` : `${baseUrl}/sidebar-background.jpg${imageVersion}`; diff --git a/asm_app/src/config/defaults.ts b/asm_app/src/config/defaults.ts index 55e84fb..f17cff0 100644 --- a/asm_app/src/config/defaults.ts +++ b/asm_app/src/config/defaults.ts @@ -7,7 +7,7 @@ export const DEFAULT_HOSPITAL = 'Assir Central Hospital'; export const DEFAULT_WAREHOUSE = 'Stores - AGH'; // Fallback Frappe Backend Base URL -export const DEFAULT_FRAPPE_BASE_URL = 'https://asir-ch.seeraarabia.com'; +export const DEFAULT_FRAPPE_BASE_URL = 'http://34.166.33.197'; // Default logos export const DEFAULT_HOSPITAL_LOGO = '/files/hospital.png'; diff --git a/asm_app/src/hooks/useDocTypeMeta.ts b/asm_app/src/hooks/useDocTypeMeta.ts index faa04ce..204b1f1 100644 --- a/asm_app/src/hooks/useDocTypeMeta.ts +++ b/asm_app/src/hooks/useDocTypeMeta.ts @@ -25,14 +25,18 @@ export const useDocTypeMeta = (doctype: string) => { try { setLoading(true); + // getdoctype returns the merged meta, so Custom Fields (custom_*) and + // Property Setters are included. /api/resource/DocType only returns the + // standard fields, which makes every custom field look like + // allow_on_submit = 0. const response = await apiService.apiCall( - `/api/resource/DocType/${doctype}` + `/api/method/frappe.desk.form.load.getdoctype?doctype=${encodeURIComponent(doctype)}` ); // Handle different response structures from Frappe API - // Response can be: { data: {...} } or directly {...} - const docTypeData = response.data || response; - const fieldsList: DocTypeField[] = docTypeData.fields || []; + // Response can be: { docs: [{...}] }, { data: {...} } or directly {...} + const docTypeData = response?.docs?.[0] || response?.data || response; + const fieldsList: DocTypeField[] = docTypeData?.fields || []; // Extract fields that allow editing on submit const allowOnSubmitSet = new Set(); diff --git a/asm_app/src/pages/AssetDetail.tsx b/asm_app/src/pages/AssetDetail.tsx index c8c7849..767b066 100644 --- a/asm_app/src/pages/AssetDetail.tsx +++ b/asm_app/src/pages/AssetDetail.tsx @@ -44,6 +44,8 @@ const AssetDetail: React.FC = () => { asset_category: '', custom_moh_id: '', custom_floor: '', + custom_building: '', + custom_room_number: '', custom_moh_engg: '', custom_smeh_engg: '', custom_manufacturing_year: '', @@ -501,6 +503,8 @@ const AssetDetail: React.FC = () => { asset_category: asset.asset_category || '', custom_moh_id: (asset as any).custom_moh_id || '', custom_floor: (asset as any).custom_floor || '', + custom_building: (asset as any).custom_building || '', + custom_room_number: (asset as any).custom_room_number || '', custom_moh_engg: (asset as any).custom_moh_engg || '', custom_smeh_engg: (asset as any).custom_smeh_engg || '', custom_manufacturing_year: asset.custom_manufacturing_year || '', @@ -2908,32 +2912,23 @@ const handlePPMPlan = async () => { /> -
- - -
+ setFormData({ ...formData, custom_floor: val })} + disabled={isFieldDisabled('custom_floor')} + placeholder="Select Floor" + /> -
- - -
+ setFormData({ ...formData, custom_building: val })} + disabled={isFieldDisabled('custom_building')} + placeholder="Select Building" + /> {/*
*/} -
- - -
+ setFormData({ ...formData, custom_room_number: val })} + disabled={isFieldDisabled('custom_room_number')} + placeholder="Select Room" + /> {/*
)} + +
+ setFormData({ ...formData, custom_building: val })} + disabled={!isEditing} + placeholder="Select Building" + /> +
+ +
+ setFormData({ ...formData, custom_room_no: val })} + disabled={!isEditing} + placeholder="Select Room" + /> +
+ +
+ setFormData({ ...formData, custom_location: val })} + disabled={!isEditing} + placeholder="Select Location" + /> +
@@ -1495,7 +1576,19 @@ const canEditBasedOnWorkflow =
+ +
+ +
+ +

+ Updated by workflow (e.g. Open → Work In Progress → Completed → Closed) +

@@ -1538,6 +1634,11 @@ const canEditBasedOnWorkflow = Need Procurement + {currentWorkflowState === 'Repair InProgress' && ( +

+ Check and save to enable Material Request → Pending Purchase +

+ )}
@@ -1698,6 +1799,11 @@ const canEditBasedOnWorkflow = rows={4} className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-700 bg-white dark:bg-gray-700 text-gray-900 dark:text-white resize-none" /> + {currentWorkflowState === 'Repair InProgress' && ( +

+ Required for Work Done (with Assigned Technician set) +

+ )} @@ -1747,6 +1853,11 @@ const canEditBasedOnWorkflow = filters={assignedTechnicianFilters} placeholder={formData.company ? 'Select Technician' : 'Select Hospital first'} /> + {['Sent to Team Leader', 'Repair InProgress', 'Completed'].includes(currentWorkflowState) && ( +

+ Required for Send For Repair, Work Done, and Approve & Close workflow actions +

+ )}
@@ -2289,12 +2400,25 @@ const canEditBasedOnWorkflow = {/* No Actions Available */} {!workflowLoading && transitions.length === 0 && ( -
+

- No workflow actions available + No workflow actions available for your role or current document state

-

- (Conditions may not be met for available transitions) + {workflowHints.length > 0 && ( +

    + {workflowHints.map((hint) => ( +
  • {hint}
  • + ))} +
+ )} +
+ )} + + {/* Hints while actions are visible (save reminder when editing) */} + {!workflowLoading && transitions.length > 0 && isEditing && ( +
+

+ Save after editing so workflow conditions match the server before applying an action.

)} @@ -2303,7 +2427,7 @@ const canEditBasedOnWorkflow = {/* WO Feedback — read-only summary when submitted (submit via public link only) */} {!isNewWorkOrder && - ['Approved', 'Closed'].includes(currentWorkflowState) && + ['Completed', 'Closed'].includes(currentWorkflowState) && workOrderName && } {/* Status Summary Card */} diff --git a/asm_app/src/services/assetService.ts b/asm_app/src/services/assetService.ts index cf33e70..0a66f03 100644 --- a/asm_app/src/services/assetService.ts +++ b/asm_app/src/services/assetService.ts @@ -18,6 +18,8 @@ export interface Asset { asset_category?: string; custom_moh_id?: string; custom_floor?: string; + custom_building?: string; + custom_room_number?: string; custom_moh_engg?: string; custom_smeh_engg?: string; custom_manufacturing_year?: string; @@ -127,6 +129,8 @@ export interface CreateAssetData { asset_category?: string; custom_moh_id?: string; custom_floor?: string; + custom_building?: string; + custom_room_number?: string; custom_moh_engg?: string; custom_smeh_engg?: string; custom_manufacturing_year?: string; diff --git a/asm_app/src/services/workOrderService.ts b/asm_app/src/services/workOrderService.ts index 9200cdf..1a2bab9 100644 --- a/asm_app/src/services/workOrderService.ts +++ b/asm_app/src/services/workOrderService.ts @@ -68,6 +68,9 @@ export interface WorkOrder { stock_items?: StockItem[]; site_name?: string; custom_assign_to_contractor?: string; + custom_building?: string; + custom_room_no?: string; + custom_location?: string; } export interface StockItem { diff --git a/asm_app/src/utils/workOrderWorkflowUtils.ts b/asm_app/src/utils/workOrderWorkflowUtils.ts new file mode 100644 index 0000000..1175ebb --- /dev/null +++ b/asm_app/src/utils/workOrderWorkflowUtils.ts @@ -0,0 +1,96 @@ +/** + * Helpers for the Work Order workflow (backend: Workflow "Work Order"). + * Transitions and allow_edit rules are loaded from Frappe; these hints mirror + * the conditions configured on the workflow. + */ + +export interface WorkOrderWorkflowDoc { + workflow_state?: string; + asset_type?: string; + site_name?: string; + custom_assign_to_contractor?: string; + actions_performed?: string; + need_procurement?: number; +} + +/** Actions that should prompt for confirmation before applying. */ +export const WORK_ORDER_CONFIRM_ACTIONS = [ + 'Approve & Close', + 'Re-Open', + 'Reject', + 'Cancel', + 'Close', +] as const; + +/** repair_status values set by workflow state update_field (backend). */ +export const WORK_ORDER_REPAIR_STATUS_BY_STATE: Record = { + Draft: 'Open', + 'Sent to Team Leader': 'Open', + 'Sent To Site Manager': 'Open', + 'Repair InProgress': 'Work In Progress', + 'Pending Approval': 'Pending Review', + Completed: 'Completed', + Cancelled: 'Cancelled', + Closed: 'Closed', +}; + +/** + * Human-readable hints when no workflow actions are shown (conditions / save). + */ +export function getWorkOrderWorkflowHints( + state: string, + doc: WorkOrderWorkflowDoc, + options?: { isEditing?: boolean } +): string[] { + const hints: string[] = []; + const isEditing = options?.isEditing ?? false; + + if (isEditing) { + hints.push('Save the work order after changing fields so workflow actions refresh.'); + } + + switch (state) { + case 'Draft': + if (doc.asset_type === 'Biomedical' && !doc.site_name) { + hints.push('PHCC End User: set Site Name (Biomedical asset) to enable Apply.'); + } + break; + + case 'Sent to Team Leader': + if (!doc.custom_assign_to_contractor) { + hints.push( + 'Maintenance Manager: set Assigned Technician, save, then use Send For Repair.' + ); + } + break; + + case 'Repair InProgress': + if (!doc.custom_assign_to_contractor) { + hints.push('Set Assigned Technician and save to enable Work Done.'); + } else if (!doc.actions_performed?.trim()) { + hints.push('Fill Actions Performed and save to enable Work Done.'); + } + if (doc.need_procurement !== 1) { + hints.push('Check Need Procurement and save to enable Material Request.'); + } + break; + + case 'Pending Purchase': + hints.push('Finance Manager: use Send For Repair when procurement is complete.'); + break; + + case 'Completed': + if (!doc.custom_assign_to_contractor) { + hints.push( + 'Set Assigned Technician and save to enable Approve & Close (End user / Manager roles).' + ); + } + hints.push('Maintenance Manager can Re-Open without a contractor assigned.'); + break; + + default: + break; + } + + return hints; +}