Commit bd44195e authored by Mal's avatar Mal

New purchase module to perform purchasing for the food co-op.

parent f32b7e73
......@@ -149,6 +149,10 @@ class Banking extends Base {
$mysqli->close();
}
public function Factory($fn) {
}
public function Group() {
}
......@@ -169,7 +173,8 @@ class Banking extends Base {
'number VARCHAR(20),'.
'bsb VARCHAR(7),'.
'credit TINYINT(1),'.
'next_week TINYINT(1),'.
'surcharge TINYINT(1),'.
'next_week INT(10) UNSIGNED NOT NULL,'.
'PRIMARY KEY(user)'.
') ENGINE=MyISAM';
if (!$mysqli->query($query)) {
......@@ -202,6 +207,12 @@ class Banking extends Base {
public function Update() {
// This is called when the version of the module is updated,
// to provide a way to update or modify tables etc..
$mysqli = connect_db();
$query = 'ALTER TABLE banking ADD COLUMN surcharge TINYINT(1)';
if (!$mysqli->query($query)) {
error_log('Banking->Update: '.$mysqli->error);
}
$mysqli->close();
}
public function UpdateScript($path) {
......
......@@ -200,6 +200,10 @@ class Browser extends Base {
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -367,6 +367,10 @@ class Cart extends Base {
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -150,6 +150,10 @@ class Comment extends Base {
$mysqli->close();
}
public function Factory($fn) {
}
public function Group() {
return "post-comment";
}
......
......@@ -116,6 +116,10 @@ class Detail extends Base {
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -104,6 +104,10 @@ class Gift extends Base {
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -150,6 +150,10 @@ class Graph extends Base {
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -45,6 +45,10 @@ class Grid extends Base {
}
public function Factory($fn) {
}
public function Group() {
}
......@@ -55,7 +59,8 @@ class Grid extends Base {
public function Install($path) {
$this->AppendScript($path, "dobrado.grid.js", false);
$site_style = array('"",".grid","width","600px"',
$site_style = array('"",".grid","width","800px"',
'"",".grid","height","400px"',
'"",".grid","border","1px solid #aaaaaa"',
'"",".grid","border-radius","2px"',
'"",".grid","margin-top","10px"');
......
......@@ -83,6 +83,10 @@ class Player extends Base {
$this->CopyStyle($id, $old_owner, $old_id);
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -157,6 +157,10 @@ class Post extends Base {
$this->CopyStyle($id, $old_owner, $old_id);
}
public function Factory($fn) {
}
public function Group() {
return "post-comment";
}
......
<?php
// Dobrado Content Management System
// Copyright (C) 2012 Malcolm Blaney
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
class Purchase extends Base {
public function Add($id) {
}
public function Callback() {
$object = array();
$mysqli = connect_db();
$action = $mysqli->escape_string($_POST["action"]);
if ($action == "list") {
// Autocomplete users and products.
$query = 'SELECT user FROM users';
if ($result = $mysqli->query($query)) {
while ($users = $result->fetch_assoc()) {
$object["users"][] = $users["user"];
}
$result->close();
}
$stock = new Module($this->user, $this->owner, "stock", $this->config);
$object["products"] = $stock->Factory("AvailableProducts");
}
$mysqli->close();
return $object;
}
public function CanAdd($page) {
// Can only have one purchase module on a page.
return !$this->AlreadyOnPage("purchase", $page);
}
public function CanEdit($id) {
return false;
}
public function CanRemove($id) {
return true;
}
public function Content($id) {
$first = "";
$thumbnail = "";
$img = '<img class="thumb" src="/images/default_thumb.jpg">';
$mysqli = connect_db();
$query = 'SELECT first, thumbnail FROM user_detail WHERE user="'.
$this->user->name.'"';
if ($result = $mysqli->query($query)) {
if ($detail = $result->fetch_assoc()) {
$first = $detail['first'];
$thumbnail = $detail['thumbnail'];
if (preg_match('/^(.+)\.(.+)$/', $thumbnail, $matches)) {
$name = $matches[1];
$type = $matches[2];
if (in_array($type, array("gif", "jpeg", "jpg", "png"))) {
$name .= "_thumb.".$type;
$img = '<img class="thumb" src="'.$name.'">';
}
}
}
$result->close();
}
else {
error_log('Purchase->Content: '.$mysqli->error);
}
$name = $first;
// If the user hasn't filled out their details just display their username.
if ($name === "") {
$name = $this->user->name;
}
$content = '<div class="message">'.$img.'Hello '.$name.
', thankyou for volunteering today!</div>'.
'<form id="purchase-form">'.
'<div class="save hidden">You are currently taking an order for '.
'<span class="name"></span> '.
'<button class="complete">complete order</button><hr>'.
'</div>'.
'<label for="purchase-name-input">Name: </label>'.
'<input id="purchase-name-input" size="15" maxlength="50"><br>'.
'<label for="purchase-product-input">Product: </label>'.
'<input id="purchase-product-input" size="15" maxlength="100"><br>'.
'<label for="purchase-quantity-input">Quantity: </label>'.
'<input id="purchase-quantity-input" size="6" maxlength="8"><br>'.
'<label for="purchase-price-input">Price: </label>'.
'<input id="purchase-price-input" size="15" maxlength="50" '.
'readonly="true"><br>'.
'<button class="add">add</button>'.
'<button class="remove">remove</button>'.
'</form>';
return $content;
}
public function Copy($id, $old_owner, $old_id) {
}
public function Factory($fn) {
}
public function Group() {
}
public function IncludeScript() {
return true;
}
public function Install($path) {
// Append dobrado.purchase.js to the existing dobrado.js file.
// Note that updating the module is only available when logged in.
$this->AppendScript($path, "dobrado.purchase.js", false);
$mysqli = connect_db();
$query = 'CREATE TABLE IF NOT EXISTS purchase ('.
'user VARCHAR(50) NOT NULL,'.
'timestamp INT(10) UNSIGNED NOT NULL,'.
'name VARCHAR(100) NOT NULL,'.
'quantity INT UNSIGNED NOT NULL,'.
'price DECIMAL(8,2) NOT NULL,'.
'PRIMARY KEY(user, timestamp, name)'.
') ENGINE=MyISAM';
if (!$mysqli->query($query)) {
error_log('Product->Install: '.$mysqli->error);
}
$mysqli->close();
$site_style = array('"","#purchase-form","background-color","#eeeeee"',
'"","#purchase-form","border","1px solid #aaaaaa"',
'"","#purchase-form","border-radius","2px"',
'"","#purchase-form","padding","5px"',
'"","#purchase-name-input","margin-left","1.4em"',
'"","#purchase-product-input","margin-left","0.3em"',
'"","#purchase-price-input","margin-left","1.8em"',
'"","#purchase-form .add","float","right"');
$this->AddSiteStyle($site_style);
}
public function Placement() {
return "middle";
}
public function Remove($id) {
}
public function SetContent($id, $us_content) {
}
public function Update() {
// This is called when the version of the module is updated,
// to provide a way to update or modify tables etc..
}
public function UpdateScript($path) {
$this->AppendScript($path, "dobrado.purchase.js", false);
}
// Private functions below here ////////////////////////////////////////////
}
?>
\ No newline at end of file
......@@ -150,6 +150,10 @@ class Reader extends Base {
// TODO: Copy from the reader table for the new module here.
}
public function Factory($fn) {
}
public function Group() {
return "reader-writer";
}
......
......@@ -83,6 +83,10 @@ class Slider extends Base {
$this->CopyStyle($id, $old_owner, $old_id);
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -129,6 +129,10 @@ class Stock extends Base {
}
public function Factory($fn) {
if ($fn == "AvailableProducts") return $this->AvailableProducts();
}
public function Group() {
}
......@@ -188,12 +192,35 @@ class Stock extends Base {
$this->AppendScript($path, "dobrado.stock.js", false);
}
public function AvailableProducts() {
$object = array();
$mysqli = connect_db();
$query = 'SELECT name, user, unit, price, category FROM stock '.
'WHERE available="1" ORDER BY name';
if ($result = $mysqli->query($query)) {
while ($stock = $result->fetch_assoc()) {
$object[] = array("name" => $stock["name"],
"user" => $stock["user"],
"unit" => $stock["unit"],
"price" => (float)$stock["price"],
"category" => $stock["category"]);
}
$result->close();
}
else {
error_log('Stock->Products: '.$mysqli->error);
}
$mysqli->close();
return $object;
}
// Private functions below here ////////////////////////////////////////////
private function Products() {
$object = array();
$mysqli = connect_db();
$query = 'SELECT name, user, unit, price, category, available FROM stock';
$query = 'SELECT name, user, unit, price, category, available FROM stock '.
'ORDER BY name';
if ($result = $mysqli->query($query)) {
while ($stock = $result->fetch_assoc()) {
$object[] = array("name" => $stock["name"],
......
......@@ -83,6 +83,10 @@ class Turner extends Base {
$this->CopyStyle($id, $old_owner, $old_id);
}
public function Factory($fn) {
}
public function Group() {
}
......
......@@ -86,6 +86,10 @@ class Writer extends Base {
}
public function Factory($fn) {
}
public function Group() {
return "reader-writer";
}
......
if (!this.dobrado.purchase) {
dobrado.purchase = {};
}
(function() {
'use strict';
// This is a representation of all purchases in json.
var purchase = null;
// This is an instance of slick grid, if available on the page.
var grid = null;
// The currently selected product.
var currentProduct = null;
$(function() {
// Don't run if the module isn't on the page.
if ($(".purchase").length === 0) return;
$("#purchase-form .remove").button({ disabled: true }).click(remove);
$("#purchase-form .add").button({ disabled: true }).click(add);
$("#purchase-form .complete").button().click(complete);
$("#purchase-name-input").val("").blur(showUser);
$("#purchase-product-input").val("");
$("#purchase-price-input").val("");
$("#purchase-quantity-input").val("");
$("#purchase-quantity-input").spinner({ disabled: true, min: 0,
spin: setQuantity,
change: setQuantity });
// If a grid module is on the page initialise columns.
if ($(".grid").length !== 0) {
$(".grid").hide();
var id = "#" + $(".grid").attr("id");
var columns =
[{ id : "product", name: "Product", field: "name", width: 200 },
{ id : "supplier", name: "Supplier", field: "supplier", width: 200 },
{ id : "quanity", name: "Quantity", field: "quantity" },
{ id : "price", name: "Price", field: "price" },
{ id : "total", name: "Total", field: "total", width: 100 }];
var options = {
enableColumnReorder: false,
forceFitColumns: true,
};
// Create a grid instance with empty rows, which will be populated
// when a user is selected to assign purchases to.
grid = dobrado.grid.instance(id, [], columns, options);
grid.onClick.subscribe(function(e, item) {
showPurchase(item.row);
});
}
if (dobrado.localStorage()) {
if (localStorage["purchase"]) {
purchase = JSON.parse(localStorage["purchase"]);
// If purchase is not null, it means the purchases were not saved
// before the page was reloaded. Need to offer to keep using the local
// data or clear it and load from the server. Note that a timestamp
// should be provided by the server when products are loaded and stored
// with purchases, so it should be possible to check if there's a
// problem (ie the saved data is from another day) but this would
// require getting a new current timestamp from the server to compare.
$("#purchase-name-input").autocomplete({ source: purchase.users,
select: showUser });
updateProducts();
}
}
else {
// Might want to put a warning up here about no localStorage?
}
if (!purchase) {
dobrado.log("Loading products...", "info");
$.post("/php/request.php", { request: "purchase",
action: "list",
token: dobrado.token },
function(response) {
if (dobrado.checkResponseError(response, "purchase list request")) {
return;
}
purchase = JSON.parse(response);
updateProducts();
$("#purchase-name-input").autocomplete({ source: purchase.users,
select: showUser });
// When loading from the server only products and users are provided,
// the data associated with purchases starts out empty.
purchase.data = {};
});
}
});
function setFormControls() {
if (currentProduct && $("#purchase-name-input").val() !== "") {
$("#purchase-quantity-input").spinner("enable");
$("#purchase-form .remove").button("option", "disabled", false);
$("#purchase-form .add").button("option", "disabled", false);
}
else {
$("#purchase-quantity-input").spinner("disable");
$("#purchase-form .remove").button("option", "disabled", true);
$("#purchase-form .add").button("option", "disabled", true);
}
}
function showUser(event, ui) {
var user = $("#purchase-name-input").val();
if (ui) {
user = ui.item.value;
}
// When the user is changed need to reload the grid with their data, or
// create a new data set for this user if it doesn't exist.
if (purchase.data[user]) {
if (grid && purchase.data[user].length !== 0) {
grid.setData(purchase.data[user]);
grid.updateRowCount();
grid.render();
$(".grid").show();
}
}
else {
purchase.data[user] = [];
$(".grid").hide();
}
$("#purchase-form .save .name").html(user);
$("#purchase-form .save").show();
currentProduct = null;
$("#purchase-product-input").val("");
$("#purchase-price-input").val("");
$("#purchase-quantity-input").val("");
setFormControls();
}
function showPurchase(row) {
var user = $("#purchase-name-input").val();
var data = purchase.data[user][row];
$("#purchase-product-input").val(data.name);
$("#purchase-quantity-input").val(data.quantity);
var price = data.quantity * data.price;
// Set currentProduct to updateFormControls and get units.
$.each(purchase.products, function(index, item) {
if (data.name === item.name) {
currentProduct = item;
return false;
}
});
$("#purchase-price-input").val("$" + price.toFixed(2) + " @ (" + data.price
+ "/" + currentProduct.unit + ")");
setFormControls();
}
function updateProducts() {
function showProductFromMenu(event, ui) {
$.each(purchase.products, function(index, item) {
if (ui.item.value === item.name) {
// Set the current product to this item.
currentProduct = item;
// Clear the quantity input when the product changes.
$("#purchase-quantity-input").val("");
$("#purchase-price-input").val("(" + item.price.toFixed(2) + "/" +
item.unit + ")");
return false;
}
});
setFormControls();
}
// Create an autocomplete list for the product name.
var products = [];
$.each(purchase.products, function(index, item) {
products.push(item.name);
});
$("#purchase-product-input").autocomplete({ source: products,
select: showProductFromMenu });
}
function setQuantity(event, ui) {
var quantity = 0;
if ("value" in ui) {
// value is set for the 'spin' event, before the input field is updated.
quantity = ui.value;
}
else {
// Otherwise a 'change' event has been fired, which doesn't provide a
// 'value' property. Can just check val of the input field in this case.
quantity = parseFloat($(this).val());
if (!quantity || quantity < 0) {
quantity = 0;
$(this).val("0");
}
}
if (currentProduct) {
var price = quantity * currentProduct.price;
$("#purchase-price-input").val("$" + price.toFixed(2) + " @ (" +
currentProduct.price.toFixed(2) +
"/" + currentProduct.unit + ")");
}
}
function remove() {
var user = $("#purchase-name-input").val();
$.each(purchase.data[user], function(i, item) {
if (item.name === currentProduct.name) {
purchase.data[user].splice(i, 1);
if (purchase.data[user].length > 0 && grid) {
grid.updateRowCount();
grid.render();
}
else {
$(".grid").hide();
}
return false;
}
});
currentProduct = null;
$("#purchase-product-input").val("");
$("#purchase-price-input").val("");
$("#purchase-quantity-input").val("");
setFormControls();
return false;
}
function add() {
var user = $("#purchase-name-input").val();
var supplier = currentProduct.user;
var product = currentProduct.name;
var price = currentProduct.price;
var quantity = parseFloat($("#purchase-quantity-input").val());
if (!quantity || quantity < 0) {
return false;
}
var newItem = true;
$.each(purchase.data[user], function(i, item) {
if (item.name === product) {
// Just need to update quantity and total if an item already exists.
var total = quantity * price;
purchase.data[user][i] = { name: product, supplier: supplier,
quantity: quantity, price: price.toFixed(2),
total: total.toFixed(2) };
if (grid) {
// Need to call setData due to assignment to purchase.data[user].
grid.setData(purchase.data[user]);
}
newItem = false;
return false;
}
});
// Otherwise add the new purchase to the list.
if (newItem) {
var total = quantity * price;
purchase.data[user].push({ name: product, supplier: supplier,
quantity: quantity, price: price.toFixed(2),
total: total.toFixed(2) });
// Need to call setData if the array was previously empty.
if (purchase.data[user].length === 1) {
grid.setData(purchase.data[user]);
}
}
if (grid) {
grid.updateRowCount();
grid.render();
$(".grid").show();
}
return false;
}
function complete() {
// Need to show a summary of the current user's purchases, save it to the
// server and remove the user from the purchase.data array when a successful
// response is returned.
return false;
}
})();
......@@ -32,8 +32,8 @@ if (!this.dobrado.stock) {
if ($(".grid").length !== 0) {
var id = "#" + $(".grid").attr("id");
var columns =
[{ id : "product", name: "Product", field: "name", width: 100 },
{ id : "supplier", name: "Supplier", field: "user", width: 100 },
[{ id : "product", name: "Product", field: "name", width: 200 },
{ id : "supplier", name: "Supplier", field: "user", width: 200 },
{ id : "units", name: "Units", field: "unit" },
{ id : "price", name: "Price", field: "price" },
{ id : "category", name: "Category", field: "category",
......@@ -43,7 +43,6 @@ if (!this.dobrado.stock) {
var options = {
enableColumnReorder: false,
forceFitColumns: true,
autoHeight: true
};
grid = dobrado.grid.instance(id, stock, columns, options);
grid.onClick.subscribe(function(e, item) {
......
......@@ -485,6 +485,17 @@ if (!this.dobrado) {
});
};
dobrado.localStorage = function() {