Use a QR code to pull patient and visit context instantly.
+
+ ${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") {
SummaryAll steps are merged into a final review before submit.
-
-
-
-
MessagePrioritize fasting sample
+
+
Diagnosispatient_diagnosa
+
+
`,
@@ -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 || "",