Merge lp:~invitu/openobject-addons/trunk-point_of_sale_select_customer into lp:openobject-addons
- trunk-point_of_sale_select_customer
- Merge into trunk
Proposed by
invitu
Status: | Needs review |
---|---|
Proposed branch: | lp:~invitu/openobject-addons/trunk-point_of_sale_select_customer |
Merge into: | lp:openobject-addons |
Diff against target: |
1055 lines (+575/-89) 9 files modified
plugin_outlook/i18n/et.po (+1/-1) point_of_sale/point_of_sale.py (+16/-17) point_of_sale/static/src/css/pos.css (+165/-0) point_of_sale/static/src/js/db.js (+58/-0) point_of_sale/static/src/js/models.js (+18/-4) point_of_sale/static/src/js/screens.js (+149/-66) point_of_sale/static/src/js/widgets.js (+89/-1) point_of_sale/static/src/xml/pos.xml (+77/-0) project_issue/project_issue_view.xml (+2/-0) |
To merge this branch: | bzr merge lp:~invitu/openobject-addons/trunk-point_of_sale_select_customer |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP Core Team | Pending | ||
Review via email: mp+195496@code.launchpad.net |
Commit message
Description of the change
[point_of_sale] allows customer selection in pos (inspired by Michael Telahun Makonnen's work)
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'plugin_outlook/i18n/et.po' |
2 | --- plugin_outlook/i18n/et.po 2013-09-12 06:40:18 +0000 |
3 | +++ plugin_outlook/i18n/et.po 2013-11-17 01:16:28 +0000 |
4 | @@ -27,7 +27,7 @@ |
5 | msgid "Download and install the plug-in" |
6 | msgstr "" |
7 | |
8 | -#. module: plugin_outlook |
9 | +#. mdule: plugin_outlook |
10 | #: model:ir.model,name:plugin_outlook.model_outlook_installer |
11 | msgid "outlook.installer" |
12 | msgstr "outlook.installer" |
13 | |
14 | === modified file 'point_of_sale/point_of_sale.py' |
15 | --- point_of_sale/point_of_sale.py 2013-10-18 12:58:18 +0000 |
16 | +++ point_of_sale/point_of_sale.py 2013-11-17 01:16:28 +0000 |
17 | @@ -48,7 +48,7 @@ |
18 | _columns = { |
19 | 'name' : fields.char('Point of Sale Name', size=32, select=1, |
20 | required=True, help="An internal identification of the point of sale"), |
21 | - 'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', |
22 | + 'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', |
23 | 'pos_config_id', 'journal_id', 'Available Payment Methods', |
24 | domain="[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",), |
25 | 'warehouse_id' : fields.many2one('stock.warehouse', 'Warehouse', |
26 | @@ -202,7 +202,7 @@ |
27 | readonly=True, |
28 | states={'opening_control' : [('readonly', False)]} |
29 | ), |
30 | - 'start_at' : fields.datetime('Opening Date', readonly=True), |
31 | + 'start_at' : fields.datetime('Opening Date', readonly=True), |
32 | 'stop_at' : fields.datetime('Closing Date', readonly=True), |
33 | |
34 | 'state' : fields.selection(POS_SESSION_STATE, 'Status', |
35 | @@ -221,10 +221,10 @@ |
36 | type='many2one', relation='account.bank.statement', |
37 | string='Cash Register', store=True), |
38 | |
39 | - 'opening_details_ids' : fields.related('cash_register_id', 'opening_details_ids', |
40 | + 'opening_details_ids' : fields.related('cash_register_id', 'opening_details_ids', |
41 | type='one2many', relation='account.cashbox.line', |
42 | string='Opening Cash Control'), |
43 | - 'details_ids' : fields.related('cash_register_id', 'details_ids', |
44 | + 'details_ids' : fields.related('cash_register_id', 'details_ids', |
45 | type='one2many', relation='account.cashbox.line', |
46 | string='Cash Control'), |
47 | |
48 | @@ -419,7 +419,7 @@ |
49 | raise osv.except_osv( _('Error!'), |
50 | _("Your ending balance is too different from the theoretical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff)) |
51 | if (st.journal_id.type not in ['bank', 'cash']): |
52 | - raise osv.except_osv(_('Error!'), |
53 | + raise osv.except_osv(_('Error!'), |
54 | _("The type of the journal for your payment method should be bank or cash ")) |
55 | if st.difference and st.journal_id.cash_control == True: |
56 | if st.difference > 0.0: |
57 | @@ -503,14 +503,13 @@ |
58 | to_invoice = tmp_order['to_invoice'] |
59 | order = tmp_order['data'] |
60 | |
61 | - |
62 | order_id = self.create(cr, uid, { |
63 | 'name': order['name'], |
64 | 'user_id': order['user_id'] or False, |
65 | 'session_id': order['pos_session_id'], |
66 | 'lines': order['lines'], |
67 | 'pos_reference':order['name'], |
68 | - 'partner_id': order['partner_id'] or False |
69 | + 'partner_id': order['partner_id'] if 'partner_id' in order else False |
70 | }, context) |
71 | for payments in order['statement_ids']: |
72 | payment = payments[2] |
73 | @@ -630,7 +629,7 @@ |
74 | 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True), |
75 | 'partner_id': fields.many2one('res.partner', 'Customer', change_default=True, select=1, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]}), |
76 | |
77 | - 'session_id' : fields.many2one('pos.session', 'Session', |
78 | + 'session_id' : fields.many2one('pos.session', 'Session', |
79 | #required=True, |
80 | select=1, |
81 | domain="[('state', '=', 'opened')]", |
82 | @@ -659,7 +658,7 @@ |
83 | return session_ids and session_ids[0] or False |
84 | |
85 | def _default_pricelist(self, cr, uid, context=None): |
86 | - session_ids = self._default_session(cr, uid, context) |
87 | + session_ids = self._default_session(cr, uid, context) |
88 | if session_ids: |
89 | session_record = self.pool.get('pos.session').browse(cr, uid, session_ids, context=context) |
90 | return session_record.config_id.pricelist_id and session_record.config_id.pricelist_id.id or False |
91 | @@ -668,7 +667,7 @@ |
92 | _defaults = { |
93 | 'user_id': lambda self, cr, uid, context: uid, |
94 | 'state': 'draft', |
95 | - 'name': '/', |
96 | + 'name': '/', |
97 | 'date_order': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), |
98 | 'nb_print': 0, |
99 | 'session_id': _default_session, |
100 | @@ -737,7 +736,7 @@ |
101 | }, context=context) |
102 | if line.qty < 0: |
103 | location_id, output_id = output_id, location_id |
104 | - |
105 | + |
106 | picking_obj.signal_button_confirm(cr, uid, [picking_id]) |
107 | picking_obj.force_assign(cr, uid, [picking_id], context) |
108 | return True |
109 | @@ -813,7 +812,7 @@ |
110 | """Create a copy of order for refund order""" |
111 | clone_list = [] |
112 | line_obj = self.pool.get('pos.order.line') |
113 | - |
114 | + |
115 | for order in self.browse(cr, uid, ids, context=context): |
116 | current_session_ids = self.pool.get('pos.session').search(cr, uid, [ |
117 | ('state', '!=', 'closed'), |
118 | @@ -1030,8 +1029,8 @@ |
119 | else: |
120 | grouped_data[key].append(values) |
121 | |
122 | - #because of the weird way the pos order is written, we need to make sure there is at least one line, |
123 | - #because just after the 'for' loop there are references to 'line' and 'income_account' variables (that |
124 | + #because of the weird way the pos order is written, we need to make sure there is at least one line, |
125 | + #because just after the 'for' loop there are references to 'line' and 'income_account' variables (that |
126 | #are set inside the for loop) |
127 | #TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack |
128 | assert order.lines, _('The POS order must have lines when calling this method') |
129 | @@ -1292,7 +1291,7 @@ |
130 | for obj in self.browse(cr, uid, ids, context=context): |
131 | result[obj.id] = tools.image_get_resized_images(obj.image) |
132 | return result |
133 | - |
134 | + |
135 | def _set_image(self, cr, uid, id, name, value, args, context=None): |
136 | return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) |
137 | |
138 | @@ -1302,7 +1301,7 @@ |
139 | 'parent_id': fields.many2one('pos.category','Parent Category', select=True), |
140 | 'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'), |
141 | 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."), |
142 | - |
143 | + |
144 | # NOTE: there is no 'default image', because by default we don't show thumbnails for categories. However if we have a thumbnail |
145 | # for at least one category, then we display a default image on the other, so that the buttons have consistent styling. |
146 | # In this case, the default image is set by the js code. |
147 | @@ -1364,7 +1363,7 @@ |
148 | _columns = { |
149 | 'income_pdt': fields.boolean('Point of Sale Cash In', help="Check if, this is a product you can use to put cash into a statement for the point of sale backend."), |
150 | 'expense_pdt': fields.boolean('Point of Sale Cash Out', help="Check if, this is a product you can use to take cash from a statement for the point of sale backend, example: money lost, transfer to bank, etc."), |
151 | - 'available_in_pos': fields.boolean('Available in the Point of Sale', help='Check if you want this product to appear in the Point of Sale'), |
152 | + 'available_in_pos': fields.boolean('Available in the Point of Sale', help='Check if you want this product to appear in the Point of Sale'), |
153 | 'pos_categ_id': fields.many2one('pos.category','Point of Sale Category', |
154 | help="These products belong to those categories that are used to group similar products and are specific to the Point of Sale."), |
155 | 'to_weight' : fields.boolean('To Weight', help="Check if the product should be weighted (mainly used with self check-out interface)."), |
156 | |
157 | === modified file 'point_of_sale/static/src/css/pos.css' |
158 | --- point_of_sale/static/src/css/pos.css 2013-11-12 14:04:57 +0000 |
159 | +++ point_of_sale/static/src/css/pos.css 2013-11-17 01:16:28 +0000 |
160 | @@ -208,6 +208,15 @@ |
161 | box-shadow: 0px 1px 2px rgb(63, 66, 139) inset; |
162 | } |
163 | |
164 | +.point-of-sale #rightheader .customername{ |
165 | + float:right; |
166 | + color:#DDD; |
167 | + font-size:16px; |
168 | + margin-right:32px; |
169 | + margin-top:10px; |
170 | + font-style:italic; |
171 | +} |
172 | + |
173 | /* c) The session buttons */ |
174 | |
175 | .point-of-sale #rightheader .header-button{ |
176 | @@ -1379,6 +1388,162 @@ |
177 | line-height:180px; |
178 | } |
179 | |
180 | +.point-of-sale .modal-dialog .popup-selection{ |
181 | + position: absolute; |
182 | + left:30%; |
183 | + top:10%; |
184 | + width: 700px; |
185 | + height:70%; |
186 | + padding:10px; |
187 | + padding-top:20px; |
188 | + text-align:left; |
189 | + font-size:14px; |
190 | + font-weight:bold; |
191 | + background-color: #F0EEEE; |
192 | + border-radius: 4px; |
193 | + box-shadow: 0px -1px white, 0px 1px white, 0px 4px #949494, 0px 10px 20px rgba(0, 0, 0, 0.3); |
194 | + z-index:1200; |
195 | +} |
196 | + |
197 | +.point-of-sale .popup-selection .button{ |
198 | + float:right; |
199 | + width: 110px; |
200 | + height: 40px; |
201 | + line-height:40px; |
202 | + text-align:center; |
203 | + margin:3px; |
204 | + margin-top:10px; |
205 | + margin-right:50px; |
206 | + |
207 | + font-size: 14px; |
208 | + font-weight: bold; |
209 | + |
210 | + cursor: pointer; |
211 | + |
212 | + border: 1px solid #cacaca; |
213 | + border-radius: 4px; |
214 | + |
215 | + background: #e2e2e2; |
216 | + background: -webkit-linear-gradient(#f0f0f0, #e2e2e2); |
217 | + background: -moz-linear-gradient(#f0f0f0, #e2e2e2); |
218 | + background: -ms-linear-gradient(#f0f0f0, #e2e2e2); |
219 | + background: linear-gradient(#f0f0f0, #e2e2e2); |
220 | + -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3); |
221 | + -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3); |
222 | + box-shadow: 0px 2px 2px rgba(0,0,0, 0.3); |
223 | +} |
224 | +.point-of-sale .popup-selection .button:hover { |
225 | + color: white; |
226 | + background: #7f82ac; |
227 | + border: 1px solid #7f82ac; |
228 | + background: -webkit-linear-gradient(#9d9fc5, #7f82ac); |
229 | + background: -moz-linear-gradient(#9d9fc5, #7f82ac); |
230 | + background: -ms-linear-gradient(#9d9fc5, #7f82ac); |
231 | + background: linear-gradient(#9d9fc5, #7f82ac); |
232 | + |
233 | + -webkit-transition-property: background, border; |
234 | + -webkit-transition-duration: 0.2s; |
235 | + -webkit-transition-timing-function: ease-out; |
236 | +} |
237 | + |
238 | +/* customer selection */ |
239 | + |
240 | +#customer-cancel { |
241 | + position: absolute; |
242 | + left: 10px; |
243 | + bottom: 10px; |
244 | +} |
245 | + |
246 | +.point-of-sale .customer-list-container { |
247 | + position: absolute; |
248 | + top:70px; |
249 | + left:5px; |
250 | + right:5px; |
251 | + bottom: 70px; |
252 | + text-align: left; |
253 | + overflow: auto; |
254 | +} |
255 | + |
256 | +.point-of-sale .customer-list-scroller { |
257 | + -webkit-box-sizing: border-box; |
258 | + -moz-box-sizing: border-box; |
259 | + -ms-box-sizing: border-box; |
260 | + box-sizing: border-box; |
261 | + width:98%; |
262 | +} |
263 | + |
264 | +.customer-list-scroller ol { |
265 | + list-style: none; |
266 | +} |
267 | + |
268 | +.customer-list-scroller ol li { } |
269 | + |
270 | +.customer-list-scroller ol li a { |
271 | + display:block; |
272 | + text-decoration:none; |
273 | + color:#000000; |
274 | + background-color:#FFFFFF; |
275 | + line-height:20px; |
276 | + border-bottom-style:solid; |
277 | + border-bottom-width:1px; |
278 | + border-bottom-color:#CCCCCC; |
279 | + padding-left:10px; |
280 | + cursor:pointer; |
281 | +} |
282 | + |
283 | +.customer-list-scroller ol li a:hover { |
284 | + color:#FFFFFF; |
285 | + background-image:url(/point_of_sale/static/src/img/hover.png); |
286 | + background-repeat:repeat-x; |
287 | +} |
288 | + |
289 | +.point-of-sale .customer { |
290 | + width: 100%; |
291 | +} |
292 | + |
293 | +.point-of-sale .customer a { |
294 | + width: 100%; |
295 | +} |
296 | + |
297 | +.point-of-sale .customer .customer-field { |
298 | + width: 130px; |
299 | + padding: 5px; |
300 | + margin: 5px; |
301 | +} |
302 | + |
303 | +.point-of-sale .customer .customer-name{ |
304 | + width: 200px; |
305 | + padding: 5px; |
306 | + margin: 5px; |
307 | +} |
308 | + |
309 | +.point-of-sale .customer .customer-phone { |
310 | + width: 50px; |
311 | + padding: 5px; |
312 | + margin: 5px; |
313 | +} |
314 | + |
315 | +.point-of-sale .customer-searchbox { |
316 | + right: 2px; |
317 | +} |
318 | +.point-of-sale .customer-searchbox input { |
319 | + width: 130px; |
320 | + border-radius: 11px; |
321 | + border: 1px solid #cecbcb; |
322 | + padding: 3px 19px; |
323 | + margin: 6px; |
324 | + background: url("../img/search.png") no-repeat 5px; |
325 | + background-color: white; |
326 | +} |
327 | +.point-of-sale .customer-search-clear { |
328 | + postion: absolute; |
329 | + top: 11px; |
330 | + right: 600px; |
331 | + cursor: pointer; |
332 | + display: none; |
333 | +} |
334 | + |
335 | + |
336 | /* ********* The ScrollBarWidget ********* */ |
337 | |
338 | .point-of-sale .scrollbar{ |
339 | |
340 | === added file 'point_of_sale/static/src/img/hover.png' |
341 | Binary files point_of_sale/static/src/img/hover.png 1970-01-01 00:00:00 +0000 and point_of_sale/static/src/img/hover.png 2013-11-17 01:16:28 +0000 differ |
342 | === modified file 'point_of_sale/static/src/js/db.js' |
343 | --- point_of_sale/static/src/js/db.js 2013-09-23 14:51:39 +0000 |
344 | +++ point_of_sale/static/src/js/db.js 2013-11-17 01:16:28 +0000 |
345 | @@ -55,6 +55,7 @@ |
346 | this.packagings_by_id = {}; |
347 | this.packagings_by_product_id = {}; |
348 | this.packagings_by_ean13 = {}; |
349 | + this.customer_list_search_strings = ''; |
350 | }, |
351 | /* returns the category object from its id. If you pass a list of id as parameters, you get |
352 | * a list of category objects. |
353 | @@ -216,6 +217,35 @@ |
354 | } |
355 | } |
356 | }, |
357 | + _customer_search_string: function(customer){ |
358 | + var str = '' + customer.id + ':' + customer.name; |
359 | + if(customer.vat){ |
360 | + str += '|' + customer.vat; |
361 | + } |
362 | + if(customer.email){ |
363 | + str += '|' + customer.email; |
364 | + } |
365 | + if(customer.phone){ |
366 | + str += '|' + customer.phone; |
367 | + } |
368 | + if(customer.mobile){ |
369 | + str += '|' + customer.mobile; |
370 | + } |
371 | + return str + '\n'; |
372 | + }, |
373 | + add_customers: function(customers){ |
374 | + var stored_customers = this.load('customers',{}); |
375 | + |
376 | + if(!customers instanceof Array){ |
377 | + customers = [customers]; |
378 | + } |
379 | + for(var i = 0, len = customers.length; i < len; i++){ |
380 | + var c = customers[i]; |
381 | + this.customer_list_search_strings += this._customer_search_string(c); |
382 | + stored_customers[c.id] = c; |
383 | + } |
384 | + this.save('customers',stored_customers); |
385 | + }, |
386 | /* removes all the data from the database. TODO : being able to selectively remove data */ |
387 | clear: function(stores){ |
388 | for(var i = 0, len = arguments.length; i < len; i++){ |
389 | @@ -258,6 +288,17 @@ |
390 | } |
391 | return list; |
392 | }, |
393 | + get_customer_by_id: function(id){ |
394 | + return this.load('customers',{})[id]; |
395 | + }, |
396 | + get_all_customers: function(){ |
397 | + list = []; |
398 | + stored_customers = this.load('customers',{}); |
399 | + for (var i in stored_customers) { |
400 | + list.push(stored_customers[i]); |
401 | + } |
402 | + return list; |
403 | + }, |
404 | /* returns a list of products with : |
405 | * - a category that is or is a child of category_id, |
406 | * - a name, package or ean13 containing the query (case insensitive) |
407 | @@ -276,6 +317,23 @@ |
408 | } |
409 | return results; |
410 | }, |
411 | + /* returns a list of customers with : |
412 | + * * - a name, TIN, email, or phone/mobile containing the query (case insensitive) |
413 | + * */ |
414 | + search_customers: function(query){ |
415 | + var re = RegExp("([0-9]+):.*?"+query,"gi"); |
416 | + var results = []; |
417 | + for(var i = 0; i < this.limit; i++){ |
418 | + r = re.exec(this.customer_list_search_strings); |
419 | + if(r){ |
420 | + var id = Number(r[1]); |
421 | + results.push(this.get_customer_by_id(id)); |
422 | + }else{ |
423 | + break; |
424 | + } |
425 | + } |
426 | + return results; |
427 | + }, |
428 | add_order: function(order){ |
429 | var order_id = order.uid; |
430 | var orders = this.load('orders',[]); |
431 | |
432 | === modified file 'point_of_sale/static/src/js/models.js' |
433 | --- point_of_sale/static/src/js/models.js 2013-09-23 17:13:10 +0000 |
434 | +++ point_of_sale/static/src/js/models.js 2013-11-17 01:16:28 +0000 |
435 | @@ -26,7 +26,7 @@ |
436 | this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy |
437 | this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy |
438 | this.db = new module.PosLS(); // a database used to store the products and categories |
439 | - this.db.clear('products','categories'); |
440 | + this.db.clear('products','categories','customers'); |
441 | this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode |
442 | |
443 | // default attributes values. If null, it will be loaded below. |
444 | @@ -44,6 +44,7 @@ |
445 | 'orders': new module.OrderCollection(), |
446 | //this is the product list as seen by the product list widgets, it will change based on the category filters |
447 | 'products': new module.ProductCollection(), |
448 | + 'customers': new module.CustomerCollection(), |
449 | 'cashRegisters': null, |
450 | |
451 | 'bank_statements': null, |
452 | @@ -135,6 +136,10 @@ |
453 | }).then(function(partners){ |
454 | self.set('partner_list',partners); |
455 | |
456 | + return self.fetch('res.partner', ['name','vat','email','phone','mobile'], [['customer', '=', true]]); |
457 | + }).then(function(customers){ |
458 | + self.db.add_customers(customers); |
459 | + |
460 | return self.fetch('account.tax', ['amount', 'price_include', 'type']); |
461 | }).then(function(taxes){ |
462 | self.set('taxes', taxes); |
463 | @@ -866,7 +871,7 @@ |
464 | this.get('paymentLines').each(function(paymentline){ |
465 | paymentlines.push(paymentline.export_for_printing()); |
466 | }); |
467 | - var client = this.get('client'); |
468 | + var client = this.pos.get('selectedOrder').get('client'); |
469 | var cashier = this.pos.get('cashier') || this.pos.get('user'); |
470 | var company = this.pos.get('company'); |
471 | var shop = this.pos.get('shop'); |
472 | @@ -883,7 +888,7 @@ |
473 | total_discount: this.getDiscountTotal(), |
474 | change: this.getChange(), |
475 | name : this.getName(), |
476 | - client: client ? client.name : null , |
477 | + client: client ? client : null , |
478 | invoice_id: null, //TODO |
479 | cashier: cashier ? cashier.name : null, |
480 | date: { |
481 | @@ -919,6 +924,7 @@ |
482 | (this.get('paymentLines')).each(_.bind( function(item) { |
483 | return paymentLines.push([0, 0, item.export_as_JSON()]); |
484 | }, this)); |
485 | + var client = this.pos.get('selectedOrder').get('client'); |
486 | return { |
487 | name: this.getName(), |
488 | amount_paid: this.getPaidTotal(), |
489 | @@ -928,7 +934,7 @@ |
490 | lines: orderLines, |
491 | statement_ids: paymentLines, |
492 | pos_session_id: this.pos.get('pos_session').id, |
493 | - partner_id: this.get_client() ? this.get_client().id : false, |
494 | + partner_id: client ? client.id : undefined, |
495 | user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id, |
496 | uid: this.uid, |
497 | }; |
498 | @@ -1019,4 +1025,12 @@ |
499 | this.set({buffer:'0'}); |
500 | }, |
501 | }); |
502 | + |
503 | + module.Customer = Backbone.Model.extend({ |
504 | + }); |
505 | + |
506 | + module.CustomerCollection = Backbone.Collection.extend({ |
507 | + model: module.Customer, |
508 | + }); |
509 | + |
510 | } |
511 | |
512 | === modified file 'point_of_sale/static/src/js/screens.js' |
513 | --- point_of_sale/static/src/js/screens.js 2013-11-12 15:10:41 +0000 |
514 | +++ point_of_sale/static/src/js/screens.js 2013-11-17 01:16:28 +0000 |
515 | @@ -284,8 +284,10 @@ |
516 | this.pos_widget.client_button.hide(); |
517 | } |
518 | if(this.cashier_mode){ |
519 | + this.pos_widget.select_customer_button.show(); |
520 | this.pos_widget.close_button.show(); |
521 | }else{ |
522 | + this.pos_widget.select_customer_button.hide(); |
523 | this.pos_widget.close_button.hide(); |
524 | } |
525 | |
526 | @@ -784,10 +786,13 @@ |
527 | this.user = this.pos.get('user'); |
528 | this.company = this.pos.get('company'); |
529 | this.shop_obj = this.pos.get('shop'); |
530 | + this.client = null; |
531 | + |
532 | }, |
533 | renderElement: function() { |
534 | this._super(); |
535 | this.pos.bind('change:selectedOrder', this.change_selected_order, this); |
536 | + this.pos.get('selectedOrder').bind('change:client', this.change_client, this); |
537 | this.change_selected_order(); |
538 | }, |
539 | show: function(){ |
540 | @@ -850,6 +855,10 @@ |
541 | this.currentPaymentLines.bind('all', this.refresh, this); |
542 | this.refresh(); |
543 | }, |
544 | + change_client: function() { |
545 | + this.client = this.pos.get('selectedOrder').get('client'); |
546 | + this.refresh(); |
547 | + }, |
548 | refresh: function() { |
549 | this.currentOrder = this.pos.get('selectedOrder'); |
550 | $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this})); |
551 | @@ -874,53 +883,53 @@ |
552 | var self = this; |
553 | |
554 | if( this.pos.iface_cashdrawer && this.pos.get('selectedOrder').get('paymentLines').find( function(pl){ return pl.cashregister.get('journal').type === 'cash'; })){ |
555 | - this.pos.proxy.open_cashbox(); |
556 | + this.pos.proxy.open_cashbox(); |
557 | } |
558 | |
559 | this.set_numpad_state(this.pos_widget.numpad.state); |
560 | - |
561 | - this.add_action_button({ |
562 | - label: _t('Back'), |
563 | - icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', |
564 | - click: function(){ |
565 | - _.each(self.paymentlinewidgets, function(widget){ |
566 | - if( widget.payment_line.get_amount() === 0 ){ |
567 | - widget.payment_line.destroy(); |
568 | - } |
569 | - }); |
570 | - self.pos_widget.screen_selector.set_current_screen(self.back_screen); |
571 | - }, |
572 | - }); |
573 | - |
574 | - this.add_action_button({ |
575 | - label: _t('Validate'), |
576 | - name: 'validation', |
577 | - icon: '/point_of_sale/static/src/img/icons/png48/validate.png', |
578 | - click: function(){ |
579 | - self.validateCurrentOrder(); |
580 | - }, |
581 | - }); |
582 | - |
583 | + |
584 | + this.add_action_button({ |
585 | + label: _t('Back'), |
586 | + icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', |
587 | + click: function(){ |
588 | + _.each(self.paymentlinewidgets, function(widget){ |
589 | + if( widget.payment_line.get_amount() === 0 ){ |
590 | + widget.payment_line.destroy(); |
591 | + } |
592 | + }); |
593 | + self.pos_widget.screen_selector.set_current_screen(self.back_screen); |
594 | + }, |
595 | + }); |
596 | + |
597 | + this.add_action_button({ |
598 | + label: _t('Validate'), |
599 | + name: 'validation', |
600 | + icon: '/point_of_sale/static/src/img/icons/png48/validate.png', |
601 | + click: function(){ |
602 | + self.validateCurrentOrder(); |
603 | + }, |
604 | + }); |
605 | + |
606 | if(this.pos.iface_invoicing){ |
607 | this.add_action_button({ |
608 | - label: 'Invoice', |
609 | - name: 'invoice', |
610 | - icon: '/point_of_sale/static/src/img/icons/png48/invoice.png', |
611 | - click: function(){ |
612 | - self.validateCurrentOrder({invoice: true}); |
613 | - }, |
614 | - }); |
615 | + label: 'Invoice', |
616 | + name: 'invoice', |
617 | + icon: '/point_of_sale/static/src/img/icons/png48/invoice.png', |
618 | + click: function(){ |
619 | + self.validateCurrentOrder({invoice: true}); |
620 | + }, |
621 | + }); |
622 | } |
623 | |
624 | if( this.pos.iface_cashdrawer ){ |
625 | this.add_action_button({ |
626 | - label: _t('Cash'), |
627 | - name: 'cashbox', |
628 | - icon: '/point_of_sale/static/src/img/open-cashbox.png', |
629 | - click: function(){ |
630 | - self.pos.proxy.open_cashbox(); |
631 | - }, |
632 | - }); |
633 | + label: _t('Cash'), |
634 | + name: 'cashbox', |
635 | + icon: '/point_of_sale/static/src/img/open-cashbox.png', |
636 | + click: function(){ |
637 | + self.pos.proxy.open_cashbox(); |
638 | + }, |
639 | + }); |
640 | } |
641 | |
642 | this.updatePaymentSummary(); |
643 | @@ -966,12 +975,12 @@ |
644 | |
645 | }else{ |
646 | this.pos.push_order(currentOrder) |
647 | - if(this.pos.iface_print_via_proxy){ |
648 | - this.pos.proxy.print_receipt(currentOrder.export_for_printing()); |
649 | - this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen |
650 | - }else{ |
651 | - this.pos_widget.screen_selector.set_current_screen(this.next_screen); |
652 | - } |
653 | + if(this.pos.iface_print_via_proxy){ |
654 | + this.pos.proxy.print_receipt(currentOrder.export_for_printing()); |
655 | + this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen |
656 | + }else{ |
657 | + this.pos_widget.screen_selector.set_current_screen(this.next_screen); |
658 | + } |
659 | } |
660 | }, |
661 | bindPaymentLineEvents: function() { |
662 | @@ -1004,7 +1013,7 @@ |
663 | addPaymentLine: function(newPaymentLine) { |
664 | var self = this; |
665 | var l = new module.PaymentlineWidget(this, { |
666 | - payment_line: newPaymentLine, |
667 | + payment_line: newPaymentLine, |
668 | }); |
669 | l.on('delete_payment_line', self, function(r) { |
670 | self.deleteLine(r); |
671 | @@ -1023,14 +1032,14 @@ |
672 | this.paymentlinewidgets[i].destroy(); |
673 | } |
674 | this.paymentlinewidgets = []; |
675 | - |
676 | + |
677 | this.currentPaymentLines.each(_.bind( function(paymentLine) { |
678 | this.addPaymentLine(paymentLine); |
679 | }, this)); |
680 | this.updatePaymentSummary(); |
681 | }, |
682 | deleteLine: function(lineWidget) { |
683 | - this.currentPaymentLines.remove([lineWidget.payment_line]); |
684 | + this.currentPaymentLines.remove([lineWidget.payment_line]); |
685 | lineWidget.destroy(); |
686 | }, |
687 | updatePaymentSummary: function() { |
688 | @@ -1047,30 +1056,104 @@ |
689 | if(currentOrder.selected_orderline === undefined){ |
690 | remaining = 1; // What is this ? |
691 | } |
692 | - |
693 | + |
694 | if(this.pos_widget.action_bar){ |
695 | this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0.000001); |
696 | this.pos_widget.action_bar.set_button_disabled('invoice', remaining > 0.000001); |
697 | } |
698 | }, |
699 | set_numpad_state: function(numpadState) { |
700 | - if (this.numpadState) { |
701 | - this.numpadState.unbind('set_value', this.set_value); |
702 | - this.numpadState.unbind('change:mode', this.setNumpadMode); |
703 | - } |
704 | - this.numpadState = numpadState; |
705 | - if (this.numpadState) { |
706 | - this.numpadState.bind('set_value', this.set_value, this); |
707 | - this.numpadState.bind('change:mode', this.setNumpadMode, this); |
708 | - this.numpadState.reset(); |
709 | - this.setNumpadMode(); |
710 | - } |
711 | - }, |
712 | - setNumpadMode: function() { |
713 | - this.numpadState.set({mode: 'payment'}); |
714 | - }, |
715 | + if (this.numpadState) { |
716 | + this.numpadState.unbind('set_value', this.set_value); |
717 | + this.numpadState.unbind('change:mode', this.setNumpadMode); |
718 | + } |
719 | + this.numpadState = numpadState; |
720 | + if (this.numpadState) { |
721 | + this.numpadState.bind('set_value', this.set_value, this); |
722 | + this.numpadState.bind('change:mode', this.setNumpadMode, this); |
723 | + this.numpadState.reset(); |
724 | + this.setNumpadMode(); |
725 | + } |
726 | + }, |
727 | + setNumpadMode: function() { |
728 | + this.numpadState.set({mode: 'payment'}); |
729 | + }, |
730 | set_value: function(val) { |
731 | - this.currentPaymentLines.last().set_amount(val); |
732 | - }, |
733 | - }); |
734 | + this.currentPaymentLines.last().set_amount(val); |
735 | + }, |
736 | + }); |
737 | + module.SelectCustomerPopupWidget = module.PopUpWidget.extend({ |
738 | + template:'SelectCustomerPopupWidget', |
739 | + |
740 | + start: function(){ |
741 | + this._super(); |
742 | + var self = this; |
743 | + this.customer_list_widget = new module.CustomerListWidget(this,{ |
744 | + click_customer_action: function(customer){ |
745 | + this.pos.get('selectedOrder').set_client(customer); |
746 | + this.pos_widget.customername.refresh(); |
747 | + this.pos_widget.screen_selector.set_current_screen('products'); |
748 | + }, |
749 | + }); |
750 | + }, |
751 | + |
752 | + show: function(){ |
753 | + this._super(); |
754 | + var self = this; |
755 | + this.renderElement(); |
756 | + |
757 | + this.customer_list_widget.replace($('.placeholder-CustomerListWidget')); |
758 | + |
759 | + this.$('.button.cancel').off('click').click(function(){ |
760 | + self.pos_widget.screen_selector.set_current_screen('products'); |
761 | + }); |
762 | + |
763 | + this.customer_search(); |
764 | + }, |
765 | + |
766 | + // Customer search filter |
767 | + customer_search: function(){ |
768 | + var self = this; |
769 | + |
770 | + // find all products belonging to the current category |
771 | + var customers = this.pos.db.get_all_customers(); |
772 | + self.pos.get('customers').reset(customers); |
773 | + |
774 | + // filter customers according to the search string |
775 | + this.$('.customer-searchbox input').keyup(function(event){ |
776 | + query = $(this).val().toLowerCase(); |
777 | + if(query){ |
778 | + var customers = self.pos.db.search_customers(query); |
779 | + self.pos.get('customers').reset(customers); |
780 | + self.$('.customer-search-clear').fadeIn(); |
781 | + if(event.keyCode == 13){ |
782 | + var c = null; |
783 | + if(customers.length == 1){ |
784 | + c = self.pos.get('customers').get(customers[0]); |
785 | + } |
786 | + if(c !== null){ |
787 | + self.pos_widget.select_customer_popup.customer_list_widget.click_customer_action(c); |
788 | + self.$('.customer-search-clear').trigger('click'); |
789 | + } |
790 | + } |
791 | + }else{ |
792 | + var customers = self.pos.db.get_all_customers(); |
793 | + self.pos.get('customers').reset(customers); |
794 | + self.$('.customer-search-clear').fadeOut(); |
795 | + } |
796 | + }); |
797 | + |
798 | + this.$('.customer-searchbox input').click(function(){}); //Why ??? |
799 | + |
800 | + //reset the search when clicking on reset |
801 | + this.$('.customer-search-clear').click(function(){ |
802 | + var customers = self.pos.db.get_all_customers(); |
803 | + self.pos.get('customers').reset(customers); |
804 | + self.$('.customer-searchbox input').val('').focus(); |
805 | + self.$('.customer-search-clear').fadeOut(); |
806 | + }); |
807 | + }, |
808 | + |
809 | + }); |
810 | + |
811 | } |
812 | |
813 | === modified file 'point_of_sale/static/src/js/widgets.js' |
814 | --- point_of_sale/static/src/js/widgets.js 2013-11-12 16:05:34 +0000 |
815 | +++ point_of_sale/static/src/js/widgets.js 2013-11-17 01:16:28 +0000 |
816 | @@ -672,6 +672,27 @@ |
817 | }, |
818 | }); |
819 | |
820 | + module.CustomernameWidget = module.PosBaseWidget.extend({ |
821 | + template: 'CustomernameWidget', |
822 | + init: function(parent, options){ |
823 | + var options = options || {}; |
824 | + this._super(parent,options); |
825 | + this.pos.bind('change:selectedOrder', this.renderElement, this); |
826 | + }, |
827 | + refresh: function(){ |
828 | + this.renderElement(); |
829 | + }, |
830 | + get_name: function(){ |
831 | + var user; |
832 | + customer = this.pos.get('selectedOrder').get_client(); |
833 | + if(customer){ |
834 | + return customer.name; |
835 | + }else{ |
836 | + return ""; |
837 | + } |
838 | + }, |
839 | + }); |
840 | + |
841 | module.HeaderButtonWidget = module.PosBaseWidget.extend({ |
842 | template: 'HeaderButtonWidget', |
843 | init: function(parent, options){ |
844 | @@ -961,6 +982,9 @@ |
845 | this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {}); |
846 | this.error_invoice_transfer_popup.appendTo($('.point-of-sale')); |
847 | |
848 | + this.select_customer_popup = new module.SelectCustomerPopupWidget(this, {}); |
849 | + this.select_customer_popup.appendTo($('.point-of-sale')); |
850 | + |
851 | // -------- Misc --------- |
852 | |
853 | this.notification = new module.SynchNotificationWidget(this,{}); |
854 | @@ -1001,7 +1025,15 @@ |
855 | }); |
856 | this.client_button.appendTo(this.$('#rightheader')); |
857 | |
858 | - |
859 | + this.select_customer_button = new module.HeaderButtonWidget(this,{ |
860 | + label:'Select Customer', |
861 | + action: function(){ self.screen_selector.show_popup('select-customer'); }, |
862 | + }); |
863 | + this.select_customer_button.appendTo(this.$('#rightheader')); |
864 | + |
865 | + this.customername = new module.CustomernameWidget(this,{}); |
866 | + this.customername.appendTo(this.$('#rightheader')); |
867 | + |
868 | // -------- Screen Selector --------- |
869 | |
870 | this.screen_selector = new module.ScreenSelector({ |
871 | @@ -1024,6 +1056,7 @@ |
872 | 'choose-receipt': this.choose_receipt_popup, |
873 | 'error-no-client': this.error_no_client_popup, |
874 | 'error-invoice-transfer': this.error_invoice_transfer_popup, |
875 | + 'select-customer': this.select_customer_popup, |
876 | }, |
877 | default_client_screen: 'welcome', |
878 | default_cashier_screen: 'products', |
879 | @@ -1133,4 +1166,59 @@ |
880 | this._super(); |
881 | } |
882 | }); |
883 | + module.CustomerWidget = module.PosBaseWidget.extend({ |
884 | + template: 'CustomerWidget', |
885 | + init: function(parent, options) { |
886 | + this._super(parent,options); |
887 | + this.model = options.model; |
888 | + this.click_customer_action = options.click_customer_action; |
889 | + }, |
890 | + renderElement: function() { |
891 | + this._super(); |
892 | + var self = this; |
893 | + $("a", this.$el).click(function(e){ |
894 | + if(self.click_customer_action){ |
895 | + self.click_customer_action(self.model.toJSON()); |
896 | + } |
897 | + }); |
898 | + }, |
899 | + }); |
900 | + |
901 | + module.CustomerListWidget = module.ScreenWidget.extend({ |
902 | + template:'CustomerListWidget', |
903 | + init: function(parent, options) { |
904 | + var self = this; |
905 | + this._super(parent,options); |
906 | + this.model = options.model; |
907 | + this.customer_list = []; |
908 | + this.next_screen = options.next_screen || false; |
909 | + this.click_customer_action = options.click_customer_action; |
910 | + |
911 | + var customers = self.pos.db.get_all_customers(); |
912 | + self.pos.get('customers').reset(customers); |
913 | + this.pos.get('customers').bind('reset', function(){ |
914 | + self.renderElement(); |
915 | + }); |
916 | + }, |
917 | + renderElement: function() { |
918 | + var self = this; |
919 | + this._super(); |
920 | + this.customer_list = []; |
921 | + |
922 | + this.pos.get('customers') |
923 | + .chain() |
924 | + .map(function(customer) { |
925 | + var customer = new module.CustomerWidget(self, { |
926 | + model: customer, |
927 | + next_screen: 'products', |
928 | + click_customer_action: self.click_customer_action, |
929 | + }) |
930 | + self.customer_list.push(customer); |
931 | + return customer; |
932 | + }) |
933 | + .invoke('appendTo', this.$('.customer-list')); |
934 | + |
935 | + }, |
936 | + }); |
937 | + |
938 | } |
939 | |
940 | === modified file 'point_of_sale/static/src/xml/pos.xml' |
941 | --- point_of_sale/static/src/xml/pos.xml 2013-11-12 14:04:57 +0000 |
942 | +++ point_of_sale/static/src/xml/pos.xml 2013-11-17 01:16:28 +0000 |
943 | @@ -582,6 +582,12 @@ |
944 | </span> |
945 | </t> |
946 | |
947 | + <t t-name="CustomernameWidget"> |
948 | + <span class="customername"> |
949 | + <t t-esc="widget.get_name()" /> |
950 | + </span> |
951 | + </t> |
952 | + |
953 | <t t-name="PosTicket"> |
954 | <div class="pos-sale-ticket"> |
955 | |
956 | @@ -589,9 +595,18 @@ |
957 | Date.CultureInfo.formatPatterns.longTime)"/> <t t-esc="widget.currentOrder.attributes.name"/></div> |
958 | <br /> |
959 | <t t-esc="widget.company.name"/><br /> |
960 | + <t t-if="widget.company.vat"> |
961 | + TIN: <t t-esc="widget.company.vat"/><br /> |
962 | + </t> |
963 | Phone: <t t-esc="widget.company.phone || ''"/><br /> |
964 | User: <t t-esc="widget.user.name"/><br /> |
965 | Shop: <t t-esc="widget.shop_obj.name"/><br /> |
966 | + <t t-if="widget.client"> |
967 | + Customer: <t t-esc="widget.client.name"/><br /> |
968 | + <t t-if="widget.client.vat"> |
969 | + Customer TIN: <t t-esc="widget.client.vat"/><br /> |
970 | + </t> |
971 | + </t> |
972 | <br /> |
973 | <table> |
974 | <colgroup> |
975 | @@ -780,5 +795,67 @@ |
976 | <p class="close_button">close</p> |
977 | </div> |
978 | </t> |
979 | + <t t-name="CustomerListWidget"> |
980 | + <div class='customer-list-container'> |
981 | + <div class="customer-list-scroller"> |
982 | + <ol id="customers-screen-ol" class="customer-list"> |
983 | + </ol> |
984 | + </div> |
985 | + </div> |
986 | + </t> |
987 | + |
988 | + <t t-name="SelectCustomerPopupWidget"> |
989 | + <div class="modal-dialog"> |
990 | + <div class="popup-selection"> |
991 | + <div class="customer-title"> |
992 | + Customer Selection |
993 | + </div> |
994 | + |
995 | + <div class="customer-searchbox"> |
996 | + <input placeholder="Search Customers" /> |
997 | + <img class="customer-search-clear" src="/point_of_sale/static/src/img/search_reset.gif" /> |
998 | + </div> |
999 | + |
1000 | + <div class="content-container"> |
1001 | + <span class="placeholder-CustomerListWidget" /> |
1002 | + </div> |
1003 | + |
1004 | + <div id="customer-cancel" class = "button cancel"> |
1005 | + Cancel |
1006 | + </div> |
1007 | + </div> |
1008 | + </div> |
1009 | + </t> |
1010 | + |
1011 | + <t t-name="CustomerWidget"> |
1012 | + <li class='customer'> |
1013 | + <a href="#"> |
1014 | + <span class="customer-field customer-name"> |
1015 | + <t t-esc="widget.model.get('name')"/> |
1016 | + </span> |
1017 | + <span class="customer-field"> |
1018 | + <t t-if="widget.model.get('vat')"> |
1019 | + <t t-esc="widget.model.get('vat')"/> |
1020 | + </t> |
1021 | + </span> |
1022 | + <span class="customer-field"> |
1023 | + <t t-if="widget.model.get('email')"> |
1024 | + <t t-esc="widget.model.get('email')"/> |
1025 | + </t> |
1026 | + </span> |
1027 | + <span class="customer-field customer-phone"> |
1028 | + <t t-if="widget.model.get('phone')"> |
1029 | + <t t-esc="widget.model.get('phone')"/> |
1030 | + </t> |
1031 | + </span> |
1032 | + <span class="customer-field customer-phone"> |
1033 | + <t t-if="widget.model.get('mobile')"> |
1034 | + <t t-esc="widget.model.get('mobile')"/> |
1035 | + </t> |
1036 | + </span> |
1037 | + </a> |
1038 | + </li> |
1039 | + </t> |
1040 | + |
1041 | |
1042 | </templates> |
1043 | |
1044 | === modified file 'project_issue/project_issue_view.xml' |
1045 | --- project_issue/project_issue_view.xml 2013-10-17 11:35:03 +0000 |
1046 | +++ project_issue/project_issue_view.xml 2013-11-17 01:16:28 +0000 |
1047 | @@ -81,6 +81,8 @@ |
1048 | <label for="task_id" groups="base.group_user"/> |
1049 | <div groups="base.group_user"> |
1050 | <field name="task_id" on_change="onchange_task_id(task_id)" class="oe_inline" context="{'default_project_id':project_id}"/> |
1051 | + <button string="Convert To Task" name="convert_issue_task" icon="gtk-index" type="object" |
1052 | + attrs="{'invisible':[('task_id','!=',False)]}" /> |
1053 | <field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}" class="oe_inline"/> |
1054 | </div> |
1055 | </group> |