Added Import for Large CSV
This commit is contained in:
+59
-141
@@ -2,7 +2,7 @@
|
||||
|
||||
class Consolidation extends Base {
|
||||
|
||||
// Variables.
|
||||
// Variables
|
||||
|
||||
private $current_user_serial = 0;
|
||||
private $current_user_id = "";
|
||||
@@ -47,6 +47,11 @@ class Consolidation extends Base {
|
||||
|
||||
case "run":
|
||||
|
||||
$XSLParms = array();
|
||||
$permit_record_count = 0;
|
||||
$processed_record_count = 0;
|
||||
$connection = null;
|
||||
|
||||
$sql_insert = "insert into rdiconsolidations
|
||||
|
||||
( rdiconsolidation_permit_facts_id,
|
||||
@@ -86,149 +91,72 @@ class Consolidation extends Base {
|
||||
rdiconsolidation_12mth_prev_permits,
|
||||
rdiconsolidation_date )
|
||||
|
||||
values ( :rdiconsolidation_permit_facts_id,
|
||||
:rdiconsolidation_entry_date_str,
|
||||
:rdiconsolidation_entry_date,
|
||||
:rdiconsolidation_permit_number,
|
||||
:rdiconsolidation_permit_date_str,
|
||||
:rdiconsolidation_permit_date,
|
||||
:rdiconsolidation_project_addr,
|
||||
:rdiconsolidation_sub_div_name,
|
||||
:rdiconsolidation_project_type,
|
||||
:rdiconsolidation_company,
|
||||
:rdiconsolidation_address,
|
||||
:rdiconsolidation_city,
|
||||
:rdiconsolidation_state,
|
||||
:rdiconsolidation_zip,
|
||||
:rdiconsolidation_phone,
|
||||
:rdiconsolidation_project_city,
|
||||
:rdiconsolidation_project_state,
|
||||
:rdiconsolidation_size,
|
||||
:rdiconsolidation_units,
|
||||
:rdiconsolidation_building_type,
|
||||
:rdiconsolidation_value,
|
||||
:rdiconsolidation_work_type,
|
||||
:rdiconsolidation_county,
|
||||
:rdiconsolidation_lot,
|
||||
:rdiconsolidation_dist_ll,
|
||||
:rdiconsolidation_ct1,
|
||||
:rdiconsolidation_owner_name,
|
||||
:rdiconsolidation_owner_address,
|
||||
:rdiconsolidation_owner_city,
|
||||
:rdiconsolidation_owner_state,
|
||||
:rdiconsolidation_owner_zip,
|
||||
:rdiconsolidation_owner_phone,
|
||||
:rdiconsolidation_ytd_permits,
|
||||
:rdiconsolidation_ytd_value,
|
||||
:rdiconsolidation_12mth_prev_permits,
|
||||
:rdiconsolidation_date ) ";
|
||||
select coalesce(permit_permit_facts_id, 0),
|
||||
coalesce(permit_entry_date_str, ''),
|
||||
coalesce(permit_entry_date, '0000-00-00'),
|
||||
coalesce(permit_permit_number, ''),
|
||||
coalesce(permit_permit_date_str, ''),
|
||||
coalesce(permit_permit_date, '0000-00-00'),
|
||||
coalesce(permit_project_addr, ''),
|
||||
coalesce(permit_sub_div_name, ''),
|
||||
coalesce(permit_project_type, ''),
|
||||
coalesce(permit_company, ''),
|
||||
coalesce(permit_address, ''),
|
||||
coalesce(permit_city, ''),
|
||||
coalesce(permit_state, ''),
|
||||
coalesce(permit_zip, ''),
|
||||
coalesce(permit_phone, ''),
|
||||
coalesce(permit_project_city, ''),
|
||||
coalesce(permit_project_state, ''),
|
||||
coalesce(permit_size, 0),
|
||||
coalesce(permit_units, 0),
|
||||
coalesce(permit_building_type, 0),
|
||||
coalesce(permit_value, 0),
|
||||
coalesce(permit_work_type, 0),
|
||||
coalesce(permit_county, ''),
|
||||
coalesce(permit_lot, ''),
|
||||
coalesce(permit_dist_ll, ''),
|
||||
coalesce(permit_ct1, 0),
|
||||
coalesce(permit_owner_name, ''),
|
||||
coalesce(permit_owner_address, ''),
|
||||
coalesce(permit_owner_city, ''),
|
||||
coalesce(permit_owner_state, ''),
|
||||
coalesce(permit_owner_zip, ''),
|
||||
coalesce(permit_owner_phone, ''),
|
||||
coalesce(permit_ytd_permits, 0),
|
||||
coalesce(permit_ytd_value, 0),
|
||||
coalesce(permit_12mth_prev_permits, 0),
|
||||
current_date()
|
||||
from permits";
|
||||
|
||||
try {
|
||||
|
||||
$connection = $this->connect();
|
||||
|
||||
$connection->beginTransaction();
|
||||
$statement = $connection->prepare($sql_insert);
|
||||
|
||||
$rdi_records = $this->getRdiTable();
|
||||
$rdi_record_count = $rdi_records->count();
|
||||
$permit_record_count = (integer) $connection->query("select count(*) from permits")->fetchColumn();
|
||||
$processed_record_count = (integer) $connection->exec($sql_insert);
|
||||
|
||||
$processed_record_count = 0;
|
||||
|
||||
foreach ($rdi_records as $record) {
|
||||
|
||||
$rdiconsolidation_permit_facts_id = (integer) $record->permit_facts_id ?: 0;
|
||||
$rdiconsolidation_entry_date_str = (integer) $record->entry_date_str ?: "";
|
||||
$rdiconsolidation_entry_date = (string) $record->entry_date ?: "0000-00-00";
|
||||
$rdiconsolidation_permit_number = (string) $record->permit_number ?: "";
|
||||
$rdiconsolidation_permit_date_str = (string) $record->permit_date_str ?: "";
|
||||
$rdiconsolidation_permit_date = (string) $record->permit_date ?: "0000-00-00";
|
||||
$rdiconsolidation_project_addr = (string) $record->project_addr ?: "";
|
||||
$rdiconsolidation_sub_div_name = (string) $record->sub_div_name ?: "";
|
||||
$rdiconsolidation_project_type = (string) $record->project_type ?: "";
|
||||
$rdiconsolidation_company = (string) $record->company ?: "";
|
||||
$rdiconsolidation_address = (string) $record->address ?: "";
|
||||
$rdiconsolidation_city = (string) $record->city ?: "";
|
||||
$rdiconsolidation_state = (string) $record->state ?: "";
|
||||
$rdiconsolidation_zip = (string) $record->zip ?: "";
|
||||
$rdiconsolidation_phone = (string) $record->phone ?: "";
|
||||
$rdiconsolidation_project_city = (string) $record->project_city ?: "";
|
||||
$rdiconsolidation_project_state = (string) $record->project_state ?: "";
|
||||
$rdiconsolidation_size = (integer) $record->size ?: 0;
|
||||
$rdiconsolidation_units = (integer) $record->units ?: 0;
|
||||
$rdiconsolidation_building_type = (integer) $record->building_type ?: 0;
|
||||
$rdiconsolidation_value = (integer) $record->value ?: 0;
|
||||
$rdiconsolidation_work_type = (integer) $record->work_type ?: 0;
|
||||
$rdiconsolidation_county = (string) $record->county ?: "";
|
||||
$rdiconsolidation_lot = (string) $record->lot ?: "";
|
||||
$rdiconsolidation_dist_ll = (string) $record->dist_ll ?: "";
|
||||
$rdiconsolidation_ct1 = (integer) $record->ct1 ?: 0;
|
||||
$rdiconsolidation_owner_name = (string) $record->owner_name ?: "";
|
||||
$rdiconsolidation_owner_address = (string) $record->owner_address ?: "";
|
||||
$rdiconsolidation_owner_city = (string) $record->owner_city ?: "";
|
||||
$rdiconsolidation_owner_state = (string) $record->owner_state ?: "";
|
||||
$rdiconsolidation_owner_zip = (string) $record->owner_zip ?: "";
|
||||
$rdiconsolidation_owner_phone = (string) $record->owner_phone ?: "";
|
||||
$rdiconsolidation_ytd_permits = (integer) $record->ytd_permits ?: 0;
|
||||
$rdiconsolidation_ytd_value = (integer) $record->ytd_value ?: 0;
|
||||
$rdiconsolidation_12mth_prev_permits = (integer) $record->{"12mth_prev_permits"} ?: 0;
|
||||
$rdiconsolidation_date = date("Y-m-d");
|
||||
|
||||
$statement->bindValue(":rdiconsolidation_permit_facts_id", $rdiconsolidation_permit_facts_id);
|
||||
$statement->bindValue(":rdiconsolidation_entry_date_str", $rdiconsolidation_entry_date_str);
|
||||
$statement->bindValue(":rdiconsolidation_entry_date", $rdiconsolidation_entry_date);
|
||||
$statement->bindValue(":rdiconsolidation_permit_number", $rdiconsolidation_permit_number);
|
||||
$statement->bindValue(":rdiconsolidation_permit_date_str", $rdiconsolidation_permit_date_str);
|
||||
$statement->bindValue(":rdiconsolidation_permit_date", $rdiconsolidation_permit_date);
|
||||
$statement->bindValue(":rdiconsolidation_project_addr", $rdiconsolidation_project_addr);
|
||||
$statement->bindValue(":rdiconsolidation_sub_div_name", $rdiconsolidation_sub_div_name);
|
||||
$statement->bindValue(":rdiconsolidation_project_type", $rdiconsolidation_project_type);
|
||||
$statement->bindValue(":rdiconsolidation_company", $rdiconsolidation_company);
|
||||
$statement->bindValue(":rdiconsolidation_address", $rdiconsolidation_address);
|
||||
$statement->bindValue(":rdiconsolidation_city", $rdiconsolidation_city);
|
||||
$statement->bindValue(":rdiconsolidation_state", $rdiconsolidation_state);
|
||||
$statement->bindValue(":rdiconsolidation_zip", $rdiconsolidation_zip);
|
||||
$statement->bindValue(":rdiconsolidation_phone", $rdiconsolidation_phone);
|
||||
$statement->bindValue(":rdiconsolidation_project_city", $rdiconsolidation_project_city);
|
||||
$statement->bindValue(":rdiconsolidation_project_state", $rdiconsolidation_project_state);
|
||||
$statement->bindValue(":rdiconsolidation_size", $rdiconsolidation_size);
|
||||
$statement->bindValue(":rdiconsolidation_units", $rdiconsolidation_units);
|
||||
$statement->bindValue(":rdiconsolidation_building_type", $rdiconsolidation_building_type);
|
||||
$statement->bindValue(":rdiconsolidation_value", $rdiconsolidation_value);
|
||||
$statement->bindValue(":rdiconsolidation_work_type", $rdiconsolidation_work_type);
|
||||
$statement->bindValue(":rdiconsolidation_county", $rdiconsolidation_county);
|
||||
$statement->bindValue(":rdiconsolidation_lot", $rdiconsolidation_lot);
|
||||
$statement->bindValue(":rdiconsolidation_dist_ll", $rdiconsolidation_dist_ll);
|
||||
$statement->bindValue(":rdiconsolidation_ct1", $rdiconsolidation_ct1);
|
||||
$statement->bindValue(":rdiconsolidation_owner_name", $rdiconsolidation_owner_name);
|
||||
$statement->bindValue(":rdiconsolidation_owner_address", $rdiconsolidation_owner_address);
|
||||
$statement->bindValue(":rdiconsolidation_owner_city", $rdiconsolidation_owner_city);
|
||||
$statement->bindValue(":rdiconsolidation_owner_state", $rdiconsolidation_owner_state);
|
||||
$statement->bindValue(":rdiconsolidation_owner_zip", $rdiconsolidation_owner_zip);
|
||||
$statement->bindValue(":rdiconsolidation_owner_phone", $rdiconsolidation_owner_phone);
|
||||
$statement->bindValue(":rdiconsolidation_ytd_permits", $rdiconsolidation_ytd_permits);
|
||||
$statement->bindValue(":rdiconsolidation_ytd_value", $rdiconsolidation_ytd_value);
|
||||
$statement->bindValue(":rdiconsolidation_12mth_prev_permits", $rdiconsolidation_12mth_prev_permits);
|
||||
$statement->bindValue(":rdiconsolidation_date", $rdiconsolidation_date);
|
||||
|
||||
if (!$statement->execute()) {
|
||||
|
||||
$connection->rollBack();
|
||||
$this->debug($connection->errorInfo()[2]);
|
||||
if ($processed_record_count !== $permit_record_count) {
|
||||
throw new RuntimeException("Record counts did not match. Rolling back database. Contact Support.");
|
||||
}
|
||||
|
||||
$processed_record_count++;
|
||||
}
|
||||
|
||||
if ($processed_record_count !== $rdi_record_count) {
|
||||
$connection->rollBack();
|
||||
$XSLParms["CONSOLIDATION_ERROR"] = "Record Counts Did not match. Rolling Back Database. Contact Support.";
|
||||
} else {
|
||||
$connection->commit();
|
||||
$this->executeSQL("truncate rdis");
|
||||
$connection->exec("truncate permits");
|
||||
} catch (Exception $e) {
|
||||
|
||||
if (($connection instanceof PDO) && $connection->inTransaction()) {
|
||||
$connection->rollBack();
|
||||
}
|
||||
|
||||
$XSLParms["RDI-RECORD-COUNT"] = $rdi_record_count;
|
||||
$XSLParms["CONSOLIDATION-ERROR"] = $e->getMessage();
|
||||
}
|
||||
|
||||
$XSLParms["RDI-RECORD-COUNT"] = $permit_record_count;
|
||||
$XSLParms["PROCESSED-RECORD-COUNT"] = $processed_record_count;
|
||||
$content = $this->applyXSL("", $this->getXSL("consolidationResults"), $XSLParms);
|
||||
|
||||
$this->displayContent($content);
|
||||
|
||||
break;
|
||||
@@ -239,16 +167,6 @@ class Consolidation extends Base {
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
// Return an RDI Object
|
||||
// ====================
|
||||
|
||||
public function getRdiTable() {
|
||||
|
||||
$SQL = "SELECT * FROM permits";
|
||||
|
||||
return $this->getTable("rdis", $SQL);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
+89
-14
@@ -45,7 +45,11 @@ class Import extends Base {
|
||||
|
||||
case "prompt":
|
||||
|
||||
$content = $this->applyXSL("", $this->getXSL("uploadCSVFile"));
|
||||
$import_type = filter_input(INPUT_POST, "import_type") ?: "incremental";
|
||||
|
||||
$XSLParms['IMPORT-TYPE'] = $import_type;
|
||||
|
||||
$content = $this->applyXSL("", $this->getXSL("uploadCSVFile"), $XSLParms);
|
||||
$this->displayContent($content);
|
||||
|
||||
break;
|
||||
@@ -54,6 +58,7 @@ class Import extends Base {
|
||||
|
||||
$XSLParms['UPLOAD_STATUS'] = "UPLOAD SUCCESSFUL";
|
||||
$environment = (string) $this->getSettings()->Environment ?: "Production";
|
||||
$upload_type = filter_input(INPUT_POST, 'upload_type') ?: "incremental";
|
||||
|
||||
if (empty($environment)) {
|
||||
$this->logError("Invalid Environment ({$environment}). " . __METHOD__);
|
||||
@@ -63,30 +68,57 @@ class Import extends Base {
|
||||
|
||||
// Process the Attachment
|
||||
|
||||
if (!empty($_FILES)) {
|
||||
|
||||
if (!is_dir($environment_directory)) {
|
||||
mkdir($environment_directory);
|
||||
}
|
||||
|
||||
$tempname = $_FILES["csvfile"]["tmp_name"];
|
||||
$filename = $_FILES["csvfile"]["name"];
|
||||
|
||||
if (empty($tempname) or empty($filename)) {
|
||||
if (empty($_FILES["csvfile"])) {
|
||||
$this->displayNotice("No CSV file was uploaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!move_uploaded_file($tempname, "{$environment_directory}/{$filename}")) {
|
||||
$XSLParms['UPLOAD_STATUS'] = "UPLOAD FAILED";
|
||||
if (!is_dir($environment_directory) && !mkdir($environment_directory, 0775, true)) {
|
||||
$this->displayNotice("Unable to create the upload directory. Please contact Support.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($_FILES["csvfile"]["error"] !== UPLOAD_ERR_OK) {
|
||||
$this->displayNotice("The CSV file upload failed. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
$tempname = $_FILES["csvfile"]["tmp_name"];
|
||||
$filename = basename($_FILES["csvfile"]["name"]);
|
||||
|
||||
if (empty($tempname) or empty($filename)) {
|
||||
$this->displayNotice("The uploaded CSV file could not be read. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
$file_directory = "{$environment_directory}/{$filename}";
|
||||
|
||||
$import = $this->parseCSVFile($file_directory);
|
||||
if (!move_uploaded_file($tempname, $file_directory)) {
|
||||
$this->displayNotice("Unable to save the uploaded CSV file. Please contact Support.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($upload_type === 'incremental') {
|
||||
$this->parseCSVFile($file_directory);
|
||||
$content = $this->applyXSL("", $this->getXSL("uploadMessage"), $XSLParms);
|
||||
$this->displayContent($content);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($upload_type === 'full') {
|
||||
|
||||
$successful = $this->createImportRequest($file_directory);
|
||||
|
||||
if (!$successful) {
|
||||
$this->displayNotice("An error occurred while creating the import request. Please contact Support.");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->displayNotice("Your import has been queued. You will receive a notification when it is complete.");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->displayNotice("Invalid upload type. Please contact Support.");
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -176,6 +208,7 @@ class Import extends Base {
|
||||
permit_ytd_permits,
|
||||
permit_ytd_value,
|
||||
permit_12mth_prev_permits )
|
||||
|
||||
VALUES (
|
||||
:permit_permit_facts_id,
|
||||
:permit_entry_date,
|
||||
@@ -255,6 +288,48 @@ class Import extends Base {
|
||||
return true;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Create Import Request
|
||||
// =====================
|
||||
|
||||
public function createImportRequest($filepath = "") {
|
||||
|
||||
if (!$filepath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$SQL = "insert into importjobs
|
||||
|
||||
( importjob_file_path,
|
||||
importjob_notified,
|
||||
importjob_creator,
|
||||
importjob_created )
|
||||
|
||||
VALUES ( :importjob_file_path,
|
||||
:importjob_notified,
|
||||
:importjob_creator,
|
||||
:importjob_created )";
|
||||
|
||||
$statement = $this->connect()->prepare($SQL);
|
||||
|
||||
$statement->bindValue(":importjob_file_path", "../{$filepath}", PDO::PARAM_STR);
|
||||
$statement->bindValue(":importjob_notified", false, PDO::PARAM_BOOL);
|
||||
$statement->bindValue(":importjob_creator", $this->current_user_name, PDO::PARAM_STR);
|
||||
$statement->bindValue(":importjob_created", $this->getTimestamp());
|
||||
|
||||
if (!$statement->execute()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$to = $this->getSettings()->DeveloperEmail;
|
||||
$subject = "CSV Import Jobs Queued";
|
||||
$message = "Import initialized successfully. Processing will start momentarily.";
|
||||
|
||||
(new Email())->sendEmail($to, $subject, $message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Return an Formatted Date
|
||||
// ========================
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
// ======================
|
||||
// Import Large CSV Class
|
||||
// ======================
|
||||
|
||||
class ImportLargeCSV extends Base {
|
||||
|
||||
// Variables
|
||||
|
||||
private $current_user_serial = 0;
|
||||
private $current_user_id = "";
|
||||
private $current_user_name = "";
|
||||
private $current_user_email = "";
|
||||
private $pagination_size = 20;
|
||||
private $pagination_page = 1;
|
||||
|
||||
// =================
|
||||
// Class Constructor
|
||||
// =================
|
||||
|
||||
public function __construct() {
|
||||
|
||||
set_error_handler(array($this, "displayError"));
|
||||
|
||||
date_default_timezone_set(self::TIMEZONE);
|
||||
|
||||
$this->current_user_serial = $_SESSION[self::USER_SERIAL] ?? 0;
|
||||
$this->current_user_id = $_SESSION[self::USER_ID] ?? "";
|
||||
$this->current_user_name = $_SESSION[self::USER_NAME] ?? "";
|
||||
$this->current_user_email = $_SESSION[self::USER_EMAIL] ?? "";
|
||||
|
||||
$this->pagination_page = $_SESSION[self::PAGINATION_PAGE] ?? 1;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Create Import Request
|
||||
// =====================
|
||||
|
||||
public function createImportRequest() {
|
||||
|
||||
$subscription_serial = filter_input(INPUT_POST, "subscription_serial", FILTER_SANITIZE_NUMBER_INT) ?: 0;
|
||||
|
||||
$subscription = (new Subscriptions())->getSubscription($subscription_serial);
|
||||
|
||||
if ($subscription->count() === 0) {
|
||||
$this->logError("Invalid Subscription ({$subscription_serial}): " . __METHOD__);
|
||||
}
|
||||
|
||||
$import_type = filter_input(INPUT_POST, "import_type") ?: "";
|
||||
$report_type = filter_input(INPUT_POST, "report_type") ?: "";
|
||||
|
||||
$importjob_import_type = str_replace(' ', '', $subscription->record->subscription_full_title) . "$import_type";
|
||||
$importjob_file_name = str_replace(' ', '_', $subscription->record->subscription_full_title) . "_all_records.{$report_type}";
|
||||
// $importjob_file_name = str_replace('&', 'And', $subscription->record->subscription_full_title) . "_all_records.{$report_type}";
|
||||
|
||||
$SQL = "insert into importjobs
|
||||
|
||||
( importjob_subscription,
|
||||
importjob_import_type,
|
||||
importjob_file_name,
|
||||
importjob_file_type,
|
||||
importjob_creator,
|
||||
importjob_created )
|
||||
|
||||
VALUES ( :importjob_subscription,
|
||||
:importjob_import_type,
|
||||
:importjob_file_name,
|
||||
:importjob_file_type,
|
||||
:importjob_creator,
|
||||
:importjob_created )";
|
||||
|
||||
$statement = $this->connect()->prepare($SQL);
|
||||
|
||||
$statement->bindValue(":importjob_subscription", $subscription_serial);
|
||||
$statement->bindValue(":importjob_import_type", $importjob_import_type);
|
||||
$statement->bindValue(":importjob_file_name", $importjob_file_name);
|
||||
$statement->bindValue(":importjob_file_type", strtoupper($report_type));
|
||||
$statement->bindValue(":importjob_creator", $this->current_user_serial);
|
||||
$statement->bindValue(":importjob_created", $this->getTimestamp());
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$this->displayNotice("Your import has been queued. You will receive an notification when it is ready.");
|
||||
}
|
||||
|
||||
// ============
|
||||
// User Imports
|
||||
// ============
|
||||
|
||||
public function usersImports() {
|
||||
|
||||
$imports = $this->getUsersImports();
|
||||
$popovers = (new Popovers())->getPopovers();
|
||||
|
||||
$this->markImportsAsSeen();
|
||||
|
||||
$XML = $this->mergeXML($imports, "<XML/>");
|
||||
$XML = $this->mergeXML($popovers, $XML);
|
||||
|
||||
$html = $this->applyXSL($XML, $this->getXSL("viewImports"));
|
||||
|
||||
$this->displayContent($html);
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Mark User Imports as Seen
|
||||
// =========================
|
||||
|
||||
public function markImportsAsSeen() {
|
||||
|
||||
$SQL = "update importjobs
|
||||
set importjob_notified = true
|
||||
where importjobs.importjob_creator = {$this->current_user_serial}";
|
||||
|
||||
$statement = $this->connect()->prepare($SQL);
|
||||
|
||||
$statement->execute();
|
||||
}
|
||||
|
||||
// =======================
|
||||
// Return a Imports Object
|
||||
// =======================
|
||||
|
||||
public function getUsersImports() {
|
||||
|
||||
// Find Criteria
|
||||
|
||||
$find_importjob = filter_input(INPUT_POST, "find_importjob") ?: "";
|
||||
$find_words = $this->sanitizeString($find_importjob);
|
||||
|
||||
// Show Per Page
|
||||
$this->pagination_size = filter_input(INPUT_POST, "importjobs_per_page", FILTER_SANITIZE_NUMBER_INT) ?: $this->pagination_size;
|
||||
|
||||
// Record Count
|
||||
|
||||
$connection = $this->connect();
|
||||
|
||||
$SQL = "select count(*) as count
|
||||
from view_importjobs
|
||||
where view_importjobs.importjob_creator = {$this->current_user_serial}
|
||||
and view_importjobs.subscription_full_title regexp :subscription_full_title";
|
||||
|
||||
$statement = $connection->prepare($SQL);
|
||||
|
||||
$statement->bindValue(":subscription_full_title", $find_words);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$recordset = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$count = (count($recordset) > 0) ? (integer) $recordset["count"] : (integer) 0;
|
||||
|
||||
// Pagination
|
||||
|
||||
$pagination = filter_input(INPUT_POST, "pagination") ?: "";
|
||||
|
||||
switch ($pagination) {
|
||||
|
||||
case "first" : $this->pagination_page = 1;
|
||||
break;
|
||||
case "previous" : $this->pagination_page = max(($this->pagination_page - 1), 1);
|
||||
break;
|
||||
case "next" : $this->pagination_page = ($this->pagination_page + 1);
|
||||
break;
|
||||
case "last" : $this->pagination_page = ($count / $this->pagination_size);
|
||||
break;
|
||||
}
|
||||
|
||||
$offset = (($this->pagination_page * $this->pagination_size) - $this->pagination_size);
|
||||
$start = ($offset + 1);
|
||||
$end = min($count, (($start + $this->pagination_size) - 1));
|
||||
|
||||
// Data
|
||||
|
||||
$SQL = "select view_importjobs.*
|
||||
from view_importjobs
|
||||
where view_importjobs.importjob_creator = {$this->current_user_serial}
|
||||
and view_importjobs.subscription_full_title regexp :subscription_full_title
|
||||
limit {$this->pagination_size}
|
||||
offset {$offset}";
|
||||
|
||||
$statement = $connection->prepare($SQL);
|
||||
|
||||
$statement->bindValue(":subscription_full_title", $find_words);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$importjobs = $this->getTableXML("importjobs", $statement);
|
||||
|
||||
// Insert Pagination Attributes
|
||||
|
||||
$importjobs->addAttribute("count", $count);
|
||||
$importjobs->addAttribute("start", $start);
|
||||
$importjobs->addAttribute("end", $end);
|
||||
$importjobs->addAttribute("page", $this->pagination_page);
|
||||
|
||||
$_SESSION[self::PAGINATION_PAGE] = $this->pagination_page;
|
||||
|
||||
return $importjobs;
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// Return if users imports are completed
|
||||
// =====================================
|
||||
|
||||
public function checkUsersCompletedImports_AJAX() {
|
||||
|
||||
$SQL = "select count(*) as notify
|
||||
from view_importjobs
|
||||
where view_importjobs.importjob_creator = :current_user_serial
|
||||
and view_importjobs.importjob_status = 'complete'
|
||||
and view_importjobs.importjob_notified = 0";
|
||||
|
||||
$importjobs = $this->getTable("importjobs", $SQL, array('current_user_serial' => $this->current_user_serial));
|
||||
|
||||
// Create the User Object.
|
||||
|
||||
$users_imports = array();
|
||||
|
||||
$users_imports["notify"] = ($importjobs->record->notify == 0) ? false : true;
|
||||
|
||||
if ($_SESSION[self::AJAX]) {
|
||||
echo json_encode($users_imports);
|
||||
} else {
|
||||
return $users_imports;
|
||||
}
|
||||
}
|
||||
|
||||
// ===============
|
||||
// Download Import
|
||||
// ===============
|
||||
|
||||
public function downloadImport() {
|
||||
|
||||
$import_code = filter_input(INPUT_POST, "import_code") ?: "";
|
||||
|
||||
$SQL = "select view_importjobs.*
|
||||
from view_importjobs
|
||||
where view_importjobs.importjob_download_token = :import_code
|
||||
and view_importjobs.importjob_status = 'complete' limit 1";
|
||||
|
||||
$importjob = $this->getTable("importjob", $SQL, array("import_code" => $import_code));
|
||||
|
||||
$file_path = str_replace('../imports/', './imports/', $importjob->record->importjob_file_path);
|
||||
$file_name = $importjob->record->importjob_file_name;
|
||||
|
||||
header('Content-Type: application/octet-stream');
|
||||
header("Content-Disposition: attachment; filename=\"$file_name\"");
|
||||
|
||||
readfile($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,584 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$basePath = realpath(__DIR__ . '/..');
|
||||
require_once $basePath . '/vendor/autoload.php';
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
// =====================
|
||||
// Process_ImportRequest
|
||||
// =====================
|
||||
|
||||
class Process_ImportRequest {
|
||||
|
||||
// Variables
|
||||
|
||||
private $log = "";
|
||||
private $settings = "";
|
||||
private $connection = "";
|
||||
|
||||
// ==================
|
||||
// Class Constructor.
|
||||
// ==================
|
||||
|
||||
public function __construct() {
|
||||
|
||||
$basePath = realpath(__DIR__ . '/..');
|
||||
$logfile = $basePath . "/scripts/importrequests.log";
|
||||
|
||||
// ==========================
|
||||
// Log Rotation (50 MB limit)
|
||||
// ==========================
|
||||
|
||||
$maxLogSize = 50 * 1024 * 1024; // 50 MB
|
||||
|
||||
if (file_exists($logfile) && filesize($logfile) >= $maxLogSize) {
|
||||
|
||||
$rotated = $logfile . '-';
|
||||
rename($logfile, $rotated);
|
||||
}
|
||||
|
||||
$this->log = fopen($logfile, "a");
|
||||
|
||||
if ($this->log === false) {
|
||||
|
||||
error_log("Process_ImportRequest : Unable to open log file");
|
||||
exit();
|
||||
}
|
||||
|
||||
$this->log(str_repeat("*", 50));
|
||||
|
||||
// Get Settings
|
||||
|
||||
$this->settings = new SimpleXMLElement((file_exists($basePath . "/xml/settings.xml") ? file_get_contents($basePath . "/xml/settings.xml") : "<settings/>"));
|
||||
|
||||
if ($this->settings->count() === 0) {
|
||||
|
||||
error_log("Process_ImportRequest : Unable to initialize settings");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Open Database Connections
|
||||
|
||||
$this->connection = $this->connect();
|
||||
}
|
||||
|
||||
// ================================================
|
||||
// Return a New DEC-DR Database Database Connection
|
||||
// ================================================
|
||||
|
||||
public function connect() {
|
||||
|
||||
if ($this->settings->Environment == 'Production') {
|
||||
$host = 'localhost';
|
||||
} else {
|
||||
$host = '192.168.1.190';
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$database = (string) $this->settings->DatabaseName;
|
||||
$user = (string) $this->settings->DatabaseUser;
|
||||
$password = (string) $this->settings->DatabasePassword;
|
||||
|
||||
$connection = new PDO("mysql:host={$host};dbname={$database};charset=utf8", $user, $password);
|
||||
|
||||
$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException | Exception $e) {
|
||||
|
||||
trigger_error($e->getMessage());
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
// =============================
|
||||
// Log a message to the Log File
|
||||
// =============================
|
||||
|
||||
private function log($message = "") {
|
||||
|
||||
fwrite($this->log, date("Y-m-d H:i A") . " : " . "{$message} \n");
|
||||
}
|
||||
|
||||
// ===================
|
||||
// Process Parcel Data
|
||||
// ===================
|
||||
|
||||
public function process() {
|
||||
|
||||
$this->log("Starting All Record Import Job");
|
||||
|
||||
$this->runImportJob();
|
||||
|
||||
$this->purgeImportJobs();
|
||||
|
||||
$this->log("Completed All Record Import Job");
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Run Import Job
|
||||
// ==============
|
||||
|
||||
private function runImportJob() {
|
||||
|
||||
$this->log("Processing Import Job");
|
||||
|
||||
$nextjob = $this->claimNextJob();
|
||||
|
||||
if (!$nextjob) {
|
||||
$this->log("Nothing to do.");
|
||||
return;
|
||||
}
|
||||
|
||||
$importjob_serial = (int) $nextjob['importjob_serial'];
|
||||
$importjob_file_path = (string) $nextjob['importjob_file_path'];
|
||||
|
||||
try {
|
||||
|
||||
$success = $this->parseCSVFile($importjob_file_path);
|
||||
|
||||
$SQL = "UPDATE importjobs
|
||||
SET importjob_status = 'complete',
|
||||
importjob_completed = NOW()
|
||||
WHERE importjob_serial = :importjob_serial ";
|
||||
|
||||
$update = $this->connect()->prepare($SQL);
|
||||
|
||||
$update->execute([':importjob_serial' => $importjob_serial]);
|
||||
} catch (Throwable $e) {
|
||||
|
||||
$SQL = "UPDATE importjobs
|
||||
SET importjob_status = 'failed',
|
||||
importjob_completed = NOW(),
|
||||
importjob_error_message = :importjob_error_message
|
||||
WHERE importjob_serial = :importjob_serial";
|
||||
|
||||
$fail = $this->connect()->prepare($SQL);
|
||||
|
||||
$fail->execute([':importjob_serial' => $importjob_serial, ':importjob_error_message' => $e->getMessage()]);
|
||||
|
||||
// Notify Admins of error
|
||||
$this->sendWebHookAlert("Import Job {$importjob_serial} has failed. Check Error Logs for details.");
|
||||
$this->email_admins("Import Job Failure.", "Import Job {$importjob_serial} has failed. Check Error Logs for details.");
|
||||
}
|
||||
}
|
||||
|
||||
// =================================
|
||||
// Purge Imports Older then 24 Hours
|
||||
// =================================
|
||||
|
||||
private function purgeImportJobs() {
|
||||
|
||||
$this->log("Purging Import Jobs Database Files");
|
||||
|
||||
$this->purgeFiles();
|
||||
}
|
||||
|
||||
// =======================
|
||||
// Claim Next Job in Queue
|
||||
// =======================
|
||||
|
||||
private function claimNextJob() {
|
||||
|
||||
$this->connection->beginTransaction();
|
||||
|
||||
$SQL = "SELECT view_importjobs.*
|
||||
FROM view_importjobs
|
||||
WHERE view_importjobs.importjob_status = 'queued'
|
||||
ORDER BY view_importjobs.importjob_created ASC
|
||||
LIMIT 1 FOR UPDATE";
|
||||
|
||||
$statement = $this->connection->prepare($SQL);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$job = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$job) {
|
||||
$this->connection->commit();
|
||||
return null;
|
||||
}
|
||||
|
||||
$SQL = "UPDATE importjobs
|
||||
SET importjob_status = 'running'
|
||||
WHERE importjob_serial = :importjob_serial";
|
||||
|
||||
$statement = $this->connection->prepare($SQL);
|
||||
|
||||
$statement->execute([':importjob_serial' => (int) $job['importjob_serial']]);
|
||||
|
||||
$this->connection->commit();
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
// ====================
|
||||
// Purges Files on Disk
|
||||
// ====================
|
||||
|
||||
private function purgeFiles() {
|
||||
|
||||
$SQL = "select *
|
||||
from importjobs
|
||||
where importjobs.importjob_created < (NOW() - INTERVAL 24 HOUR)";
|
||||
|
||||
$importfiles = $this->connect()->query($SQL)->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (count($importfiles) == 0) {
|
||||
$this->log("Nothing To Purge");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($importfiles as $files) {
|
||||
|
||||
$filename = $files['importjob_file_path'];
|
||||
|
||||
unlink($filename);
|
||||
}
|
||||
|
||||
$baseDir = './imports';
|
||||
|
||||
foreach (new DirectoryIterator($baseDir) as $item) {
|
||||
|
||||
if ($item->isDot() || !$item->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$folderPath = $item->getPathname();
|
||||
|
||||
// Check if directory is empty
|
||||
$files = scandir($folderPath);
|
||||
|
||||
if ($files !== false && count($files) === 2) {
|
||||
rmdir($folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
$this->log("Purging Import Jobs Database Records");
|
||||
|
||||
$SQL = "delete from importjobs where importjobs.importjob_created < (NOW() - INTERVAL 24 HOUR)";
|
||||
|
||||
$this->connection->query($SQL);
|
||||
}
|
||||
|
||||
// =======================
|
||||
// Format Raw Phone Number
|
||||
// =======================
|
||||
|
||||
public function formatPhoneNumber(string $phonenumber): string {
|
||||
|
||||
// Remove everything except digits
|
||||
$digits = preg_replace('/\D+/', '', $phonenumber);
|
||||
|
||||
// Handle leading country code (US)
|
||||
if (strlen($digits) === 11 && str_starts_with($digits, '1')) {
|
||||
$digits = substr($digits, 1);
|
||||
}
|
||||
|
||||
return substr($digits, 0, 3) . '-' . substr($digits, 3, 3) . '-' . substr($digits, 6, 4);
|
||||
}
|
||||
|
||||
// ==============
|
||||
// Parse CSV File
|
||||
// ==============
|
||||
|
||||
public function parseCSVFile($file) {
|
||||
|
||||
$csvfile = fopen($file, 'r');
|
||||
|
||||
$RecordCount = 0;
|
||||
|
||||
while (($data = fgetcsv($csvfile, 0, ",", '"', '\\')) !== FALSE) {
|
||||
|
||||
$PermitFactsId = $data[1];
|
||||
$EntryDate = $this->fixDate($data[2]);
|
||||
$PermitNumber = $data[4];
|
||||
$PermitDate = $this->fixDate($data[5]);
|
||||
$ProjectAddress = $data[7];
|
||||
$SubdivisionName = $data[8];
|
||||
$ProjectType = $data[9];
|
||||
$Company = substr($data[10], 0, 30);
|
||||
$Address = substr($data[11], 0, 30);
|
||||
$City = $data[12];
|
||||
$State = $data[13];
|
||||
$Zip = $data[14];
|
||||
$Phone = $data[15];
|
||||
$ProjectCity = $data[16];
|
||||
$ProjectState = $data[17];
|
||||
$Size = $data[18] ?: 0;
|
||||
$Units = $data[19];
|
||||
$BuildingType = $data[20];
|
||||
$Value = $data[21] ?: 0;
|
||||
$WorkType = $data[22];
|
||||
$County = $data[23] ?: 0;
|
||||
$Lot = $data[24];
|
||||
$DistrictLL = $data[25];
|
||||
$Ctl = $data[26];
|
||||
$OwnerName = substr($data[27], 0, 30);
|
||||
$OwnerAddress = substr($data[28], 0, 30);
|
||||
$OwnerCity = $data[29];
|
||||
$OwnerState = substr($data[30], 0, 2);
|
||||
$OwnerZip = $data[31];
|
||||
$OwnerPhone = $data[32];
|
||||
$YTDPermits = $data[33] ?: 0;
|
||||
$YTDValue = $data[34] ?: 0;
|
||||
$TwelveMonthPreviousPermits = $data[35] ?: 0;
|
||||
|
||||
$County = $this->getCountiesByName($County)['county_serial'] ?? 0;
|
||||
|
||||
$SQL = "INSERT INTO permits (
|
||||
permit_permit_facts_id,
|
||||
permit_entry_date,
|
||||
permit_permit_number,
|
||||
permit_permit_date,
|
||||
permit_project_addr,
|
||||
permit_sub_div_name,
|
||||
permit_project_type,
|
||||
permit_company,
|
||||
permit_address,
|
||||
permit_city,
|
||||
permit_state,
|
||||
permit_zip,
|
||||
permit_phone,
|
||||
permit_project_city,
|
||||
permit_project_state,
|
||||
permit_size,
|
||||
permit_units,
|
||||
permit_building_type,
|
||||
permit_value,
|
||||
permit_work_type,
|
||||
permit_county,
|
||||
permit_lot,
|
||||
permit_dist_ll,
|
||||
permit_ct1,
|
||||
permit_owner_name,
|
||||
permit_owner_address,
|
||||
permit_owner_city,
|
||||
permit_owner_state,
|
||||
permit_owner_zip,
|
||||
permit_owner_phone,
|
||||
permit_ytd_permits,
|
||||
permit_ytd_value,
|
||||
permit_12mth_prev_permits )
|
||||
|
||||
VALUES (
|
||||
:permit_permit_facts_id,
|
||||
:permit_entry_date,
|
||||
:permit_permit_number,
|
||||
:permit_permit_date,
|
||||
:permit_project_addr,
|
||||
:permit_sub_div_name,
|
||||
:permit_project_type,
|
||||
:permit_company,
|
||||
:permit_address,
|
||||
:permit_city,
|
||||
:permit_state,
|
||||
:permit_zip,
|
||||
:permit_phone,
|
||||
:permit_project_city,
|
||||
:permit_project_state,
|
||||
:permit_size,
|
||||
:permit_units,
|
||||
:permit_building_type,
|
||||
:permit_value,
|
||||
:permit_work_type,
|
||||
:permit_county,
|
||||
:permit_lot,
|
||||
:permit_dist_ll,
|
||||
:permit_ct1,
|
||||
:permit_owner_name,
|
||||
:permit_owner_address,
|
||||
:permit_owner_city,
|
||||
:permit_owner_state,
|
||||
:permit_owner_zip,
|
||||
:permit_owner_phone,
|
||||
:permit_ytd_permits,
|
||||
:permit_ytd_value,
|
||||
:permit_12mth_prev_permits )";
|
||||
|
||||
$statement = $this->connection->prepare($SQL);
|
||||
|
||||
$statement->bindValue(":permit_permit_facts_id", $PermitFactsId);
|
||||
$statement->bindValue(":permit_permit_number", $PermitNumber);
|
||||
$statement->bindValue(":permit_entry_date", $EntryDate);
|
||||
$statement->bindValue(":permit_permit_date", $PermitDate);
|
||||
$statement->bindValue(":permit_project_addr", $ProjectAddress);
|
||||
$statement->bindValue(":permit_sub_div_name", $SubdivisionName);
|
||||
$statement->bindValue(":permit_project_type", $ProjectType);
|
||||
$statement->bindValue(":permit_company", $Company);
|
||||
$statement->bindValue(":permit_address", $Address);
|
||||
$statement->bindValue(":permit_city", $City);
|
||||
$statement->bindValue(":permit_state", $State);
|
||||
$statement->bindValue(":permit_zip", $Zip);
|
||||
$statement->bindValue(":permit_phone", $Phone);
|
||||
$statement->bindValue(":permit_project_city", $ProjectCity);
|
||||
$statement->bindValue(":permit_project_state", $ProjectState);
|
||||
$statement->bindValue(":permit_size", $Size);
|
||||
$statement->bindValue(":permit_units", $Units);
|
||||
$statement->bindValue(":permit_building_type", $BuildingType);
|
||||
$statement->bindValue(":permit_value", $Value);
|
||||
$statement->bindValue(":permit_work_type", $WorkType);
|
||||
$statement->bindValue(":permit_county", $County);
|
||||
$statement->bindValue(":permit_lot", $Lot);
|
||||
$statement->bindValue(":permit_dist_ll", $DistrictLL);
|
||||
$statement->bindValue(":permit_ct1", $Ctl);
|
||||
$statement->bindValue(":permit_owner_name", $OwnerName);
|
||||
$statement->bindValue(":permit_owner_address", $OwnerAddress);
|
||||
$statement->bindValue(":permit_owner_city", $OwnerCity);
|
||||
$statement->bindValue(":permit_owner_state", $OwnerState);
|
||||
$statement->bindValue(":permit_owner_zip", $OwnerZip);
|
||||
$statement->bindValue(":permit_owner_phone", $OwnerPhone);
|
||||
$statement->bindValue(":permit_ytd_permits", $YTDPermits);
|
||||
$statement->bindValue(":permit_ytd_value", $YTDValue);
|
||||
$statement->bindValue(":permit_12mth_prev_permits", $TwelveMonthPreviousPermits);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$RecordCount++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Return a Counties Object
|
||||
// ========================
|
||||
|
||||
public function getCountiesByName($county_name) {
|
||||
|
||||
$SQL = "select view_counties.*
|
||||
from view_counties
|
||||
where view_counties.county_name = '{$county_name}'";
|
||||
|
||||
$statement = $this->connection->prepare($SQL);
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$county_name = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
return $county_name;
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// Send an Alert/Notification
|
||||
// ==========================
|
||||
|
||||
public function sendWebHookAlert($message, $title = "🚨 DEC Application Alert", $alerttype = "1fff00") {
|
||||
|
||||
$webhookUrl = (string) $this->settings->DiscordWebHookURL;
|
||||
|
||||
$data = [
|
||||
'embeds' => [[
|
||||
'title' => $title,
|
||||
'description' => $message,
|
||||
'color' => hexdec($alerttype),
|
||||
'footer' => [
|
||||
'text' => 'Real Estate Data Inc. System Notificiation',
|
||||
],
|
||||
'timestamp' => date('c'), // ISO 8601 format
|
||||
]]
|
||||
];
|
||||
|
||||
$jsonData = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$ch = curl_init($webhookUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if ($response === false) {
|
||||
$this->log('Curl error: ' . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
// ============
|
||||
// Email Admins
|
||||
// ============
|
||||
|
||||
public function email_admins($subject = "", $message = "") {
|
||||
|
||||
$this->log(date("Y-m-d H:i:s") . " - Emailing Admins...");
|
||||
|
||||
$settings = new SimpleXMLElement((file_exists("./xml/settings.xml") ? file_get_contents("./xml/settings.xml") : "<settings/>"));
|
||||
|
||||
// Server Variables
|
||||
$host = (string) $settings->SmtpServer;
|
||||
$username = (string) $settings->SmtpUserName;
|
||||
$password = (string) $settings->SmtpPassword;
|
||||
|
||||
// Recipient Variables
|
||||
$from_email = (string) $settings->FromEmail;
|
||||
$from_name = (string) $settings->FromName;
|
||||
|
||||
$support_email = (string) $settings->SupportEmail;
|
||||
$support_name = (string) $settings->SupportName;
|
||||
|
||||
if ((string) $settings->Environment == 'Production') {
|
||||
|
||||
$admin_email = (string) $settings->AdminEmail;
|
||||
$admin_name = (string) $settings->AdminName;
|
||||
}
|
||||
|
||||
// Content Variables
|
||||
|
||||
$attachment_name = "";
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
|
||||
//Server settings
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $host;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
$mail->Port = 465;
|
||||
|
||||
//Recipients
|
||||
$mail->setFrom($from_email, $from_name);
|
||||
$mail->addAddress($support_email, $support_name);
|
||||
|
||||
if ($settings->Environment == 'Production') {
|
||||
$mail->addAddress($admin_email, $admin_name);
|
||||
}
|
||||
|
||||
//Attachments
|
||||
$mail->addAttachment($attachment_name);
|
||||
|
||||
//Content
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $message;
|
||||
|
||||
$mail->send();
|
||||
} catch (Exception $e) {
|
||||
$this->log("Message could not be sent. Mailer Error: {$e->ErrorInfo}");
|
||||
}
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Return an Formatted Date
|
||||
// ========================
|
||||
|
||||
public function fixDate($givenDate) {
|
||||
|
||||
$formattedDate = date('Y-m-d', strtotime($givenDate));
|
||||
|
||||
return $formattedDate;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -321,10 +321,12 @@ class Subscriptions extends Base {
|
||||
$user_serial = filter_input(INPUT_POST, "user_reference", FILTER_SANITIZE_NUMBER_INT) ?: 0;
|
||||
|
||||
$user = (new Users())->getUser($user_serial);
|
||||
$usersubscriptions = $this->getUsersActiveSubscriptions($user_serial);
|
||||
$subscriptions = $this->getSubscriptions();
|
||||
|
||||
$XML = $this->mergeXML($user, "<XML/>");
|
||||
$XML = $this->mergeXML($subscriptions, $XML);
|
||||
$XML = $this->mergeXML($usersubscriptions, $XML);
|
||||
|
||||
$content = $this->applyXSL($XML, $this->getXSL("addSubscriptionToUser"));
|
||||
|
||||
@@ -334,11 +336,18 @@ class Subscriptions extends Base {
|
||||
|
||||
case "add":
|
||||
|
||||
$subscription_serial = filter_input(INPUT_POST, "subscription_serial", FILTER_SANITIZE_NUMBER_INT) ?: 0;
|
||||
$user_serial = filter_input(INPUT_POST, "user_serial", FILTER_SANITIZE_NUMBER_INT) ?: 0;
|
||||
$subscription_stop_date = filter_input(INPUT_POST, "subscription_stop_date") ?: "";
|
||||
$subscription_serials = filter_input(INPUT_POST, "subscriptions", FILTER_SANITIZE_NUMBER_INT, FILTER_REQUIRE_ARRAY) ?: array();
|
||||
$subscription_end_dates = filter_input(INPUT_POST, "subscription_end_date", FILTER_DEFAULT, FILTER_REQUIRE_ARRAY) ?: array();
|
||||
$subscriptions = array();
|
||||
|
||||
$subscription_stop_date = date("Y-m-d", strtotime($subscription_stop_date));
|
||||
foreach ($subscription_serials as $index => $subscription_serial) {
|
||||
|
||||
$subscriptions[] = array(
|
||||
"usersubscription_subscription" => $subscription_serial,
|
||||
"usersubscription_end_date" => date("Y-m-d", strtotime($subscription_end_dates[$index] ?? ""))
|
||||
);
|
||||
}
|
||||
|
||||
$SQL = "insert into usersubscriptions
|
||||
|
||||
@@ -357,12 +366,19 @@ class Subscriptions extends Base {
|
||||
$statement = $this->connect()->prepare($SQL);
|
||||
|
||||
$statement->bindValue(":usersubscription_user_serial", $user_serial);
|
||||
$statement->bindValue(":usersubscription_subscription_serial", $subscription_serial);
|
||||
$statement->bindValue(":usersubscription_stop_date", $subscription_stop_date);
|
||||
$statement->bindValue(":usersubscription_timestamp", $this->getTimestamp());
|
||||
$statement->bindValue(":usersubscription_origin", $this->current_user_name);
|
||||
|
||||
foreach ($subscriptions as $subscription) {
|
||||
|
||||
$subscription_serial = $subscription['usersubscription_subscription'];
|
||||
$subscription_stop_date = $subscription['usersubscription_end_date'];
|
||||
|
||||
$statement->bindValue(":usersubscription_subscription_serial", $subscription_serial);
|
||||
$statement->bindValue(":usersubscription_stop_date", $subscription_stop_date);
|
||||
|
||||
$statement->execute();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
@@ -743,6 +743,20 @@ class Users extends Base {
|
||||
error_log($statement->errorInfo()[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Return a Users Login Activity Object
|
||||
// ====================================
|
||||
|
||||
public function getLoginActivity($userserial) {
|
||||
|
||||
$SQL = "select view_journal.journal_timestamp_verbose, view_journal.journal_ip
|
||||
from view_journal
|
||||
where view_journal.journal_origin = '{$userserial}'
|
||||
and view_journal.journal_entry like '{$userserial} %logged in%'";
|
||||
|
||||
return $this->getTable("loginactivity", $SQL);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
+79
-16
@@ -16,9 +16,9 @@ $(document).ready(function () {
|
||||
placeholder: "MM/DD/YYYY",
|
||||
showMaskOnHover: false});
|
||||
|
||||
// --------------
|
||||
// Select Picker.
|
||||
// --------------
|
||||
// -------------
|
||||
// Select Picker
|
||||
// -------------
|
||||
|
||||
$(".search-dropdown").selectpicker({noneSelectedText: "",
|
||||
style: "",
|
||||
@@ -34,25 +34,31 @@ $(document).ready(function () {
|
||||
// Date Pickers
|
||||
// ------------
|
||||
|
||||
$("#subscription_stop_date").datepicker({format: "mm-dd-yyyy",
|
||||
container: "div.subscription_stop_date",
|
||||
$(".subscription-end-date").each(function () {
|
||||
$(this).datepicker({
|
||||
format: "mm/dd/yyyy",
|
||||
container: $(this).closest(".subscription_end_date"),
|
||||
keyboardNavigation: false,
|
||||
showOnFocus: false,
|
||||
autoclose: true,
|
||||
orientation: "left bottom",
|
||||
orientation: "bottom left",
|
||||
todayBtn: "linked",
|
||||
todayHighlight: true});
|
||||
todayHighlight: true
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ---------------------
|
||||
// Show Calendar Buttons
|
||||
// ---------------------
|
||||
|
||||
$(".btnSubscriptionStopDateCalendar").on("click", function (e) {
|
||||
|
||||
$(".btnSubscriptionEndDateCalendar").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
let end_date = $(this).siblings(".subscription-end-date");
|
||||
|
||||
$("#subscription_stop_date").focus().datepicker("show");
|
||||
|
||||
if (!end_date.prop("disabled")) {
|
||||
end_date.focus().datepicker("show");
|
||||
}
|
||||
});
|
||||
|
||||
// -----------
|
||||
@@ -81,26 +87,83 @@ $(document).ready(function () {
|
||||
|
||||
});
|
||||
|
||||
function setSubscriptionDateState(subscription_checkbox) {
|
||||
|
||||
let row = subscription_checkbox.closest("tr");
|
||||
let checked = subscription_checkbox.prop("checked");
|
||||
|
||||
row.find(".subscription-end-date").prop("disabled", !checked);
|
||||
row.find(".btnSubscriptionEndDateCalendar").prop("disabled", !checked);
|
||||
|
||||
}
|
||||
|
||||
function parseSubscriptionDate(date_value) {
|
||||
|
||||
let parts = date_value.split("/");
|
||||
|
||||
return new Date(parts[2], parts[0] - 1, parts[1]);
|
||||
|
||||
}
|
||||
|
||||
$(".subscription-checkbox").each(function () {
|
||||
setSubscriptionDateState($(this));
|
||||
});
|
||||
|
||||
$(".subscription-checkbox").on("change", function () {
|
||||
|
||||
let totalSubscriptions = $(".subscription-checkbox").length;
|
||||
let checkedSubscriptions = $(".subscription-checkbox:checked").length;
|
||||
|
||||
$("#selectAllSubscriptions").prop("checked", totalSubscriptions === checkedSubscriptions);
|
||||
setSubscriptionDateState($(this));
|
||||
|
||||
});
|
||||
|
||||
$("#selectAllSubscriptions").on("change", function () {
|
||||
|
||||
$(".subscription-checkbox").prop("checked", $(this).prop("checked")).each(function () {
|
||||
setSubscriptionDateState($(this));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// ---------------
|
||||
// Validate Inputs
|
||||
// ---------------
|
||||
|
||||
$("#addSubscriptionToUser").submit(function (e) {
|
||||
|
||||
var subscription_title = $("#subscription_serial"), subscription_stop_date = $("#subscription_stop_date");
|
||||
let checkedSubscriptions = $(".subscription-checkbox:checked");
|
||||
|
||||
clearInputErrors();
|
||||
$("#subscriptions_errors").addClass("d-none");
|
||||
|
||||
if ($("option:selected", subscription_title).val() === "") {
|
||||
createInputError(subscription_title, "Subscription is required");
|
||||
// Validate at least one subscription selected
|
||||
if (checkedSubscriptions.length === 0) {
|
||||
e.preventDefault();
|
||||
$("#subscriptions_errors").removeClass("d-none");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isBlank(subscription_stop_date)) {
|
||||
createInputError(subscription_stop_date, "Date is required");
|
||||
for (let i = 0; i < checkedSubscriptions.length; i++) {
|
||||
|
||||
let row = checkedSubscriptions.eq(i).closest("tr");
|
||||
let end_date = row.find(".subscription-end-date");
|
||||
|
||||
if (isBlank(end_date)) {
|
||||
createInputError(end_date, "Date is required");
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((!isBlank(end_date)) && (!end_date.inputmask("isComplete"))) {
|
||||
createInputError(end_date, "Date is incomplete");
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
$.post("./", $(this).serialize())
|
||||
|
||||
@@ -15,6 +15,7 @@ $(document).ready(function () {
|
||||
|
||||
$sessionStorage.action = "Import.uploadCSVFile";
|
||||
$sessionStorage.step = "prompt";
|
||||
$sessionStorage.import_type = "full";
|
||||
|
||||
sessionStorage.setItem($application, JSON.stringify($sessionStorage));
|
||||
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ $(document).ready(function () {
|
||||
|
||||
$(".file-input").on("change", function (e) {
|
||||
|
||||
if ((e.target.files.length === 0) || (e.target.files[0].type !== "text/csv")) {
|
||||
if (e.target.files.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ $(document).ready(function () {
|
||||
.done(function () {
|
||||
|
||||
$sessionStorage.action = "";
|
||||
$sessionStorage.toast = "RDI Records Imported";
|
||||
$sessionStorage.toast = ($("#upload_type").val() === "full") ? "CSV Import Queued" : "Permit Records Imported";
|
||||
|
||||
sessionStorage.setItem($application, JSON.stringify($sessionStorage));
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
|
||||
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
|
||||
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
|
||||
<group/>
|
||||
<group>
|
||||
<file>file:/C:/Users/JamesRichie/OneDrive%20-%20OneSource%20IT%20Solutions%20LLC/Documents/NetBeansProjects/dec-international/atlhousingreport/classes/Import.php</file>
|
||||
<file>file:/C:/Users/JamesRichie/OneDrive%20-%20OneSource%20IT%20Solutions%20LLC/Documents/NetBeansProjects/dec-international/atlhousingreport/xsl/uploadCSVFile.xsl</file>
|
||||
<file>file:/C:/Users/JamesRichie/OneDrive%20-%20OneSource%20IT%20Solutions%20LLC/Documents/NetBeansProjects/dec-international/atlhousingreport/classes/Consolidation.php</file>
|
||||
<file>file:/C:/Users/JamesRichie/OneDrive%20-%20OneSource%20IT%20Solutions%20LLC/Documents/NetBeansProjects/dec-international/atlhousingreport/xml/settings.xml</file>
|
||||
</group>
|
||||
</open-files>
|
||||
</project-private>
|
||||
|
||||
@@ -81,7 +81,8 @@ class User_Summary extends TCPDF {
|
||||
}
|
||||
|
||||
$usernotes = (new Notes())->getAllNotes($this->user_serial);
|
||||
$subscriptions = (new Subscriptions())->getUsersSubscriptions($this->user_serial);
|
||||
$subscriptions = (new Subscriptions())->getUsersActiveSubscriptions($this->user_serial);
|
||||
$userloginactivity = (new Users())->getLoginActivity($user->record->user_name);
|
||||
|
||||
// Set report defaults
|
||||
|
||||
@@ -167,11 +168,57 @@ class User_Summary extends TCPDF {
|
||||
|
||||
$this->RoundedRect(10, 23, 190, $this->GetY() - 21, 1.50, "1111");
|
||||
|
||||
// --------------
|
||||
// Login Activity
|
||||
// --------------
|
||||
|
||||
$this->Ln(7);
|
||||
|
||||
$this->SetFont("Calibri", "B", 12);
|
||||
$this->Cell(0, 0, "Login Activity", 0, 1, "L", false, "", 3);
|
||||
$this->Ln(3);
|
||||
$this->SetFont("Calibri", "B", 10);
|
||||
|
||||
$this->Cell(30, 0, "Date", "B", 0, "L", false, "", 3);
|
||||
$this->Cell(20, 0, "IP Address", "B", 0, "L", false, "", 3);
|
||||
|
||||
$this->SetFont("Calibri", "", 10);
|
||||
|
||||
$this->Ln(6);
|
||||
|
||||
if ($userloginactivity->count() == 0) {
|
||||
|
||||
$this->Cell(0, 0, "No Login Activity...", 0, 1, "L", false, "", 3);
|
||||
} else {
|
||||
|
||||
foreach ($userloginactivity as $userlogin) {
|
||||
|
||||
$this->Cell(30, 0, $userlogin->journal_timestamp_verbose, 0, 0, "L", false, "", 3);
|
||||
$this->Cell(20, 0, $userlogin->journal_ip, 0, 0, "L", false, "", 3);
|
||||
|
||||
$this->Ln(4);
|
||||
|
||||
if ($this->GetY() > 265) {
|
||||
|
||||
$this->AddPage("P", "LETTER");
|
||||
|
||||
$this->SetFont("Calibri", "B", 10);
|
||||
|
||||
$this->Cell(30, 0, "Date", "B", 0, "L", false, "", 3);
|
||||
$this->Cell(20, 0, "Origin", "B", 0, "L", false, "", 3);
|
||||
|
||||
$this->SetFont("Calibri", "", 10);
|
||||
|
||||
$this->Ln(6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----
|
||||
// Notes
|
||||
// -----
|
||||
|
||||
$this->Ln(7);
|
||||
$this->Ln(5);
|
||||
|
||||
$this->SetFont("Calibri", "B", 12);
|
||||
$this->Cell(0, 0, "Notes", 0, 1, "L", false, "", 3);
|
||||
@@ -282,7 +329,7 @@ class User_Summary extends TCPDF {
|
||||
$this->setCellPaddings(0, 0, 0, 0);
|
||||
$this->setCellMargins(2, 0, 0, 0);
|
||||
|
||||
$this->ImageSVG('images/dec-logo.svg', 10, 0, 25, 25);
|
||||
$this->Image('images/dec-international-logo.png', 10, 7, 25, 10);
|
||||
|
||||
$this->SetFont("Calibri", "B", 16);
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# -- Development --
|
||||
cd /var/www/html/clients/dec-international/ahrhousingreport
|
||||
# -- Production --
|
||||
#cd /var/www/html/webapps/dec-international/ahrhousingreport
|
||||
php ./scripts/ImportRequest_Worker.php
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$basePath = realpath(__DIR__ . '/..');
|
||||
require_once $basePath . '/classes/Process_ImportRequest.php';
|
||||
|
||||
(new Process_ImportRequest())->process();
|
||||
?>
|
||||
@@ -0,0 +1,10 @@
|
||||
2025-11-14 22:35 PM : **************************************************
|
||||
2025-11-14 22:35 PM : Starting Migration of RDI Users
|
||||
2025-11-14 22:35 PM : Processing RDI Users
|
||||
2025-11-14 22:35 PM : Processed 10 records...
|
||||
2025-11-14 22:35 PM : Processed 20 records...
|
||||
2025-11-14 22:35 PM : Processed 30 records...
|
||||
2025-11-14 22:35 PM : Processed 40 records...
|
||||
2025-11-14 22:35 PM : Processed 50 records...
|
||||
2025-11-14 22:35 PM : Processed 60 records...
|
||||
2025-11-14 22:35 PM : RDI Users Migration Process Complete
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
$SQL = "create table importjobs (
|
||||
|
||||
importjob_serial serial,
|
||||
|
||||
importjob_file_path varchar(1024) default null,
|
||||
importjob_status enum('queued','running','complete','failed') NOT NULL DEFAULT 'queued',
|
||||
importjob_notified boolean default false,
|
||||
importjob_error_message text default null,
|
||||
importjob_creator varchar(100) default null,
|
||||
importjob_created datetime default null,
|
||||
importjob_completed datetime default null,
|
||||
|
||||
primary key (importjob_serial))";
|
||||
?>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
$SQL = "create view view_importjobs as
|
||||
|
||||
select importjobs.*,
|
||||
date_format(importjob_created, '%c/%e/%Y %l:%i %p') as importjob_created_verbose,
|
||||
date_format(importjob_completed, '%c/%e/%Y %l:%i %p') as importjob_completed_verbose
|
||||
|
||||
from importjobs";
|
||||
?>
|
||||
@@ -22,6 +22,9 @@
|
||||
<xsl:value-of select="$PAGE-TITLE"/>
|
||||
|
|
||||
<xsl:value-of select="//users/record/user_name"/>
|
||||
|
|
||||
<xsl:value-of select="//users/record/user_id"/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@@ -30,40 +33,77 @@
|
||||
|
||||
<input type="hidden" name="action" value="Subscriptions.addSubscriptionToUser"/>
|
||||
<input type="hidden" name="step" value="add"/>
|
||||
|
||||
<input type="hidden" name="user_serial" value="{//users/record/user_serial}"/>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-8 mb-3">
|
||||
<label class="form-label" for="subscription_serial">Subscription</label>
|
||||
<select class="form-control form-control-sm search-dropdown" name="subscription_serial" id="subscription_serial" size="1">
|
||||
<xsl:apply-templates select="//subscriptions"/>
|
||||
</select>
|
||||
<div class="alert alert-info py-2 small">
|
||||
<i class="bi bi-info-circle me-2"/>
|
||||
The subscriptions listed below will be added to the user.
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 mb-3">
|
||||
<div class="row g-4">
|
||||
|
||||
<label class="form-label" for="subscription_stop_date">Stop Date</label>
|
||||
<div class="input-group input-group-sm subscription_stop_date">
|
||||
<input type="text" class="form-control form-control-sm date-mask" name="subscription_stop_date" id="subscription_stop_date" value="{$TODAY_MMDDYYYY}"/>
|
||||
<button class="btn btn-sm btn-outline-primary btnSubscriptionStopDateCalendar" type="button" data-bs-toggle="tooltip" data-bs-title="Calendar" data-bs-custom-class="custom-tooltip" tabindex="-1">
|
||||
<i class="bi bi-calendar3"/>
|
||||
</button>
|
||||
<!-- Left Side: Subscriptions Being Copied -->
|
||||
<div class="col-lg-8">
|
||||
|
||||
<div class="card h-100">
|
||||
|
||||
<div class="card-body p-0">
|
||||
|
||||
<div class="">
|
||||
<table class="table table-sm table-striped table-hover mb-0 align-middle subscriptions_error">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 20px;">
|
||||
<input type="checkbox" class="form-check-input" id="selectAllSubscriptions"/>
|
||||
</th>
|
||||
<th>Subscription</th>
|
||||
<th>End Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<xsl:choose>
|
||||
<xsl:when test="count(//subscriptions/record[not(subscription_serial = //usersubscriptions/record/usersubscription_subscription_serial)]) = 0">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-3">
|
||||
This user already has all available subscriptions.
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:apply-templates select="//subscriptions/record[not(subscription_serial = //usersubscriptions/record/usersubscription_subscription_serial)]"/>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="subscriptions_errors" class="d-none px-3 pt-3">
|
||||
<div class="alert alert-danger py-2 small">
|
||||
<i class="bi bi-info-circle me-2"/>
|
||||
Select at least one subscription to copy.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="buttons mt-2 mb-2">
|
||||
<button type="button" class="btn btn-sm btn-primary me-2 btnSave">
|
||||
<i class="bi bi-save me-2"/>Save</button>
|
||||
<div class="buttons mt-1 mb-2">
|
||||
|
||||
<div class="buttons mt-3 mb-1">
|
||||
<button type="button" class="btn btn-sm btn-primary me-2 btnCancel">
|
||||
<i class="bi bi-x-lg me-2"/>Cancel</button>
|
||||
<i class="bi bi-box-arrow-left me-2"/>Cancel
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-primary me-2 btnSave">
|
||||
<i class="bi bi-floppy me-2"/>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -74,22 +114,24 @@
|
||||
|
||||
</xsl:template>
|
||||
|
||||
<!-- Template for Subscription dropdown -->
|
||||
|
||||
<xsl:template match="subscriptions">
|
||||
|
||||
<option value="">Choose..</option>
|
||||
|
||||
<xsl:for-each select="record">
|
||||
|
||||
<option value="{subscription_serial}" data-tokens="{subscription_serial}">
|
||||
<xsl:value-of select="subscription_title"/>
|
||||
-
|
||||
<xsl:value-of select="subscription_description"/>
|
||||
</option>
|
||||
|
||||
</xsl:for-each>
|
||||
|
||||
<!-- Subscriptions Rows -->
|
||||
<xsl:template match="subscriptions/record">
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" class="form-check-input subscription-checkbox" name="subscriptions[]" value="{subscription_serial}"/>
|
||||
</td>
|
||||
<td class="fw-semibold">
|
||||
<xsl:value-of select="concat(subscription_description, ' - ', subscription_projecttype, ' - ', subscription_area)"/>
|
||||
</td>
|
||||
<td>
|
||||
<div class="input-group input-group-sm subscription_end_date">
|
||||
<input type="text" class="form-control form-control-sm date-mask subscription-end-date" name="subscription_end_date[]"/>
|
||||
<button class="btn btn-sm btn-outline-primary btnSubscriptionEndDateCalendar" type="button" data-bs-toggle="tooltip" data-bs-title="Calendar" data-bs-custom-class="custom-tooltip" tabindex="-1">
|
||||
<i class="bi bi-calendar3"/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||
<xsl:output method="html" encoding="utf-8" indent="yes"/>
|
||||
|
||||
<!-- Parameters -->
|
||||
|
||||
<xsl:param name="VERSION">1.0</xsl:param>
|
||||
<xsl:param name="PAGE-TITLE">Add Subscription to User</xsl:param>
|
||||
<xsl:param name="PAGE-NAME">addSubscriptionToUser</xsl:param>
|
||||
|
||||
<xsl:template match="/">
|
||||
|
||||
<script src="js/{$PAGE-NAME}.js?version={$VERSION}"/>
|
||||
|
||||
<div class="{$PAGE-NAME}">
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="card-header bg-primary text-white p-1 ps-3">
|
||||
<i class="bi bi-list-check me-2"/>
|
||||
<xsl:value-of select="$PAGE-TITLE"/>
|
||||
|
|
||||
<xsl:value-of select="//users/record/user_name"/>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<form action="" name="{$PAGE-NAME}" method="post" id="{$PAGE-NAME}">
|
||||
|
||||
<input type="hidden" name="action" value="Subscriptions.addSubscriptionToUser"/>
|
||||
<input type="hidden" name="step" value="add"/>
|
||||
|
||||
<input type="hidden" name="user_serial" value="{//users/record/user_serial}"/>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-8 mb-3">
|
||||
<label class="form-label" for="subscription_serial">Subscription</label>
|
||||
<select class="form-control form-control-sm search-dropdown" name="subscription_serial" id="subscription_serial" size="1">
|
||||
<xsl:apply-templates select="//subscriptions"/>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 mb-3">
|
||||
|
||||
<label class="form-label" for="subscription_stop_date">Stop Date</label>
|
||||
<div class="input-group input-group-sm subscription_stop_date">
|
||||
<input type="text" class="form-control form-control-sm date-mask" name="subscription_stop_date" id="subscription_stop_date" value="{$TODAY_MMDDYYYY}"/>
|
||||
<button class="btn btn-sm btn-outline-primary btnSubscriptionStopDateCalendar" type="button" data-bs-toggle="tooltip" data-bs-title="Calendar" data-bs-custom-class="custom-tooltip" tabindex="-1">
|
||||
<i class="bi bi-calendar3"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
<div class="buttons mt-2 mb-2">
|
||||
<button type="button" class="btn btn-sm btn-primary me-2 btnSave">
|
||||
<i class="bi bi-save me-2"/>Save</button>
|
||||
<button type="button" class="btn btn-sm btn-primary me-2 btnCancel">
|
||||
<i class="bi bi-x-lg me-2"/>Cancel</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</xsl:template>
|
||||
|
||||
<!-- Template for Subscription dropdown -->
|
||||
|
||||
<xsl:template match="subscriptions">
|
||||
|
||||
<option value="">Choose..</option>
|
||||
|
||||
<xsl:for-each select="record">
|
||||
|
||||
<option value="{subscription_serial}" data-tokens="{subscription_serial}">
|
||||
<xsl:value-of select="subscription_title"/>
|
||||
-
|
||||
<xsl:value-of select="subscription_description"/>
|
||||
</option>
|
||||
|
||||
</xsl:for-each>
|
||||
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
@@ -29,10 +29,10 @@
|
||||
<p>Processed Records Count: <xsl:value-of select="$PROCESSED-RECORD-COUNT"/></p>
|
||||
|
||||
<xsl:choose>
|
||||
<xsl:when test="$RDI-RECORD-COUNT != $PROCESSED-RECORD-COUNT">
|
||||
<xsl:when test="$CONSOLIDATION-ERROR != '' or $RDI-RECORD-COUNT != $PROCESSED-RECORD-COUNT">
|
||||
<h3 class="text-danger">Consolidation Unsuccessful</h3>
|
||||
<p>
|
||||
<xsl:value-of select="CONSOLIDATION-ERROR"/>
|
||||
<xsl:value-of select="$CONSOLIDATION-ERROR"/>
|
||||
</p>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||
<xsl:output method="html" encoding="utf-8" indent="yes"/>
|
||||
|
||||
<!-- Parameters -->
|
||||
|
||||
<xsl:param name="PAGE-NAME">Queued Import Message</xsl:param>
|
||||
|
||||
<xsl:template match="/">
|
||||
|
||||
<script src="js/{$PAGE-NAME}.js?version={$VERSION}"/>
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="card-header bg-primary text-white p-1 ps-3 border-bottom-0">
|
||||
<i class="bi bi-exclamation-triangle me-2" aria-hidden="true"/>
|
||||
<xsl:copy-of select="$PAGE-NAME"/>
|
||||
</div>
|
||||
|
||||
<div class="card-body text-center">
|
||||
|
||||
<p>
|
||||
The CSV File has been successfully uploaded and queued for processing. An email will be sent when the import process is complete.
|
||||
</p>
|
||||
|
||||
<div class="buttons mt-2 mb-2">
|
||||
<a role="button" class="btn btn-sm btn-primary me-2 btnDone">
|
||||
<i class="bi bi-box-arrow-right me-2"/>Done</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
+49
-1
@@ -8,6 +8,7 @@
|
||||
<xsl:param name="VERSION">1.0</xsl:param>
|
||||
<xsl:param name="PAGE-TITLE">Upload CSV File</xsl:param>
|
||||
<xsl:param name="PAGE-NAME">uploadCSVFile</xsl:param>
|
||||
<xsl:param name="IMPORT-TYPE">incremental</xsl:param>
|
||||
|
||||
<xsl:template match="/">
|
||||
|
||||
@@ -35,9 +36,56 @@
|
||||
<div class="row">
|
||||
|
||||
<div class="col-6 mt-3 mb-4">
|
||||
<input type="file" class="form-control file-input" name="csvfile" id="csvfile" accept=".csv"/>
|
||||
<input type="file" class="form-control form-control-sm file-input" name="csvfile" id="csvfile" accept=".csv"/>
|
||||
</div>
|
||||
|
||||
<div class="col-3 mt-3 mb-4">
|
||||
<select class="form-control form-control-sm" name="upload_type" id="upload_type">
|
||||
|
||||
<xsl:choose>
|
||||
|
||||
<xsl:when test="$IMPORT-TYPE = 'incremental'">
|
||||
|
||||
<option value="incremental" selected="selected">
|
||||
Incremental Upload
|
||||
</option>
|
||||
|
||||
<option value="full">
|
||||
Full Upload
|
||||
</option>
|
||||
|
||||
</xsl:when>
|
||||
|
||||
<xsl:when test="$IMPORT-TYPE = 'full'">
|
||||
|
||||
<option value="incremental">
|
||||
Incremental Upload
|
||||
</option>
|
||||
|
||||
<option value="full" selected="selected">
|
||||
Full Upload
|
||||
</option>
|
||||
|
||||
</xsl:when>
|
||||
|
||||
<xsl:otherwise>
|
||||
|
||||
<option value="incremental">
|
||||
Incremental Upload
|
||||
</option>
|
||||
|
||||
<option value="full">
|
||||
Full Upload
|
||||
</option>
|
||||
|
||||
</xsl:otherwise>
|
||||
|
||||
</xsl:choose>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
+2
-2
@@ -261,7 +261,7 @@
|
||||
<div class="buttons mt-2 mb-1">
|
||||
|
||||
<button type="button" class="btn btn-sm btn-primary mt-2 me-1 mb-1 btnAddSubscription">
|
||||
<i class="bi bi-plus me-2"/>Add Subscription</button>
|
||||
<i class="bi bi-plus-lg me-2"/>Add Subscription</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
|
||||
<div class="buttons mt-2 mb-1">
|
||||
<button type="button" class="btn btn-sm btn-primary mt-2 me-1 mb-1 btnAddNote">
|
||||
<i class="bi bi-plus me-2"/>Add Note</button>
|
||||
<i class="bi bi-plus-lg me-2"/>Add Note</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user