From 7597f79ef82b6869cd39f66b55c200fb0e6f4284 Mon Sep 17 00:00:00 2001 From: Jerry Padgett Date: Tue, 26 Mar 2024 14:09:29 -0400 Subject: [PATCH] Weno fixup and credential API (#7256) * Weno fixup and credential API - change fa font size - prevent old styles loading * - add acl to setup - validate vitals for under age - fix popup error for invalid action - add validation and default to change events - better validate setting requirements * - New WenoValidate class to verift keys and user credentials - verify and reset encryption key if needed for phamacy downloads. - temporally chang testing URL to weno dev server until new api goes online. - setup weno module for using upgrade in pre for auto upgrade feature. * - update code stamdards and do a little cleanup of our utility.js - numerous validation additions. - add all weno required settings (weno user id, facility location id and admin credentials validation) to module setup page. * - moved the custom modules to top of managers list. appears I have a bias! - add a simple but useful help panel to top of list table. - color coded module name for clarity if laminas or custom. * - had to create a separate setup_facilities script for setup so can use when module isn't enabled. - add a help button to MM and help_requested event listener - refactor controller for readability - add some security * - fix pathing problems - refactor reloads to replace page with new version * - too full of myself. utility Cleanup failed. - add new dowdlog log viewer - fix download create directory * - fix download won't break on duplicate key - auto save weno id in facilities section. - some setup refactor * - prevent resave of demographics on dialog close * - prevent demos from throwing warning in upgrade for old weno table population. v5.0.1 I believe. - files clean out - remove a chance for a race condition in background tasks * - change and move help - refactor to use the online api from dev api * - refactor and consoladate weno menu from Other to Weno eRx Tools - add Module setup and new Dowmload log viewer for admin to manage - add traps for internet off line. - remove unused class methods and cleanup WenoValidate class for readability. * - combine the download facility download viewer page --- .../oe-module-weno/ModuleManagerListener.php | 24 +- .../custom_modules/oe-module-weno/README.md | 42 ++- .../custom_modules/oe-module-weno/info.txt | 2 +- .../oe-module-weno/public/assets/js/synch.js | 24 +- .../oe-module-weno/scripts/file_download.php | 18 +- .../oe-module-weno/scripts/weno_log_sync.php | 28 +- .../oe-module-weno/src/Bootstrap.php | 84 +++--- .../src/Services/DownloadWenoPharmacies.php | 2 +- .../src/Services/LogImportBuild.php | 2 +- .../src/Services/LogProperties.php | 15 +- .../src/Services/ModuleService.php | 33 +-- .../src/Services/PharmacyService.php | 61 ++-- .../src/Services/TransmitProperties.php | 51 ++-- .../src/Services/WenoLogService.php | 6 +- .../src/Services/WenoValidate.php | 264 ++++++++++++++++++ .../oe-module-weno/{sql => }/table.sql | 7 +- .../templates/download_log_viewer.php | 263 +++++++++++++++++ .../oe-module-weno/templates/facilities.php | 235 ---------------- .../oe-module-weno/templates/indexrx.php | 167 +++++++---- .../templates/pharmacy_list_form.php | 35 +-- .../templates/setup_facilities.php | 92 ++++++ .../templates/weno_fragment.php | 6 +- .../oe-module-weno/templates/weno_setup.php | 223 ++++++++++----- .../oe-module-weno/templates/weno_users.php | 103 +++++++ .../custom_modules/oe-module-weno/version.php | 9 +- .../Controller/InstallerController.php | 95 ++++--- .../src/Installer/Model/InstModuleTable.php | 4 +- .../view/installer/installer/configure.phtml | 5 +- .../view/installer/installer/index.phtml | 134 ++++++--- src/Services/Utils/SQLUpgradeService.php | 2 +- 30 files changed, 1435 insertions(+), 601 deletions(-) create mode 100644 interface/modules/custom_modules/oe-module-weno/src/Services/WenoValidate.php rename interface/modules/custom_modules/oe-module-weno/{sql => }/table.sql (93%) create mode 100644 interface/modules/custom_modules/oe-module-weno/templates/download_log_viewer.php delete mode 100644 interface/modules/custom_modules/oe-module-weno/templates/facilities.php create mode 100644 interface/modules/custom_modules/oe-module-weno/templates/setup_facilities.php create mode 100644 interface/modules/custom_modules/oe-module-weno/templates/weno_users.php diff --git a/interface/modules/custom_modules/oe-module-weno/ModuleManagerListener.php b/interface/modules/custom_modules/oe-module-weno/ModuleManagerListener.php index 356849fedd9..707a342f5b6 100644 --- a/interface/modules/custom_modules/oe-module-weno/ModuleManagerListener.php +++ b/interface/modules/custom_modules/oe-module-weno/ModuleManagerListener.php @@ -53,7 +53,8 @@ public function moduleManagerAction($methodName, $modId, string $currentActionSt if (method_exists(self::class, $methodName)) { return self::$methodName($modId, $currentActionStatus); } else { - return "Module cleanup method $methodName does not exist."; + // no reason to report action method is missing. + return $currentActionStatus; } } @@ -71,8 +72,8 @@ public static function getModuleNamespace(): string } /** - * Required method to return this class object, - * so it is instantiated in Laminas Manager. + * Required method to return this class object + * so it will be instantiated in Laminas Manager. * * @return ModuleManagerListener */ @@ -98,6 +99,23 @@ private function install($modId, $currentActionStatus): mixed return $currentActionStatus; } + /** + * @param $modId + * @param $currentActionStatus + * @return mixed + */ + private function help_requested($modId, $currentActionStatus): mixed + { + // must call a script that implements a dialog to show help. + // I can't find a way to override the Laminas UI except using a dialog. + try { + include 'show_help.php'; + } catch (Exception $e) { + return $e->getMessage(); + } + return $currentActionStatus; + } + /** * @param $modId * @param $currentActionStatus diff --git a/interface/modules/custom_modules/oe-module-weno/README.md b/interface/modules/custom_modules/oe-module-weno/README.md index 93ed8f76789..051042f2bf7 100644 --- a/interface/modules/custom_modules/oe-module-weno/README.md +++ b/interface/modules/custom_modules/oe-module-weno/README.md @@ -1,9 +1,35 @@ -# Weno EZ Integration Module for OpenEMR -This module enables prescribers to make ePrescription through Weno's iFrame. +# Module Install and Setup. +- To start, go to OpenEMRs top menu and select **Modules->Manage Modules** whereby the Module Manager (MM) page is presented. For brevity the below screenshot shows when the Weno module is installed and the Config Settings cog icon is clicked. Experienced users will note that I redesigned the MM adding new features outlined on another topic on this forum and refactoring the old two tab page to a single page. +- The **Install** button will become an **Enable** button after install completes. **Click Confi cog/gear** icon or **Enable**. +- If this is the first time the module has been installed a warning will pop up telling that Weno Admin settings have not been completed and validated. This is indicated by a red module config icon. By clicking this icon whether red or normal the **Weno eRx Service Admin Setup** settings will show in a panel. The module can not be enabled until the Primary Admin section passes validation. +- The trash can icon is used to unregister the module. Any previous Weno setup will persist and remain in the last state of configuration. This way when and if the module is re-registered, all previous setup will remain. -## To Install the Weno EZ Integration Module -- On the top menu Modules -> Manage Modules. Click the unregistered button to display all unregistered modules. -- Find the Weno EZ Integration Module and click the "Register Button". -- On the Registered tab, click install and enable. -- After successful installation, headover to Admin -> Config -> Weno to enter the weno Admin settings. To register for an account, visit www.online.wenoexchange.com -- Follow the instructions on the Developer Page to configure your Weno EZ integration. \ No newline at end of file +- **Important to note that The Primary Admin Section** will require using the Validate and Save button after completing this section. All other sections will auto save when values are changed. +## Setup Summary +- There are three sections. After entering the required Admin credentials, Weno User ID for all prescribers and the Weno Location ID for the appropriate facility, all of which was received when a Weno account was created, click the **Enable** button to enable the module allowing the start of initial pharmacies download. You may then go to the User Settings page to enter the provider/prescribers credentials. For yourself in this case. + All providers that will be prescribing using Weno eRx must also have their credentials set otherwise the Weno eRx widget will not display. +- After a log out and in or by clicking the **Restart OpenEMR** button in config panel the Weno menu items of **Admin->Other->Weno Management** and **Reports->Clients->Prescription Log** will be enabled. +## Weno Required and Ancillary Setup for OpenEMR +- Weno provided values necessary for setup are: + 1. The account holders Admin credentials. + 2. Weno User Id: Uxxxx + 3. Assigned Location Id's for all the facilities used by the above User Id: Lxxxxx + 4. The users credentials assign to each prescriber: username(email address) and password. +- **Important!** It is good practice for all user/prescribers to have their default facility set in their Users settings. Otherwise the location from the first/default from all practice facilities will be used. + +There are three sections within the Weno eRx Service Admin Setup that allow the user to setup almost all the necessary settings to successfully start e-prescribing. The only other item is that each Weno prescriber credentials are set up in their User Settings. +### The Weno Primary Admin Section. +- All values must be entered and validated. +- If validation fails because either email and/or password are invalid an alert will be shown stating such. +- If the encryption key is deemed invalid an alert will show and a new Encryption Reset button enabled. First try re-entering the key but if that doesn't work clicking the Reset button will create a new key. This change will also be reflected in the Admins main Weno account and no other actions are needed by the user. You may look on the key as an API token which may be a more familiar term to the reader. +### The Map Weno User Id`s (Required) Section. +- This section presents a table of all authorised users showing their default facility if assigned and an input field to enter their Weno user id Uxxxx. This value is important in order to form a relationship between Weno and the OpenEMR user for tracking prescriptions. +- All values are automatically saved for the user whenever the Weno Provider ID is entered or changed. +- As a convenience, an edit button is supplied to present a dialog containing the Users settings in edit mode. From here user may edit any setting such as assigning a default facility. This would be the same as accessing Users from top menu Admin->Users selected provider. +### The Map Weno Facility Id`s (Required) Section. +- This section is pretty self explanatory with perhaps noting this same data may be accessed from top menu Admin->Other->Weno Management as explained below. +- This section also auto saves for convenience. +### Other methods for various set up items accessed from top menu. +- Open **Admin->Users** and select the user associated with the weno user id Uxxx and enter and save the weno user id in the **Weno Provider ID** field. +- Next open **Admin->Other->Weno Management** and enter the assigned Location Id Lxxxxx for the locations facilities. +- Lastly from the top patient bar user icon click **Settings**. Scroll down or find the Weno button and click. Enter your username(email) and password in the **Weno Provider Email and Weno Provider Password** fields and **Save**. **Note** If these credentials are absent or wrong, you will not be able to prescribe prescriptions. diff --git a/interface/modules/custom_modules/oe-module-weno/info.txt b/interface/modules/custom_modules/oe-module-weno/info.txt index 930033b218f..0c55cc28227 100644 --- a/interface/modules/custom_modules/oe-module-weno/info.txt +++ b/interface/modules/custom_modules/oe-module-weno/info.txt @@ -1 +1 @@ -Weno EZ Integration eRx Module v1.2.0 \ No newline at end of file +Weno EZ Integration eRx Module v1.0.0 diff --git a/interface/modules/custom_modules/oe-module-weno/public/assets/js/synch.js b/interface/modules/custom_modules/oe-module-weno/public/assets/js/synch.js index 3e6319247c0..fa78f25eacf 100644 --- a/interface/modules/custom_modules/oe-module-weno/public/assets/js/synch.js +++ b/interface/modules/custom_modules/oe-module-weno/public/assets/js/synch.js @@ -38,7 +38,7 @@ function wenoAlertManager(option, element, spinElement) { element.classList.add("d-none"); element.classList.remove("alert", "alert-success"); element.innerHTML = ""; - window.location.reload(); + window.location.replace(window.location.href) }, 3000 ); @@ -54,7 +54,6 @@ function wenoAlertManager(option, element, spinElement) { } } -// Reserved for future use. function renderDialog(action, uid, event) { event.preventDefault(); // Trim action URL @@ -65,30 +64,21 @@ function renderDialog(action, uid, event) { const urls = { 'demographics': '/interface/patient_file/summary/demographics_full.php', 'user_settings': '/interface/super/edit_globals.php?mode=user', - 'weno_manage': '/interface/modules/custom_modules/oe-module-weno/templates/facilities.php', + 'weno_manage': '/interface/modules/custom_modules/oe-module-weno/templates/weno_setup.php', 'users': '/interface/usergroup/user_admin.php' }; - // Construct action URL - const urlPart = urls[action].includes('?') ? '&' : '?'; - const actionUrl = `${urls[action]}${urlPart}id=${encodeURIComponent(uid)}&csrf_token_form=${encodeURIComponent(csrf)}`; - if (urls[action] === undefined) { console.error('Invalid action URL'); alert(action.toUpperCase() + " " + xl('Direct action not implemented yet.')); return; } + // Construct action URL + const urlPart = urls[action].includes('?') ? '&' : '?'; + const actionUrl = `${urls[action]}${urlPart}id=${encodeURIComponent(uid)}&csrf_token_form=${encodeURIComponent(csrf)}`; + // Open modal dialog dlgopen('', 'dialog-mod', '900', 'full', '', '', { buttons: [ - /*{ - text: jsText('Click'), - close: false, - id: jsAttr('click-me'), - click: function () { - //tidyUp(); - }, - style: 'primary' - },*/ { text: jsText('Return to eRx Widget'), close: true, @@ -97,12 +87,12 @@ function renderDialog(action, uid, event) { ], allowResize: true, allowDrag: true, + onClosed: 'reload', dialogId: 'error-dialog', type: 'iframe', resolvePromiseOn: 'close', url: top.webroot_url + actionUrl }).then(function (dialog) { top.restoreSession(); - window.location.reload(); }); } diff --git a/interface/modules/custom_modules/oe-module-weno/scripts/file_download.php b/interface/modules/custom_modules/oe-module-weno/scripts/file_download.php index c3bae6ab03f..70c16c95501 100644 --- a/interface/modules/custom_modules/oe-module-weno/scripts/file_download.php +++ b/interface/modules/custom_modules/oe-module-weno/scripts/file_download.php @@ -98,7 +98,7 @@ function download_zipfile($fileUrl, $zipped_file) $isError = $wenolog->scrapeWenoErrorHtml($rpt); if ($isError['is_error']) { error_log('Pharmacy download failed: ' . $isError['messageText']); - $wenolog->insertWenoLog("pharmacy", "loginfail"); + $wenolog->insertWenoLog("pharmacy", "Exceeded_download_limits"); } EventAuditLogger::instance()->newEvent("pharmacy_log", $_SESSION['authUser'], $_SESSION['authProvider'], 0, $isError['messageText']); $wenolog->insertWenoLog("pharmacy", "Failed"); @@ -125,6 +125,7 @@ function download_zipfile($fileUrl, $zipped_file) sqlStatementNoLog('START TRANSACTION'); } while (!feof($records)) { + $isError = false; $line = fgetcsv($records); if ($l <= 1) { @@ -190,9 +191,9 @@ function download_zipfile($fileUrl, $zipped_file) $insertdata['fullDay'] = $fullDay; if ($data['Daily'] == 'Y') { - $insertPharmacy->updatePharmacies($insertdata); + $isError = $insertPharmacy->updatePharmacies($insertdata); } else { - $insertPharmacy->insertPharmacies($insertdata); + $isError = $insertPharmacy->insertPharmacies($insertdata); } ++$l; } @@ -212,6 +213,17 @@ function download_zipfile($fileUrl, $zipped_file) unlink($file); } } + if ($isError) { + EventAuditLogger::instance()->newEvent( + "pharmacy_log", + $_SESSION['authUser'], + $_SESSION['authProvider'], + 0, + "Pharmacy Import download failed SQL Insert error." + ); + $wenolog->insertWenoLog("pharmacy", "Failed"); + error_log("User Initialed Pharmacy Import Failed Insert error"); + } // let's brag about it. EventAuditLogger::instance()->newEvent( "pharmacy_log", diff --git a/interface/modules/custom_modules/oe-module-weno/scripts/weno_log_sync.php b/interface/modules/custom_modules/oe-module-weno/scripts/weno_log_sync.php index 23033780f45..ee238b7d81f 100644 --- a/interface/modules/custom_modules/oe-module-weno/scripts/weno_log_sync.php +++ b/interface/modules/custom_modules/oe-module-weno/scripts/weno_log_sync.php @@ -14,24 +14,30 @@ use OpenEMR\Common\Logging\EventAuditLogger; use OpenEMR\Modules\WenoModule\Services\LogProperties; use OpenEMR\Modules\WenoModule\Services\WenoPharmaciesJson; +use OpenEMR\Modules\WenoModule\Services\WenoValidate; function downloadWenoPharmacy() { + // Check if the encryption key is valid. If not, request a new key and then set it. + $wenoValidate = new WenoValidate(); + $isKey = $wenoValidate->validateAdminCredentials(true); // auto reset on invalid. + if ((int)$isKey >= 998) { + EventAuditLogger::instance()->newEvent( + "pharmacy_background", + $_SESSION['authUser'], + $_SESSION['authProvider'], + 1, + text("Background Initiated Pharmacy download attempt failed. Internet problem!") + ); + error_log('Background Initiated Pharmacy Download not ran. Internet problem: ' . text($isKey)); + die; + } + error_log('Background Initiated Encryption Verify returned: ' . text($isKey == '1' ? 'Verified key is valid.' : 'Invalid Key')); + $cryptoGen = new CryptoGen(); $localPharmacyJson = new WenoPharmaciesJson($cryptoGen); - // Check if the background service is active. Intervals are set to once a day - // Weno has decided to not force the import of pharmacies since they are using the iframe - // and the pharmacy can be selected at the time of creating the prescription. $value = $localPharmacyJson->checkBackgroundService(); - - EventAuditLogger::instance()->newEvent( - "pharmacy_background", - $_SESSION['authUser'], - $_SESSION['authProvider'], - 1, - "Init Background Pharmacy Download Service Status:" . text(ucfirst($value)) - ); if ($value == 'active' || $value == 'live') { error_log('Background Initiated Pharmacy Download Started.'); diff --git a/interface/modules/custom_modules/oe-module-weno/src/Bootstrap.php b/interface/modules/custom_modules/oe-module-weno/src/Bootstrap.php index 86e31b9fefb..1fb811bf2f8 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Bootstrap.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Bootstrap.php @@ -30,9 +30,6 @@ class Bootstrap { - const OPENEMR_GLOBALS_LOCATION = "../../../../globals.php"; - const MODULE_INSTALLATION_PATH = "/interface/modules/custom_modules/oe-module-weno"; - const MODULE_NAME = ""; const MODULE_MENU_NAME = "Weno"; /** @@ -65,6 +62,18 @@ class Bootstrap * @var SelectedPatientPharmacy */ private SelectedPatientPharmacy $selectedPatientPharmacy; + public string $installPath; + + public function __construct(EventDispatcher $dispatcher) + { + $this->installPath = $GLOBALS['web_root'] . "/interface/modules/custom_modules/oe-module-weno"; + $this->eventDispatcher = $dispatcher; + $this->globalsConfig = new WenoGlobalConfig(); + $this->moduleDirectoryName = basename(dirname(__DIR__)); + $this->modulePath = dirname(__DIR__); + $this->logger = new SystemLogger(); + $this->selectedPatientPharmacy = new SelectedPatientPharmacy(); + } /** * @return void @@ -72,12 +81,11 @@ class Bootstrap public function subscribeToEvents(): void { $modService = new ModuleService(); + // let Admin configure Weno if module is not configured. + $this->addGlobalSettings(); if (!$modService->isWenoConfigured()) { - // let Admin configure Weno if module is not configured. - $this->addGlobalSettings(); return; } - $this->addGlobalSettings(); $this->registerMenuItems(); $this->registerDemographicsEvents(); $this->demographicsSelectorEvents(); @@ -87,16 +95,6 @@ public function subscribeToEvents(): void $modService::setModuleState('oe-module-weno', '1', '0'); } - public function __construct(EventDispatcher $dispatcher) - { - $this->eventDispatcher = $dispatcher; - $this->globalsConfig = new WenoGlobalConfig(); - $this->moduleDirectoryName = basename(dirname(__DIR__)); - $this->modulePath = dirname(__DIR__); - $this->logger = new SystemLogger(); - $this->selectedPatientPharmacy = new SelectedPatientPharmacy(); - } - /** * @return \Twig\Environment */ @@ -225,38 +223,58 @@ public function registerMenuItems(): void public function addCustomMenuItem(MenuEvent $event): MenuEvent { $menu = $event->getMenu(); + // Top level menu + $topMenu = new \stdClass(); + $topMenu->requirement = 0; + $topMenu->target = 'adm0'; + $topMenu->menu_id = 'adm'; + $topMenu->label = xlt("Weno eRx Tools"); + $topMenu->icon = "fa-caret-right"; + $topMenu->children = []; + $topMenu->acl_req = ["admin", "super"]; + $topMenu->global_req = ["weno_rx_enable"]; //Prescription Log $menuItem = new \stdClass(); $menuItem->requirement = 0; $menuItem->target = 'rep'; $menuItem->menu_id = 'rep0'; - $menuItem->label = xlt("Prescription Log"); - $menuItem->url = self::MODULE_INSTALLATION_PATH . "/templates/rxlogmanager.php"; + $menuItem->label = xlt("Weno Prescription Log"); + $menuItem->url = "/interface/modules/custom_modules/oe-module-weno/templates/rxlogmanager.php"; $menuItem->children = []; $menuItem->acl_req = ["patients", "rx"]; $menuItem->global_req = ["weno_rx_enable"]; - - //Weno Management - $mgtMenu = new \stdClass(); - $mgtMenu->requirement = 0; - $mgtMenu->target = 'adm0'; - $mgtMenu->menu_id = 'adm'; - $mgtMenu->label = xlt("Weno Management"); - $mgtMenu->url = self::MODULE_INSTALLATION_PATH . "/templates/facilities.php"; - $mgtMenu->children = []; - $mgtMenu->acl_req = ["admin", "super"]; - $mgtMenu->global_req = ["weno_rx_enable"]; - + //Weno log + $dlMenu = new \stdClass(); + $dlMenu->requirement = 0; + $dlMenu->target = 'adm1'; + $dlMenu->menu_id = 'adm'; + $dlMenu->label = xlt("Weno Downloads Management"); + $dlMenu->url = "/interface/modules/custom_modules/oe-module-weno/templates/download_log_viewer.php"; + $dlMenu->children = []; + $dlMenu->acl_req = ["admin", "super"]; + $dlMenu->global_req = ["weno_rx_enable"]; + //Weno Setup + $setupMenu = new \stdClass(); + $setupMenu->requirement = 0; + $setupMenu->target = 'adm0'; + $setupMenu->menu_id = 'adm'; + $setupMenu->label = xlt("Weno eRx Service Setup"); + $setupMenu->url = "/interface/modules/custom_modules/oe-module-weno/templates/weno_setup.php"; + $setupMenu->children = []; + $setupMenu->acl_req = ["admin", "super"]; + $setupMenu->global_req = ["weno_rx_enable"]; + // Write the menu items to the menu foreach ($menu as $item) { if ($item->menu_id == 'admimg') { + $item->children[] = $topMenu; foreach ($item->children as $other) { - if ($other->label == 'Other') { - $other->children[] = $mgtMenu; + if ($other->label == 'Weno eRx Tools') { + $other->children[] = $dlMenu; + $other->children[] = $setupMenu; break; } } } - if ($item->menu_id == 'repimg') { foreach ($item->children as $clientReport) { if ($clientReport->label == 'Clients') { diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/DownloadWenoPharmacies.php b/interface/modules/custom_modules/oe-module-weno/src/Services/DownloadWenoPharmacies.php index ba99ffdfd00..2681657eeb0 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Services/DownloadWenoPharmacies.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/DownloadWenoPharmacies.php @@ -90,7 +90,7 @@ public function extractFile($path_to_extract, $storelocation): ?string if ($isError['is_error']) { EventAuditLogger::instance()->newEvent("pharmacy_background", $_SESSION['authUser'], $_SESSION['authProvider'], 0, "Pharmacy Failed download! Weno error: " . $isError['messageText']); error_log('Pharmacy download failed: ' . $isError['messageText']); - $wenolog->insertWenoLog("pharmacy", "loginfail"); + $wenolog->insertWenoLog("pharmacy", "Exceeded_download_limits"); } else { EventAuditLogger::instance()->newEvent("pharmacy_background", $_SESSION['authUser'], $_SESSION['authProvider'], 0, "Pharmacy Failed download! Weno error Other"); error_log("Pharmacy Failed download! Weno error: Other"); diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/LogImportBuild.php b/interface/modules/custom_modules/oe-module-weno/src/Services/LogImportBuild.php index 0339132d9e0..3f09d77aeb5 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Services/LogImportBuild.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/LogImportBuild.php @@ -59,7 +59,7 @@ public function checkMessageId() return $entry['count']; } - public function buildInsertArray(): bool|string + public function buildPrescriptionInserts(): bool|string { $l = 0; if (file_exists($this->rxsynclog)) { diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/LogProperties.php b/interface/modules/custom_modules/oe-module-weno/src/Services/LogProperties.php index 78efe828387..e27cfe27b96 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Services/LogProperties.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/LogProperties.php @@ -71,6 +71,10 @@ public function __construct() $this->cryptoGen = new CryptoGen(); $this->method = "aes-256-cbc"; $this->rxsynclog = $GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/weno/logsync.csv"; + $logDir = $GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/weno"; + if (!is_dir($logDir)) { + mkdir($logDir, 0775, true); + } $this->enc_key = $this->cryptoGen->decryptStandard($GLOBALS['weno_encryption_key'] ?? ''); $this->key = substr(hash('sha256', $this->enc_key, true), 0, 32); $this->iv = chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0); @@ -165,7 +169,7 @@ public function logSync() if ($isError['is_error']) { $error = $isError['messageText']; error_log('Prescription download failed: ' . $error); - $wenolog->insertWenoLog("prescription", "loginfail"); + $wenolog->insertWenoLog("prescription", "possible_invalid_credentials"); $wenolog->insertWenoLog("prescription", "Failed"); EventAuditLogger::instance()->newEvent("prescriptions_log", $_SESSION['authUser'], $_SESSION['authProvider'], 0, $error); die(js_escape($error)); @@ -174,13 +178,15 @@ public function logSync() } else { // yes record failures. EventAuditLogger::instance()->newEvent("prescriptions_log", $_SESSION['authUser'], $_SESSION['authProvider'], 0, "$statusCode"); + error_log("Prescription download failed: $statusCode"); + $wenolog->insertWenoLog("prescription", "http_error_$statusCode"); $wenolog->insertWenoLog("prescription", "Failed"); return false; } if (file_exists($this->rxsynclog)) { $log = new LogImportBuild(); - $rtn = $log->buildInsertArray(); + $rtn = $log->buildPrescriptionInserts(); if (!$rtn) { return false; } @@ -191,9 +197,9 @@ public function logSync() } /** - * @return mixed + * @return string|array */ - public function getProviderEmail(): mixed + public function getProviderEmail(): string|array { if ($_SESSION['authUser']) { $provider_info = ['email' => $GLOBALS['weno_provider_email']]; @@ -213,6 +219,7 @@ public function getProviderEmail(): mixed echo TransmitProperties::styleErrors($error); exit; } + return ''; } /** diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/ModuleService.php b/interface/modules/custom_modules/oe-module-weno/src/Services/ModuleService.php index 28d9f67fded..3fea679b6c5 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Services/ModuleService.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/ModuleService.php @@ -94,6 +94,7 @@ public function saveVendorGlobals($items): void $vendors['weno_secondary_admin_password'] = $items['weno_secondary_admin_password']; foreach ($vendors as $key => $vendor) { + $GLOBALS[$key] = $vendor; sqlQuery( "INSERT INTO `globals` (`gl_name`,`gl_value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `gl_name` = ?, `gl_value` = ?", array($key, $vendor, $key, $vendor) @@ -129,22 +130,21 @@ public function isWenoConfigured(): bool $config = $this->getVendorGlobals(); $keys = array_keys($config); foreach ($keys as $key) { + // these are always required to run module. if ( - $key === 'weno_rx_enable_test' - || $key === 'weno_secondary_admin_username' - || $key === 'weno_secondary_admin_password' - || $key === 'weno_secondary_encryption_key' + $key === 'weno_rx_enable' + || $key === 'weno_admin_username' + || $key === 'weno_admin_password' + || $key === 'weno_encryption_key' ) { - continue; - } - $value = $GLOBALS[$key] ?? null; - - if (empty($value)) { - self::setTaskState('0', false); - return false; + $value = $config[$key] ?? null; + if (empty($value)) { + self::setTaskState('0'); + return false; + } } } - self::setTaskState('1', false); + self::setTaskState('1'); return true; } @@ -152,13 +152,14 @@ public static function statusPharmacyDownloadReset(): bool { $logService = new WenoLogService(); $log = $logService->getLastPharmacyDownloadStatus(); - if ($log['status'] ?? '' != 'Success') { + if ($log['status'] ?? '' == 'Failed') { if (($log['count'] ?? 0) > 0) { return true; } - $sql = "UPDATE `background_services` SET `next_run` = current_timestamp(), `active` = '1' WHERE `name` = ? && `next_run` > current_timestamp()"; - sqlQuery($sql, array('WenoExchangePharmacies')); - return true; + // TODO need to add lookup for last 3 failed status and if it's been over three attempts then stop trying. + //$sql = "UPDATE `background_services` SET `next_run` = current_timestamp(), `active` = '1' WHERE `name` = ? && `next_run` > current_timestamp()"; + //sqlQuery($sql, array('WenoExchangePharmacies')); + //return true; } return false; } diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/PharmacyService.php b/interface/modules/custom_modules/oe-module-weno/src/Services/PharmacyService.php index 448a63b100d..50f80ec5734 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Services/PharmacyService.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/PharmacyService.php @@ -123,26 +123,25 @@ public function checkWenoPharmacyLog() } } - public function insertPharmacies($insertdata) + public function insertPharmacies($insertdata): bool { - - $sql = "INSERT INTO weno_pharmacy SET "; - $sql .= "ncpdp = ?, "; - $sql .= "npi = ?, "; - $sql .= "business_name = ?, "; - $sql .= "address_line_1 = ?, "; - $sql .= "address_line_2 = ?, "; - $sql .= "city = ?, "; - $sql .= "state = ?, "; - $sql .= "zipcode = ?,"; - $sql .= "country_code = ?, "; - $sql .= "international = ?, "; - $sql .= "pharmacy_phone = ?, "; - $sql .= "on_weno = ?, "; - $sql .= "test_pharmacy = ?, "; - $sql .= "state_wide_mail_order = ?, "; - $sql .= "24hr = ? "; - + $sql = "INSERT INTO weno_pharmacy (ncpdp, npi, business_name, address_line_1, address_line_2, city, state, zipcode, country_code, international, pharmacy_phone, on_weno, test_pharmacy, state_wide_mail_order, 24hr) "; + $sql .= "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "; + $sql .= "ON DUPLICATE KEY UPDATE "; + $sql .= "npi = ?, "; + $sql .= "business_name = ?, "; + $sql .= "address_line_1 = ?, "; + $sql .= "address_line_2 = ?, "; + $sql .= "city = ?, "; + $sql .= "state = ?, "; + $sql .= "zipcode = ?,"; + $sql .= "country_code = ?, "; + $sql .= "international = ?, "; + $sql .= "pharmacy_phone = ?, "; + $sql .= "on_weno = ?, "; + $sql .= "test_pharmacy = ?, "; + $sql .= "state_wide_mail_order = ?, "; + $sql .= "24hr = ? "; try { sqlStatementNoLog($sql, [ $insertdata['ncpdp'], @@ -160,13 +159,29 @@ public function insertPharmacies($insertdata) $insertdata['test_pharmacy'], $insertdata['state_wide_mail'] ?? '', $insertdata['fullDay'], + $insertdata['npi'], + $insertdata['business_name'], + $insertdata['address_line_1'], + $insertdata['address_line_2'], + $insertdata['city'], + $insertdata['state'], + $insertdata['zipcode'], + $insertdata['country'], + $insertdata['international'], + $insertdata['pharmacy_phone'], + $insertdata['on_weno'], + $insertdata['test_pharmacy'], + $insertdata['state_wide_mail'] ?? '', + $insertdata['fullDay'], ]); } catch (Exception $e) { - return $e->getMessage(); + $e = $e->getMessage(); + return true; } + return false; } - public function updatePharmacies($insertdata) + public function updatePharmacies($insertdata): bool { $sql = "UPDATE weno_pharmacy SET "; $sql .= "npi = ?, "; @@ -202,8 +217,10 @@ public function updatePharmacies($insertdata) $insertdata['ncpdp'] ]); } catch (Exception $e) { - return $e->getMessage(); + $e = $e->getMessage(); + return true; } + return false; } public function removeWenoPharmacies() diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/TransmitProperties.php b/interface/modules/custom_modules/oe-module-weno/src/Services/TransmitProperties.php index 5a18289eea7..3708daa007a 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Services/TransmitProperties.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/TransmitProperties.php @@ -196,9 +196,11 @@ public function createJsonObject(): false|string $wenObj['PrimaryPhone'] = $phonePrimary; $wenObj['SupportsSMS'] = 'Y'; - $wenObj['PatientHeight'] = substr($this->vitals['height'] ?? '', 0, -3); - $wenObj['PatientWeight'] = substr($this->vitals['weight'] ?? '', 0, -3); - $wenObj['HeightWeightObservationDate'] = $heightDate[0]; + if ($age < 19) { + $wenObj['PatientHeight'] = substr($this->vitals['height'] ?? '', 0, -3); + $wenObj['PatientWeight'] = substr($this->vitals['weight'] ?? '', 0, -3); + $wenObj['HeightWeightObservationDate'] = $heightDate[0]; + } $wenObj["ResponsiblePartySameAsPatient"] = $age < 19 ? 'N' : 'Y'; if ($age < 19 && !empty($this->responsibleParty)) { $wenObj['ResponsiblePartyLastName'] = $this->responsibleParty['ResponsiblePartyLastName']; @@ -299,7 +301,7 @@ public function getProviderEmail(): array|string */ public function getFacilityInfo(): array|null|false { - // is user logged into facility + // is user logged into a facility if (!empty($_SESSION['facilityId'])) { $locId = sqlQuery("select name, street, city, state, postal_code, phone, fax, weno_id from facility where id = ?", [$_SESSION['facilityId'] ?? null]); } else { @@ -308,15 +310,23 @@ public function getFacilityInfo(): array|null|false $locId = $facilityService->getFacilityForUser($_SESSION['authUserID']); } - if (empty($locId['weno_id'])) { - //if not in an encounter then get the first facility location id as default - $default_facility = sqlQuery("SELECT name, street, city, state, postal_code, phone, fax, weno_id from facility order by id limit 1"); - + if (empty($locId['weno_id'] ?? '')) { + if (!empty($locId['id'])) { + // weno_id is not set in service so at least we have their facility id + // so we'll look if it's set there anyway. Bottom line is get users default facility. + $default_facility = sqlQuery("SELECT name, street, city, state, postal_code, phone, fax, weno_id from facility where `id` = ? limit 1", [$locId['id']]); + } + if (empty($default_facility['weno_id'] ?? '')) { + //if no default for user then get the first facility location id as default + $default_facility = sqlQuery("SELECT name, street, city, state, postal_code, phone, fax, weno_id from facility order by id limit 1"); + } if (empty($default_facility['weno_id'])) { - $default_facility['error'] = "REQED:{weno_manage}" . xlt('Facility ID is missing. From Admin select Other then Weno Management. Enter the Weno ID of your facility'); + // still no joy so let user know and get it set! + $default_facility['error'] = "REQED:{weno_manage}" . xlt('Facility ID is missing. From Admin select Weno eRx Tools then Weno eRx Service Setup. Enter the Weno ID of your facility'); } return $default_facility; } + return $locId; } @@ -429,9 +439,18 @@ public function getVitals(): ?array { $vitals = sqlQuery("SELECT date, height, weight FROM form_vitals WHERE pid = ? ORDER BY id DESC", [$_SESSION["pid"] ?? null]); // Check if vitals are empty or missing height and weight - if (empty($vitals) || ($vitals['height'] <= 0) || ($vitals['weight'] <= 0)) { - return [ - "REQED:{vitals}" . xlt("A Vitals Height and Weight are required to transmit a prescription. Create or add Vitals in an encounter.") + $patient = $this->getPatientInfo(); + if (self::getAge($patient['dob']) < 19) { + if (empty($vitals) || ($vitals['height'] <= 0) || ($vitals['weight'] <= 0)) { + return [ + "REQED:{vitals}" . xlt("Vitals Height and Weight required for patient under 19 yo. Create or add Vitals in an encounter.") + ]; + } + } elseif (empty($vitals)) { + $vitals = [ + "date" => date('Y-m-d H:i:s'), + "height" => 0, + "weight" => 0 ]; } return $vitals; @@ -509,19 +528,19 @@ public function getWenoProviderId($id = null): mixed if (empty($id)) { $id = $_SESSION['authUserID'] ?? ''; } - // get the weno provider id from the user table (weno_prov_id + // get the weno provider id from the user table (weno_prov_id) $provider = sqlQuery("SELECT weno_prov_id FROM users WHERE id = ?", [$id]); if (!empty(trim($provider['weno_prov_id'] ?? ''))) { $doIt = ($GLOBALS['weno_provider_uid'] ?? '') != trim($provider['weno_prov_id']); if ($doIt) { $GLOBALS['weno_provider_uid'] = trim($provider['weno_prov_id']); $sql = "UPDATE `user_settings` SET `setting_value` = ? WHERE `setting_user` = ? AND `setting_label` = 'global:weno_provider_uid'"; - //sqlQuery($sql, [$GLOBALS['weno_provider_uid'], $_SESSION['authUserID']]); + sqlQuery($sql, [$GLOBALS['weno_provider_uid'], $_SESSION['authUserID']]); } return $provider['weno_prov_id']; } elseif (!empty($GLOBALS['weno_provider_uid'])) { // if not in user table then check globals - // update user table with weno provider id - //sqlQuery("UPDATE `users` SET `weno_prov_id` = ? WHERE `id` = ? OR `weno_prov_id` = ?", [$GLOBALS['weno_provider_uid'], $id, $id]); + //update user table with weno provider id + sqlQuery("UPDATE `users` SET `weno_prov_id` = ? WHERE `id` = ? OR `weno_prov_id` = ?", [$GLOBALS['weno_provider_uid'], $id, $id]); return $GLOBALS['weno_provider_uid']; } else { return "REQED:{users}" . xlt("Weno Provider Id missing. Select Admin then Users and edit the user to add Weno Provider Id"); diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/WenoLogService.php b/interface/modules/custom_modules/oe-module-weno/src/Services/WenoLogService.php index 7219807c041..e0be198ee5e 100644 --- a/interface/modules/custom_modules/oe-module-weno/src/Services/WenoLogService.php +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/WenoLogService.php @@ -32,7 +32,7 @@ public function getLastPrescriptionLogStatus(): bool|array|null public function getLastPharmacyDownloadStatus(): bool|array|null { $params = "pharmacy"; - $v = ['count' => 0, 'created_at' => '', 'status' => '']; + $v = ['count' => 0, 'created_at' => '', 'status' => 'Unknown']; $vsql = sqlQuery("SELECT * FROM `weno_download_log` WHERE `value` = ? ORDER BY `created_at` DESC LIMIT 1", [$params]); if (!$vsql) { return $v; @@ -81,8 +81,8 @@ public function scrapeWenoErrorHtml($content) $message .= $node->nodeValue; } $type = 'other'; - if (stripos($message, "loginfail") !== false) { - $type = "loginfail"; + if (stripos($message, "Exceeded_download_limits") !== false) { + $type = "Exceeded_download_limits"; } return ['is_error' => true, 'type' => $type, 'messageText' => trim($message), 'messageHtml' => trim($content_html)]; } diff --git a/interface/modules/custom_modules/oe-module-weno/src/Services/WenoValidate.php b/interface/modules/custom_modules/oe-module-weno/src/Services/WenoValidate.php new file mode 100644 index 00000000000..d5fca95c54b --- /dev/null +++ b/interface/modules/custom_modules/oe-module-weno/src/Services/WenoValidate.php @@ -0,0 +1,264 @@ +generateMessageID(); + $this->setMessageProperties(); + $this->client = new Client(); + } + + /** + * @return void + */ + private function generateMessageID(): void + { + $random_string = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 5); + $timestamp = time(); + $this->messageID = substr($timestamp . $random_string, 0, 10); + } + + /** + * @return void + */ + private function setMessageProperties(): void + { + $settings = $this->getVendorGlobals(); + $this->md5UserPassword = md5($settings['weno_admin_password'] ?? ''); + $this->userEmail = $settings['weno_admin_username'] ?? ''; + $this->encryptionKey = $settings['weno_encryption_key'] ?: 'Missing'; + } + + /** + * @param $key + * @return void + */ + public function setNewEncryptionKey($key): void + { + $gbl = $this->getVendorGlobals(); + $gbl['weno_encryption_key'] = $key; + // save the new key to the database. + // save will also set the global to stay current. + $this->saveVendorGlobals($gbl); + error_log('A new encryption key ' . $key . ' was created and saved: ' . date('Y-d-m H:i:s', time())); + $wenoLog = new WenoLogService(); + $wenoLog->insertWenoLog("new_encryption_key", "saved new key value " . $key); + } + + /** + * @return string + */ + private function buildVerifyEncryptionKey(): string + { + $this->setMessageProperties(); + return " + +
+ {$this->messageID} + " . date('Y-m-d\TH:i:s.v') . " + + {$this->userEmail} + {$this->md5UserPassword} + + + Administrator + OpenEMR Weno EZ eRx + 7.0.2(1) + +
+ + {$this->encryptionKey} + +
"; + } + + /** + * @return string + */ + private function buildResetEncryptionKey(): string + { + $this->setMessageProperties(); + return " + +
+ {$this->messageID} + " . date('Y-m-d\TH:i:s.v') . " + + {$this->userEmail} + {$this->md5UserPassword} + + + Administrator + OpenEMR Weno EZ eRx + 7.0.2(1) + +
+ + True + +
"; + } + + /** + * @param $code + * @param string $desc + * @return void + */ + private function handleValidationFailure($code, string $desc = 'invalid'): void + { + error_log($desc . ': ' . date('Y-d-m H:i:s', time())); + $wenoLog = new WenoLogService(); + $wenoLog->insertWenoLog($code, $desc); + } + + /** + * @param $response + * @return string|bool + */ + private function extractValidationResult($response): string|bool + { + if (isset($response['Body']['Error'])) { + return $response['Body']['Error']['Description'] ?? '0' . ' ' . 'error.'; + } + return $response['Body']['Success']['EncryptionKeyValid'] ?? '0'; + } + + /** + * @return false|string + */ + public function requestEncryptionKeyReset(): false|int|string + { + $payload = $this->buildResetEncryptionKey(); + try { + $response = $this->sendRequest($payload); + // check for wire error + if (is_string($response) && (stripos($response, 'connection_problem_') !== false)) { + $this->handleValidationFailure('reset_encryption_key', $response); + if (stripos($response, 'notconnected') !== false) { + return 999; + } + return 998; + } + if (isset($response['Body']['Error'])) { + $this->handleValidationFailure('reset_encryption_key', $response['Body']['Error']['Description'] ?? 'reset_failed'); + return false; + } + + $newKey = $response['Body']['Success']['NewEncryptionKey'] ?? ''; + return ($response !== false && !empty($newKey)) ? trim($newKey) : false; + } catch (GuzzleException $e) { + // Handle Guzzle Exception + return false; + } + } + + /** + * @return bool|int|string + */ + public function verifyEncryptionKey(): bool|int|string + { + $payload = $this->buildVerifyEncryptionKey(); + try { + $response = $this->sendRequest($payload); + // check for wire error + if (is_string($response) && (stripos($response, 'connection_problem_') !== false)) { + $this->handleValidationFailure('verify_encryption_key', $response); + if (stripos($response, 'notconnected') !== false) { + return 999; + } + return 998; + } + + $code = $response['Body']['Error']['Code'] ?? ''; + if ($response === false) { + $this->handleValidationFailure('verify_encryption_key', 'empty_response'); + return false; + } + // extract the result + $valid = $this->extractValidationResult($response); + // check for valid response + if ($valid === false) { + $this->handleValidationFailure('verify_encryption_key', 'invalid_response'); + return false; + } elseif (stripos($valid, 'ERROR') !== false || $code !== '') { + $this->handleValidationFailure('verify_encryption_key', $valid); + return (int)$code; + } else { + $valid = (strtolower($valid) === 'true') || ($valid == '1') && !empty($valid); + } + return $valid; + } catch (\Exception $e) { + return false; + } + } + + /** + * @param $payload + * @return array|false + */ + private function sendRequest($payload): false|array|string + { + try { + $response = $this->client->post($this->requestUrl, [ + 'body' => $payload, + 'headers' => [ + 'Content-Type' => 'text/xml', + ], + ]); + $httpCode = $response->getStatusCode(); + + if ($httpCode >= 200 && $httpCode < 300) { + $result = $response->getBody()->getContents(); + // more escaping hell! + $xmlContent = html_entity_decode($result); + $xmlContent = preg_replace('/]*>/', '', $xmlContent); + $xmlContent = preg_replace('/<\/string>/', '', $xmlContent); + $result = simplexml_load_string($xmlContent, 'SimpleXMLElement', LIBXML_NOCDATA); + $result = json_decode(json_encode($result), true); // make associative array. + return $result ?: []; + } else { + error_log("invalid_http_status_$httpCode"); + return "connection_problem_$httpCode"; + } + } catch (GuzzleException $e) { + error_log($e->getMessage()); + return 'connection_problem_notconnected'; + } + } + + /** + * @param $resetOnInvalid + * @return bool + */ + public function validateAdminCredentials($resetOnInvalid = false): bool + { + $newKey = ''; + $isKeyValid = $this->verifyEncryptionKey(); + if ($isKeyValid >= 998) { + return $isKeyValid; + } + if (!$isKeyValid && $resetOnInvalid) { + $newKey = $this->requestEncryptionKeyReset(); + if (!empty($newKey)) { + // save new admin production key. + $this->setNewEncryptionKey($newKey); + } + } + // return new key or encrypted key status (default). + return !empty($newKey) ? trim($newKey) : $isKeyValid; + } +} diff --git a/interface/modules/custom_modules/oe-module-weno/sql/table.sql b/interface/modules/custom_modules/oe-module-weno/table.sql similarity index 93% rename from interface/modules/custom_modules/oe-module-weno/sql/table.sql rename to interface/modules/custom_modules/oe-module-weno/table.sql index 0c9bdf59a8e..6106b63d6d9 100644 --- a/interface/modules/custom_modules/oe-module-weno/sql/table.sql +++ b/interface/modules/custom_modules/oe-module-weno/table.sql @@ -54,8 +54,8 @@ CREATE TABLE `weno_assigned_pharmacy` ( #IfNotTable weno_download_log CREATE TABLE `weno_download_log` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, - `value` VARCHAR(12) NOT NULL, - `status` VARCHAR(10) NOT NULL, + `value` VARCHAR(63) NOT NULL, + `status` VARCHAR(255) NOT NULL, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `value` (`value`) @@ -85,3 +85,6 @@ UPDATE `globals` SET gl_name='weno_admin_password' WHERE gl_name='weno_provider_ UPDATE `globals` SET gl_name='weno_admin_username' WHERE gl_name='weno_provider_username'; #EndIf +#IfNotColumnType weno_download_log status varchar(255) +ALTER TABLE `weno_download_log` CHANGE `value` `value` VARCHAR(63) NOT NULL, CHANGE `status` `status` VARCHAR(255) NOT NULL; +#EndIf diff --git a/interface/modules/custom_modules/oe-module-weno/templates/download_log_viewer.php b/interface/modules/custom_modules/oe-module-weno/templates/download_log_viewer.php new file mode 100644 index 00000000000..5da04279f24 --- /dev/null +++ b/interface/modules/custom_modules/oe-module-weno/templates/download_log_viewer.php @@ -0,0 +1,263 @@ +getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Must be an Admin")]); + exit; +} + +$logService = new WenoLogService(); +$pres_log = $logService->getLastPrescriptionLogStatus(); +$pharm_log = $logService->getLastPharmacyDownloadStatus(); + +$startDate = $_GET['startDate'] ?? date('m/d/Y'); // just default to today +$endDate = $_GET['endDate'] ?? date('m/d/Y'); +?> + + + + + + <?php echo xlt('Weno Downloads'); ?> + + + + + + +
+

+
+
+

+
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
#
1 + +
2 + +
+
+
+

+ + + +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ $fmtEndDate) { + echo ''; + exit; + } + } + if (isset($_GET['delete'])) { + if ($startDate == date('m/d/Y')) { + echo ''; + exit; + } + if ($endDate == date('m/d/Y')) { + $fmtEndDate = date('m/d/Y', strtotime('-1 day')); // only up till yesterday + } + // keep pharmacy and error history. Only delete prescription history as only concerned with current status. + // TODO possibly allow cleaning of all logs separate button. + $sql = "DELETE FROM `weno_download_log` WHERE `created_at` BETWEEN ? AND ? AND `value` = 'prescription'"; + sqlStatement($sql, [$fmtStartDate . ' 00:00:00', $fmtEndDate . ' 23:59:59']); + $message = ''; + echo $message; + } + if (isset($_GET['search']) || isset($_GET['delete'])) { + $sql = "SELECT `id`, `value`, `status`, `created_at` FROM `weno_download_log` WHERE `created_at` BETWEEN ? AND ? ORDER BY `created_at` DESC"; + $result = sqlStatement($sql, [$fmtStartDate . ' 00:00:00', $fmtEndDate . ' 23:59:59']); + // Display logs in a table + if ($result ?? false) { + echo '
'; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + while ($row = sqlFetchArray($result)) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + echo '
' . xlt("ID") . '' . xlt("Value") . '' . xlt("Status") . '' . xlt("Created At") . '
' . text($row['id']) . '' . text($row['value']) . '' . text($row['status']) . '' . text($row['created_at']) . '
'; + echo '
'; + } else { + echo ''; + } + } + unset($_GET['delete']); + ?> +
+ + + + diff --git a/interface/modules/custom_modules/oe-module-weno/templates/facilities.php b/interface/modules/custom_modules/oe-module-weno/templates/facilities.php deleted file mode 100644 index 3e791e941ab..00000000000 --- a/interface/modules/custom_modules/oe-module-weno/templates/facilities.php +++ /dev/null @@ -1,235 +0,0 @@ - - * @author Kofi Appiah - * @author Jerry Padgett - * @copyright Copyright (c) 2020 Sherwin Gaddis - * @copyright Copyright (c) 2023 omega systems group international - * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 - */ - -require_once(dirname(__DIR__, 4) . "/globals.php"); - -use OpenEMR\Common\Acl\AclMain; -use OpenEMR\Common\Csrf\CsrfUtils; -use OpenEMR\Common\Twig\TwigContainer; -use OpenEMR\Core\Header; - -use OpenEMR\Modules\WenoModule\Services\FacilityProperties; -use OpenEMR\Modules\WenoModule\Services\WenoLogService; - -//ensure user has proper access -if (!AclMain::aclCheckCore('admin', 'super')) { - echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Weno Admin")]); - exit; -} - -$data = new FacilityProperties(); -$logService = new WenoLogService(); - -if ($_POST) { - if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token"])) { - CsrfUtils::csrfNotVerified(); - } - $data->facilityupdates = $_POST; - $data->updateFacilityNumber(); -} - -$facilities = $data->getFacilities(); -$pres_log = $logService->getLastPrescriptionLogStatus(); -$pharm_log = $logService->getLastPharmacyDownloadStatus(); - -?> - - - <?php echo xlt('Weno Admin'); ?> - - - - - - -
- - -
-
-


-

-
- - - - - - - - - - - "; - print ""; - print ""; - print ""; - ++$i; - } - ?> -
" . text($facility["name"]) . "" . text($facility['street']) - . "" . text($facility['city']) . "
- -
-
-
-

-
- -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
#
1 - -
2 - -
-
-
- - diff --git a/interface/modules/custom_modules/oe-module-weno/templates/indexrx.php b/interface/modules/custom_modules/oe-module-weno/templates/indexrx.php index 46651856ef4..df28c0c3e86 100644 --- a/interface/modules/custom_modules/oe-module-weno/templates/indexrx.php +++ b/interface/modules/custom_modules/oe-module-weno/templates/indexrx.php @@ -20,70 +20,138 @@ use OpenEMR\Core\Header; use OpenEMR\Modules\WenoModule\Services\PharmacyService; use OpenEMR\Modules\WenoModule\Services\TransmitProperties; +use OpenEMR\Modules\WenoModule\Services\WenoValidate; -//ensure user has proper access +//ensure user has proper access permissions. if (!AclMain::aclCheckCore('patients', 'rx')) { echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Weno eRx")]); exit; } +// Let's see if letting user decide to reset fly's! +// We really don't need because we can do transparently but Weno requested so... +$wenoValidate = new WenoValidate(); +if (isset($_GET['form_reset_key'])) { + unset($_GET['form_reset_key']); + // if we are here then we need to reset the key. + $newKey = $wenoValidate->requestEncryptionKeyReset(); + $wenoValidate->setNewEncryptionKey($newKey); + // Redirect to the same page to refresh the page with the new key. + $isValidKey = true; +} else { + // Validate if the user has a valid encryption key. + // If not, show a reset button below. + // This is a manual process for now. + $isValidKey = $wenoValidate->verifyEncryptionKey(); +} +/* +// We can automate! If the key is not valid, request a new key and set it +// for the user. This will be transparent to the user. +// This is easier, but for now we want to alert user by showing button. +// Clicking will do the same as the below function. + $isKey = $wenoValidate->validateAdminCredentials(true); +*/ + +// set up the dependencies for the page. $pharmacyService = new PharmacyService(); +$wenoProperties = new TransmitProperties(); $primary_pharmacy = $pharmacyService->getWenoPrimaryPharm($_SESSION['pid']) ?? []; $alt_pharmacy = $pharmacyService->getWenoAlternatePharm($_SESSION['pid']) ?? []; -$wenoProperties = new TransmitProperties(); $provider_info = $wenoProperties->getProviderEmail(); -$urlParam = $wenoProperties->cipherPayload(); //lets encrypt the data +$urlParam = $wenoProperties->cipherPayload(); $vitals = $wenoProperties->getVitals(); $provider_name = $wenoProperties->getProviderName(); $patient_name = $wenoProperties->getPatientName(); $facility_name = $wenoProperties->getFacilityInfo(); +//set the url for the iframe $newRxUrl = "https://online.wenoexchange.com/en/NewRx/ComposeRx?useremail="; if ($urlParam == 'error') { //check to make sure there were no errors echo TransmitProperties::styleErrors(xlt("Cipher failure check encryption key")); exit; } ?> - - - <?php echo xlt('Weno eRx') ?> + + -
-
-

- "> - -

-
-
-
+
+
+
+
+

+ "> + + + +

+
+
+
-
:
+
:
:
@@ -94,25 +162,28 @@
-
- - - - - - - - - - - - - -
:
-
+ + 0 && $vitals['weight'] > 0) { ?> +
+ + + + + + + + + + + + + +
:
+
+
-
:
-
:
+
:
+
:
diff --git a/interface/modules/custom_modules/oe-module-weno/templates/pharmacy_list_form.php b/interface/modules/custom_modules/oe-module-weno/templates/pharmacy_list_form.php index 3a0f067d80f..768e297ce38 100644 --- a/interface/modules/custom_modules/oe-module-weno/templates/pharmacy_list_form.php +++ b/interface/modules/custom_modules/oe-module-weno/templates/pharmacy_list_form.php @@ -222,40 +222,43 @@ function init(prevPrimPharmacy, prevAltPharmacy) { function pharmSelChanged() { const e = document.getElementById("weno_pharmacy"); - this.wenoValChanged = e.options[e.selectedIndex].value; - this.wenoPrimPharm = e.options[e.selectedIndex].text; + this.wenoValChanged = e ? e.options[e.selectedIndex].value : ''; + this.wenoPrimPharm = e ? e.options[e.selectedIndex].text : ''; } function zipChanged() { - var wenoZip = document.getElementById('weno_zipcode').selectedOptions[0].value; - this.wenoZipCode = wenoZip; + var wenoZip = document.getElementById('weno_zipcode').value; + this.wenoZipCode = wenoZip ? wenoZip : ''; + + $('#weno_zipcode').removeClass("is-invalid"); + $('.warn').text(''); } function stateChanged() { - var wenoState = document.getElementById('weno_state').selectedOptions[0].value; - this.wenoState = wenoState; + var wenoState = document.getElementById('weno_state').selectedOptions[0]; + this.wenoState = wenoState ? wenoState.value : ''; } function cityChanged() { - var wenoCity = document.getElementById('weno_city').selectedOptions[0].value; - this.wenoCity = wenoCity; + var wenoCity = document.getElementById('weno_city').selectedOptions[0]; + this.wenoCity = wenoCity ? wenoCity.value : ''; } function onWenoChanged(cb) { - this.wenoOnly = cb.checked; + this.wenoOnly = cb ? cb.checked : false; } function coverageChanged() { - var coverage = document.getElementById('weno_coverage').selectedOptions[0].value; - this.coverage = coverage; + var coverage = document.getElementById('weno_coverage').selectedOptions[0]; + this.coverage = coverage ? coverage.value : ''; } function fullDayChanged(cb) { - this.fullDay = cb.checked; + this.fullDay = cb ? cb.checked : false; } function testPharmaciesChanged(cb) { - this.testPharmacies = cb.checked; + this.testPharmacies = cb ? cb.checked : false; } function doAjax() { @@ -358,7 +361,7 @@ function search() { const isValidCityAndState = wenoCity && wenoState && !wenoZipcode; if (isValidZipcode || isValidCityAndState) { - $('#weno_city, #weno_state, #weno_coverage').removeClass("is-invalid"); + $('#weno_city, #weno_state, #weno_coverage, #weno_zipcode').removeClass("is-invalid"); $('.warn').text(''); if ($('#weno_pharmacy').hasClass('select2-hidden-accessible')) { @@ -435,11 +438,11 @@ function assignAlternatePharmacy() { } function resetForm() { - var searchbox = document.getElementById("weno_state"); + const searchbox = document.getElementById("weno_state"); searchbox.selectedIndex = 0; document.getElementById('weno_state').selectedOptions[0].value = ''; - document.getElementById('weno_zipcode').selectedOptions.value = ''; + document.getElementById('weno_zipcode').value = ''; $('#weno_primary').text(''); $('#weno_primary').val(''); $('#primary_pharmacy').val(); diff --git a/interface/modules/custom_modules/oe-module-weno/templates/setup_facilities.php b/interface/modules/custom_modules/oe-module-weno/templates/setup_facilities.php new file mode 100644 index 00000000000..bf3f52c3a75 --- /dev/null +++ b/interface/modules/custom_modules/oe-module-weno/templates/setup_facilities.php @@ -0,0 +1,92 @@ + + * @copyright Copyright (c) 2024 Jerry Padgett + * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 + */ + +namespace OpenEMR\Modules\WenoModule\Services; + +require_once(dirname(__DIR__, 4) . "/globals.php"); + +use OpenEMR\Common\Acl\AclMain; +use OpenEMR\Common\Csrf\CsrfUtils; +use OpenEMR\Common\Twig\TwigContainer; +use OpenEMR\Core\Header; + +//ensure user has proper access +if (!AclMain::aclCheckCore('admin', 'super')) { + echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Weno Admin")]); + exit; +} + +if ($_POST) { + if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token"])) { + CsrfUtils::csrfNotVerified(); + } + unset($_POST['csrf_token']); + foreach ($_POST as $location) { + sqlQuery("update facility set weno_id = ? where id = ?", [$location[1], $location[0]]); + } +} + +$list = sqlStatement("SELECT id, name, street, city, weno_id FROM facility"); +$facilities = []; +while ($row = sqlFetchArray($list)) { + $facilities[] = $row; +} + +?> + + + + <?php echo xlt('Facility IDs'); ?> + + + + +
+
+
+
+ + + + + + + + + + + + "; + print ""; + print ""; + print ""; + ++$i; + } + ?> +
" . text($facility["name"]) . "" . text($facility['street']) + . "" . text($facility['city']) . "
+ +
+
+
+ + diff --git a/interface/modules/custom_modules/oe-module-weno/templates/weno_fragment.php b/interface/modules/custom_modules/oe-module-weno/templates/weno_fragment.php index 47825fdebb3..04f178982c9 100644 --- a/interface/modules/custom_modules/oe-module-weno/templates/weno_fragment.php +++ b/interface/modules/custom_modules/oe-module-weno/templates/weno_fragment.php @@ -21,16 +21,14 @@ exit; } -$logService = new WenoLogService(); -$pharmacy_log = $logService->getLastPharmacyDownloadStatus(); - $validate = new TransmitProperties(true); $validate_errors = ""; $cite = ''; $logService = new WenoLogService(); $pharmacyLog = $logService->getLastPharmacyDownloadStatus(); -$status = xlt("Last pharmacy update failed! Current count") . ": " . text($pharmacyLog['count'] ?? 0) . ". " . "Last success was" . ": " . text($pharmacyLog['created_at'] ?? ''); + +$status = xlt("Last pharmacy update failed! Status") . ": " . text($pharmacyLog['status'] ?? '') . ". " . xlt("Current number of Pharmacies available") . ": " . text($pharmacyLog['count'] ?? 0) . " " . xlt("that is from last successful update on") . ": " . text($pharmacyLog['created_at'] ?? ''); $cite = << $status diff --git a/interface/modules/custom_modules/oe-module-weno/templates/weno_setup.php b/interface/modules/custom_modules/oe-module-weno/templates/weno_setup.php index a893b3a484d..21b85c6b31b 100644 --- a/interface/modules/custom_modules/oe-module-weno/templates/weno_setup.php +++ b/interface/modules/custom_modules/oe-module-weno/templates/weno_setup.php @@ -6,18 +6,24 @@ * @package OpenEMR Module * @link http://www.open-emr.org * @author Jerry Padgett - * @copyright Copyright (c) 2023-24 Jerry Padgett + * @copyright Copyright (c) 2023-2024 Jerry Padgett * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 */ -$sessionAllowWrite = true; require_once(__DIR__ . "/../../../../globals.php"); +use OpenEMR\Common\Acl\AclMain; use OpenEMR\Common\Csrf\CsrfUtils; +use OpenEMR\Common\Twig\TwigContainer; use OpenEMR\Core\Header; use OpenEMR\Modules\WenoModule\Services\ModuleService; +use OpenEMR\Modules\WenoModule\Services\WenoValidate; -$module_config = 1; +if (!AclMain::aclCheckCore('admin', 'super')) { + // renders in MM iFrame + echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Must be an Admin")]); + exit; +} $vendors = []; $vendors['weno_rx_enable'] = ''; @@ -29,31 +35,51 @@ $vendors['weno_secondary_admin_username'] = ''; $vendors['weno_secondary_admin_password'] = ''; +$facilityUrl = $GLOBALS['web_root'] . "/interface/modules/custom_modules/oe-module-weno/templates/setup_facilities.php"; +$usersUrl = $GLOBALS['web_root'] . "/interface/modules/custom_modules/oe-module-weno/templates/weno_users.php"; +$saveAction = false; +$isValidKey = true; $boot = new ModuleService(); +$wenoValidate = new WenoValidate(); if (($_POST['form_save'] ?? null)) { if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) { CsrfUtils::csrfNotVerified(); } unset($_POST['form_save'], $_POST['csrf_token_form']); $boot->saveVendorGlobals($_POST); + $isValidKey = $wenoValidate->verifyEncryptionKey(); + $saveAction = true; +} +if (isset($_REQUEST['form_reset_key'])) { + if (!CsrfUtils::verifyCsrfToken($_POST["csrf_token_form"])) { + CsrfUtils::csrfNotVerified(); + } + unset($_GET['form_reset_key']); + // if we are here then we need to reset the key. + $newKey = $wenoValidate->requestEncryptionKeyReset(); + $wenoValidate->setNewEncryptionKey($newKey); + // Redirect to the same page to refresh the page with the new key. + $isValidKey = true; } $vendors = $boot->getVendorGlobals(); ?> + - ><?php echo xlt("Weno Config") ?> + <?php echo xlt("Weno Config") ?> getVendorGlobals(); } - Header::setupHeader(); ?> @@ -77,11 +136,42 @@

-

+
+
+
+ +
+ + Weno eRx Tools->Weno eRx Service after module is enabled.\nThere are three sections within the Weno eRx Service Admin Setup that allow the user to setup almost all the necessary settings to successfully start e-prescribing. An additional item is each Weno prescriber credentials are set up in their individual User Settings.\n +*** The Weno Primary Admin Section. +- All values must be entered and validated. +- If validation fails because either email and/or password are invalid an alert will be shown stating such. +- If the encryption key is deemed invalid then an alert will show and the Encryption Reset button is enabled. First try re-entering the key but if that doesn't work then clicking the Reset button will create a new key. This change will also be reflected in the Admins main Weno account and no other actions are required by the user. You may look on the key as an API token which may be a more familiar term to the reader.\n +*** The Map Weno User Id`s (Required) Section. +- This section presents a table of all authorised users showing their default facility if assigned and an input field to enter their Weno user id Uxxxx. This value is important in order to form a relationship between Weno and the OpenEMR user for tracking prescriptions. +- All values are automatically saved for the user whenever the Weno Provider ID is entered or changed. +- As a convenience, an edit button is supplied to present a dialog containing the Users settings in edit mode. From here user may edit any setting such as assigning a default facility. This would be the same as accessing Users from top menu Admin->Users selected provider.\n +*** The Map Weno Facility Id`s (Required) Section. +- This section is pretty self explanatory with perhaps noting this same data may be accessed from top menu Admin->Weno eRx Tools->Weno eRx Service. +- It is important to note that the prescribing user should have their default facility set in their User settings. This is the same as the Weno User ID section in that it is required to form a relationship between Weno and the OpenEMR facility for tracking prescriptions. +- This section also auto saves for convenience.")); + ?> +
+
+
+
+ (' . xlt('Required') . ')'; ?> +
+
+
+
@@ -95,69 +185,87 @@ >
-
-
-
-
- -
-
-
-
-
"> - +
"> +
"> - +
-
"> - +
"> + +
+
+ +
+
+ +

- -
+ (' . xlt('Required') . ')'; ?>
+
-
-
- -
"> - +
+
+
+ (' . xlt('Required') . ')'; ?> +
+
-
- -
"> - +
+
+
+
+ (' . xlt('Not Required') . ')'; ?> +
+
-
- -
"> - +
+
+ +
"> + +
-
-
-
-
- - +
+ +
"> + +
+
+
+ +
"> + +
+
+
+
+
+ + +
@@ -165,27 +273,6 @@
-
- -
- - ' . text('Additional values from Weno for finishing setup are:') . '' . - text('1. Weno Provider Id: Uxxxx') . '
' . - text('2. Assigned Location Id for all the facilities used by the above User Id: Lxxxxx') . '
' . - text('3. The provider credentials assign to each prescriber: username(email address) and password.') . '

' . - '
' . text('To continue setup follow the below steps.') . '
' . - text('1. Find top menu Admin->Users and select the user associated with the Weno Provider ID Uxxx and enter and save the assigned ID in the Weno Provider ID field.') . '
' . - text('2. Find top menu Admin->Other->Weno Management and enter the assigned Location Id Lxxxxx for the location facilities.') . '
' . - text('3. Find top Patient Bar User icon and click Settings. Scroll down or find the Weno button in left sidebar and click. Enter your email and password in the Weno Provider Email and Weno Provider Password fields and Save.') . '

' . - '
' . text('Patient Chart Requirements.') . '
' . - text('1. Each Patient is required to have an assigned primary pharmacy from Demographics->Choices. It is good practice to assign an Alternate Pharmacy too.') . '
' . - text('2. Each Patient is required to have a Vitals Height and Weight assigned. Create or enter in from an encounter vitals form') . '

'; - ?> -

>

-
-