From f0bb1ced656e04e112ae461f276f12a413b1772c Mon Sep 17 00:00:00 2001 From: "sas.fajri" Date: Mon, 13 Apr 2026 16:07:01 +0700 Subject: [PATCH] Source order details from FPP catalog --- server.js | 187 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 143 insertions(+), 44 deletions(-) diff --git a/server.js b/server.js index 51f69d2..9b511cc 100644 --- a/server.js +++ b/server.js @@ -11,6 +11,7 @@ const sessionKey = "doclink_session"; const sampleLogin = { username: "yogayogi", doctorId: "31010002", + mouId: "2773", password: "123456", }; @@ -461,7 +462,7 @@ function renderOrderDetail(order) { `; } -function renderOrderForm(step, stepKey = "demografi") { +function renderOrderForm(step, stepKey = "demografi", fppTests = [], mouId = "") { const steps = [ ["demografi", "Demografi", "Patient identity and contact details."], ["diagnosa", "Diagnosa", "Clinical indication and diagnosis."], @@ -472,46 +473,104 @@ function renderOrderForm(step, stepKey = "demografi") { const activeIndex = Math.max(0, steps.findIndex((item) => item[0] === stepKey)); const stepBody = { demografi: ` -
- - - - - +
+
+ ${panelHeader("Mandatory", "These fields are required or expected by the backend before save.")} +
+ + +
+ Backend Mou ID +

Auto-filled from the logged-in session.

+
+
+
+
+ ${panelHeader("Optional", "These fields can be filled when available.")} +
+ + + + +
+
`, diagnosa: ` -
- - - +
+
+ ${panelHeader("Mandatory", "Clinical indication is expected before save.")} +
+ +
+
+
+ ${panelHeader("Optional", "Notes are useful for the lab or front office.")} +
+ +
+
`, pemeriksaan: ` -
- ${["Hematology", "Clinical Chemistry", "Urinalysis", "Immunology", "Microbiology"].map((item) => `${escapeHtml(item)}`).join("")} -
-
-
- ${["CBC", "Glucose", "Lipid Profile", "CRP"].map((item) => ` - - `).join("")} +
+
+ ${panelHeader("Mandatory", "The backend accepts a details array, so keep at least one item here when saving.")} + ${ + fppTests.length + ? `
${fppTests.slice(0, 4).map((test, index) => ` + + `).join("")}
` + : `
FPP catalog not available yet.
` + } +
+
+ ${panelHeader("Optional", "Add more test rows when needed.")} +
+ ${ + fppTests.length > 4 + ? fppTests.slice(4, 8).map((test, index) => ` + + `).join("") + : `
No additional FPP rows available.
` + } +
+
`, qrcode: ` -
-
- Scan existing patient QR -

Use a QR code to pull patient and visit context instantly.

-
QR preview area
+
+
+ ${panelHeader("Mandatory", "QR flow is optional in the backend, but keep the scan control at the top if used.")} +
+
+ Scan existing patient QR +

Use a QR code to pull patient and visit context instantly.

+
QR preview area
+
+
-
- Manual fallback -

Still allow manual entry if the scan is unavailable.

- +
+ ${panelHeader("Optional", "Manual fallback when scanning is not available.")} +
+
+ Manual fallback +

Still allow manual entry if the scan is unavailable.

+ +
+
`, @@ -519,10 +578,10 @@ function renderOrderForm(step, stepKey = "demografi") {
Summary

All steps are merged into a final review before submit.

-
Patient

Siti Amelia

-
Diagnosis

Check-up rutin

-
Tests

CBC, Glucose, Urine

-
Message

Prioritize fasting sample

+
Patient name

patient_name

+
Diagnosis

patient_diagnosa

+
Note

patient_note

+
Details

details[]

`, @@ -947,9 +1006,10 @@ function problemLoginPage() { ); } -function orderNewPage(path) { +async function orderNewPage(session, path) { const step = path.split("/").filter(Boolean)[2] || "demografi"; - return layout("Create Order", `
${renderOrderForm({}, step)}
`, { + const fppTests = await loadFppCatalog(session); + return layout("Create Order", `
${renderOrderForm({}, step, fppTests, session?.mouId || "")}
`, { authenticated: true, activePath: "/orders", subtitle: "The new project collapses the old step-heavy flow into a cleaner shell while keeping the same workflow.", @@ -1130,12 +1190,14 @@ function normalizeSession(payload) { const username = user?.M_UserUsername || data?.username || data?.M_UserUsername || data?.name || ""; const doctorId = user?.M_UserM_DoctorID || data?.doctor_id || data?.doctorId || ""; const doctorCode = user?.M_UserM_DoctorCode || data?.doctor_code || ""; + const mouId = user?.M_UserM_MouID || data?.M_UserM_MouID || data?.mou_id || ""; const userId = user?.M_UserID || data?.M_UserID || data?.user_id || ""; return { token, username, doctorId, doctorCode, + mouId, userId, raw: payload, }; @@ -1296,6 +1358,29 @@ async function loadFpp(session, group = "All") { } } +async function loadFppCatalog(session) { + try { + const payload = await apiPost("/Fpp/loadFPP/1/1", { token: session.token }, session.token); + const rows = extractArray(payload) || payload?.rows || []; + const headings = Array.isArray(rows) ? rows : []; + const tests = headings.flatMap((heading) => + (heading?.details || []).flatMap((detail) => + (detail?.tests || []).map((test) => ({ + heading: heading?.heading || "FPP", + subcategory: detail?.subcategories || "", + testId: String(test?.testid || ""), + code: String(test?.code || ""), + name: String(test?.name || ""), + price: String(test?.price || "0"), + })), + ), + ); + return tests.filter((item) => item.name); + } catch { + return []; + } +} + async function loadOrderDetail(session, orderId) { const [orders, saran, hasil] = await Promise.all([ loadOrders(session, "", "All"), @@ -1348,7 +1433,19 @@ async function readBody(req) { const raw = Buffer.concat(chunks).toString("utf8"); const contentType = req.headers["content-type"] || ""; if (contentType.includes("application/json")) return raw ? JSON.parse(raw) : {}; - return Object.fromEntries(new URLSearchParams(raw)); + const flat = Object.fromEntries(new URLSearchParams(raw)); + const details = []; + for (const [key, value] of Object.entries(flat)) { + const match = key.match(/^details\[(\d+)\]\[(\w+)\]$/); + if (!match) continue; + const index = Number(match[1]); + const field = match[2]; + details[index] ||= {}; + details[index][field] = value; + delete flat[key]; + } + if (details.length) flat.details = details.filter(Boolean); + return flat; } function fragmentOrdersTable(search = "", status = "All", ordersData = null) { @@ -1539,8 +1636,9 @@ function fragmentFpp(group = "All", itemsData = null) { `; } -function fragmentOrderStep(step) { - return renderOrderForm({}, step); +async function fragmentOrderStep(session, step) { + const fppTests = await loadFppCatalog(session); + return renderOrderForm({}, step, fppTests, session?.mouId || ""); } function fragmentPesanKhusus(orderId) { @@ -1627,6 +1725,7 @@ async function renderRoute(req, res, url) { username: normalized.username || body.username || "", doctorId: normalized.doctorId || body.doctor_id || "", doctorCode: normalized.doctorCode || body.doctor_id || "", + mouId: normalized.mouId || body.M_MouID || "", userId: normalized.userId || "", raw: payload, }); @@ -1693,13 +1792,13 @@ async function renderRoute(req, res, url) { if (path === "/orders/new" && isGet) { if (!requireAuth(req, res)) return; - html(res, 200, orderNewPage(path)); + html(res, 200, await orderNewPage(session, path)); return; } if (path.startsWith("/orders/new/") && isGet) { if (!requireAuth(req, res)) return; - html(res, 200, orderNewPage(path)); + html(res, 200, await orderNewPage(session, path)); return; } @@ -1822,7 +1921,7 @@ async function renderRoute(req, res, url) { if (path.startsWith("/fragments/forms/order-step/") && isGet) { if (!requireAuth(req, res)) return; const step = path.split("/")[4] || "demografi"; - html(res, 200, fragmentOrderStep(step)); + html(res, 200, await fragmentOrderStep(session, step)); return; } @@ -1853,7 +1952,7 @@ async function renderRoute(req, res, url) { "/order/order_patient", { token: sessionData.token, - M_MouID: body.M_MouID || sessionData.doctorId || "", + M_MouID: body.M_MouID || sessionData.mouId || sampleLogin.mouId || "", patient_name: body.patient_name || "", patient_diagnosa: body.patient_diagnosa || "", patient_address: body.patient_address || "",