Commit db6cb3f3 authored by Malcolm Blaney's avatar Malcolm Blaney

Stock tracking is now per product, update purchase availability

from order availability can be done per supplier. Manager page can
search for multiple suppliers at once using csv's. Report page can
download accounts data.
parent 2b20c848
......@@ -1709,7 +1709,21 @@ class Purchase extends Base {
if ($search !== '') {
$search .= ' AND ';
}
$search .= 'supplier = "'.$supplier.'"';
// Allow supplier to be a comma separated list of usernames.
if (strpos($supplier, ',') !== false) {
$supplier_search = '';
$supplier_list = explode(',', $supplier);
for ($i = 0; $i < count($supplier_list); $i++) {
if ($supplier_search !== '') {
$supplier_search .= ' OR ';
}
$supplier_search .= 'supplier = "'.trim($supplier_list[$i]).'"';
}
$search .= '('.$supplier_search.')';
}
else {
$search .= 'supplier = "'.$supplier.'"';
}
// When searching for a supplier and no user was given, show the
// base_price which is the amount the supplier receives.
if ($user === '') $price_query = 'base_price';
......
......@@ -334,6 +334,13 @@ class Report extends Base {
$info = '<b>'.count($data).'</b> accounts payable, total: <b>$';
}
$info .= number_format($total * -1, 2, '.', '').'</b>';
if ($_POST['download'] == '1' && count($data) > 0) {
$date = date('Y-m-d');
$filename = 'accounts-payable-'.$date.'.csv';
$this->CreateCSV($filename, $data, false, 'payment');
return array('info' => $info, 'data' => $data, 'filename' => $filename);
}
return array('info' => $info, 'data' => $data);
}
......@@ -672,6 +679,11 @@ class Report extends Base {
'<option value="activeMembers">member status</option>'.
'<option value="paidDeposit">paid deposit</option>'.
'</select>'.
'<div class="form-spacing">'.
'<label for="report-download-accounts">Download available data:'.
'</label>'.
'<input id="report-download-accounts" type="checkbox">'.
'</div>'.
'<div class="report-accounts-info"></div>'.
'</div>';
}
......@@ -752,6 +764,13 @@ class Report extends Base {
$info = '<b>'.$total.'</b> active members';
}
$info .= ' ('.(count($data) - $total).' inactive).';
if ($_POST['download'] == '1' && count($data) > 0) {
$date = date('Y-m-d');
$filename = 'active-members-'.$date.'.csv';
$this->CreateCSV($filename, $data, false, 'active');
return array('info' => $info, 'data' => $data, 'filename' => $filename);
}
return array('info' => $info, 'data' => $data);
}
......@@ -813,6 +832,13 @@ class Report extends Base {
}
$this->user->group = $default_group;
$info = 'Most recent attendance for <b>'.count($data).'</b> members.';
if ($_POST['download'] == '1' && count($data) > 0) {
$date = date('Y-m-d');
$filename = 'last-attendance-'.$date.'.csv';
$this->CreateCSV($filename, $data, true, 'date');
return array('info' => $info, 'data' => $data, 'filename' => $filename);
}
return array('info' => $info, 'data' => $data);
}
......@@ -859,6 +885,13 @@ class Report extends Base {
}
$this->user->group = $default_group;
$info = 'Current reminders for <b>'.count($data).'</b> members.';
if ($_POST['download'] == '1' && count($data) > 0) {
$date = date('Y-m-d');
$filename = 'membership-reminders-'.$date.'.csv';
$this->CreateCSV($filename, $data, true, 'date');
return array('info' => $info, 'data' => $data, 'filename' => $filename);
}
return array('info' => $info, 'data' => $data);
}
......@@ -920,6 +953,13 @@ class Report extends Base {
$info = '<b>'.$total.'</b> members have paid a deposit';
}
$info .= ' ('.(count($data) - $total).' have not).';
if ($_POST['download'] == '1' && count($data) > 0) {
$date = date('Y-m-d');
$filename = 'paid-deposit-'.$date.'.csv';
$this->CreateCSV($filename, $data, false, 'deposit');
return array('info' => $info, 'data' => $data, 'filename' => $filename);
}
return array('info' => $info, 'data' => $data);
}
......
This diff is collapsed.
......@@ -110,13 +110,13 @@ if(viewPurchases){$("#purchase-quota-info").html(showQuota("form"));}
else if(user===""){$("#purchase-quota-info").html(showQuota("all-users"));viewAllUsers();}
return false;}});if(!productFound){resetForm();}
setFormControls();$("#purchase-quantity-input").focus();}
function formatFloat(value){value=value.toFixed(3);if(value.indexOf(".")!==-1){for(var i=value.length-1;i>0;i--){if(value[i]==="0"){continue;}
function formatFloat(value){value=parseFloat(value);value=value.toFixed(3);if(value.indexOf(".")!==-1){for(var i=value.length-1;i>0;i--){if(value[i]==="0"){continue;}
if(value[i]==="."){value=value.substring(0,i);}
else{value=value.substring(0,i+1);}
break;}}
return value;}
function soldOut(quantity){if(currentProduct.quantity<0.001){alert("Could not add purchase: Product has sold out.");return true;}
if(quantity>currentProduct.quantity){var available=formatFloat(currentProduct.quantity);var unit=currentProduct.unit;if(unit==="each"||unit==="variable"){unit="";}
function soldOut(quantity){if(currentProduct.track&&currentProduct.quantity<0.001){alert("Could not add purchase: Product has sold out.");return true;}
if(currentProduct.track&&currentProduct.quantity<quantity){var available=formatFloat(currentProduct.quantity);var unit=currentProduct.unit;if(unit==="each"||unit==="variable"){unit="";}
alert("Could not add purchase: Only "+available+unit+" available.");return true;}
return false;}
function showQuota(input){var total=0;function setQuotaQuantity(i,item){if(item.name===currentProduct.name){total+=item.quantity;}}
......@@ -124,7 +124,7 @@ if(!currentProduct){return"";}
if(!orderMode&&input!=="all-users"){return"";}
if(currentProduct.size===0){return"";}
for(var user in purchase.data){$.each(purchase.data[user],setQuotaQuantity);}
var soldOutText='';if(purchase.stockLimited&&currentProduct.quantity<0.001){soldOutText=" <b>This product is sold out.</b>";if(input==="form"){$("#purchase-quantity-input").val("").spinner("disable");}}
var soldOutText='';if(purchase.stockLimited&&currentProduct.track&&currentProduct.quantity<0.001){soldOutText=" <b>This product is sold out.</b>";if(input==="form"){$("#purchase-quantity-input").val("").spinner("disable");}}
var remainder=0;remainder=total%currentProduct.size;var current=remainder;if(remainder===0&&total!==0){current=currentProduct.size;}
var percent=current/currentProduct.size*100;var color;if(percent<30){color="red";}
else if(percent<60){color="orange";}
......
......@@ -752,6 +752,8 @@ if (!this.dobrado.purchase) {
}
function formatFloat(value) {
// Call parseFloat first as toFixed gives an error if passed a string.
value = parseFloat(value);
// Make sure the calculated value doesn't end up with more than 3 decimal
// places and remove trailing zeros.
value = value.toFixed(3);
......@@ -774,12 +776,12 @@ if (!this.dobrado.purchase) {
function soldOut(quantity) {
// First check if this product has completely sold out.
if (currentProduct.quantity < 0.001) {
if (currentProduct.track && currentProduct.quantity < 0.001) {
alert("Could not add purchase: Product has sold out.");
return true;
}
// Otherwise check if adding the given quantity is too much.
if (quantity > currentProduct.quantity) {
if (currentProduct.track && currentProduct.quantity < quantity) {
var available = formatFloat(currentProduct.quantity);
var unit = currentProduct.unit;
if (unit === "each" || unit === "variable") {
......@@ -822,7 +824,8 @@ if (!this.dobrado.purchase) {
// input form because it's additive, but it's left enabled in the dialogs
// because quantity can be reduced there.
var soldOutText = '';
if (purchase.stockLimited && currentProduct.quantity < 0.001) {
if (purchase.stockLimited &&
currentProduct.track && currentProduct.quantity < 0.001) {
soldOutText = " <b>This product is sold out.</b>";
if (input === "form") {
$("#purchase-quantity-input").val("").spinner("disable");
......
......@@ -31,7 +31,7 @@ if(index===1){accountGridId='#'+$(this).attr('id');}});if($('.grid').length!==0)
if(value1===value2){return 0;}
if(value1>value2){return sign;}
else{return sign* -1;}});quotaGrid.invalidate();});}
if($('.grid').length>=2){var accountColumns=[{id:'user',name:'Username',field:'user',width:150,sortable:true},{id:'fullname',name:'Full Name',field:'fullname',width:200,sortable:true},{id:'email',name:'Email',field:'email',width:150,sortable:true},{id:'data',name:'Data',field:'data',width:100,sortable:true}];var accountGridOptions={enableColumnReorder:false,forceFitColumns:true};accountGrid=dobrado.grid.instance(accountGridId,[],accountColumns,accountGridOptions);accountGrid.onSort.subscribe(function(e,args){account.sort(function(row1,row2){var field=args.sortCol.field;var sign=args.sortAsc?1:-1;var value1=row1[field];var value2=row2[field];if(field==='data'){value1=parseFloat(value1);value2=parseFloat(value2);}
if($('.grid').length>=2){var accountColumns=[{id:'user',name:'Username',field:'user',width:150,sortable:true},{id:'fullname',name:'Full Name',field:'fullname',width:200,sortable:true},{id:'email',name:'Email',field:'email',width:150,sortable:true},{id:'data',name:'Data',field:'data',width:100,sortable:true}];var accountGridOptions={enableColumnReorder:false,forceFitColumns:true};accountGrid=dobrado.grid.instance(accountGridId,[],accountColumns,accountGridOptions);accountGrid.onSort.subscribe(function(e,args){account.sort(function(row1,row2){var field=args.sortCol.field;var sign=args.sortAsc?1:-1;var value1=row1[field];var value2=row2[field];if(field==='data'){value1=value1===''?0:parseFloat(value1);value2=value2===''?0:parseFloat(value2);}
if(value1===value2){return 0;}
if(value1>value2){return sign;}
else{return sign* -1;}});accountGrid.invalidate();});}
......@@ -78,10 +78,11 @@ var end=parseInt($.datepicker.formatDate('@',$('#report-attendance-end').datepic
dobrado.log('Loading attendance history...','info');$.post('/php/request.php',{request:'report',action:'attendanceHistory',group:$('#report-group-select').val(),start:start,end:end,url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'report attendanceHistory')){return;}
var attendance=JSON.parse(response);$('.report-attendance-date').html(attendance.date);$('.report-attendance-total').html(attendance.total);$('.report-attendance').show();if(attendanceGraphId){$('#'+attendanceGraphId).html('').parent().hide();$('#'+attendanceGraphId).parent().appendTo('.report-attendance-graph');if(attendance.data){$('#'+attendanceGraphId).parent().show();dobrado.graph.loadData(attendanceGraphId,attendance.data,attendance.series);}}});}
function accountOptions(){var option=$('#report-accounts-select').val();if(option===''){return false;}
dobrado.log('Loading account information...','info');$.post('/php/request.php',{request:'report',action:option,group:$('#report-group-select').val(),url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'report accountOptions')){return;}
dobrado.log('Loading account information...','info');var download=$('#report-download-accounts:checked').length;$.post('/php/request.php',{request:'report',action:option,group:$('#report-group-select').val(),download:download,url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'report accountOptions')){return;}
var result=JSON.parse(response);account=result.data;if(accountGrid){$(accountGridId).appendTo('.report-accounts');var columns=accountGrid.getColumns();if(option==='membershipReminders'||option==='lastAttendance'){columns[3].name='Date';columns[3].formatter=Slick.Formatters.Timestamp;}
else if(option==='accountsPayable'){columns[3].name='Payment';columns[3].formatter=Slick.Formatters.Dollar;}
else if(option==='activeMembers'){columns[3].name='Active';columns[3].formatter=Slick.Formatters.Checkmark;}
else if(option==='paidDeposit'){columns[3].name='Deposit';columns[3].formatter=Slick.Formatters.Checkmark;}
$(accountGridId).hide();if(account&&account.length!==0){accountGrid.setColumns(columns);accountGrid.setData(account);accountGrid.updateRowCount();accountGrid.render();$(accountGridId).show();}
$('.report-accounts-info').html(result.info);}});}})();
\ No newline at end of file
$('.report-accounts-info').html(result.info);}
if(download===1&&result.filename){location.href="/php/private.php?file="+result.filename;}});}})();
\ No newline at end of file
......@@ -123,8 +123,8 @@ if (!this.dobrado.report) {
var value1 = row1[field];
var value2 = row2[field];
if (field === 'data') {
value1 = parseFloat(value1);
value2 = parseFloat(value2);
value1 = value1 === '' ? 0 : parseFloat(value1);
value2 = value2 === '' ? 0 : parseFloat(value2);
}
if (value1 === value2) {
return 0;
......@@ -531,9 +531,11 @@ if (!this.dobrado.report) {
}
dobrado.log('Loading account information...', 'info');
var download = $('#report-download-accounts:checked').length;
$.post('/php/request.php', { request: 'report',
action: option,
group: $('#report-group-select').val(),
download: download,
url: location.href,
token: dobrado.token },
function(response) {
......@@ -571,6 +573,9 @@ if (!this.dobrado.report) {
}
$('.report-accounts-info').html(result.info);
}
if (download === 1 && result.filename) {
location.href = "/php/private.php?file=" + result.filename;
}
});
}
......
This diff is collapsed.
......@@ -77,6 +77,7 @@ if (!this.dobrado.stock) {
$("#stock-size-input").change(updateSize);
$("#stock-import-file").val("").change(loadImportData);
$(".stock-add-supplier").button().click(dobrado.account.registerSupplier);
$("#stock-new-quantity-input").change(checkTracking);
$(".stock-quantity-dialog .submit").button().click(adjustQuantity);
$(".stock-quantity-dialog .show-all").button().click(showAllAdjustments);
$(".stock-quantity-dialog .export").button().click(exportAdjustments);
......@@ -292,6 +293,12 @@ if (!this.dobrado.stock) {
event.preventDefault();
}
function checkTracking() {
if ($(this).val() !== "" && $(this).val() !== "0") {
$("#stock-track-input").prop("checked", true);
}
}
function showMoveSupplier(product) {
if ($("#stock-move-user-input").val() !== "") {
return;
......@@ -344,6 +351,7 @@ if (!this.dobrado.stock) {
prop("checked", item.supplierAvailable === 1);
$("#stock-taxable-input").prop("checked", item.taxable === 1);
$("#stock-composite-input").prop("checked", item.composite === 1);
$("#stock-track-input").prop("checked", item.track === 1);
$("#stock-alternative").prop("checked", false);
$("#stock-alternative").button("refresh");
$("#stock-alternative").button("option", "disabled",
......@@ -777,6 +785,7 @@ if (!this.dobrado.stock) {
var supplierAvailable = $("#stock-supplier-available-input:checked").length;
var taxable = $("#stock-taxable-input:checked").length;
var composite = $("#stock-composite-input:checked").length;
var track = $("#stock-track-input:checked").length;
var selectedRow = 0;
var selected = stockGrid.getSelectedRows();
......@@ -804,6 +813,7 @@ if (!this.dobrado.stock) {
supplierAvailable: supplierAvailable,
taxable: taxable,
composite: composite,
track: track,
url: location.href,
token: dobrado.token },
function(response) {
......@@ -854,6 +864,7 @@ if (!this.dobrado.stock) {
supplierAvailable: supplierAvailable,
taxable: taxable,
composite: composite,
track: track,
url: location.href,
token: dobrado.token },
function(response) {
......@@ -889,7 +900,7 @@ if (!this.dobrado.stock) {
purchaseAvailable: purchaseAvailable,
supplierAvailable: supplierAvailable,
taxable: taxable, composite: composite,
updated: item.updated,
track: track, updated: item.updated,
alternative: item.alternative };
selectedRow = i;
newItem = false;
......@@ -905,7 +916,8 @@ if (!this.dobrado.stock) {
available: available,
purchaseAvailable: purchaseAvailable,
supplierAvailable: supplierAvailable,
taxable: taxable, updated: item.updated,
taxable: taxable, composite: composite,
track: track, updated: item.updated,
alternative: item.alternative };
selectedRow = i;
newItem = false;
......@@ -921,7 +933,8 @@ if (!this.dobrado.stock) {
available: available,
purchaseAvailable: purchaseAvailable,
supplierAvailable: supplierAvailable,
taxable: taxable, updated: 0, alternative: 0 });
taxable: taxable, composite: composite,
track: track, updated: 0, alternative: 0 });
// Can then update the product autocomplete list.
updateProducts();
// Sort the stock by name.
......@@ -1050,6 +1063,7 @@ if (!this.dobrado.stock) {
$("#stock-supplier-available-input").prop("checked", false);
$("#stock-taxable-input").prop("checked", false);
$("#stock-composite-input").prop("checked", false);
$("#stock-track-input").prop("checked", false);
currentProduct = null;
}
......
......@@ -278,7 +278,8 @@ abstract class Base {
$mysqli->close();
}
protected function CreateCSV($filename, $data, $convert_date = true) {
protected function CreateCSV($filename, $data,
$convert_date = true, $data_label = '') {
if (!is_array($data) || !isset($data[0])) return false;
$content = '';
......@@ -286,8 +287,15 @@ abstract class Base {
$header = array_keys($data[0]);
for ($i = 0; $i < count($header); $i++) {
if ($content !== '') $content .= ',';
// Quote all values and also remove existing quotes.
$content .= '"'.preg_replace('/"/', '', $header[$i]).'"';
// The report module has to use the generic key "data" for one column,
// allow it to change the name used in the file.
if ($data_label !== '' && $header[$i] == 'data') {
$content .= '"'.$data_label.'"';
}
else {
// Quote all values and also remove existing quotes.
$content .= '"'.preg_replace('/"/', '', $header[$i]).'"';
}
}
$content .= "\n";
for ($i = 0; $i < count($data); $i++) {
......@@ -297,8 +305,11 @@ abstract class Base {
$value = $data[$i][$header[$j]];
// Convert timestamps (which are assumed to be in milliseconds for js)
// to human readable dates.
if ($convert_date && $header[$j] == 'date') {
$value = date('Y-m-d', $value / 1000);
if ($convert_date && ($header[$j] == 'date' ||
$header[$j] == 'data' && $data_label == 'date')) {
if ($value !== 0 && $value !== '') {
$value = date('Y-m-d', $value / 1000);
}
}
// Quote all values and also remove existing quotes.
$row .= '"'.preg_replace('/"/', '', $value).'"';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment