1 function openerp_pos_models(module, instance){ //module is instance.point_of_sale
2 var QWeb = instance.web.qweb;
4 module.LocalStorageDAO = instance.web.Class.extend({
5 add_operation: function(operation) {
7 return $.async_when().pipe(function() {
8 var tmp = self._get('oe_pos_operations', []);
9 var last_id = self._get('oe_pos_operations_sequence', 1);
10 tmp.push({'id': last_id, 'data': operation});
11 self._set('oe_pos_operations', tmp);
12 self._set('oe_pos_operations_sequence', last_id + 1);
15 remove_operation: function(id) {
17 return $.async_when().pipe(function() {
18 var tmp = self._get('oe_pos_operations', []);
19 tmp = _.filter(tmp, function(el) {
22 self._set('oe_pos_operations', tmp);
25 get_operations: function() {
27 return $.async_when().pipe(function() {
28 return self._get('oe_pos_operations', []);
31 _get: function(key, default_) {
32 var txt = localStorage[key];
35 return JSON.parse(txt);
37 _set: function(key, value) {
38 localStorage[key] = JSON.stringify(value);
42 var fetch = function(osvModel, fields, domain){
43 var dataSetSearch = new instance.web.DataSetSearch(null, osvModel, {}, domain);
44 return dataSetSearch.read_slice(fields, 0);
48 Gets all the necessary data from the OpenERP web client (instance, shop data etc.)
50 module.PosModel = Backbone.Model.extend({
51 initialize: function(session, attributes) {
52 Backbone.Model.prototype.initialize.call(this, attributes);
54 this.dao = new module.LocalStorageDAO();
55 this.ready = $.Deferred();
56 this.flush_mutex = new $.Mutex();
57 this.build_tree = _.bind(this.build_tree, this);
58 this.session = session;
60 this.barcode_reader = new module.BarcodeReader({'pos': this});
61 this.proxy = new module.ProxyDevice({'pos': this});
63 'nbr_pending_operations': 0,
64 'currency': {symbol: '$', position: 'after'},
68 'orders': new module.OrderCollection(),
69 'products': new module.ProductCollection(),
70 //'cashRegisters': [], // new module.CashRegisterCollection(this.pos.get('bank_statements')),
71 'selectedOrder': undefined,
74 var cat_def = fetch('pos.category', ['name', 'parent_id', 'child_id'])
75 .pipe(function(result){
76 return self.set({'categories': result});
81 ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small'],
82 [['pos_categ_id','!=', false]]
83 ).then(function(result){
84 console.log('product_list:',result);
85 return self.set({'product_list': result});
89 'account.bank.statement',
90 ['account_id', 'currency', 'journal_id', 'state', 'name'],
91 [['state','=','open'], ['user_id', '=', this.session.uid]]
92 ).then(function(result){
93 console.log('bank_statements:',result);
94 return self.set({'bank_statements': result});
97 var tax_def = fetch('account.tax', ['amount','price_include','type'])
98 .then(function(result){
99 console.log('taxes:',result);
100 return self.set({'taxes': result});
103 $.when(cat_def,prod_def,bank_def,tax_def,this.get_app_data(), this.flush())
104 .pipe(_.bind(this.build_tree, this))
106 self.set({'cashRegisters': new module.CashRegisterCollection(self.get('bank_statements')) });
107 self.ready.resolve();
110 return (this.get('orders')).bind('remove', _.bind( function(removedOrder) {
111 if ((this.get('orders')).isEmpty()) {
112 this.addAndSelectOrder(new module.Order({pos: self}));
114 if ((this.get('selectedOrder')) === removedOrder) {
116 selectedOrder: (this.get('orders')).last()
123 get_app_data: function() {
125 return $.when(new instance.web.Model("sale.shop").get_func("search_read")([]).pipe(function(result) {
126 self.set({'shop': result[0]});
127 var company_id = result[0]['company_id'][0];
128 return new instance.web.Model("res.company").get_func("read")(company_id, ['currency_id', 'name', 'phone']).pipe(function(result) {
129 self.set({'company': result});
130 var currency_id = result['currency_id'][0]
131 return new instance.web.Model("res.currency").get_func("read")([currency_id],
132 ['symbol', 'position']).pipe(function(result) {
133 self.set({'currency': result[0]});
137 }), new instance.web.Model("res.users").get_func("read")(this.session.uid, ['name']).pipe(function(result) {
138 self.set({'user': result});
141 push_order: function(record) {
143 return this.dao.add_operation(record).pipe(function(){
147 addAndSelectOrder: function(newOrder) {
148 (this.get('orders')).add(newOrder);
150 selectedOrder: newOrder
154 return this.flush_mutex.exec(_.bind(function() {
155 return this._int_flush();
158 _int_flush : function() {
161 this.dao.get_operations().pipe(function(operations) {
162 self.set( {'nbr_pending_operations':operations.length} );
163 if(operations.length === 0){
166 var op = operations[0];
168 // we prevent the default error handler and assume errors
169 // are a normal use case, except we stop the current iteration
171 return new instance.web.Model('pos.order').get_func('create_from_ui')([op])
172 .fail(function(unused, event){
173 event.preventDefault();
176 console.debug('saved 1 record'); //TODO Debug this
177 self.dao.remove_operation(operations[0].id).pipe(function(){
178 return self._int_flush();
185 build_tree: function() {
186 var c, id, _i, _len, _ref, _ref2;
187 _ref = this.get('categories');
188 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
190 this.categories[c.id] = {
193 children: c.child_id,
194 parent: c.parent_id[0],
199 _ref2 = this.categories;
202 this.current_category = c;
203 this.build_ancestors(c.parent);
204 this.build_subtree(c);
206 this.categories[0] = {
208 children: (function() {
209 var _j, _len2, _ref3, _results;
210 _ref3 = this.get('categories');
212 for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) {
214 if (!(c.parent_id[0] != null)) {
220 subtree: (function() {
221 var _j, _len2, _ref3, _results;
222 _ref3 = this.get('categories');
224 for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) {
232 build_ancestors: function(parent) {
233 if (parent != null) {
234 this.current_category.ancestors.unshift(parent);
235 return this.build_ancestors(this.categories[parent].parent);
238 build_subtree: function(category) {
239 var c, _i, _len, _ref, _results;
240 _ref = category.children;
242 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
244 this.current_category.subtree.push(c);
245 _results.push(this.build_subtree(this.categories[c]));
251 module.CashRegister = Backbone.Model.extend({
254 module.CashRegisterCollection = Backbone.Collection.extend({
255 model: module.CashRegister,
258 module.Product = Backbone.Model.extend({
261 module.ProductCollection = Backbone.Collection.extend({
262 model: module.Product,
266 Each Order contains zero or more Orderlines (i.e. the content of the "shopping cart".)
267 There should only ever be one Orderline per distinct product in an Order.
268 To add more of the same product, just update the quantity accordingly.
269 The Order also contains payment information.
271 module.Orderline = Backbone.Model.extend({
278 initialize: function(attributes) {
279 this.pos = attributes.pos;
280 console.log(attributes);
281 Backbone.Model.prototype.initialize.apply(this, arguments);
283 if(attributes.weight){
284 this.setWeight(attributes.weight);
285 this.set({weighted: true});
288 this.bind('change:quantity', function(unused, qty) {
290 this.trigger('killme');
293 setWeight: function(weight){
298 incrementQuantity: function() {
300 quantity: (this.get('quantity')) + 1
303 incrementWeight: function(weight){
305 quantity: (this.get('quantity')) + weight,
308 getPriceWithoutTax: function() {
309 return this.getAllPrices().priceWithoutTax;
311 getPriceWithTax: function() {
312 return this.getAllPrices().priceWithTax;
315 return this.getAllPrices().tax;
317 getAllPrices: function() {
319 var base = (this.get('quantity')) * (this.get('list_price')) * (1 - (this.get('discount')) / 100);
321 var totalNoTax = base;
323 var product_list = self.pos.get('product_list');
324 var product = _.detect(product_list, function(el) {return el.id === self.get('id');});
325 var taxes_ids = product.taxes_id;
326 var taxes = self.pos.get('taxes');
328 _.each(taxes_ids, function(el) {
329 var tax = _.detect(taxes, function(t) {return t.id === el;});
330 if (tax.price_include) {
332 if (tax.type === "percent") {
333 tmp = base - (base / (1 + tax.amount));
334 } else if (tax.type === "fixed") {
335 tmp = tax.amount * self.get('quantity');
337 throw "This type of tax is not supported by the point of sale: " + tax.type;
343 if (tax.type === "percent") {
344 tmp = tax.amount * base;
345 } else if (tax.type === "fixed") {
346 tmp = tax.amount * self.get('quantity');
348 throw "This type of tax is not supported by the point of sale: " + tax.type;
355 "priceWithTax": totalTax,
356 "priceWithoutTax": totalNoTax,
360 exportAsJSON: function() {
362 qty: this.get('quantity'),
363 price_unit: this.get('list_price'),
364 discount: this.get('discount'),
365 product_id: this.get('id')
370 module.OrderlineCollection = Backbone.Collection.extend({
371 model: module.Orderline,
374 // Every PaymentLine has all the attributes of the corresponding CashRegister.
375 module.Paymentline = Backbone.Model.extend({
379 initialize: function(attributes) {
380 Backbone.Model.prototype.initialize.apply(this, arguments);
382 getAmount: function(){
383 return this.get('amount');
385 exportAsJSON: function(){
387 name: instance.web.datetime_to_str(new Date()),
388 statement_id: this.get('id'),
389 account_id: (this.get('account_id'))[0],
390 journal_id: (this.get('journal_id'))[0],
391 amount: this.getAmount()
396 module.PaymentlineCollection = Backbone.Collection.extend({
397 model: module.Paymentline,
400 module.Order = Backbone.Model.extend({
405 initialize: function(attributes){
406 Backbone.Model.prototype.initialize.apply(this, arguments);
408 creationDate: new Date(),
409 orderLines: new module.OrderlineCollection(),
410 paymentLines: new module.PaymentlineCollection(),
411 name: "Order " + this.generateUniqueId(),
413 this.pos = attributes.pos; //TODO put that in set and remember to use 'get' to read it ...
414 this.bind('change:validated', this.validatedChanged);
418 'change:validated': 'validatedChanged'
420 validatedChanged: function() {
421 if (this.get("validated") && !this.previous("validated")) {
422 this.pos.screen_selector.set_current_screen('receipt');
423 //this.set({'screen': 'receipt'});
426 generateUniqueId: function() {
427 return new Date().getTime();
429 addProduct: function(product) {
431 existing = (this.get('orderLines')).get(product.id);
432 if (existing != null) {
433 if(existing.get('weighted')){
434 existing.incrementWeight(product.attributes.weight);
436 existing.incrementQuantity();
439 var attr = product.toJSON();
441 var line = new module.Orderline(attr);
442 this.get('orderLines').add(line);
443 line.bind('killme', function() {
444 this.get('orderLines').remove(line);
448 addPaymentLine: function(cashRegister) {
450 newPaymentline = new module.Paymentline(cashRegister);
451 /* TODO: Should be 0 for cash-like accounts */
453 amount: this.getDueLeft()
455 return (this.get('paymentLines')).add(newPaymentline);
457 getName: function() {
458 return this.get('name');
460 getTotal: function() {
461 return (this.get('orderLines')).reduce((function(sum, orderLine) {
462 return sum + orderLine.getPriceWithTax();
465 getTotalTaxExcluded: function() {
466 return (this.get('orderLines')).reduce((function(sum, orderLine) {
467 return sum + orderLine.getPriceWithoutTax();
471 return (this.get('orderLines')).reduce((function(sum, orderLine) {
472 return sum + orderLine.getTax();
475 getPaidTotal: function() {
476 return (this.get('paymentLines')).reduce((function(sum, paymentLine) {
477 return sum + paymentLine.getAmount();
480 getChange: function() {
481 return this.getPaidTotal() - this.getTotal();
483 getDueLeft: function() {
484 return this.getTotal() - this.getPaidTotal();
486 exportAsJSON: function() {
487 var orderLines, paymentLines;
489 (this.get('orderLines')).each(_.bind( function(item) {
490 return orderLines.push([0, 0, item.exportAsJSON()]);
493 (this.get('paymentLines')).each(_.bind( function(item) {
494 return paymentLines.push([0, 0, item.exportAsJSON()]);
497 name: this.getName(),
498 amount_paid: this.getPaidTotal(),
499 amount_total: this.getTotal(),
500 amount_tax: this.getTax(),
501 amount_return: this.getChange(),
503 statement_ids: paymentLines
508 module.OrderCollection = Backbone.Collection.extend({
513 The numpad handles both the choice of the property currently being modified
514 (quantity, price or discount) and the edition of the corresponding numeric value.
516 module.NumpadState = Backbone.Model.extend({
521 appendNewChar: function(newChar) {
523 oldBuffer = this.get('buffer');
524 if (oldBuffer === '0') {
528 } else if (oldBuffer === '-0') {
530 buffer: "-" + newChar
534 buffer: (this.get('buffer')) + newChar
539 deleteLastChar: function() {
541 tempNewBuffer = (this.get('buffer')).slice(0, -1) || "0";
542 if (isNaN(tempNewBuffer)) {
546 buffer: tempNewBuffer
550 switchSign: function() {
552 oldBuffer = this.get('buffer');
554 buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
558 changeMode: function(newMode) {
570 updateTarget: function() {
571 var bufferContent, params;
572 bufferContent = this.get('buffer');
573 if (bufferContent && !isNaN(bufferContent)) {
574 this.trigger('setValue', parseFloat(bufferContent));