| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| openemr_manual [2026/05/12 23:56] – v2.1.3 release — security fixes from Aisle review brad.sharp | openemr_manual [2026/05/22 22:34] (current) – Fix Method 2: OE Manage Modules has no zip-upload UI for custom modules — extract zip and drop files into custom_modules/ via SFTP instead brad.sharp |
|---|
| ====== OpenEMR ClaimRev Connect Module ====== | ====== OpenEMR ClaimRev Connect Module ====== |
| |
| The **ClaimRev Connect** module integrates ClaimRev's clearinghouse services directly into OpenEMR, giving practices access to claims processing, eligibility verification, ERA downloads, payment posting, reconciliation, patient balance management, and analytics — all without leaving their EHR. | The **ClaimRev Connect** module integrates ClaimRev's clearinghouse services directly into OpenEMR, giving practices access to claims processing, eligibility verification, ERA downloads, payment posting, reconciliation, patient balance management, and analytics — all without leaving their EHR. |
| |
| ===== Table of Contents ===== | ===== Table of Contents ===== |
| * An active ClaimRev account with API credentials | * An active ClaimRev account with API credentials |
| |
| ==== Download ==== | ==== Choose Your Install Method ==== |
| |
| Two builds are available — pick the one that matches your OpenEMR major version. The 7.x build adds a small compatibility shim layer for classes introduced in 8.x; on 8.x installs that layer is not needed and not included. | The ClaimRev Connect module is published on Packagist as ''claimrevolution/oe-module-claimrev-connect''. Starting with v2.1.3 a **single build** runs on both OpenEMR 7.x and OpenEMR 8.x — runtime compatibility shims in ''src/Compat/'' activate only when the host core lacks the newer APIs. Pick the install method that matches your environment. |
| |
| **Current Version: 2.1.3** | **Current Version: 2.1.3** |
| |
| ~~NOTOC~~ | === Method 1: Composer (Recommended) === |
| |
| === For OpenEMR 8.x === | If you have CLI access to your OpenEMR install: |
| |
| | **Module File** | {{ :oe-module-claimrev-connect-v2.1.3-openemr-8.zip | OpenEMR 8.x Module}} | | <code bash> |
| | **Compatible with** | OpenEMR 8.0 and later | | cd /path/to/openemr |
| | **Released** | May 12, 2026 | | composer require claimrevolution/oe-module-claimrev-connect:^2.1 |
| | **Notes** | Targets OpenEMR 8.x natively (no compatibility shims) | | sudo systemctl reload php-fpm # or restart Apache |
| | </code> |
| |
| === For OpenEMR 7.x === | Composer downloads the latest v2.1.x release, drops it into ''interface/modules/custom_modules/oe-module-claimrev-connect/'', and updates the autoloader. Continue with [[#Activation|Activation]] below. |
| |
| | **Module File** | {{ :oe-module-claimrev-connect-v2.1.3-openemr-7.zip | OpenEMR 7.x Module}} | | To **update** the module later: |
| | **Compatible with** | OpenEMR 7.0.0 through 7.x | | |
| | **Released** | May 12, 2026 | | |
| | **Notes** | Includes the ''src/Compat/'' shim layer for ''OEGlobalsBag'', ''ServiceContainer'', and ''CryptoInterface''; ''CsrfHelper'' detects which ''CsrfUtils'' signature is in use at runtime | | |
| |
| **Changelog**: See [[#Changelog|Changelog]] below. | <code bash> |
| | composer update claimrevolution/oe-module-claimrev-connect |
| | sudo systemctl reload php-fpm |
| | </code> |
| |
| **Note:** If you do not yet have a ClaimRev account, visit [[https://www.claimrev.com|claimrev.com]] or contact us at [[mailto:sales@claimrev.com|sales@claimrev.com]] to get started. | === Method 2: Manual File Drop (Shared Hosting / No CLI) === |
| |
| ==== Installation Steps ==== | OpenEMR's //Manage Modules// page does not have a zip-upload feature for custom modules — it scans ''interface/modules/custom_modules/'' on every page load and lists whatever directories it finds. For installs without CLI access (shared hosting, restricted environments), drop the files into place via SFTP/FTP: |
| |
| - Download the appropriate zip for your OpenEMR version from the section above. | - Download the current release zip from [[https://github.com/claimrevolution/oe-module-claimrev-connect/releases|the GitHub releases page]]. |
| - Log into OpenEMR as an administrator. | - Unzip it locally. You should see a folder named ''oe-module-claimrev-connect/'' containing ''composer.json'', ''info.txt'', ''src/'', ''public/'', ''templates/'', etc. |
| - Navigate to **Modules > Manage Modules**. | - Connect to your OpenEMR server with SFTP, FTP, or your hosting control panel's file manager. |
| - Click the **Install** tab, then click **Upload Module** and select the downloaded zip file. | - Upload the entire ''oe-module-claimrev-connect/'' folder into ''interface/modules/custom_modules/'' so the resulting path is ''interface/modules/custom_modules/oe-module-claimrev-connect/composer.json'' (and so on for the other files). |
| - After upload completes, find **ClaimRev Connect** in the module list and click **Install**. | - Continue with [[#Activation|Activation]] below — OpenEMR will see the new directory on the next page load. |
| - Once installed, click **Enable** to activate the module. | |
| | **Note:** Updates with this method are the same flow — replace the contents of the ''oe-module-claimrev-connect/'' folder with the contents of the new zip. Method 1 (Composer) is much easier if you have any way to run shell commands on the server. |
| | |
| | === Method 3: Release Tarball === |
| | |
| | If you installed OpenEMR via an official release tarball that already bundles ''claimrevolution/oe-module-claimrev-connect'' in its ''composer.json'', the module arrives pre-extracted at ''interface/modules/custom_modules/oe-module-claimrev-connect/''. No additional download step. |
| | |
| | ==== Activation (all methods) ==== |
| | |
| | - In OpenEMR, navigate to **Modules > Manage Modules**. |
| | - Find **ClaimRev Connect** in the module list. Click **Install**, then **Enable**. |
| - Navigate to **ClaimRev Connect > Setup** and click **Run Upgrade** to create the required database tables. | - Navigate to **ClaimRev Connect > Setup** and click **Run Upgrade** to create the required database tables. |
| | |
| | **Note:** If you do not yet have a ClaimRev account, visit [[https://www.claimrev.com|claimrev.com]] or contact us at [[mailto:sales@claimrev.com|sales@claimrev.com]] to get started. |
| |
| --- | --- |
| |
| === Claim Pipeline === | === Claim Pipeline === |
| * **Claims In Flight** — Encounters billed and awaiting payer response | * **Claims In Flight** — Encounters billed and awaiting payer response |
| * **Pending ERAs** — ERAs received from ClaimRev but not yet posted to OpenEMR | * **Pending ERAs** — ERAs received from ClaimRev but not yet posted to OpenEMR |
| * **Rejected / Denied** — Claims denied in the last 90 days | * **Rejected / Denied** — Claims denied in the last 90 days |
| * **Clean Claim Rate** — Percentage of claims accepted on first pass (90-day window). Color-coded: green (≥95%), yellow (90-94%), red (<90%) | * **Clean Claim Rate** — Percentage of claims accepted on first pass (90-day window). Color-coded: green (≥95%), yellow (90-94%), red (<90%) |
| |
| === Accounts Receivable === | === Accounts Receivable === |
| * **Total AR** — Sum of all outstanding balances, with the amount over 90 days shown below | * **Total AR** — Sum of all outstanding balances, with the amount over 90 days shown below |
| * **Avg Days in AR** — Average age of outstanding balances. Color-coded: green (≤35 days), yellow (36-50), red (>50) | * **Avg Days in AR** — Average age of outstanding balances. Color-coded: green (≤35 days), yellow (36-50), red (>50) |
| * **Collections This Month** — Total payments received this month, with last month shown for comparison | * **Collections This Month** — Total payments received this month, with last month shown for comparison |
| * **Collections This Quarter** — Quarterly total | * **Collections This Quarter** — Quarterly total |
| |
| === Denials === | === Denials === |
| * **Denial Rate** — Percentage of claims denied vs. total processed (90-day window) | * **Denial Rate** — Percentage of claims denied vs. total processed (90-day window) |
| * **Top Adjustment Reasons** — The five most common adjustment reasons from posted ERAs | * **Top Adjustment Reasons** — The five most common adjustment reasons from posted ERAs |
| |
| === Patient Responsibility === | === Patient Responsibility === |
| * **Patient AR** — Total outstanding patient balances (encounters where insurance has responded) | * **Patient AR** — Total outstanding patient balances (encounters where insurance has responded) |
| * **Need Statements** — Encounters with a patient balance that have never had a statement sent | * **Need Statements** — Encounters with a patient balance that have never had a statement sent |
| |
| === Quick Actions === | === Quick Actions === |
| * Date range (service date or received date) | * Date range (service date or received date) |
| * Payer name or payer number | * Payer name or payer number |
| * Patient control number (PCN) — format is ''pid-encounter'' | * Patient control number (PCN) — format is ''pid-encounter'' |
| * Claim status (e.g., Accepted, Rejected, Pending) | * Claim status (e.g., Accepted, Rejected, Pending) |
| * Billing provider NPI | * Billing provider NPI |
| Each claim row provides action buttons: | Each claim row provides action buttons: |
| |
| * **Open Encounter** (folder icon, blue) — Opens the OpenEMR encounter directly in a new tab, allowing you to review charges, notes, and clinical documentation. | * **Open Encounter** (folder icon, blue) — Opens the OpenEMR encounter directly in a new tab, allowing you to review charges, notes, and clinical documentation. |
| * **Sync Status** (sync icon, red) — Appears when ClaimRev shows a claim as rejected but OpenEMR still shows it as billed. Clicking this updates the OpenEMR claim status to **Denied** and records the rejection reason from ClaimRev in the claim's process file. This saves manual data entry when payers reject claims. | * **Sync Status** (sync icon, red) — Appears when ClaimRev shows a claim as rejected but OpenEMR still shows it as billed. Clicking this updates the OpenEMR claim status to **Denied** and records the rejection reason from ClaimRev in the claim's process file. This saves manual data entry when payers reject claims. |
| * **Requeue for Billing** (redo icon, yellow) — Reopens the encounter for billing so it can be corrected and resubmitted. This resets the billing flags and creates a new claim version with status **Unbilled**, placing it back in the billing queue. | * **Requeue for Billing** (redo icon, yellow) — Reopens the encounter for billing so it can be corrected and resubmitted. This resets the billing flags and creates a new claim version with status **Unbilled**, placing it back in the billing queue. |
| |
| === Detail View === | === Detail View === |
| - Click **Batch Post All** to post all eligible claims in a payment advice at once. | - Click **Batch Post All** to post all eligible claims in a payment advice at once. |
| - The system processes each claim and shows a results summary with: | - The system processes each claim and shows a results summary with: |
| * **Posted** — Successfully posted claims (green) | * **Posted** — Successfully posted claims (green) |
| * **Skipped** — Claims that were already posted or could not be matched (gray) | * **Skipped** — Claims that were already posted or could not be matched (gray) |
| * **Errors** — Claims that encountered an error during posting (red) | * **Errors** — Claims that encountered an error during posting (red) |
| * **Needs Approval** — Reversals and pended claims that require individual review (yellow, see below) | * **Needs Approval** — Reversals and pended claims that require individual review (yellow, see below) |
| |
| == Reversals and Pended Claims == | == Reversals and Pended Claims == |
| Some claim statuses require special attention before posting: | Some claim statuses require special attention before posting: |
| |
| * **Reversals** (CLP02=22) — These represent a takeaway of a previous payment. Posting a reversal will create a negative payment in OpenEMR. When you click Post on a reversal, a confirmation dialog explains the impact and asks for approval before proceeding. | * **Reversals** (CLP02=22) — These represent a takeaway of a previous payment. Posting a reversal will create a negative payment in OpenEMR. When you click Post on a reversal, a confirmation dialog explains the impact and asks for approval before proceeding. |
| * **Pended Claims** (CLP02=5) — These indicate the payer is still processing the claim. Posting records the current adjudication info, but amounts may change. A confirmation dialog warns you of this before posting. | * **Pended Claims** (CLP02=5) — These indicate the payer is still processing the claim. Posting records the current adjudication info, but amounts may change. A confirmation dialog warns you of this before posting. |
| |
| In **batch mode**, reversals and pended claims are **not** auto-posted. Instead, they are separated into a "Needs Approval" section in the batch results, where you can review each one individually and click **Approve & Post** to post them one at a time. | In **batch mode**, reversals and pended claims are **not** auto-posted. Instead, they are separated into a "Needs Approval" section in the batch results, where you can review each one individually and click **Approve & Post** to post them one at a time. |
| When an ERA is posted, the following data flows into OpenEMR: | When an ERA is posted, the following data flows into OpenEMR: |
| |
| * **Payments** — Insurance payments are recorded in ''ar_session'' and ''ar_activity'' | * **Payments** — Insurance payments are recorded in ''ar_session'' and ''ar_activity'' |
| * **Contractual Adjustments** — CO (Contractual Obligation) group adjustments reduce the balance | * **Contractual Adjustments** — CO (Contractual Obligation) group adjustments reduce the balance |
| * **Patient Responsibility Memos** — PR (Patient Responsibility) adjustments are recorded as zero-dollar memos with labels like ''Ins1 dedbl: 150.00'', ''Ins1 coins: 45.00'', ''Ins1 copay: 25.00''. These memos are later parsed by the Patient Balance screen to show the PR breakdown. | * **Patient Responsibility Memos** — PR (Patient Responsibility) adjustments are recorded as zero-dollar memos with labels like ''Ins1 dedbl: 150.00'', ''Ins1 coins: 45.00'', ''Ins1 copay: 25.00''. These memos are later parsed by the Patient Balance screen to show the PR breakdown. |
| * **Insurance Level Close** — The ''last_level_closed'' flag on the encounter is updated, indicating insurance has finished processing | * **Insurance Level Close** — The ''last_level_closed'' flag on the encounter is updated, indicating insurance has finished processing |
| |
| === Test Mode === | === Test Mode === |
| A **Test Mode** toggle is available below the search button. When enabled: | When the **Enable Test Mode** global is on (Admin > Globals > ClaimRev Connect), the Payment Advice, ERA, and Reconciliation pages return **mock data** generated from local OpenEMR billing rows. Useful for demos, training, and posting-workflow rehearsal without hitting the live ClaimRev API. |
| * The search returns **mock data** instead of calling the ClaimRev API, allowing you to practice the posting workflow without real data. | |
| * Posting still writes to OpenEMR (useful for testing with a development database). | |
| * Claims are **not** marked as worked in ClaimRev when test mode is active. | |
| * A yellow warning banner is displayed to remind you that test mode is active. | |
| |
| **Important:** Only use test mode in development or training environments. Do not enable test mode in production with real patient data unless you understand that mock ERA data will be posted to OpenEMR. | * Posting still writes to OpenEMR — useful when running against a development database. |
| | * Claims are **not** marked as worked in ClaimRev while the global is on. |
| | * Reconciliation row actions (Sync rejected status, Requeue) short-circuit to a fake-success response. |
| | * ERA Download returns a 0-byte placeholder file. |
| | |
| | **Important:** Only enable the global in development or training environments. The global is the only switch — there is no longer a per-search checkbox. |
| |
| ==== Reconciliation ==== | ==== Reconciliation ==== |
| - Navigate to the **Reconciliation** tab. | - Navigate to the **Reconciliation** tab. |
| - Set your search filters: | - Set your search filters: |
| * **Date Range** — Filter encounters by service date | * **Date Range** — Filter encounters by service date |
| * **OpenEMR Status** — Choose from: | * **OpenEMR Status** — Choose from: |
| * **Billed** — Encounters marked as billed or crossover (default) | * **Billed** — Encounters marked as billed or crossover (default) |
| * **Denied** — Encounters marked as denied | * **Denied** — Encounters marked as denied |
| * **All Billed** — All encounters that have entered the billing process | * **All Billed** — All encounters that have entered the billing process |
| * **Patient Name** — Filter by first and/or last name | * **Patient Name** — Filter by first and/or last name |
| * **Payer Name** — Filter by insurance company | * **Payer Name** — Filter by insurance company |
| * **Discrepancies Only** — Show only encounters where the OE and ClaimRev statuses don't match | * **Discrepancies Only** — Show only encounters where the OE and ClaimRev statuses don't match |
| - Click **Search** to run the reconciliation. | - Click **Search** to run the reconciliation. |
| |
| === Summary Cards === | === Summary Cards === |
| At the top of the results, four summary cards show: | At the top of the results, four summary cards show: |
| * **Total Encounters** — Number of OpenEMR encounters matching your filters | * **Total Encounters** — Number of OpenEMR encounters matching your filters |
| * **Found in ClaimRev** — How many of those encounters were found in ClaimRev | * **Found in ClaimRev** — How many of those encounters were found in ClaimRev |
| * **Not in ClaimRev** — Encounters that were not found (potential submission issues) | * **Not in ClaimRev** — Encounters that were not found (potential submission issues) |
| * **Discrepancies** — Encounters where the OE and ClaimRev statuses disagree | * **Discrepancies** — Encounters where the OE and ClaimRev statuses disagree |
| |
| === Results Table === | === Results Table === |
| === Actions === | === Actions === |
| Each row provides action buttons depending on the situation: | Each row provides action buttons depending on the situation: |
| * **Open Encounter** — Navigate directly to the encounter in OpenEMR | * **Open Encounter** — Navigate directly to the encounter in OpenEMR |
| * **Sync Status** — Update OpenEMR to match ClaimRev's rejection status (appears for rejected claims) | * **Sync Status** — Update OpenEMR to match ClaimRev's rejection status (appears for rejected claims) |
| * **Requeue for Billing** — Put the claim back in the billing queue for correction and resubmission | * **Requeue for Billing** — Put the claim back in the billing queue for correction and resubmission |
| * **View in Portal** — Open the claim in the ClaimRev portal for full details | * **View in Portal** — Open the claim in the ClaimRev portal for full details |
| |
| === Detail View === | === Detail View === |
| Click on any row to expand a detailed comparison showing: | Click on any row to expand a detailed comparison showing: |
| * **OpenEMR Info** — Claim status, bill time, process file | * **OpenEMR Info** — Claim status, bill time, process file |
| * **ClaimRev Info** — Claim status, payer acceptance, worked status | * **ClaimRev Info** — Claim status, payer acceptance, worked status |
| * **ERA Info** — ERA classification, payer paid amount | * **ERA Info** — ERA classification, payer paid amount |
| |
| ==== Patient Balance ==== | ==== Patient Balance ==== |
| |
| === Search Filters === | === Search Filters === |
| * **Service Date Range** — Filter by encounter date | * **Service Date Range** — Filter by encounter date |
| * **Patient Name** — Search by patient first or last name | * **Patient Name** — Search by patient first or last name |
| * **Payer** — Filter by primary insurance company | * **Payer** — Filter by primary insurance company |
| * **Min Amount** — Only show balances above a threshold (default: $0.01) | * **Min Amount** — Only show balances above a threshold (default: $0.01) |
| * **Statement Status** — Filter by: | * **Statement Status** — Filter by: |
| * **All** — Show everything | * **All** — Show everything |
| * **Never Sent** — Encounters where no statement has been sent | * **Never Sent** — Encounters where no statement has been sent |
| * **Sent 1x** — Exactly one statement sent | * **Sent 1x** — Exactly one statement sent |
| * **Sent 2+** — Two or more statements sent | * **Sent 2+** — Two or more statements sent |
| * **In Collection** — Encounters marked as in collection | * **In Collection** — Encounters marked as in collection |
| |
| === Summary Cards === | === Summary Cards === |
| * **Total w/ Balance** — Number of encounters with outstanding patient balances | * **Total w/ Balance** — Number of encounters with outstanding patient balances |
| * **Total Amount** — Sum of all outstanding patient balances | * **Total Amount** — Sum of all outstanding patient balances |
| * **Never Sent** — Encounters needing their first statement | * **Never Sent** — Encounters needing their first statement |
| * **Sent 1x / Sent 2+** — Statement frequency breakdown | * **Sent 1x / Sent 2+** — Statement frequency breakdown |
| * **In Collection** — Encounters flagged for collections | * **In Collection** — Encounters flagged for collections |
| |
| === Results Table === | === Results Table === |
| Click any row to expand and see: | Click any row to expand and see: |
| |
| * **Patient Responsibility Breakdown** — Parsed from the ERA posting memos. Shows the breakdown of //why// the patient owes money: | * **Patient Responsibility Breakdown** — Parsed from the ERA posting memos. Shows the breakdown of //why// the patient owes money: |
| * **Deductible** — Amount applied to the patient's deductible | * **Deductible** — Amount applied to the patient's deductible |
| * **Coinsurance** — Patient's coinsurance portion | * **Coinsurance** — Patient's coinsurance portion |
| * **Copay** — Copay amount | * **Copay** — Copay amount |
| * **Pt Resp** — Other patient responsibility amounts | * **Pt Resp** — Other patient responsibility amounts |
| * **Per-Code Breakdown** — Each procedure code with its charge, adjustment, and remaining balance (from OpenEMR's InvoiceSummary) | * **Per-Code Breakdown** — Each procedure code with its charge, adjustment, and remaining balance (from OpenEMR's InvoiceSummary) |
| * **Statement History** — Log of all statements sent for this encounter, with date, method, amount, and who sent it | * **Statement History** — Log of all statements sent for this encounter, with date, method, amount, and who sent it |
| |
| === Actions === | === Actions === |
| |
| * **Generate Statement** (file icon) — Opens OpenEMR's built-in statement tool (sl_eob_search) where you can print or email patient statements | * **Generate Statement** (file icon) — Opens OpenEMR's built-in statement tool (sl_eob_search) where you can print or email patient statements |
| * **Mark Sent** (check icon) — Records that a statement was sent for this encounter. Logs the date, amount, and user to the statement history. | * **Mark Sent** (check icon) — Records that a statement was sent for this encounter. Logs the date, amount, and user to the statement history. |
| * **Add Note** (sticky note icon) — Add a free-text note to the encounter's statement history | * **Add Note** (sticky note icon) — Add a free-text note to the encounter's statement history |
| * **View Ledger** (book icon) — Opens the OpenEMR payment ledger for the encounter | * **View Ledger** (book icon) — Opens the OpenEMR payment ledger for the encounter |
| * **Open Encounter** (folder icon) — Opens the encounter in OpenEMR | * **Open Encounter** (folder icon) — Opens the encounter in OpenEMR |
| * **Generate via ClaimRev** (cloud icon, disabled) — Placeholder for future ClaimRev statement generation integration | * **Generate via ClaimRev** (cloud icon, disabled) — Placeholder for future ClaimRev statement generation integration |
| |
| === Statement Tracking === | === Statement Tracking === |
| |
| === Features === | === Features === |
| * **Work Queue** — Claims sorted by status, showing which need attention | * **Work Queue** — Claims sorted by status, showing which need attention |
| * **Timeline View** — Click any claim to see its full event history: submitted, accepted, rejected, ERA received, payment posted, manual notes | * **Timeline View** — Click any claim to see its full event history: submitted, accepted, rejected, ERA received, payment posted, manual notes |
| * **Real-Time Status Check** — Send a 276 status inquiry to the payer and get a 277 response showing current claim status | * **Real-Time Status Check** — Send a 276 status inquiry to the payer and get a 277 response showing current claim status |
| * **Batch Sync** — Sync multiple claims from ClaimRev at once to update local tracking data | * **Batch Sync** — Sync multiple claims from ClaimRev at once to update local tracking data |
| * **Manual Notes** — Add notes to any claim's timeline for team communication | * **Manual Notes** — Add notes to any claim's timeline for team communication |
| * **Dashboard Stats** — Summary cards showing claims by status category | * **Dashboard Stats** — Summary cards showing claims by status category |
| |
| === Status Categories === | === Status Categories === |
| Claims are categorized into actionable groups: | Claims are categorized into actionable groups: |
| * **Needs Attention** — Rejected, denied, stale (no update in 30+ days), or paid but not posted | * **Needs Attention** — Rejected, denied, stale (no update in 30+ days), or paid but not posted |
| * **In Progress** — Submitted, accepted by payer, awaiting adjudication | * **In Progress** — Submitted, accepted by payer, awaiting adjudication |
| * **Completed** — Paid and posted, or worked and resolved | * **Completed** — Paid and posted, or worked and resolved |
| |
| ==== AR Aging Report ==== | ==== AR Aging Report ==== |
| |
| === Summary Cards === | === Summary Cards === |
| * **Total AR** — Sum of all outstanding balances | * **Total AR** — Sum of all outstanding balances |
| * **Aging Buckets** — Dollar amounts in each bucket: 0-30, 31-60, 61-90, 91-120, 120+ days | * **Aging Buckets** — Dollar amounts in each bucket: 0-30, 31-60, 61-90, 91-120, 120+ days |
| * **Payer Count** — Number of distinct payers with outstanding balances | * **Payer Count** — Number of distinct payers with outstanding balances |
| * **Encounter Count** — Total encounters with balances | * **Encounter Count** — Total encounters with balances |
| |
| === Payer Aging Table === | === Payer Aging Table === |
| | Distribution | Visual bar showing the percentage of AR that is over 90 days old. Green = healthy, yellow = caution, red = problem. | | | Distribution | Visual bar showing the percentage of AR that is over 90 days old. Green = healthy, yellow = caution, red = problem. | |
| |
| Payers are sorted by total AR descending — the biggest balances appear first. | Payers are sorted by total AR descending — the biggest balances appear first. |
| |
| === CSV Export === | === CSV Export === |
| |
| === Summary Cards === | === Summary Cards === |
| * **Total Adjustments** — Number of individual adjustment line items in the period | * **Total Adjustments** — Number of individual adjustment line items in the period |
| * **Total Adjusted** — Dollar amount of all adjustments | * **Total Adjusted** — Dollar amount of all adjustments |
| * **Encounters** — Number of distinct encounters affected | * **Encounters** — Number of distinct encounters affected |
| * **Payers** — Number of distinct payers | * **Payers** — Number of distinct payers |
| |
| === Top Adjustment Reasons === | === Top Adjustment Reasons === |
| The left panel shows the 20 most common adjustment reasons, with: | The left panel shows the 20 most common adjustment reasons, with: |
| * **Reason** — The adjustment memo as recorded during ERA posting (e.g., "Adjust code 45") | * **Reason** — The adjustment memo as recorded during ERA posting (e.g., "Adjust code 45") |
| * **CARC Code** — The Claim Adjustment Reason Code number, if present | * **CARC Code** — The Claim Adjustment Reason Code number, if present |
| * **CARC Description** — Human-readable description (e.g., "Charge exceeds fee schedule/max allowable"). The module includes descriptions for 30+ common CARC codes. | * **CARC Description** — Human-readable description (e.g., "Charge exceeds fee schedule/max allowable"). The module includes descriptions for 30+ common CARC codes. |
| * **Count** — How many times this adjustment appeared | * **Count** — How many times this adjustment appeared |
| * **Amount** — Total dollar amount adjusted | * **Amount** — Total dollar amount adjusted |
| * **Visual bar** — Relative frequency compared to the top reason | * **Visual bar** — Relative frequency compared to the top reason |
| |
| === By Payer === | === By Payer === |
| ==== Recoupment Report ==== | ==== Recoupment Report ==== |
| |
| The **Recoupment Report** is found under the **Analytics** dropdown. It identifies claims where payments were reversed (recouped) — typically from Medicare reprocessing or payer take-backs — and tracks whether a reprocessed payment has been received. | The **Recoupment Report** is found under the **Analytics** dropdown. It identifies claims where payments were reversed (recouped) — typically from Medicare reprocessing or payer take-backs — and tracks whether a reprocessed payment has been received. |
| |
| === Purpose === | === Purpose === |
| - Click **Analytics > Recoupment Report** in the navigation bar. | - Click **Analytics > Recoupment Report** in the navigation bar. |
| - Set your filters: | - Set your filters: |
| * **Date Range** — Filter recoupments by post date (defaults to the last 6 months) | * **Date Range** — Filter recoupments by post date (defaults to the last 6 months) |
| * **Payer** — Filter by payer name | * **Payer** — Filter by payer name |
| * **Patient** — Filter by patient name | * **Patient** — Filter by patient name |
| - Click **Run Report**. | - Click **Run Report**. |
| |
| === Summary Cards === | === Summary Cards === |
| * **Recoupments** — Total number of recoupment events found | * **Recoupments** — Total number of recoupment events found |
| * **Total Recouped** — Dollar amount taken back by payers (shown in red) | * **Total Recouped** — Dollar amount taken back by payers (shown in red) |
| * **Reprocessed** — Dollar amount received in reprocessed payments (shown in green) | * **Reprocessed** — Dollar amount received in reprocessed payments (shown in green) |
| * **Net Impact** — Difference between recouped and reprocessed amounts, shown as a gain or loss. The card border is color-coded: green if net positive, red if net negative. | * **Net Impact** — Difference between recouped and reprocessed amounts, shown as a gain or loss. The card border is color-coded: green if net positive, red if net negative. |
| * **Pending Reprocess** — Number of recoupments where no reprocessed payment has been received yet (shown in yellow) | * **Pending Reprocess** — Number of recoupments where no reprocessed payment has been received yet (shown in yellow) |
| |
| === Results Table === | === Results Table === |
| | Original Paid | Total of payments received before the recoupment | | | Original Paid | Total of payments received before the recoupment | |
| | Recouped | Amount taken back (shown in red) | | | Recouped | Amount taken back (shown in red) | |
| | Reprocessed | Amount received after recoupment (green), or "—" if still pending | | | Reprocessed | Amount received after recoupment (green), or "—" if still pending | |
| | Net Impact | Gain or loss from the recoupment cycle (bold, color-coded) | | | Net Impact | Gain or loss from the recoupment cycle (bold, color-coded) | |
| | Balance | Current encounter balance | | | Balance | Current encounter balance | |
| Click any row to expand a three-column detail panel: | Click any row to expand a three-column detail panel: |
| |
| * **Recoupment Details** — The recoupment date, amount, check reference, check date, and memo from the ''ar_activity'' record | * **Recoupment Details** — The recoupment date, amount, check reference, check date, and memo from the ''ar_activity'' record |
| * **Original Payments** — Table of all positive payments posted before the recoupment, with date, amount, and reference number | * **Original Payments** — Table of all positive payments posted before the recoupment, with date, amount, and reference number |
| * **Reprocessed Payments** — Table of positive payments posted after the recoupment date. If none exist, a warning message is shown: "No reprocessed payment found yet. Medicare may still be processing, or the new ERA has not been posted." | * **Reprocessed Payments** — Table of positive payments posted after the recoupment date. If none exist, a warning message is shown: "No reprocessed payment found yet. Medicare may still be processing, or the new ERA has not been posted." |
| |
| === How It Works === | === How It Works === |
| The report queries ''ar_activity'' for records with a negative ''pay_amount'' (the recoupment). For each recoupment found, it then looks up: | The report queries ''ar_activity'' for records with a negative ''pay_amount'' (the recoupment). For each recoupment found, it then looks up: |
| - All positive payments on the same encounter posted **before** the recoupment date → classified as "original" payments | - All positive payments on the same encounter posted **before** the recoupment date → classified as "original" payments |
| - All positive payments posted **after** the recoupment date → classified as "reprocessed" payments | - All positive payments posted **after** the recoupment date → classified as "reprocessed" payments |
| |
| This classification helps billers understand the full payment lifecycle for reprocessed claims. | This classification helps billers understand the full payment lifecycle for reprocessed claims. |
| * Search for available ERA files by date range | * Search for available ERA files by date range |
| * Download individual files for import into the OpenEMR billing system | * Download individual files for import into the OpenEMR billing system |
| | * When **Enable Test Mode** is on, the ERA tab returns mock search results from local billing data and the Download button returns a 0-byte placeholder file. |
| |
| ==== X12 Tracker ==== | ==== X12 Tracker ==== |
| === Search and Filtering === | === Search and Filtering === |
| Filter appointments using any combination of: | Filter appointments using any combination of: |
| * **Date Range** — Start and end date (defaults to today through 7 days out) | * **Date Range** — Start and end date (defaults to today through 7 days out) |
| * **Facility** — Filter by service location | * **Facility** — Filter by service location |
| * **Provider** — Filter by rendering provider | * **Provider** — Filter by rendering provider |
| * **Eligibility Status** — Filter by: | * **Eligibility Status** — Filter by: |
| * **All** — Show all appointments | * **All** — Show all appointments |
| * **Needs Attention** — Appointments with no eligibility check, errors, or stale results | * **Needs Attention** — Appointments with no eligibility check, errors, or stale results |
| * **Active Coverage** — Appointments where eligibility returned successfully | * **Active Coverage** — Appointments where eligibility returned successfully |
| * **Not Checked** — Appointments with no eligibility record at all | * **Not Checked** — Appointments with no eligibility record at all |
| * **Stale** — Appointments where the eligibility check is older than the configured stale threshold | * **Stale** — Appointments where the eligibility check is older than the configured stale threshold |
| |
| === Results Table === | === Results Table === |
| |
| === Actions === | === Actions === |
| * **Check Now** — Immediately runs an eligibility check for the appointment via AJAX and updates the status in-place without a page reload. Shows a spinner while processing. | * **Check Now** — Immediately runs an eligibility check for the appointment via AJAX and updates the status in-place without a page reload. Shows a spinner while processing. |
| * **Queue** — Queues the eligibility check for background processing by the send/receive service. | * **Queue** — Queues the eligibility check for background processing by the send/receive service. |
| * **Queue Selected** — Queues all checked appointments (or all if none are checked) for background eligibility processing. | * **Queue Selected** — Queues all checked appointments (or all if none are checked) for background eligibility processing. |
| * **Check Now Selected** — Runs immediate eligibility checks for all checked appointments in parallel via AJAX, updating each row's status as results come back. | * **Check Now Selected** — Runs immediate eligibility checks for all checked appointments in parallel via AJAX, updating each row's status as results come back. |
| |
| === Detail View === | === Detail View === |
| - On each run, it checks whether today is one of the configured sweep days. | - On each run, it checks whether today is one of the configured sweep days. |
| - If it is a sweep day, it looks ahead N days for appointments where eligibility is: | - If it is a sweep day, it looks ahead N days for appointments where eligibility is: |
| * **Missing** — No eligibility record exists (never checked) | * **Missing** — No eligibility record exists (never checked) |
| * **In Error** — Previous check returned an error | * **In Error** — Previous check returned an error |
| * **Stale** — Results are older than the configured stale threshold | * **Stale** — Results are older than the configured stale threshold |
| - Appointments already queued (''waiting'' or ''creating'' status) are skipped to avoid duplicates. | - Appointments already queued (''waiting'' or ''creating'' status) are skipped to avoid duplicates. |
| - Matching appointments are queued for the existing eligibility send/receive background service to process. | - Matching appointments are queued for the existing eligibility send/receive background service to process. |
| |
| ==== Supported OpenEMR Versions ==== | ==== Supported OpenEMR Versions ==== |
| The ClaimRev Connect module supports: | The ClaimRev Connect module ships as a **single binary** covering: |
| * **OpenEMR 8.x** — Full native support (use the //OpenEMR 8.x// build above) | * **OpenEMR 8.x** (master and the 8.0.x patch line) — verified against ''openemr/openemr:flex'' and ''openemr/openemr:8.0.0.3-2026-03-25''. |
| * **OpenEMR 7.x** — Supported via a small compatibility shim layer (use the //OpenEMR 7.x// build above) | * **OpenEMR 7.x** (7.0.0 and later) — verified against ''openemr/openemr:7.0.2''. |
| |
| The OpenEMR 7.x build includes a ''src/Compat/'' directory with class_alias-based shims for classes and interfaces introduced in OpenEMR 8.x. On OpenEMR 7.x these shims provide equivalent functionality by wrapping legacy APIs directly. The OpenEMR 8.x build does not ship the shim layer at all — it targets the native classes directly. | The ''src/Compat/'' shim layer activates per-host. On OE 8.x cores that already expose the modern APIs the shims are no-ops; on OE 7.x or older 8.0.x cores the shims provide ''OEGlobalsBag'', ''CryptoInterface'', and ''ServiceContainer'' via ''class_alias'', and ''CsrfHelper'' detects the active ''CsrfUtils'' signature at runtime. |
| |
| === Shimmed Components (7.x build only) === | === Shimmed Components === |
| |
| | **Component** | **What the shim does on 7.x** | | | **Component** | **What the shim does on older cores** | |
| | ''OEGlobalsBag'' | Wraps ''$GLOBALS'' with typed getters | | | ''OEGlobalsBag'' | Wraps ''$GLOBALS'' with typed getters (used directly on 7.x; on 8.0.x patch line the module imports the shim explicitly because the core's ''OEGlobalsBag'' is thinner than expected) | |
| | ''ServiceContainer'' | Wraps ''CryptoGen'' and other services directly | | | ''ServiceContainer'' | Wraps ''CryptoGen'' and other services directly | |
| | ''LoggerInterface'' (''getLogger'') | Provides a PSR-3 logger via OpenEMR's ''SystemLogger'' | | | ''LoggerInterface'' (''getLogger'') | Provides a PSR-3 logger via OpenEMR's ''SystemLogger'' | |
| | ''ClockInterface'' (''getClock'') | Provides a PSR-20 clock returning the current time | | | ''ClockInterface'' (''getClock'') | Provides a PSR-20 clock returning the current time | |
| | ''CryptoInterface'' | Wraps ''CryptoGen'' behind the 8.x crypto interface | | | ''CryptoInterface'' | Wraps ''CryptoGen'' behind the 8.x crypto interface | |
| | ''CsrfHelper'' | Reflects on ''CsrfUtils::collectCsrfToken'' and dispatches to either the 7.x ''($subject, ?$session)'' or 8.x ''(SessionInterface, $subject)'' signature | | | ''CsrfHelper'' | Detects whether ''SessionWrapperFactory::getActiveSession'' exists; routes to ''CsrfUtils'' with the correct argument order and ''$_SESSION'' fallback when needed | |
| | | ''GlobalConfig::getClientSecret'' | Prefers ''CryptoGen::decryptFromDatabase'' (upstream PR #11956, May 2026) when available; falls back to ''decryptStandard'' on older cores | |
| |
| ==== Compatibility Check ==== | ==== Compatibility Check ==== |
| - Click the **Run Compatibility Check** button. | - Click the **Run Compatibility Check** button. |
| - A diagnostic page shows the status of each critical component: | - A diagnostic page shows the status of each critical component: |
| * ''OEGlobalsBag'' — Whether the native class or shim is in use | * ''OEGlobalsBag'' — Whether the native class or shim is in use |
| * ''ServiceContainer'' — Whether the native class or shim is in use | * ''ServiceContainer'' — Whether the native class or shim is in use |
| * ''LoggerInterface'' / ''ClockInterface'' / ''CryptoInterface'' — Whether native or shimmed | * ''LoggerInterface'' / ''ClockInterface'' / ''CryptoInterface'' — Whether native or shimmed |
| * ''GlobalConfig'' and ''Bootstrap'' — Whether they instantiate successfully | * ''GlobalConfig'' and ''Bootstrap'' — Whether they instantiate successfully |
| * OpenEMR version and PHP version | * OpenEMR version and PHP version |
| |
| If all checks show green, the module is fully operational. If any check shows "Using ClaimRev shim (7.x mode)", the module is running with compatibility shims — all features work normally. | If all checks show green, the module is fully operational. If any check shows "Using ClaimRev shim", the module is running with compatibility shims — all features work normally. |
| |
| --- | --- |
| ==== Eligibility Check Returns No Results ==== | ==== Eligibility Check Returns No Results ==== |
| * Verify the patient has insurance data entered with a valid payer | * Verify the patient has insurance data entered with a valid payer |
| * Ensure the payer is enrolled for eligibility transactions — see [[payer_enrollment|Payer Enrollments]] | * Ensure the payer is enrolled for eligibility transactions — see [[payer_enrollment|Payer Enrollments]] |
| * Check that the subscriber ID and patient demographics are accurate | * Check that the subscriber ID and patient demographics are accurate |
| |
| ==== "Error communicating with server" When Running Check Now ==== | ==== "Error communicating with server" When Running Check Now ==== |
| * Most common cause is the upstream ClaimRev API taking longer than the script's allowed run time. The 2.1.2 build raises the script time limit and adds OAuth retries — make sure you're on 2.1.2 or later. | * Most common cause is the upstream ClaimRev API taking longer than the script's allowed run time. The 2.1.2 build raises the script time limit and adds OAuth retries — make sure you're on 2.1.2 or later. |
| * Click **Reset** next to **Check Now** to clear cached results and try again. A retry usually succeeds because the previous request finished server-side after the browser gave up. | * Click **Reset** next to **Check Now** to clear cached results and try again. A retry usually succeeds because the previous request finished server-side after the browser gave up. |
| * If the error reproduces every time on a specific patient, capture the OpenEMR error log entry around the failed check (see **Connectivity** for log location) and contact ClaimRev support. | * If the error reproduces every time on a specific patient, capture the OpenEMR error log entry around the failed check (see **Connectivity** for log location) and contact ClaimRev support. |
| |
| ==== Patient Balance Shows No Results ==== | ==== Patient Balance Shows No Results ==== |
| * Make sure ERAs have been posted — the Patient Balance queue only shows encounters where ''last_level_closed >= 1'' (insurance has responded) | * Make sure ERAs have been posted — the Patient Balance queue only shows encounters where ''last_level_closed >= 1'' (insurance has responded) |
| * Navigate to **Setup** and click **Run Upgrade** to ensure the ''mod_claimrev_patient_statements'' table exists | * Navigate to **Setup** and click **Run Upgrade** to ensure the ''mod_claimrev_patient_statements'' table exists |
| * Try lowering the **Min Amount** filter or clearing all filters | * Try lowering the **Min Amount** filter or clearing all filters |
| ==== Calendar Indicators Not Showing ==== | ==== Calendar Indicators Not Showing ==== |
| * Verify the **Enable Calendar Eligibility Indicators** setting is turned on in **Admin > Config > Connectors > ClaimRev Connect** | * Verify the **Enable Calendar Eligibility Indicators** setting is turned on in **Admin > Config > Connectors > ClaimRev Connect** |
| * Make sure the module is fully configured (API credentials set) — calendar indicators only register when the module is configured | * Make sure the module is fully configured (API credentials set) — calendar indicators only register when the module is configured |
| * Check that patients have eligibility records in the system — appointments for patients with no eligibility data will show a yellow (unchecked) indicator | * Check that patients have eligibility records in the system — appointments for patients with no eligibility data will show a yellow (unchecked) indicator |
| * Clear your browser cache and reload the calendar | * Clear your browser cache and reload the calendar |
| |
| * Confirm the ''ClaimRev_Elig_Sweep'' background service is active in **Admin > System > Background Services** | * Confirm the ''ClaimRev_Elig_Sweep'' background service is active in **Admin > System > Background Services** |
| * Check the OpenEMR error log for sweep-related messages (search for "ClaimRev Eligibility Sweep") | * Check the OpenEMR error log for sweep-related messages (search for "ClaimRev Eligibility Sweep") |
| * The sweep runs once per day (every 1440 minutes) — it will not re-run on the same day | * The sweep runs once per day (every 1440 minutes) — it will not re-run on the same day |
| |
| ==== Database Upgrade Errors ==== | ==== Database Upgrade Errors ==== |
| * If you see SQL errors when clicking **Run Upgrade** on the Setup tab, check the OpenEMR error log for the specific query that failed | * If you see SQL errors when clicking **Run Upgrade** on the Setup tab, check the OpenEMR error log for the specific query that failed |
| * The upgrade process is idempotent — it skips tables and rows that already exist, so it is safe to run multiple times | * The upgrade process is idempotent — it skips tables and rows that already exist, so it is safe to run multiple times |
| |
| ==== Module Not Appearing ==== | ==== Module Not Appearing ==== |
| ==== "Wrong build" Symptoms ==== | ==== "Wrong build" Symptoms ==== |
| If you installed the //OpenEMR 8.x// build on a 7.x install (or vice versa), you may see: | If you installed the //OpenEMR 8.x// build on a 7.x install (or vice versa), you may see: |
| * **Fatal error: Class "OpenEMR\Core\OEGlobalsBag" not found** when loading any module page on 7.x — uninstall and use the //OpenEMR 7.x// build. | * **Fatal error: Class "OpenEMR\Core\OEGlobalsBag" not found** when loading any module page on 7.x — uninstall and use the //OpenEMR 7.x// build. |
| * **Call to undefined method ... CsrfUtils::collectCsrfToken()** with a different signature error — same fix; download the build that matches your OpenEMR major version. | * **Call to undefined method ... CsrfUtils::collectCsrfToken()** with a different signature error — same fix; download the build that matches your OpenEMR major version. |
| |
| --- | --- |
| |
| ===== Changelog ===== | ===== Changelog ===== |
| | |
| | == Version 2.1.3 — May 22, 2026 (Packagist edition) == |
| | * **Now published on Packagist** as ''claimrevolution/oe-module-claimrev-connect''. Install via ''composer require claimrevolution/oe-module-claimrev-connect:^2.1''. Replaces the previous May 12 (OE 7.x) and May 13 (OE 8.0.x rebuild) separate-zip builds with a single binary. |
| | * **Single-binary cross-version compatibility** — the ''src/Compat/'' shim layer lives in the module's own repo on GitHub, not duplicated across two build branches. Same code path runs on OE 8.x master, the OE 8.0.x patch line, and OE 7.x. |
| | * **Crypto helper hardened for cross-version** — ''GlobalConfig::getClientSecret'' uses ''CryptoGen::decryptStandard'' (works on both OE 7.x and 8.x) instead of the OE 8.x-only ''decryptFromDatabase''. |
| | * **X12_SFTP install fix** — module enable no longer references the non-existent ''background_services.last_run'' column that had been breaking installs on OE 8.x master. |
| | |
| | == Version 2.1.3 — May 13, 2026 rebuild for OE 8.0.x == |
| | * **OpenEMR 8.x build rebuilt from the ''release/v8-0'' branch.** The previous 8.x build assumed every 8.x core exposed ''OEGlobalsBag::getKernel'', ''SessionWrapperFactory::getActiveSession'', and ''CsrfUtils::collectCsrfToken(session, subject)''. The OE 8.0.x patch line has none of those — its ''OEGlobalsBag'' is thin, ''SessionWrapperFactory'' uses a different acquisition pattern, and ''CsrfUtils::collectCsrfToken'' takes ''(subject, ?session)'' with a ''$_SESSION'' fallback. The new build ships the ''src/Compat/'' shim layer with runtime-detection for the divergent APIs so the same zip works on 8.0.0.3 and on newer 8.x / master. |
| | * **Verified against ''openemr/openemr:8.0.0.3-2026-03-25''** with full patient + encounter + billing data copied from a flex install. Login, navigation, Claims tab, Reconciliation, Payment Advice, ERA, and Setup all load cleanly. |
| | * **Cross-core crypto handling** — ''GlobalConfig::getClientSecret'' now prefers the newer ''CryptoGen::decryptFromDatabase'' (upstream PR #11956, May 2026) when present and falls back to ''decryptStandard'' transparently. The 2.1.3 hotfix-only revert in the original 8.x build (which always called ''decryptStandard'') is no longer needed. |
| | * Module version constant stays at **2.1.3**; this is a rebuild of the same release for the actual 8.0.x patch line. |
| |
| == Version 2.1.3 (May 2026) == | == Version 2.1.3 (May 2026) == |
| * **Module-enable consent** — ''ClaimRevModuleSetup::ensureCoreSftpEnabled'' only flips the X12_SFTP background service to ''active = 1'' on a truly fresh install (''last_run IS NULL''). An admin who deliberately disabled the service keeps that setting across module re-enables. | * **Module-enable consent** — ''ClaimRevModuleSetup::ensureCoreSftpEnabled'' only flips the X12_SFTP background service to ''active = 1'' on a truly fresh install (''last_run IS NULL''). An admin who deliberately disabled the service keeps that setting across module re-enables. |
| * **Payment posting race window** — ''PaymentAdvicePostingService::post'' wraps the duplicate check and ''arPostSession'' insert in a MySQL named lock keyed by ''paymentAdviceId''. Concurrent submits of the same advice can no longer post twice; the second call returns a clear //concurrent post in progress// message. | * **Payment posting race window** — ''PaymentAdvicePostingService::post'' wraps the duplicate check and ''arPostSession'' insert in a MySQL named lock keyed by ''paymentAdviceId''. Concurrent submits of the same advice can no longer post twice; the second call returns a clear //concurrent post in progress// message. |
| * **Cross-core crypto compatibility** — ''GlobalConfig::getClientSecret'' keeps calling ''CryptoGen::decryptStandard'' (the legacy API) instead of the newer ''decryptFromDatabase'' that upstream PR #11956 introduced in May 2026. The new method does not exist on older OpenEMR cores most installs still run, and using it would break the Connectivity tab on those installs. | * **Cross-core crypto compatibility** — ''GlobalConfig::getClientSecret'' originally kept calling ''CryptoGen::decryptStandard'' (the legacy API) instead of the newer ''decryptFromDatabase'' that upstream PR #11956 introduced in May 2026. The new method does not exist on older OpenEMR cores most installs still run, and using it would break the Connectivity tab on those installs. //(The May 13 rebuild above changes this to a runtime-detected fallback so the same module works on both.)// |
| |
| == Version 2.1.2 (May 2026) == | == Version 2.1.2 (May 2026) == |
| * **Eligibility request body** — Send ''serviceTypeCodes'' as a JSON array (''List<string>'') instead of a comma-separated string, and always emit ''isRevenueToolsPayerId: false'' on each payer entry. The ClaimRev API tightened request validation and started rejecting the older shape with HTTP 400, breaking **Check Now**. Empty service-type-code configuration still asks for all benefits. | * **Eligibility request body** — Send ''serviceTypeCodes'' as a JSON array (''List<string>'') instead of a comma-separated string, and always emit ''isRevenueToolsPayerId: false'' on each payer entry. The ClaimRev API tightened request validation and started rejecting the older shape with HTTP 400, breaking **Check Now**. Empty service-type-code configuration still asks for all benefits. |
| * **MBI Finder mutex + request shape** — MBI Finder is now mutually exclusive with Eligibility (matching the existing Coverage Discovery / Eligibility mutex). The ''payers'' array is omitted when only non-eligibility products are selected, since the API ignores it for those products and its presence corrupts MBI Finder results. When MBI Finder is requested, the subscriber number is copied to the top-level ''subscriberId'' field where MBI Finder reads it. | * **MBI Finder mutex + request shape** — MBI Finder is now mutually exclusive with Eligibility (matching the existing Coverage Discovery / Eligibility mutex). The ''payers'' array is omitted when only non-eligibility products are selected, since the API ignores it for those products and its presence corrupts MBI Finder results. When MBI Finder is requested, the subscriber number is copied to the top-level ''subscriberId'' field where MBI Finder reads it. |
| * **Coverage Discovery results render correctly** — Coverage Discovery now renders with the full Quick Info / Deductibles / Benefits / Medicare / Validations layout. The API returns the same response shape as Eligibility, but the old Coverage Discovery view only showed the flat top-level coverage fields and dropped the nested benefit and deductible data. | * **Coverage Discovery results render correctly** — Coverage Discovery now renders with the full Quick Info / Deductibles / Benefits / Medicare / Validations layout. The API returns the same response shape as Eligibility, but the old Coverage Discovery view only showed the flat top-level coverage fields and dropped the nested benefit and deductible data. |
| * **Run without insurance** — Coverage Discovery, Demographics, and MBI Finder can now be run on patients who have no insurance entered. The eligibility tab shows a **No Insurance** view that hides the Eligibility option, pre-selects Coverage Discovery, and submits a request built from patient demographics alone. | * **Run without insurance** — Coverage Discovery, Demographics, and MBI Finder can now be run on patients who have no insurance entered. The eligibility tab shows a **No Insurance** view that hides the Eligibility option, pre-selects Coverage Discovery, and submits a request built from patient demographics alone. |
| * **Stop "Error communicating with server" on slow checks** — The eligibility AJAX endpoint can sit through a Cloud Run cold start (~60s) plus a ''retryLater'' poll loop (~60s) on Coverage Discovery, which exceeded PHP's default 30-second ''max_execution_time''. The Check Now and Appointment Check Now endpoints now allow up to 180 seconds, the Guzzle clients have explicit ''connect_timeout'' / ''timeout'' set so a stuck call can't burn the whole budget, and the OAuth token POST is retried up to two extra times on transient B2C hiccups. | * **Stop "Error communicating with server" on slow checks** — The eligibility AJAX endpoint can sit through a Cloud Run cold start (~60s) plus a ''retryLater'' poll loop (~60s) on Coverage Discovery, which exceeded PHP's default 30-second ''max_execution_time''. The Check Now and Appointment Check Now endpoints now allow up to 180 seconds, the Guzzle clients have explicit ''connect_timeout'' / ''timeout'' set so a stuck call can't burn the whole budget, and the OAuth token POST is retried up to two extra times on transient B2C hiccups. |
| * **Reset button** — A red **Reset** button next to **Check Now** clears every cached eligibility row for the current patient (across all payer responsibilities) after a confirmation prompt. Useful when re-testing a check from a clean slate. | * **Reset button** — A red **Reset** button next to **Check Now** clears every cached eligibility row for the current patient (across all payer responsibilities) after a confirmation prompt. Useful when re-testing a check from a clean slate. |
| |
| == Version 2.1.1 (May 2026) == | == Version 2.1.1 (May 2026) == |
| * **Two-build distribution** — The module now ships as separate downloads for OpenEMR 8.x and 7.x. The 8.x build targets the native 8.x classes directly; the 7.x build adds the compatibility shim layer. Pick the build that matches your OpenEMR major version. | * **Two-build distribution** — The module now ships as separate downloads for OpenEMR 8.x and 7.x. The 8.x build targets the native 8.x classes directly; the 7.x build adds the compatibility shim layer. Pick the build that matches your OpenEMR major version. |
| * **Hardening pass** — CSRF verification added to all state-changing AJAX endpoints (eligibility check/sync/chat, appointment check, claim mark-worked, CSV export). Bootstrap stops issuing per-request UPDATE writes and respects an admin-disabled core SFTP setting. Watchdog excludes itself from the stuck-service reset query so a long-running watchdog can't clear its own running flag. | * **Hardening pass** — CSRF verification added to all state-changing AJAX endpoints (eligibility check/sync/chat, appointment check, claim mark-worked, CSV export). Bootstrap stops issuing per-request UPDATE writes and respects an admin-disabled core SFTP setting. Watchdog excludes itself from the stuck-service reset query so a long-running watchdog can't clear its own running flag. |
| * **Bug fixes** — Eligibility Sweep now correctly includes Sunday (''0'') in the configured sweep days. ''claim_export_csv'' parses request input through the typed boundary helper rather than passing raw ''$_POST'' to the service. Menu entry hidden from users without ''acct/bill'' access. Migration runner uses ''QueryUtils::escapeTableName/escapeColumnName'' for schema-validated identifier escaping. | * **Bug fixes** — Eligibility Sweep now correctly includes Sunday (''0'') in the configured sweep days. ''claim_export_csv'' parses request input through the typed boundary helper rather than passing raw ''$_POST'' to the service. Menu entry hidden from users without ''acct/bill'' access. Migration runner uses ''QueryUtils::escapeTableName/escapeColumnName'' for schema-validated identifier escaping. |
| * **Standards** — All module PHP files declare ''strict_types=1'' to catch type coercion bugs at the boundary. | * **Standards** — All module PHP files declare ''strict_types=1'' to catch type coercion bugs at the boundary. |
| * **Internal** — ''PaymentAdvicePostingService'' and ''ReconciliationService'' refactored to expose pure helpers (idempotency-reference building, PCN parsing, claim status labels, service-line aggregation, discrepancy classification) with PHPUnit isolated test coverage. | * **Internal** — ''PaymentAdvicePostingService'' and ''ReconciliationService'' refactored to expose pure helpers (idempotency-reference building, PCN parsing, claim status labels, service-line aggregation, discrepancy classification) with PHPUnit isolated test coverage. |
| |
| == Version 2.1.0 (March 2026) == | == Version 2.1.0 (March 2026) == |
| * **KPI Dashboard** — Home tab replaced with a full revenue cycle dashboard showing claim pipeline, AR metrics, collections, denial rates, and patient responsibility at a glance | * **KPI Dashboard** — Home tab replaced with a full revenue cycle dashboard showing claim pipeline, AR metrics, collections, denial rates, and patient responsibility at a glance |
| * **Patient Balance Queue** — New tab showing encounters with outstanding patient responsibility after insurance has responded. Includes PR breakdown (deductible/coinsurance/copay parsed from ERA memos), per-code detail, statement tracking, and actions to generate statements or mark as sent | * **Patient Balance Queue** — New tab showing encounters with outstanding patient responsibility after insurance has responded. Includes PR breakdown (deductible/coinsurance/copay parsed from ERA memos), per-code detail, statement tracking, and actions to generate statements or mark as sent |
| * **Claim Status Dashboard** — New work-queue tab with claim lifecycle timeline, real-time 276/277 status checks, batch sync, and manual notes | * **Claim Status Dashboard** — New work-queue tab with claim lifecycle timeline, real-time 276/277 status checks, batch sync, and manual notes |
| * **Claim Lifecycle Tracking Tables** — New ''mod_claimrev_claims'' and ''mod_claimrev_claim_events'' database tables for tracking claim status, payer acceptance, ERA classification, paid amounts, and a full event audit trail (submitted, rejected, accepted, denied, ERA received, payment posted, etc.) | * **Claim Lifecycle Tracking Tables** — New ''mod_claimrev_claims'' and ''mod_claimrev_claim_events'' database tables for tracking claim status, payer acceptance, ERA classification, paid amounts, and a full event audit trail (submitted, rejected, accepted, denied, ERA received, payment posted, etc.) |
| * **AR Aging Report** — New analytics page with 30/60/90/120 day aging buckets grouped by payer, distribution visualization, and CSV export | * **AR Aging Report** — New analytics page with 30/60/90/120 day aging buckets grouped by payer, distribution visualization, and CSV export |
| * **Denial Analytics** — New analytics page analyzing adjustment patterns by reason code, payer, and monthly trend with CARC code descriptions and CSV export | * **Denial Analytics** — New analytics page analyzing adjustment patterns by reason code, payer, and monthly trend with CARC code descriptions and CSV export |
| * **Recoupment Report** — New analytics page identifying claims with reversed/recouped payments (e.g., Medicare reprocessing). Shows original payment, recoupment, reprocessed payment, net impact, and pending status with CSV export | * **Recoupment Report** — New analytics page identifying claims with reversed/recouped payments (e.g., Medicare reprocessing). Shows original payment, recoupment, reprocessed payment, net impact, and pending status with CSV export |
| * **Analytics Dropdown** — AR Aging Report, Denial Analytics, and Recoupment Report grouped under an "Analytics" dropdown in the navigation bar | * **Analytics Dropdown** — AR Aging Report, Denial Analytics, and Recoupment Report grouped under an "Analytics" dropdown in the navigation bar |
| * **Appointments Page Enhancements** — Added facility, provider, and eligibility status filters. Eligibility status filter supports All, Needs Attention, Active Coverage, Not Checked, and Stale views. Added "Check Now" and "Check Now Selected" buttons for real-time AJAX eligibility checks without page reload. Expandable detail rows show full eligibility response for completed checks. | * **Appointments Page Enhancements** — Added facility, provider, and eligibility status filters. Eligibility status filter supports All, Needs Attention, Active Coverage, Not Checked, and Stale views. Added "Check Now" and "Check Now Selected" buttons for real-time AJAX eligibility checks without page reload. Expandable detail rows show full eligibility response for completed checks. |
| * **Calendar Eligibility Indicators** — Color-coded left borders on appointment blocks in the main OpenEMR calendar showing eligibility status at a glance (green=active, red=inactive/error, yellow=unchecked/stale, blue=pending). Uses batch query for performance. Enable via the new "Enable Calendar Eligibility Indicators" global setting. | * **Calendar Eligibility Indicators** — Color-coded left borders on appointment blocks in the main OpenEMR calendar showing eligibility status at a glance (green=active, red=inactive/error, yellow=unchecked/stale, blue=pending). Uses batch query for performance. Enable via the new "Enable Calendar Eligibility Indicators" global setting. |
| * **Eligibility Sweep Background Service** — New ''ClaimRev_Elig_Sweep'' background service that proactively queues eligibility checks for upcoming appointments on configured days of the week. Configurable sweep days (default Monday and Thursday) and lookahead window (default 7 days). Skips appointments with fresh results or already-queued checks. | * **Eligibility Sweep Background Service** — New ''ClaimRev_Elig_Sweep'' background service that proactively queues eligibility checks for upcoming appointments on configured days of the week. Configurable sweep days (default Monday and Thursday) and lookahead window (default 7 days). Skips appointments with fresh results or already-queued checks. |
| * **OpenEMR 7.x Compatibility** — Built-in compatibility shims (''OEGlobalsBag'', ''ServiceContainer'', ''LoggerInterface'', ''ClockInterface'', ''CryptoInterface'') allow the module to run on OpenEMR 7.x without modification | * **OpenEMR 7.x Compatibility** — Built-in compatibility shims (''OEGlobalsBag'', ''ServiceContainer'', ''LoggerInterface'', ''ClockInterface'', ''CryptoInterface'') allow the module to run on OpenEMR 7.x without modification |
| * **Compatibility Check** — New diagnostic page (Connectivity > Run Compatibility Check) verifies all module dependencies resolve correctly | * **Compatibility Check** — New diagnostic page (Connectivity > Run Compatibility Check) verifies all module dependencies resolve correctly |
| * **Statement Tracking Table** — New ''mod_claimrev_patient_statements'' database table for tracking statement history per encounter | * **Statement Tracking Table** — New ''mod_claimrev_patient_statements'' database table for tracking statement history per encounter |
| |
| == Version 2.0.0 == | == Version 2.0.0 == |
| * **Claims Tab** — Added OpenEMR claim status display, encounter links, status sync (ClaimRev rejected → OE denied), and requeue-for-billing action | * **Claims Tab** — Added OpenEMR claim status display, encounter links, status sync (ClaimRev rejected → OE denied), and requeue-for-billing action |
| * **Payment Advice** — New tab for searching, previewing, and posting ERA/835 payment advice to OpenEMR (single and batch), with approval workflows for reversals and pended claims | * **Payment Advice** — New tab for searching, previewing, and posting ERA/835 payment advice to OpenEMR (single and batch), with approval workflows for reversals and pended claims |
| * **Payment Advice Test Mode** — Mock data mode for testing the posting workflow without live API calls | * **Payment Advice Test Mode** — Mock data mode for testing the posting workflow without live API calls |
| * **Reconciliation** — New tab that compares OpenEMR encounters against ClaimRev statuses with automatic discrepancy detection | * **Reconciliation** — New tab that compares OpenEMR encounters against ClaimRev statuses with automatic discrepancy detection |
| * **X12 Tracker** — File transmission history and download | * **X12 Tracker** — File transmission history and download |
| |
| == Version 1.0.0 == | == Version 1.0.0 == |