Initial Work on Client Account Credit System, DB Structure, Credit Balance Calculation, added Apply Credit and add Credit with Expire dates, Added DB Structure for Discount Codes, UI Rewrite on client top header now using grouped cards, more dark mode work as well

This commit is contained in:
johnnyq
2025-07-24 11:31:45 -04:00
parent 99aaeefe8e
commit 197dd9f299
15 changed files with 1093 additions and 620 deletions
+88
View File
@@ -0,0 +1,88 @@
<?php
require_once '../includes/ajax_header.php';
$invoice_id = intval($_GET['id']);
$sql = mysqli_query(
$mysqli,
"SELECT * FROM invoices
LEFT JOIN clients ON invoice_client_id = client_id
LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1
WHERE invoice_id = $invoice_id
LIMIT 1"
);
$row = mysqli_fetch_array($sql);
$invoice_id = intval($row['invoice_id']);
$invoice_prefix = nullable_htmlentities($row['invoice_prefix']);
$invoice_number = intval($row['invoice_number']);
$invoice_amount = floatval($row['invoice_amount']);
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']);
$client_currency_code = nullable_htmlentities($row['client_currency_code']);
$contact_name = nullable_htmlentities($row['contact_name']);
$contact_email = nullable_htmlentities($row['contact_email']);
// Get Credit Balance
$sql_credit_balance = mysqli_query($mysqli, "SELECT SUM(credit_amount) AS credit_balance FROM credits WHERE credit_client_id = $client_id");
$row = mysqli_fetch_array($sql_credit_balance);
$credit_balance = floatval($row['credit_balance']);
//Add up all the payments for the invoice and get the total amount paid to the invoice
$sql_amount_paid = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS amount_paid FROM payments WHERE payment_invoice_id = $invoice_id");
$row = mysqli_fetch_array($sql_amount_paid);
$amount_paid = floatval($row['amount_paid']);
$invoice_balance = $invoice_amount - $amount_paid;
// Generate the HTML form content using output buffering.
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-wallet mr-2"></i><?php echo "$invoice_prefix$invoice_number"; ?>: Apply Credit (Balance: <?php echo numfmt_format_currency($currency_format, $credit_balance, $client_currency_code); ?>)</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="invoice_id" value="<?php echo $invoice_id; ?>">
<input type="hidden" name="invoice_balance" value="<?php echo $invoice_balance; ?>">
<input type="hidden" name="currency_code" value="<?php echo $client_currency_code; ?>">
<div class="modal-body">
<div class="form-group">
<label>Credit Amount <strong class="text-danger">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-wallet"></i></span>
</div>
<input type="text" class="form-control" inputmode="numeric" pattern="[0-9]*\.?[0-9]{0,2}" name="amount" value="<?php echo number_format($credit_balance, 2, '.', ''); ?>" placeholder="0.00" required>
</div>
</div>
<?php if (!empty($config_smtp_host) && !empty($contact_email)) { ?>
<div class="form-group">
<label>Email Receipt</label>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customControlAutosizing" name="email_receipt" value="1" checked>
<label class="custom-control-label" for="customControlAutosizing"><?php echo $contact_email; ?></label>
</div>
</div>
<?php } ?>
</div>
<div class="modal-footer">
<button type="submit" name="apply_credit" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Apply Credit</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
</div>
</form>
<?php
require_once "../includes/ajax_footer.php";
+3 -3
View File
@@ -36,7 +36,7 @@ $balance = $invoice_amount - $amount_paid;
ob_start();
?>
<div class="modal-header">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card mr-2"></i><?php echo "$invoice_prefix$invoice_number"; ?>: Make Payment</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
@@ -46,7 +46,7 @@ ob_start();
<input type="hidden" name="invoice_id" value="<?php echo $invoice_id; ?>">
<input type="hidden" name="balance" value="<?php echo $balance; ?>">
<input type="hidden" name="currency_code" value="<?php echo $client_currency_code; ?>">
<div class="modal-body bg-white">
<div class="modal-body">
<div class="form-row">
<div class="col-md">
@@ -169,7 +169,7 @@ ob_start();
</div>
<div class="modal-footer bg-white">
<div class="modal-footer">
<button type="submit" name="add_payment" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Pay</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fas fa-times mr-2"></i>Cancel</button>
</div>
+33 -23
View File
@@ -80,8 +80,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
?>
<div class="card card-dark">
<div class="card-header py-2">
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fa fa-fw fa-user-friends mr-2"></i><?php if($leads_filter == 0){ echo "Clients"; } else { echo "Leads"; } ?></h3>
<div class="card-tools">
<?php if (lookupUserPermission("module_client") >= 2) { ?>
@@ -104,14 +104,14 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<?php } ?>
</div>
</div>
<div class="card-body p-2 p-md-3">
<form class="mb-4" autocomplete="off">
<div class="card-header pb-1 pt-3">
<form autocomplete="off">
<input type="hidden" name="leads" value="<?php echo $leads_filter; ?>">
<input type="hidden" name="archived" value="<?php echo $archived; ?>">
<div class="row">
<div class="col-md-4">
<div class="input-group mb-3 mb-sm-0">
<div class="form-group">
<div class="input-group">
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(nullable_htmlentities($q)); } ?>" placeholder="Search <?php if($leads_filter == 0){ echo "clients"; } else { echo "leads"; } ?>" autofocus>
<div class="input-group-append">
<button class="btn btn-secondary" type="button" data-toggle="collapse" data-target="#advancedFilter"><i class="fas fa-filter"></i></button>
@@ -119,8 +119,9 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</div>
</div>
<div class="col-md-8">
<div class="btn-toolbar float-right">
<div class="btn-toolbar form-group float-right">
<div class="btn-group mr-2">
<a href="?leads=0" class="btn btn-<?php if ($leads_filter == 0){ echo "primary"; } else { echo "default"; } ?>"><i class="fa fa-fw fa-user-friends mr-2"></i>Clients</a>
<a href="?leads=1" class="btn btn-<?php if ($leads_filter == 1){ echo "primary"; } else { echo "default"; } ?>"><i class="fa fa-fw fa-bullhorn mr-2"></i>Leads</a>
@@ -175,7 +176,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
<div
class="collapse mt-3
class="collapse
<?php
if (
isset($_GET['dtf'])
@@ -220,9 +221,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>Tag</label>
<div class="input-group">
<select onchange="this.form.submit()" class="form-control select2" name="tags[]" data-placeholder="- Select Tags -" multiple>
<?php
$sql_tags_filter = mysqli_query($mysqli, "
@@ -284,12 +284,13 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</div>
</div>
</form>
<hr>
</div>
<form id="bulkActions" action="post.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<div class="table-responsive-sm">
<table class="table table-striped table-hover table-borderless">
<thead class="<?php if ($num_rows[0] == 0) { echo "d-none"; } ?> text-nowrap">
<table class="table table-hover table-borderless mb-0">
<thead class="<?php if ($num_rows[0] == 0) { echo "d-none"; } ?> text-nowrap bg-light">
<tr>
<td class="bg-light pr-0">
<div class="form-check">
@@ -403,6 +404,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$balance_text_color = "";
}
// Get Credit Balance
$sql_credit_balance = mysqli_query($mysqli, "SELECT SUM(credit_amount) AS credit_balance FROM credits WHERE credit_client_id = $client_id");
$row = mysqli_fetch_array($sql_credit_balance);
$credit_balance = floatval($row['credit_balance']);
//Get Monthly Recurring Total
$sql_recurring_monthly_total = mysqli_query($mysqli, "SELECT SUM(recurring_invoice_amount) AS recurring_monthly_total FROM recurring_invoices WHERE recurring_invoice_status = 1 AND recurring_invoice_frequency = 'month' AND recurring_invoice_client_id = $client_id");
$row = mysqli_fetch_array($sql_recurring_monthly_total);
@@ -480,7 +487,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
if (!empty($contact_email)) { ?>
<div class="mt-1">
<i class="fa fa-fw fa-envelope text-secondary mr-2"></i><a href="mailto:<?php echo $contact_email; ?>"><?php echo $contact_email; ?></a><button class='btn btn-sm clipboardjs' data-clipboard-text='<?php echo $contact_email; ?>'><i class='far fa-copy text-secondary'></i></button>
<i class="fa fa-fw fa-envelope text-secondary mr-2"></i><a href="mailto:<?php echo $contact_email; ?>"><?php echo $contact_email; ?></a><button class='btn btn-sm clipboardjs' type="button" data-clipboard-text='<?php echo $contact_email; ?>'><i class='far fa-copy text-secondary'></i></button>
</div>
<?php } ?>
</td>
@@ -488,16 +495,19 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<!-- Show Billing if perms & if accounting module is enabled -->
<?php if ((lookupUserPermission("module_financial") >= 1) && $config_module_enable_accounting == 1) { ?>
<td class="text-right">
<div class="mt-1">
<div>
<span class="text-secondary">Balance</span> <span class="<?php echo $balance_text_color; ?>"><?php echo numfmt_format_currency($currency_format, $balance, $session_company_currency); ?></span>
</div>
<div class="mt-1">
<div>
<span class="text-secondary">Paid</span> <?php echo numfmt_format_currency($currency_format, $amount_paid, $session_company_currency); ?>
</div>
<div class="mt-1">
<div>
<span class="text-secondary mr-2">Credit</span><span class="text-success"><?php echo numfmt_format_currency($currency_format, $credit_total, $session_company_currency); ?></span>
</div>
<div>
<span class="text-secondary">Monthly</span> <?php echo numfmt_format_currency($currency_format, $recurring_monthly, $session_company_currency); ?>
</div>
<div class="mt-1">
<div>
<span class="text-secondary">Hourly Rate</span> <?php echo numfmt_format_currency($currency_format, $client_rate, $session_company_currency); ?>
</div>
</td>
@@ -508,7 +518,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<td>
<div class="dropdown dropleft text-center">
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#"
@@ -549,10 +559,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
require_once "modals/client_bulk_email_modal.php";
?>
</form>
<?php require_once "includes/filter_footer.php";
?>
</div>
</div>
<!-- Ends Card Body -->
<?php require_once "includes/filter_footer.php"; ?>
</div> <!-- End Card -->
<script src="js/bulk_actions.js"></script>
+37 -6
View File
@@ -3768,9 +3768,40 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.3'");
}
/* 2025-07-21 - JQ For next release Pauyment Provider Switch Over
if (CURRENT_DATABASE_VERSION == '2.2.3') {
mysqli_query($mysqli, "CREATE TABLE `credits` (
`credit_id` INT(11) NOT NULL AUTO_INCREMENT,
`credit_amount` DECIMAL(15,2) NOT NULL,
`credit_reference` VARCHAR(250) DEFAULT NULL,
`credit_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
`credit_created_by` INT(11) NOT NULL,
`credit_expire_at` DATE DEFAULT NULL,
`credit_client_id` INT(11) NOT NULL,
PRIMARY KEY (`credit_id`)
)");
mysqli_query($mysqli, "ALTER TABLE `invoices` ADD `invoice_credit_amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER `invoice_discount_amount`");
mysqli_query($mysqli, "CREATE TABLE `discount_codes` (
`discount_code_id` INT(11) NOT NULL AUTO_INCREMENT,
`discount_code_description` VARCHAR(250) DEFAULT NULL,
`discount_code_amount` DECIMAL(15,2) NOT NULL,
`discount_code` VARCHAR(200) NOT NULL,
`discount_code_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
`discount_code_created_by` INT(11) NOT NULL,
`discount_code_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
`discount_code_archived_at` DATETIME NULL DEFAULT NULL,
`discount_code_expire_at` DATE DEFAULT NULL,
PRIMARY KEY (`discount_code_id`)
)");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.4'");
}
/* 2025-07-21 - JQ For next release Pauyment Provider Switch Over
if (CURRENT_DATABASE_VERSION == '2.2.4') {
// Delete all Recurring Payments that are Stripe
mysqli_query($mysqli, "DELETE FROM recurring_payments WHERE recurring_payment_method = 'Stripe'");
@@ -3794,14 +3825,14 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
DROP `config_ai_api_key`
");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.4'");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.5'");
}
/*
*/
// if (CURRENT_DATABASE_VERSION == '2.2.4') {
// // Insert queries here required to update to DB version 2.2.5
// if (CURRENT_DATABASE_VERSION == '2.2.5') {
// // Insert queries here required to update to DB version 2.2.6
// // Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.5'");
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.6'");
// }
} else {
+42 -1
View File
@@ -838,6 +838,25 @@ CREATE TABLE `credentials` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `credits`
--
DROP TABLE IF EXISTS `credits`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `credits` (
`credit_id` int(11) NOT NULL AUTO_INCREMENT,
`credit_amount` decimal(15,2) NOT NULL,
`credit_reference` varchar(250) DEFAULT NULL,
`credit_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`credit_created_by` int(11) NOT NULL,
`credit_expire_at` date DEFAULT NULL,
`credit_client_id` int(11) NOT NULL,
PRIMARY KEY (`credit_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `custom_fields`
--
@@ -894,6 +913,27 @@ CREATE TABLE `custom_values` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `discount_codes`
--
DROP TABLE IF EXISTS `discount_codes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `discount_codes` (
`discount_code_id` int(11) NOT NULL AUTO_INCREMENT,
`discount_code_description` varchar(250) DEFAULT NULL,
`discount_code_amount` decimal(15,2) NOT NULL,
`discount_code` varchar(200) NOT NULL,
`discount_code_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`discount_code_created_by` int(11) NOT NULL,
`discount_code_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),
`discount_code_archived_at` datetime DEFAULT NULL,
`discount_code_expire_at` date DEFAULT NULL,
PRIMARY KEY (`discount_code_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `document_files`
--
@@ -1190,6 +1230,7 @@ CREATE TABLE `invoices` (
`invoice_date` date NOT NULL,
`invoice_due` date NOT NULL,
`invoice_discount_amount` decimal(15,2) NOT NULL DEFAULT 0.00,
`invoice_credit_amount` decimal(15,2) NOT NULL DEFAULT 0.00,
`invoice_amount` decimal(15,2) NOT NULL DEFAULT 0.00,
`invoice_currency_code` varchar(200) NOT NULL,
`invoice_note` text DEFAULT NULL,
@@ -2718,4 +2759,4 @@ CREATE TABLE `vendors` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2025-07-08 14:06:43
-- Dump completed on 2025-07-24 11:29:27
+1 -1
View File
@@ -5,4 +5,4 @@
* It is used in conjunction with database_updates.php
*/
DEFINE("LATEST_DATABASE_VERSION", "2.2.3");
DEFINE("LATEST_DATABASE_VERSION", "2.2.4");
+7 -7
View File
@@ -15,11 +15,12 @@ if ($total_found_rows > 5) {
?>
<hr>
<div class="card-footer pb-0 pt-3 border-top">
<div class="row">
<div class="col-sm mb-2">
<div class="col-sm">
<form action="post.php" method="post">
<div class="form-group">
<select onchange="this.form.submit()" class="form-control select2 col-12 col-sm-3" name="change_records_per_page">
<option <?php if ($user_config_records_per_page == 5) { echo "selected"; } ?> >5</option>
<option <?php if ($user_config_records_per_page == 10) { echo "selected"; } ?> >10</option>
@@ -28,6 +29,7 @@ if ($total_found_rows > 5) {
<option <?php if ($user_config_records_per_page == 100) { echo "selected"; } ?> >100</option>
<option <?php if ($user_config_records_per_page == 500) { echo "selected"; } ?> >500</option>
</select>
</div>
</form>
</div>
@@ -50,16 +52,13 @@ if ($total_found_rows > 5) {
// Now output something like "Showing X to Y of Z records"
?>
<div class="col-sm mb-2">
<div class="col-sm">
<p class="text-center">
Showing <strong><?php echo $start; ?></strong> to <strong><?php echo $end; ?></strong> of <strong><?php echo $total_found_rows; ?></strong> records
</p>
<!--<p class="text-center mt-2"><?php echo $total_found_rows; ?></p> -->
</div>
<div class="col-sm mb-2">
<div class="col-sm">
<ul class="pagination justify-content-sm-end">
<?php
@@ -115,6 +114,7 @@ if ($total_found_rows > 5) {
</ul>
</div>
</div>
</div>
<?php
+1 -1
View File
@@ -46,5 +46,5 @@ header("X-Frame-Options: DENY");
<script src="plugins/toastr/toastr.min.js"></script>
</head>
<body class="hold-transition sidebar-mini layout-fixed layout-navbar-fixed dark-mode accent-<?php if (isset($_GET['client_id'])) { echo "blue"; } else { echo nullable_htmlentities($config_theme); } ?>">
<body class="hold-transition sidebar-mini layout-fixed layout-navbar-fixed dark-moe accent-<?php if (isset($_GET['client_id'])) { echo "blue"; } else { echo nullable_htmlentities($config_theme); } ?>">
<div class="wrapper text-sm">
+6
View File
@@ -131,6 +131,12 @@ if (isset($_GET['client_id'])) {
$recurring_monthly = $recurring_monthly_total + $recurring_yearly_total;
// Get Credit Balance
$sql_credit_balance = mysqli_query($mysqli, "SELECT SUM(credit_amount) AS credit_balance FROM credits WHERE credit_client_id = $client_id");
$row = mysqli_fetch_array($sql_credit_balance);
$credit_balance = floatval($row['credit_balance']);
// Badge Counts
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('contact_id') AS num FROM contacts WHERE contact_archived_at IS NULL AND contact_client_id = $client_id"));
+32 -29
View File
@@ -1,13 +1,13 @@
<div class="card d-print-none">
<div class="card-body py-2">
<div class="row">
<div class="col">
<a href="#" data-toggle="collapse" data-target="#clientHeader"><h4 class="text-secondary" data-toggle="tooltip" data-placement="right" title="Client ID: <?php echo $client_id; ?>"><strong><?php echo $client_name; ?></strong> <?php if ($client_archived_at) { echo "(archived)"; } ?></h4></a>
<div class="card-header pb-1 pt-2 px-3">
<div class="card-title">
<a href="#" data-toggle="collapse" data-target="#clientHeader"><h4 class="text-dark" data-toggle="tooltip" data-placement="right" title="Client ID: <?php echo $client_id; ?>"><strong><?php echo $client_name; ?></strong> <?php if ($client_archived_at) { echo "(archived)"; } ?></h4></a>
</div>
<div class="col">
<?php if (!empty($client_tag_name_display_array)) { ?><div class="card-title ml-2"><?php echo $client_tags_display; ?></div> <?php } ?>
<?php if (lookupUserPermission("module_client") >= 2) { ?>
<div class="card-tools">
<div class="dropdown dropleft text-center">
<button class="btn btn-dark btn-sm float-right" type="button" data-toggle="dropdown">
<button class="btn btn-dark btn-sm" type="button" data-toggle="dropdown">
<i class="fas fa-fw fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu">
@@ -17,6 +17,12 @@
data-ajax-id="<?php echo $client_id; ?>">
<i class="fas fa-fw fa-edit mr-2"></i>Edit Client
</a>
<?php if (lookupUserPermission("module_billing") >= 2) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#addCreditModal">
<i class="fas fa-fw fa-wallet mr-2"></i>Add Credit
</a>
<?php } ?>
<?php if (lookupUserPermission("module_client") >= 3) { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#exportClientPDFModal">
@@ -45,16 +51,16 @@
</div>
</div>
</div>
<?php } ?>
</div>
</div>
</div>
<div class="collapse <?php if (basename($_SERVER["PHP_SELF"]) == "client_overview.php") { echo "show"; } ?>" id="clientHeader">
<div class="collapse <?php if (basename($_SERVER["PHP_SELF"]) == "client_overview.php") { echo "show"; } ?>" id="clientHeader">
<div class="row">
<div class="col-md border-top">
<h5 class="text-secondary mt-1">Primary Location</h5>
<div class="card-group mb-3">
<div class="card card-body px-3 py-2">
<h5>Primary Location</h5>
<?php if (!empty($location_address)) { ?>
<div>
<a href="//maps.<?php echo $session_map_source; ?>.com/?q=<?php echo "$location_address $location_zip"; ?>" target="_blank">
@@ -70,7 +76,7 @@
<?php }
if (!empty($location_phone)) { ?>
<div class="mt-1">
<div>
<i class="fa fa-fw fa-phone text-secondary ml-1 mr-2"></i><a href="tel:<?php echo $location_phone?>"><?php echo $location_phone; ?></a>
</div>
<hr class="my-2">
@@ -81,11 +87,10 @@
<i class="fa fa-fw fa-globe text-secondary ml-1 mr-2"></i><a target="_blank" href="//<?php echo $client_website; ?>"><?php echo $client_website; ?></a>
</div>
<?php } ?>
</div>
<div class="col-md border-left border-top">
<h5 class="text-secondary mt-1">Primary Contact</h5>
<div class="card card-body px-3 py-2">
<h5>Primary Contact</h5>
<?php
if (!empty($contact_name)) { ?>
@@ -119,12 +124,12 @@
<i class="fa fa-fw fa-mobile-alt text-secondary ml-1 mr-2"></i><a href="tel:<?php echo $contact_mobile; ?>"><?php echo $contact_mobile; ?></a>
</div>
<?php } ?>
</div>
<?php if (lookupUserPermission("module_financial") >= 1 && $config_module_enable_accounting == 1) { ?>
<div class="col-md border-left border-top">
<h5 class="text-secondary mt-1">Billing</h5>
<div class="card card-body px-3 py-2">
<h5>Billing</h5>
<div class="ml-1 text-secondary">Hourly Rate
<span class="text-dark float-right"> <?php echo numfmt_format_currency($currency_format, $client_rate, $client_currency_code); ?></span>
</div>
@@ -134,6 +139,11 @@
<div class="ml-1 mt-1 text-secondary">Balance
<span class="<?php if ($balance > 0) { echo "text-danger"; }else{ echo "text-dark"; } ?> float-right"> <?php echo numfmt_format_currency($currency_format, $balance, $client_currency_code); ?></span>
</div>
<?php if ($credit_balance) { ?>
<div class="ml-1 mt-1 text-secondary">Credit
<span class="text-success float-right"><?php echo numfmt_format_currency($currency_format, $credit_balance, $client_currency_code); ?></span>
</div>
<?php } ?>
<div class="ml-1 mt-1 text-secondary">Monthly Recurring
<span class="text-dark float-right"> <?php echo numfmt_format_currency($currency_format, $recurring_monthly, $client_currency_code); ?></span>
</div>
@@ -149,28 +159,21 @@
<?php } ?>
<?php if (lookupUserPermission("module_support") >= 1 && $config_module_enable_ticketing == 1) { ?>
<div class="col-md border-left border-top">
<h5 class="text-secondary mt-1">Support</h5>
<div class="card card-body px-3 py-2">
<h5>Support</h5>
<div class="ml-1 text-secondary">Open Tickets
<span class="text-dark float-right"><?php echo $num_active_tickets; ?></span>
</div>
<div class="ml-1 text-secondary mt-1">Closed Tickets
<span class="text-dark float-right"><?php echo $num_closed_tickets; ?></span>
</div>
<?php
if (!empty($client_tag_name_display_array)) { ?>
<hr>
<?php echo $client_tags_display; ?>
<?php } ?>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<?php
require_once "modals/client_credit_add_modal.php";
require_once "modals/client_delete_modal.php";
require_once "modals/client_download_pdf_modal.php";
+20
View File
@@ -47,6 +47,7 @@ if (isset($_GET['invoice_id'])) {
$invoice_due = nullable_htmlentities($row['invoice_due']);
$invoice_amount = floatval($row['invoice_amount']);
$invoice_discount = floatval($row['invoice_discount_amount']);
$invoice_credit = floatval($row['invoice_credit_amount']);
$invoice_currency_code = nullable_htmlentities($row['invoice_currency_code']);
$invoice_note = nullable_htmlentities($row['invoice_note']);
$invoice_url_key = nullable_htmlentities($row['invoice_url_key']);
@@ -142,6 +143,12 @@ if (isset($_GET['invoice_id'])) {
$balance = $invoice_amount - $amount_paid;
// Get Credit Balance
$sql_credit_balance = mysqli_query($mysqli, "SELECT SUM(credit_amount) AS credit_balance FROM credits WHERE credit_client_id = $client_id");
$row = mysqli_fetch_array($sql_credit_balance);
$credit_balance = floatval($row['credit_balance']);
//check to see if overdue
if ($invoice_status !== "Paid" && $invoice_status !== "Draft" && $invoice_status !== "Cancelled" && $invoice_status !== "Non-Billable") {
$unixtime_invoice_due = strtotime($invoice_due) + 86400;
@@ -240,6 +247,9 @@ if (isset($_GET['invoice_id'])) {
<?php if ($config_stripe_enable) { ?>
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown"></button>
<div class="dropdown-menu">
<?php if ($credit_balance) { ?>
<a class="dropdown-item" href="#" data-toggle="ajax-modal" data-ajax-url="ajax/ajax_invoice_apply_credit.php" data-ajax-id="<?php echo $invoice_id; ?>"><i class="fas fa-fw fa-wallet mr-2"></i>Apply Credit (Balance: <?php echo numfmt_format_currency($currency_format, $credit_balance, $client_currency_code); ?>)</a>
<?php } ?>
<a class="dropdown-item" href="guest/guest_pay_invoice_stripe.php?invoice_id=<?php echo "$invoice_id&url_key=$invoice_url_key"; ?>">Enter Card Manually</a>
<?php
if (mysqli_num_rows($sql_saved_payment_methods) > 0) { ?>
@@ -543,6 +553,16 @@ if (isset($_GET['invoice_id'])) {
<?php
}
?>
<?php
if ($invoice_credit > 0) {
?>
<tr>
<td>Credit:</td>
<td class="text-right">-<?php echo numfmt_format_currency($currency_format, $invoice_credit, $invoice_currency_code); ?></td>
</tr>
<?php
}
?>
<?php if ($total_tax > 0) { ?>
<tr>
<td>Tax:</td>
+5 -6
View File
@@ -7,11 +7,8 @@
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<ul class="modal-header nav nav-pills nav-justified">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a>
</li>
@@ -31,7 +28,9 @@
</li>
</ul>
<hr>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="tab-content">
@@ -373,7 +372,7 @@
</div>
<div class="modal-footer">
<button type="submit" name="add_client" class="btn btn-primary text-bold" onclick="promptPrimaryContact()"><i class="fa fa-check mr-2"></i>Create</button>
<button type="button" class="btn btn-light" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Close</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Close</button>
</div>
</form>
</div>
+82
View File
@@ -0,0 +1,82 @@
<div class="modal" id="addCreditModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-dark">
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-wallet mr-2"></i>Adding <strong>Credit</strong> (Credit Balance: <?php echo numfmt_format_currency($currency_format, $credit_balance, $client_currency_code); ?>)</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="form-group">
<label>Expire</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-day"></i></span>
</div>
<input type="date" class="form-control" name="expire" max="2999-12-31">
</div>
</div>
<div class="form-group">
<label>Amount<strong class="text-danger ml-2">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
</div>
<input type="text" class="form-control" inputmode="numeric" pattern="[0-9]*\.?[0-9]{0,2}" name="amount" placeholder="0.00" required>
</div>
</div>
<div class="form-group">
<label>Reference<strong class="text-danger ml-2">*</strong></label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-file-alt"></i></span>
</div>
<input type="text" class="form-control" name="reference" placeholder="Enter a reference" maxlength="250">
</div>
</div>
<?php if (isset($_GET['client_id'])) { ?>
<input type="hidden" name="client" value="<?php echo $client_id; ?>">
<?php } else { ?>
<div class="form-group col-md">
<label>Client</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
</div>
<select class="form-control select2" name="client" required>
<option value="0">- Client (Optional) -</option>
<?php
$sql = mysqli_query($mysqli, "SELECT client_id, client_name FROM clients ORDER BY client_name ASC");
while ($row = mysqli_fetch_array($sql)) {
$client_id = intval($row['client_id']);
$client_name = nullable_htmlentities($row['client_name']);
?>
<option value="<?php echo $client_id; ?>"><?php echo $client_name; ?></option>
<?php
}
?>
</select>
</div>
</div>
<?php } ?>
</div>
<div class="modal-footer">
<button type="submit" name="add_credit" class="btn btn-primary text-bold"><i class="fa fa-fw fa-check mr-2"></i>Add</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><i class="fa fa-times mr-2"></i>Cancel</button>
</div>
</form>
</div>
</div>
</div>
+29
View File
@@ -0,0 +1,29 @@
<?php
/*
* ITFlow - GET/POST request handler for credits
*/
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
if (isset($_POST['add_credit'])) {
validateCSRFToken($_POST['csrf_token']);
enforceUserPermission('module_sales', 2);
$client_id = intval($_POST['client']);
$amount = floatval($_POST['amount']);
$expire = sanitizeInput($_POST['expire']);
$reference = sanitizeInput($_POST['reference']);
mysqli_query($mysqli,"INSERT INTO credits SET credit_amount = $amount, credit_reference = '$reference', credit_created_by = $session_user_id, credit_client_id = $client_id");
$credit_id = mysqli_insert_id($mysqli);
// Logging
logAction("Credit", "Create", "$session_name added " . numfmt_format_currency($currency_format, $amount, $session_company_currency) . "", $client_id, $credit_id);
$_SESSION['alert_message'] = "" . numfmt_format_currency($currency_format, $amount, $session_company_currency) . " Credit Added ";
header("Location: " . $_SERVER["HTTP_REFERER"]);
}
+164
View File
@@ -894,6 +894,170 @@ if (isset($_POST['add_payment'])) {
}
}
if (isset($_POST['apply_credit'])) {
enforceUserPermission('module_sales', 2);
enforceUserPermission('module_financial', 2);
$invoice_id = intval($_POST['invoice_id']);
$amount = floatval($_POST['amount']);
$invoice_balance = floatval($_POST['invoice_balance']);
$currency_code = sanitizeInput($_POST['currency_code']);
$email_receipt = intval($_POST['email_receipt'] ?? 0);
$client_id = getFieldByID('invoices',$invoice_id,'invoice_client_id');
$invoice_prefix = getFieldByID('invoices',$invoice_id,'invoice_prefix');
$invoice_number = getFieldByID('invoices',$invoice_id,'invoice_number');
$invoice_status = getFieldByID('invoices',$invoice_id,'invoice_status');
//Check to see if amount entered is greater than the balance of the invoice
if ($amount > $invoice_balance) {
$_SESSION['alert_message'] = "Credit is more than the balance";
header("Location: " . $_SERVER["HTTP_REFERER"]);
} else {
mysqli_query($mysqli,"UPDATE invoices SET invoice_credit_amount = $amount WHERE invoice_id = $invoice_id");
/*
//Add up all the payments for the invoice and get the total amount paid to the invoice
$sql_total_payments_amount = mysqli_query($mysqli,"SELECT SUM(payment_amount) AS payments_amount FROM payments WHERE payment_invoice_id = $invoice_id");
$row = mysqli_fetch_array($sql_total_payments_amount);
$total_payments_amount = floatval($row['payments_amount']);
//Get the invoice total
$sql = mysqli_query($mysqli,"SELECT * FROM invoices
LEFT JOIN clients ON invoice_client_id = client_id
LEFT JOIN contacts ON clients.client_id = contacts.contact_client_id AND contact_primary = 1
WHERE invoice_id = $invoice_id"
);
$row = mysqli_fetch_array($sql);
$invoice_amount = floatval($row['invoice_amount']);
$invoice_prefix = sanitizeInput($row['invoice_prefix']);
$invoice_number = intval($row['invoice_number']);
$invoice_url_key = sanitizeInput($row['invoice_url_key']);
$invoice_currency_code = sanitizeInput($row['invoice_currency_code']);
$client_id = intval($row['client_id']);
$client_name = sanitizeInput($row['client_name']);
$contact_name = sanitizeInput($row['contact_name']);
$contact_email = sanitizeInput($row['contact_email']);
$contact_phone = sanitizeInput(formatPhoneNumber($row['contact_phone'], $row['contact_phone_country_code']));
$contact_extension = preg_replace("/[^0-9]/", '',$row['contact_extension']);
$contact_mobile = sanitizeInput(formatPhoneNumber($row['contact_mobile'], $row['contact_mobile_country_code']));
$sql = mysqli_query($mysqli,"SELECT * FROM companies WHERE company_id = 1");
$row = mysqli_fetch_array($sql);
$company_name = sanitizeInput($row['company_name']);
$company_country = sanitizeInput($row['company_country']);
$company_address = sanitizeInput($row['company_address']);
$company_city = sanitizeInput($row['company_city']);
$company_state = sanitizeInput($row['company_state']);
$company_zip = sanitizeInput($row['company_zip']);
$company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'], $row['company_phone_country_code']));
$company_email = sanitizeInput($row['company_email']);
$company_website = sanitizeInput($row['company_website']);
$company_logo = sanitizeInput($row['company_logo']);
// Sanitize Config vars from get_settings.php
$config_invoice_from_name = sanitizeInput($config_invoice_from_name);
$config_invoice_from_email = sanitizeInput($config_invoice_from_email);
//Calculate the Invoice balance
$invoice_balance = $invoice_amount - $total_payments_amount;
$email_data = [];
//Determine if invoice has been paid then set the status accordingly
if ($invoice_balance == 0) {
$invoice_status = "Paid";
if ($email_receipt == 1) {
$subject = "Payment Received - Invoice $invoice_prefix$invoice_number";
$body = "Hello $contact_name,<br><br>We have received your payment in full for the amount of " . numfmt_format_currency($currency_format, $amount, $invoice_currency_code) . " for invoice <a href=\'https://$config_base_url/guest/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\'>$invoice_prefix$invoice_number</a>. Please keep this email as a receipt for your records.<br><br>Amount Paid: " . numfmt_format_currency($currency_format, $amount, $invoice_currency_code) . "<br>Payment Method: $payment_method<br>Payment Reference: $reference<br><br>Thank you for your business!<br><br><br>--<br>$company_name - Billing Department<br>$config_invoice_from_email<br>$company_phone";
// Queue Mail
$email = [
'from' => $config_invoice_from_email,
'from_name' => $config_invoice_from_name,
'recipient' => $contact_email,
'recipient_name' => $contact_name,
'subject' => $subject,
'body' => $body
];
$email_data[] = $email;
// Add email to queue
if (!empty($email)) {
addToMailQueue($email_data);
}
// Get Email ID for reference
$email_id = mysqli_insert_id($mysqli);
// Email Logging
mysqli_query($mysqli,"INSERT INTO history SET history_status = 'Sent', history_description = 'Payment Receipt sent to mail queue ID: $email_id!', history_invoice_id = $invoice_id");
logAction("Invoice", "Payment", "Payment receipt for invoice $invoice_prefix$invoice_number queued to $contact_email Email ID: $email_id", $client_id, $invoice_id);
}
} else {
$invoice_status = "Partial";
if ($email_receipt == 1) {
$subject = "Partial Payment Received - Invoice $invoice_prefix$invoice_number";
$body = "Hello $contact_name,<br><br>We have received partial payment in the amount of " . numfmt_format_currency($currency_format, $amount, $invoice_currency_code) . " and it has been applied to invoice <a href=\'https://$config_base_url/guest/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\'>$invoice_prefix$invoice_number</a>. Please keep this email as a receipt for your records.<br><br>Amount Paid: " . numfmt_format_currency($currency_format, $amount, $invoice_currency_code) . "<br>Payment Method: $payment_method<br>Payment Reference: $reference<br>Invoice Balance: " . numfmt_format_currency($currency_format, $invoice_balance, $invoice_currency_code) . "<br><br>Thank you for your business!<br><br><br>~<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
// Queue Mail
$email = [
'from' => $config_invoice_from_email,
'from_name' => $config_invoice_from_name,
'recipient' => $contact_email,
'recipient_name' => $contact_name,
'subject' => $subject,
'body' => $body
];
$email_data[] = $email;
// Add email to queue
if (!empty($email)) {
addToMailQueue($email_data);
}
// Get Email ID for reference
$email_id = mysqli_insert_id($mysqli);
// Email Logging
mysqli_query($mysqli,"INSERT INTO history SET history_status = 'Sent', history_description = 'Payment Receipt sent to mail queue ID: $email_id!', history_invoice_id = $invoice_id");
logAction("Invoice", "Payment", "Payment receipt for invoice $invoice_prefix$invoice_number queued to $contact_email Email ID: $email_id", $client_id, $invoice_id);
}
}
//Update Invoice Status
mysqli_query($mysqli,"UPDATE invoices SET invoice_status = '$invoice_status' WHERE invoice_id = $invoice_id");
*/
//Add Payment to History
mysqli_query($mysqli,"INSERT INTO history SET history_status = '$invoice_status', history_description = 'Credit applied', history_invoice_id = $invoice_id");
// Logging
logAction("Invoice", "Payment", "Credit" . numfmt_format_currency($currency_format, $amount, $session_company_currency) . " added to invoice $invoice_prefix$invoice_number", $client_id, $invoice_id);
customAction('invoice_pay', $invoice_id);
$_SESSION['alert_message'] .= "Credit amount <strong>" . numfmt_format_currency($currency_format, $amount, $session_company_currency) . "</strong> applied";
header("Location: " . $_SERVER["HTTP_REFERER"]);
}
}
if (isset($_GET['add_payment_stripe'])) {
enforceUserPermission('module_sales', 2);