diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1ac7651..528ba258 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,23 @@
# CHANGELOG
+### Version 10.0
+- Discord server link added
+- Added archive option when deleting a formula
+- Added JSON export for customers
+- Added inventory create/update info for customers
+- Customers messages changed to toast
+- Added JSON export for lids
+- Added inventory create/update info for compounds
+- Added JSON import/export for compounds
+- Allow a formula to be marked complete when contains skipped materials
+- Bottle edit page format update
+- Weight added for the bottles inventory
+- Bottles inventory messages changed to toast
+- Added Inventory for finished Compounds
+- Added document size and created date in fotmula attachements page
+- Fix search when replacing a material
+- PV Scale integration - WIP
+- Log ingredient id in history
+
### Version 9.9
- Record replaced ingredients when finalising a formula in the generated PDF
- Increase toast duration for formula making to 10 seconds
diff --git a/README.md b/README.md
index 585330b3..42943dcd 100755
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@ A sophisticated tool to help perfumers organize their formulas, ingredients and
This is a FREE software provided as is without ANY warranty under MIT license.
[![Current Release](https://img.shields.io/github/v/release/globaldyne/parfumvault.svg "Current Release")](https://github.com/globaldyne/parfumvault/releases/latest) [![PayPal](https://img.shields.io/badge/donate-PayPal-green.svg)](https://paypal.me/jbparfum)
+![Discord](https://img.shields.io/discord/1238069309356638217)
# Features
diff --git a/VERSION.md b/VERSION.md
index a61a79be..2f52450b 100755
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-9.9
+10.0
diff --git a/core/formula_timeline_data.php b/core/formula_timeline_data.php
index 3ded40df..02f6696d 100644
--- a/core/formula_timeline_data.php
+++ b/core/formula_timeline_data.php
@@ -38,6 +38,7 @@
$r['id'] = (int)$h['id'];
$r['fid'] = (string)$h['fid'];
+ $r['ing_id'] = (int)$h['ing_id'];
$r['change_made'] = (string)$h['change_made'];
$r['date_time'] = (string)$h['date_time'];
$r['user'] = (string)$h['user'];
diff --git a/core/list_bottle_data.php b/core/list_bottle_data.php
index 9ae801db..1481a4b3 100644
--- a/core/list_bottle_data.php
+++ b/core/list_bottle_data.php
@@ -34,11 +34,14 @@
$r['height'] = (double)$rq['height']?:0;
$r['width'] = (double)$rq['width']?:0;
$r['diameter'] = (double)$rq['diameter']?:0;
+ $r['weight'] = (double)$rq['weight']?:0;
$r['supplier'] = (string)$rq['supplier']?:'N/A';
$r['supplier_link'] = (string)$rq['supplier_link']?:'N/A';
$r['notes'] = (string)$rq['notes']?:'N/A';
$r['pieces'] = (int)$rq['pieces']?:0;
-
+ $r['created'] = (string)$rq['created']?:'00:00:00';
+ $r['updated'] = (string)$rq['updated']?:'00:00:00';
+
$photo = mysqli_fetch_array(mysqli_query($conn,"SELECT docData FROM documents WHERE type = '4' AND ownerID = '".$r['id']."'"));
$r['photo'] = (string)$photo['docData']?:'data:image/png;base64,'.$defImage;
diff --git a/core/list_customer_data.php b/core/list_customer_data.php
index a97320e8..0c5a6ce8 100644
--- a/core/list_customer_data.php
+++ b/core/list_customer_data.php
@@ -33,7 +33,9 @@
$r['phone'] = (string)$rq['phone']?:'N/A';
$r['email'] = (string)$rq['email']?:'N/A';
$r['web'] = (string)$rq['web']?:'N/A';
-
+ $r['created'] = (string)$rq['created']?:'00:00:00';
+ $r['updated'] = (string)$rq['updated']?:'00:00:00';
+
$rx[]=$r;
}
$total = mysqli_fetch_assoc(mysqli_query($conn,"SELECT COUNT(id) AS entries FROM customers"));
diff --git a/core/list_formula_attachments_data.php b/core/list_formula_attachments_data.php
index 4ae53f10..93bb47e4 100644
--- a/core/list_formula_attachments_data.php
+++ b/core/list_formula_attachments_data.php
@@ -20,6 +20,7 @@
$r['type'] = (int)$doc['type'];
$r['name'] = (string)$doc['name']?:'N/A';
$r['notes'] = (string)$doc['notes']?:'N/A';
+ $r['created'] = (string)$doc['created']?:'N/A';
$r['docData'] = (string)$doc['docData'];
$r['docSize'] = (string)formatBytes(strlen($doc['docData']));
diff --git a/core/list_inventory_compounds_data.php b/core/list_inventory_compounds_data.php
new file mode 100644
index 00000000..fcccfbb8
--- /dev/null
+++ b/core/list_inventory_compounds_data.php
@@ -0,0 +1,88 @@
+ $type['name'],
+ 'concentration' => $type['concentration'],
+ 'bottles_total' => calculateBottles($bottleSize, $type['concentration'], $defBtlSize)
+ ];
+ }
+
+ $r['breakDown'] = $rt;
+ $rx[] = $r;
+}
+
+$total = mysqli_fetch_assoc(mysqli_query($conn,"SELECT COUNT(id) AS entries FROM $t"));
+$filtered = mysqli_fetch_assoc(mysqli_query($conn,"SELECT COUNT(id) AS entries FROM $t ".$f));
+
+$response = array(
+ "draw" => (int)$_POST['draw'],
+ "recordsTotal" => (int)$total['entries'],
+ "recordsFiltered" => (int)$filtered['entries'],
+ "data" => $rx
+);
+
+if(empty($r)){
+ $response['data'] = [];
+}
+
+header('Content-Type: application/json; charset=utf-8');
+echo json_encode($response);
+return;
+?>
diff --git a/css/vault.css b/css/vault.css
index 5c855629..00004414 100755
--- a/css/vault.css
+++ b/css/vault.css
@@ -1449,3 +1449,9 @@ th.dt-center, td.dt-center {
#liveToast {
width: 100%;
}
+
+.pvScale-data-footer {
+ position: -webkit-sticky;
+ position: sticky;
+ bottom: 0;
+}
diff --git a/db/pvault.sql b/db/pvault.sql
index 46a1e640..288b6b68 100755
--- a/db/pvault.sql
+++ b/db/pvault.sql
@@ -28,10 +28,13 @@ CREATE TABLE `bottles` (
`height` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
`width` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
`diameter` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
+ `weight` DOUBLE NOT NULL DEFAULT '0',
`supplier` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
`supplier_link` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
`notes` text COLLATE utf8_general_ci DEFAULT NULL,
- `pieces` int(11) NOT NULL DEFAULT 0
+ `pieces` int(11) NOT NULL DEFAULT 0,
+ `updated` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL,
+ `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
CREATE TABLE `customers` (
@@ -40,7 +43,10 @@ CREATE TABLE `customers` (
`address` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
`email` varchar(225) COLLATE utf8_general_ci DEFAULT NULL,
`phone` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
- `web` varchar(255) COLLATE utf8_general_ci DEFAULT NULL
+ `web` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
+ `owner_id` INT NOT NULL DEFAULT '0',
+ `updated` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL,
+ `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
CREATE TABLE `formulas` (
@@ -495,6 +501,7 @@ CREATE TABLE `documents` (
`name` varchar(255) COLLATE utf8_general_ci NOT NULL,
`notes` varchar(255) COLLATE utf8_general_ci DEFAULT NULL,
`docData` longblob NOT NULL,
+ `isBatch` INT NOT NULL DEFAULT '0',
`created` datetime NOT NULL DEFAULT current_timestamp(),
`updated` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
@@ -599,6 +606,7 @@ CREATE TABLE `formulasRevisions` (
CREATE TABLE `formula_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fid` varchar(255) NOT NULL,
+ `ing_id` INT NOT NULL DEFAULT '0',
`change_made` text COLLATE utf8_general_ci NOT NULL,
`date_time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`user` varchar(255) COLLATE utf8_general_ci NOT NULL,
@@ -679,7 +687,9 @@ CREATE TABLE `backup_provider` (
`description` varchar(255) NOT NULL,
`gdrive_name` varchar(255) NOT NULL DEFAULT 'pvault',
UNIQUE KEY `id` (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
INSERT INTO `backup_provider` (`id`, `credentials`, `provider`, `schedule`, `enabled`, `description`, `gdrive_name`) VALUES
-(1, '{}', 'Google', '00:00:00', 1, 'My PV Backups', 'pvault');
\ No newline at end of file
+(1, '{}', 'Google', '00:00:00', 1, 'My PV Backups', 'pvault');
+
+CREATE TABLE `inventory_compounds` ( `id` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `description` TEXT NOT NULL , `batch_id` VARCHAR(255) NOT NULL DEFAULT '-' , `size` DOUBLE NOT NULL DEFAULT '0' , `updated` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `owner_id` INT NOT NULL DEFAULT '0' , `location` VARCHAR(255) NOT NULL , `label_info` TEXT NOT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB CHARSET=utf8 COLLATE utf8_general_ci;
\ No newline at end of file
diff --git a/db/schema.ver b/db/schema.ver
index a61a79be..2f52450b 100644
--- a/db/schema.ver
+++ b/db/schema.ver
@@ -1 +1 @@
-9.9
+10.0
diff --git a/db/updates/update_9.9-10.0.sql b/db/updates/update_9.9-10.0.sql
new file mode 100644
index 00000000..95c4af1f
--- /dev/null
+++ b/db/updates/update_9.9-10.0.sql
@@ -0,0 +1,6 @@
+CREATE TABLE `inventory_compounds` ( `id` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `description` TEXT NOT NULL , `batch_id` VARCHAR(255) NOT NULL DEFAULT '-' , `size` DOUBLE NOT NULL DEFAULT '0' , `updated` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `owner_id` INT NOT NULL DEFAULT '0' , `location` VARCHAR(255) NOT NULL , `label_info` TEXT NOT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB CHARSET=utf8 COLLATE utf8_general_ci;
+ALTER TABLE `documents` ADD `isBatch` INT NOT NULL DEFAULT '0' AFTER `docData`;
+ALTER TABLE `bottles` ADD `weight` DOUBLE NOT NULL DEFAULT '0' AFTER `supplier`;
+ALTER TABLE `customers` ADD `owner_id` INT NOT NULL DEFAULT '0' AFTER `web`, ADD `updated` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL AFTER `owner_id`, ADD `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `updated`;
+ALTER TABLE `bottles` ADD `updated` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL AFTER `pieces`, ADD `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `updated`;
+ALTER TABLE `formula_history` ADD `ing_id` INT NOT NULL DEFAULT '0' AFTER `fid`;
\ No newline at end of file
diff --git a/func/genBatchPDF.php b/func/genBatchPDF.php
index 253148fb..d282af8f 100644
--- a/func/genBatchPDF.php
+++ b/func/genBatchPDF.php
@@ -284,7 +284,7 @@ function Code39($xpos, $ypos, $code, $baseline=0.5, $height=5){
$docData = 'data:application/pdf;base64,' .$pdf;
if($formulaTable == "makeFormula"){
- mysqli_query($conn, "INSERT INTO documents (ownerID,type,name,notes,docData) VALUES (".$meta['id'].",'5','$batchID','Auto generated by Formula Make','$docData')");
+ mysqli_query($conn, "INSERT INTO documents (ownerID,type,name,notes,docData,isBatch) VALUES (".$meta['id'].",'5','$batchID','Auto generated by Formula Make','$docData','1')");
}else{
diff --git a/img/pvScaleSP1.png b/img/pvScaleSP1.png
new file mode 100644
index 00000000..da328997
Binary files /dev/null and b/img/pvScaleSP1.png differ
diff --git a/index.php b/index.php
index fd9c4b09..012597ac 100755
--- a/index.php
+++ b/index.php
@@ -212,7 +212,7 @@ function chkUpdate() {
" href="/?do=ingredients">Ingredients
Suppliers
Customers
+ Compounds
+
Bottles
Bottle Lids
@@ -313,7 +315,8 @@ function chkUpdate() {
require_once(__ROOT__.'/pages/customers.php');
}elseif($_GET['do'] == 'compareFormulas'){
require_once(__ROOT__.'/pages/compareFormulas.php');
-
+ }elseif($_GET['do'] == 'compounds'){
+ require_once(__ROOT__.'/pages/compounds.php');
}else{
require_once(__ROOT__.'/pages/dashboard.php');
}
diff --git a/js/import.compounds.js b/js/import.compounds.js
new file mode 100644
index 00000000..e6867257
--- /dev/null
+++ b/js/import.compounds.js
@@ -0,0 +1,118 @@
+/*
+IMPORT INGREDIENTS JSON
+*/
+
+function uploadProgressHandler(event) {
+ $("#loaded_n_total").html("Uploaded " + event.loaded + " bytes of " + event.total);
+ var percent = (event.loaded / event.total) * 100;
+ var progress = Math.round(percent);
+ $("#uploadProgressBar").html(progress + " %");
+ $("#uploadProgressBar").css("width", progress + "%");
+}
+
+function loadHandler(event) {
+ $("#status").html(event.target.responseText);
+ $(".progress").hide();
+ //$("#btnImportCompounds").hide();
+ $("#jsonFile").val('');
+ $('#btnCloseBK').prop('value', 'Close');
+ $("#uploadProgressBar").css("width", "0%");
+}
+
+function errorHandler(event) {
+ $("#JSRestMsg").html('Upload failed
');
+}
+
+function abortHandler(event) {
+ $("#JSRestMsg").html('Upload Aborted
');
+}
+
+$(".progress").hide();
+$("#btnImportCompounds").prop("disabled", true);
+$("#jsonFile").change(function(){
+ var allowedTypes = ['application/json'];
+ var file = this.files[0];
+ var fileType = file.type;
+ var fileSize = file.size;
+ $("#JSRestMsg").html('');
+ var fileSizePHP = $("#raw").data("size");
+ if(!allowedTypes.includes(fileType)){
+ $("#JSRestMsg").html('Invalid file selected. Please select a JSON file exported from PV.
');
+ $("#jsonFile").val('');
+ $("#btnImportCompounds").prop("disabled", true);
+ return false;
+ }
+
+ if (fileSize > fileSizePHP){
+ $("#JSRestMsg").html('File size ('+formatBytes(fileSize)+') is exceeding your server file upload limit '+ formatBytes(fileSizePHP)+'
');
+ $("#jsonFile").val('');
+ $("#btnImportCompounds").prop("disabled", true);
+ return false;
+ }
+
+ $("#btnImportCompounds").prop("disabled", false);
+ $('#btnImportCompounds').prop('value', 'Import');
+});
+
+//RESTORE Ingredients
+$('#btnImportCompounds').click(function() {
+
+ event.preventDefault();
+ var fd = new FormData();
+ var files = $('#jsonFile')[0].files;
+
+ if(files.length > 0 ){
+ fd.append('jsonFile',files[0]);
+ }
+
+ $.ajax({
+ url: '/pages/operations.php?action=importCompounds',
+ type: 'POST',
+ data: fd,
+ contentType: false,
+ processData: false,
+ cache: false,
+ dataType: 'json',
+ xhr: function () {
+ var xhr = new window.XMLHttpRequest();
+ xhr.upload.addEventListener("progress",
+ uploadProgressHandler,
+ false
+ );
+ xhr.addEventListener("load", loadHandler, false);
+ xhr.addEventListener("error", errorHandler, false);
+ xhr.addEventListener("abort", abortHandler, false);
+ $(".progress").show();
+ $("#btnImportCompounds").prop("disabled", true);
+ $('#btnImportCompounds').prop('value', 'Please wait...');
+ return xhr;
+ },
+
+ success: function (data) {
+ if(data.success){
+ var msg = ' '+data.success+'
';
+ $("#btnImportCompounds").hide();
+ $("#backupArea").css('display', 'none');
+
+ }else if(data.error){
+ var msg = ''+data.error+'
';
+ $("#btnImportCompounds").show();
+ $("#btnImportCompounds").prop("disabled", false);
+ $('#btnImportCompounds').prop('value', 'Import');
+ }
+ $('#btnImportCompounds').prop('value', 'Import');
+ $("#btnImportCompounds").prop("disabled", false);
+ $('#JSRestMsg').html(msg);
+ }
+
+ });
+});
+
+function formatBytes(bytes, decimals = 2) {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+}
diff --git a/js/pvScale.js b/js/pvScale.js
new file mode 100644
index 00000000..07bbb0d8
--- /dev/null
+++ b/js/pvScale.js
@@ -0,0 +1,190 @@
+//
+//PV SCALE JS
+//
+
+$(document).ready(function() {
+ var msg = "";
+ pvScaleConnVal();
+
+ $('#chkConn').click(function(event) {
+ pvScaleConnVal();
+ });
+
+ $('#scaleScreenOn').click(function(event) {
+ $.ajax({
+ url: '/pages/views/pvscale/manage.php',
+ type: 'GET',
+ data: {
+ action: 'screen',
+ status: 1
+ },
+ dataType: 'json',
+ success: function(data) {
+ if (data.success) {
+ msg = ' Screen on
';
+ } else {
+ msg = ' ' + data.error + '
';
+ }
+ $('#scmsg').html(msg);
+ }
+ });
+ });
+
+ $('#scaleScreenOff').click(function(event) {
+ $.ajax({
+ url: '/pages/views/pvscale/manage.php',
+ type: 'GET',
+ data: {
+ action: 'screen',
+ status: 0
+ },
+ dataType: 'json',
+ success: function(data) {
+ if (data.success) {
+ msg = ' Scrreen off
';
+ } else {
+ msg = ' ' + data.error + '
';
+ }
+ $('#scmsg').html(msg);
+ }
+ });
+ });
+
+ $('#chkFirm').click(function() {
+ $('#scmsg').html(' Please wait...
');
+
+ $.ajax({
+ url: '/pages/views/pvscale/manage.php',
+ type: 'GET',
+ data: {
+ action: 'firmwareCheck'
+ },
+ dataType: 'json',
+ success: function(data) {
+ if (data.success === true) {
+ if(data.response.isUpgradable) {
+ msg = 'Upgrade available (' + data.response.ver + ').
Upgrade now ';
+ } else {
+ msg = ' ' + data.response.message + ' (' + data.response.ver + ')
';
+ }
+ } else {
+ msg = ' Unable to get data
';
+ }
+ $('#scmsg').html(msg);
+ $('#updFirm').click(function(e) {
+ e.preventDefault();
+ $('#scmsg').html(' Please wait...
');
+
+ $.ajax({
+ url: '/pages/views/pvscale/manage.php',
+ type: 'GET',
+ data: {
+ action: 'firmwareUpdate'
+ },
+ dataType: 'json',
+ success: function(data) {
+ if (data.success === true) {
+ if(data.response.isUpgradable) {
+ msg = 'Upgrade available (' + data.response.ver + ').
Upgrade now ';
+ } else {
+ msg = ' ' + data.response + ' (' + data.response.ver + ')
';
+ }
+ } else {
+ msg = ' ' + data.response + '
';
+ }
+ $('#scmsg').html(msg);
+ }
+ });
+
+ });
+ }
+ });
+
+ });
+
+
+
+
+ $('#subScale').click(function() {
+ pvScaleConnVal(function(success) {
+ if (success == true) {
+ var pv_scale_enabled = $('#pv_scale_enabled').is(':checked') ? '1' : '0';
+ } else {
+ var pv_scale_enabled = '0';
+ }
+ $.ajax({
+ url: '/pages/views/pvscale/manage.php',
+ type: 'POST',
+ data: {
+ action: 'update',
+ enabled: pv_scale_enabled,
+ pv_scale_host: $("#pv_scale_host").val()
+ },
+ dataType: 'json',
+ success: function(data) {
+ if (data.success) {
+ msg = ' ' + data.success + '
';
+ } else {
+ msg = ' ' + data.error + '
';
+ }
+ $('#scmsg').html(msg);
+ }
+ });
+
+ });
+
+ });
+
+ function pvScaleConnVal(callback) {
+ //event.preventDefault(); // Prevent form submission
+ var success = false;
+
+ $('#scmsg').html(' Trying to connect...
');
+
+ $('#chkConn').addClass('d-none');
+ $('#connSpinner').removeClass('d-none');
+ $('#controlScale').addClass('d-none');
+
+
+ $.ajax({
+ url: '/pages/views/pvscale/manage.php',
+ type: 'POST',
+ data: {
+ ping: 1,
+ pv_scale_host: $("#pv_scale_host").val()
+ },
+ dataType: 'json',
+ success: function(data) {
+ if (data.success === true) {
+ var sysData = data.sysData;
+ $('#sysData').html(
+ 'MAC: ' + sysData.mac + '
' +
+ 'SSID: ' + sysData.ssid + '
' +
+ 'IP: ' + sysData.ip + '
' +
+ 'Calibration Factor: ' + sysData.calibration_factor + '
' +
+ 'PV Scale Version: ' + sysData.pvScaleVersion + '
'
+ );
+ $('#controlScale').removeClass('d-none');
+ $('#scmsg').html('');
+ success = true;
+ } else {
+ $('#sysData').html('');
+ $('#controlScale').addClass('d-none');
+ $('#scmsg').html(' Connection failed
');
+ }
+ },
+ error: function() {
+ $('#sysData').html('');
+ $('#scmsg').html(' Network error
');
+ },
+ complete: function() {
+ $('#chkConn').removeClass('d-none');
+ $('#connSpinner').addClass('d-none');
+ if (typeof callback === 'function') {
+ callback(success);
+ }
+ }
+ });
+}
+
+});
diff --git a/pages/IFRA.php b/pages/IFRA.php
index de812fb4..8c298c07 100755
--- a/pages/IFRA.php
+++ b/pages/IFRA.php
@@ -7,7 +7,7 @@
@@ -207,7 +207,9 @@
diff --git a/pages/compounds.php b/pages/compounds.php
new file mode 100644
index 00000000..9cb75bc7
--- /dev/null
+++ b/pages/compounds.php
@@ -0,0 +1,451 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Description
+ Batch
+ Size()
+ Label
+ Location
+ Added
+ Updated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Compound name
+
+
+
+ Batch
+
+
+ '.$b['name'].'';
+ }
+ ?>
+
+
+
+ Bottle size ()
+
+
+
+ Location
+
+
+
+ Short Description
+
+
+
+ Label info
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/customers.php b/pages/customers.php
index afcd42d3..6e1e9d81 100644
--- a/pages/customers.php
+++ b/pages/customers.php
@@ -4,11 +4,10 @@
-
@@ -17,6 +16,8 @@
@@ -29,6 +30,8 @@
Address
Email
Web Site
+ Created
+ Updated
@@ -41,34 +44,42 @@
-
-
+
+
@@ -89,11 +100,13 @@
diff --git a/pages/listFormulas.php b/pages/listFormulas.php
index c321f548..75476d85 100644
--- a/pages/listFormulas.php
+++ b/pages/listFormulas.php
@@ -31,7 +31,7 @@
\ No newline at end of file
diff --git a/pages/views/pvscale/buy.php b/pages/views/pvscale/buy.php
new file mode 100644
index 00000000..5bcdd9da
--- /dev/null
+++ b/pages/views/pvscale/buy.php
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PV Scale is a specially designed formulation scale which will weight, guide you and automatically update your inventory when you formulating.
+
+ Its currently not available to buy but you can use discussions page in github to request/discuss features and to be notified when is available.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/views/pvscale/configure.php b/pages/views/pvscale/configure.php
new file mode 100644
index 00000000..1c6b8357
--- /dev/null
+++ b/pages/views/pvscale/configure.php
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+ Scale IP
+
+
+
+ >
+ Enabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/views/pvscale/manage.php b/pages/views/pvscale/manage.php
index 0237f2b3..d0e85ea6 100644
--- a/pages/views/pvscale/manage.php
+++ b/pages/views/pvscale/manage.php
@@ -8,6 +8,55 @@
$PVSCALE = $settings['pv_scale_host'];
+
+if ($_POST['action'] == 'update' ){
+
+ if (!filter_var($_POST['pv_scale_host'], FILTER_VALIDATE_IP)) {
+ $result['error'] = "Scale IP is invalid";
+ echo json_encode($result);
+ return;
+ }
+ $pv_scale_enabled = (int)$_POST['enabled'];
+ $q = mysqli_query($conn,"UPDATE settings SET pv_scale_enabled = '$pv_scale_enabled'");
+
+ if($q){
+ $result['success'] = "Settings updated";
+ } else {
+ $result['error'] = "Unable to update settings ".mysqli_error($conn);
+ }
+
+ echo json_encode($result);
+ return;
+}
+
+
+if ($_POST['ping']){
+ $pvScHost = $_POST['pv_scale_host'];
+ $timeout = 30; // Timeout in seconds
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $pvScHost."/ping");
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+ $response = curl_exec($ch);
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+
+ if ($http_code == 200 && $response == '{"response":"pong"}') {
+ $sysResponse = file_get_contents("http://".$pvScHost."/sys");
+ echo json_encode([
+ 'success' => true,
+ 'data' => $response,
+ 'sysData' => json_decode($sysResponse, true),
+ 'msg' => "Connection was successful"
+ ]);
+ } else {
+ echo json_encode(['success' => false, 'data' => $response]);
+ }
+ return;
+}
+
if ($_GET['action'] == 'check_reload_signal'){
$reloadSignal = file_get_contents($tmp_path.'reload_signal.txt');
echo $reloadSignal;
@@ -19,50 +68,60 @@
return;
}
-if ($_GET['action'] == 'version'){
- $url = "http://$PVSCALE/version";
+if ($_GET['action'] == 'firmwareCheck'){
+ $url = "http://$PVSCALE/firmware/check";
$response = file_get_contents($url);
if ($response !== false) {
$responseData = json_decode($response);
if ($responseData !== null) {
- echo json_encode(['success' => true, 'data' => $responseData]);
+ echo json_encode(['success' => true, 'response' => $responseData]);
} else {
- echo json_encode(['success' => false, 'error' => 'Error decoding JSON response']);
+ echo json_encode(['success' => false, 'response' => 'Error decoding JSON response']);
}
} else {
- echo json_encode(['success' => false, 'error' => 'Error occurred getting version']);
+ echo json_encode(['success' => false, 'response' => 'Error occurred getting version']);
}
return;
}
+if ($_GET['action'] == 'firmwareUpdate'){
+ $url = "http://$PVSCALE/firmware/update";
+ $response = file_get_contents($url);
+
+ if ($response !== false) {
+ $responseData = json_decode($response);
+ if ($responseData->success == true) {
+
+ echo json_encode(['success' => true, 'response' => $responseData->message]);
+
+ } else {
+ echo json_encode(['success' => false, 'response' => $responseData->message]);
+ }
+ } else {
+ echo json_encode(['success' => false, 'response' => $responseData->message]);
+ }
+ return;
+}
if ($_GET['action'] == 'send2PVScale') {
$url = "http://$PVSCALE/api";
$jsonData = file_get_contents('php://input');
- // Decode the JSON data
$requestData = json_decode($jsonData);
- // Check if decoding was successful
if ($requestData !== null) {
- // Prepare data for sending
$postData = json_encode($requestData);
- // Initialize curl session
$ch = curl_init();
- // Set curl options
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- // Execute curl session
$response = curl_exec($ch);
- // Check for errors
if ($response === false) {
echo json_encode(['success' => false, 'error' => 'Error sending data to the remote server: ' . curl_error($ch)]);
} else {
- // Process the response
$responseData = json_decode($response);
if ($responseData !== null) {
echo json_encode(['success' => true, 'message' => $responseData->message]);
@@ -71,7 +130,6 @@
}
}
- // Close curl session
curl_close($ch);
} else {
echo json_encode(['success' => false, 'error' => 'Error decoding JSON data']);
@@ -80,7 +138,25 @@
return;
}
+if ($_GET['action'] == 'screen'){
+ $status = $_GET['status'];
+ $url = "http://$PVSCALE/display/$status";
+ $response = file_get_contents($url);
+
+ if ($response !== false) {
+ $responseData = json_decode($response);
+ if ($responseData !== null) {
+ echo json_encode(['success' => true, 'response' => $responseData]);
+
+ } else {
+ echo json_encode(['success' => false, 'response' => 'Error decoding JSON response']);
+ }
+ } else {
+ echo json_encode(['success' => false, 'response' => 'Error occurred getting version']);
+ }
+ return;
+}
diff --git a/pages/views/settings/integrations.php b/pages/views/settings/integrations.php
index 0837cf36..06ba44d1 100644
--- a/pages/views/settings/integrations.php
+++ b/pages/views/settings/integrations.php
@@ -12,6 +12,12 @@
}else{
$state = '
Disabled ';
}
+
+if($settings['pv_scale_enabled']){
+ $scaleState = '
Enabled ';
+}else{
+ $scaleState = '
Disabled ';
+}
?>
Integrations
@@ -46,7 +52,7 @@ class="fas fa-arrows-rotate mx-2">Restart
-
+
@@ -105,7 +111,27 @@ class="fas fa-cart-shopping mx-2">Buy a PV Scale
$(".modal-body", this).html(data);
});
});
+
+ $("#configureScale").on("show.bs.modal", function(e) {
+ const id = e.relatedTarget.dataset.id;
+ const name = e.relatedTarget.dataset.name;
+
+ $.get("/pages/views/pvscale/configure.php")
+ .then(data => {
+ $(".modal-body", this).html(data);
+ });
+ });
+ $("#buyScale").on("show.bs.modal", function(e) {
+ const id = e.relatedTarget.dataset.id;
+ const name = e.relatedTarget.dataset.name;
+
+ $.get("/pages/views/pvscale/buy.php")
+ .then(data => {
+ $(".modal-body", this).html(data);
+ });
+ });
+
$("#listBackup").on("show.bs.modal", function(e) {
const id = e.relatedTarget.dataset.id;
const name = e.relatedTarget.dataset.name;
@@ -348,3 +374,37 @@ function try_restart(){
+
+
+
+
+
+
\ No newline at end of file
diff --git a/releasenotes.md b/releasenotes.md
index 0fa8207a..9e94d182 100755
--- a/releasenotes.md
+++ b/releasenotes.md
@@ -1,11 +1,12 @@
-Whats New in v9.9
+Whats New in v10.0
--------------------------
-- Record replaced ingredients when finalising a formula in the generated PDF
-- Increase toast duration for formula making to 10 seconds
-- Update view for bottle add modal window
-- Choose a replacement material when making a formula and update stock and final documents automatically
-- Skip a mterial when you making a formula
-- Increase update interval check from PV Scale to 5s
-- Added separate notes field for Formula Make
-- When a formula is marked as completed it generates a document in formula attachments
+- Discord - please join here: https://discord.gg/dsQwDAem
+- Added archive option when deleting a formula
+- Added inventory create/update info for compounds
+- Added JSON import/export for compounds
+- Allow a formula to be marked complete when contains skipped materials
+- Bottle edit page format update
+- Weight added for the bottles inventory
+- Added Inventory for finished Compounds
+- Minor improvements in formula attachments section
- For full details please refer to the CHANGELOG