X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fpoint_of_sale%2Fstatic%2Fsrc%2Fjs%2Fmodels.js;fp=addons%2Fpoint_of_sale%2Fstatic%2Fsrc%2Fjs%2Fmodels.js;h=946c7f16988c349eb6bf78d220e744e96481f95e;hb=fcd06145dda957a83e687fd033ff294c407da5f4;hp=f68f6897faebf4b67230115bf5e89495dcecd101;hpb=d9610efd979c974e540353716f8d0b9ecdb9d952;p=odoo%2Fodoo.git diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index f68f689..946c7f1 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1,6 +1,9 @@ -function openerp_pos_models(instance, module){ //module is instance.point_of_sale +openerp.point_of_sale.load_models = function load_models(instance, module){ //module is instance.point_of_sale + "use strict"; + var QWeb = instance.web.qweb; var _t = instance.web._t; + var barcode_parser_module = instance.barcodes; var round_di = instance.web.round_decimals; var round_pr = instance.web.round_precision @@ -24,7 +27,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.pos_widget = attributes.pos_widget; this.proxy = new module.ProxyDevice(this); // used to communicate to the hardware devices via a local proxy - this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy, patterns: {}}); // used to read barcodes + this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy}); + this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy this.db = new module.PosDB(); // a local database used to search trough products and categories & store pending orders this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode @@ -41,7 +45,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.partners = []; this.cashier = null; this.cashregisters = []; - this.bankstatements = []; this.taxes = []; this.pos_session = null; this.config = null; @@ -79,8 +82,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if(self.config.use_proxy){ return self.connect_to_proxy(); } - }); - + }); // used to read barcodes); }, // releases ressources holds by the model at the end of life of the posmodel @@ -162,14 +164,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal self.units_by_id = units_by_id; } },{ - model: 'res.users', - fields: ['name','ean13'], - domain: null, - loaded: function(self,users){ self.users = users; }, - },{ model: 'res.partner', - fields: ['name','street','city','state_id','country_id','vat','phone','zip','mobile','email','ean13','write_date'], - domain: null, + fields: ['name','street','city','state_id','country_id','vat','phone','zip','mobile','email','barcode','write_date'], + domain: [['customer','=',true]], loaded: function(self,partners){ self.partners = partners; self.db.add_partners(partners); @@ -190,18 +187,20 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal model: 'account.tax', fields: ['name','amount', 'price_include', 'type'], domain: null, - loaded: function(self,taxes){ self.taxes = taxes; }, + loaded: function(self,taxes){ + self.taxes = taxes; + self.taxes_by_id = {}; + + for (var i = 0; i < taxes.length; i++) { + self.taxes_by_id[taxes[i].id] = taxes[i]; + } + }, },{ model: 'pos.session', fields: ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number','login_number'], domain: function(self){ return [['state','=','opened'],['user_id','=',self.session.uid]]; }, loaded: function(self,pos_sessions){ self.pos_session = pos_sessions[0]; - - var orders = self.db.get_orders(); - for (var i = 0; i < orders.length; i++) { - self.pos_session.sequence_number = Math.max(self.pos_session.sequence_number, orders[i].data.sequence_number+1); - } }, },{ model: 'pos.config', @@ -214,19 +213,46 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal self.config.iface_print_via_proxy || self.config.iface_scan_via_proxy || self.config.iface_cashdrawer; - - self.barcode_reader.add_barcode_patterns({ - 'product': self.config.barcode_product, - 'cashier': self.config.barcode_cashier, - 'client': self.config.barcode_customer, - 'weight': self.config.barcode_weight, - 'discount': self.config.barcode_discount, - 'price': self.config.barcode_price, - }); if (self.config.company_id[0] !== self.user.company_id[0]) { throw new Error(_t("Error: The Point of Sale User must belong to the same company as the Point of Sale. You are probably trying to load the point of sale as an administrator in a multi-company setup, with the administrator account set to the wrong company.")); } + + self.db.set_uuid(self.config.uuid); + + var orders = self.db.get_orders(); + for (var i = 0; i < orders.length; i++) { + self.pos_session.sequence_number = Math.max(self.pos_session.sequence_number, orders[i].data.sequence_number+1); + } + }, + },{ + model: 'res.users', + fields: ['name','pos_security_pin','groups_id','barcode'], + domain: function(self){ return [['company_id','=',self.user.company_id[0]],'|', ['groups_id','=', self.config.group_pos_manager_id[0]],['groups_id','=', self.config.group_pos_user_id[0]]]; }, + loaded: function(self,users){ + // we attribute a role to the user, 'cashier' or 'manager', depending + // on the group the user belongs. + var pos_users = []; + for (var i = 0; i < users.length; i++) { + var user = users[i]; + for (var j = 0; j < user.groups_id.length; j++) { + var group_id = user.groups_id[j]; + if (group_id === self.config.group_pos_manager_id[0]) { + user.role = 'manager'; + break; + } else if (group_id === self.config.group_pos_user_id[0]) { + user.role = 'cashier'; + } + } + if (user.role) { + pos_users.push(user); + } + // replace the current user with its updated version + if (user.id === self.user.id) { + self.user = user; + } + } + self.users = pos_users; }, },{ model: 'stock.location', @@ -253,7 +279,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, },{ model: 'product.packaging', - fields: ['ean','product_tmpl_id'], + fields: ['barcode','product_tmpl_id'], domain: null, loaded: function(self, packagings){ self.db.add_packagings(packagings); @@ -267,10 +293,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, },{ model: 'product.product', - fields: ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', - 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'description_sale', 'description', + fields: ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'barcode', 'default_code', + 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description', 'product_tmpl_id'], - domain: function(self){ return [['sale_ok','=',true],['available_in_pos','=',true]]; }, + order: ['sequence','name'], + domain: [['sale_ok','=',true],['available_in_pos','=',true]], context: function(self){ return { pricelist: self.pricelist.id, display_default_code: false }; }, loaded: function(self, products){ self.db.add_products(products); @@ -279,11 +306,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal model: 'account.bank.statement', fields: ['account_id','currency','journal_id','state','name','user_id','pos_session_id'], domain: function(self){ return [['state', '=', 'open'],['pos_session_id', '=', self.pos_session.id]]; }, - loaded: function(self, bankstatements, tmp){ - self.bankstatements = bankstatements; + loaded: function(self, cashregisters, tmp){ + self.cashregisters = cashregisters; tmp.journals = []; - _.each(bankstatements,function(statement){ + _.each(cashregisters,function(statement){ tmp.journals.push(statement.journal_id[0]); }); }, @@ -295,17 +322,26 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal self.journals = journals; // associate the bank statements with their journals. - var bankstatements = self.bankstatements; - for(var i = 0, ilen = bankstatements.length; i < ilen; i++){ + var cashregisters = self.cashregisters; + for(var i = 0, ilen = cashregisters.length; i < ilen; i++){ for(var j = 0, jlen = journals.length; j < jlen; j++){ - if(bankstatements[i].journal_id[0] === journals[j].id){ - bankstatements[i].journal = journals[j]; + if(cashregisters[i].journal_id[0] === journals[j].id){ + cashregisters[i].journal = journals[j]; } } } - self.cashregisters = bankstatements; + + self.cashregisters_by_id = {}; + for (var i = 0; i < self.cashregisters.length; i++) { + self.cashregisters_by_id[self.cashregisters[i].id] = self.cashregisters[i]; + } + + self.cashregisters = self.cashregisters.sort(function(a,b){ + return a.journal.sequence - b.journal.sequence; + }); + }, - },{ + }, { label: 'fonts', loaded: function(self){ var fonts_loaded = new $.Deferred(); @@ -358,7 +394,14 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal return logo_loaded; }, - }, + }, { + label: 'barcodes', + loaded: function(self) { + var barcode_parser = new barcode_parser_module.BarcodeParser({'nomenclature_id': self.config.barcode_nomenclature_id}); + self.barcode_reader.set_barcode_parser(barcode_parser); + return barcode_parser.is_loaded(); + }, + } ], // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded. @@ -375,10 +418,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }else{ var model = self.models[index]; self.pos_widget.loading_message(_t('Loading')+' '+(model.label || model.model || ''), progress); + + var cond = typeof model.condition === 'function' ? model.condition(self,tmp) : true; + if (!cond) { + load_model(index+1); + return; + } + var fields = typeof model.fields === 'function' ? model.fields(self,tmp) : model.fields; var domain = typeof model.domain === 'function' ? model.domain(self,tmp) : model.domain; var context = typeof model.context === 'function' ? model.context(self,tmp) : model.context; var ids = typeof model.ids === 'function' ? model.ids(self,tmp) : model.ids; + var order = typeof model.order === 'function' ? model.order(self,tmp): model.order; progress += progress_step; @@ -386,7 +437,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if (model.ids) { var records = new instance.web.Model(model.model).call('read',[ids,fields],context); } else { - var records = new instance.web.Model(model.model).query(fields).filter(domain).context(context).all() + var records = new instance.web.Model(model.model).query(fields).filter(domain).order_by(order).context(context).all() } records.then(function(result){ try{ // catching exceptions in model.loaded(...) @@ -394,6 +445,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal .then(function(){ load_model(index + 1); }, function(err){ loaded.reject(err); }); }catch(err){ + console.error(err.stack); loaded.reject(err); } },function(err){ @@ -438,16 +490,17 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } else { def.reject(); } - }, function(){ def.reject(); }); + }, function(err,event){ event.preventDefault(); def.reject(); }); return def; }, // this is called when an order is removed from the order collection. It ensures that there is always an existing // order and a valid selected order on_removed_order: function(removed_order,index,reason){ - if( (reason === 'abandon' || removed_order.temporary) && this.get('orders').size() > 0){ + var order_list = this.get_order_list(); + if( (reason === 'abandon' || removed_order.temporary) && order_list.length > 0){ // when we intentionally remove an unfinished order, and there is another existing one - this.set({'selectedOrder' : this.get('orders').at(index) || this.get('orders').last()}); + this.set_order(order_list[index] || order_list[order_list.length -1]); }else{ // when the order was automatically removed after completion, // or when we intentionally delete the only concurrent order @@ -455,20 +508,83 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } }, + // returns the user who is currently the cashier for this point of sale + get_cashier: function(){ + return this.cashier || this.user; + }, + // changes the current cashier + set_cashier: function(user){ + this.cashier = user; + }, //creates a new empty order and sets it as the current order add_new_order: function(){ - var order = new module.Order({pos:this}); + var order = new module.Order({},{pos:this}); this.get('orders').add(order); this.set('selectedOrder', order); + return order; + }, + // load the locally saved unpaid orders for this session. + load_orders: function(){ + var jsons = this.db.get_unpaid_orders(); + var orders = []; + var not_loaded_count = 0; + + for (var i = 0; i < jsons.length; i++) { + var json = jsons[i]; + if (json.pos_session_id === this.pos_session.id) { + orders.push(new module.Order({},{ + pos: this, + json: json, + })); + } else { + not_loaded_count += 1; + } + } + + if (not_loaded_count) { + console.info('There are '+not_loaded_count+' locally saved unpaid orders belonging to another session'); + } + + orders = orders.sort(function(a,b){ + return a.sequence_number - b.sequence_number; + }); + + if (orders.length) { + this.get('orders').add(orders); + } }, + set_start_order: function(){ + var orders = this.get('orders').models; + + if (orders.length && !this.get('selectedOrder')) { + this.set('selectedOrder',orders[0]); + } else { + this.add_new_order(); + } + }, + + // return the current order get_order: function(){ return this.get('selectedOrder'); }, + // change the current order + set_order: function(order){ + this.set({ selectedOrder: order }); + }, + + // return the list of unpaid orders + get_order_list: function(){ + return this.get('orders').models; + }, + //removes the current order delete_current_order: function(){ - this.get('selectedOrder').destroy({'reason':'abandon'}); + var order = this.get_order(); + if (order) { + order.destroy({'reason':'abandon'}); + } }, // saves the order locally and try to send it to the backend. @@ -602,8 +718,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal return server_ids; }).fail(function (error, event){ if(error.code === 200 ){ // Business Logic Error, not a connection problem - //if warning do not need to dispaly traceback!! - if(error.data.exception_type == 'warning'){ + //if warning do not need to display traceback!! + if (error.data.exception_type == 'warning') { delete error.data.debug; } self.pos_widget.screen_selector.show_popup('error-traceback',{ @@ -620,25 +736,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal scan_product: function(parsed_code){ var self = this; - var selectedOrder = this.get('selectedOrder'); - if(parsed_code.encoding === 'ean13'){ - var product = this.db.get_product_by_ean13(parsed_code.base_code); - }else if(parsed_code.encoding === 'reference'){ - var product = this.db.get_product_by_reference(parsed_code.code); - } + var selectedOrder = this.get_order(); + var product = this.db.get_product_by_barcode(parsed_code.base_code); if(!product){ return false; } if(parsed_code.type === 'price'){ - selectedOrder.addProduct(product, {price:parsed_code.value}); + selectedOrder.add_product(product, {price:parsed_code.value}); }else if(parsed_code.type === 'weight'){ - selectedOrder.addProduct(product, {quantity:parsed_code.value, merge:false}); + selectedOrder.add_product(product, {quantity:parsed_code.value, merge:false}); }else if(parsed_code.type === 'discount'){ - selectedOrder.addProduct(product, {discount:parsed_code.value, merge:false}); + selectedOrder.add_product(product, {discount:parsed_code.value, merge:false}); }else{ - selectedOrder.addProduct(product); + selectedOrder.add_product(product); } return true; }, @@ -651,8 +763,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // An Order contains zero or more Orderlines. module.Orderline = Backbone.Model.extend({ initialize: function(attr,options){ - this.pos = options.pos; + this.pos = options.pos; this.order = options.order; + if (options.json) { + this.init_from_JSON(options.json); + return; + } this.product = options.product; this.price = options.product.price; this.quantity = 1; @@ -663,6 +779,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.selected = false; this.id = orderline_id++; }, + init_from_JSON: function(json) { + this.product = this.pos.db.get_product_by_id(json.product_id); + if (!this.product) { + console.error('ERROR: attempting to recover product not available in the point of sale'); + } + this.price = json.price_unit; + this.set_discount(json.discount); + this.set_quantity(json.qty); + }, clone: function(){ var orderline = new module.Orderline({},{ pos: this.pos, @@ -699,7 +824,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // rounded to zero set_quantity: function(quantity){ if(quantity === 'remove'){ - this.order.removeOrderline(this); + this.order.remove_orderline(this); return; }else{ var quant = parseFloat(quantity) || 0; @@ -792,7 +917,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal return { quantity: this.get_quantity(), unit_name: this.get_unit().name, - price: this.get_unit_price(), + price: this.get_unit_display_price(), discount: this.get_discount(), product_name: this.get_product().display_name, price_display : this.get_display_price(), @@ -811,12 +936,28 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal get_unit_price: function(){ return this.price; }, + get_unit_display_price: function(){ + if (this.pos.config.iface_tax_included) { + var quantity = this.quantity; + this.quantity = 1.0; + var price = this.get_all_prices().priceWithTax; + this.quantity = quantity; + return price; + } else { + return this.get_unit_price(); + } + }, get_base_price: function(){ var rounding = this.pos.currency.rounding; return round_pr(round_pr(this.get_unit_price() * this.get_quantity(),rounding) * (1- this.get_discount()/100.0),rounding); }, get_display_price: function(){ return this.get_base_price(); + if (this.pos.config.iface_tax_included) { + return this.get_all_prices().priceWithTax; + } else { + return this.get_base_price(); + } }, get_price_without_tax: function(){ return this.get_all_prices().priceWithoutTax; @@ -830,6 +971,14 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal get_tax_details: function(){ return this.get_all_prices().taxDetails; }, + get_taxes: function(){ + var taxes_ids = this.get_product().taxes_id; + var taxes = []; + for (var i = 0; i < taxes_ids.length; i++) { + taxes.push(this.pos.taxes_by_id[taxes_ids[i]]); + } + return taxes; + }, get_all_prices: function(){ var self = this; var currency_rounding = this.pos.currency.rounding; @@ -888,16 +1037,25 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // Every Paymentline contains a cashregister and an amount of money. module.Paymentline = Backbone.Model.extend({ initialize: function(attributes, options) { + this.pos = options.pos; this.amount = 0; + this.selected = false; + if (options.json) { + this.init_from_JSON(options.json); + return; + } this.cashregister = options.cashregister; this.name = this.cashregister.journal_id[1]; - this.selected = false; - this.pos = options.pos; + }, + init_from_JSON: function(json){ + this.amount = json.amount; + this.cashregister = this.pos.cashregisters_by_id[json.statement_id]; + this.name = this.cashregister.journal_id[1]; }, //sets the amount of money on this payment line set_amount: function(value){ this.amount = round_di(parseFloat(value) || 0, this.pos.currency.decimals); - this.trigger('change:amount',this); + this.trigger('change',this); }, // returns the amount of money on this paymentline get_amount: function(){ @@ -909,7 +1067,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal set_selected: function(selected){ if(this.selected !== selected){ this.selected = selected; - this.trigger('change:selected',this); + this.trigger('change',this); } }, // returns the associated cashregister @@ -935,40 +1093,219 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal module.PaymentlineCollection = Backbone.Collection.extend({ model: module.Paymentline, }); - // An order more or less represents the content of a client's shopping cart (the OrderLines) // plus the associated payment information (the Paymentlines) // there is always an active ('selected') order in the Pos, a new one is created // automaticaly once an order is completed and sent to the server. module.Order = Backbone.Model.extend({ - initialize: function(attributes){ + initialize: function(attributes,options){ Backbone.Model.prototype.initialize.apply(this, arguments); - this.pos = attributes.pos; - this.sequence_number = this.pos.pos_session.sequence_number++; - this.uid = this.generateUniqueId(); - this.set({ - creationDate: new Date(), - orderLines: new module.OrderlineCollection(), - paymentLines: new module.PaymentlineCollection(), - name: _t("Order ") + this.uid, - client: null, - }); + options = options || {}; + + this.init_locked = true; + this.pos = options.pos; this.selected_orderline = undefined; this.selected_paymentline = undefined; - this.screen_data = {}; // see ScreenSelector - this.receipt_type = 'receipt'; // 'receipt' || 'invoice' - this.temporary = attributes.temporary || false; - this.to_invoice = false; + this.screen_data = {}; // see ScreenSelector + this.temporary = options.temporary || false; + this.creation_date = new Date(); + this.to_invoice = false; + this.orderlines = new module.OrderlineCollection(); + this.paymentlines = new module.PaymentlineCollection(); + this.pos_session_id = this.pos.pos_session.id; + + this.set({ client: null }); + + if (options.json) { + this.init_from_JSON(options.json); + } else { + this.sequence_number = this.pos.pos_session.sequence_number++; + this.uid = this.generate_unique_id(); + this.name = _t("Order ") + this.uid; + } + + this.on('change', function(){ this.save_to_db("order:change"); }, this); + this.orderlines.on('change', function(){ this.save_to_db("orderline:change"); }, this); + this.orderlines.on('add', function(){ this.save_to_db("orderline:add"); }, this); + this.orderlines.on('remove', function(){ this.save_to_db("orderline:remove"); }, this); + this.paymentlines.on('change', function(){ this.save_to_db("paymentline:change"); }, this); + this.paymentlines.on('add', function(){ this.save_to_db("paymentline:add"); }, this); + this.paymentlines.on('remove', function(){ this.save_to_db("paymentline:rem"); }, this); + + this.init_locked = false; + this.save_to_db(); + return this; }, + save_to_db: function(){ + if (!this.init_locked) { + this.pos.db.save_unpaid_order(this); + } + }, + init_from_JSON: function(json) { + this.sequence_number = json.sequence_number; + this.pos.pos_session.sequence_number = Math.max(this.sequence_number+1,this.pos.pos_session.sequence_number); + this.session_id = json.pos_session_id; + this.uid = json.uid; + this.name = _t("Order ") + this.uid; + if (json.partner_id) { + var client = this.pos.db.get_partner_by_id(json.partner_id); + if (!client) { + console.error('ERROR: trying to load a parner not available in the pos'); + } + } else { + var client = null; + } + this.set_client(client); + + this.temporary = false; // FIXME + this.to_invoice = false; // FIXME + + var orderlines = json.lines; + for (var i = 0; i < orderlines.length; i++) { + var orderline = orderlines[i][2]; + this.add_orderline(new module.Orderline({}, {pos: this.pos, order: this, json: orderline})); + } + + var paymentlines = json.statement_ids; + for (var i = 0; i < paymentlines.length; i++) { + var paymentline = paymentlines[i][2]; + var newpaymentline = new module.Paymentline({},{pos: this.pos, json: paymentline}); + this.paymentlines.add(newpaymentline); + + if (i === paymentlines.length - 1) { + this.select_paymentline(newpaymentline); + } + } + }, + export_as_JSON: function() { + var orderLines, paymentLines; + orderLines = []; + this.orderlines.each(_.bind( function(item) { + return orderLines.push([0, 0, item.export_as_JSON()]); + }, this)); + paymentLines = []; + this.paymentlines.each(_.bind( function(item) { + return paymentLines.push([0, 0, item.export_as_JSON()]); + }, this)); + return { + name: this.get_name(), + amount_paid: this.get_total_paid(), + amount_total: this.get_total_with_tax(), + amount_tax: this.get_total_tax(), + amount_return: this.get_change(), + lines: orderLines, + statement_ids: paymentLines, + pos_session_id: this.pos_session_id, + partner_id: this.get_client() ? this.get_client().id : false, + user_id: this.pos.cashier ? this.pos.cashier.id : this.pos.user.id, + uid: this.uid, + sequence_number: this.sequence_number, + }; + }, + export_for_printing: function(){ + var orderlines = []; + var self = this; + + this.orderlines.each(function(orderline){ + orderlines.push(orderline.export_for_printing()); + }); + + var paymentlines = []; + this.paymentlines.each(function(paymentline){ + paymentlines.push(paymentline.export_for_printing()); + }); + var client = this.get('client'); + var cashier = this.pos.cashier || this.pos.user; + var company = this.pos.company; + var shop = this.pos.shop; + var date = new Date(); + + function is_xml(subreceipt){ + return subreceipt ? (subreceipt.split('\n')[0].indexOf('= 0) : false; + } + + function render_xml(subreceipt){ + if (!is_xml(subreceipt)) { + return subreceipt; + } else { + subreceipt = subreceipt.split('\n').slice(1).join('\n'); + var qweb = new QWeb2.Engine(); + qweb.debug = instance.session.debug; + qweb.default_dict = _.clone(QWeb.default_dict); + qweb.add_template(''+subreceipt+''); + + return qweb.render('subreceipt',{'pos':self.pos,'widget':self.pos.pos_widget,'order':self, 'receipt': receipt}) ; + } + } + + var receipt = { + orderlines: orderlines, + paymentlines: paymentlines, + subtotal: this.get_subtotal(), + total_with_tax: this.get_total_with_tax(), + total_without_tax: this.get_total_without_tax(), + total_tax: this.get_total_tax(), + total_paid: this.get_total_paid(), + total_discount: this.get_total_discount(), + tax_details: this.get_tax_details(), + change: this.get_change(), + name : this.get_name(), + client: client ? client.name : null , + invoice_id: null, //TODO + cashier: cashier ? cashier.name : null, + header: this.pos.config.receipt_header || '', + footer: this.pos.config.receipt_footer || '', + precision: { + price: 2, + money: 2, + quantity: 3, + }, + date: { + year: date.getFullYear(), + month: date.getMonth(), + date: date.getDate(), // day of the month + day: date.getDay(), // day of the week + hour: date.getHours(), + minute: date.getMinutes() , + isostring: date.toISOString(), + localestring: date.toLocaleString(), + }, + company:{ + email: company.email, + website: company.website, + company_registry: company.company_registry, + contact_address: company.partner_id[1], + vat: company.vat, + name: company.name, + phone: company.phone, + logo: this.pos.company_logo_base64, + }, + shop:{ + name: shop.name, + }, + currency: this.pos.currency, + }; + + if (is_xml(this.pos.config.receipt_header)){ + receipt.header_xml = render_xml(this.pos.config.receipt_header); + } + + if (is_xml(this.pos.config.receipt_footer)){ + receipt.footer_xml = render_xml(this.pos.config.receipt_footer); + } + + return receipt; + }, is_empty: function(){ - return (this.get('orderLines').models.length === 0); + return this.orderlines.models.length === 0; }, - // Generates a public identification number for the order. - // The generated number must be unique and sequential. They are made 12 digit long - // to fit into EAN-13 barcodes, should it be needed - generateUniqueId: function() { + generate_unique_id: function() { + // Generates a public identification number for the order. + // The generated number must be unique and sequential. They are made 12 digit long + // to fit into EAN-13 barcodes, should it be needed + function zero_pad(num,size){ var s = ""+num; while (s.length < size) { @@ -980,15 +1317,38 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal zero_pad(this.pos.pos_session.login_number,3) +'-'+ zero_pad(this.sequence_number,4); }, - addOrderline: function(line){ + get_name: function() { + return this.name; + }, + /* ---- Order Lines --- */ + add_orderline: function(line){ if(line.order){ - order.removeOrderline(line); + line.order.remove_orderline(line); } line.order = this; - this.get('orderLines').add(line); - this.selectLine(this.getLastOrderline()); + this.orderlines.add(line); + this.select_orderline(this.get_last_orderline()); + }, + get_orderline: function(id){ + var orderlines = this.orderlines.models; + for(var i = 0; i < orderlines.length; i++){ + if(orderlines[i].id === id){ + return orderlines[i]; + } + } + return null; }, - addProduct: function(product, options){ + get_orderlines: function(){ + return this.orderlines.models; + }, + get_last_orderline: function(){ + return this.orderlines.at(this.orderlines.length -1); + }, + remove_orderline: function( line ){ + this.orderlines.remove(line); + this.select_orderline(this.get_last_orderline()); + }, + add_product: function(product, options){ options = options || {}; var attr = JSON.parse(JSON.stringify(product)); attr.pos = this.pos; @@ -1005,75 +1365,117 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal line.set_discount(options.discount); } - var last_orderline = this.getLastOrderline(); + if(options.extras !== undefined){ + for (var prop in options.extras) { + line[prop] = options.extras[prop]; + } + } + + var last_orderline = this.get_last_orderline(); if( last_orderline && last_orderline.can_be_merged_with(line) && options.merge !== false){ last_orderline.merge(line); }else{ - this.get('orderLines').add(line); + this.orderlines.add(line); } - this.selectLine(this.getLastOrderline()); + this.select_orderline(this.get_last_orderline()); }, - removeOrderline: function( line ){ - this.get('orderLines').remove(line); - this.selectLine(this.getLastOrderline()); + get_selected_orderline: function(){ + return this.selected_orderline; }, - getOrderline: function(id){ - var orderlines = this.get('orderLines').models; - for(var i = 0; i < orderlines.length; i++){ - if(orderlines[i].id === id){ - return orderlines[i]; + select_orderline: function(line){ + if(line){ + if(line !== this.selected_orderline){ + if(this.selected_orderline){ + this.selected_orderline.set_selected(false); + } + this.selected_orderline = line; + this.selected_orderline.set_selected(true); } + }else{ + this.selected_orderline = undefined; } - return null; }, - getLastOrderline: function(){ - return this.get('orderLines').at(this.get('orderLines').length -1); + deselect_orderline: function(){ + if(this.selected_orderline){ + this.selected_orderline.set_selected(false); + this.selected_orderline = undefined; + } }, - addPaymentline: function(cashregister) { - var paymentLines = this.get('paymentLines'); - var newPaymentline = new module.Paymentline({},{cashregister:cashregister, pos:this.pos}); - if(cashregister.journal.type !== 'cash'){ - newPaymentline.set_amount( Math.max(this.getDueLeft(),0) ); + /* ---- Payment Lines --- */ + add_paymentline: function(cashregister) { + var newPaymentline = new module.Paymentline({},{cashregister:cashregister, pos: this.pos}); + if(cashregister.journal.type !== 'cash' || this.pos.config.iface_precompute_cash){ + newPaymentline.set_amount( Math.max(this.get_due(),0) ); } - paymentLines.add(newPaymentline); - this.selectPaymentline(newPaymentline); + this.paymentlines.add(newPaymentline); + this.select_paymentline(newPaymentline); }, - removePaymentline: function(line){ + get_paymentlines: function(){ + return this.paymentlines.models; + }, + remove_paymentline: function(line){ if(this.selected_paymentline === line){ - this.selectPaymentline(undefined); + this.select_paymentline(undefined); } - this.get('paymentLines').remove(line); + this.paymentlines.remove(line); }, - getName: function() { - return this.get('name'); + clean_empty_paymentlines: function() { + var lines = this.paymentlines.models; + var empty = []; + for ( var i = 0; i < lines.length; i++) { + if (!lines[i].get_amount()) { + empty.push(lines[i]); + } + } + for ( var i = 0; i < empty.length; i++) { + this.remove_paymentline(empty[i]); + } + }, + select_paymentline: function(line){ + if(line !== this.selected_paymentline){ + if(this.selected_paymentline){ + this.selected_paymentline.set_selected(false); + } + this.selected_paymentline = line; + if(this.selected_paymentline){ + this.selected_paymentline.set_selected(true); + } + this.trigger('change:selected_paymentline',this.selected_paymentline); + } }, - getSubtotal : function(){ - return (this.get('orderLines')).reduce((function(sum, orderLine){ + /* ---- Payment Status --- */ + get_subtotal : function(){ + return this.orderlines.reduce((function(sum, orderLine){ return sum + orderLine.get_display_price(); }), 0); }, - getTotalTaxIncluded: function() { - return (this.get('orderLines')).reduce((function(sum, orderLine) { + get_total_with_tax: function() { + return this.orderlines.reduce((function(sum, orderLine) { return sum + orderLine.get_price_with_tax(); }), 0); }, - getDiscountTotal: function() { - return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity()); + get_total_without_tax: function() { + return this.orderlines.reduce((function(sum, orderLine) { + return sum + orderLine.get_price_without_tax(); }), 0); }, - getTotalTaxExcluded: function() { - return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + orderLine.get_price_without_tax(); + get_total_discount: function() { + return this.orderlines.reduce((function(sum, orderLine) { + return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity()); }), 0); }, - getTax: function() { - return (this.get('orderLines')).reduce((function(sum, orderLine) { + get_total_tax: function() { + return this.orderlines.reduce((function(sum, orderLine) { return sum + orderLine.get_tax(); }), 0); }, - getTaxDetails: function(){ + get_total_paid: function() { + return this.paymentlines.reduce((function(sum, paymentLine) { + return sum + paymentLine.get_amount(); + }), 0); + }, + get_tax_details: function(){ var details = {}; var fulldetails = []; var taxes_by_id = {}; @@ -1082,7 +1484,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal taxes_by_id[this.pos.taxes[i].id] = this.pos.taxes[i]; } - this.get('orderLines').each(function(line){ + this.orderlines.each(function(line){ var ldetails = line.get_tax_details(); for(var id in ldetails){ if(ldetails.hasOwnProperty(id)){ @@ -1099,17 +1501,58 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal return fulldetails; }, - getPaidTotal: function() { - return (this.get('paymentLines')).reduce((function(sum, paymentLine) { - return sum + paymentLine.get_amount(); - }), 0); + // Returns a total only for the orderlines with products belonging to the category + get_total_for_category_with_tax: function(categ_id){ + var total = 0; + var self = this; + + if (categ_id instanceof Array) { + for (var i = 0; i < categ_id.length; i++) { + total += this.get_total_for_category_with_tax(categ_id[i]); + } + return total; + } + + this.orderlines.each(function(line){ + if ( self.pos.db.category_contains(categ_id,line.product.id) ) { + total += line.get_price_with_tax(); + } + }); + + return total; }, - getChange: function(paymentline) { + get_total_for_taxes: function(tax_id){ + var total = 0; + var self = this; + + if (!(tax_id instanceof Array)) { + tax_id = [tax_id]; + } + + var tax_set = {}; + + for (var i = 0; i < tax_id.length; i++) { + tax_set[tax_id[i]] = true; + } + + this.orderlines.each(function(line){ + var taxes_ids = line.get_product().taxes_id; + for (var i = 0; i < taxes_ids.length; i++) { + if (tax_set[taxes_ids[i]]) { + total += line.get_price_with_tax(); + return; + } + } + }); + + return total; + }, + get_change: function(paymentline) { if (!paymentline) { - var change = this.getPaidTotal() - this.getTotalTaxIncluded(); + var change = this.get_total_paid() - this.get_total_with_tax(); } else { - var change = -this.getTotalTaxIncluded(); - var lines = this.get('paymentLines').models; + var change = -this.get_total_with_tax(); + var lines = this.paymentlines.models; for (var i = 0; i < lines.length; i++) { change += lines[i].get_amount(); if (lines[i] === paymentline) { @@ -1119,12 +1562,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } return round_pr(Math.max(0,change), this.pos.currency.rounding); }, - getDueLeft: function(paymentline) { + get_due: function(paymentline) { if (!paymentline) { - var due = this.getTotalTaxIncluded() - this.getPaidTotal(); + var due = this.get_total_with_tax() - this.get_total_paid(); } else { - var due = this.getTotalTaxIncluded(); - var lines = this.get('paymentLines').models; + var due = this.get_total_with_tax(); + var lines = this.paymentlines.models; for (var i = 0; i < lines.length; i++) { if (lines[i] === paymentline) { break; @@ -1135,24 +1578,29 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } return round_pr(Math.max(0,due), this.pos.currency.rounding); }, - isPaid: function(){ - return this.getDueLeft() === 0; + is_paid: function(){ + return this.get_due() === 0; }, - isPaidWithCash: function(){ - return !!this.get('paymentLines').find( function(pl){ + is_paid_with_cash: function(){ + return !!this.paymentlines.find( function(pl){ return pl.cashregister.journal.type === 'cash'; }); }, finalize: function(){ this.destroy(); }, - // sets the type of receipt 'receipt'(default) or 'invoice' - set_receipt_type: function(type){ - this.receipt_type = type; + destroy: function(args){ + Backbone.Model.prototype.destroy.apply(this,arguments); + this.pos.db.remove_unpaid_order(this); + }, + /* ---- Invoice --- */ + set_to_invoice: function(to_invoice) { + this.to_invoice = to_invoice; }, - get_receipt_type: function(){ - return this.receipt_type; + is_to_invoice: function(){ + return this.to_invoice; }, + /* ---- Client / Customer --- */ // the client related to the current order. set_client: function(client){ this.set('client',client); @@ -1164,6 +1612,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var client = this.get('client'); return client ? client.name : ""; }, + /* ---- Screen Status --- */ // the order also stores the screen status, as the PoS supports // different active screens per order. This method is used to // store the screen status. @@ -1171,158 +1620,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if(arguments.length === 2){ this.screen_data[key] = value; }else if(arguments.length === 1){ - for(key in arguments[0]){ + for(var key in arguments[0]){ this.screen_data[key] = arguments[0][key]; } } }, - set_to_invoice: function(to_invoice) { - this.to_invoice = to_invoice; - }, - is_to_invoice: function(){ - return this.to_invoice; - }, - // remove all the paymentlines with zero money in it - clean_empty_paymentlines: function() { - var lines = this.get('paymentLines').models; - var empty = []; - for ( var i = 0; i < lines.length; i++) { - if (!lines[i].get_amount()) { - empty.push(lines[i]); - } - } - for ( var i = 0; i < empty.length; i++) { - this.removePaymentline(empty[i]); - } - }, //see set_screen_data get_screen_data: function(key){ return this.screen_data[key]; }, - // exports a JSON for receipt printing - export_for_printing: function(){ - var orderlines = []; - this.get('orderLines').each(function(orderline){ - orderlines.push(orderline.export_for_printing()); - }); - - var paymentlines = []; - this.get('paymentLines').each(function(paymentline){ - paymentlines.push(paymentline.export_for_printing()); - }); - var client = this.get('client'); - var cashier = this.pos.cashier || this.pos.user; - var company = this.pos.company; - var shop = this.pos.shop; - var date = new Date(); - - return { - orderlines: orderlines, - paymentlines: paymentlines, - subtotal: this.getSubtotal(), - total_with_tax: this.getTotalTaxIncluded(), - total_without_tax: this.getTotalTaxExcluded(), - total_tax: this.getTax(), - total_paid: this.getPaidTotal(), - total_discount: this.getDiscountTotal(), - tax_details: this.getTaxDetails(), - change: this.getChange(), - name : this.getName(), - client: client ? client.name : null , - invoice_id: null, //TODO - cashier: cashier ? cashier.name : null, - header: this.pos.config.receipt_header || '', - footer: this.pos.config.receipt_footer || '', - precision: { - price: 2, - money: 2, - quantity: 3, - }, - date: { - year: date.getFullYear(), - month: date.getMonth(), - date: date.getDate(), // day of the month - day: date.getDay(), // day of the week - hour: date.getHours(), - minute: date.getMinutes() , - isostring: date.toISOString(), - localestring: date.toLocaleString(), - }, - company:{ - email: company.email, - website: company.website, - company_registry: company.company_registry, - contact_address: company.partner_id[1], - vat: company.vat, - name: company.name, - phone: company.phone, - logo: this.pos.company_logo_base64, - }, - shop:{ - name: shop.name, - }, - currency: this.pos.currency, - }; - }, - export_as_JSON: function() { - var orderLines, paymentLines; - orderLines = []; - (this.get('orderLines')).each(_.bind( function(item) { - return orderLines.push([0, 0, item.export_as_JSON()]); - }, this)); - paymentLines = []; - (this.get('paymentLines')).each(_.bind( function(item) { - return paymentLines.push([0, 0, item.export_as_JSON()]); - }, this)); - return { - name: this.getName(), - amount_paid: this.getPaidTotal(), - amount_total: this.getTotalTaxIncluded(), - amount_tax: this.getTax(), - amount_return: this.getChange(), - lines: orderLines, - statement_ids: paymentLines, - pos_session_id: this.pos.pos_session.id, - partner_id: this.get_client() ? this.get_client().id : false, - user_id: this.pos.cashier ? this.pos.cashier.id : this.pos.user.id, - uid: this.uid, - sequence_number: this.sequence_number, - }; - }, - getSelectedLine: function(){ - return this.selected_orderline; - }, - selectLine: function(line){ - if(line){ - if(line !== this.selected_orderline){ - if(this.selected_orderline){ - this.selected_orderline.set_selected(false); - } - this.selected_orderline = line; - this.selected_orderline.set_selected(true); - } - }else{ - this.selected_orderline = undefined; - } - }, - deselectLine: function(){ - if(this.selected_orderline){ - this.selected_orderline.set_selected(false); - this.selected_orderline = undefined; - } - }, - selectPaymentline: function(line){ - if(line !== this.selected_paymentline){ - if(this.selected_paymentline){ - this.selected_paymentline.set_selected(false); - } - this.selected_paymentline = line; - if(this.selected_paymentline){ - this.selected_paymentline.set_selected(true); - } - this.trigger('change:selected_paymentline',this.selected_paymentline); - } - }, }); module.OrderCollection = Backbone.Collection.extend({