Ajout : point_of_sale.patch .
[burette/pos_membership.git] / point_of_sale.patch
1 diff --git a/controllers/main.py b/controllers/main.py
2 index b189c8f..d3743ac 100644
3 --- a/controllers/main.py
4 +++ b/controllers/main.py
5 @@ -29,7 +29,6 @@ class PointOfSaleController(openerp.addons.web.http.Controller):
6 def manifest(self, req, **kwargs):
7 """ This generates a HTML5 cache manifest files that preloads the categories and products thumbnails
8 and other ressources necessary for the point of sale to work offline """
9 -
10 ml = ["CACHE MANIFEST"]
11
12 # loading all the images in the static/src/img/* directories
13 @@ -56,10 +55,17 @@ class PointOfSaleController(openerp.addons.web.http.Controller):
14 category_id = c['id']
15 url = "/web/binary/image?session_id=%s&model=pos.category&field=image&id=%s" % (req.session_id, category_id)
16 ml.append(url)
17 +
18 + partners = req.session.model('res.partner')
19 + for c in partners.search_read([],['name']):
20 + partner_id = c['id']
21 + url = "/web/binary/image?session_id=%s&model=res.partner&field=image&id=%s" % (req.session_id, partner_id)
22 + ml.append(url)
23
24 ml += ["NETWORK:","*"]
25 m = "\n".join(ml)
26
27 + print ("DEV: [point_of_sale] [controllers] [main] return=%s" % m)
28 return m
29
30 @openerp.addons.web.http.jsonrequest
31 diff --git a/point_of_sale.py b/point_of_sale.py
32 index 7e8487b..371a07b 100644
33 --- a/point_of_sale.py
34 +++ b/point_of_sale.py
35 @@ -479,8 +479,22 @@ class pos_order(osv.osv):
36 _description = "Point of Sale"
37 _order = "id desc"
38
39 + def create_partner_from_ui(self, cr, uid, partners, context=None):
40 + print ("DEV: [point_of_sale] [create_partner_from_ui] partners=%s" % str(partners))
41 + partner_ids = []
42 + partner_obj = self.pool.get('res.partner')
43 + for tmp_partner in partners:
44 + partner = tmp_partner['data']
45 + partner_id = partner_obj.create(cr, uid, {
46 + 'name': partner['name'],
47 + }, context)
48 + partner_ids.append(partner_id)
49 + #self.signal_paid(cr, uid, [partner_id])
50 + return partner_ids
51 +
52 def create_from_ui(self, cr, uid, orders, context=None):
53 #_logger.info("orders: %r", orders)
54 + print ("DEV: [point_of_sale] [create_from_ui] order=", str(orders))
55 order_ids = []
56 for tmp_order in orders:
57 order = tmp_order['data']
58 @@ -489,7 +503,8 @@ class pos_order(osv.osv):
59 'user_id': order['user_id'] or False,
60 'session_id': order['pos_session_id'],
61 'lines': order['lines'],
62 - 'pos_reference':order['name']
63 + 'pos_reference':order['name'],
64 + 'partner_id':order['partner_id'],
65 }, context)
66
67 for payments in order['statement_ids']:
68 diff --git a/static/src/css/pos.css b/static/src/css/pos.css
69 index d9a4d6d..9357d5d 100644
70 --- a/static/src/css/pos.css
71 +++ b/static/src/css/pos.css
72 @@ -619,6 +619,200 @@
73 padding-top:15px;
74 }
75
76 +/* ********* The partner list ********* */
77 +
78 +.point-of-sale .partner-list {
79 + padding:10px !important;
80 +}
81 +
82 +.point-of-sale .partner-list-scroller{
83 + -webkit-box-sizing: border-box;
84 + -moz-box-sizing: border-box;
85 + -ms-box-sizing: border-box;
86 + box-sizing: border-box;
87 + width:100%;
88 + height:100%;
89 + overflow: hidden;
90 +}
91 +.point-of-sale .partner-list-container {
92 + position:absolute;
93 + top:0px;
94 + bottom:0px;
95 + left:0px;
96 + right:0px;
97 +}
98 +
99 +/* a) the search box */
100 +
101 +.point-of-sale #partners-screen .searchbox {
102 + position: absolute;
103 + right: 2px;
104 +}
105 +.point-of-sale #partners-screen .searchbox input {
106 + width: 130px;
107 + border-radius: 11px;
108 + border: 1px solid #cecbcb;
109 + padding: 3px 19px;
110 + margin: 6px;
111 + background: url("../img/search.png") no-repeat 5px;
112 + background-color: white;
113 +}
114 +.point-of-sale #partners-screen .search-clear {
115 + position: absolute;
116 + top: 11px;
117 + right: 11px;
118 + cursor: pointer;
119 + display: none;
120 +}
121 +
122 +.point-of-sale #partners-screen .search-create {
123 + position: absolute;
124 + top: 6px;
125 + right: 22px;
126 + cursor: pointer;
127 + display: none;
128 +}
129 +
130 +
131 +/* b) the partner */
132 +
133 +.point-of-sale .partner {
134 + position:relative;
135 + vertical-align: top;
136 + display: inline-block;
137 + line-height: 100px;
138 + font-size: 11px;
139 + margin: 5px !important;
140 + width: 120px;
141 + height:120px;
142 + background:#fff;
143 + border: 1px solid #fff;
144 + border-radius: 3px;
145 + -webkit-box-shadow: 0px 2px 0px #dad8e4, 0px 1px 8px #636480;
146 + -moz-box-shadow: 0px 2px 0px #dad8e4, 0px 1px 8px #636480;
147 + box-shadow: 0px 2px 0px #dad8e4, 0px 1px 8px #636480;
148 +}
149 +
150 +.point-of-sale .partner .partner-img {
151 + position: relative;
152 + width: 120px;
153 + height: 100px;
154 + background: white;
155 + text-align: center;
156 +}
157 +
158 +.point-of-sale .partner .partner-img img {
159 + max-height: 100px;
160 + max-width: 120px;
161 +}
162 +
163 +.point-of-sale .partner .price-tag {
164 + position: absolute;
165 + top: 2px;
166 + right: 2px;
167 + vertical-align: top;
168 + color: white;
169 + line-height: 13px;
170 + background: #7f82ac;
171 + padding: 2px 5px;
172 + border-radius: 3px;
173 + box-shadow: 0px 1px 0px #9A9CC5, 0px 3px 0px #7E86AC, 0px 3px 3px rgba(12, 14, 68, 0.67);
174 +}
175 +
176 +.point-of-sale .partner .partner-name {
177 + position: absolute;
178 + -webkit-box-sizing: border-box;
179 + -moz-box-sizing: border-box;
180 + -ms-box-sizing: border-box;
181 + box-sizing: border-box;
182 + bottom:0;
183 + top:auto;
184 + line-height: 14px;
185 + width:100%;
186 + background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
187 + background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
188 + background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
189 + /* troublesome in latest webkit
190 + background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
191 + */
192 + /*background:#FFF;*/
193 + padding: 3px;
194 + padding-top:15px;
195 +}
196 +
197 +/* c) the partner creation */
198 +
199 +.point-of-sale .pos-step-container {
200 + display: inline-block;
201 + font-size: 1.5em;
202 +}
203 +.point-of-sale .greyed-out{
204 + color: #AAA;
205 +}
206 +.point-of-sale .pos-step-container input{
207 + font-size: 1em;
208 + outline: none;
209 + border: none;
210 + padding: 0px 8px;
211 + padding-top: 8px;
212 + margin-left: 16px;
213 + border-radius: 5px;
214 + background: white;
215 + box-shadow: 0px -1px 0px #E2E2E2 inset,0px 1px 0px white inset, 0px 4px 0px #DDD inset, 0px 4px 8px rgba(0, 0, 0, 0.55) inset;
216 + color: #4c4c4c;
217 + -webkit-animation: all 250ms linear;
218 +}
219 +
220 +.point-of-sale .pos-step-container input:focus{
221 + box-shadow: 0px -1px 0px #C9CFFD inset,0px 1px 0px #B8C8FC inset, 0px 4px 0px #9FD5FF inset, 0px 4px 9px rgba(0, 31, 255, 0.55) inset;
222 + color: #5d7ad6;
223 + -webkit-animation: all 250ms linear;
224 +}
225 +
226 +.point-of-sale .pos-partner-create-container {
227 + text-align: left;
228 + min-width: 500px;
229 + margin-top: 50px;
230 + padding: 40px;
231 + background-color: #f8f8f8;
232 + border-radius: 4px;
233 + box-shadow: 0px 1px 0px white,0px -1px 0px white, 0px 4px 0px #DFDFDF, 0px 10px 30px rgba(0, 0, 0, 0.21);
234 +}
235 +.point-of-sale .pos-partner-create-container .left-block{
236 + display: inline-block;
237 + width:49%;
238 + margin:0;
239 + padding:0;
240 + text-align:left;
241 +}
242 +.point-of-sale .pos-partner-create-container .header{
243 + margin-top: 0px;
244 + margin-bottom:20px;
245 + font-weight: bold;
246 +}
247 +.point-of-sale .pos-partner-create-container .infoline{
248 + margin-top:5px;
249 + margin-bottom:5px;
250 +}
251 +.point-of-sale .pos-partner-create-container .right-block{
252 + display: inline-block;
253 + width:49%;
254 + margin:0;
255 + padding:0;
256 + text-align:right;
257 +}
258 +.point-of-sale .pos-partner-create-container table {
259 + width: 100%;
260 + margin-bottom: 20px;
261 +}
262 +.point-of-sale .pos-partner-create-container td {
263 + vertical-align: middle;
264 +}
265 +.point-of-sale .pos-partner-create-container .partner-createline-type {
266 + font-size: 1em;
267 + font-weight: bold;
268 + margin-right:10px;
269 +}
270
271 /* ********* The Screens ********* */
272
273 diff --git a/static/src/css/pos_nohover.css b/static/src/css/pos_nohover.css
274 index 6f24abd..7dcb4f0 100644
275 --- a/static/src/css/pos_nohover.css
276 +++ b/static/src/css/pos_nohover.css
277 @@ -579,6 +579,175 @@
278 padding-top:15px;
279 }
280
281 +/* ********* The partner list ********* */
282 +
283 +.point-of-sale .partner-list {
284 + padding:10px;
285 +}
286 +
287 +.point-of-sale .partner-list-scroller{
288 + -webkit-box-sizing: border-box;
289 + -moz-box-sizing: border-box;
290 + -ms-box-sizing: border-box;
291 + box-sizing: border-box;
292 + width:100%;
293 + height:100%;
294 + overflow: hidden;
295 +}
296 +.point-of-sale .partner-list-container {
297 + position:absolute;
298 + top:0px;
299 + bottom:0px;
300 + left:0px;
301 + right:0px;
302 + background: #eeedff;
303 +}
304 +
305 +/* a) the search box */
306 +
307 +.point-of-sale #partners-screen .searchbox {
308 + position: absolute;
309 + right: 2px;
310 +}
311 +.point-of-sale #partners-screen .searchbox input {
312 + width: 130px;
313 + border-radius: 11px;
314 + border: 1px solid #cecbcb;
315 + padding: 3px 19px;
316 + margin: 6px;
317 + background: url("../img/search.png") no-repeat 5px;
318 + background-color: white;
319 +}
320 +.point-of-sale #partners-screen .search-clear {
321 + position: absolute;
322 + top: 11px;
323 + right: 11px;
324 + cursor: pointer;
325 + display: none;
326 +}
327 +.point-of-sale #partners-screen .search-create {
328 + position: absolute;
329 + top: 6px;
330 + right: 22px;
331 + cursor: pointer;
332 + display: none;
333 +}
334 +
335 +/* b) the partner */
336 +
337 +.point-of-sale .partner {
338 + position:relative;
339 + vertical-align: top;
340 + display: inline-block;
341 + line-height: 100px;
342 + font-size: 11px;
343 + margin: 5px;
344 + width: 120px;
345 + height:120px;
346 + background:#fff;
347 + border: 1px solid #fff;
348 + border-radius: 3px;
349 + -webkit-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
350 + -moz-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
351 + box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
352 +}
353 +
354 +.point-of-sale .partner .partner-img {
355 + position: relative;
356 + width: 120px;
357 + height: 100px;
358 + background: white;
359 + text-align: center;
360 +}
361 +
362 +.point-of-sale .partner .partner-img img {
363 + max-height: 100px;
364 + max-width: 120px;
365 +}
366 +
367 +.point-of-sale .partner .price-tag {
368 + position: absolute;
369 + top: 2px;
370 + right: 2px;
371 + vertical-align: top;
372 + color: white;
373 + line-height: 14px;
374 + background: #7f82ac;
375 + padding: 2px 5px;
376 + border-radius: 3px;
377 +}
378 +
379 +.point-of-sale .partner .partner-name {
380 + position: absolute;
381 + -webkit-box-sizing: border-box;
382 + -moz-box-sizing: border-box;
383 + -ms-box-sizing: border-box;
384 + box-sizing: border-box;
385 + bottom:0;
386 + top:auto;
387 + line-height: 14px;
388 + width:100%;
389 + background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
390 + background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
391 + background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
392 + background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
393 + /*background:#FFF;*/
394 + padding: 3px;
395 + padding-top:15px;
396 +}
397 +
398 +/* c) the partner creation */
399 +
400 +.point-of-sale .pos-step-container {
401 + display: inline-block;
402 + font-size: 1.5em;
403 +}
404 +.point-of-sale .greyed-out{
405 + color: #AAA;
406 +}
407 +.point-of-sale .pos-step-container input{
408 + font-size: 1em;
409 +}
410 +
411 +.point-of-sale .pos-partner-create-container {
412 + text-align: left;
413 + min-width: 500px;
414 +}
415 +.point-of-sale .pos-partner-create-container .left-block{
416 + display: inline-block;
417 + width:49%;
418 + margin:0;
419 + padding:0;
420 + text-align:left;
421 +}
422 +.point-of-sale .pos-partner-create-container .header{
423 + margin-top: 50px;
424 + margin-bottom:20px;
425 + font-weight: bold;
426 +}
427 +.point-of-sale .pos-partner-create-container .infoline{
428 + margin-top:5px;
429 + margin-bottom:5px;
430 +}
431 +.point-of-sale .pos-partner-create-container .right-block{
432 + display: inline-block;
433 + width:49%;
434 + margin:0;
435 + padding:0;
436 + text-align:right;
437 +}
438 +.point-of-sale .pos-partner-create-container table {
439 + width: 100%;
440 + margin-bottom: 20px;
441 +}
442 +.point-of-sale .pos-partner-create-container td {
443 + vertical-align: middle;
444 +}
445 +.point-of-sale .pos-partner-create-container .partner-createline-type {
446 + font-size: 1em;
447 + font-weight: bold;
448 + margin-right:10px;
449 +}
450
451 /* ********* The Screens ********* */
452
453 diff --git a/static/src/js/db.js b/static/src/js/db.js
454 index a79b111..93c71e9 100644
455 --- a/static/src/js/db.js
456 +++ b/static/src/js/db.js
457 @@ -40,6 +40,9 @@ function openerp_pos_db(instance, module){
458 //cache the data in memory to avoid roundtrips to the localstorage
459 this.cache = {};
460
461 + this.partner_search_string = '';
462 + this.partner_by_id = {};
463 + this.partner_list = [];
464 this.product_by_id = {};
465 this.product_by_ean13 = {};
466 this.product_by_category_id = {};
467 @@ -196,6 +199,23 @@ function openerp_pos_db(instance, module){
468 }
469 }
470 },
471 + _partner_search_string: function(partner){
472 + var str = '' + partner.id + ':' + partner.name;
473 + return str + '\n';
474 + },
475 + add_partners: function(partners){
476 + if(!partners instanceof Array){
477 + partners = [partners];
478 + }
479 + for(var i = 0, len = partners.length; i < len; i++){
480 + var partner = partners[i];
481 + //console.log("[db] [add_partners] partner.name:",partner.name);
482 + this.partner_by_id[partner.id] = partner;
483 + this.partner_list.push(partner);
484 + this.partner_search_string += this._partner_search_string(partner);
485 + }
486 + //console.log("[db] [add_partners] partner_search_string:", this.partner_search_string);
487 + },
488 add_packagings: function(packagings){
489 for(var i = 0, len = packagings.length; i < len; i++){
490 var pack = packagings[i];
491 @@ -225,6 +245,9 @@ function openerp_pos_db(instance, module){
492 }
493 return count;
494 },
495 + get_partner_by_id: function(id){
496 + return this.partner_by_id[id];
497 + },
498 get_product_by_id: function(id){
499 return this.product_by_id[id];
500 },
501 @@ -266,13 +289,48 @@ function openerp_pos_db(instance, module){
502 }
503 return results;
504 },
505 + search_partner: function(query){
506 + var re = RegExp("([0-9]+):.*?"+query,"gi");
507 + var results = [];
508 + //console.log("[db] [search_partner] query:",query);
509 + for(var i = 0; i < this.limit; i++){
510 + r = re.exec(this.partner_search_string);
511 + if(r){
512 + var id = Number(r[1]);
513 + //console.log("[db] [search_partner] id:",id);
514 + results.push(this.get_partner_by_id(id));
515 + }else{
516 + break;
517 + }
518 + }
519 + return results;
520 + },
521 + get_partner_list: function(){
522 + return this.partner_list;
523 + },
524 + add_partner: function(partner){
525 + console.log("[PosLS] [add_partner] partner=", partner.name);
526 + var last_id = this.load('last_partner_id',0);
527 + var partners = this.load('partners',[]);
528 + partners.push({id: last_id + 1, data: partner});
529 + this.save('last_partner_id',last_id+1);
530 + this.save('partners',partners);
531 + },
532 add_order: function(order){
533 + console.log("[PosLS] [add_order] order=", order);
534 var last_id = this.load('last_order_id',0);
535 var orders = this.load('orders',[]);
536 orders.push({id: last_id + 1, data: order});
537 this.save('last_order_id',last_id+1);
538 this.save('orders',orders);
539 },
540 + remove_partner: function(partner_id){
541 + var partners = this.load('partners',[]);
542 + partners = _.filter(partners, function(partner){
543 + return partner.id !== partner_id;
544 + });
545 + this.save('partners',partners);
546 + },
547 remove_order: function(order_id){
548 var orders = this.load('orders',[]);
549 orders = _.filter(orders, function(order){
550 @@ -280,6 +338,9 @@ function openerp_pos_db(instance, module){
551 });
552 this.save('orders',orders);
553 },
554 + get_partners: function(){
555 + return this.load('partners',[]);
556 + },
557 get_orders: function(){
558 return this.load('orders',[]);
559 },
560 diff --git a/static/src/js/models.js b/static/src/js/models.js
561 index d2cf15b..409f193 100644
562 --- a/static/src/js/models.js
563 +++ b/static/src/js/models.js
564 @@ -41,7 +41,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
565 this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
566 this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
567 this.db = new module.PosLS(); // a database used to store the products and categories
568 - this.db.clear('products','categories');
569 + this.db.clear('products','categories','partners');
570 this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
571
572 // default attributes values. If null, it will be loaded below.
573 @@ -59,6 +59,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
574 'orders': new module.OrderCollection(),
575 //this is the product list as seen by the product list widgets, it will change based on the category filters
576 'products': new module.ProductCollection(),
577 + 'partners': new module.PartnerCollection(),
578 'cashRegisters': null,
579
580 'bank_statements': null,
581 @@ -69,6 +70,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
582 'units_by_id': null,
583
584 'selectedOrder': null,
585 + 'selectedPartner': null,
586 });
587
588 this.get('orders').bind('remove', function(){ self.on_removed_order(); });
589 @@ -144,6 +146,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
590 }).then(function(partners){
591 self.set('partner_list',partners);
592
593 + return self.fetch('res.partner', ['name']);
594 + }).then(function(partners){
595 + self.db.add_partners(partners);
596 +
597 return self.fetch('account.tax', ['amount', 'price_include', 'type']);
598 }).then(function(taxes){
599 self.set('taxes', taxes);
600 @@ -256,12 +262,23 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
601 }
602 },
603
604 + push_partner: function(partner) {
605 + console.log("[PosModel] [push_partner] partner=", partner.name);
606 + this.db.add_partner(partner);
607 + this.flush_partner();
608 + },
609 // saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
610 push_order: function(record) {
611 this.db.add_order(record);
612 this.flush();
613 },
614
615 + add_new_partner: function(attr){
616 + var partner = new module.PartnerCreate({pos:this, name:attr.name});
617 + console.log("[PosModel] [add_new_partner] partner=", partner.get('name'));
618 + this.get('partners').add(partner);
619 + this.set('selectedPartner', partner);
620 + },
621 //creates a new empty order and sets it as the current order
622 add_new_order: function(){
623 var order = new module.Order({pos:this});
624 @@ -269,6 +286,16 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
625 this.set('selectedOrder', order);
626 },
627
628 + flush_partner: function() {
629 + //TODO make the mutex work
630 + //this makes sure only one _int_flush is called at the same time
631 + /*
632 + return this.flush_mutex.exec(_.bind(function() {
633 + return this._flush_partner(0);
634 + }, this));
635 + */
636 + this._flush_partner(0);
637 + },
638 // attemps to send all pending orders ( stored in the pos_db ) to the server,
639 // and remove the successfully sent ones from the db once
640 // it has been confirmed that they have been sent correctly.
641 @@ -282,6 +309,40 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
642 */
643 this._flush(0);
644 },
645 + _flush_partner: function(index){
646 + var self = this;
647 + var partners = this.db.get_partners();
648 + self.set('nbr_pending_operations',partners.length);
649 +
650 + var partner = partners[index];
651 + console.log("[PosModel] [_flush_partner] index=", index);
652 + if(!partner){
653 + return;
654 + }
655 + console.log("[PosModel] [_flush_partner] partner=", partner.data.name);
656 + //try to push a partner to the server
657 + return (new instance.web.Model('pos.order')).get_func('create_partner_from_ui')([partner])
658 + .fail(function(unused, event){
659 + //don't show error popup if it fails
660 + event.preventDefault();
661 + console.error('Failed to send partner:',partner);
662 + self._flush_partner(index+1);
663 + })
664 + .done(function(args){
665 + //remove from db if success
666 + console.log("[PosModel] [_flush_partner] [.done] args=", args);
667 + self.db.remove_partner(partner.id);
668 + var name = partner.data.name;
669 + p = {name:name, id:args[0]};
670 + console.log("[PosModel] [_flush_partner] [.done] partner=", p);
671 + self.db.add_partners([p]);
672 + //self.set('selectedPartner', p);
673 + //console.log("[PosModel] [_flush_partner] [.done] selectedPartner=", self.get('selectedPartner').name);
674 + //console.log("[PosModel] [_flush_partner] [.done] selectedPartner.id=", self.get('selectedPartner').id);
675 + self.get('selectedOrder').addPartner(p); // NOTE: set .id asynchronously..
676 + self._flush_partner(index);
677 + });
678 + },
679 // attempts to send an order of index 'index' in the list of order to send. The index
680 // is used to skip orders that failed. do not call this method outside the mutex provided
681 // by flush()
682 @@ -295,6 +356,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
683 return;
684 }
685 //try to push an order to the server
686 + console.log("[PosModel] [_flush] order=", order);
687 (new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
688 .fail(function(unused, event){
689 //don't show error popup if it fails
690 @@ -341,10 +403,22 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
691 return instance.session.url('/web/binary/image', {model: 'product.product', field: 'image', id: this.get('id')});
692 },
693 });
694 + module.Partner = Backbone.Model.extend({
695 + initialize: function(attr, options) {
696 + this.name = attr.name;
697 + this.id = attr.id;
698 + },
699 + get_image_url: function(){
700 + return instance.session.url('/web/binary/image', {model: 'res.partner', field: 'image', id: this.get('id')});
701 + },
702 + });
703
704 module.ProductCollection = Backbone.Collection.extend({
705 model: module.Product,
706 });
707 + module.PartnerCollection = Backbone.Collection.extend({
708 + model: module.Partner,
709 + });
710
711 // An orderline represent one element of the content of a client's shopping cart.
712 // An orderline contains a product, its quantity, its price, discount. etc.
713 @@ -598,6 +672,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
714 });
715
716
717 + module.PartnerCreate = Backbone.Model.extend({
718 + initialize: function(attr){
719 + Backbone.Model.prototype.initialize.apply(this, arguments);
720 + this.pos = attr.pos;
721 + this.name = attr.name;
722 + return this;
723 + },
724 + exportAsJSON: function() {
725 + console.log("[PartnerCreate] [exportAsJSON] name=",this.name);
726 + return {
727 + name: this.name,
728 + };
729 + },
730 + });
731 +
732 // An order more or less represents the content of a client's shopping cart (the OrderLines)
733 // plus the associated payment information (the PaymentLines)
734 // there is always an active ('selected') order in the Pos, a new one is created
735 @@ -643,6 +732,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
736 }
737 this.selectLine(this.getLastOrderline());
738 },
739 + addPartner: function(partner, options){
740 + options = options || {};
741 + console.log("[Order] [addPartner] partner=", partner.name, " partner_id=", partner.id);
742 + this.set_client(partner);
743 + },
744 removeOrderline: function( line ){
745 this.get('orderLines').remove(line);
746 this.selectLine(this.getLastOrderline());
747 @@ -747,6 +841,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
748 var company = this.pos.get('company');
749 var shop = this.pos.get('shop');
750 var date = new Date();
751 + console.log("[Order] [export_for_printing] this.get('client')=",this.get('client'));
752
753 return {
754 orderlines: orderlines,
755 @@ -804,7 +899,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
756 lines: orderLines,
757 statement_ids: paymentLines,
758 pos_session_id: this.pos.get('pos_session').id,
759 - partner_id: this.pos.get('client') ? this.pos.get('client').id : undefined,
760 + partner_id: this.get('client') ? this.get('client').id : undefined,
761 user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,
762 };
763 },
764 diff --git a/static/src/js/screens.js b/static/src/js/screens.js
765 index eca4a3c..eb1986a 100644
766 --- a/static/src/js/screens.js
767 +++ b/static/src/js/screens.js
768 @@ -729,10 +729,54 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
769 },
770 });
771
772 + module.PartnerScreenWidget = module.ScreenWidget.extend({
773 + template:'PartnerScreenWidget',
774 +
775 + next_screen: 'products',
776 +
777 + show_numpad: false,
778 + show_leftpane: false,
779 + init: function(parent, options) {
780 + this._super(parent,options);
781 + this.model = options.model;
782 + // TODO: this.pos.bind('change:selectedOrder', this.change_selected_order, this);
783 + },
784 +
785 + start: function(){
786 + var self = this;
787 +
788 + this.partner_search_widget = new module.PartnerSearchWidget(this,{});
789 + this.partner_search_widget.replace($('.placeholder-PartnerSearchWidget'));
790 +
791 + this.partner_list_widget = new module.PartnerListWidget(this,{
792 + click_partner_action: function(partner){
793 + self.pos.get('selectedOrder').addPartner(partner);
794 + self.pos_widget.screen_selector.set_current_screen(self.next_screen);
795 + },
796 + });
797 + this.partner_list_widget.replace($('.placeholder-PartnerListWidget'));
798 + },
799 +
800 + show: function(){
801 + this._super();
802 + var self = this;
803 +
804 + this.partner_search_widget.reset_filter();
805 + },
806 +
807 + close: function(){
808 + this._super();
809 + this.pos_widget.order_widget.set_numpad_state(null);
810 + this.pos_widget.payment_screen.set_numpad_state(null);
811 + },
812 +
813 + });
814 +
815 module.ProductScreenWidget = module.ScreenWidget.extend({
816 template:'ProductScreenWidget',
817
818 scale_screen: 'scale_invite',
819 + back_screen: 'partner',
820 client_next_screen: 'client_payment',
821
822 show_numpad: true,
823 @@ -775,6 +819,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
824 }
825 });
826 }
827 +
828 + this.back_button = this.add_action_button({
829 + label: 'Back',
830 + icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
831 + click: function(){
832 + self.pos.get('selectedOrder').get('orderLines').reset([]);
833 + self.pos_widget.numpad.state.trigger('set_value','remove');
834 + self.pos_widget.screen_selector.set_current_screen(self.back_screen);
835 + },
836 + });
837 },
838
839 close: function(){
840 @@ -846,6 +900,63 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
841 },
842 });
843
844 + module.PartnerCreateScreenWidget = module.ScreenWidget.extend({
845 + template: 'PartnerCreateScreenWidget',
846 + show_numpad: false,
847 + show_leftpane: false,
848 + back_screen: 'partner',
849 + next_screen: 'products',
850 + init: function(parent, options) {
851 + this._super(parent,options);
852 + this.model = options.model;
853 + },
854 + show: function(){
855 + this._super();
856 + var self = this;
857 +
858 + this.back_button = this.add_action_button({
859 + label: 'Back',
860 + icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
861 + click: function(){
862 + self.pos_widget.screen_selector.set_current_screen(self.back_screen);
863 + },
864 + });
865 + this.validate_button = this.add_action_button({
866 + label: 'Validate',
867 + name: 'validation',
868 + icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
869 + click: function(){
870 + self.validatePartner();
871 + },
872 + });
873 + this.updatePartnerCreateSummary();
874 + },
875 + close: function(){
876 + this._super();
877 + },
878 + back: function() {
879 + this.pos_widget.screen_selector.set_current_screen(self.back_screen);
880 + },
881 + validatePartner: function() {
882 + var partner = this.pos.get('selectedPartner');
883 +
884 + this.pos.push_partner(partner.exportAsJSON());
885 + this.pos.get('selectedOrder').addPartner({name:partner.name}); // NOTE: .id set asynchronously in .done()
886 + this.pos_widget.screen_selector.set_current_screen(this.next_screen);
887 + },
888 + renderElement: function() {
889 + this._super();
890 + this.updatePartnerCreateSummary();
891 + },
892 + updatePartnerCreateSummary: function() {
893 + var partner = this.pos.get('selectedPartner');
894 + if(partner){
895 + console.log("[PartnerCreateScreenWidget] [updatePartnerCreateSummary] partner=",partner.get('name'));
896 + this.$('#partner-create-name').html(partner.get('name'));
897 + }
898 + },
899 + });
900 +
901 module.PaymentScreenWidget = module.ScreenWidget.extend({
902 template: 'PaymentScreenWidget',
903 back_screen: 'products',
904 diff --git a/static/src/js/widgets.js b/static/src/js/widgets.js
905 index 874c387..9b6c120 100644
906 --- a/static/src/js/widgets.js
907 +++ b/static/src/js/widgets.js
908 @@ -282,6 +282,27 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
909 },
910 });
911
912 + module.PartnerWidget = module.PosBaseWidget.extend({
913 + template: 'PartnerWidget',
914 + init: function(parent, options) {
915 + this._super(parent,options);
916 + this.model = options.model;
917 + this.next_screen = options.next_screen; //when a partner is clicked, this screen is set
918 + this.click_partner_action = options.click_partner_action;
919 + },
920 + // returns the url of the partner thumbnail
921 + renderElement: function() {
922 + this._super();
923 + this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.model.get_image_url()));
924 + var self = this;
925 + $("a", this.$el).click(function(e){
926 + if(self.click_partner_action){
927 + self.click_partner_action(self.model);
928 + }
929 + });
930 + },
931 + });
932 +
933 module.ProductWidget = module.PosBaseWidget.extend({
934 template: 'ProductWidget',
935 init: function(parent, options) {
936 @@ -289,6 +310,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
937 this.model = options.model;
938 this.model.attributes.weight = options.weight;
939 this.next_screen = options.next_screen; //when a product is clicked, this screen is set
940 + //this.back_screen = options.back_screen;
941 this.click_product_action = options.click_product_action;
942 },
943 // returns the url of the product thumbnail
944 @@ -465,6 +487,79 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
945
946 module.CategoryButton = module.PosBaseWidget.extend({
947 });
948 +
949 + module.PartnerSearchWidget = module.PosBaseWidget.extend({
950 + template: 'PartnerSearchWidget',
951 + init: function(parent, options){
952 + var self = this;
953 + this._super(parent,options);
954 + },
955 +
956 + get_image_url: function(partner){
957 + return instance.session.url('/web/binary/image', {model: 'res.partner', field: 'image', id: partner.id});
958 + },
959 +
960 + renderElement: function(){
961 + var self = this;
962 + this._super();
963 + this.filter();
964 + },
965 + reset_filter: function(){
966 + this.renderElement();
967 + this.filter();
968 + },
969 + filter: function(category){
970 + var self = this;
971 +
972 + var partners = self.pos.db.get_partner_list();
973 + self.pos.get('partners').reset(partners);
974 +
975 + // filter the partners according to the search string
976 + this.$('.searchbox input').keyup(function(){
977 + query = $(this).val();
978 + if(query){
979 + console.log("[PartnerSearchWidget] [filter]: query:", query);
980 + var partners = self.pos.db.search_partner(query);
981 + self.pos.get('partners').reset(partners);
982 + //if(partners.length == 0) {
983 + console.log("[PartnerSearchWidget] [filter]: no result");
984 + self.query = query;
985 + self.$('.search-create').fadeIn();
986 + //}else{
987 + // self.$('.search-create').fadeOut();
988 + //}
989 + self.$('.search-clear').fadeIn();
990 + }else{
991 + var partners = self.pos.db.get_partner_list();
992 + self.pos.get('partners').reset(partners);
993 + self.$('.search-clear').fadeOut();
994 + self.$('.search-create').fadeOut();
995 + }
996 + });
997 +
998 + //this.$('.searchbox input').click(function(){}); //Why ???
999 +
1000 + //reset the search when clicking on reset
1001 + this.$('.search-clear').click(function(){
1002 + var partners = self.pos.db.get_partner_list();
1003 + self.pos.get('partners').reset(partners);
1004 + self.$('.searchbox input').val('').focus();
1005 + self.$('.search-clear').fadeOut();
1006 + self.$('.search-create').fadeOut();
1007 + });
1008 + this.$('.search-create').click(function(){
1009 + var partners = self.pos.db.get_partner_list();
1010 + self.pos.get('partners').reset(partners);
1011 + self.$('.searchbox input').val('').focus();
1012 + self.$('.search-clear').fadeOut();
1013 + self.$('.search-create').fadeOut();
1014 + console.log("[PartnerSearchWidget] [filter] [search-create] query=",self.query);
1015 + self.pos.add_new_partner({name:self.query});
1016 + self.pos_widget.screen_selector.set_current_screen('partner_create');
1017 + });
1018 + },
1019 + });
1020 +
1021 module.ProductCategoriesWidget = module.PosBaseWidget.extend({
1022 template: 'ProductCategoriesWidget',
1023 init: function(parent, options){
1024 @@ -588,7 +683,61 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
1025 });
1026 },
1027 });
1028 +
1029 + module.PartnerListWidget = module.ScreenWidget.extend({
1030 + template:'PartnerListWidget',
1031 + init: function(parent, options) {
1032 + var self = this;
1033 + this._super(parent,options);
1034 + this.model = options.model;
1035 + this.partnerwidgets = [];
1036 + //this.weight = options.weight || 0;
1037 + this.show_scale = options.show_scale || false;
1038 + this.next_screen = options.next_screen || false;
1039 + this.click_partner_action = options.click_partner_action;
1040
1041 + this.pos.get('partners').bind('reset', function(){
1042 + self.renderElement();
1043 + });
1044 + },
1045 + renderElement: function() {
1046 + var self = this;
1047 + this._super();
1048 +
1049 + // free subwidgets memory from previous renders
1050 + for(var i = 0, len = this.partnerwidgets.length; i < len; i++){
1051 + this.partnerwidgets[i].destroy();
1052 + }
1053 + this.partnerwidgets = [];
1054 + if(this.scrollbar){
1055 + this.scrollbar.destroy();
1056 + }
1057 + var partners = this.pos.get('partners').models || [];
1058 + for(var i = 0, len = partners.length; i < len; i++){
1059 + //console.log("[PartnerListWidget] [renderElement]: partners[i].get('name')=",partners[i].get('name'));
1060 + var partner = new module.PartnerWidget(self, {
1061 + model: partners[i],
1062 + click_partner_action: this.click_partner_action,
1063 + });
1064 + this.partnerwidgets.push(partner);
1065 + partner.appendTo(this.$('.partner-list'));
1066 + }
1067 + this.scrollbar = new module.ScrollbarWidget(this,{
1068 + target_widget: this,
1069 + target_selector: '.partner-list-scroller',
1070 + on_show: function(){
1071 + self.$('.partner-list-scroller').css({'padding-right':'62px'},100);
1072 + },
1073 + on_hide: function(){
1074 + self.$('.partner-list-scroller').css({'padding-right':'0px'},100);
1075 + },
1076 + });
1077 +
1078 + this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget'));
1079 +
1080 + },
1081 + });
1082 +
1083 module.ProductListWidget = module.ScreenWidget.extend({
1084 template:'ProductListWidget',
1085 init: function(parent, options) {
1086 @@ -599,6 +748,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
1087 this.weight = options.weight || 0;
1088 this.show_scale = options.show_scale || false;
1089 this.next_screen = options.next_screen || false;
1090 + //this.back_screen = options.back_screen || false;
1091 this.click_product_action = options.click_product_action;
1092
1093 this.pos.get('products').bind('reset', function(){
1094 @@ -899,6 +1049,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
1095
1096 // -------- Screens ---------
1097
1098 + this.partner_screen = new module.PartnerScreenWidget(this,{});
1099 + this.partner_screen.appendTo($('#rightpane'));
1100 +
1101 this.product_screen = new module.ProductScreenWidget(this,{});
1102 this.product_screen.appendTo($('#rightpane'));
1103
1104 @@ -908,6 +1061,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
1105 this.payment_screen = new module.PaymentScreenWidget(this, {});
1106 this.payment_screen.appendTo($('#rightpane'));
1107
1108 + this.partner_create_screen = new module.PartnerCreateScreenWidget(this, {});
1109 + this.partner_create_screen.appendTo($('#rightpane'));
1110 +
1111 this.welcome_screen = new module.WelcomeScreenWidget(this,{});
1112 this.welcome_screen.appendTo($('#rightpane'));
1113
1114 @@ -993,6 +1149,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
1115 'scale': this.scale_screen,
1116 'receipt' : this.receipt_screen,
1117 'welcome' : this.welcome_screen,
1118 + 'partner' : this.partner_screen,
1119 + 'partner_create' : this.partner_create_screen,
1120 },
1121 popup_set:{
1122 'help': this.help_popup,
1123 @@ -1003,7 +1161,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
1124 'choose-receipt': this.choose_receipt_popup,
1125 },
1126 default_client_screen: 'welcome',
1127 - default_cashier_screen: 'products',
1128 + default_cashier_screen: 'partner',
1129 default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier',
1130 });
1131
1132 diff --git a/static/src/xml/pos.xml b/static/src/xml/pos.xml
1133 index 3da76ec..6aabe35 100644
1134 --- a/static/src/xml/pos.xml
1135 +++ b/static/src/xml/pos.xml
1136 @@ -185,6 +185,49 @@
1137 </div>
1138 </t>
1139
1140 + <t t-name="PartnerSearchWidget">
1141 + <header>
1142 + <div class="searchbox">
1143 + <input placeholder="Search Products" />
1144 + <img class="search-create" src="/point_of_sale/static/src/img/validate-icon.png" />
1145 + <img class="search-clear" src="/point_of_sale/static/src/img/search_reset.gif" />
1146 + </div>
1147 + </header>
1148 + </t>
1149 +
1150 + <t t-name="PartnerListWidget">
1151 + <div class='partner-list-container'>
1152 + <div class="partner-list-scroller">
1153 + <ol id="partners-screen-ol" class="partner-list">
1154 + </ol>
1155 + </div>
1156 + <div class="shadow-top"></div>
1157 + <span class="placeholder-ScrollbarWidget" />
1158 + </div>
1159 + </t>
1160 +
1161 + <t t-name="PartnerScreenWidget">
1162 + <div id="partners-screen" class="screen">
1163 + <table class="layout-table">
1164 +
1165 + <tr class="header-row">
1166 + <td class="header-cell">
1167 + <span class="placeholder-PartnerSearchWidget" />
1168 + </td>
1169 + </tr>
1170 +
1171 + <tr class="content-row">
1172 + <td class="content-cell">
1173 + <div class="content-container">
1174 + <span class="placeholder-PartnerListWidget" />
1175 + </div>
1176 + </td>
1177 + </tr>
1178 +
1179 + </table>
1180 + </div>
1181 + </t>
1182 +
1183 <t t-name="ScaleScreenWidget">
1184 <div class="scale-screen screen">
1185 <header><h2>Product Weighting</h2></header>
1186 @@ -210,6 +253,41 @@
1187 </div>
1188 </t>
1189
1190 + <t t-name="PartnerCreateScreenWidget">
1191 + <div id="partner-create-screen" class="screen">
1192 + <header><h2>Partner Create</h2></header>
1193 + <div class="pos-step-container">
1194 + <div class="pos-partner-create-container">
1195 + <br />
1196 + <div class="header">
1197 + <span class="left-block">
1198 + Name:
1199 + </span>
1200 + <span class='right-block' id="partner-create-name"></span>
1201 + <!--
1202 + <span class="left-block">
1203 + Name2:
1204 + </span>
1205 + <span class='right-block'>
1206 + <t t-if="widget.model.get('name')"/>
1207 + </span>
1208 + -->
1209 + </div>
1210 + <!--<table id="partner-createlines">
1211 + </table>-->
1212 + <div class="footer">
1213 + <div class="infoline">
1214 + <span class='left-block'>
1215 + Address:
1216 + </span>
1217 + <span class='right-block' id="partner-create-address"></span>
1218 + </div>
1219 + </div>
1220 + </div>
1221 + </div>
1222 + </div>
1223 + </t>
1224 +
1225 <t t-name="PaymentScreenWidget">
1226 <div id="payment-screen" class="screen">
1227 <header><h2>Payment</h2></header>
1228 @@ -370,6 +448,30 @@
1229 </div>
1230 </t>
1231
1232 + <t t-name="PartnerWidget">
1233 + <li class='partner'>
1234 + <a href="#">
1235 + <div class="partner-img">
1236 + <img src='' /> <!-- the partner thumbnail -->
1237 + <!--<t t-if="!widget.model.get('to_weight')">
1238 + <span class="price-tag">
1239 + <t t-esc="widget.format_currency(widget.model.get('price'))"/>
1240 + </span>
1241 + </t>
1242 + <t t-if="widget.model.get('to_weight')">
1243 + <span class="price-tag">
1244 + <t t-esc="widget.format_currency(widget.model.get('price'))+'/Kg'"/>
1245 + </span>
1246 + </t>-->
1247 + </div>
1248 + <div class="partner-name">
1249 + <t t-esc="widget.model.get('name')"/>
1250 + <!--(<t t-esc="widget.model.get('id')"/>)-->
1251 + </div>
1252 + </a>
1253 + </li>
1254 + </t>
1255 +
1256 <t t-name="ProductWidget">
1257 <li class='product'>
1258 <a href="#">