Merge lp:~mmakonnen/openobject-addons/point_of_sale_enhanced-70 into lp:openobject-addons

Proposed by Mauricio Matute
Status: Needs review
Proposed branch: lp:~mmakonnen/openobject-addons/point_of_sale_enhanced-70
Merge into: lp:openobject-addons
Diff against target: 1267 lines (+868/-4) (has conflicts)
9 files modified
point_of_sale/controllers/main.py (+21/-0)
point_of_sale/point_of_sale.py (+11/-2)
point_of_sale/static/src/css/pos.css (+173/-0)
point_of_sale/static/src/js/db.js (+78/-0)
point_of_sale/static/src/js/devices.js (+179/-0)
point_of_sale/static/src/js/models.js (+71/-2)
point_of_sale/static/src/js/screens.js (+113/-0)
point_of_sale/static/src/js/widgets.js (+138/-0)
point_of_sale/static/src/xml/pos.xml (+84/-0)
Text conflict in point_of_sale/controllers/main.py
Text conflict in point_of_sale/point_of_sale.py
Text conflict in point_of_sale/static/src/js/db.js
Text conflict in point_of_sale/static/src/js/models.js
Text conflict in point_of_sale/static/src/js/screens.js
Text conflict in point_of_sale/static/src/js/widgets.js
Text conflict in point_of_sale/static/src/xml/pos.xml
To merge this branch: bzr merge lp:~mmakonnen/openobject-addons/point_of_sale_enhanced-70
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+194755@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

8522. By Michael Telahun Makonnen

In point_of_sale module add ability to select customer from the POS screen

Add a button in the upper right header which, when clicked, pops up a
dialog box to select a customer. You can search for a customer by
name, tin no, email, phone or mobile.

Currently doesn't support adding a new customer from this screen. You
will have to leave the POS and add the customer in the normal way.

8521. By Michael Telahun Makonnen

Add a keypad input device to the point of sale

It uses the numeric keypad on the keyboard to mimic the keypad found on
most cash registers. The numbers are used to match a product by its code,
and the non-numeric keys are used as modifiers:
'/' - X Quantity
'*' - AMT Manual Price Override
'-' - % - Discount %
'+' - PLU (Product Code)
Keyboard "Back Space" and "Delete" keys may be used to clear the buffer.

It may be useful to tape over the modifier keys with the appropriate
symbols to make it easier for the cashiers to get used to it.

Implementation Notes:
---------------------
  - By necessity the product code may contain only numeric identifiers
  - Doesn't emulate VOID key (using Enter key would interfere with
    bar code reader)
  - Doesn't emulate surcharge key (% +)

Some examples of usage showing what the operation would look like
on a regular Cash Register and using the numeric keypad:

1. Add a product
   (code: 100, quantity: 1, Price:list price, no discount)
    Cash Register: 100 [PLU]
    Numeric Keypad: 100 +

2. Add 9 pieces of a product
   (code: 102, quantity: 9, Price: list price, no discount)
    Cash Register: 9 [ X ] 102 [PLU]
    Numeric Keypad: 9 / 102 +

3. Add 3 pieces of a product and set the price at 9.99
   (code: 450, quantity: 3, Price: 9.99, no discount)
    Cash Register: 3 [ X ] 9.99 [AMT] 450 [PLU]
    Numeric Keypad: 3 / 9.99 * 450 +

4. Add 5 pieces of a product, set price to 23.50, and discount it by 10%
   (code: 300, quantity: 5, price: 23.50, discount: 10%)
    Cash Register: 5 [ X ] 23.50 [AMT] 10 [% -] 300 [PLU]
    Numeric Keypad: 5 / 23.50 * 10 - 300 +

8520. By Michael Telahun Makonnen

POS: When we hit [Enter] in the search box add the product with the matching product code

The previous commit only added the product if it was the only one returned by the search.
Now, it will search the list of matched products for one with the same exact
product code as the search term. If it finds a match it will add it to the order.

8519. By Michael Telahun Makonnen

POS: hitting [Enter] in the search field will add the product to the order

8518. By Michael Telahun Makonnen

In POS allow searching by product code as well

8517. By Michael Telahun Makonnen

In the POS module show the product code before the price

If you have a lot of similarly (identicaly) named products it becomes difficult to
tell them apart in the POS unless you can also see their code.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'point_of_sale/controllers/main.py'
--- point_of_sale/controllers/main.py 2013-10-22 17:06:59 +0000
+++ point_of_sale/controllers/main.py 2013-11-11 21:43:26 +0000
@@ -89,8 +89,29 @@
89 """89 """
90 print 'scan_item_error_unrecognized: ' + str(ean)90 print 'scan_item_error_unrecognized: ' + str(ean)
9191
92<<<<<<< TREE
92 @http.route('/pos/help_needed', type='json', auth='admin')93 @http.route('/pos/help_needed', type='json', auth='admin')
93 def help_needed(self):94 def help_needed(self):
95=======
96 @openerp.addons.web.http.jsonrequest
97 def keypad_item_success(self, request, data):
98 """
99 A product has been entered by keypad with success
100 """
101 print 'keypad_item_success: ' + str(data)
102 return
103
104 @openerp.addons.web.http.jsonrequest
105 def keypad_item_error_unrecognized(self, request, data):
106 """
107 A product has been entered by keypad without success
108 """
109 print 'keypad_item_error_unrecognized: ' + str(data)
110 return
111
112 @openerp.addons.web.http.jsonrequest
113 def help_needed(self, request):
114>>>>>>> MERGE-SOURCE
94 """115 """
95 The user wants an help (ex: light is on)116 The user wants an help (ex: light is on)
96 """117 """
97118
=== modified file 'point_of_sale/point_of_sale.py'
--- point_of_sale/point_of_sale.py 2013-10-18 12:58:18 +0000
+++ point_of_sale/point_of_sale.py 2013-11-11 21:43:26 +0000
@@ -502,15 +502,24 @@
502 for tmp_order in orders:502 for tmp_order in orders:
503 to_invoice = tmp_order['to_invoice']503 to_invoice = tmp_order['to_invoice']
504 order = tmp_order['data']504 order = tmp_order['data']
505505<<<<<<< TREE
506506
507
508=======
509 partner_id = order['partner_id'] if 'partner_id' in order else False
510>>>>>>> MERGE-SOURCE
507 order_id = self.create(cr, uid, {511 order_id = self.create(cr, uid, {
508 'name': order['name'],512 'name': order['name'],
509 'user_id': order['user_id'] or False,513 'user_id': order['user_id'] or False,
510 'session_id': order['pos_session_id'],514 'session_id': order['pos_session_id'],
511 'lines': order['lines'],515 'lines': order['lines'],
516<<<<<<< TREE
512 'pos_reference':order['name'],517 'pos_reference':order['name'],
513 'partner_id': order['partner_id'] or False518 'partner_id': order['partner_id'] or False
519=======
520 'pos_reference':order['name'],
521 'partner_id':partner_id,
522>>>>>>> MERGE-SOURCE
514 }, context)523 }, context)
515 for payments in order['statement_ids']:524 for payments in order['statement_ids']:
516 payment = payments[2]525 payment = payments[2]
517526
=== modified file 'point_of_sale/static/src/css/pos.css'
--- point_of_sale/static/src/css/pos.css 2013-09-17 10:19:10 +0000
+++ point_of_sale/static/src/css/pos.css 2013-11-11 21:43:26 +0000
@@ -205,6 +205,15 @@
205 box-shadow: 0px 1px 2px rgb(63, 66, 139) inset;205 box-shadow: 0px 1px 2px rgb(63, 66, 139) inset;
206}206}
207207
208.point-of-sale #rightheader .customername{
209 float:right;
210 color:#DDD;
211 font-size:16px;
212 margin-right:32px;
213 margin-top:10px;
214 font-style:italic;
215}
216
208/* c) The session buttons */217/* c) The session buttons */
209218
210.point-of-sale #rightheader .header-button{219.point-of-sale #rightheader .header-button{
@@ -605,6 +614,15 @@
605 max-width: 120px;614 max-width: 120px;
606}615}
607616
617.point-of-sale .product .code-tag {
618 position: absolute;
619 top: 2px;
620 left: 2px;
621 vertical-align: top;
622 line-height: 13px;
623 padding: 2px 5px;
624}
625
608.point-of-sale .product .price-tag {626.point-of-sale .product .price-tag {
609 position: absolute;627 position: absolute;
610 top: 2px;628 top: 2px;
@@ -1373,6 +1391,161 @@
1373 line-height:180px;1391 line-height:180px;
1374}1392}
13751393
1394.point-of-sale .modal-dialog .popup-selection{
1395 position: absolute;
1396 left:30%;
1397 top:10%;
1398 width: 700px;
1399 height:70%;
1400 padding:10px;
1401 padding-top:20px;
1402 text-align:left;
1403 font-size:14px;
1404 font-weight:bold;
1405 background-color: #F0EEEE;
1406 border-radius: 4px;
1407 box-shadow: 0px -1px white, 0px 1px white, 0px 4px #949494, 0px 10px 20px rgba(0, 0, 0, 0.3);
1408 z-index:1200;
1409}
1410
1411.point-of-sale .popup-selection .button{
1412 float:right;
1413 width: 110px;
1414 height: 40px;
1415 line-height:40px;
1416 text-align:center;
1417 margin:3px;
1418 margin-top:10px;
1419 margin-right:50px;
1420
1421 font-size: 14px;
1422 font-weight: bold;
1423
1424 cursor: pointer;
1425
1426 border: 1px solid #cacaca;
1427 border-radius: 4px;
1428
1429 background: #e2e2e2;
1430 background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
1431 background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
1432 background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
1433 background: linear-gradient(#f0f0f0, #e2e2e2);
1434 -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
1435 -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
1436 box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
1437}
1438.point-of-sale .popup-selection .button:hover {
1439 color: white;
1440 background: #7f82ac;
1441 border: 1px solid #7f82ac;
1442 background: -webkit-linear-gradient(#9d9fc5, #7f82ac);
1443 background: -moz-linear-gradient(#9d9fc5, #7f82ac);
1444 background: -ms-linear-gradient(#9d9fc5, #7f82ac);
1445 background: linear-gradient(#9d9fc5, #7f82ac);
1446
1447 -webkit-transition-property: background, border;
1448 -webkit-transition-duration: 0.2s;
1449 -webkit-transition-timing-function: ease-out;
1450}
1451
1452/* customer selection */
1453
1454#customer-cancel {
1455 position: absolute;
1456 left: 10px;
1457 bottom: 10px;
1458}
1459
1460.point-of-sale .customer-list-container {
1461 position: absolute;
1462 top:70px;
1463 left:5px;
1464 right:5px;
1465 bottom: 70px;
1466 text-align: left;
1467 overflow: auto;
1468}
1469
1470.point-of-sale .customer-list-scroller {
1471 -webkit-box-sizing: border-box;
1472 -moz-box-sizing: border-box;
1473 -ms-box-sizing: border-box;
1474 box-sizing: border-box;
1475 width:98%;
1476}
1477
1478.customer-list-scroller ol {
1479 list-style: none;
1480}
1481
1482.customer-list-scroller ol li { }
1483
1484.customer-list-scroller ol li a {
1485 display:block;
1486 text-decoration:none;
1487 color:#000000;
1488 background-color:#FFFFFF;
1489 line-height:20px;
1490 border-bottom-style:solid;
1491 border-bottom-width:1px;
1492 border-bottom-color:#CCCCCC;
1493 padding-left:10px;
1494 cursor:pointer;
1495}
1496
1497.customer-list-scroller ol li a:hover {
1498 color:#FFFFFF;
1499 background-image:url(/point_of_sale/static/src/img/hover.png);
1500 background-repeat:repeat-x;
1501}
1502
1503.point-of-sale .customer {
1504 width: 100%;
1505}
1506
1507.point-of-sale .customer a {
1508 width: 100%;
1509}
1510
1511.point-of-sale .customer .customer-field {
1512 width: 130px;
1513 padding: 5px;
1514 margin: 5px;
1515}
1516
1517.point-of-sale .customer .customer-name{
1518 width: 200px;
1519 padding: 5px;
1520 margin: 5px;
1521}
1522
1523.point-of-sale .customer .customer-phone {
1524 width: 50px;
1525 padding: 5px;
1526 margin: 5px;
1527}
1528
1529.point-of-sale .customer-searchbox {
1530 right: 2px;
1531}
1532.point-of-sale .customer-searchbox input {
1533 width: 130px;
1534 border-radius: 11px;
1535 border: 1px solid #cecbcb;
1536 padding: 3px 19px;
1537 margin: 6px;
1538 background: url("../img/search.png") no-repeat 5px;
1539 background-color: white;
1540}
1541.point-of-sale .customer-search-clear {
1542 postion: absolute;
1543 top: 11px;
1544 right: 600px;
1545 cursor: pointer;
1546 display: none;
1547}
1548
1376/* ********* The ScrollBarWidget ********* */1549/* ********* The ScrollBarWidget ********* */
13771550
1378.point-of-sale .scrollbar{ 1551.point-of-sale .scrollbar{
13791552
=== added file 'point_of_sale/static/src/img/hover.png'
1380Binary files point_of_sale/static/src/img/hover.png 1970-01-01 00:00:00 +0000 and point_of_sale/static/src/img/hover.png 2013-11-11 21:43:26 +0000 differ1553Binary files point_of_sale/static/src/img/hover.png 1970-01-01 00:00:00 +0000 and point_of_sale/static/src/img/hover.png 2013-11-11 21:43:26 +0000 differ
=== modified file 'point_of_sale/static/src/js/db.js'
--- point_of_sale/static/src/js/db.js 2013-09-23 14:51:39 +0000
+++ point_of_sale/static/src/js/db.js 2013-11-11 21:43:26 +0000
@@ -54,7 +54,11 @@
54 this.category_search_string = {};54 this.category_search_string = {};
55 this.packagings_by_id = {};55 this.packagings_by_id = {};
56 this.packagings_by_product_id = {};56 this.packagings_by_product_id = {};
57<<<<<<< TREE
57 this.packagings_by_ean13 = {};58 this.packagings_by_ean13 = {};
59=======
60 this.customer_list_search_strings = '';
61>>>>>>> MERGE-SOURCE
58 },62 },
59 /* returns the category object from its id. If you pass a list of id as parameters, you get63 /* returns the category object from its id. If you pass a list of id as parameters, you get
60 * a list of category objects. 64 * a list of category objects.
@@ -151,9 +155,15 @@
151 if(product.ean13){155 if(product.ean13){
152 str += '|' + product.ean13;156 str += '|' + product.ean13;
153 }157 }
158<<<<<<< TREE
154 if(product.default_code){159 if(product.default_code){
155 str += '|' + product.default_code;160 str += '|' + product.default_code;
156 }161 }
162=======
163 if(product.code){
164 str += '|' + product.code;
165 }
166>>>>>>> MERGE-SOURCE
157 var packagings = this.packagings_by_product_id[product.id] || [];167 var packagings = this.packagings_by_product_id[product.id] || [];
158 for(var i = 0; i < packagings.length; i++){168 for(var i = 0; i < packagings.length; i++){
159 str += '|' + packagings[i].ean;169 str += '|' + packagings[i].ean;
@@ -216,6 +226,35 @@
216 }226 }
217 }227 }
218 },228 },
229 _customer_search_string: function(customer){
230 var str = '' + customer.id + ':' + customer.name;
231 if(customer.vat){
232 str += '|' + customer.vat;
233 }
234 if(customer.email){
235 str += '|' + customer.email;
236 }
237 if(customer.phone){
238 str += '|' + customer.phone;
239 }
240 if(customer.mobile){
241 str += '|' + customer.mobile;
242 }
243 return str + '\n';
244 },
245 add_customers: function(customers){
246 var stored_customers = this.load('customers',{});
247
248 if(!customers instanceof Array){
249 customers = [customers];
250 }
251 for(var i = 0, len = customers.length; i < len; i++){
252 var c = customers[i];
253 this.customer_list_search_strings += this._customer_search_string(c);
254 stored_customers[c.id] = c;
255 }
256 this.save('customers',stored_customers);
257 },
219 /* removes all the data from the database. TODO : being able to selectively remove data */258 /* removes all the data from the database. TODO : being able to selectively remove data */
220 clear: function(stores){259 clear: function(stores){
221 for(var i = 0, len = arguments.length; i < len; i++){260 for(var i = 0, len = arguments.length; i < len; i++){
@@ -245,9 +284,20 @@
245 }284 }
246 return undefined;285 return undefined;
247 },286 },
287<<<<<<< TREE
248 get_product_by_reference: function(ref){288 get_product_by_reference: function(ref){
249 return this.product_by_reference[ref];289 return this.product_by_reference[ref];
250 },290 },
291=======
292 get_product_by_code: function(code){
293 var products = this.load('products', {});
294 for(var i in products){
295 if(products[i] && products[i].code === code){
296 return products[i];
297 }
298 }
299 },
300>>>>>>> MERGE-SOURCE
251 get_product_by_category: function(category_id){301 get_product_by_category: function(category_id){
252 var product_ids = this.product_by_category_id[category_id];302 var product_ids = this.product_by_category_id[category_id];
253 var list = [];303 var list = [];
@@ -258,6 +308,17 @@
258 }308 }
259 return list;309 return list;
260 },310 },
311 get_customer_by_id: function(id){
312 return this.load('customers',{})[id];
313 },
314 get_all_customers: function(){
315 list = [];
316 stored_customers = this.load('customers',{});
317 for (var i in stored_customers) {
318 list.push(stored_customers[i]);
319 }
320 return list;
321 },
261 /* returns a list of products with :322 /* returns a list of products with :
262 * - a category that is or is a child of category_id,323 * - a category that is or is a child of category_id,
263 * - a name, package or ean13 containing the query (case insensitive) 324 * - a name, package or ean13 containing the query (case insensitive)
@@ -276,6 +337,23 @@
276 }337 }
277 return results;338 return results;
278 },339 },
340 /* returns a list of customers with :
341 * - a name, TIN, email, or phone/mobile containing the query (case insensitive)
342 */
343 search_customers: function(query){
344 var re = RegExp("([0-9]+):.*?"+query,"gi");
345 var results = [];
346 for(var i = 0; i < this.limit; i++){
347 r = re.exec(this.customer_list_search_strings);
348 if(r){
349 var id = Number(r[1]);
350 results.push(this.get_customer_by_id(id));
351 }else{
352 break;
353 }
354 }
355 return results;
356 },
279 add_order: function(order){357 add_order: function(order){
280 var order_id = order.uid;358 var order_id = order.uid;
281 var orders = this.load('orders',[]);359 var orders = this.load('orders',[]);
282360
=== modified file 'point_of_sale/static/src/js/devices.js'
--- point_of_sale/static/src/js/devices.js 2013-09-24 11:07:01 +0000
+++ point_of_sale/static/src/js/devices.js 2013-11-11 21:43:26 +0000
@@ -156,6 +156,18 @@
156 return this.message('scan_item_error_unrecognized',{ean: ean});156 return this.message('scan_item_error_unrecognized',{ean: ean});
157 },157 },
158158
159 //a product has been entered by keypad and recognized with success
160 // data is a parsed {product.code,qty,price} object
161 keypad_item_success: function(data){
162 return this.message('keypad_item_success',{data: data});
163 },
164
165 // a product has been entered by keypad but not recognized
166 // data is a parsed {product.code,qty,price} object
167 keypad_item_error_unrecognized: function(data){
168 return this.message('keypad_item_error_unrecognized',{data: data});
169 },
170
159 //the client is asking for help171 //the client is asking for help
160 help_needed: function(){172 help_needed: function(){
161 return this.message('help_needed');173 return this.message('help_needed');
@@ -600,5 +612,172 @@
600 $('body').off('keypress', this.handler)612 $('body').off('keypress', this.handler)
601 },613 },
602 });614 });
615
616 // this module mimics a keypad-only cash register. Use connect() and
617 // disconnect() to activate and deactivate it. Use set_action_callback to
618 // tell it what to do when the cashier enters product data(qty, price, etc).
619 module.Keypad = instance.web.Class.extend({
620 init: function(attributes){
621 this.pos = attributes.pos;
622 this.action_callback = undefined;
623 this.saved_callback_stack = [];
624 },
625
626 save_callback: function(){
627 this.saved_callback_stack.push(this.action_callback);
628 },
629
630 restore_callback: function(){
631 if (this.saved_callback_stack.length > 0) {
632 this.action_callback = this.saved_callback_stack.pop();
633 }
634 },
635
636 set_action_callback: function(callback){
637 this.action_callback = callback
638 },
639
640 //remove action callback
641 reset_action_callback: function(){
642 this.action_callback = undefined;
643 },
644
645 reset_parse_result: function(parse_result) {
646 parse_result.code = 0;
647 parse_result.qty = 0;
648 parse_result.priceOverride = false;
649 parse_result.price = 0.00;
650 parse_result.discount = 0.00;
651 parse_result.void_last_line = false;
652 },
653
654 copy_parse_result: function(src) {
655 var dst = {
656 code: null,
657 qty: 1,
658 priceOverride: false,
659 price: 0.00,
660 discount: 0.00,
661 void_last_line: false,
662 };
663 dst.code = src.code;
664 dst.qty = src.qty;
665 dst.priceOverride = src.priceOverride;
666 dst.price = src.price;
667 dst.discount = src.discount;
668 dst.void_last_line = src.void_last_line;
669 return dst;
670 },
671
672 // starts catching keyboard events and tries to interpret keystrokes,
673 // calling the callback when needed.
674 connect: function(){
675 var self = this;
676 var KC_PLU = 107; // KeyCode: Product Code (Keypad '+')
677 var KC_QTY = 111; // KeyCode: Quantity (Keypad '/')
678 var KC_AMT = 106; // KeyCode: Price (Keypad '*')
679 var KC_DISC = 109; // KeyCode: Discount Percentage [0..100] (Keypad '-')
680 var KC_VOID = 13; // KeyCode: Void current line (Keyboard/Keypad Enter key)
681 var KC_CLR1 = 46; // KeyCode: Clear last line of order (Keyboard Delete key)
682 var KC_CLR2 = 8; // KeyCode: Clear current line (Keyboard Backspace key)
683 var codeNumbers = [];
684 var codeChars = [];
685 var parse_result = {
686 code: null,
687 qty: 1,
688 priceOverride: false,
689 price: 0.00,
690 discount: 0.00,
691 void_last_line: false,
692 };
693 var kc_lookup = {
694 96: '0',
695 97: '1',
696 98: '2',
697 99: '3',
698 100: '4',
699 101: '5',
700 102: '6',
701 103: '7',
702 104: '8',
703 105: '9',
704 106: '*',
705 107: '+',
706 109: '-',
707 110: '.',
708 111: '/',
709 };
710
711 // Catch keyup events anywhere in the POS interface. Barcode reader also does this, but won't interfere
712 // because it looks for a specific timing between keyup events. On the plus side this should mean that you
713 // can use both the keypad and the barcode reader during the same session (but for separate order lines).
714 // This could be useful in cases where the scanner can't read the barcode.
715 $('body').delegate('','keyup', function (e){
716 console.log('keyup:'+String.fromCharCode(e.keyCode)+' '+e.keyCode,e);
717 //We only care about numbers and modifiers
718 token = e.keyCode;
719 if ((token >= 96 && token <= 111) || token === KC_PLU || token === KC_QTY || token === KC_AMT) {
720
721 if (token === KC_PLU) {
722 parse_result.code = codeChars.join('');
723 var res = self.copy_parse_result(parse_result);
724 codeNumbers = [];
725 codeChars = [];
726 self.reset_parse_result(parse_result);
727 console.log('PLU token: code:'+res.code+', qty:'+res.qty+', price:'+res.price+', discount:'+res.discount);
728 self.action_callback(res);
729 } else if (token === KC_QTY) {
730 parse_result.qty = parseInt(codeChars.join(''));
731 codeNumbers = [];
732 codeChars = [];
733 console.log('QTY token: qty:'+parse_result.qty);
734 } else if (token === KC_AMT) {
735 parse_result.price = parseFloat(codeChars.join('')).toFixed(2);
736 parse_result.priceOverride = true;
737 codeNumbers = [];
738 codeChars = [];
739 console.log('AMT token: price:'+parse_result.price);
740 } else if (token === KC_DISC) {
741 parse_result.discount = parseFloat(codeChars.join(''));
742 codeNumbers = [];
743 codeChars = [];
744 console.log('DISC token: discount:'+parse_result.discount);
745 } else {
746 codeNumbers.push(token - 48);
747 codeChars.push(kc_lookup[token]);
748 }
749 } else if (token === KC_VOID) {
750 /*
751 * This is commented out for now. We don't want to interfere with
752 * the 'Enter' keycode used by the barcode reader to signify a scan.
753 */
754 // Void the last line of the order only if there isn't another line in pregress.
755// if (codeNumbers.length === 0) {
756// parse_result.void_last_line = true;
757// var res = self.copy_parse_result(parse_result);
758// codeNumbers = [];
759// codeChars = [];
760// self.reset_parse_result(parse_result);
761// console.log('VOID token:'+res.void_last_line);
762// self.action_callback(res);
763// }
764 } else {
765 // For now pressing Backspace or Delete just defaults to doing nothing.
766 // In the future we might want it to display a popup or something.
767 if (token === KC_CLR1 || token === KC_CLR2) {
768 ;
769 }
770 codeNumbers = [];
771 codeChars = [];
772 self.reset_parse_result(parse_result);
773 }
774 });
775 },
776
777 // stops catching keyboard events
778 disconnect: function(){
779 $('body').undelegate('', 'keyup')
780 },
781 });
603782
604}783}
605784
=== modified file 'point_of_sale/static/src/js/models.js'
--- point_of_sale/static/src/js/models.js 2013-09-23 17:13:10 +0000
+++ point_of_sale/static/src/js/models.js 2013-11-11 21:43:26 +0000
@@ -23,11 +23,18 @@
23 this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time23 this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
2424
25 this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes25 this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
26 this.keypad = new module.Keypad({'pos': this}); // used to simulate a cash register keypad
26 this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy27 this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
27 this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy28 this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy
28 this.db = new module.PosLS(); // a database used to store the products and categories29 this.db = new module.PosLS(); // a database used to store the products and categories
30<<<<<<< TREE
29 this.db.clear('products','categories');31 this.db.clear('products','categories');
30 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode 32 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
33=======
34 this.db.clear('products','categories','customers');
35 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
36
37>>>>>>> MERGE-SOURCE
3138
32 // default attributes values. If null, it will be loaded below.39 // default attributes values. If null, it will be loaded below.
33 this.set({40 this.set({
@@ -44,6 +51,7 @@
44 'orders': new module.OrderCollection(),51 'orders': new module.OrderCollection(),
45 //this is the product list as seen by the product list widgets, it will change based on the category filters52 //this is the product list as seen by the product list widgets, it will change based on the category filters
46 'products': new module.ProductCollection(), 53 'products': new module.ProductCollection(),
54 'customers': new module.CustomerCollection(),
47 'cashRegisters': null, 55 'cashRegisters': null,
4856
49 'bank_statements': null,57 'bank_statements': null,
@@ -135,6 +143,10 @@
135 }).then(function(partners){143 }).then(function(partners){
136 self.set('partner_list',partners);144 self.set('partner_list',partners);
137145
146 return self.fetch('res.partner', ['name','vat','email','phone','mobile'], [['customer', '=', true]]);
147 }).then(function(customers){
148 self.db.add_customers(customers);
149
138 return self.fetch('account.tax', ['amount', 'price_include', 'type']);150 return self.fetch('account.tax', ['amount', 'price_include', 'type']);
139 }).then(function(taxes){151 }).then(function(taxes){
140 self.set('taxes', taxes);152 self.set('taxes', taxes);
@@ -187,7 +199,11 @@
187199
188 return self.fetch(200 return self.fetch(
189 'product.product', 201 'product.product',
202<<<<<<< TREE
190 ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code',203 ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code',
204=======
205 ['name', 'code', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13',
206>>>>>>> MERGE-SOURCE
191 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description'],207 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description'],
192 [['sale_ok','=',true],['available_in_pos','=',true]],208 [['sale_ok','=',true],['available_in_pos','=',true]],
193 {pricelist: self.get('pricelist').id} // context for price209 {pricelist: self.get('pricelist').id} // context for price
@@ -450,6 +466,46 @@
450 }466 }
451 return true;467 return true;
452 },468 },
469
470 keypad_enter_product: function(parsed_data){
471 var self = this;
472 var doMerge = true;
473 var product = this.db.get_product_by_code(parsed_data.code);
474 var selectedOrder = this.get('selectedOrder');
475
476 if (parsed_data.void_last_line) {
477 line = selectedOrder.getLastOrderline();
478 if (line) {
479 line.set_quantity(Number.NaN);
480 }
481 return true;
482 }
483
484 if (!product){
485 return false;
486 }
487
488 if (parsed_data.discount > 0) {
489 doMerge = false;
490 }
491
492 if (!parsed_data.priceOverride) {
493 parsed_data.price = product.price;
494 }
495
496// if (product.get('to_weight') && self.pos.iface_electronic_scale) {
497// self.pos_widget.screen_selector.set_current_screen(self.scale_screen, {product: product});
498// } else {
499 selectedOrder.addProduct(new module.Product(product), { quantity: parsed_data.qty,
500 price: parsed_data.price,
501 merge: doMerge});
502// }
503 if (!doMerge){
504 selectedOrder.selected_orderline.set_discount(parsed_data.discount);
505 }
506 self.get('products').reset(product);
507 return true;
508 },
453 });509 });
454510
455 module.CashRegister = Backbone.Model.extend({511 module.CashRegister = Backbone.Model.extend({
@@ -866,7 +922,7 @@
866 this.get('paymentLines').each(function(paymentline){922 this.get('paymentLines').each(function(paymentline){
867 paymentlines.push(paymentline.export_for_printing());923 paymentlines.push(paymentline.export_for_printing());
868 });924 });
869 var client = this.get('client');925 var client = this.pos.get('selectedOrder').get('client');
870 var cashier = this.pos.get('cashier') || this.pos.get('user');926 var cashier = this.pos.get('cashier') || this.pos.get('user');
871 var company = this.pos.get('company');927 var company = this.pos.get('company');
872 var shop = this.pos.get('shop');928 var shop = this.pos.get('shop');
@@ -883,7 +939,7 @@
883 total_discount: this.getDiscountTotal(),939 total_discount: this.getDiscountTotal(),
884 change: this.getChange(),940 change: this.getChange(),
885 name : this.getName(),941 name : this.getName(),
886 client: client ? client.name : null ,942 client: client ? client : null ,
887 invoice_id: null, //TODO943 invoice_id: null, //TODO
888 cashier: cashier ? cashier.name : null,944 cashier: cashier ? cashier.name : null,
889 date: { 945 date: {
@@ -919,6 +975,7 @@
919 (this.get('paymentLines')).each(_.bind( function(item) {975 (this.get('paymentLines')).each(_.bind( function(item) {
920 return paymentLines.push([0, 0, item.export_as_JSON()]);976 return paymentLines.push([0, 0, item.export_as_JSON()]);
921 }, this));977 }, this));
978 var client = this.pos.get('selectedOrder').get('client');
922 return {979 return {
923 name: this.getName(),980 name: this.getName(),
924 amount_paid: this.getPaidTotal(),981 amount_paid: this.getPaidTotal(),
@@ -928,7 +985,11 @@
928 lines: orderLines,985 lines: orderLines,
929 statement_ids: paymentLines,986 statement_ids: paymentLines,
930 pos_session_id: this.pos.get('pos_session').id,987 pos_session_id: this.pos.get('pos_session').id,
988<<<<<<< TREE
931 partner_id: this.get_client() ? this.get_client().id : false,989 partner_id: this.get_client() ? this.get_client().id : false,
990=======
991 partner_id: client ? client.id : undefined,
992>>>>>>> MERGE-SOURCE
932 user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,993 user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,
933 uid: this.uid,994 uid: this.uid,
934 };995 };
@@ -1019,4 +1080,12 @@
1019 this.set({buffer:'0'});1080 this.set({buffer:'0'});
1020 },1081 },
1021 });1082 });
1083
1084 module.Customer = Backbone.Model.extend({
1085 });
1086
1087 module.CustomerCollection = Backbone.Collection.extend({
1088 model: module.Customer,
1089 });
1090
1022}1091}
10231092
=== modified file 'point_of_sale/static/src/js/screens.js'
--- point_of_sale/static/src/js/screens.js 2013-09-23 14:51:39 +0000
+++ point_of_sale/static/src/js/screens.js 2013-11-11 21:43:26 +0000
@@ -223,6 +223,20 @@
223 }223 }
224 },224 },
225225
226 // what happens when a product is entered by keypad emulator :
227 // it will add the product to the order.
228 keypad_product_action: function(data){
229 var self = this;
230 if(self.pos.keypad_enter_product(data)){
231 self.pos.proxy.keypad_item_success(data);
232 }else{
233 self.pos.proxy.keypad_item_error_unrecognized(data);
234 if(self.product_error_popup && self.pos_widget.screen_selector.get_user_mode() === 'cashier'){
235 self.pos_widget.screen_selector.show_popup(self.product_error_popup);
236 }
237 }
238 },
239
226 // shows an action bar on the screen. The actionbar is automatically shown when you add a button240 // shows an action bar on the screen. The actionbar is automatically shown when you add a button
227 // with add_action_button()241 // with add_action_button()
228 show_action_bar: function(){242 show_action_bar: function(){
@@ -283,9 +297,15 @@
283 }else{297 }else{
284 this.pos_widget.client_button.hide();298 this.pos_widget.client_button.hide();
285 }299 }
300<<<<<<< TREE
286 if(this.cashier_mode){301 if(this.cashier_mode){
302=======
303 if(cashier_mode){
304 this.pos_widget.select_customer_button.show();
305>>>>>>> MERGE-SOURCE
287 this.pos_widget.close_button.show();306 this.pos_widget.close_button.show();
288 }else{307 }else{
308 this.pos_widget.select_customer_button.hide();
289 this.pos_widget.close_button.hide();309 this.pos_widget.close_button.hide();
290 }310 }
291 311
@@ -297,11 +317,16 @@
297 'client' : self.barcode_client_action ? function(code){ self.barcode_client_action(code); } : undefined ,317 'client' : self.barcode_client_action ? function(code){ self.barcode_client_action(code); } : undefined ,
298 'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined,318 'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined,
299 });319 });
320
321 this.pos.keypad.set_action_callback(function(data){ self.keypad_product_action(data); });
300 },322 },
301323
302 // this method is called when the screen is closed to make place for a new screen. this is a good place324 // this method is called when the screen is closed to make place for a new screen. this is a good place
303 // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()325 // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
304 close: function(){326 close: function(){
327 if(this.pos.keypad){
328 this.pos.keypad.reset_action_callback();
329 }
305 if(this.pos.barcode_reader){330 if(this.pos.barcode_reader){
306 this.pos.barcode_reader.reset_action_callbacks();331 this.pos.barcode_reader.reset_action_callbacks();
307 }332 }
@@ -401,6 +426,7 @@
401 this._super();426 this._super();
402 this.pos.proxy.help_needed();427 this.pos.proxy.help_needed();
403 this.pos.proxy.scan_item_error_unrecognized();428 this.pos.proxy.scan_item_error_unrecognized();
429 this.pos.proxy.keypad_item_error_unrecognized();
404430
405 this.pos.barcode_reader.save_callbacks();431 this.pos.barcode_reader.save_callbacks();
406 this.pos.barcode_reader.reset_action_callbacks();432 this.pos.barcode_reader.reset_action_callbacks();
@@ -411,6 +437,8 @@
411 self.pos_widget.screen_selector.set_user_mode('cashier');437 self.pos_widget.screen_selector.set_user_mode('cashier');
412 },438 },
413 });439 });
440 this.pos.keypad.save_callback();
441 this.pos.keypad.reset_action_callback();
414 this.$('.footer .button').off('click').click(function(){442 this.$('.footer .button').off('click').click(function(){
415 self.pos_widget.screen_selector.close_popup();443 self.pos_widget.screen_selector.close_popup();
416 });444 });
@@ -418,6 +446,7 @@
418 close:function(){446 close:function(){
419 this._super();447 this._super();
420 this.pos.proxy.help_canceled();448 this.pos.proxy.help_canceled();
449 this.pos.keypad.restore_callback();
421 this.pos.barcode_reader.restore_callbacks();450 this.pos.barcode_reader.restore_callbacks();
422 },451 },
423 });452 });
@@ -784,10 +813,12 @@
784 this.user = this.pos.get('user');813 this.user = this.pos.get('user');
785 this.company = this.pos.get('company');814 this.company = this.pos.get('company');
786 this.shop_obj = this.pos.get('shop');815 this.shop_obj = this.pos.get('shop');
816 this.client = null;
787 },817 },
788 renderElement: function() {818 renderElement: function() {
789 this._super();819 this._super();
790 this.pos.bind('change:selectedOrder', this.change_selected_order, this);820 this.pos.bind('change:selectedOrder', this.change_selected_order, this);
821 this.pos.get('selectedOrder').bind('change:client', this.change_client, this);
791 this.change_selected_order();822 this.change_selected_order();
792 },823 },
793 show: function(){824 show: function(){
@@ -850,6 +881,10 @@
850 this.currentPaymentLines.bind('all', this.refresh, this);881 this.currentPaymentLines.bind('all', this.refresh, this);
851 this.refresh();882 this.refresh();
852 },883 },
884 change_client: function() {
885 this.client = this.pos.get('selectedOrder').get('client');
886 this.refresh();
887 },
853 refresh: function() {888 refresh: function() {
854 this.currentOrder = this.pos.get('selectedOrder');889 this.currentOrder = this.pos.get('selectedOrder');
855 $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));890 $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));
@@ -1062,4 +1097,82 @@
1062 this.currentPaymentLines.last().set_amount(val);1097 this.currentPaymentLines.last().set_amount(val);
1063 },1098 },
1064 });1099 });
1100<<<<<<< TREE
1101=======
1102
1103 module.SelectCustomerPopupWidget = module.PopUpWidget.extend({
1104 template:'SelectCustomerPopupWidget',
1105
1106 start: function(){
1107 this._super();
1108 var self = this;
1109 this.customer_list_widget = new module.CustomerListWidget(this,{
1110 click_customer_action: function(customer){
1111 this.pos.get('selectedOrder').set_client(customer);
1112 this.pos_widget.customername.refresh();
1113 this.pos_widget.screen_selector.set_current_screen('products');
1114 },
1115 });
1116 },
1117
1118 show: function(){
1119 this._super();
1120 var self = this;
1121 this.renderElement();
1122
1123 this.customer_list_widget.replace($('.placeholder-CustomerListWidget'));
1124
1125 this.$('.button.cancel').off('click').click(function(){
1126 self.pos_widget.screen_selector.set_current_screen('products');
1127 });
1128
1129 this.customer_search();
1130 },
1131
1132 // Customer search filter
1133 customer_search: function(){
1134 var self = this;
1135
1136 // find all products belonging to the current category
1137 var customers = this.pos.db.get_all_customers();
1138 self.pos.get('customers').reset(customers);
1139
1140 // filter customers according to the search string
1141 this.$('.customer-searchbox input').keyup(function(event){
1142 query = $(this).val().toLowerCase();
1143 if(query){
1144 var customers = self.pos.db.search_customers(query);
1145 self.pos.get('customers').reset(customers);
1146 self.$('.customer-search-clear').fadeIn();
1147 if(event.keyCode == 13){
1148 var c = null;
1149 if(customers.length == 1){
1150 c = self.pos.get('customers').get(customers[0]);
1151 }
1152 if(c !== null){
1153 self.pos_widget.select_customer_popup.customer_list_widget.click_customer_action(c);
1154 self.$('.customer-search-clear').trigger('click');
1155 }
1156 }
1157 }else{
1158 var customers = self.pos.db.get_all_customers();
1159 self.pos.get('customers').reset(customers);
1160 self.$('.customer-search-clear').fadeOut();
1161 }
1162 });
1163
1164 this.$('.customer-searchbox input').click(function(){}); //Why ???
1165
1166 //reset the search when clicking on reset
1167 this.$('.customer-search-clear').click(function(){
1168 var customers = self.pos.db.get_all_customers();
1169 self.pos.get('customers').reset(customers);
1170 self.$('.customer-searchbox input').val('').focus();
1171 self.$('.customer-search-clear').fadeOut();
1172 });
1173 },
1174
1175 });
1176
1177>>>>>>> MERGE-SOURCE
1065}1178}
10661179
=== modified file 'point_of_sale/static/src/js/widgets.js'
--- point_of_sale/static/src/js/widgets.js 2013-10-09 13:29:44 +0000
+++ point_of_sale/static/src/js/widgets.js 2013-11-11 21:43:26 +0000
@@ -561,10 +561,15 @@
561 self.pos.get('products').reset(products);561 self.pos.get('products').reset(products);
562562
563 // filter the products according to the search string563 // filter the products according to the search string
564<<<<<<< TREE
564 this.$('.searchbox input').keyup(function(event){565 this.$('.searchbox input').keyup(function(event){
565 console.log('event',event);566 console.log('event',event);
567=======
568 this.$('.searchbox input').keyup(function(event){
569>>>>>>> MERGE-SOURCE
566 query = $(this).val().toLowerCase();570 query = $(this).val().toLowerCase();
567 if(query){571 if(query){
572<<<<<<< TREE
568 if(event.which === 13){573 if(event.which === 13){
569 if( self.pos.get('products').size() === 1 ){574 if( self.pos.get('products').size() === 1 ){
570 self.pos.get('selectedOrder').addProduct(self.pos.get('products').at(0));575 self.pos.get('selectedOrder').addProduct(self.pos.get('products').at(0));
@@ -575,6 +580,30 @@
575 self.pos.get('products').reset(products);580 self.pos.get('products').reset(products);
576 self.$('.search-clear').fadeIn();581 self.$('.search-clear').fadeIn();
577 }582 }
583=======
584 var products = self.pos.db.search_product_in_category(self.category.id, query);
585 self.pos.get('products').reset(products);
586 self.$('.search-clear').fadeIn();
587 if(event.keyCode == 13){
588 var p = null;
589 if(products.length > 1){
590 i = 0;
591 while(i < products.length){
592 if (products[i].code == query){
593 p = self.pos.get('products').get(products[i]);
594 break;
595 }
596 i++;
597 }
598 }else if(products.length == 1){
599 p = self.pos.get('products').get(products[0]);
600 }
601 if(p !== null){
602 self.pos_widget.product_screen.product_list_widget.click_product_action(p);
603 self.$('.search-clear').trigger('click');
604 }
605 }
606>>>>>>> MERGE-SOURCE
578 }else{607 }else{
579 var products = self.pos.db.get_product_by_category(self.category.id);608 var products = self.pos.db.get_product_by_category(self.category.id);
580 self.pos.get('products').reset(products);609 self.pos.get('products').reset(products);
@@ -666,6 +695,27 @@
666 },695 },
667 });696 });
668697
698 module.CustomernameWidget = module.PosBaseWidget.extend({
699 template: 'CustomernameWidget',
700 init: function(parent, options){
701 var options = options || {};
702 this._super(parent,options);
703 this.pos.bind('change:selectedOrder', this.renderElement, this);
704 },
705 refresh: function(){
706 this.renderElement();
707 },
708 get_name: function(){
709 var user;
710 customer = this.pos.get('selectedOrder').get_client();
711 if(customer){
712 return customer.name;
713 }else{
714 return "";
715 }
716 },
717 });
718
669 module.HeaderButtonWidget = module.PosBaseWidget.extend({719 module.HeaderButtonWidget = module.PosBaseWidget.extend({
670 template: 'HeaderButtonWidget',720 template: 'HeaderButtonWidget',
671 init: function(parent, options){721 init: function(parent, options){
@@ -874,6 +924,8 @@
874924
875925
876 self.pos.barcode_reader.connect();926 self.pos.barcode_reader.connect();
927
928 self.pos.keypad.connect();
877929
878 instance.webclient.set_content_full_screen(true);930 instance.webclient.set_content_full_screen(true);
879931
@@ -948,6 +1000,9 @@
9481000
949 this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});1001 this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
950 this.error_negative_price_popup.appendTo($('.point-of-sale'));1002 this.error_negative_price_popup.appendTo($('.point-of-sale'));
1003
1004 this.select_customer_popup = new module.SelectCustomerPopupWidget(this, {});
1005 this.select_customer_popup.appendTo($('.point-of-sale'));
9511006
952 this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});1007 this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});
953 this.error_no_client_popup.appendTo($('.point-of-sale'));1008 this.error_no_client_popup.appendTo($('.point-of-sale'));
@@ -995,6 +1050,15 @@
995 });1050 });
996 this.client_button.appendTo(this.$('#rightheader'));1051 this.client_button.appendTo(this.$('#rightheader'));
9971052
1053 this.select_customer_button = new module.HeaderButtonWidget(this,{
1054 label:'Select Customer',
1055 action: function(){ self.screen_selector.show_popup('select-customer'); },
1056 });
1057 this.select_customer_button.appendTo(this.$('#rightheader'));
1058
1059 this.customername = new module.CustomernameWidget(this,{});
1060 this.customername.appendTo(this.$('#rightheader'));
1061
998 1062
999 // -------- Screen Selector ---------1063 // -------- Screen Selector ---------
10001064
@@ -1016,8 +1080,12 @@
1016 'error-session': this.error_session_popup,1080 'error-session': this.error_session_popup,
1017 'error-negative-price': this.error_negative_price_popup,1081 'error-negative-price': this.error_negative_price_popup,
1018 'choose-receipt': this.choose_receipt_popup,1082 'choose-receipt': this.choose_receipt_popup,
1083<<<<<<< TREE
1019 'error-no-client': this.error_no_client_popup,1084 'error-no-client': this.error_no_client_popup,
1020 'error-invoice-transfer': this.error_invoice_transfer_popup,1085 'error-invoice-transfer': this.error_invoice_transfer_popup,
1086=======
1087 'select-customer': this.select_customer_popup,
1088>>>>>>> MERGE-SOURCE
1021 },1089 },
1022 default_client_screen: 'welcome',1090 default_client_screen: 'welcome',
1023 default_cashier_screen: 'products',1091 default_cashier_screen: 'products',
@@ -1097,6 +1165,7 @@
1097 },1165 },
1098 close: function() {1166 close: function() {
1099 var self = this;1167 var self = this;
1168<<<<<<< TREE
11001169
1101 function close(){1170 function close(){
1102 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(1171 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
@@ -1120,6 +1189,19 @@
1120 }else{1189 }else{
1121 return close();1190 return close();
1122 }1191 }
1192=======
1193 this.pos.barcode_reader.disconnect();
1194 this.pos.keypad.disconnect();
1195 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
1196 _.bind(function(res) {
1197 return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
1198 var action = result;
1199 action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
1200 //self.destroy();
1201 this.do_action(action);
1202 }, this));
1203 }, this));
1204>>>>>>> MERGE-SOURCE
1123 },1205 },
1124 destroy: function() {1206 destroy: function() {
1125 this.pos.destroy();1207 this.pos.destroy();
@@ -1127,4 +1209,60 @@
1127 this._super();1209 this._super();
1128 }1210 }
1129 });1211 });
1212
1213 module.CustomerWidget = module.PosBaseWidget.extend({
1214 template: 'CustomerWidget',
1215 init: function(parent, options) {
1216 this._super(parent,options);
1217 this.model = options.model;
1218 this.click_customer_action = options.click_customer_action;
1219 },
1220 renderElement: function() {
1221 this._super();
1222 var self = this;
1223 $("a", this.$el).click(function(e){
1224 if(self.click_customer_action){
1225 self.click_customer_action(self.model.toJSON());
1226 }
1227 });
1228 },
1229 });
1230
1231 module.CustomerListWidget = module.ScreenWidget.extend({
1232 template:'CustomerListWidget',
1233 init: function(parent, options) {
1234 var self = this;
1235 this._super(parent,options);
1236 this.model = options.model;
1237 this.customer_list = [];
1238 this.next_screen = options.next_screen || false;
1239 this.click_customer_action = options.click_customer_action;
1240
1241 var customers = self.pos.db.get_all_customers();
1242 self.pos.get('customers').reset(customers);
1243 this.pos.get('customers').bind('reset', function(){
1244 self.renderElement();
1245 });
1246 },
1247 renderElement: function() {
1248 var self = this;
1249 this._super();
1250 this.customer_list = [];
1251
1252 this.pos.get('customers')
1253 .chain()
1254 .map(function(customer) {
1255 var customer = new module.CustomerWidget(self, {
1256 model: customer,
1257 next_screen: 'products',
1258 click_customer_action: self.click_customer_action,
1259 })
1260 self.customer_list.push(customer);
1261 return customer;
1262 })
1263 .invoke('appendTo', this.$('.customer-list'));
1264
1265 },
1266 });
1267
1130}1268}
11311269
=== modified file 'point_of_sale/static/src/xml/pos.xml'
--- point_of_sale/static/src/xml/pos.xml 2013-09-23 14:51:39 +0000
+++ point_of_sale/static/src/xml/pos.xml 2013-11-11 21:43:26 +0000
@@ -404,7 +404,14 @@
404 <a href="#">404 <a href="#">
405 <div class="product-img">405 <div class="product-img">
406 <img src='' /> <!-- the product thumbnail -->406 <img src='' /> <!-- the product thumbnail -->
407<<<<<<< TREE
407 <t t-if="!product.get('to_weight')">408 <t t-if="!product.get('to_weight')">
409=======
410 <span class="code-tag">
411 <t t-esc="widget.model.get('code')"/>
412 </span>
413 <t t-if="!widget.model.get('to_weight')">
414>>>>>>> MERGE-SOURCE
408 <span class="price-tag">415 <span class="price-tag">
409 <t t-esc="widget.format_currency(product.get('price'))"/>416 <t t-esc="widget.format_currency(product.get('price'))"/>
410 </span>417 </span>
@@ -582,6 +589,12 @@
582 </span>589 </span>
583 </t>590 </t>
584591
592 <t t-name="CustomernameWidget">
593 <span class="customername">
594 <t t-esc="widget.get_name()" />
595 </span>
596 </t>
597
585 <t t-name="PosTicket">598 <t t-name="PosTicket">
586 <div class="pos-sale-ticket">599 <div class="pos-sale-ticket">
587 600
@@ -589,9 +602,18 @@
589 Date.CultureInfo.formatPatterns.longTime)"/> <t t-esc="widget.currentOrder.attributes.name"/></div>602 Date.CultureInfo.formatPatterns.longTime)"/> <t t-esc="widget.currentOrder.attributes.name"/></div>
590 <br />603 <br />
591 <t t-esc="widget.company.name"/><br />604 <t t-esc="widget.company.name"/><br />
605 <t t-if="widget.company.vat">
606 TIN: <t t-esc="widget.company.vat"/><br />
607 </t>
592 Phone: <t t-esc="widget.company.phone || ''"/><br />608 Phone: <t t-esc="widget.company.phone || ''"/><br />
593 User: <t t-esc="widget.user.name"/><br />609 User: <t t-esc="widget.user.name"/><br />
594 Shop: <t t-esc="widget.shop_obj.name"/><br />610 Shop: <t t-esc="widget.shop_obj.name"/><br />
611 <t t-if="widget.client">
612 Customer: <t t-esc="widget.client.name"/><br />
613 <t t-if="widget.client.vat">
614 Customer TIN: <t t-esc="widget.client.vat"/><br />
615 </t>
616 </t>
595 <br />617 <br />
596 <table>618 <table>
597 <colgroup>619 <colgroup>
@@ -781,4 +803,66 @@
781 </div>803 </div>
782 </t>804 </t>
783805
806 <t t-name="CustomerListWidget">
807 <div class='customer-list-container'>
808 <div class="customer-list-scroller">
809 <ol id="customers-screen-ol" class="customer-list">
810 </ol>
811 </div>
812 </div>
813 </t>
814
815 <t t-name="SelectCustomerPopupWidget">
816 <div class="modal-dialog">
817 <div class="popup-selection">
818 <div class="customer-title">
819 Customer Selection
820 </div>
821
822 <div class="customer-searchbox">
823 <input placeholder="Search Customers" />
824 <img class="customer-search-clear" src="/point_of_sale/static/src/img/search_reset.gif" />
825 </div>
826
827 <div class="content-container">
828 <span class="placeholder-CustomerListWidget" />
829 </div>
830
831 <div id="customer-cancel" class = "button cancel">
832 Cancel
833 </div>
834 </div>
835 </div>
836 </t>
837
838 <t t-name="CustomerWidget">
839 <li class='customer'>
840 <a href="#">
841 <span class="customer-field customer-name">
842 <t t-esc="widget.model.get('name')"/>
843 </span>
844 <span class="customer-field">
845 <t t-if="widget.model.get('vat')">
846 <t t-esc="widget.model.get('vat')"/>
847 </t>
848 </span>
849 <span class="customer-field">
850 <t t-if="widget.model.get('email')">
851 <t t-esc="widget.model.get('email')"/>
852 </t>
853 </span>
854 <span class="customer-field customer-phone">
855 <t t-if="widget.model.get('phone')">
856 <t t-esc="widget.model.get('phone')"/>
857 </t>
858 </span>
859 <span class="customer-field customer-phone">
860 <t t-if="widget.model.get('mobile')">
861 <t t-esc="widget.model.get('mobile')"/>
862 </t>
863 </span>
864 </a>
865 </li>
866 </t>
867
784</templates>868</templates>

Subscribers

People subscribed via source and target branches

to all changes: