Back to Journal

Hardening the sign-and-close pipeline

Listen to this articleAI narration
0:00 / 0:00

A doctor closes a case. Behind the scenes, seven sequential API calls fire against Carasent's EHR system — patient lookup, booking, visit registration, invoice, record update, signing, and invoice payment. Then the case closes in our database, a message is posted, and an SMS goes out. All of this happens in a single request handler with one try-catch around the entire thing.

When step 4 fails, everything after it never runs. Including the journal entry itself.

The Production Failure

A real case came through the logs. Patient lookup succeeded. Booking created. Visit registered. Then invoice creation returned a 500 from Carasent: {"error":{"code":500,"message":"Ett fel inträffade"}}. The outer catch swallowed it, returned a generic error to the doctor, and left an empty visit record sitting in Carasent — no keywords, no diagnosis codes, no assessment. Just a blank journal entry attached to a real patient.

What Happened on 2026-02-191. Patient2. Booking3. Visit4. Invoice500 ERROR5. Keywordsskipped6. Signskipped7. PayskippedConsequencesBooking + visit exist in Carasent → orphaned, empty recordCase still open in Doktera → doctor retries → duplicate bookingDoctor sees: "Misslyckades att signera ärendet." — no indication Carasent was partially updated

The visit response from Carasent had "price": null in its payment array. The patient was registered under "Region Sörmland" — a county healthcare contract. Invoice creation likely failed because Carasent couldn't generate a billing line for a contract patient with no price. A valid scenario that the code didn't account for.

The Carasent Journal Entry

After the failure, someone retried or manually intervened. The journal entry in Webdoc showed up signed — but with empty fields. Kontaktorsak was blank despite being a required field in the UI with a fallback value of "Online konsultation". Bedömning had content. Anamnes had the Q&A form data. Status was empty, which is expected since it's optional.

The empty Kontaktorsak makes sense once you understand the sequence. The visit record was created at step 3, but the keywords (kontaktorsak, anamnes, status, bedömning) are written at step 5. Step 5 never ran. The record existed in Carasent as a shell — created during visit registration but never populated.

The Fix

The invoice step was treated as critical when it shouldn't be. Creating an invoice is a billing operation. The clinical documentation — keywords, diagnosis codes, signing — is what actually matters. If the invoice fails, the journal entry should still be written and signed.

Step 7 (marking invoice as paid) already had a try-catch for exactly this reason — freecard patients can cause payment marking to fail. Step 4b (creating the invoice) needed the same treatment:

try {
  const invoice = await createCarasentInvoice(bearer, clinicId, visitId, "100");
  invoiceId = invoice.invoiceId;
} catch (invoiceCreateError) {
  console.warn(
    "[Sign & Close] ⚠ Failed to create invoice (continuing with signing):",
    invoiceCreateError instanceof Error
      ? invoiceCreateError.message
      : invoiceCreateError
  );
}

If invoice creation fails, invoiceId stays null, step 7 (mark as paid) is skipped, and the flow continues to step 5 (update record with keywords and diagnosis codes), step 6 (sign), and case closure. The clinical documentation is complete. The billing discrepancy can be handled administratively.

After Fix: Invoice Failure Is Non-Fatal1-3. PatientBooking, Visit ✓4. Invoice⚠ warn + continue5. Keywords✓ runs anyway6. Sign✓ runs anyway7. Mark Paidskipped (no ID)8. Close CaseOutcomeJournal entry complete. Record signed. Case closed. No invoice — handled manually.Doctor sees success. Patient gets proper documentation.

Why the Invoice Failed

The visit registration response included "price": null in its payment array. The patient was under a county healthcare contract ("Region Sörmland") but living in a different county (Gävleborg, code 2161). Carasent returned a 500 when asked to generate an invoice for a visit with no price. This is a valid edge case for contract patients where billing is handled differently.

Remaining Gaps

This fix handles the immediate crash. There are deeper issues in this pipeline that still need attention.

If step 5 (record update) or step 6 (signing) fails after the booking and visit are created, the same orphaned record problem occurs — just one step later. The real fix is idempotency: storing the Carasent record ID on the case after signing succeeds, so retries can skip the Carasent steps entirely and jump straight to closing.

There's also no server-side validation of keyword content. The UI validates that kontaktorsak and bedömning are non-empty, but the API route passes whatever the client sends directly to Carasent. If a retry happens in a different UI state, empty data reaches the EHR.

Both are on the list. Today's change stops the bleeding — doctors can close cases even when Carasent's invoice endpoint returns errors for contract patients.