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

Proposed by Antony Lesuisse (OpenERP)
Status: Needs review
Proposed branch: lp:~mmakonnen/openobject-addons/point_of_sale_enhanced-70
Merge into: lp:openobject-addons/7.0
Diff against target: 1181 lines (+810/-6) (has conflicts)
9 files modified
point_of_sale/controllers/main.py (+16/-0)
point_of_sale/point_of_sale.py (+3/-1)
point_of_sale/static/src/css/pos.css (+173/-0)
point_of_sale/static/src/js/db.js (+72/-0)
point_of_sale/static/src/js/devices.js (+179/-0)
point_of_sale/static/src/js/models.js (+65/-4)
point_of_sale/static/src/js/screens.js (+109/-0)
point_of_sale/static/src/js/widgets.js (+113/-1)
point_of_sale/static/src/xml/pos.xml (+80/-0)
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
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+151375@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Alex (agg-bcn) wrote :

Hi everyone,

How can this module be installed? I have a fresh installation of OpenERP 7 and I need to select a customer in POS. SO I found this module and I've tried to install it several times, but I can't.
I can't find it in Modules list (I must be doing something wrong). Somewhere I read I should replace the original module with this one, but I didn't work.
I guess it must be a very easy issue, but I'm unable to find it ou ... Sorry.

Thanks in advance,

Àlex

Revision history for this message
Eloi-Minorisa (eloigf) wrote :

> Hi everyone,
>
> How can this module be installed? I have a fresh installation of OpenERP 7 and
> I need to select a customer in POS. SO I found this module and I've tried to
> install it several times, but I can't.
> I can't find it in Modules list (I must be doing something wrong). Somewhere I
> read I should replace the original module with this one, but I didn't work.
> I guess it must be a very easy issue, but I'm unable to find it ou ... Sorry.
>
> Thanks in advance,
>
> Àlex

This module requires point_of_sale to be overriden.
But Thierry Godin has a better solution. Check out this module before.
http://thierry-godin.developpez.com/openerp/openerp-module-pos-enhanced-en/

Revision history for this message
Alex (agg-bcn) wrote :

Hi Minorisa,

thanks for your answer. I had already seen Thierry Godin solution, but I think it is in French (I'm not talking about the post, but the solution)

Thanks,

Àlex

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 2012-11-29 22:26:45 +0000
+++ point_of_sale/controllers/main.py 2013-03-03 00:11:29 +0000
@@ -84,6 +84,22 @@
84 return 84 return
8585
86 @openerp.addons.web.http.jsonrequest86 @openerp.addons.web.http.jsonrequest
87 def keypad_item_success(self, request, data):
88 """
89 A product has been entered by keypad with success
90 """
91 print 'keypad_item_success: ' + str(data)
92 return
93
94 @openerp.addons.web.http.jsonrequest
95 def keypad_item_error_unrecognized(self, request, data):
96 """
97 A product has been entered by keypad without success
98 """
99 print 'keypad_item_error_unrecognized: ' + str(data)
100 return
101
102 @openerp.addons.web.http.jsonrequest
87 def help_needed(self, request):103 def help_needed(self, request):
88 """104 """
89 The user wants an help (ex: light is on)105 The user wants an help (ex: light is on)
90106
=== modified file 'point_of_sale/point_of_sale.py'
--- point_of_sale/point_of_sale.py 2013-02-27 10:45:05 +0000
+++ point_of_sale/point_of_sale.py 2013-03-03 00:11:29 +0000
@@ -484,12 +484,14 @@
484 order_ids = []484 order_ids = []
485 for tmp_order in orders:485 for tmp_order in orders:
486 order = tmp_order['data']486 order = tmp_order['data']
487 partner_id = order['partner_id'] if 'partner_id' in order else False
487 order_id = self.create(cr, uid, {488 order_id = self.create(cr, uid, {
488 'name': order['name'],489 'name': order['name'],
489 'user_id': order['user_id'] or False,490 'user_id': order['user_id'] or False,
490 'session_id': order['pos_session_id'],491 'session_id': order['pos_session_id'],
491 'lines': order['lines'],492 'lines': order['lines'],
492 'pos_reference':order['name']493 'pos_reference':order['name'],
494 'partner_id':partner_id,
493 }, context)495 }, context)
494496
495 for payments in order['statement_ids']:497 for payments in order['statement_ids']:
496498
=== modified file 'point_of_sale/static/src/css/pos.css'
--- point_of_sale/static/src/css/pos.css 2013-01-29 15:24:21 +0000
+++ point_of_sale/static/src/css/pos.css 2013-03-03 00:11:29 +0000
@@ -194,6 +194,15 @@
194 font-weight: 900;194 font-weight: 900;
195}195}
196196
197.point-of-sale #rightheader .customername{
198 float:right;
199 color:#DDD;
200 font-size:16px;
201 margin-right:32px;
202 margin-top:10px;
203 font-style:italic;
204}
205
197/* c) The session buttons */206/* c) The session buttons */
198207
199.point-of-sale #rightheader .header-button{208.point-of-sale #rightheader .header-button{
@@ -585,6 +594,15 @@
585 max-width: 120px;594 max-width: 120px;
586}595}
587596
597.point-of-sale .product .code-tag {
598 position: absolute;
599 top: 2px;
600 left: 2px;
601 vertical-align: top;
602 line-height: 13px;
603 padding: 2px 5px;
604}
605
588.point-of-sale .product .price-tag {606.point-of-sale .product .price-tag {
589 position: absolute;607 position: absolute;
590 top: 2px;608 top: 2px;
@@ -1346,6 +1364,161 @@
1346 line-height:180px;1364 line-height:180px;
1347}1365}
13481366
1367.point-of-sale .modal-dialog .popup-selection{
1368 position: absolute;
1369 left:30%;
1370 top:10%;
1371 width: 700px;
1372 height:70%;
1373 padding:10px;
1374 padding-top:20px;
1375 text-align:left;
1376 font-size:14px;
1377 font-weight:bold;
1378 background-color: #F0EEEE;
1379 border-radius: 4px;
1380 box-shadow: 0px -1px white, 0px 1px white, 0px 4px #949494, 0px 10px 20px rgba(0, 0, 0, 0.3);
1381 z-index:1200;
1382}
1383
1384.point-of-sale .popup-selection .button{
1385 float:right;
1386 width: 110px;
1387 height: 40px;
1388 line-height:40px;
1389 text-align:center;
1390 margin:3px;
1391 margin-top:10px;
1392 margin-right:50px;
1393
1394 font-size: 14px;
1395 font-weight: bold;
1396
1397 cursor: pointer;
1398
1399 border: 1px solid #cacaca;
1400 border-radius: 4px;
1401
1402 background: #e2e2e2;
1403 background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
1404 background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
1405 background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
1406 background: linear-gradient(#f0f0f0, #e2e2e2);
1407 -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
1408 -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
1409 box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
1410}
1411.point-of-sale .popup-selection .button:hover {
1412 color: white;
1413 background: #7f82ac;
1414 border: 1px solid #7f82ac;
1415 background: -webkit-linear-gradient(#9d9fc5, #7f82ac);
1416 background: -moz-linear-gradient(#9d9fc5, #7f82ac);
1417 background: -ms-linear-gradient(#9d9fc5, #7f82ac);
1418 background: linear-gradient(#9d9fc5, #7f82ac);
1419
1420 -webkit-transition-property: background, border;
1421 -webkit-transition-duration: 0.2s;
1422 -webkit-transition-timing-function: ease-out;
1423}
1424
1425/* customer selection */
1426
1427#customer-cancel {
1428 position: absolute;
1429 left: 10px;
1430 bottom: 10px;
1431}
1432
1433.point-of-sale .customer-list-container {
1434 position: absolute;
1435 top:70px;
1436 left:5px;
1437 right:5px;
1438 bottom: 70px;
1439 text-align: left;
1440 overflow: auto;
1441}
1442
1443.point-of-sale .customer-list-scroller {
1444 -webkit-box-sizing: border-box;
1445 -moz-box-sizing: border-box;
1446 -ms-box-sizing: border-box;
1447 box-sizing: border-box;
1448 width:98%;
1449}
1450
1451.customer-list-scroller ol {
1452 list-style: none;
1453}
1454
1455.customer-list-scroller ol li { }
1456
1457.customer-list-scroller ol li a {
1458 display:block;
1459 text-decoration:none;
1460 color:#000000;
1461 background-color:#FFFFFF;
1462 line-height:20px;
1463 border-bottom-style:solid;
1464 border-bottom-width:1px;
1465 border-bottom-color:#CCCCCC;
1466 padding-left:10px;
1467 cursor:pointer;
1468}
1469
1470.customer-list-scroller ol li a:hover {
1471 color:#FFFFFF;
1472 background-image:url(/point_of_sale/static/src/img/hover.png);
1473 background-repeat:repeat-x;
1474}
1475
1476.point-of-sale .customer {
1477 width: 100%;
1478}
1479
1480.point-of-sale .customer a {
1481 width: 100%;
1482}
1483
1484.point-of-sale .customer .customer-field {
1485 width: 130px;
1486 padding: 5px;
1487 margin: 5px;
1488}
1489
1490.point-of-sale .customer .customer-name{
1491 width: 200px;
1492 padding: 5px;
1493 margin: 5px;
1494}
1495
1496.point-of-sale .customer .customer-phone {
1497 width: 50px;
1498 padding: 5px;
1499 margin: 5px;
1500}
1501
1502.point-of-sale .customer-searchbox {
1503 right: 2px;
1504}
1505.point-of-sale .customer-searchbox input {
1506 width: 130px;
1507 border-radius: 11px;
1508 border: 1px solid #cecbcb;
1509 padding: 3px 19px;
1510 margin: 6px;
1511 background: url("../img/search.png") no-repeat 5px;
1512 background-color: white;
1513}
1514.point-of-sale .customer-search-clear {
1515 postion: absolute;
1516 top: 11px;
1517 right: 600px;
1518 cursor: pointer;
1519 display: none;
1520}
1521
1349/* ********* The ScrollBarWidget ********* */1522/* ********* The ScrollBarWidget ********* */
13501523
1351.point-of-sale .scrollbar{ 1524.point-of-sale .scrollbar{
13521525
=== added file 'point_of_sale/static/src/img/hover.png'
1353Binary 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-03-03 00:11:29 +0000 differ1526Binary 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-03-03 00:11:29 +0000 differ
=== modified file 'point_of_sale/static/src/js/db.js'
--- point_of_sale/static/src/js/db.js 2013-01-24 14:06:31 +0000
+++ point_of_sale/static/src/js/db.js 2013-03-03 00:11:29 +0000
@@ -53,7 +53,11 @@
53 this.category_search_string = {};53 this.category_search_string = {};
54 this.packagings_by_id = {};54 this.packagings_by_id = {};
55 this.packagings_by_product_id = {};55 this.packagings_by_product_id = {};
56<<<<<<< TREE
56 this.packagings_by_ean13 = {};57 this.packagings_by_ean13 = {};
58=======
59 this.customer_list_search_strings = '';
60>>>>>>> MERGE-SOURCE
57 },61 },
58 /* returns the category object from its id. If you pass a list of id as parameters, you get62 /* returns the category object from its id. If you pass a list of id as parameters, you get
59 * a list of category objects. 63 * a list of category objects.
@@ -150,6 +154,9 @@
150 if(product.ean13){154 if(product.ean13){
151 str += '|' + product.ean13;155 str += '|' + product.ean13;
152 }156 }
157 if(product.code){
158 str += '|' + product.code;
159 }
153 var packagings = this.packagings_by_product_id[product.id] || [];160 var packagings = this.packagings_by_product_id[product.id] || [];
154 for(var i = 0; i < packagings.length; i++){161 for(var i = 0; i < packagings.length; i++){
155 str += '|' + packagings[i].ean;162 str += '|' + packagings[i].ean;
@@ -209,6 +216,35 @@
209 }216 }
210 }217 }
211 },218 },
219 _customer_search_string: function(customer){
220 var str = '' + customer.id + ':' + customer.name;
221 if(customer.vat){
222 str += '|' + customer.vat;
223 }
224 if(customer.email){
225 str += '|' + customer.email;
226 }
227 if(customer.phone){
228 str += '|' + customer.phone;
229 }
230 if(customer.mobile){
231 str += '|' + customer.mobile;
232 }
233 return str + '\n';
234 },
235 add_customers: function(customers){
236 var stored_customers = this.load('customers',{});
237
238 if(!customers instanceof Array){
239 customers = [customers];
240 }
241 for(var i = 0, len = customers.length; i < len; i++){
242 var c = customers[i];
243 this.customer_list_search_strings += this._customer_search_string(c);
244 stored_customers[c.id] = c;
245 }
246 this.save('customers',stored_customers);
247 },
212 /* removes all the data from the database. TODO : being able to selectively remove data */248 /* removes all the data from the database. TODO : being able to selectively remove data */
213 clear: function(stores){249 clear: function(stores){
214 for(var i = 0, len = arguments.length; i < len; i++){250 for(var i = 0, len = arguments.length; i < len; i++){
@@ -238,6 +274,14 @@
238 }274 }
239 return undefined;275 return undefined;
240 },276 },
277 get_product_by_code: function(code){
278 var products = this.load('products', {});
279 for(var i in products){
280 if(products[i] && products[i].code === code){
281 return products[i];
282 }
283 }
284 },
241 get_product_by_category: function(category_id){285 get_product_by_category: function(category_id){
242 var product_ids = this.product_by_category_id[category_id];286 var product_ids = this.product_by_category_id[category_id];
243 var list = [];287 var list = [];
@@ -248,6 +292,17 @@
248 }292 }
249 return list;293 return list;
250 },294 },
295 get_customer_by_id: function(id){
296 return this.load('customers',{})[id];
297 },
298 get_all_customers: function(){
299 list = [];
300 stored_customers = this.load('customers',{});
301 for (var i in stored_customers) {
302 list.push(stored_customers[i]);
303 }
304 return list;
305 },
251 /* returns a list of products with :306 /* returns a list of products with :
252 * - a category that is or is a child of category_id,307 * - a category that is or is a child of category_id,
253 * - a name, package or ean13 containing the query (case insensitive) 308 * - a name, package or ean13 containing the query (case insensitive)
@@ -266,6 +321,23 @@
266 }321 }
267 return results;322 return results;
268 },323 },
324 /* returns a list of customers with :
325 * - a name, TIN, email, or phone/mobile containing the query (case insensitive)
326 */
327 search_customers: function(query){
328 var re = RegExp("([0-9]+):.*?"+query,"gi");
329 var results = [];
330 for(var i = 0; i < this.limit; i++){
331 r = re.exec(this.customer_list_search_strings);
332 if(r){
333 var id = Number(r[1]);
334 results.push(this.get_customer_by_id(id));
335 }else{
336 break;
337 }
338 }
339 return results;
340 },
269 add_order: function(order){341 add_order: function(order){
270 var last_id = this.load('last_order_id',0);342 var last_id = this.load('last_order_id',0);
271 var orders = this.load('orders',[]);343 var orders = this.load('orders',[]);
272344
=== modified file 'point_of_sale/static/src/js/devices.js'
--- point_of_sale/static/src/js/devices.js 2013-01-11 14:42:55 +0000
+++ point_of_sale/static/src/js/devices.js 2013-03-03 00:11:29 +0000
@@ -66,6 +66,18 @@
66 return this.message('scan_item_error_unrecognized',{ean: ean});66 return this.message('scan_item_error_unrecognized',{ean: ean});
67 },67 },
6868
69 //a product has been entered by keypad and recognized with success
70 // data is a parsed {product.code,qty,price} object
71 keypad_item_success: function(data){
72 return this.message('keypad_item_success',{data: data});
73 },
74
75 // a product has been entered by keypad but not recognized
76 // data is a parsed {product.code,qty,price} object
77 keypad_item_error_unrecognized: function(data){
78 return this.message('keypad_item_error_unrecognized',{data: data});
79 },
80
69 //the client is asking for help81 //the client is asking for help
70 help_needed: function(){82 help_needed: function(){
71 return this.message('help_needed');83 return this.message('help_needed');
@@ -472,5 +484,172 @@
472 $('body').undelegate('', 'keyup')484 $('body').undelegate('', 'keyup')
473 },485 },
474 });486 });
487
488 // this module mimics a keypad-only cash register. Use connect() and
489 // disconnect() to activate and deactivate it. Use set_action_callback to
490 // tell it what to do when the cashier enters product data(qty, price, etc).
491 module.Keypad = instance.web.Class.extend({
492 init: function(attributes){
493 this.pos = attributes.pos;
494 this.action_callback = undefined;
495 this.saved_callback_stack = [];
496 },
497
498 save_callback: function(){
499 this.saved_callback_stack.push(this.action_callback);
500 },
501
502 restore_callback: function(){
503 if (this.saved_callback_stack.length > 0) {
504 this.action_callback = this.saved_callback_stack.pop();
505 }
506 },
507
508 set_action_callback: function(callback){
509 this.action_callback = callback
510 },
511
512 //remove action callback
513 reset_action_callback: function(){
514 this.action_callback = undefined;
515 },
516
517 reset_parse_result: function(parse_result) {
518 parse_result.code = 0;
519 parse_result.qty = 0;
520 parse_result.priceOverride = false;
521 parse_result.price = 0.00;
522 parse_result.discount = 0.00;
523 parse_result.void_last_line = false;
524 },
525
526 copy_parse_result: function(src) {
527 var dst = {
528 code: null,
529 qty: 1,
530 priceOverride: false,
531 price: 0.00,
532 discount: 0.00,
533 void_last_line: false,
534 };
535 dst.code = src.code;
536 dst.qty = src.qty;
537 dst.priceOverride = src.priceOverride;
538 dst.price = src.price;
539 dst.discount = src.discount;
540 dst.void_last_line = src.void_last_line;
541 return dst;
542 },
543
544 // starts catching keyboard events and tries to interpret keystrokes,
545 // calling the callback when needed.
546 connect: function(){
547 var self = this;
548 var KC_PLU = 107; // KeyCode: Product Code (Keypad '+')
549 var KC_QTY = 111; // KeyCode: Quantity (Keypad '/')
550 var KC_AMT = 106; // KeyCode: Price (Keypad '*')
551 var KC_DISC = 109; // KeyCode: Discount Percentage [0..100] (Keypad '-')
552 var KC_VOID = 13; // KeyCode: Void current line (Keyboard/Keypad Enter key)
553 var KC_CLR1 = 46; // KeyCode: Clear last line of order (Keyboard Delete key)
554 var KC_CLR2 = 8; // KeyCode: Clear current line (Keyboard Backspace key)
555 var codeNumbers = [];
556 var codeChars = [];
557 var parse_result = {
558 code: null,
559 qty: 1,
560 priceOverride: false,
561 price: 0.00,
562 discount: 0.00,
563 void_last_line: false,
564 };
565 var kc_lookup = {
566 96: '0',
567 97: '1',
568 98: '2',
569 99: '3',
570 100: '4',
571 101: '5',
572 102: '6',
573 103: '7',
574 104: '8',
575 105: '9',
576 106: '*',
577 107: '+',
578 109: '-',
579 110: '.',
580 111: '/',
581 };
582
583 // Catch keyup events anywhere in the POS interface. Barcode reader also does this, but won't interfere
584 // because it looks for a specific timing between keyup events. On the plus side this should mean that you
585 // can use both the keypad and the barcode reader during the same session (but for separate order lines).
586 // This could be useful in cases where the scanner can't read the barcode.
587 $('body').delegate('','keyup', function (e){
588 console.log('keyup:'+String.fromCharCode(e.keyCode)+' '+e.keyCode,e);
589 //We only care about numbers and modifiers
590 token = e.keyCode;
591 if ((token >= 96 && token <= 111) || token === KC_PLU || token === KC_QTY || token === KC_AMT) {
592
593 if (token === KC_PLU) {
594 parse_result.code = codeChars.join('');
595 var res = self.copy_parse_result(parse_result);
596 codeNumbers = [];
597 codeChars = [];
598 self.reset_parse_result(parse_result);
599 console.log('PLU token: code:'+res.code+', qty:'+res.qty+', price:'+res.price+', discount:'+res.discount);
600 self.action_callback(res);
601 } else if (token === KC_QTY) {
602 parse_result.qty = parseInt(codeChars.join(''));
603 codeNumbers = [];
604 codeChars = [];
605 console.log('QTY token: qty:'+parse_result.qty);
606 } else if (token === KC_AMT) {
607 parse_result.price = parseFloat(codeChars.join('')).toFixed(2);
608 parse_result.priceOverride = true;
609 codeNumbers = [];
610 codeChars = [];
611 console.log('AMT token: price:'+parse_result.price);
612 } else if (token === KC_DISC) {
613 parse_result.discount = parseFloat(codeChars.join(''));
614 codeNumbers = [];
615 codeChars = [];
616 console.log('DISC token: discount:'+parse_result.discount);
617 } else {
618 codeNumbers.push(token - 48);
619 codeChars.push(kc_lookup[token]);
620 }
621 } else if (token === KC_VOID) {
622 /*
623 * This is commented out for now. We don't want to interfere with
624 * the 'Enter' keycode used by the barcode reader to signify a scan.
625 */
626 // Void the last line of the order only if there isn't another line in pregress.
627// if (codeNumbers.length === 0) {
628// parse_result.void_last_line = true;
629// var res = self.copy_parse_result(parse_result);
630// codeNumbers = [];
631// codeChars = [];
632// self.reset_parse_result(parse_result);
633// console.log('VOID token:'+res.void_last_line);
634// self.action_callback(res);
635// }
636 } else {
637 // For now pressing Backspace or Delete just defaults to doing nothing.
638 // In the future we might want it to display a popup or something.
639 if (token === KC_CLR1 || token === KC_CLR2) {
640 ;
641 }
642 codeNumbers = [];
643 codeChars = [];
644 self.reset_parse_result(parse_result);
645 }
646 });
647 },
648
649 // stops catching keyboard events
650 disconnect: function(){
651 $('body').undelegate('', 'keyup')
652 },
653 });
475654
476}655}
477656
=== modified file 'point_of_sale/static/src/js/models.js'
--- point_of_sale/static/src/js/models.js 2013-01-28 17:18:00 +0000
+++ point_of_sale/static/src/js/models.js 2013-03-03 00:11:29 +0000
@@ -39,10 +39,17 @@
39 this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time39 this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
4040
41 this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes41 this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
42 this.keypad = new module.Keypad({'pos': this}); // used to simulate a cash register keypad
42 this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy43 this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
43 this.db = new module.PosLS(); // a database used to store the products and categories44 this.db = new module.PosLS(); // a database used to store the products and categories
45<<<<<<< TREE
44 this.db.clear('products','categories');46 this.db.clear('products','categories');
45 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode 47 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
48=======
49 this.db.clear('products','categories','customers');
50 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
51
52>>>>>>> MERGE-SOURCE
4653
47 // default attributes values. If null, it will be loaded below.54 // default attributes values. If null, it will be loaded below.
48 this.set({55 this.set({
@@ -59,6 +66,7 @@
59 'orders': new module.OrderCollection(),66 'orders': new module.OrderCollection(),
60 //this is the product list as seen by the product list widgets, it will change based on the category filters67 //this is the product list as seen by the product list widgets, it will change based on the category filters
61 'products': new module.ProductCollection(), 68 'products': new module.ProductCollection(),
69 'customers': new module.CustomerCollection(),
62 'cashRegisters': null, 70 'cashRegisters': null,
6371
64 'bank_statements': null,72 'bank_statements': null,
@@ -144,6 +152,10 @@
144 }).then(function(partners){152 }).then(function(partners){
145 self.set('partner_list',partners);153 self.set('partner_list',partners);
146154
155 return self.fetch('res.partner', ['name','vat','email','phone','mobile'], [['customer', '=', true]]);
156 }).then(function(customers){
157 self.db.add_customers(customers);
158
147 return self.fetch('account.tax', ['amount', 'price_include', 'type']);159 return self.fetch('account.tax', ['amount', 'price_include', 'type']);
148 }).then(function(taxes){160 }).then(function(taxes){
149 self.set('taxes', taxes);161 self.set('taxes', taxes);
@@ -187,7 +199,7 @@
187199
188 return self.fetch(200 return self.fetch(
189 'product.product', 201 'product.product',
190 ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 202 ['name', 'code', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13',
191 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description'],203 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description'],
192 [['sale_ok','=',true],['available_in_pos','=',true]],204 [['sale_ok','=',true],['available_in_pos','=',true]],
193 {pricelist: self.get('shop').pricelist_id[0]} // context for price205 {pricelist: self.get('shop').pricelist_id[0]} // context for price
@@ -327,6 +339,46 @@
327 }339 }
328 return true;340 return true;
329 },341 },
342
343 keypad_enter_product: function(parsed_data){
344 var self = this;
345 var doMerge = true;
346 var product = this.db.get_product_by_code(parsed_data.code);
347 var selectedOrder = this.get('selectedOrder');
348
349 if (parsed_data.void_last_line) {
350 line = selectedOrder.getLastOrderline();
351 if (line) {
352 line.set_quantity(Number.NaN);
353 }
354 return true;
355 }
356
357 if (!product){
358 return false;
359 }
360
361 if (parsed_data.discount > 0) {
362 doMerge = false;
363 }
364
365 if (!parsed_data.priceOverride) {
366 parsed_data.price = product.price;
367 }
368
369// if (product.get('to_weight') && self.pos.iface_electronic_scale) {
370// self.pos_widget.screen_selector.set_current_screen(self.scale_screen, {product: product});
371// } else {
372 selectedOrder.addProduct(new module.Product(product), { quantity: parsed_data.qty,
373 price: parsed_data.price,
374 merge: doMerge});
375// }
376 if (!doMerge){
377 selectedOrder.selected_orderline.set_discount(parsed_data.discount);
378 }
379 self.get('products').reset(product);
380 return true;
381 },
330 });382 });
331383
332 module.CashRegister = Backbone.Model.extend({384 module.CashRegister = Backbone.Model.extend({
@@ -742,7 +794,7 @@
742 this.get('paymentLines').each(function(paymentline){794 this.get('paymentLines').each(function(paymentline){
743 paymentlines.push(paymentline.export_for_printing());795 paymentlines.push(paymentline.export_for_printing());
744 });796 });
745 var client = this.get('client');797 var client = this.pos.get('selectedOrder').get('client');
746 var cashier = this.pos.get('cashier') || this.pos.get('user');798 var cashier = this.pos.get('cashier') || this.pos.get('user');
747 var company = this.pos.get('company');799 var company = this.pos.get('company');
748 var shop = this.pos.get('shop');800 var shop = this.pos.get('shop');
@@ -759,7 +811,7 @@
759 total_discount: this.getDiscountTotal(),811 total_discount: this.getDiscountTotal(),
760 change: this.getChange(),812 change: this.getChange(),
761 name : this.getName(),813 name : this.getName(),
762 client: client ? client.name : null ,814 client: client ? client : null ,
763 invoice_id: null, //TODO815 invoice_id: null, //TODO
764 cashier: cashier ? cashier.name : null,816 cashier: cashier ? cashier.name : null,
765 date: { 817 date: {
@@ -795,6 +847,7 @@
795 (this.get('paymentLines')).each(_.bind( function(item) {847 (this.get('paymentLines')).each(_.bind( function(item) {
796 return paymentLines.push([0, 0, item.export_as_JSON()]);848 return paymentLines.push([0, 0, item.export_as_JSON()]);
797 }, this));849 }, this));
850 var client = this.pos.get('selectedOrder').get('client');
798 return {851 return {
799 name: this.getName(),852 name: this.getName(),
800 amount_paid: this.getPaidTotal(),853 amount_paid: this.getPaidTotal(),
@@ -804,7 +857,7 @@
804 lines: orderLines,857 lines: orderLines,
805 statement_ids: paymentLines,858 statement_ids: paymentLines,
806 pos_session_id: this.pos.get('pos_session').id,859 pos_session_id: this.pos.get('pos_session').id,
807 partner_id: this.pos.get('client') ? this.pos.get('client').id : undefined,860 partner_id: client ? client.id : undefined,
808 user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,861 user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,
809 };862 };
810 },863 },
@@ -894,4 +947,12 @@
894 this.set({buffer:'0'});947 this.set({buffer:'0'});
895 },948 },
896 });949 });
950
951 module.Customer = Backbone.Model.extend({
952 });
953
954 module.CustomerCollection = Backbone.Collection.extend({
955 model: module.Customer,
956 });
957
897}958}
898959
=== modified file 'point_of_sale/static/src/js/screens.js'
--- point_of_sale/static/src/js/screens.js 2013-01-29 14:01:46 +0000
+++ point_of_sale/static/src/js/screens.js 2013-03-03 00:11:29 +0000
@@ -222,6 +222,20 @@
222 }222 }
223 },223 },
224224
225 // what happens when a product is entered by keypad emulator :
226 // it will add the product to the order.
227 keypad_product_action: function(data){
228 var self = this;
229 if(self.pos.keypad_enter_product(data)){
230 self.pos.proxy.keypad_item_success(data);
231 }else{
232 self.pos.proxy.keypad_item_error_unrecognized(data);
233 if(self.product_error_popup && self.pos_widget.screen_selector.get_user_mode() === 'cashier'){
234 self.pos_widget.screen_selector.show_popup(self.product_error_popup);
235 }
236 }
237 },
238
225 // shows an action bar on the screen. The actionbar is automatically shown when you add a button239 // shows an action bar on the screen. The actionbar is automatically shown when you add a button
226 // with add_action_button()240 // with add_action_button()
227 show_action_bar: function(){241 show_action_bar: function(){
@@ -283,8 +297,10 @@
283 this.pos_widget.client_button.hide();297 this.pos_widget.client_button.hide();
284 }298 }
285 if(cashier_mode){299 if(cashier_mode){
300 this.pos_widget.select_customer_button.show();
286 this.pos_widget.close_button.show();301 this.pos_widget.close_button.show();
287 }else{302 }else{
303 this.pos_widget.select_customer_button.hide();
288 this.pos_widget.close_button.hide();304 this.pos_widget.close_button.hide();
289 }305 }
290 306
@@ -296,11 +312,16 @@
296 'client' : self.barcode_client_action ? function(ean){ self.barcode_client_action(ean); } : undefined ,312 'client' : self.barcode_client_action ? function(ean){ self.barcode_client_action(ean); } : undefined ,
297 'discount': self.barcode_discount_action ? function(ean){ self.barcode_discount_action(ean); } : undefined,313 'discount': self.barcode_discount_action ? function(ean){ self.barcode_discount_action(ean); } : undefined,
298 });314 });
315
316 this.pos.keypad.set_action_callback(function(data){ self.keypad_product_action(data); });
299 },317 },
300318
301 // this method is called when the screen is closed to make place for a new screen. this is a good place319 // this method is called when the screen is closed to make place for a new screen. this is a good place
302 // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()320 // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
303 close: function(){321 close: function(){
322 if(this.pos.keypad){
323 this.pos.keypad.reset_action_callback();
324 }
304 if(this.pos.barcode_reader){325 if(this.pos.barcode_reader){
305 this.pos.barcode_reader.reset_action_callbacks();326 this.pos.barcode_reader.reset_action_callbacks();
306 }327 }
@@ -400,6 +421,7 @@
400 this._super();421 this._super();
401 this.pos.proxy.help_needed();422 this.pos.proxy.help_needed();
402 this.pos.proxy.scan_item_error_unrecognized();423 this.pos.proxy.scan_item_error_unrecognized();
424 this.pos.proxy.keypad_item_error_unrecognized();
403425
404 this.pos.barcode_reader.save_callbacks();426 this.pos.barcode_reader.save_callbacks();
405 this.pos.barcode_reader.reset_action_callbacks();427 this.pos.barcode_reader.reset_action_callbacks();
@@ -410,6 +432,8 @@
410 self.pos_widget.screen_selector.set_user_mode('cashier');432 self.pos_widget.screen_selector.set_user_mode('cashier');
411 },433 },
412 });434 });
435 this.pos.keypad.save_callback();
436 this.pos.keypad.reset_action_callback();
413 this.$('.footer .button').off('click').click(function(){437 this.$('.footer .button').off('click').click(function(){
414 self.pos_widget.screen_selector.close_popup();438 self.pos_widget.screen_selector.close_popup();
415 });439 });
@@ -417,6 +441,7 @@
417 close:function(){441 close:function(){
418 this._super();442 this._super();
419 this.pos.proxy.help_canceled();443 this.pos.proxy.help_canceled();
444 this.pos.keypad.restore_callback();
420 this.pos.barcode_reader.restore_callbacks();445 this.pos.barcode_reader.restore_callbacks();
421 },446 },
422 });447 });
@@ -797,10 +822,12 @@
797 this.user = this.pos.get('user');822 this.user = this.pos.get('user');
798 this.company = this.pos.get('company');823 this.company = this.pos.get('company');
799 this.shop_obj = this.pos.get('shop');824 this.shop_obj = this.pos.get('shop');
825 this.client = null;
800 },826 },
801 renderElement: function() {827 renderElement: function() {
802 this._super();828 this._super();
803 this.pos.bind('change:selectedOrder', this.change_selected_order, this);829 this.pos.bind('change:selectedOrder', this.change_selected_order, this);
830 this.pos.get('selectedOrder').bind('change:client', this.change_client, this);
804 this.change_selected_order();831 this.change_selected_order();
805 },832 },
806 show: function(){833 show: function(){
@@ -840,6 +867,10 @@
840 this.currentPaymentLines.bind('all', this.refresh, this);867 this.currentPaymentLines.bind('all', this.refresh, this);
841 this.refresh();868 this.refresh();
842 },869 },
870 change_client: function() {
871 this.client = this.pos.get('selectedOrder').get('client');
872 this.refresh();
873 },
843 refresh: function() {874 refresh: function() {
844 this.currentOrder = this.pos.get('selectedOrder');875 this.currentOrder = this.pos.get('selectedOrder');
845 $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));876 $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));
@@ -1006,4 +1037,82 @@
1006 this.currentPaymentLines.last().set_amount(val);1037 this.currentPaymentLines.last().set_amount(val);
1007 },1038 },
1008 });1039 });
1040<<<<<<< TREE
1041=======
1042
1043 module.SelectCustomerPopupWidget = module.PopUpWidget.extend({
1044 template:'SelectCustomerPopupWidget',
1045
1046 start: function(){
1047 this._super();
1048 var self = this;
1049 this.customer_list_widget = new module.CustomerListWidget(this,{
1050 click_customer_action: function(customer){
1051 this.pos.get('selectedOrder').set_client(customer);
1052 this.pos_widget.customername.refresh();
1053 this.pos_widget.screen_selector.set_current_screen('products');
1054 },
1055 });
1056 },
1057
1058 show: function(){
1059 this._super();
1060 var self = this;
1061 this.renderElement();
1062
1063 this.customer_list_widget.replace($('.placeholder-CustomerListWidget'));
1064
1065 this.$('.button.cancel').off('click').click(function(){
1066 self.pos_widget.screen_selector.set_current_screen('products');
1067 });
1068
1069 this.customer_search();
1070 },
1071
1072 // Customer search filter
1073 customer_search: function(){
1074 var self = this;
1075
1076 // find all products belonging to the current category
1077 var customers = this.pos.db.get_all_customers();
1078 self.pos.get('customers').reset(customers);
1079
1080 // filter customers according to the search string
1081 this.$('.customer-searchbox input').keyup(function(event){
1082 query = $(this).val().toLowerCase();
1083 if(query){
1084 var customers = self.pos.db.search_customers(query);
1085 self.pos.get('customers').reset(customers);
1086 self.$('.customer-search-clear').fadeIn();
1087 if(event.keyCode == 13){
1088 var c = null;
1089 if(customers.length == 1){
1090 c = self.pos.get('customers').get(customers[0]);
1091 }
1092 if(c !== null){
1093 self.pos_widget.select_customer_popup.customer_list_widget.click_customer_action(c);
1094 self.$('.customer-search-clear').trigger('click');
1095 }
1096 }
1097 }else{
1098 var customers = self.pos.db.get_all_customers();
1099 self.pos.get('customers').reset(customers);
1100 self.$('.customer-search-clear').fadeOut();
1101 }
1102 });
1103
1104 this.$('.customer-searchbox input').click(function(){}); //Why ???
1105
1106 //reset the search when clicking on reset
1107 this.$('.customer-search-clear').click(function(){
1108 var customers = self.pos.db.get_all_customers();
1109 self.pos.get('customers').reset(customers);
1110 self.$('.customer-searchbox input').val('').focus();
1111 self.$('.customer-search-clear').fadeOut();
1112 });
1113 },
1114
1115 });
1116
1117>>>>>>> MERGE-SOURCE
1009}1118}
10101119
=== modified file 'point_of_sale/static/src/js/widgets.js'
--- point_of_sale/static/src/js/widgets.js 2013-01-29 14:01:46 +0000
+++ point_of_sale/static/src/js/widgets.js 2013-03-03 00:11:29 +0000
@@ -564,12 +564,31 @@
564 self.pos.get('products').reset(products);564 self.pos.get('products').reset(products);
565565
566 // filter the products according to the search string566 // filter the products according to the search string
567 this.$('.searchbox input').keyup(function(){567 this.$('.searchbox input').keyup(function(event){
568 query = $(this).val().toLowerCase();568 query = $(this).val().toLowerCase();
569 if(query){569 if(query){
570 var products = self.pos.db.search_product_in_category(self.category.id, query);570 var products = self.pos.db.search_product_in_category(self.category.id, query);
571 self.pos.get('products').reset(products);571 self.pos.get('products').reset(products);
572 self.$('.search-clear').fadeIn();572 self.$('.search-clear').fadeIn();
573 if(event.keyCode == 13){
574 var p = null;
575 if(products.length > 1){
576 i = 0;
577 while(i < products.length){
578 if (products[i].code == query){
579 p = self.pos.get('products').get(products[i]);
580 break;
581 }
582 i++;
583 }
584 }else if(products.length == 1){
585 p = self.pos.get('products').get(products[0]);
586 }
587 if(p !== null){
588 self.pos_widget.product_screen.product_list_widget.click_product_action(p);
589 self.$('.search-clear').trigger('click');
590 }
591 }
573 }else{592 }else{
574 var products = self.pos.db.get_product_by_category(self.category.id);593 var products = self.pos.db.get_product_by_category(self.category.id);
575 self.pos.get('products').reset(products);594 self.pos.get('products').reset(products);
@@ -672,6 +691,27 @@
672 },691 },
673 });692 });
674693
694 module.CustomernameWidget = module.PosBaseWidget.extend({
695 template: 'CustomernameWidget',
696 init: function(parent, options){
697 var options = options || {};
698 this._super(parent,options);
699 this.pos.bind('change:selectedOrder', this.renderElement, this);
700 },
701 refresh: function(){
702 this.renderElement();
703 },
704 get_name: function(){
705 var user;
706 customer = this.pos.get('selectedOrder').get_client();
707 if(customer){
708 return customer.name;
709 }else{
710 return "";
711 }
712 },
713 });
714
675 module.HeaderButtonWidget = module.PosBaseWidget.extend({715 module.HeaderButtonWidget = module.PosBaseWidget.extend({
676 template: 'HeaderButtonWidget',716 template: 'HeaderButtonWidget',
677 init: function(parent, options){717 init: function(parent, options){
@@ -867,6 +907,8 @@
867 window.screen_selector = self.screen_selector;907 window.screen_selector = self.screen_selector;
868908
869 self.pos.barcode_reader.connect();909 self.pos.barcode_reader.connect();
910
911 self.pos.keypad.connect();
870912
871 instance.webclient.set_content_full_screen(true);913 instance.webclient.set_content_full_screen(true);
872914
@@ -939,6 +981,9 @@
939981
940 this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});982 this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
941 this.error_negative_price_popup.appendTo($('.point-of-sale'));983 this.error_negative_price_popup.appendTo($('.point-of-sale'));
984
985 this.select_customer_popup = new module.SelectCustomerPopupWidget(this, {});
986 this.select_customer_popup.appendTo($('.point-of-sale'));
942987
943 // -------- Misc ---------988 // -------- Misc ---------
944989
@@ -980,6 +1025,15 @@
980 });1025 });
981 this.client_button.appendTo(this.$('#rightheader'));1026 this.client_button.appendTo(this.$('#rightheader'));
9821027
1028 this.select_customer_button = new module.HeaderButtonWidget(this,{
1029 label:'Select Customer',
1030 action: function(){ self.screen_selector.show_popup('select-customer'); },
1031 });
1032 this.select_customer_button.appendTo(this.$('#rightheader'));
1033
1034 this.customername = new module.CustomernameWidget(this,{});
1035 this.customername.appendTo(this.$('#rightheader'));
1036
983 1037
984 // -------- Screen Selector ---------1038 // -------- Screen Selector ---------
9851039
@@ -1001,6 +1055,7 @@
1001 'error-session': this.error_session_popup,1055 'error-session': this.error_session_popup,
1002 'error-negative-price': this.error_negative_price_popup,1056 'error-negative-price': this.error_negative_price_popup,
1003 'choose-receipt': this.choose_receipt_popup,1057 'choose-receipt': this.choose_receipt_popup,
1058 'select-customer': this.select_customer_popup,
1004 },1059 },
1005 default_client_screen: 'welcome',1060 default_client_screen: 'welcome',
1006 default_cashier_screen: 'products',1061 default_cashier_screen: 'products',
@@ -1087,6 +1142,7 @@
1087 close: function() {1142 close: function() {
1088 var self = this;1143 var self = this;
1089 this.pos.barcode_reader.disconnect();1144 this.pos.barcode_reader.disconnect();
1145 this.pos.keypad.disconnect();
1090 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(1146 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
1091 _.bind(function(res) {1147 _.bind(function(res) {
1092 return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {1148 return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
@@ -1103,4 +1159,60 @@
1103 this._super();1159 this._super();
1104 }1160 }
1105 });1161 });
1162
1163 module.CustomerWidget = module.PosBaseWidget.extend({
1164 template: 'CustomerWidget',
1165 init: function(parent, options) {
1166 this._super(parent,options);
1167 this.model = options.model;
1168 this.click_customer_action = options.click_customer_action;
1169 },
1170 renderElement: function() {
1171 this._super();
1172 var self = this;
1173 $("a", this.$el).click(function(e){
1174 if(self.click_customer_action){
1175 self.click_customer_action(self.model.toJSON());
1176 }
1177 });
1178 },
1179 });
1180
1181 module.CustomerListWidget = module.ScreenWidget.extend({
1182 template:'CustomerListWidget',
1183 init: function(parent, options) {
1184 var self = this;
1185 this._super(parent,options);
1186 this.model = options.model;
1187 this.customer_list = [];
1188 this.next_screen = options.next_screen || false;
1189 this.click_customer_action = options.click_customer_action;
1190
1191 var customers = self.pos.db.get_all_customers();
1192 self.pos.get('customers').reset(customers);
1193 this.pos.get('customers').bind('reset', function(){
1194 self.renderElement();
1195 });
1196 },
1197 renderElement: function() {
1198 var self = this;
1199 this._super();
1200 this.customer_list = [];
1201
1202 this.pos.get('customers')
1203 .chain()
1204 .map(function(customer) {
1205 var customer = new module.CustomerWidget(self, {
1206 model: customer,
1207 next_screen: 'products',
1208 click_customer_action: self.click_customer_action,
1209 })
1210 self.customer_list.push(customer);
1211 return customer;
1212 })
1213 .invoke('appendTo', this.$('.customer-list'));
1214
1215 },
1216 });
1217
1106}1218}
11071219
=== modified file 'point_of_sale/static/src/xml/pos.xml'
--- point_of_sale/static/src/xml/pos.xml 2013-01-28 17:18:00 +0000
+++ point_of_sale/static/src/xml/pos.xml 2013-03-03 00:11:29 +0000
@@ -375,6 +375,9 @@
375 <a href="#">375 <a href="#">
376 <div class="product-img">376 <div class="product-img">
377 <img src='' /> <!-- the product thumbnail -->377 <img src='' /> <!-- the product thumbnail -->
378 <span class="code-tag">
379 <t t-esc="widget.model.get('code')"/>
380 </span>
378 <t t-if="!widget.model.get('to_weight')">381 <t t-if="!widget.model.get('to_weight')">
379 <span class="price-tag">382 <span class="price-tag">
380 <t t-esc="widget.format_currency(widget.model.get('price'))"/>383 <t t-esc="widget.format_currency(widget.model.get('price'))"/>
@@ -553,6 +556,12 @@
553 </span>556 </span>
554 </t>557 </t>
555558
559 <t t-name="CustomernameWidget">
560 <span class="customername">
561 <t t-esc="widget.get_name()" />
562 </span>
563 </t>
564
556 <t t-name="PosTicket">565 <t t-name="PosTicket">
557 <div class="pos-sale-ticket">566 <div class="pos-sale-ticket">
558 567
@@ -560,9 +569,18 @@
560 Date.CultureInfo.formatPatterns.longTime)"/> <t t-esc="widget.currentOrder.attributes.name"/></div>569 Date.CultureInfo.formatPatterns.longTime)"/> <t t-esc="widget.currentOrder.attributes.name"/></div>
561 <br />570 <br />
562 <t t-esc="widget.company.name"/><br />571 <t t-esc="widget.company.name"/><br />
572 <t t-if="widget.company.vat">
573 TIN: <t t-esc="widget.company.vat"/><br />
574 </t>
563 Phone: <t t-esc="widget.company.phone || ''"/><br />575 Phone: <t t-esc="widget.company.phone || ''"/><br />
564 User: <t t-esc="widget.user.name"/><br />576 User: <t t-esc="widget.user.name"/><br />
565 Shop: <t t-esc="widget.shop_obj.name"/><br />577 Shop: <t t-esc="widget.shop_obj.name"/><br />
578 <t t-if="widget.client">
579 Customer: <t t-esc="widget.client.name"/><br />
580 <t t-if="widget.client.vat">
581 Customer TIN: <t t-esc="widget.client.vat"/><br />
582 </t>
583 </t>
566 <br />584 <br />
567 <table>585 <table>
568 <tr t-foreach="widget.currentOrderLines.toArray()" t-as="orderline">586 <tr t-foreach="widget.currentOrderLines.toArray()" t-as="orderline">
@@ -747,4 +765,66 @@
747 </div>765 </div>
748 </t>766 </t>
749767
768 <t t-name="CustomerListWidget">
769 <div class='customer-list-container'>
770 <div class="customer-list-scroller">
771 <ol id="customers-screen-ol" class="customer-list">
772 </ol>
773 </div>
774 </div>
775 </t>
776
777 <t t-name="SelectCustomerPopupWidget">
778 <div class="modal-dialog">
779 <div class="popup-selection">
780 <div class="customer-title">
781 Customer Selection
782 </div>
783
784 <div class="customer-searchbox">
785 <input placeholder="Search Customers" />
786 <img class="customer-search-clear" src="/point_of_sale/static/src/img/search_reset.gif" />
787 </div>
788
789 <div class="content-container">
790 <span class="placeholder-CustomerListWidget" />
791 </div>
792
793 <div id="customer-cancel" class = "button cancel">
794 Cancel
795 </div>
796 </div>
797 </div>
798 </t>
799
800 <t t-name="CustomerWidget">
801 <li class='customer'>
802 <a href="#">
803 <span class="customer-field customer-name">
804 <t t-esc="widget.model.get('name')"/>
805 </span>
806 <span class="customer-field">
807 <t t-if="widget.model.get('vat')">
808 <t t-esc="widget.model.get('vat')"/>
809 </t>
810 </span>
811 <span class="customer-field">
812 <t t-if="widget.model.get('email')">
813 <t t-esc="widget.model.get('email')"/>
814 </t>
815 </span>
816 <span class="customer-field customer-phone">
817 <t t-if="widget.model.get('phone')">
818 <t t-esc="widget.model.get('phone')"/>
819 </t>
820 </span>
821 <span class="customer-field customer-phone">
822 <t t-if="widget.model.get('mobile')">
823 <t t-esc="widget.model.get('mobile')"/>
824 </t>
825 </span>
826 </a>
827 </li>
828 </t>
829
750</templates>830</templates>

Subscribers

People subscribed via source and target branches