[MERGE] lp:~openerp-dev/openobject-addons/trunk-pos-fva

bzr revid: stw@openerp.com-20120430092650-3oozjjx9pgbqh73j
This commit is contained in:
Stephane Wirtel
2012-04-30 11:26:50 +02:00
19 changed files with 2264 additions and 16 deletions
+19 -2
View File
@@ -235,8 +235,27 @@ class account_cash_statement(osv.osv):
print "line: %r" % (line,)
if line and len(line)==3 and line[2]:
amount_total+= line[2]['pieces'] * line[2]['number_opening']
vals.update(balance_start= amount_total)
vals.update(balance_end_real=self._compute_balance_end_real(cr, uid, vals['journal_id'], context=context))
details = vals.get('details_ids')
if not details:
result = self.onchange_journal_id(cr, uid, None, vals['journal_id'], context=context)
vals['details_ids'] = []
for value in (result['value']['details_ids'] or []):
print "value: %r" % (value,)
nested_values = {
'number_closing' : False,
'number_opening' : False,
'pieces' : value['pieces'],
'subtotal_closing' : False,
'subtotal_opening' : False,
}
vals['details_ids'].append([0, False, nested_values])
return super(account_cash_statement, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
@@ -285,8 +304,6 @@ class account_cash_statement(osv.osv):
for line in journal.cashbox_line_ids:
values['value']['details_ids'].append({'pieces' : line.pieces})
print "values: %r" % (values,)
return values
else:
return {
+2 -1
View File
@@ -44,4 +44,5 @@ class res_currency_account(osv.osv):
res_currency_account()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+7 -1
View File
@@ -82,7 +82,13 @@ Main features :
'application': True,
'certificate' : '001156338024966477869',
# Web client
'js': ['static/lib/backbone/backbone-0.5.3.js', 'static/src/js/pos.js'], #, 'static/src/js/pos2.js'],
'js': [
'static/lib/backbone/backbone-0.5.3.js',
'static/src/js/pos_models.js',
'static/src/js/pos_widgets.js',
'static/src/js/pos_devices.js',
'static/src/js/pos_main.js'
],
'css': ['static/src/css/pos.css'],
'qweb': ['static/src/xml/pos.xml'],
'auto_install': True,
+5 -3
View File
@@ -180,7 +180,7 @@ class pos_session(osv.osv):
def _create_cash_register(self, cr, uid, pos_config, name, context=None):
import pdb
pdb.set_trace()
#pdb.set_trace()
if not pos_config:
return False
@@ -200,7 +200,9 @@ class pos_session(osv.osv):
'name' : name,
'journal_id' : journal_id,
}
return proxy.create(cr, uid, values, context=context)
cash_register_id = proxy.create(cr, uid, values, context=context)
return cash_register_id
def create(self, cr, uid, values, context=None):
@@ -348,7 +350,7 @@ class pos_order(osv.osv):
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True),
'partner_id': fields.many2one('res.partner', 'Customer', change_default=True, select=1, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]}),
#'session_id' : fields.many2one('pos.session', 'Session', required=True, select=1),
'session_id' : fields.many2one('pos.session', 'Session', required=True, select=1),
'state': fields.selection([('draft', 'New'),
('cancel', 'Cancelled'),
File diff suppressed because one or more lines are too long
+11 -1
View File
@@ -861,12 +861,22 @@
<notebook colspan="4">
<page string="Cash">
<field name="cash_register_id" invisible="0" />
<field name="details_ids" colspan="4" nolabel="1" />
<field name="details_ids" colspan="4" nolabel="1">
<tree string="" editable="bottom">
<field name="pieces" readonly="1" />
<field name="number_opening" string="Opening Unit Numbers" on_change="on_change_sub_opening(pieces, number_opening, parent.balance_end)" />
<field name="subtotal_opening" string="Opening Subtotal"/>
<field name="number_closing" string="Closing Unit Numbers" on_change="on_change_sub_closing(pieces, number_closing, parent.balance_end)" />
<field name="subtotal_closing" string="Closing Subtotal"/>
</tree>
</field>
</page>
<!--
<page string="Others">
</page>
<page string="Nothing">
</page>
-->
</notebook>
<group colspan="4" col="5">
+240 -6
View File
@@ -1,4 +1,6 @@
body{
overflow: hidden;
}
.point-of-sale {
padding: 0;
margin: 0;
@@ -116,9 +118,15 @@
bottom: 0;
}
.point-of-sale #leftpane {
height: 100%;
-webkit-box-sizing:border-box;
position:absolute;
left:0;
width:440px;
top:0px;
bottom:105px;
/*height: 87%;
width: 440px;
position: relative;
position: relative;*/
border-right: solid 1px #afafb6;
background-color: white;
}
@@ -249,10 +257,10 @@
.point-of-sale #rightpane {
position: absolute;
top: 0;
bottom: 0;
left: 441px;
bottom: 105px;
left: 440px;
//height:100%;
right: 0;
height: 100%;
vertical-align: top;
}
.point-of-sale #rightpane header {
@@ -487,3 +495,229 @@
}
}
.point-of-sale .pos-actionbar{
position:absolute;
left: 0;
bottom: 0px;
height: 105px;
width: 100%;
margin: 0;
background: #f5f5f5; /*#ebebeb;*/
border-top: solid 1px #afafb6;
}
.point-of-sale .pos-actionbar ul{
list-style: none;
}
.point-of-sale .pos-actionbar-left-region{
height: 100%;
width: 434px;
margin: 0px;
padding-left:3px;
padding-right:3px;
border-right: solid 1px #dfdfdf;
float: left;
}
.point-of-sale .pos-actionbar-right-region{
height: 100%;
margin: 0px;
padding-left:3px;
padding-right:3px;
overflow:hidden;
}
.point-of-sale .pos-actionbar .button{
width: 90px;
height: 90px;
text-align:center;
margin:3px;
margin-top:6px;
//line-height: 90px;
//vertical-align:center;
float:left;
font-size: 14px;
font-weight: bold;
cursor: pointer;
border: 1px solid #cacaca;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
background: #e2e2e2;
background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
background: -webkit-gradient(linear, left top, left bottom, from(#f0f0f0), to(#e2e2e2));
-webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
}
.point-of-sale .pos-actionbar .button .label{
margin-top: 37px;
}
.point-of-sale .pos-actionbar .button .icon{
margin-top: 10px;
}
.point-of-sale .pos-actionbar .button:hover {
color: white;
background: #7f82ac;
border: 1px solid #7f82ac;
background: -moz-linear-gradient(#9d9fc5, #7f82ac);
background: -webkit-gradient(linear, left top, left bottom, from(#9d9fc5), to(#7f82ac));
-webkit-transition-property: background, border;
-webkit-transition-duration: 0.2s;
-webkit-transition-timing-function: ease-out;
}
.point-of-sale .pos-actionbar .button.rightalign{
float:right;
}
/* Onscreen Keyboard http://net.tutsplus.com/tutorials/javascript-ajax/creating-a-keyboard-with-css-and-jquery/ */
.point-of-sale .keyboard_frame{
display: none;
position:absolute;
left: 0;
bottom: 0px;
margin: 0;
padding: 0;
padding-top: 15px;
width: 100%;
height: 0px; /* 235px, animated via jquery */
background-color: #BBB;
overflow:hidden;
-webkit-box-shadow: 0px 0px 10px rgba(0,0,0, 0.3);
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.point-of-sale .keyboard_frame .close_button{
height:40px;
width:60px;
text-align:center;
background-color: #DDD;
font-size: 12px;
line-height:40px;
border: 1px solid #CCC;
-webkit-border-radius: 5px;
-webkit-box-shadow: 0px 2px 5px rgba(0,0,0, 0.2);
position:absolute;
top:0;
right:15px;
cursor: pointer;
}
.point-of-sale .keyboard li {
float: left;
text-align: center;
background-color: #fff;
border: 1px solid #f0f0f0;
top:0;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
-webkit-box-shadow: 0px 2px 5px rgba(0,0,0, 0.2);
-webkit-transition-property: top, background-color;
-webkit-transition-duration: 0.2s;
-webkit-transition-timing-function: linear;
}
.point-of-sale .keyboard li:hover {
position: relative;
top: 2px;
left: 0px;
border-color: #ddd;
background-color:#e5e5e5;
cursor: pointer;
-webkit-transition-property: top, background-color;
-webkit-transition-duration: 0.1s;
-webkit-transition-timing-function: ease-out;
}
.point-of-sale .uppercase {
text-transform: uppercase;
}
.point-of-sale .on {
display: none;
}
.point-of-sale .firstitem{
clear: left;
}
.point-of-sale .keyboard .lastitem {
margin-right: 0;
}
/* ---- full sized keyboard ---- */
.point-of-sale .full_keyboard {
list-style: none;
font-size: 14px;
width: 680px;
height: 100%;
margin-left: auto;
margin-right: auto;
}
.point-of-sale .full_keyboard li{
margin: 0 5px 5px 0;
width: 40px;
height: 40px;
line-height: 40px;
}
.point-of-sale .full_keyboard .tab, .point-of-sale .full_keyboard .delete {
width: 70px;
}
.point-of-sale .full_keyboard .capslock {
width: 80px;
}
.point-of-sale .full_keyboard .return {
width: 77px;
}
.point-of-sale .full_keyboard .left-shift {
width: 95px;
}
.point-of-sale .full_keyboard .right-shift {
width: 109px;
}
.point-of-sale .full_keyboard .space {
clear: left;
width: 673px;
}
/* ---- simplified keyboard ---- */
.point-of-sale .simple_keyboard {
list-style: none;
font-size: 16px;
width: 545px;
height: 220px;
margin-left: auto;
margin-right: auto;
}
.point-of-sale .simple_keyboard li{
margin: 0 5px 5px 0;
width: 49px;
height: 49px;
line-height: 49px;
}
.point-of-sale .simple_keyboard .firstitem.row_asdf{
margin-left:25px;
}
.point-of-sale .simple_keyboard .firstitem.row_zxcv{
margin-left:55px;
}
.point-of-sale .simple_keyboard .delete{
width: 103px;
}
.point-of-sale .simple_keyboard .return{
width: 103px;
}
.point-of-sale .simple_keyboard .space{
width:268px;
}
.point-of-sale .simple_keyboard .numlock{
width:103px;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -0,0 +1,149 @@
function openerp_pos_devices(module, instance){ //module is instance.point_of_sale
module.BarcodeReader = instance.web.Class.extend({
init: function(attributes){
this.pos = attributes.pos;
this.connect();
},
// returns true if the code is a valid EAN codebar number by checking the control digit.
checkEan: function(code){
var st1 = code.slice();
var st2 = st1.slice(0,st1.length-1).reverse();
// some EAN13 barcodes have a length of 12, as they start by 0
while (st2.length < 12) {
st2.push(0);
}
var countSt3 = 1;
var st3 = 0;
$.each(st2, function() {
if (countSt3%2 === 1) {
st3 += this;
}
countSt3 ++;
});
st3 *= 3;
var st4 = 0;
var countSt4 = 1;
$.each(st2, function() {
if (countSt4%2 === 0) {
st4 += this;
}
countSt4 ++;
});
var st5 = st3 + st4;
var cd = (10 - (st5%10)) % 10;
return code[code.length-1] === cd;
},
// returns a product that has a packaging with an EAN matching to provided ean string.
// returns undefined if no such product is found.
getProductByEAN: function(ean) {
var allProducts = this.pos.get('product_list');
var allPackages = this.pos.get('product.packaging');
var prefix = ean.substring(0,2);
var scannedProductModel = undefined;
if (prefix in {'02':'', '22':'', '24':'', '26':'', '28':''}) {
// PRICE barcode
var itemCode = ean.substring(0,7);
var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;});
if (scannedPackaging !== undefined) {
scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
scannedProductModel.list_price = Number(ean.substring(7,12))/100;
}
} else if (prefix in {'21':'','23':'','27':'','29':'','25':''}) {
// WEIGHT barcode
var weight = Number(barcode.substring(7,12))/1000;
var itemCode = ean.substring(0,7);
var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;});
if (scannedPackaging !== undefined) {
scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
scannedProductModel.list_price *= weight;
scannedProductModel.name += ' - ' + weight + ' Kg.';
}
} else {
// UNIT barcode
scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === ean;}); //TODO DOES NOT SCALE
}
return scannedProductModel;
},
// starts catching keyboard events and tries to interpret codebar
connect: function(){
var self = this;
var codeNumbers = [];
var timeStamp = 0;
var lastTimeStamp = 0;
// The barcode readers acts as a keyboard, we catch all keyup events and try to find a
// barcode sequence in the typed keys, then act accordingly.
$('body').delegate('','keyup', function (e){
//We only care about numbers
if (!isNaN(Number(String.fromCharCode(e.keyCode)))) {
// The barcode reader sends keystrokes with a specific interval.
// We look if the typed keys fit in the interval.
if (codeNumbers.length==0) {
timeStamp = new Date().getTime();
} else {
if (lastTimeStamp + 30 < new Date().getTime()) {
// not a barcode reader
codeNumbers = [];
timeStamp = new Date().getTime();
}
}
codeNumbers.push(e.keyCode - 48);
lastTimeStamp = new Date().getTime();
if (codeNumbers.length == 13) {
//console.log('found code:', codeNumbers.join(''));
// a barcode reader
if (!self.checkEan(codeNumbers)) {
// barcode read error, raise warning
$(QWeb.render('pos-scan-warning')).dialog({
resizable: false,
height:220,
modal: true,
title: "Warning",
});
}
var selectedOrder = self.pos.get('selectedOrder');
var scannedProductModel = self.getProductByEAN(codeNumbers.join(''),allPackages,allProducts);
if (scannedProductModel === undefined) {
// product not recognized, raise warning
$(QWeb.render('pos-scan-warning')).dialog({
resizable: false,
height:220,
modal: true,
title: "Warning",
/*
buttons: {
"OK": function() {
$( this ).dialog( "close" );
return;
},
}*/
});
} else {
selectedOrder.addProduct(new module.Product(scannedProductModel));
}
codeNumbers = [];
}
} else {
// NaN
codeNumbers = [];
}
});
},
// stops catching keyboard events
disconnect: function(){
$('body').undelegate('', 'keyup')
},
});
}
@@ -0,0 +1,17 @@
openerp.point_of_sale = function(instance) {
instance.point_of_sale = {};
var module = instance.point_of_sale;
openerp_pos_models(module,instance); // import pos_models.js
openerp_pos_widgets(module,instance); // import pos_widgets.js
openerp_pos_devices(module,instance); // import pos_devices.js
instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.PosWidget');
};
@@ -0,0 +1,550 @@
function openerp_pos_models(module, instance){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
module.LocalStorageDAO = instance.web.Class.extend({
add_operation: function(operation) {
var self = this;
return $.async_when().pipe(function() {
var tmp = self._get('oe_pos_operations', []);
var last_id = self._get('oe_pos_operations_sequence', 1);
tmp.push({'id': last_id, 'data': operation});
self._set('oe_pos_operations', tmp);
self._set('oe_pos_operations_sequence', last_id + 1);
});
},
remove_operation: function(id) {
var self = this;
return $.async_when().pipe(function() {
var tmp = self._get('oe_pos_operations', []);
tmp = _.filter(tmp, function(el) {
return el.id !== id;
});
self._set('oe_pos_operations', tmp);
});
},
get_operations: function() {
var self = this;
return $.async_when().pipe(function() {
return self._get('oe_pos_operations', []);
});
},
_get: function(key, default_) {
var txt = localStorage[key];
if (! txt)
return default_;
return JSON.parse(txt);
},
_set: function(key, value) {
localStorage[key] = JSON.stringify(value);
},
});
var fetch = function(osvModel, fields, domain){
var dataSetSearch = new instance.web.DataSetSearch(null, osvModel, {}, domain);
return dataSetSearch.read_slice(fields, 0);
};
/*
Gets all the necessary data from the OpenERP web client (instance, shop data etc.)
*/
module.PosModel = Backbone.Model.extend({
initialize: function(session, attributes) {
Backbone.Model.prototype.initialize.call(this, attributes);
var self = this;
this.dao = new module.LocalStorageDAO();
this.ready = $.Deferred();
this.flush_mutex = new $.Mutex();
this.build_tree = _.bind(this.build_tree, this);
this.session = session;
this.categories = {};
this.set({
'nbr_pending_operations': 0,
'currency': {symbol: '$', position: 'after'},
'shop': {},
'company': {},
'user': {},
'orders': new module.OrderCollection(),
'products': new module.ProductCollection(),
//'cashRegisters': [], // new module.CashRegisterCollection(this.pos.get('bank_statements')),
'selectedOrder': undefined,
});
var cat_def = fetch('pos.category', ['name', 'parent_id', 'child_id'])
.pipe(function(result){
return self.set({'categories': result});
});
var prod_def = fetch(
'product.product',
['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small'],
[['pos_categ_id','!=', 'false']]
).then(function(result){
return self.set({'product_list': result});
});
var bank_def = fetch(
'account.bank.statement',
['account_id', 'currency', 'journal_id', 'state', 'name'],
[['state','=','open'], ['user_id', '=', this.session.uid]]
).then(function(result){
return self.set({'bank_statements': result});
});
var tax_def = fetch('account.tax', ['amount','price_include','type'])
.then(function(result){
return self.set({'taxes': result});
});
$.when(cat_def,prod_def,bank_def,tax_def,this.get_app_data(), this.flush())
.pipe(_.bind(this.build_tree, this))
.pipe(function(){
self.set({'cashRegisters': new module.CashRegisterCollection(self.get('bank_statements')) });
self.ready.resolve();
});
return (this.get('orders')).bind('remove', _.bind( function(removedOrder) {
if ((this.get('orders')).isEmpty()) {
this.addAndSelectOrder(new module.Order({pos: self}));
}
if ((this.get('selectedOrder')) === removedOrder) {
return this.set({
selectedOrder: (this.get('orders')).last()
});
}
}, this));
},
get_app_data: function() {
var self = this;
return $.when(new instance.web.Model("sale.shop").get_func("search_read")([]).pipe(function(result) {
self.set({'shop': result[0]});
var company_id = result[0]['company_id'][0];
return new instance.web.Model("res.company").get_func("read")(company_id, ['currency_id', 'name', 'phone']).pipe(function(result) {
self.set({'company': result});
var currency_id = result['currency_id'][0]
return new instance.web.Model("res.currency").get_func("read")([currency_id],
['symbol', 'position']).pipe(function(result) {
self.set({'currency': result[0]});
});
});
}), new instance.web.Model("res.users").get_func("read")(this.session.uid, ['name']).pipe(function(result) {
self.set({'user': result});
}));
},
push_order: function(record) {
var self = this;
return this.dao.add_operation(record).pipe(function(){
return self.flush();
});
},
addAndSelectOrder: function(newOrder) {
(this.get('orders')).add(newOrder);
return this.set({
selectedOrder: newOrder
});
},
flush: function() {
return this.flush_mutex.exec(_.bind(function() {
return this._int_flush();
}, this));
},
_int_flush : function() {
var self = this;
this.dao.get_operations().pipe(function(operations) {
self.set( {'nbr_pending_operations':operations.length} );
if(operations.length === 0){
return $.when();
}
var op = operations[0];
// we prevent the default error handler and assume errors
// are a normal use case, except we stop the current iteration
return new instance.web.Model('pos.order').get_func('create_from_ui')([op])
.fail(function(unused, event){
event.preventDefault();
})
.pipe(function(){
console.debug('saved 1 record'); //TODO Debug this
self.dao.remove_operation(operations[0].id).pipe(function(){
return self._int_flush();
});
}, function(){
return $.when();
});
});
},
build_tree: function() {
var c, id, _i, _len, _ref, _ref2;
_ref = this.get('categories');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
c = _ref[_i];
this.categories[c.id] = {
id: c.id,
name: c.name,
children: c.child_id,
parent: c.parent_id[0],
ancestors: [c.id],
subtree: [c.id]
};
}
_ref2 = this.categories;
for (id in _ref2) {
c = _ref2[id];
this.current_category = c;
this.build_ancestors(c.parent);
this.build_subtree(c);
}
this.categories[0] = {
ancestors: [],
children: (function() {
var _j, _len2, _ref3, _results;
_ref3 = this.get('categories');
_results = [];
for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) {
c = _ref3[_j];
if (!(c.parent_id[0] != null)) {
_results.push(c.id);
}
}
return _results;
}).call(this),
subtree: (function() {
var _j, _len2, _ref3, _results;
_ref3 = this.get('categories');
_results = [];
for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) {
c = _ref3[_j];
_results.push(c.id);
}
return _results;
}).call(this)
};
},
build_ancestors: function(parent) {
if (parent != null) {
this.current_category.ancestors.unshift(parent);
return this.build_ancestors(this.categories[parent].parent);
}
},
build_subtree: function(category) {
var c, _i, _len, _ref, _results;
_ref = category.children;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
c = _ref[_i];
this.current_category.subtree.push(c);
_results.push(this.build_subtree(this.categories[c]));
}
return _results;
}
});
module.CashRegister = Backbone.Model.extend({
});
module.CashRegisterCollection = Backbone.Collection.extend({
model: module.CashRegister,
});
module.Product = Backbone.Model.extend({
});
module.ProductCollection = Backbone.Collection.extend({
model: module.Product,
});
/*
Each Order contains zero or more Orderlines (i.e. the content of the "shopping cart".)
There should only ever be one Orderline per distinct product in an Order.
To add more of the same product, just update the quantity accordingly.
The Order also contains payment information.
*/
module.Orderline = Backbone.Model.extend({
defaults: {
quantity: 1,
list_price: 0,
discount: 0
},
initialize: function(attributes) {
this.pos = attributes.pos;
Backbone.Model.prototype.initialize.apply(this, arguments);
this.bind('change:quantity', function(unused, qty) {
if (qty == 0)
this.trigger('killme');
}, this);
},
incrementQuantity: function() {
return this.set({
quantity: (this.get('quantity')) + 1
});
},
getPriceWithoutTax: function() {
return this.getAllPrices().priceWithoutTax;
},
getPriceWithTax: function() {
return this.getAllPrices().priceWithTax;
},
getTax: function() {
return this.getAllPrices().tax;
},
getAllPrices: function() {
var self = this;
var base = (this.get('quantity')) * (this.get('list_price')) * (1 - (this.get('discount')) / 100);
var totalTax = base;
var totalNoTax = base;
var product_list = self.pos.get('product_list');
var product = _.detect(product_list, function(el) {return el.id === self.get('id');});
var taxes_ids = product.taxes_id;
var taxes = self.pos.get('taxes');
var taxtotal = 0;
_.each(taxes_ids, function(el) {
var tax = _.detect(taxes, function(t) {return t.id === el;});
if (tax.price_include) {
var tmp;
if (tax.type === "percent") {
tmp = base - (base / (1 + tax.amount));
} else if (tax.type === "fixed") {
tmp = tax.amount * self.get('quantity');
} else {
throw "This type of tax is not supported by the point of sale: " + tax.type;
}
taxtotal += tmp;
totalNoTax -= tmp;
} else {
var tmp;
if (tax.type === "percent") {
tmp = tax.amount * base;
} else if (tax.type === "fixed") {
tmp = tax.amount * self.get('quantity');
} else {
throw "This type of tax is not supported by the point of sale: " + tax.type;
}
taxtotal += tmp;
totalTax += tmp;
}
});
return {
"priceWithTax": totalTax,
"priceWithoutTax": totalNoTax,
"tax": taxtotal,
};
},
exportAsJSON: function() {
return {
qty: this.get('quantity'),
price_unit: this.get('list_price'),
discount: this.get('discount'),
product_id: this.get('id')
};
},
});
module.OrderlineCollection = Backbone.Collection.extend({
model: module.Orderline,
});
// Every PaymentLine has all the attributes of the corresponding CashRegister.
module.Paymentline = Backbone.Model.extend({
defaults: {
amount: 0,
},
initialize: function(attributes) {
Backbone.Model.prototype.initialize.apply(this, arguments);
},
getAmount: function(){
return this.get('amount');
},
exportAsJSON: function(){
return {
name: instance.web.datetime_to_str(new Date()),
statement_id: this.get('id'),
account_id: (this.get('account_id'))[0],
journal_id: (this.get('journal_id'))[0],
amount: this.getAmount()
};
},
});
module.PaymentlineCollection = Backbone.Collection.extend({
model: module.Paymentline,
});
module.Order = Backbone.Model.extend({
defaults:{
validated: false,
step: 'products',
},
initialize: function(attributes){
Backbone.Model.prototype.initialize.apply(this, arguments);
this.set({
creationDate: new Date(),
orderLines: new module.OrderlineCollection(),
paymentLines: new module.PaymentlineCollection(),
name: "Order " + this.generateUniqueId(),
});
this.pos = attributes.pos; //TODO put that in set and remember to use 'get' to read it ...
this.bind('change:validated', this.validatedChanged);
return this;
},
events: {
'change:validated': 'validatedChanged'
},
validatedChanged: function() {
if (this.get("validated") && !this.previous("validated")) {
this.set({'step': 'receipt'});
}
},
generateUniqueId: function() {
return new Date().getTime();
},
addProduct: function(product) {
var existing;
existing = (this.get('orderLines')).get(product.id);
if (existing != null) {
existing.incrementQuantity();
} else {
var attr = product.toJSON();
attr.pos = this.pos;
var line = new module.Orderline(attr);
this.get('orderLines').add(line);
line.bind('killme', function() {
this.get('orderLines').remove(line);
}, this);
}
},
addPaymentLine: function(cashRegister) {
var newPaymentline;
newPaymentline = new module.Paymentline(cashRegister);
/* TODO: Should be 0 for cash-like accounts */
newPaymentline.set({
amount: this.getDueLeft()
});
return (this.get('paymentLines')).add(newPaymentline);
},
getName: function() {
return this.get('name');
},
getTotal: function() {
return (this.get('orderLines')).reduce((function(sum, orderLine) {
return sum + orderLine.getPriceWithTax();
}), 0);
},
getTotalTaxExcluded: function() {
return (this.get('orderLines')).reduce((function(sum, orderLine) {
return sum + orderLine.getPriceWithoutTax();
}), 0);
},
getTax: function() {
return (this.get('orderLines')).reduce((function(sum, orderLine) {
return sum + orderLine.getTax();
}), 0);
},
getPaidTotal: function() {
return (this.get('paymentLines')).reduce((function(sum, paymentLine) {
return sum + paymentLine.getAmount();
}), 0);
},
getChange: function() {
return this.getPaidTotal() - this.getTotal();
},
getDueLeft: function() {
return this.getTotal() - this.getPaidTotal();
},
exportAsJSON: function() {
var orderLines, paymentLines;
orderLines = [];
(this.get('orderLines')).each(_.bind( function(item) {
return orderLines.push([0, 0, item.exportAsJSON()]);
}, this));
paymentLines = [];
(this.get('paymentLines')).each(_.bind( function(item) {
return paymentLines.push([0, 0, item.exportAsJSON()]);
}, this));
return {
name: this.getName(),
amount_paid: this.getPaidTotal(),
amount_total: this.getTotal(),
amount_tax: this.getTax(),
amount_return: this.getChange(),
lines: orderLines,
statement_ids: paymentLines
};
},
});
module.OrderCollection = Backbone.Collection.extend({
model: module.Order,
});
/*
The numpad handles both the choice of the property currently being modified
(quantity, price or discount) and the edition of the corresponding numeric value.
*/
module.NumpadState = Backbone.Model.extend({
defaults: {
buffer: "0",
mode: "quantity"
},
appendNewChar: function(newChar) {
var oldBuffer;
oldBuffer = this.get('buffer');
if (oldBuffer === '0') {
this.set({
buffer: newChar
});
} else if (oldBuffer === '-0') {
this.set({
buffer: "-" + newChar
});
} else {
this.set({
buffer: (this.get('buffer')) + newChar
});
}
this.updateTarget();
},
deleteLastChar: function() {
var tempNewBuffer;
tempNewBuffer = (this.get('buffer')).slice(0, -1) || "0";
if (isNaN(tempNewBuffer)) {
tempNewBuffer = "0";
}
this.set({
buffer: tempNewBuffer
});
this.updateTarget();
},
switchSign: function() {
var oldBuffer;
oldBuffer = this.get('buffer');
this.set({
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
});
this.updateTarget();
},
changeMode: function(newMode) {
this.set({
buffer: "0",
mode: newMode
});
},
reset: function() {
this.set({
buffer: "0",
mode: "quantity"
});
},
updateTarget: function() {
var bufferContent, params;
bufferContent = this.get('buffer');
if (bufferContent && !isNaN(bufferContent)) {
this.trigger('setValue', parseFloat(bufferContent));
}
},
});
}
File diff suppressed because it is too large Load Diff
+138 -1
View File
@@ -2,7 +2,7 @@
<!-- vim:fdl=1:
-->
<templates id="template" xml:space="preserve">
<t t-name="PointOfSale">
<t t-name="POSWidget">
<div class="point-of-sale">
<div id="topheader">
<div id="branding">
@@ -141,6 +141,9 @@
</a>
</span>
</t>
<t t-name="pos-scan-warning">
<div>The product could not be recognized. Please contact an employee.</div>
</t>
<t t-name="pos-close-warning">
<div>There are pending operations that could not be saved into the database, are you sure you want to exit?</div>
</t>
@@ -291,4 +294,138 @@
</table>
</div>
</t>
<t t-name="pos-actionbar">
<div class="pos-actionbar">
<ul class="pos-actionbar-left-region">
<!-- <li class="button">yoyo</li>
<li class="button">foo</li>
<li class="button rightalign">bar</li> -->
</ul>
<ul class="pos-actionbar-right-region">
<!-- <li class="button">BUTTOOON</li>
<li class="button">JEEENKIINS</li>
<li class="button rightalign">ARGH</li> -->
</ul>
</div>
</t>
<t t-name="pos-action-button">
<li t-att-class=" 'button '+ (widget.rightalign ? 'rightalign ' : '')">
<div class='label'>
<t t-esc="widget.label" />
</div>
</li>
</t>
<t t-name="pos-action-button-with-icon">
<li t-att-class=" 'button '+ (widget.rightalign ? 'rightalign ' : '')">
<div class='icon'>
<img t-att-src="widget.icon" />
<div class='iconlabel'><t t-esc="widget.label" /></div>
</div>
</li>
</t>
<!-- Onscreen Keyboard :
http://net.tutsplus.com/tutorials/javascript-ajax/creating-a-keyboard-with-css-and-jquery/ -->
<t t-name="pos-onscreen-keyboard-full-template">
<div class="keyboard_frame">
<ul class="keyboard full_keyboard">
<li class="symbol"><span class="off">`</span><span class="on">~</span></li>
<li class="symbol"><span class="off">1</span><span class="on">!</span></li>
<li class="symbol"><span class="off">2</span><span class="on">@</span></li>
<li class="symbol"><span class="off">3</span><span class="on">#</span></li>
<li class="symbol"><span class="off">4</span><span class="on">$</span></li>
<li class="symbol"><span class="off">5</span><span class="on">%</span></li>
<li class="symbol"><span class="off">6</span><span class="on">^</span></li>
<li class="symbol"><span class="off">7</span><span class="on">&amp;</span></li>
<li class="symbol"><span class="off">8</span><span class="on">*</span></li>
<li class="symbol"><span class="off">9</span><span class="on">(</span></li>
<li class="symbol"><span class="off">0</span><span class="on">)</span></li>
<li class="symbol"><span class="off">-</span><span class="on">_</span></li>
<li class="symbol"><span class="off">=</span><span class="on">+</span></li>
<li class="delete lastitem">delete</li>
<li class="tab firstitem">tab</li>
<li class="letter">q</li>
<li class="letter">w</li>
<li class="letter">e</li>
<li class="letter">r</li>
<li class="letter">t</li>
<li class="letter">y</li>
<li class="letter">u</li>
<li class="letter">i</li>
<li class="letter">o</li>
<li class="letter">p</li>
<li class="symbol"><span class="off">[</span><span class="on">{</span></li>
<li class="symbol"><span class="off">]</span><span class="on">}</span></li>
<li class="symbol lastitem"><span class="off">\</span><span class="on">|</span></li>
<li class="capslock firstitem">caps lock</li>
<li class="letter">a</li>
<li class="letter">s</li>
<li class="letter">d</li>
<li class="letter">f</li>
<li class="letter">g</li>
<li class="letter">h</li>
<li class="letter">j</li>
<li class="letter">k</li>
<li class="letter">l</li>
<li class="symbol"><span class="off">;</span><span class="on">:</span></li>
<li class="symbol"><span class="off">'</span><span class="on">&quot;</span></li>
<li class="return lastitem">return</li>
<li class="left-shift firstitem">shift</li>
<li class="letter">z</li>
<li class="letter">x</li>
<li class="letter">c</li>
<li class="letter">v</li>
<li class="letter">b</li>
<li class="letter">n</li>
<li class="letter">m</li>
<li class="symbol"><span class="off">,</span><span class="on">&lt;</span></li>
<li class="symbol"><span class="off">.</span><span class="on">&gt;</span></li>
<li class="symbol"><span class="off">/</span><span class="on">?</span></li>
<li class="right-shift lastitem">shift</li>
<li class="space firstitem lastitem">&amp;nbsp;</li>
</ul>
<p class="close_button">close</p>
</div>
</t>
<t t-name="pos-onscreen-keyboard-simple-template">
<div class="keyboard_frame">
<ul class="keyboard simple_keyboard">
<li class="symbol firstitem row_qwerty"><span class="off">q</span><span class="on">1</span></li>
<li class="symbol"><span class="off">w</span><span class="on">2</span></li>
<li class="symbol"><span class="off">e</span><span class="on">3</span></li>
<li class="symbol"><span class="off">r</span><span class="on">4</span></li>
<li class="symbol"><span class="off">t</span><span class="on">5</span></li>
<li class="symbol"><span class="off">y</span><span class="on">6</span></li>
<li class="symbol"><span class="off">u</span><span class="on">7</span></li>
<li class="symbol"><span class="off">i</span><span class="on">8</span></li>
<li class="symbol"><span class="off">o</span><span class="on">9</span></li>
<li class="symbol lastitem"><span class="off">p</span><span class="on">0</span></li>
<li class="symbol firstitem row_asdf"><span class="off">a</span><span class="on">@</span></li>
<li class="symbol"><span class="off">s</span><span class="on">#</span></li>
<li class="symbol"><span class="off">d</span><span class="on">%</span></li>
<li class="symbol"><span class="off">f</span><span class="on">*</span></li>
<li class="symbol"><span class="off">g</span><span class="on">/</span></li>
<li class="symbol"><span class="off">h</span><span class="on">-</span></li>
<li class="symbol"><span class="off">j</span><span class="on">+</span></li>
<li class="symbol"><span class="off">k</span><span class="on">(</span></li>
<li class="symbol lastitem"><span class="off">l</span><span class="on">)</span></li>
<li class="symbol firstitem row_zxcv"><span class="off">z</span><span class="on">?</span></li>
<li class="symbol"><span class="off">x</span><span class="on">!</span></li>
<li class="symbol"><span class="off">c</span><span class="on">"</span></li>
<li class="symbol"><span class="off">v</span><span class="on">'</span></li>
<li class="symbol"><span class="off">b</span><span class="on">:</span></li>
<li class="symbol"><span class="off">n</span><span class="on">;</span></li>
<li class="symbol"><span class="off">m</span><span class="on">,</span></li>
<li class="delete lastitem">delete</li>
<li class="numlock firstitem row_space"><span class="off">123</span><span class="on">ABC</span></li>
<li class="space">&amp;nbsp;</li>
<li class="symbol"><span class="off">.</span><span class="on">.</span></li>
<li class="return lastitem">return</li>
</ul>
<p class="close_button">close</p>
</div>
</t>
</templates>
@@ -36,6 +36,14 @@
key2="client_action_multi"
id="action_box_entries" />
<act_window name="Put Money In"
res_model="pos.box.entries"
src_model="pos.session"
view_mode="form"
target="new"
key2="client_action_multi"
id="action_pos_session_box_entries" />
</data>
</openerp>
@@ -36,5 +36,13 @@
key2="client_action_multi"
id="action_box_out" />
<act_window name="Take Money Out"
res_model="pos.box.out"
src_model="account.bank.statement"
view_mode="form"
target="new"
key2="client_action_multi"
id="action_pos_session_box_out" />
</data>
</openerp>
+1 -1
View File
@@ -518,7 +518,7 @@ class product_product(osv.osv):
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the product without removing it."),
'variants': fields.char('Variants', size=64),
'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade"),
'ean13': fields.char('EAN13', size=13),
'ean13': fields.char('EAN13', size=13, help='Barcode'),
'packaging' : fields.one2many('product.packaging', 'product_id', 'Logistical Units', help="Gives the different ways to package the same product. This has no impact on the picking order and is mainly used if you use the EDI module."),
'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Sale Price')),
'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Sale Price')),