1 function openerp_pos_models(instance, module){ //module is instance.point_of_sale
2 var QWeb = instance.web.qweb;
4 var round_di = instance.web.round_decimals;
5 var round_pr = instance.web.round_precision
7 // The PosModel contains the Point Of Sale's representation of the backend.
8 // Since the PoS must work in standalone ( Without connection to the server )
9 // it must contains a representation of the server's PoS backend.
10 // (taxes, product list, configuration options, etc.) this representation
11 // is fetched and stored by the PosModel at the initialisation.
12 // this is done asynchronously, a ready deferred alows the GUI to wait interactively
13 // for the loading to be completed
14 // There is a single instance of the PosModel for each Front-End instance, it is usually called
15 // 'pos' and is available to all widgets extending PosWidget.
17 module.PosModel = Backbone.Model.extend({
18 initialize: function(session, attributes) {
19 Backbone.Model.prototype.initialize.call(this, attributes);
21 this.session = session;
22 this.ready = $.Deferred(); // used to notify the GUI that the PosModel has loaded all resources
23 this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
25 this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
26 this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
27 this.db = new module.PosLS(); // a database used to store the products and categories
28 this.db.clear('products','categories');
29 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
31 // default attributes values. If null, it will be loaded below.
33 'nbr_pending_operations': 0,
35 'currency': {symbol: '$', position: 'after'},
38 'user': null, // the user that loaded the pos
39 'user_list': null, // list of all users
40 'partner_list': null, // list of all partners with an ean
41 'cashier': null, // the logged cashier, if different from user
43 'orders': new module.OrderCollection(),
44 //this is the product list as seen by the product list widgets, it will change based on the category filters
45 'products': new module.ProductCollection(),
46 'cashRegisters': null,
48 'bank_statements': null,
56 'selectedOrder': null,
59 this.get('orders').bind('remove', function(){ self.on_removed_order(); });
61 // We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
62 // Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
63 // when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
64 $.when(this.load_server_data())
66 //self.log_loaded_data(); //Uncomment if you want to log the data to the console for easier debugging
69 //we failed to load some backend data, or the backend was badly configured.
70 //the error messages will be displayed in PosWidget
75 // helper function to load data from the server
76 fetch: function(model, fields, domain, ctx){
77 return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
79 // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded.
80 load_server_data: function(){
83 var loaded = self.fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
84 .then(function(users){
85 self.set('user',users[0]);
87 return self.fetch('res.company',
98 [['id','=',users[0].company_id[0]]]);
99 }).then(function(companies){
100 self.set('company',companies[0]);
102 return self.fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]);
103 }).then(function(company_partners){
104 self.get('company').contact_address = company_partners[0].contact_address;
106 return self.fetch('product.uom', null, null);
107 }).then(function(units){
108 self.set('units',units);
109 var units_by_id = {};
110 for(var i = 0, len = units.length; i < len; i++){
111 units_by_id[units[i].id] = units[i];
113 self.set('units_by_id',units_by_id);
115 return self.fetch('product.packaging', null, null);
116 }).then(function(packagings){
117 self.set('product.packaging',packagings);
119 return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
120 }).then(function(users){
121 self.set('user_list',users);
123 return self.fetch('res.partner', ['name','ean13'], [['ean13', '!=', false]]);
124 }).then(function(partners){
125 self.set('partner_list',partners);
127 return self.fetch('account.tax', ['amount', 'price_include', 'type']);
128 }).then(function(taxes){
129 self.set('taxes', taxes);
133 ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
134 [['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
136 }).then(function(sessions){
137 self.set('pos_session', sessions[0]);
141 ['name','journal_ids','shop_id','journal_id',
142 'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
143 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
144 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
145 [['id','=', self.get('pos_session').config_id[0]]]
147 }).then(function(configs){
148 var pos_config = configs[0];
149 self.set('pos_config', pos_config);
150 self.iface_electronic_scale = !!pos_config.iface_electronic_scale;
151 self.iface_print_via_proxy = !!pos_config.iface_print_via_proxy;
152 self.iface_vkeyboard = !!pos_config.iface_vkeyboard;
153 self.iface_self_checkout = !!pos_config.iface_self_checkout;
154 self.iface_cashdrawer = !!pos_config.iface_cashdrawer;
156 return self.fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]);
157 }).then(function(shops){
158 self.set('shop',shops[0]);
160 return self.fetch('product.pricelist',['currency_id'],[['id','=',self.get('shop').pricelist_id[0]]]);
161 }).then(function(pricelists){
162 self.set('pricelist',pricelists[0]);
164 return self.fetch('res.currency',['symbol','position','rounding','accuracy'],[['id','=',self.get('pricelist').currency_id[0]]]);
165 }).then(function(currencies){
166 self.set('currency',currencies[0]);
168 return self.fetch('product.packaging',['ean','product_id']);
169 }).then(function(packagings){
170 self.db.add_packagings(packagings);
172 return self.fetch('pos.category', ['id','name','parent_id','child_id','image'])
173 }).then(function(categories){
174 self.db.add_categories(categories);
178 ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 'variants',
179 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description'],
180 [['sale_ok','=',true],['available_in_pos','=',true]],
181 {pricelist: self.get('shop').pricelist_id[0]} // context for price
183 }).then(function(products){
184 self.db.add_products(products);
187 'account.bank.statement',
188 ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
189 [['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]]
191 }).then(function(bank_statements){
192 var journals = new Array();
193 _.each(bank_statements,function(statement) {
194 journals.push(statement.journal_id[0])
196 self.set('bank_statements', bank_statements);
197 return self.fetch('account.journal', undefined, [['id','in', journals]]);
198 }).then(function(journals){
199 self.set('journals',journals);
201 // associate the bank statements with their journals.
202 var bank_statements = self.get('bank_statements');
203 for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
204 for(var j = 0, jlen = journals.length; j < jlen; j++){
205 if(bank_statements[i].journal_id[0] === journals[j].id){
206 bank_statements[i].journal = journals[j];
207 bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
211 self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
217 // logs the usefull posmodel data to the console for debug purposes
218 log_loaded_data: function(){
219 console.log('PosModel data has been loaded:');
220 console.log('PosModel: units:',this.get('units'));
221 console.log('PosModel: bank_statements:',this.get('bank_statements'));
222 console.log('PosModel: journals:',this.get('journals'));
223 console.log('PosModel: taxes:',this.get('taxes'));
224 console.log('PosModel: pos_session:',this.get('pos_session'));
225 console.log('PosModel: pos_config:',this.get('pos_config'));
226 console.log('PosModel: cashRegisters:',this.get('cashRegisters'));
227 console.log('PosModel: shop:',this.get('shop'));
228 console.log('PosModel: company:',this.get('company'));
229 console.log('PosModel: currency:',this.get('currency'));
230 console.log('PosModel: user_list:',this.get('user_list'));
231 console.log('PosModel: user:',this.get('user'));
232 console.log('PosModel.session:',this.session);
233 console.log('PosModel end of data log.');
236 // this is called when an order is removed from the order collection. It ensures that there is always an existing
237 // order and a valid selected order
238 on_removed_order: function(removed_order){
239 if( this.get('orders').isEmpty()){
240 this.add_new_order();
242 this.set({ selectedOrder: this.get('orders').last() });
246 // saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
247 push_order: function(record) {
248 this.db.add_order(record);
252 //creates a new empty order and sets it as the current order
253 add_new_order: function(){
254 var order = new module.Order({pos:this});
255 this.get('orders').add(order);
256 this.set('selectedOrder', order);
259 // attemps to send all pending orders ( stored in the pos_db ) to the server,
260 // and remove the successfully sent ones from the db once
261 // it has been confirmed that they have been sent correctly.
263 //TODO make the mutex work
264 //this makes sure only one _int_flush is called at the same time
266 return this.flush_mutex.exec(_.bind(function() {
267 return this._flush(0);
272 // attempts to send an order of index 'index' in the list of order to send. The index
273 // is used to skip orders that failed. do not call this method outside the mutex provided
275 _flush: function(index){
277 var orders = this.db.get_orders();
278 self.set('nbr_pending_operations',orders.length);
280 var order = orders[index];
284 //try to push an order to the server
285 // shadow : true is to prevent a spinner to appear in case of timeout
286 (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{ shadow:true })
287 .fail(function(unused, event){
288 //don't show error popup if it fails
289 event.preventDefault();
290 console.error('Failed to send order:',order);
291 self._flush(index+1);
294 //remove from db if success
295 self.db.remove_order(order.id);
300 scan_product: function(parsed_ean){
302 var product = this.db.get_product_by_ean13(parsed_ean.base_ean);
303 var selectedOrder = this.get('selectedOrder');
309 if(parsed_ean.type === 'price'){
310 selectedOrder.addProduct(new module.Product(product), {price:parsed_ean.value});
311 }else if(parsed_ean.type === 'weight'){
312 selectedOrder.addProduct(new module.Product(product), {quantity:parsed_ean.value, merge:false});
314 selectedOrder.addProduct(new module.Product(product));
320 module.CashRegister = Backbone.Model.extend({
323 module.CashRegisterCollection = Backbone.Collection.extend({
324 model: module.CashRegister,
327 module.Product = Backbone.Model.extend({
328 get_image_url: function(){
329 return instance.session.url('/web/binary/image', {model: 'product.product', field: 'image', id: this.get('id')});
333 module.ProductCollection = Backbone.Collection.extend({
334 model: module.Product,
337 // An orderline represent one element of the content of a client's shopping cart.
338 // An orderline contains a product, its quantity, its price, discount. etc.
339 // An Order contains zero or more Orderlines.
340 module.Orderline = Backbone.Model.extend({
341 initialize: function(attr,options){
342 this.pos = options.pos;
343 this.order = options.order;
344 this.product = options.product;
345 this.price = options.product.get('price');
347 this.quantityStr = '1';
349 this.discountStr = '0';
351 this.selected = false;
353 // sets a discount [0,100]%
354 set_discount: function(discount){
355 var disc = Math.min(Math.max(parseFloat(discount) || 0, 0),100);
356 this.discount = disc;
357 this.discountStr = '' + disc;
358 this.trigger('change');
360 // returns the discount [0,100]%
361 get_discount: function(){
362 return this.discount;
364 get_discount_str: function(){
365 return this.discountStr;
367 get_product_type: function(){
370 // sets the quantity of the product. The quantity will be rounded according to the
371 // product's unity of measure properties. Quantities greater than zero will not get
373 set_quantity: function(quantity){
374 if(quantity === 'remove'){
375 this.order.removeOrderline(this);
378 var quant = Math.max(parseFloat(quantity) || 0, 0);
379 var unit = this.get_unit();
381 this.quantity = Math.max(unit.rounding, round_pr(quant, unit.rounding));
382 this.quantityStr = this.quantity.toFixed(Math.max(0,Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))));
384 this.quantity = quant;
385 this.quantityStr = '' + this.quantity;
388 this.trigger('change');
390 // return the quantity of product
391 get_quantity: function(){
392 return this.quantity;
394 get_quantity_str: function(){
395 return this.quantityStr;
397 get_quantity_str_with_unit: function(){
398 var unit = this.get_unit();
399 if(unit && unit.name !== 'Unit(s)'){
400 return this.quantityStr + ' ' + unit.name;
402 return this.quantityStr;
405 // return the unit of measure of the product
406 get_unit: function(){
407 var unit_id = (this.product.get('uos_id') || this.product.get('uom_id'));
411 unit_id = unit_id[0];
415 return this.pos.get('units_by_id')[unit_id];
417 // return the product of this orderline
418 get_product: function(){
421 // selects or deselects this orderline
422 set_selected: function(selected){
423 this.selected = selected;
424 this.trigger('change');
426 // returns true if this orderline is selected
427 is_selected: function(){
428 return this.selected;
430 // when we add an new orderline we want to merge it with the last line to see reduce the number of items
431 // in the orderline. This returns true if it makes sense to merge the two
432 can_be_merged_with: function(orderline){
433 if( this.get_product().get('id') !== orderline.get_product().get('id')){ //only orderline of the same product can be merged
435 }else if(this.get_product_type() !== orderline.get_product_type()){
437 }else if(this.get_discount() > 0){ // we don't merge discounted orderlines
439 }else if(this.price !== orderline.price){
445 merge: function(orderline){
446 this.set_quantity(this.get_quantity() + orderline.get_quantity());
448 export_as_JSON: function() {
450 qty: this.get_quantity(),
451 price_unit: this.get_unit_price(),
452 discount: this.get_discount(),
453 product_id: this.get_product().get('id'),
456 //used to create a json of the ticket, to be sent to the printer
457 export_for_printing: function(){
459 quantity: this.get_quantity(),
460 unit_name: this.get_unit().name,
461 price: this.get_unit_price(),
462 discount: this.get_discount(),
463 product_name: this.get_product().get('name'),
464 price_display : this.get_display_price(),
465 price_with_tax : this.get_price_with_tax(),
466 price_without_tax: this.get_price_without_tax(),
468 product_description: this.get_product().get('description'),
469 product_description_sale: this.get_product().get('description_sale'),
472 // changes the base price of the product for this orderline
473 set_unit_price: function(price){
474 this.price = round_di(parseFloat(price) || 0, 2);
475 this.trigger('change');
477 get_unit_price: function(){
478 var rounding = this.pos.get('currency').rounding;
479 return round_pr(this.price,rounding);
481 get_display_price: function(){
482 var rounding = this.pos.get('currency').rounding;
483 return round_pr(round_pr(this.get_unit_price() * this.get_quantity(),rounding) * (1- this.get_discount()/100.0),rounding);
485 get_price_without_tax: function(){
486 return this.get_all_prices().priceWithoutTax;
488 get_price_with_tax: function(){
489 return this.get_all_prices().priceWithTax;
492 return this.get_all_prices().tax;
494 get_all_prices: function(){
496 var currency_rounding = this.pos.get('currency').rounding;
497 var base = round_pr(round_pr(this.get_quantity() * this.get_unit_price(), currency_rounding) * (1.0 - (this.get_discount() / 100.0)), currency_rounding);
499 var totalNoTax = base;
501 var product_list = this.pos.get('product_list');
502 var product = this.get_product();
503 var taxes_ids = product.get('taxes_id');;
504 var taxes = self.pos.get('taxes');
506 _.each(taxes_ids, function(el) {
507 var tax = _.detect(taxes, function(t) {return t.id === el;});
508 if (tax.price_include) {
510 if (tax.type === "percent") {
511 tmp = base - round_pr(base / (1 + tax.amount),currency_rounding);
512 } else if (tax.type === "fixed") {
513 tmp = round_pr(tax.amount * self.get_quantity(),currency_rounding);
515 throw "This type of tax is not supported by the point of sale: " + tax.type;
517 tmp = round_pr(tmp,currency_rounding);
522 if (tax.type === "percent") {
523 tmp = tax.amount * base;
524 } else if (tax.type === "fixed") {
525 tmp = tax.amount * self.get_quantity();
527 throw "This type of tax is not supported by the point of sale: " + tax.type;
529 tmp = round_pr(tmp,currency_rounding);
535 "priceWithTax": totalTax,
536 "priceWithoutTax": totalNoTax,
542 module.OrderlineCollection = Backbone.Collection.extend({
543 model: module.Orderline,
546 // Every PaymentLine contains a cashregister and an amount of money.
547 module.Paymentline = Backbone.Model.extend({
548 initialize: function(attributes, options) {
550 this.cashregister = options.cashRegister;
552 //sets the amount of money on this payment line
553 set_amount: function(value){
554 this.amount = parseFloat(value) || 0;
555 this.trigger('change');
557 // returns the amount of money on this paymentline
558 get_amount: function(){
561 // returns the associated cashRegister
562 get_cashregister: function(){
563 return this.cashregister;
565 //exports as JSON for server communication
566 export_as_JSON: function(){
568 name: instance.web.datetime_to_str(new Date()),
569 statement_id: this.cashregister.get('id'),
570 account_id: (this.cashregister.get('account_id'))[0],
571 journal_id: (this.cashregister.get('journal_id'))[0],
572 amount: this.get_amount()
575 //exports as JSON for receipt printing
576 export_for_printing: function(){
578 amount: this.get_amount(),
579 journal: this.cashregister.get('journal_id')[1],
584 module.PaymentlineCollection = Backbone.Collection.extend({
585 model: module.Paymentline,
589 // An order more or less represents the content of a client's shopping cart (the OrderLines)
590 // plus the associated payment information (the PaymentLines)
591 // there is always an active ('selected') order in the Pos, a new one is created
592 // automaticaly once an order is completed and sent to the server.
593 module.Order = Backbone.Model.extend({
594 initialize: function(attributes){
595 Backbone.Model.prototype.initialize.apply(this, arguments);
597 creationDate: new Date(),
598 orderLines: new module.OrderlineCollection(),
599 paymentLines: new module.PaymentlineCollection(),
600 name: "Order " + this.generateUniqueId(),
603 this.pos = attributes.pos;
604 this.selected_orderline = undefined;
605 this.screen_data = {}; // see ScreenSelector
606 this.receipt_type = 'receipt'; // 'receipt' || 'invoice'
609 generateUniqueId: function() {
610 return new Date().getTime();
612 addProduct: function(product, options){
613 options = options || {};
614 var attr = product.toJSON();
617 var line = new module.Orderline({}, {pos: this.pos, order: this, product: product});
619 if(options.quantity !== undefined){
620 line.set_quantity(options.quantity);
622 if(options.price !== undefined){
623 line.set_unit_price(options.price);
626 var last_orderline = this.getLastOrderline();
627 if( last_orderline && last_orderline.can_be_merged_with(line) && options.merge !== false){
628 last_orderline.merge(line);
630 this.get('orderLines').add(line);
632 this.selectLine(this.getLastOrderline());
634 removeOrderline: function( line ){
635 this.get('orderLines').remove(line);
636 this.selectLine(this.getLastOrderline());
638 getLastOrderline: function(){
639 return this.get('orderLines').at(this.get('orderLines').length -1);
641 addPaymentLine: function(cashRegister) {
642 var paymentLines = this.get('paymentLines');
643 var newPaymentline = new module.Paymentline({},{cashRegister:cashRegister});
644 if(cashRegister.get('journal').type !== 'cash'){
645 newPaymentline.set_amount( this.getDueLeft() );
647 paymentLines.add(newPaymentline);
649 getName: function() {
650 return this.get('name');
652 getSubtotal : function(){
653 return (this.get('orderLines')).reduce((function(sum, orderLine){
654 return sum + orderLine.get_display_price();
657 getTotalTaxIncluded: function() {
658 return (this.get('orderLines')).reduce((function(sum, orderLine) {
659 return sum + orderLine.get_price_with_tax();
662 getDiscountTotal: function() {
663 return (this.get('orderLines')).reduce((function(sum, orderLine) {
664 return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity());
667 getTotalTaxExcluded: function() {
668 return (this.get('orderLines')).reduce((function(sum, orderLine) {
669 return sum + orderLine.get_price_without_tax();
673 return (this.get('orderLines')).reduce((function(sum, orderLine) {
674 return sum + orderLine.get_tax();
677 getPaidTotal: function() {
678 return (this.get('paymentLines')).reduce((function(sum, paymentLine) {
679 return sum + paymentLine.get_amount();
682 getChange: function() {
683 return this.getPaidTotal() - this.getTotalTaxIncluded();
685 getDueLeft: function() {
686 return this.getTotalTaxIncluded() - this.getPaidTotal();
688 // sets the type of receipt 'receipt'(default) or 'invoice'
689 set_receipt_type: function(type){
690 this.receipt_type = type;
692 get_receipt_type: function(){
693 return this.receipt_type;
695 // the client related to the current order.
696 set_client: function(client){
697 this.set('client',client);
699 get_client: function(){
700 return this.get('client');
702 get_client_name: function(){
703 var client = this.get('client');
704 return client ? client.name : "";
706 // the order also stores the screen status, as the PoS supports
707 // different active screens per order. This method is used to
708 // store the screen status.
709 set_screen_data: function(key,value){
710 if(arguments.length === 2){
711 this.screen_data[key] = value;
712 }else if(arguments.length === 1){
713 for(key in arguments[0]){
714 this.screen_data[key] = arguments[0][key];
718 //see set_screen_data
719 get_screen_data: function(key){
720 return this.screen_data[key];
722 // exports a JSON for receipt printing
723 export_for_printing: function(){
725 this.get('orderLines').each(function(orderline){
726 orderlines.push(orderline.export_for_printing());
729 var paymentlines = [];
730 this.get('paymentLines').each(function(paymentline){
731 paymentlines.push(paymentline.export_for_printing());
733 var client = this.get('client');
734 var cashier = this.pos.get('cashier') || this.pos.get('user');
735 var company = this.pos.get('company');
736 var shop = this.pos.get('shop');
737 var date = new Date();
740 orderlines: orderlines,
741 paymentlines: paymentlines,
742 subtotal: this.getSubtotal(),
743 total_with_tax: this.getTotalTaxIncluded(),
744 total_without_tax: this.getTotalTaxExcluded(),
745 total_tax: this.getTax(),
746 total_paid: this.getPaidTotal(),
747 total_discount: this.getDiscountTotal(),
748 change: this.getChange(),
749 name : this.getName(),
750 client: client ? client.name : null ,
751 invoice_id: null, //TODO
752 cashier: cashier ? cashier.name : null,
754 year: date.getFullYear(),
755 month: date.getMonth(),
756 date: date.getDate(), // day of the month
757 day: date.getDay(), // day of the week
758 hour: date.getHours(),
759 minute: date.getMinutes()
762 email: company.email,
763 website: company.website,
764 company_registry: company.company_registry,
765 contact_address: company.contact_address,
768 phone: company.phone,
773 currency: this.pos.get('currency'),
776 exportAsJSON: function() {
777 var orderLines, paymentLines;
779 (this.get('orderLines')).each(_.bind( function(item) {
780 return orderLines.push([0, 0, item.export_as_JSON()]);
783 (this.get('paymentLines')).each(_.bind( function(item) {
784 return paymentLines.push([0, 0, item.export_as_JSON()]);
787 name: this.getName(),
788 amount_paid: this.getPaidTotal(),
789 amount_total: this.getTotalTaxIncluded(),
790 amount_tax: this.getTax(),
791 amount_return: this.getChange(),
793 statement_ids: paymentLines,
794 pos_session_id: this.pos.get('pos_session').id,
795 partner_id: this.get('client') ? this.get('client').id : undefined,
796 user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,
799 getSelectedLine: function(){
800 return this.selected_orderline;
802 selectLine: function(line){
804 if(line !== this.selected_orderline){
805 if(this.selected_orderline){
806 this.selected_orderline.set_selected(false);
808 this.selected_orderline = line;
809 this.selected_orderline.set_selected(true);
812 this.selected_orderline = undefined;
817 module.OrderCollection = Backbone.Collection.extend({
822 The numpad handles both the choice of the property currently being modified
823 (quantity, price or discount) and the edition of the corresponding numeric value.
825 module.NumpadState = Backbone.Model.extend({
830 appendNewChar: function(newChar) {
832 oldBuffer = this.get('buffer');
833 if (oldBuffer === '0') {
837 } else if (oldBuffer === '-0') {
839 buffer: "-" + newChar
843 buffer: (this.get('buffer')) + newChar
846 this.trigger('set_value',this.get('buffer'));
848 deleteLastChar: function() {
849 if(this.get('buffer') === ""){
850 if(this.get('mode') === 'quantity'){
851 this.trigger('set_value','remove');
853 this.trigger('set_value',this.get('buffer'));
856 var newBuffer = this.get('buffer').slice(0,-1) || "";
857 this.set({ buffer: newBuffer });
858 this.trigger('set_value',this.get('buffer'));
861 switchSign: function() {
863 oldBuffer = this.get('buffer');
865 buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
867 this.trigger('set_value',this.get('buffer'));
869 changeMode: function(newMode) {
881 resetValue: function(){
882 this.set({buffer:'0'});