From 0e255c2b90a3bac730dad1c0e862b6bebc64544a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 3 Jul 2012 13:47:41 +0200 Subject: [PATCH] [IMP] point_of_sale: refactored Orderline and Paymentline, new OrderWidget design bzr revid: fva@openerp.com-20120703114741-3xixto34i2wqfy0v --- addons/point_of_sale/__openerp__.py | 2 +- addons/point_of_sale/static/src/css/pos.css | 285 ++++++++++-------- .../point_of_sale/static/src/img/gtk-no.png | Bin 0 -> 751 bytes .../point_of_sale/static/src/img/gtk-yes.png | Bin 0 -> 830 bytes .../static/src/js/pos_basewidget.js | 6 + .../point_of_sale/static/src/js/pos_models.js | 254 ++++++++-------- .../static/src/js/pos_screens.js | 225 ++++++++------ .../static/src/js/pos_widgets.js | 135 +++++---- addons/point_of_sale/static/src/xml/pos.xml | 59 ++-- 9 files changed, 558 insertions(+), 408 deletions(-) create mode 100644 addons/point_of_sale/static/src/img/gtk-no.png create mode 100644 addons/point_of_sale/static/src/img/gtk-yes.png diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 03fd20db7cd..6e31694370b 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -83,7 +83,7 @@ Main features : 'certificate' : '001156338024966477869', # Web client 'js': [ - 'static/lib/backbone/backbone-0.5.3.js', + 'static/lib/backbone/backbone-0.9.2.js', 'static/lib/mousewheel/jquery.mousewheel-3.0.6.js', 'static/src/js/pos_models.js', 'static/src/js/pos_basewidget.js', diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 27135512d1c..33fbf0b16cc 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -12,6 +12,9 @@ height: 100%; -webkit-user-select: none; } + +/* ********* The black loading screen ********* */ + .point-of-sale .loader{ background-color: #222; position:absolute; @@ -28,16 +31,8 @@ left:50%; } -.point-of-sale table { - border-spacing: 0; - border-collapse: collapse; -} -.point-of-sale td { - border: 1px solid #e9eaec; -} -.point-of-sale input { - color: #555555; -} +/* ********* Generic element styling ********* */ + .point-of-sale a { text-decoration: none; color: #555555; @@ -62,19 +57,16 @@ .point-of-sale li { list-style-type: none; } -.point-of-sale button img { - vertical-align: bottom; -} .point-of-sale .pos-right-align { text-align: right; } .point-of-sale .pos-right-align input { text-align: right; } -.point-of-sale #container { - width: 100%; - height: 100%; -} + +/* ********* The black header bar ********* */ + + .point-of-sale #topheader { position:absolute; left:0; @@ -90,16 +82,9 @@ background: -moz-linear-gradient(#7b7979, #393939); background: -webkit-gradient(linear, left top, left bottom, from(#7b7979), to(#393939)); } -.point-of-sale #topheader button { - color: black; - height:29px; - margin:2px; - margin-right:0px; - border: 1px solid black; - background: #7f82ac; - background: -moz-linear-gradient(#b2b3d7, #7f82ac); - background: -webkit-gradient(linear, left top, left bottom, from(#b2b3d7), to(#7f82ac)); -} + +/* a) The left part of the top-bar */ + .point-of-sale #branding{ position: absolute; display: table-cell; @@ -120,6 +105,17 @@ margin-left:5px; vertical-align:middle; } +.point-of-sale #branding .username{ + float:right; + color:#DDD; + font-size:16px; + margin-right:32px; + margin-top:10px; + font-style:italic; +} + +/* b) The right part of the top-bar */ + .point-of-sale #rightheader { position: absolute; left:440px; @@ -127,23 +123,75 @@ top:0; height:100%; } -.point-of-sale #neworder-button { + +.point-of-sale #rightheader button { + color: black; + height:29px; + margin:2px; + margin-right:0px; + border: 1px solid black; + background: #7f82ac; + background: -moz-linear-gradient(#b2b3d7, #7f82ac); + background: -webkit-gradient(linear, left top, left bottom, from(#b2b3d7), to(#7f82ac)); +} + +.point-of-sale #rightheader button.neworder-button { width: 32px; - margin-left:5px; + margin-left:4px; + margin-right:4px; } -.point-of-sale #loggedas { - float: right; + +.point-of-sale div#order-selector { + display: inline; } -.point-of-sale #loggedas p { - margin: 0 0 3px 0; +.point-of-sale ol#orders { + display: inline; } +.point-of-sale li.order-selector-button { + display: inline; +} +.point-of-sale li.selected-order button { + font-weight: 900; +} + +/* c) The notifications indicator */ + +.point-of-sale .oe_pos_synch-notification{ + float:right; + color: rgba(255,255,255,0.4); + padding: 8px; + line-height:16px; + font-size:16px; + vertical-align:middle; + font-style: italic; + cursor:pointer; +} + +.point-of-sale .oe_pos_synch-notification .oe_status_red{ + display:inline-block; + cursor:pointer; + width:16px; height:16px; + background: url("../img/gtk-no.png") no-repeat ; +} + +.point-of-sale .oe_pos_synch-notification .oe_status_green{ + display:inline-block; + width:16px; height:16px; + background: url("../img/gtk-yes.png") no-repeat; +} + +/* ********* Contains everything below the bar ********* */ + .point-of-sale #content { width: 100%; position: absolute; top: 35px; bottom: 0; - background: #F0EEEE; //#E6E4E4; //yellow; //#F0EEEE; + background: #F0EEEE; } + +/* ********* The leftpane contains the order, numpad and paypad ********* */ + .point-of-sale #leftpane { -webkit-box-sizing:border-box; position:absolute; @@ -151,9 +199,6 @@ width:440px; top:0px; bottom:105px; - /*height: 87%; - width: 440px; - position: relative;*/ border-right: solid 1px #CECBCB; background-color: white; } @@ -165,6 +210,9 @@ background: #F0EEEE; white-space: nowrap; } + +/* ********* The paypad contains the payment buttons ********* */ + .point-of-sale #paypad { padding: 8px 4px 8px 8px; display: inline-block; @@ -179,6 +227,7 @@ vertical-align: middle; color: #555555; border-top: 1px solid #efefef; + font-size: 14px; } .point-of-sale #paypad button:hover { color: white; @@ -186,6 +235,9 @@ background: -moz-linear-gradient(#9d9fc5, #7f82ac); background: -webkit-gradient(linear, left top, left bottom, from(#9d9fc5), to(#7f82ac)); } + +/* ********* The Numpad ********* */ + .point-of-sale #numpad { padding: 8px 8px 8px 4px; display: inline-block; @@ -212,15 +264,15 @@ background: -moz-linear-gradient(#9d9fc5, #7f82ac); background: -webkit-gradient(linear, left top, left bottom, from(#9d9fc5), to(#7f82ac)); } -.point-of-sale .payment-button { - font-size: 14px; -} .point-of-sale .input-button { font-size: 24px; } .point-of-sale .mode-button, .point-of-sale #numpad-delete, .point-of-sale #numpad-minus { font-size: 14px; } + +/* ********* The right pane contains the screens and headers ********* */ + .point-of-sale #rightpane { position: absolute; top: 0; @@ -228,8 +280,8 @@ left: 440px; right: 0; vertical-align: top; - /*border-left: solid 1px #FFF;*/ } + .point-of-sale #rightpane header { padding: 0; height: 32px; @@ -238,9 +290,13 @@ background: -moz-linear-gradient(white, #d3d3d3); background: -webkit-gradient(linear, left top, left bottom, from(white), to(#d3d3d3)); } + +/* ********* The product list ********* */ + .point-of-sale .product-list { padding:10px; } + .point-of-sale .product-list-scroller{ width:100%; height:100%; @@ -252,8 +308,10 @@ bottom:0px; left:0px; right:0px; - //background:url('../img/bg_callout_gradient_scratched_stars.png'); } + +/* a) the product list navigation bar */ + .point-of-sale .breadcrumb li { float: left; line-height: 32px; @@ -279,6 +337,9 @@ height: 19px; margin: 6px 0; } + +/* b) the search box */ + .point-of-sale .searchbox { position: absolute; right: 2px; @@ -301,6 +362,9 @@ cursor: pointer; display: none; } + +/* c) the categories list */ + .point-of-sale #categories { background:#f0f0f0; border-bottom: 1px solid #cecbcb; @@ -320,6 +384,9 @@ margin: 4px 0; font-size: 12px; } + +/* d) the product */ + .point-of-sale .product { position:relative; vertical-align: top; @@ -337,6 +404,7 @@ -webkit-box-shadow: 0px 1px 8px rgba(0,0,0,0.2); -box-shadow: 0px 1px 8px rgba(0,0,0,0.9); } + .point-of-sale .product .product-img { position: relative; width: 120px; @@ -345,6 +413,7 @@ text-align: center; -webkit-filter: blur(3px); } + .point-of-sale .product .price-tag { position: absolute; top: 2px; @@ -357,6 +426,7 @@ -webkit-border-radius: 3px; border-radius: 3px; } + .point-of-sale .product .price-subtag { position: absolute; top: 24px; @@ -369,6 +439,7 @@ -webkit-border-radius: 3px; border-radius: 3px; } + .point-of-sale .product .product-name { position: absolute; -webkit-box-sizing: border-box; @@ -380,26 +451,9 @@ padding: 3px; padding-top:15px; } -.point-of-sale #login-form label, .point-of-sale #login-form input { - display: block; -} -.point-of-sale #login-form input { - margin: 4px 0 12px; - padding: 4px; - width: 96%; -} -.point-of-sale div#order-selector { - display: inline; -} -.point-of-sale ol#orders { - display: inline; -} -.point-of-sale li.order-selector-button { - display: inline; -} -.point-of-sale li.selected-order button { - font-weight: 900; -} + + +/* ********* The Screens ********* */ .point-of-sale .screen { position:absolute; @@ -416,6 +470,8 @@ font-size: 18px; } +/* a) Layout for the Product Screen */ + .point-of-sale .screen .layout-table { border:none; width:100%; @@ -442,6 +498,8 @@ position:relative; } +/* b) The payment screen */ + .point-of-sale .pos-step-container { display: inline-block; font-size: 1.5em; @@ -473,6 +531,13 @@ font-size: 0.8em; font-weight: bold; } + +/* c) The receipt screen */ + +.point-of-sale .pos-receipt-container { + font-size: 0.75em; +} + .point-of-sale .pos-sale-ticket { text-align: left; width: 300px; @@ -488,25 +553,6 @@ .point-of-sale .pos-sale-ticket table td { border: 0; } -.point-of-sale .pos-receipt-container { - font-size: 0.75em; -} - -.point-of-sale .oe_pos_synch-notification-button { - color: white; - border: 1px solid black; - border-radius: 3px; - padding: 2px 3px 2px 3px; - background-color: #D92A2A; -} - -.receipt-buttons { - white-space: nowrap; -} - -.pos-payment-buttons { - white-space: nowrap; -} @media print { #oe_header, #oe_menu, .point-of-sale #topheader, .point-of-sale #leftpane { @@ -519,7 +565,7 @@ left: 0px; background-color: white; } - #receipt-screen header, .receipt-buttons { + #receipt-screen header { display: none; } #receipt-screen { @@ -530,6 +576,8 @@ } } +/* ********* The OrderWidget ********* */ + .point-of-sale .order-container{ position: absolute; top: 0px; @@ -537,11 +585,13 @@ width:100%; background: #F0EEEE; } + .point-of-sale .order-scroller{ width:100%; height:100%; overflow:hidden; } + .point-of-sale .order{ background: #FFF; background: -webkit-linear-gradient(0deg,rgba(245,245,245,1),rgba(255,255,255,1), rgba(245,245,245,1)); @@ -555,6 +605,35 @@ -webkit-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3); } + +.point-of-sale .order .empty{ + text-align:center; + margin-top: 15px; + margin-bottom: 5px; + color:#999; + font-weight: normal; +} + +.point-of-sale .order .summary{ + width:100%; + text-align:right; + font-weight: bold; + margin-top:20px; + margin-bottom:10px; +} +.point-of-sale .order .summary .line{ + margin-right:15px; + padding-top:5px; + border-top: solid 2px; + border-color:#777; +} +.point-of-sale .order .summary .line.empty{ + border-color:#BBB; + color:#999; +} + +/* ********* The OrderLineWidget ********* */ + .point-of-sale .order .orderline{ width:100%; margin:0px; @@ -566,14 +645,6 @@ -webkit-box-sizing: border-box; -webkit-transition: background 250ms ease-in-out; } -.point-of-sale .order .empty{ - text-align:center; - margin-top: 15px; - margin-bottom: 5px; - color:#999; - font-weight: normal; - -} .point-of-sale .order .orderline:hover{ background: rgba(140,143,183,0.05); -webkit-transition: background 50ms ease-in-out; @@ -605,25 +676,8 @@ font-weight: bold; font-style:normal; } -.point-of-sale .order .summary{ - width:100%; - text-align:right; - font-weight: bold; - margin-top:20px; - margin-bottom:10px; -} -.point-of-sale .order .summary .line{ - margin-right:15px; - padding-top:5px; - border-top: solid 2px; - border-color:#777; -} -.point-of-sale .order .summary .line.empty{ - border-color:#BBB; - color:#999; -} -/* ----------------------- ACTION BAR ---------------------- */ +/* ********* The ActionBarWidget ********* */ .point-of-sale .pos-actionbar{ position:absolute; @@ -704,7 +758,7 @@ float:right; } -/* ----------------------- POP-UPS ---------------------- */ +/* ********* The PopupWidgets ********* */ .point-of-sale .modal-dialog{ position: absolute; @@ -796,14 +850,14 @@ line-height:180px; } -/* ----------------------- SCROLLBAR ---------------------- */ +/* ********* The ScrollBarWidget ********* */ .point-of-sale .scrollbar{ position:absolute; - top:4px; + top:7px; right:7px; width:48px; - bottom:4px; + bottom:7px; background: rgba(0,0,0,0.1); } @@ -833,13 +887,6 @@ color:rgba(255,255,255,0.5); -webkit-transition: all 250ms ease-in-out; } - -/* -.point-of-sale .scrollbar .button{ - background: -webkit-linear-gradient(-90deg,#efefef,#d8d8d8); - border: 1px solid #ababab; - -webkit-box-shadow: 0px 1px 4px rgba(0,0,0,0.2); -}*/ .point-of-sale .scrollbar .down-button{ position:absolute; bottom:0px; diff --git a/addons/point_of_sale/static/src/img/gtk-no.png b/addons/point_of_sale/static/src/img/gtk-no.png new file mode 100644 index 0000000000000000000000000000000000000000..047ddcd9289d5d07ad0cc4d2128738f744038bbc GIT binary patch literal 751 zcmVqQ5U*Uv8W-s zFm7~XL_~tL(pE+bs0cA4QZ*vE||TN^Uo#zvLrxW^gJ{?(*@>Z!4t&wtiWAMv$h}*6 zc@fUCvvKAgL2!HQ_V7q~d2Fn#41)~~7_6_yU|k&sYHKl2Q-l7hDjY2?#*vDO7-!jG z7x;3{N|NVpK|yrLY(`&63HoV~KxrukoK6hzJMpxr2%qHV<3q~aD=dt%!!Ga*Uox4L z!R&1Hu_)rW)rvuf11IcuRBX05&tw_>77KcfMjWQhU`~#D*=$zWg>C?+Cp|qfMv_mX z!65Nmr^Ap|i&MlT5u!PT2nYgt={cI2iC4+tPaI#M-(-sTX(tX%1K=S0!-NS32- zl9M?YmzIgu1Ye1d#5-b!GDB8tlpS{0KO=zqx+E>WvRGmtDD;K+M*JYw5{oPm3&gZ6 z$8H!5i|lOt+J^wd;{d1cNYdJz(Woxz^|(qG79rs~u|oA{nwzEGZG&O$7{HnR09)XX zq1y|v^Dw{_k0>ttBq>J6QNu||$Z+j77-D^*xN;C+zzI-EPAVZlVrYo8G+N{UK-W=0 zcyLjxogdKae=-+zx`iWxFzg2CHLT0qh$PrF1VS_e7P)MUJT8>b9gTq?~7${q@YpogrN zF*KdAvRsK4vyF%>D)cdWlb|Q({yX155f**$<9wX&|Ko7axomM+tC?u67PM(j!JOt0 zk_qZ*k7n$DwNYYWnQleNwnk%jX}e`=Z)f5>K|Sr!j57a})2o>w%_+9*J-=?Q@n+sa z=TMn^WugYeIo{JA%_u|JzfPus{jznwd7vnGVWbRQ9PwvzGT;rQ!|P9jXD}7@{V9-oldzv>C`%5xjBdEFb;XqPW|F+; zmI{tO6D0p?NC7jXp#;9h!|gZ0c_$9`-WcreQNq!aD3e1jqh~5UvvS_mYr+28(I_8K z!ak%%)jiHI=e`=1ytf5ZIM5%BeZDBTd=`*HE=zZa$;D@5=G_D4P^jvG63#Id+>>e? ze54^ly<=2`%3&oc29bWMQ-R4H#bCf+j~q4H50~=T>nZ3?>jvjXO`nA$NN@~`WHG# zk2R3S46xlWEs#TQ=ovHIVo70#X0~?36s+Z^HE`kBM*~jG#o)xO5se>~!}CUuL(jB0 z^w1P6XxGe=^ZVCcTgJkzRpObQUE1$;ld-b*4VPw&X!^qW%=`BSG`u#Vb|O~ZaY_3< z#U^&F+QuT-AEk)3vZVF9#H+cdqrTdF`k*v!kUi5z5C_K$as}VZX^Z-nSR!86TUZu% zR2+d5Dv_fOQ?evWuF#ZfQv|Y)M|{ffjQl}lH${9&DOU`ew+d~FWg$6^vy?3=ffK$| z#WcnZY;9bYuql4Cuswd0P#l{fY|*b_8^a=*k!O~1mi&@_0vhppf`ieE;{X5v07*qo IM6N<$g6mO}LI3~& literal 0 HcmV?d00001 diff --git a/addons/point_of_sale/static/src/js/pos_basewidget.js b/addons/point_of_sale/static/src/js/pos_basewidget.js index 20d84864324..62097f39db3 100644 --- a/addons/point_of_sale/static/src/js/pos_basewidget.js +++ b/addons/point_of_sale/static/src/js/pos_basewidget.js @@ -34,6 +34,12 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of } }, + show: function(){ + this.$element.show(); + }, + hide: function(){ + this.$element.hide(); + }, }); } diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index e810ca56518..69858d8a4b2 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -94,7 +94,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 'user': null, 'user_list': null, 'cashier': null, - 'customer': null, + 'client': null, 'orders': new module.OrderCollection(), //this is the product list as seen by the product list widgets, it will change based on the category filters @@ -126,6 +126,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal [['pos_categ_id','!=', false]] ).then(function(result){ self.set({'product_list': result}); + console.log('PRODUCTS_DONE'); }); var uom_def = fetch( //unit of measure @@ -139,14 +140,16 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal units_by_id[result[i].id] = result[i]; } self.set({'units_by_id':units_by_id}); + console.log('UOM_DONE'); }); var user_def = fetch( 'res.users', - ['name','ean13'] + ['name','ean13'], [['ean13', '!=', false]] ).then(function(result){ self.set({'user_list':result}); + console.log('USERS_DONE'); }); @@ -166,11 +169,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal for(var i = 0; i < product_list.length; i++){ product_list[i].pos_category = cat_by_id[product_list[i].pos_categ_id[0]]; } + console.log('PROD_PROCESS_DONE'); }); var tax_def = fetch('account.tax', ['amount','price_include','type']) .then(function(result){ self.set({'taxes': result}); + console.log('TAX_DONE'); }); var session_def = fetch( // loading the PoS Session. @@ -204,6 +209,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal self.use_websql = result[0].iface_websql || false; self.use_barcode_scanner = result[0].iface_barscan || false; self.use_selfcheckout = result[0].iface_self_checkout || false; + console.log('POS_CONFIG_DONE'); }); var bank_def = fetch( @@ -212,6 +218,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal [['state','=','open'],['pos_session_id', '=', pos_session.id]] ).then(function(result){ self.set({'bank_statements':result}); + console.log('BANK_DEF_DONE'); }); var journal_def = fetch( @@ -220,6 +227,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal [['user_id','=',pos_session.user_id[0]]] ).then(function(result){ self.set({'journals':result}); + console.log('JOURNALS_DONE'); }); // associate the bank statements with their journals. @@ -235,6 +243,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } } } + console.log('BANK_PROCESS_DONE'); }); session_data_def = $.when(pos_config_def,bank_def,journal_def,bank_process_def); @@ -251,9 +260,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal //self.build_tree(); self.build_categories(); self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))}); - console.log('cashRegisters:',self.get('cashRegisters')); - self.ready.resolve(); self.log_loaded_data(); + self.ready.resolve(); },function(){ //we failed to load some backend data, or the backend was badly configured. //the error messages will be displayed in PosWidget @@ -277,10 +285,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal console.log('PosModel: company:',this.get('company')); console.log('PosModel: currency:',this.get('currency')); console.log('PosModel: user_list:',this.get('user_list')); + console.log('PosModel: user:',this.get('user')); console.log('PosModel.session:',this.session); console.log('PosModel.categories:',this.categories); console.log('PosModel end of data log.'); - }, // this is called when an order is removed from the order collection. It ensures that there is always an existing @@ -316,7 +324,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal push_order: function(record) { var self = this; - console.log('push_order',record); return this.dao.add_operation(record).pipe(function(){ return self.flush(); }); @@ -334,7 +341,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // it has been confirmed that they have been received. flush: function() { //this makes sure only one _int_flush is called at the same time - console.log('flush operations'); return this.flush_mutex.exec(_.bind(function() { return this._int_flush(); }, this)); @@ -347,13 +353,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // they are saved to disk and then we attempt to send them to the backend so that they can // be applied. // since the network is not reliable we potentially have many 'pending operations' that have not been sent. - self.set( {'nbr_pending_operations':operations.length} ); if(operations.length === 0){ return $.when(); } var order = operations[0]; - console.log('Pushing Order:',order); // we prevent the default error handler and assume errors // are a normal use case, except we stop the current iteration @@ -439,8 +443,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.root_category = root_category; - console.log('categories:',categories); - //we add the products to the categories. for(var i = 0, len = products.length; i < len; i++){ var product = products[i]; @@ -509,56 +511,53 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // An orderline represent one element of the content of a client's shopping cart. // An orderline contains a product, its quantity, its price, discount. etc. - // Currently there is a limitation in that there can only be once orderline by type - // of product, but this will be subject to changes TODO - // // An Order contains zero or more Orderlines. module.Orderline = Backbone.Model.extend({ - defaults: { - quantity: 1, - list_price: 0, - discount: 0, - weighted: false, - product_type: 'unit', - selected: false, + initialize: function(options){ + this.pos = options.pos; + this.order = options.order; + this.product = options.product; + this.price = options.product.get('list_price'); + this.quantity = 1; + this.discount = 0; + this.type = 'unit'; + this.selected = false; }, - initialize: function(attributes) { - this.pos = attributes.pos; - Backbone.Model.prototype.initialize.apply(this, arguments); - - if(attributes.weight){ - this.setWeight(attributes.weight); - this.set({weighted: true}); - this.set({product_type: 'weight'}); - } + // sets a discount [0,100]% + set_discount: function(discount){ + this.discount = Math.max(0,Math.min(100,discount)); + this.trigger('change'); }, - - // we override the attributes set to prevent some out of range values - // we also round the quantity according to the unit of measure rounding methods - set: function(attributes, options){ - var attributes = _.clone(attributes); //so we don't modify the argument - if(attributes.discount > 100){ - attributes.discount = 100; - }else if(attributes.discount < 0){ - attributes.discount = 0; - } - if(_.isNaN(attributes.quantity)){ - console.log(this.get('order')); - this.get('order').removeOrderline(this); - return this; - }else if(attributes.quantity !== undefined){ - attributes.quantity = Math.max(0,attributes.quantity); + // returns the discount [0,100]% + get_discount: function(){ + return this.discount; + }, + // FIXME + get_product_type: function(){ + return this.type; + }, + // sets the quantity of the product. The quantity will be rounded according to the + // product's unity of measure properties. Quantities greater than zero will not get + // rounded to zero + set_quantity: function(quantity){ + if(_.isNaN(quantity)){ + this.order.removeOrderline(this); + }else if(quantity !== undefined){ + this.quantity = Math.max(0,quantity); var unit = this.get_unit(); - if(unit && attributes.quantity){ - attributes.quantity = Math.max(unit.rounding, Math.round( attributes.quantity / unit.rounding) * unit.rounding); + if(unit && this.quantity > 0 ){ + this.quantity = Math.max(unit.rounding, Math.round(quantity / unit.rounding) * unit.rounding); } } - Backbone.Model.prototype.set.call(this,attributes,options); - return this; + this.trigger('change'); }, - // returns the unit of measure associated with the product if there is one, undefined otherwise + // return the quantity of product + get_quantity: function(){ + return this.quantity; + }, + // return the unit of measure of the product get_unit: function(){ - var unit_id = (this.get('uos_id') || this.get('uom_id')); + var unit_id = (this.product.get('uos_id') || this.product.get('uom_id')); if(!unit_id){ return undefined; } @@ -568,69 +567,77 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } return this.pos.get('units_by_id')[unit_id]; }, - + // return the product of this orderline + get_product: function(){ + return this.product; + }, + // return the base price of this product (for this orderline) + get_list_price: function(){ + return this.price; + }, + // changes the base price of the product for this orderline + set_list_price: function(price){ + this.price = price; + this.trigger('change'); + }, + // selects or deselects this orderline + set_selected: function(selected){ + this.selected = selected; + this.trigger('change'); + }, + // returns true if this orderline is selected + is_selected: function(){ + return this.selected; + }, // when we add an new orderline we want to merge it with the last line to see reduce the number of items // in the orderline. This returns true if it makes sense to merge the two can_be_merged_with: function(orderline){ - if( this.get('id') !== orderline.get('id')){ //only orderline of the same product can be merged + if( this.get_product().get('id') !== orderline.get_product().get('id')){ //only orderline of the same product can be merged return false; - }else if(this.get('product_type') !== orderline.get('product_type')){ + }else if(this.get_product_type() !== orderline.get_product_type()){ return false; - }else if(this.get('discount') > 0){ // we don't merge discounted orderlines + }else if(this.get_discount() > 0){ // we don't merge discounted orderlines return false; - }else if(this.get('product_type') === 'unit'){ + }else if(this.get_product_type() === 'unit'){ return true; - }else if(this.get('product_type') === 'weight'){ + }else if(this.get_product_type() === 'weight'){ return true; - }else if(this.get('product_type') === 'price'){ - return this.get('list_price') === orderline.get('list_price'); + }else if(this.get_product_type() === 'price'){ + return this.get_product().get('list_price') === orderline.get_product().get('list_price'); }else{ console.error('point_of_sale/pos_models.js/Orderline.can_be_merged_with() : unknown product type:',this.get('product_type')); return false; } }, - - // Modifies this orderline so that it also contains the contents of another orderline. - // the two orderlines must be mergable ('can_be_merged_with()' === true) merge: function(orderline){ - this.set({quantity : this.get('quantity') + orderline.get('quantity') }); + this.set_quantity(this.get_quantity() + orderline.get_quantity()); }, - setWeight: function(weight){ - return this.set({ - quantity: weight, - }); + export_as_JSON: function() { + return { + qty: this.get_quantity(), + price_unit: this.get_product().get('list_price'), + discount: this.get_discount(), + product_id: this.get_product().get('id') + }; }, - incrementQuantity: function() { - return this.set({ - quantity: (this.get('quantity')) + 1 - }); + get_price_without_tax: function(){ + return this.get_all_prices().priceWithoutTax; }, - incrementWeight: function(weight){ - return this.set({ - quantity: (this.get('quantity')) + weight, - }); + get_price_with_tax: function(){ + return this.get_all_prices().priceWithTax; }, - set_discount: function(discount){ - this.set({'discount': discount}); + get_tax: function(){ + return this.get_all_prices().tax; }, - getPriceWithoutTax: function() { - return this.getAllPrices().priceWithoutTax; - }, - getPriceWithTax: function() { - return this.getAllPrices().priceWithTax; - }, - getTax: function() { - return this.getAllPrices().tax; - }, - getAllPrices: function() { + get_all_prices: function() { var self = this; - var base = (this.get('quantity')) * (this.get('list_price')) * (1 - (this.get('discount')) / 100); + var base = this.get_quantity() * this.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 product_list = this.pos.get('product_list'); + var product = this.get_product(); var taxes_ids = product.taxes_id; var taxes = self.pos.get('taxes'); var taxtotal = 0; @@ -641,7 +648,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if (tax.type === "percent") { tmp = base - (base / (1 + tax.amount)); } else if (tax.type === "fixed") { - tmp = tax.amount * self.get('quantity'); + tmp = tax.amount * self.get_quantity(); } else { throw "This type of tax is not supported by the point of sale: " + tax.type; } @@ -652,7 +659,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if (tax.type === "percent") { tmp = tax.amount * base; } else if (tax.type === "fixed") { - tmp = tax.amount * self.get('quantity'); + tmp = tax.amount * self.get_quantity(); } else { throw "This type of tax is not supported by the point of sale: " + tax.type; } @@ -666,14 +673,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal "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({ @@ -682,22 +681,31 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // Every PaymentLine has all the attributes of the corresponding CashRegister. module.Paymentline = Backbone.Model.extend({ - defaults: { - amount: 0, + initialize: function(cashRegister) { + this.amount = 0; + this.cashregister = cashRegister; }, - initialize: function(attributes) { - Backbone.Model.prototype.initialize.apply(this, arguments); + //sets the amount of money on this payment line + set_amount: function(value){ + this.amount = value; + this.trigger('change'); }, - getAmount: function(){ - return this.get('amount'); + // returns the amount of money on this paymentline + get_amount: function(){ + return this.amount; }, - exportAsJSON: function(){ + // returns the associated cashRegister + get_cashregister: function(){ + return this.cashregister; + }, + //exports as JSON for server communication + export_as_JSON: 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() + amount: this.get_amount() }; }, }); @@ -706,12 +714,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 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){ Backbone.Model.prototype.initialize.apply(this, arguments); @@ -733,7 +740,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var attr = product.toJSON(); attr.pos = this.pos; attr.order = this; - var line = new module.Orderline(attr); + var line = new module.Orderline({pos: this.pos, order: this, product: product}); var self = this; var last_orderline = this.getLastOrderline(); @@ -753,14 +760,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, addPaymentLine: function(cashRegister) { var newPaymentline; - console.log('addPaymentLine:',cashRegister); newPaymentline = new module.Paymentline(cashRegister); /* TODO: Should be 0 for cash-like accounts */ //FIXME the following 'set' call calls this method once again via callback // events. Are we sure that it's what we want ??? - newPaymentline.set({ - amount: this.getDueLeft() - }); + newPaymentline.set_amount( this.getDueLeft() ); this.get('paymentLines').add(newPaymentline); }, getName: function() { @@ -768,22 +772,22 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, getTotal: function() { return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + orderLine.getPriceWithTax(); + return sum + orderLine.get_price_with_tax(); }), 0); }, getTotalTaxExcluded: function() { return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + orderLine.getPriceWithoutTax(); + return sum + orderLine.get_price_without_tax(); }), 0); }, getTax: function() { return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + orderLine.getTax(); + return sum + orderLine.get_tax(); }), 0); }, getPaidTotal: function() { return (this.get('paymentLines')).reduce((function(sum, paymentLine) { - return sum + paymentLine.getAmount(); + return sum + paymentLine.get_amount(); }), 0); }, getChange: function() { @@ -796,11 +800,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var orderLines, paymentLines; orderLines = []; (this.get('orderLines')).each(_.bind( function(item) { - return orderLines.push([0, 0, item.exportAsJSON()]); + return orderLines.push([0, 0, item.export_as_JSON()]); }, this)); paymentLines = []; (this.get('paymentLines')).each(_.bind( function(item) { - return paymentLines.push([0, 0, item.exportAsJSON()]); + return paymentLines.push([0, 0, item.export_as_JSON()]); }, this)); return { name: this.getName(), @@ -811,6 +815,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal lines: orderLines, statement_ids: paymentLines, pos_session_id: this.pos.get('pos_session').id, + partner_id: this.pos.get('client') ? this.pos.get('client').id : undefined, + user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id, }; }, getSelectedLine: function(){ @@ -820,10 +826,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if(line){ if(line !== this.selected_orderline){ if(this.selected_orderline){ - this.selected_orderline.set({'selected':false}); + this.selected_orderline.set_selected(false); } this.selected_orderline = line; - this.selected_orderline.set({'selected':true}); + this.selected_orderline.set_selected(true); } }else{ this.selected_orderline = undefined; diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 6ef88a0808b..d8ea925e1c9 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -20,7 +20,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ScreenSelector = instance.web.Class.extend({ init: function(options){ - console.log("ScreenSelector Initialized"); this.pos = options.pos; this.screen_set = options.screen_set || {}; @@ -120,21 +119,117 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ScreenWidget = module.PosBaseWidget.extend({ + show_numpad: true, + show_leftpane: true, + init: function(parent,options){ this._super(parent,options); this.hidden = false; }, + + help_button_action: function(){ + this.pos_widget.screen_selector.show_popup('help'); + }, + + logout_button_action: function(){ + this.pos_widget.screen_selector.set_user_mode('client'); + }, + + barcode_product_screen: 'scan', //if defined, this screen will be loaded when a product is scanned + barcode_product_error_popup: 'error', //if defined, this popup will be loaded when there's an error in the popup + + // what happens when a product is scanned : + // it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if + // there's an error. + barcode_product_action: function(ean){ + if(this.pos_widget.scan_product(ean)){ + this.pos.proxy.scan_item_success(); + if(this.barcode_product_screen){ + this.pos_widget.screen_selector.set_current_screen(this.barcode_product_screen); + } + }else{ + if(this.barcode_product_error_popup){ + this.pos_widget.screen_selector.show_popup(this.barcode_product_error_popup); + } + } + }, + + // what happens when a cashier id barcode is scanned. + // the default behavior is the following : + // - if there's a user with a matching ean, put it as the active 'cashier', go to cashier mode, and return true + // - else : do nothing and return false. You probably want to extend this to show and appropriate error popup... + barcode_cashier_action: function(ean){ + var users = this.pos.get('user_list'); + for(var i = 0, len = users.length; i < len; i++){ + if(users[i].ean === ean.ean){ + this.pos.set('cashier',users[i]); + this.pos_widget.username.refresh(); + this.pos.proxy.cashier_mode_activated(); + this.pos_widget.screen_selector.set_user_mode('cashier'); + return true; + } + } + return false; + }, + + // what happens when a client id barcode is scanned. + // the default behavior is the following : + // - if there's a user with a matching ean, put it as the active 'client' and return true + // - else : return false. + barcode_client_action: function(ean){ + var users = this.pos.get('user_list'); + for(var i = 0, len = users.length; i < len; i++){ + if(users[i].ean === ean.ean){ + this.pos.set('client',users[i]); + this.pos_widget.username.refresh(); + return true; + } + } + return false; + //TODO start the transaction + }, + + // what happens when a discount barcode is scanned : the default behavior + // is to set the discount on the last order. + barcode_discount_action: function(ean){ + var last_orderline = this.pos.get('selectedOrder').getLastOrderline(); + if(last_orderline){ + last_orderline.set_discount(ean.value) + } + }, + + // this method shows the screen and sets up all the widget related to this screen. Extend this method + // if you want to alter the behavior of the screen. show: function(){ this.hidden = false; if(this.$element){ this.$element.show(); } + + var self = this; + var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier'; + + this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode); + this.pos_widget.set_leftpane_visible(this.show_leftpane); + this.pos_widget.set_cashier_controls_visible(cashier_mode); + this.pos_widget.action_bar.set_element_visible('help-button', !cashier_mode, function(){ self.help_button_action(); }); + this.pos_widget.action_bar.set_element_visible('logout-button', cashier_mode, function(){ self.logout_button_action(); }); + this.pos_widget.action_bar.set_element_visible('close-button', cashier_mode); + + this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode()); + + this.pos.barcode_reader.set_action_callback({ + 'cashier': self.barcode_cashier_action ? function(ean){ self.barcode_cashier_action(ean); } : undefined , + 'product': self.barcode_product_action ? function(ean){ self.barcode_product_action(ean); } : undefined , + 'client' : self.barcode_client_action ? function(ean){ self.barcode_client_action(ean); } : undefined , + 'discount': self.barcode_discount_action ? function(ean){ self.barcode_discount_action(ean); } : undefined, + }); }, - hide: function(){ - this.hidden = true; - if(this.$element){ - this.$element.hide(); - } + + + // this method is called when the screen is closed to make place for a new screen. this is a good place + // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close() + close: function(){ if(this.pos.barcode_reader){ this.pos.barcode_reader.reset_action_callbacks(); } @@ -142,8 +237,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.pos_widget.action_bar.destroy_buttons(); } }, - close: function(){ + + // this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the + // POS initialization. + hide: function(){ + this.hidden = true; + if(this.$element){ + this.$element.hide(); + } }, + // we need this because some screens re-render themselves when they are hidden // (due to some events, or magic, or both...) we must make sure they remain hidden. // the good solution would probably be to make them not re-render themselves when they @@ -234,75 +337,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa template:'ErrorNoSessionPopupWidget', }); - module.BaseScreenWidget = module.ScreenWidget.extend({ - - show_numpad: true, - show_leftpane: true, - - help_button_action: function(){ - this.pos_widget.screen_selector.show_popup('help'); - }, - - logout_button_action: function(){ - this.pos_widget.screen_selector.set_user_mode('client'); - }, - - barcode_cashier_action: function(ean){ - this.pos.proxy.cashier_mode_activated(); - this.pos_widget.screen_selector.set_user_mode('cashier'); - }, - - barcode_product_screen: 'scan', - barcode_product_error_popup: 'error', - barcode_product_action: function(ean){ - if(this.pos_widget.scan_product(ean)){ - this.pos.proxy.scan_item_success(); - if(this.barcode_product_screen){ - this.pos_widget.screen_selector.set_current_screen(this.barcode_product_screen); - } - }else{ - if(this.barcode_product_error_popup){ - this.pos_widget.screen_selector.show_popup(this.barcode_product_error_popup); - } - } - }, - - barcode_client_action: function(ean){ - this.pos.proxy.transaction_start(); - //TODO 'log the client' - this.pos_widget.screen_selector.show_popup('receipt'); - }, - - barcode_discount_action: function(ean){ - var currentOrder = this.pos.get('selectedOrder'); - var last_orderline = currentOrder.last_orderline; - if(last_orderline){ - last_orderline.set_discount(ean.value) - } - }, - - show: function(){ - this._super(); - var self = this; - var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier'; - - this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode); - this.pos_widget.set_leftpane_visible(this.show_leftpane); - this.pos_widget.set_cashier_controls_visible(cashier_mode); - this.pos_widget.action_bar.set_element_visible('help-button', !cashier_mode, function(){ self.help_button_action(); }); - this.pos_widget.action_bar.set_element_visible('logout-button', cashier_mode, function(){ self.logout_button_action(); }); - this.pos_widget.action_bar.set_element_visible('close-button', cashier_mode); - - this.pos.barcode_reader.set_action_callback({ - 'cashier': self.barcode_cashier_action ? function(ean){ self.barcode_cashier_action(ean); } : undefined , - 'product': self.barcode_product_action ? function(ean){ self.barcode_product_action(ean); } : undefined , - 'client' : self.barcode_client_action ? function(ean){ self.barcode_client_action(ean); } : undefined , - 'discount': self.barcode_discount_action ? function(ean){ self.barcode_discount_action(ean); } : undefined, - }); - }, - }); - - module.ScaleInviteScreenWidget = module.BaseScreenWidget.extend({ + module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ template:'ScaleInviteScreenWidget', show: function(){ @@ -341,7 +376,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.ScaleProductScreenWidget = module.BaseScreenWidget.extend({ + module.ScaleProductScreenWidget = module.ScreenWidget.extend({ template:'ScaleProductSelectionScreenWidget', start: function(){ this.product_categories_widget = new module.ProductCategoriesWidget(this,{ @@ -401,7 +436,29 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.ClientPaymentScreenWidget = module.BaseScreenWidget.extend({ + module.ScaleScreenWidget = module.ScreenWidget.extend({ + template:'ScaleScreenWidget', + show: function(){ + this._super(); + var self = this; + + this.pos.proxy.weighting_start(); + this.intervalID = setInterval(function(){ + var weight = self.pos.proxy.weighting_read_kg(); + if(weight != self.weight){ + self.weight = weight; + self.renderElement(); + } + },500); + }, + close: function(){ + this._super(); + clearInterval(this.intervalID); + this.pos.proxy.weighting_end(); + }, + }); + + module.ClientPaymentScreenWidget = module.ScreenWidget.extend({ template:'ClientPaymentScreenWidget', show: function(){ this._super(); @@ -457,7 +514,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.WelcomeScreenWidget = module.BaseScreenWidget.extend({ + module.WelcomeScreenWidget = module.ScreenWidget.extend({ template:'WelcomeScreenWidget', show_numpad: false, @@ -479,7 +536,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.ScanProductScreenWidget = module.BaseScreenWidget.extend({ + module.ScanProductScreenWidget = module.ScreenWidget.extend({ template:'ScanProductScreenWidget', show_numpad: false, @@ -509,7 +566,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.SearchProductScreenWidget = module.BaseScreenWidget.extend({ + module.SearchProductScreenWidget = module.ScreenWidget.extend({ template:'SearchProductScreenWidget', show_numpad: true, @@ -550,7 +607,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }); - module.ReceiptScreenWidget = module.BaseScreenWidget.extend({ + module.ReceiptScreenWidget = module.ScreenWidget.extend({ template: 'ReceiptScreenWidget', show_numpad: true, @@ -609,7 +666,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.PaymentScreenWidget = module.BaseScreenWidget.extend({ + module.PaymentScreenWidget = module.ScreenWidget.extend({ template: 'PaymentScreenWidget', init: function(parent, options) { this._super(parent,options); @@ -682,7 +739,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.renderElement(); }, addPaymentLine: function(newPaymentLine) { - console.log('addPaymentLine:',newPaymentLine); + console.log('NEW PAYMENT LINE WIDGET',newPaymentLine); var x = new module.PaymentlineWidget(null, { payment_line: newPaymentLine }); diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 98044b1766d..cd81c456558 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -1,7 +1,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale var QWeb = instance.web.qweb; - module.NumpadWidget = instance.web.Widget.extend({ + module.NumpadWidget = module.PosBaseWidget.extend({ template:'NumpadWidget', init: function(parent, options) { this._super(parent); @@ -44,7 +44,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa renderElement: function() { var self = this; this._super(); - console.log('PaypadWidget:',this); this.pos.get('cashRegisters').each(function(cashRegister) { var button = new module.PaypadButtonWidget(self,{ @@ -97,7 +96,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa renderElement: function() { this._super(); this.$element.click(_.bind(this.click_handler, this)); - if(this.model.get('selected')){ + if(this.model.is_selected()){ this.$element.addClass('selected'); } }, @@ -130,9 +129,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa set_value: function(val) { var order = this.pos.get('selectedOrder'); if (order.get('orderLines').length !== 0) { - var param = {}; - param[this.numpadState.get('mode')] = val; - order.getSelectedLine().set(param); + var mode = this.numpadState.get('mode'); + if( mode === 'quantity'){ + order.getSelectedLine().set_quantity(val); + }else if( mode === 'discount'){ + order.getSelectedLine().set_discount(val); + }else if( mode === 'list_price'){ + order.getSelectedLine().set_list_price(val); + } } else { this.pos.get('selectedOrder').destroy(); } @@ -202,9 +206,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, }); -// ---------- Product Screen ---------- - - module.ProductWidget = module.PosBaseWidget.extend({ template: 'ProductWidget', init: function(parent, options) { @@ -250,20 +251,18 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa newAmount = event.currentTarget.value; if (newAmount && !isNaN(newAmount)) { this.amount = parseFloat(newAmount); - this.payment_line.set({ - amount: this.amount, - }); + this.payment_line.set_amount(this.amount); } }, changedAmount: function() { - if (this.amount !== this.payment_line.get('amount')) + if (this.amount !== this.payment_line.get_amount()) this.renderElement(); }, renderElement: function() { - this.name = this.payment_line.get('journal_id')[1]; + this.name = this.payment_line.get_cashregister().get('journal_id')[1]; this._super(); - $('input', this.$element).keyup(_.bind(this.changeAmount, this)); - $('.delete-payment-line', this.$element).click(this.on_delete); + this.$('input').keyup(_.bind(this.changeAmount, this)); + this.$('.delete-payment-line').click(this.on_delete); }, }); @@ -385,7 +384,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }else{ this.category = category; } - console.log('setting categories:',this.category); this.breadcrumb = []; for(var i = 1; i < this.category.ancestors.length; i++){ @@ -534,24 +532,60 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, }); + module.UsernameWidget = module.PosBaseWidget.extend({ + template: 'UsernameWidget', + init: function(parent, options){ + var options = options || {}; + this._super(parent,options); + this.mode = options.mode || 'cashier'; + }, + set_user_mode: function(mode){ + this.mode = mode; + this.refresh(); + }, + refresh: function(){ + this.renderElement(); + }, + get_name: function(){ + var user; + if(this.mode === 'cashier'){ + user = this.pos.get('cashier') || this.pos.get('user'); + }else{ + user = this.pos.get('client') || this.pos.get('user'); + } + if(user){ + return user.name; + }else{ + return ""; + } + }, + }); + // ---------- Main Point of Sale Widget ---------- // this is used to notify the user that data is being synchronized on the network - module.SynchNotificationWidget = instance.web.Widget.extend({ + module.SynchNotificationWidget = module.PosBaseWidget.extend({ template: "SynchNotificationWidget", - init: function(parent) { - this._super(parent); - this.nbr_pending = 0; + init: function(parent,options) { + options = options || {}; + this._super(parent,options); }, renderElement: function() { + var self = this; this._super(); - $('.oe_pos_synch-notification-button', this.$element).click(this.on_synch); + this.$('.oe_pos_synch-notification-button').click(function(){ + self.pos.flush(); + }); }, - on_change_nbr_pending: function(nbr_pending) { - this.nbr_pending = nbr_pending; - this.renderElement(); + start: function(){ + var self = this; + this.pos.bind('change:nbr_pending_operations', function(){ + self.renderElement(); + }); + }, + get_nbr_pending: function(){ + return this.pos.get('nbr_pending_operations'); }, - on_synch: function() {} }); // The PosWidget is the main widget that contains all other widgets in the PointOfSale. @@ -567,7 +601,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa module.PosWidget = module.PosBaseWidget.extend({ template: 'PosWidget', init: function() { - console.log('PosArguments:',arguments); this._super(arguments[0],{}); this.pos = new module.PosModel(this.session); @@ -586,16 +619,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa return self.pos.ready.then(function() { self.build_currency_template(); self.renderElement(); - self.synch_notification = new module.SynchNotificationWidget(this); - self.synch_notification.replace($('.placeholder-SynchNotificationWidget', self.$element)); - self.synch_notification.on_synch.add(_.bind(self.pos.flush, self.pos)); - - self.pos.bind('change:nbr_pending_operations', self.changed_pending_operations, self); - self.changed_pending_operations(); - - self.$element.find("#loggedas button").click(function() { - self.try_close(); - }); self.$('button#neworder-button').click(_.bind(self.create_new_order, self)); @@ -616,21 +639,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa instance.webclient.set_content_full_screen(true); if (!self.pos.get('pos_session')) { - self.screen_selector.show_popup('error', - 'Sorry, we could not create a user session'); - //}else if (!self.pos.get('bank_statements') || self.pos.get('bank_statements').length === 0){ - // self.screen_selector.show_popup('error', - // 'Sorry, we could not find any accounting journals in the configuration'); + self.screen_selector.show_popup('error', 'Sorry, we could not create a user session'); }else if(!self.pos.get('pos_config')){ - self.screen_selector.show_popup('error', - 'Sorry, we could not find any PoS Configuration for this session'); + self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session'); } - $('.loader').animate({opacity:0},3000,'swing',function(){$('.loader').hide();}); - $('.loader img').hide(); + self.$('.loader').animate({opacity:0},3000,'swing',function(){$('.loader').hide();}); + self.$('.loader img').hide(); },function(){ // error when loading models data from the backend - $('.loader img').hide(); + self.$('.loader img').hide(); return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id']) .pipe( _.bind(function(res){ return instance.connection.rpc('/web/action/load', {'action_id': res[0]['res_id']}) @@ -689,6 +707,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa // -------- Misc --------- + this.notification = new module.SynchNotificationWidget(this,{}); + this.notification.replace(this.$('.placeholder-SynchNotificationWidget')); + + this.username = new module.UsernameWidget(this,{}); + this.username.replace(this.$('.placeholder-UsernameWidget')); + this.action_bar = new module.ActionBarWidget(this); this.action_bar.appendTo($(".point-of-sale #content")); @@ -759,15 +783,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa if (parsed_ean.type === 'price') { var itemCode = parsed_ean.id; - console.log('price! id:',itemCode); var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean && pack.ean.substring(0,7) === itemCode; }); if (scannedPackaging) { - console.log('found matching package, finding matching product...'); scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); }else{ - console.log('matching package not found, finding matching product...'); scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 && (pc.ean13.substring(0,7) === parsed_ean.id);}); } if(scannedProductModel){ @@ -780,10 +801,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa return pack.ean && pack.ean.substring(0,7) === itemCode; }); if (scannedPackaging){ - console.log('found matching package, finding matching product...'); scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); }else{ - console.log('matching package not found, finding matching product...'); scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 && (pc.ean13.substring(0,7) === parsed_ean.id);}); } if(scannedProductModel){ @@ -811,13 +830,13 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa if(visible != this.numpad_visible){ this.numpad_visible = visible; if(visible){ - $('#numpad').show(); - $('#paypad').show(); - $('#current-order').css({'bottom':'271px'}); + this.numpad.show(); + this.paypad.show(); + $('.point-of-sale .order-container').css({'bottom':'232px'}); }else{ - $('#numpad').hide(); - $('#paypad').hide(); - $('#current-order').css({'bottom':'0px'}); + this.numpad.hide(); + this.paypad.hide(); + $('.point-of-sale .order-container').css({'bottom':'0px'}); } } }, diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 026473f8f04..7b4a168de87 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -8,15 +8,14 @@
-
-
- +
- +
    +
    @@ -39,11 +38,11 @@ - - - pending orders - - +
    + +
    +
    +
    @@ -146,6 +145,16 @@
    + +
    +

    Product Weighting

    +
    + 0.000Kg + + +
    +
    +
    @@ -358,29 +367,29 @@
  1. - + - +
      - +
    • - + at - + /
    • - +
    • With a - % + % discount
    • @@ -395,14 +404,14 @@ - + -
      @@ -414,6 +423,12 @@
      + + + + + +
      - + - + - + @@ -453,7 +468,7 @@ - +