[FIX] Tour: modal bug fixes
[odoo/odoo.git] / addons / point_of_sale / point_of_sale.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from datetime import datetime
23 from dateutil.relativedelta import relativedelta
24 from decimal import Decimal
25 import logging
26 import pdb
27 import time
28
29 import openerp
30 from openerp import tools
31 from openerp.osv import fields, osv
32 from openerp.tools.translate import _
33
34 import openerp.addons.decimal_precision as dp
35 import openerp.addons.product.product
36
37 _logger = logging.getLogger(__name__)
38
39 class pos_config(osv.osv):
40     _name = 'pos.config'
41
42     POS_CONFIG_STATE = [
43         ('active', 'Active'),
44         ('inactive', 'Inactive'),
45         ('deprecated', 'Deprecated')
46     ]
47
48     _columns = {
49         'name' : fields.char('Point of Sale Name', size=32, select=1,
50              required=True, help="An internal identification of the point of sale"),
51         'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', 
52              'pos_config_id', 'journal_id', 'Available Payment Methods',
53              domain="[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",),
54         'warehouse_id' : fields.many2one('stock.warehouse', 'Warehouse',
55              required=True),
56         'journal_id' : fields.many2one('account.journal', 'Sale Journal',
57              domain=[('type', '=', 'sale')],
58              help="Accounting journal used to post sales entries."),
59         'iface_self_checkout' : fields.boolean('Self Checkout Mode',
60              help="Check this if this point of sale should open by default in a self checkout mode. If unchecked, OpenERP uses the normal cashier mode by default."),
61         'iface_cashdrawer' : fields.boolean('Cashdrawer Interface'),
62         'iface_payment_terminal' : fields.boolean('Payment Terminal Interface'),
63         'iface_electronic_scale' : fields.boolean('Electronic Scale Interface'),
64         'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'),
65         'iface_print_via_proxy' : fields.boolean('Print via Proxy'),
66         'iface_invoicing': fields.boolean('Invoicing',help='Enables invoice generation from the Point of Sale'),
67
68         'state' : fields.selection(POS_CONFIG_STATE, 'Status', required=True, readonly=True),
69         'sequence_id' : fields.many2one('ir.sequence', 'Order IDs Sequence', readonly=True,
70             help="This sequence is automatically created by OpenERP but you can change it "\
71                 "to customize the reference numbers of your orders."),
72         'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
73         'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
74         'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True)
75     }
76
77     def _check_cash_control(self, cr, uid, ids, context=None):
78         return all(
79             (sum(int(journal.cash_control) for journal in record.journal_ids) <= 1)
80             for record in self.browse(cr, uid, ids, context=context)
81         )
82
83     _constraints = [
84         (_check_cash_control, "You cannot have two cash controls in one Point Of Sale !", ['journal_ids']),
85     ]
86
87     def copy(self, cr, uid, id, default=None, context=None):
88         if not default:
89             default = {}
90         d = {
91             'sequence_id' : False,
92         }
93         d.update(default)
94         return super(pos_config, self).copy(cr, uid, id, d, context=context)
95
96
97     def name_get(self, cr, uid, ids, context=None):
98         result = []
99         states = {
100             'opening_control': _('Opening Control'),
101             'opened': _('In Progress'),
102             'closing_control': _('Closing Control'),
103             'closed': _('Closed & Posted'),
104         }
105         for record in self.browse(cr, uid, ids, context=context):
106             if (not record.session_ids) or (record.session_ids[0].state=='closed'):
107                 result.append((record.id, record.name+' ('+_('not used')+')'))
108                 continue
109             session = record.session_ids[0]
110             result.append((record.id, record.name + ' ('+session.user_id.name+')')) #, '+states[session.state]+')'))
111         return result
112
113     def _default_sale_journal(self, cr, uid, context=None):
114         company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
115         res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', company_id)], limit=1, context=context)
116         return res and res[0] or False
117
118     def _default_warehouse(self, cr, uid, context=None):
119         user = self.pool.get('res.users').browse(cr, uid, uid, context)
120         res = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', user.company_id.id)], limit=1, context=context)
121         return res and res[0] or False
122
123     def _default_pricelist(self, cr, uid, context=None):
124         res = self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'sale')], limit=1, context=context)
125         return res and res[0] or False
126
127     _defaults = {
128         'state' : POS_CONFIG_STATE[0][0],
129         'warehouse_id': _default_warehouse,
130         'journal_id': _default_sale_journal,
131         'group_by' : True,
132         'pricelist_id': _default_pricelist,
133         'iface_invoicing': True,
134     }
135
136     def set_active(self, cr, uid, ids, context=None):
137         return self.write(cr, uid, ids, {'state' : 'active'}, context=context)
138
139     def set_inactive(self, cr, uid, ids, context=None):
140         return self.write(cr, uid, ids, {'state' : 'inactive'}, context=context)
141
142     def set_deprecate(self, cr, uid, ids, context=None):
143         return self.write(cr, uid, ids, {'state' : 'deprecated'}, context=context)
144
145     def create(self, cr, uid, values, context=None):
146         proxy = self.pool.get('ir.sequence')
147         sequence_values = dict(
148             name='PoS %s' % values['name'],
149             padding=5,
150             prefix="%s/"  % values['name'],
151         )
152         sequence_id = proxy.create(cr, uid, sequence_values, context=context)
153         values['sequence_id'] = sequence_id
154         return super(pos_config, self).create(cr, uid, values, context=context)
155
156     def unlink(self, cr, uid, ids, context=None):
157         for obj in self.browse(cr, uid, ids, context=context):
158             if obj.sequence_id:
159                 obj.sequence_id.unlink()
160         return super(pos_config, self).unlink(cr, uid, ids, context=context)
161
162 class pos_session(osv.osv):
163     _name = 'pos.session'
164     _order = 'id desc'
165
166     POS_SESSION_STATE = [
167         ('opening_control', 'Opening Control'),  # Signal open
168         ('opened', 'In Progress'),                    # Signal closing
169         ('closing_control', 'Closing Control'),  # Signal close
170         ('closed', 'Closed & Posted'),
171     ]
172
173     def _compute_cash_all(self, cr, uid, ids, fieldnames, args, context=None):
174         result = dict()
175
176         for record in self.browse(cr, uid, ids, context=context):
177             result[record.id] = {
178                 'cash_journal_id' : False,
179                 'cash_register_id' : False,
180                 'cash_control' : False,
181             }
182             for st in record.statement_ids:
183                 if st.journal_id.cash_control == True:
184                     result[record.id]['cash_control'] = True
185                     result[record.id]['cash_journal_id'] = st.journal_id.id
186                     result[record.id]['cash_register_id'] = st.id
187
188         return result
189
190     _columns = {
191         'config_id' : fields.many2one('pos.config', 'Point of Sale',
192                                       help="The physical point of sale you will use.",
193                                       required=True,
194                                       select=1,
195                                       domain="[('state', '=', 'active')]",
196                                      ),
197
198         'name' : fields.char('Session ID', size=32, required=True, readonly=True),
199         'user_id' : fields.many2one('res.users', 'Responsible',
200                                     required=True,
201                                     select=1,
202                                     readonly=True,
203                                     states={'opening_control' : [('readonly', False)]}
204                                    ),
205         'start_at' : fields.datetime('Opening Date', readonly=True), 
206         'stop_at' : fields.datetime('Closing Date', readonly=True),
207
208         'state' : fields.selection(POS_SESSION_STATE, 'Status',
209                 required=True, readonly=True,
210                 select=1),
211
212         'cash_control' : fields.function(_compute_cash_all,
213                                          multi='cash',
214                                          type='boolean', string='Has Cash Control'),
215         'cash_journal_id' : fields.function(_compute_cash_all,
216                                             multi='cash',
217                                             type='many2one', relation='account.journal',
218                                             string='Cash Journal', store=True),
219         'cash_register_id' : fields.function(_compute_cash_all,
220                                              multi='cash',
221                                              type='many2one', relation='account.bank.statement',
222                                              string='Cash Register', store=True),
223
224         'opening_details_ids' : fields.related('cash_register_id', 'opening_details_ids', 
225                 type='one2many', relation='account.cashbox.line',
226                 string='Opening Cash Control'),
227         'details_ids' : fields.related('cash_register_id', 'details_ids', 
228                 type='one2many', relation='account.cashbox.line',
229                 string='Cash Control'),
230
231         'cash_register_balance_end_real' : fields.related('cash_register_id', 'balance_end_real',
232                 type='float',
233                 digits_compute=dp.get_precision('Account'),
234                 string="Ending Balance",
235                 help="Computed using the cash control lines",
236                 readonly=True),
237         'cash_register_balance_start' : fields.related('cash_register_id', 'balance_start',
238                 type='float',
239                 digits_compute=dp.get_precision('Account'),
240                 string="Starting Balance",
241                 help="Computed using the cash control at the opening.",
242                 readonly=True),
243         'cash_register_total_entry_encoding' : fields.related('cash_register_id', 'total_entry_encoding',
244                 string='Total Cash Transaction',
245                 readonly=True),
246         'cash_register_balance_end' : fields.related('cash_register_id', 'balance_end',
247                 type='float',
248                 digits_compute=dp.get_precision('Account'),
249                 string="Computed Balance",
250                 help="Computed with the initial cash control and the sum of all payments.",
251                 readonly=True),
252         'cash_register_difference' : fields.related('cash_register_id', 'difference',
253                 type='float',
254                 string='Difference',
255                 help="Difference between the counted cash control at the closing and the computed balance.",
256                 readonly=True),
257
258         'journal_ids' : fields.related('config_id', 'journal_ids',
259                                        type='many2many',
260                                        readonly=True,
261                                        relation='account.journal',
262                                        string='Available Payment Methods'),
263         'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'),
264
265         'statement_ids' : fields.one2many('account.bank.statement', 'pos_session_id', 'Bank Statement', readonly=True),
266     }
267
268     _defaults = {
269         'name' : '/',
270         'user_id' : lambda obj, cr, uid, context: uid,
271         'state' : 'opening_control',
272     }
273
274     _sql_constraints = [
275         ('uniq_name', 'unique(name)', "The name of this POS Session must be unique !"),
276     ]
277
278     def _check_unicity(self, cr, uid, ids, context=None):
279         for session in self.browse(cr, uid, ids, context=None):
280             # open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
281             domain = [
282                 ('state', 'not in', ('closed','closing_control')),
283                 ('user_id', '=', session.user_id.id)
284             ]
285             count = self.search_count(cr, uid, domain, context=context)
286             if count>1:
287                 return False
288         return True
289
290     def _check_pos_config(self, cr, uid, ids, context=None):
291         for session in self.browse(cr, uid, ids, context=None):
292             domain = [
293                 ('state', '!=', 'closed'),
294                 ('config_id', '=', session.config_id.id)
295             ]
296             count = self.search_count(cr, uid, domain, context=context)
297             if count>1:
298                 return False
299         return True
300
301     _constraints = [
302         (_check_unicity, "You cannot create two active sessions with the same responsible!", ['user_id', 'state']),
303         (_check_pos_config, "You cannot create two active sessions related to the same point of sale!", ['config_id']),
304     ]
305
306     def create(self, cr, uid, values, context=None):
307         context = context or {}
308         config_id = values.get('config_id', False) or context.get('default_config_id', False)
309         if not config_id:
310             raise osv.except_osv( _('Error!'),
311                 _("You should assign a Point of Sale to your session."))
312
313         # journal_id is not required on the pos_config because it does not
314         # exists at the installation. If nothing is configured at the
315         # installation we do the minimal configuration. Impossible to do in
316         # the .xml files as the CoA is not yet installed.
317         jobj = self.pool.get('pos.config')
318         pos_config = jobj.browse(cr, uid, config_id, context=context)
319         context.update({'company_id': pos_config.warehouse_id.company_id.id})
320         if not pos_config.journal_id:
321             jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id']
322             if jid:
323                 jobj.write(cr, uid, [pos_config.id], {'journal_id': jid}, context=context)
324             else:
325                 raise osv.except_osv( _('error!'),
326                     _("Unable to open the session. You have to assign a sale journal to your point of sale."))
327
328         # define some cash journal if no payment method exists
329         if not pos_config.journal_ids:
330             journal_proxy = self.pool.get('account.journal')
331             cashids = journal_proxy.search(cr, uid, [('journal_user', '=', True), ('type','=','cash')], context=context)
332             if not cashids:
333                 cashids = journal_proxy.search(cr, uid, [('type', '=', 'cash')], context=context)
334                 if not cashids:
335                     cashids = journal_proxy.search(cr, uid, [('journal_user','=',True)], context=context)
336
337             jobj.write(cr, uid, [pos_config.id], {'journal_ids': [(6,0, cashids)]})
338
339
340         pos_config = jobj.browse(cr, uid, config_id, context=context)
341         bank_statement_ids = []
342         for journal in pos_config.journal_ids:
343             bank_values = {
344                 'journal_id' : journal.id,
345                 'user_id' : uid,
346                 'company_id' : pos_config.warehouse_id.company_id.id
347             }
348             statement_id = self.pool.get('account.bank.statement').create(cr, uid, bank_values, context=context)
349             bank_statement_ids.append(statement_id)
350
351         values.update({
352             'name' : pos_config.sequence_id._next(),
353             'statement_ids' : [(6, 0, bank_statement_ids)],
354             'config_id': config_id
355         })
356
357         return super(pos_session, self).create(cr, uid, values, context=context)
358
359     def unlink(self, cr, uid, ids, context=None):
360         for obj in self.browse(cr, uid, ids, context=context):
361             for statement in obj.statement_ids:
362                 statement.unlink(context=context)
363         return True
364
365
366     def open_cb(self, cr, uid, ids, context=None):
367         """
368         call the Point Of Sale interface and set the pos.session to 'opened' (in progress)
369         """
370         if context is None:
371             context = dict()
372
373         if isinstance(ids, (int, long)):
374             ids = [ids]
375
376         this_record = self.browse(cr, uid, ids[0], context=context)
377         this_record.signal_workflow('open')
378
379         context.update(active_id=this_record.id)
380
381         return {
382             'type' : 'ir.actions.client',
383             'name' : _('Start Point Of Sale'),
384             'tag' : 'pos.ui',
385             'context' : context,
386         }
387
388     def wkf_action_open(self, cr, uid, ids, context=None):
389         # second browse because we need to refetch the data from the DB for cash_register_id
390         for record in self.browse(cr, uid, ids, context=context):
391             values = {}
392             if not record.start_at:
393                 values['start_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
394             values['state'] = 'opened'
395             record.write(values, context=context)
396             for st in record.statement_ids:
397                 st.button_open(context=context)
398
399         return self.open_frontend_cb(cr, uid, ids, context=context)
400
401     def wkf_action_opening_control(self, cr, uid, ids, context=None):
402         return self.write(cr, uid, ids, {'state' : 'opening_control'}, context=context)
403
404     def wkf_action_closing_control(self, cr, uid, ids, context=None):
405         for session in self.browse(cr, uid, ids, context=context):
406             for statement in session.statement_ids:
407                 if (statement != session.cash_register_id) and (statement.balance_end != statement.balance_end_real):
408                     self.pool.get('account.bank.statement').write(cr, uid, [statement.id], {'balance_end_real': statement.balance_end})
409         return self.write(cr, uid, ids, {'state' : 'closing_control', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
410
411     def wkf_action_close(self, cr, uid, ids, context=None):
412         # Close CashBox
413         bsl = self.pool.get('account.bank.statement.line')
414         for record in self.browse(cr, uid, ids, context=context):
415             for st in record.statement_ids:
416                 if abs(st.difference) > st.journal_id.amount_authorized_diff:
417                     # The pos manager can close statements with maximums.
418                     if not self.pool.get('ir.model.access').check_groups(cr, uid, "point_of_sale.group_pos_manager"):
419                         raise osv.except_osv( _('Error!'),
420                             _("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))
421                 if (st.journal_id.type not in ['bank', 'cash']):
422                     raise osv.except_osv(_('Error!'), 
423                         _("The type of the journal for your payment method should be bank or cash "))
424                 if st.difference and st.journal_id.cash_control == True:
425                     if st.difference > 0.0:
426                         name= _('Point of Sale Profit')
427                         account_id = st.journal_id.profit_account_id.id
428                     else:
429                         account_id = st.journal_id.loss_account_id.id
430                         name= _('Point of Sale Loss')
431                     if not account_id:
432                         raise osv.except_osv( _('Error!'),
433                         _("Please set your profit and loss accounts on your payment method '%s'. This will allow OpenERP to post the difference of %.2f in your ending balance. To close this session, you can update the 'Closing Cash Control' to avoid any difference.") % (st.journal_id.name,st.difference))
434                     bsl.create(cr, uid, {
435                         'statement_id': st.id,
436                         'amount': st.difference,
437                         'ref': record.name,
438                         'name': name,
439                         'account_id': account_id
440                     }, context=context)
441
442                 if st.journal_id.type == 'bank':
443                     st.write({'balance_end_real' : st.balance_end})
444                 getattr(st, 'button_confirm_%s' % st.journal_id.type)(context=context)
445         self._confirm_orders(cr, uid, ids, context=context)
446         self.write(cr, uid, ids, {'state' : 'closed'}, context=context)
447
448         obj = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'point_of_sale', 'menu_point_root')[1]
449         return {
450             'type' : 'ir.actions.client',
451             'name' : 'Point of Sale Menu',
452             'tag' : 'reload',
453             'params' : {'menu_id': obj},
454         }
455
456     def _confirm_orders(self, cr, uid, ids, context=None):
457         account_move_obj = self.pool.get('account.move')
458         pos_order_obj = self.pool.get('pos.order')
459         for session in self.browse(cr, uid, ids, context=context):
460             order_ids = [order.id for order in session.order_ids if order.state == 'paid']
461
462             move_id = account_move_obj.create(cr, uid, {'ref' : session.name, 'journal_id' : session.config_id.journal_id.id, }, context=context)
463
464             pos_order_obj._create_account_move_line(cr, uid, order_ids, session, move_id, context=context)
465
466             for order in session.order_ids:
467                 if order.state not in ('paid', 'invoiced'):
468                     raise osv.except_osv(
469                         _('Error!'),
470                         _("You cannot confirm all orders of this session, because they have not the 'paid' status"))
471                 else:
472                     pos_order_obj.signal_done(cr, uid, [order.id])
473
474         return True
475
476     def open_frontend_cb(self, cr, uid, ids, context=None):
477         if not context:
478             context = {}
479         if not ids:
480             return {}
481         for session in self.browse(cr, uid, ids, context=context):
482             if session.user_id.id != uid:
483                 raise osv.except_osv(
484                         _('Error!'),
485                         _("You cannot use the session of another users. This session is owned by %s. Please first close this one to use this point of sale." % session.user_id.name))
486         context.update({'active_id': ids[0]})
487         return {
488             'type' : 'ir.actions.client',
489             'name' : _('Start Point Of Sale'),
490             'tag' : 'pos.ui',
491             'context' : context,
492         }
493
494 class pos_order(osv.osv):
495     _name = "pos.order"
496     _description = "Point of Sale"
497     _order = "id desc"
498
499     def create_from_ui(self, cr, uid, orders, context=None):
500         #_logger.info("orders: %r", orders)
501         order_ids = []
502         for tmp_order in orders:
503             to_invoice = tmp_order['to_invoice']
504             order = tmp_order['data']
505
506
507             order_id = self.create(cr, uid, {
508                 'name': order['name'],
509                 'user_id': order['user_id'] or False,
510                 'session_id': order['pos_session_id'],
511                 'lines': order['lines'],
512                 'pos_reference':order['name'],
513                 'partner_id': order['partner_id'] or False
514             }, context)
515             for payments in order['statement_ids']:
516                 payment = payments[2]
517                 self.add_payment(cr, uid, order_id, {
518                     'amount': payment['amount'] or 0.0,
519                     'payment_date': payment['name'],
520                     'statement_id': payment['statement_id'],
521                     'payment_name': payment.get('note', False),
522                     'journal': payment['journal_id']
523                 }, context=context)
524
525             if order['amount_return']:
526                 session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context)
527                 cash_journal = session.cash_journal_id
528                 cash_statement = False
529                 if not cash_journal:
530                     cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids)
531                     if not len(cash_journal_ids):
532                         raise osv.except_osv( _('error!'),
533                             _("No cash statement found for this session. Unable to record returned cash."))
534                     cash_journal = cash_journal_ids[0].journal_id
535                 self.add_payment(cr, uid, order_id, {
536                     'amount': -order['amount_return'],
537                     'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'),
538                     'payment_name': _('return'),
539                     'journal': cash_journal.id,
540                 }, context=context)
541             order_ids.append(order_id)
542             self.signal_paid(cr, uid, [order_id])
543
544             if to_invoice:
545                 self.action_invoice(cr, uid, [order_id], context)
546                 order_obj = self.browse(cr, uid, order_id, context)
547                 self.pool['account.invoice'].signal_invoice_open(cr, uid, [order_obj.invoice_id.id])
548
549         return order_ids
550
551     def write(self, cr, uid, ids, vals, context=None):
552         res = super(pos_order, self).write(cr, uid, ids, vals, context=context)
553         #If you change the partner of the PoS order, change also the partner of the associated bank statement lines
554         partner_obj = self.pool.get('res.partner')
555         bsl_obj = self.pool.get("account.bank.statement.line")
556         if 'partner_id' in vals:
557             for posorder in self.browse(cr, uid, ids, context=context):
558                 if posorder.invoice_id:
559                     raise osv.except_osv( _('Error!'), _("You cannot change the partner of a POS order for which an invoice has already been issued."))
560                 if vals['partner_id']:
561                     p_id = partner_obj.browse(cr, uid, vals['partner_id'], context=context)
562                     part_id = partner_obj._find_accounting_partner(p_id).id
563                 else:
564                     part_id = False
565                 bsl_ids = [x.id for x in posorder.statement_ids]
566                 bsl_obj.write(cr, uid, bsl_ids, {'partner_id': part_id}, context=context)
567         return res
568
569     def unlink(self, cr, uid, ids, context=None):
570         for rec in self.browse(cr, uid, ids, context=context):
571             if rec.state not in ('draft','cancel'):
572                 raise osv.except_osv(_('Unable to Delete!'), _('In order to delete a sale, it must be new or cancelled.'))
573         return super(pos_order, self).unlink(cr, uid, ids, context=context)
574
575     def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
576         if not part:
577             return {'value': {}}
578         pricelist = self.pool.get('res.partner').browse(cr, uid, part, context=context).property_product_pricelist.id
579         return {'value': {'pricelist_id': pricelist}}
580
581     def _amount_all(self, cr, uid, ids, name, args, context=None):
582         tax_obj = self.pool.get('account.tax')
583         cur_obj = self.pool.get('res.currency')
584         res = {}
585         for order in self.browse(cr, uid, ids, context=context):
586             res[order.id] = {
587                 'amount_paid': 0.0,
588                 'amount_return':0.0,
589                 'amount_tax':0.0,
590             }
591             val1 = val2 = 0.0
592             cur = order.pricelist_id.currency_id
593             for payment in order.statement_ids:
594                 res[order.id]['amount_paid'] +=  payment.amount
595                 res[order.id]['amount_return'] += (payment.amount < 0 and payment.amount or 0)
596             for line in order.lines:
597                 val1 += line.price_subtotal_incl
598                 val2 += line.price_subtotal
599             res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val1-val2)
600             res[order.id]['amount_total'] = cur_obj.round(cr, uid, cur, val1)
601         return res
602
603     def copy(self, cr, uid, id, default=None, context=None):
604         if not default:
605             default = {}
606         d = {
607             'state': 'draft',
608             'invoice_id': False,
609             'account_move': False,
610             'picking_id': False,
611             'statement_ids': [],
612             'nb_print': 0,
613             'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order'),
614         }
615         d.update(default)
616         return super(pos_order, self).copy(cr, uid, id, d, context=context)
617
618     _columns = {
619         'name': fields.char('Order Ref', size=64, required=True, readonly=True),
620         'company_id':fields.many2one('res.company', 'Company', required=True, readonly=True),
621         'warehouse_id': fields.related('session_id', 'config_id', 'warehouse_id', relation='stock.warehouse', type='many2one', string='Warehouse', store=True, readonly=True),
622         'date_order': fields.datetime('Order Date', readonly=True, select=True),
623         'user_id': fields.many2one('res.users', 'Salesman', help="Person who uses the the cash register. It can be a reliever, a student or an interim employee."),
624         'amount_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Point Of Sale'), multi='all'),
625         'amount_total': fields.function(_amount_all, string='Total', multi='all'),
626         'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits_compute=dp.get_precision('Point Of Sale'), multi='all'),
627         'amount_return': fields.function(_amount_all, 'Returned', digits_compute=dp.get_precision('Point Of Sale'), multi='all'),
628         'lines': fields.one2many('pos.order.line', 'order_id', 'Order Lines', states={'draft': [('readonly', False)]}, readonly=True),
629         'statement_ids': fields.one2many('account.bank.statement.line', 'pos_statement_id', 'Payments', states={'draft': [('readonly', False)]}, readonly=True),
630         'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True),
631         'partner_id': fields.many2one('res.partner', 'Customer', change_default=True, select=1, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]}),
632
633         'session_id' : fields.many2one('pos.session', 'Session', 
634                                         #required=True,
635                                         select=1,
636                                         domain="[('state', '=', 'opened')]",
637                                         states={'draft' : [('readonly', False)]},
638                                         readonly=True),
639
640         'state': fields.selection([('draft', 'New'),
641                                    ('cancel', 'Cancelled'),
642                                    ('paid', 'Paid'),
643                                    ('done', 'Posted'),
644                                    ('invoiced', 'Invoiced')],
645                                   'Status', readonly=True),
646
647         'invoice_id': fields.many2one('account.invoice', 'Invoice'),
648         'account_move': fields.many2one('account.move', 'Journal Entry', readonly=True),
649         'picking_id': fields.many2one('stock.picking', 'Picking', readonly=True),
650         'note': fields.text('Internal Notes'),
651         'nb_print': fields.integer('Number of Print', readonly=True),
652         'pos_reference': fields.char('Receipt Ref', size=64, readonly=True),
653         'sale_journal': fields.related('session_id', 'config_id', 'journal_id', relation='account.journal', type='many2one', string='Sale Journal', store=True, readonly=True),
654     }
655
656     def _default_session(self, cr, uid, context=None):
657         so = self.pool.get('pos.session')
658         session_ids = so.search(cr, uid, [('state','=', 'opened'), ('user_id','=',uid)], context=context)
659         return session_ids and session_ids[0] or False
660
661     def _default_pricelist(self, cr, uid, context=None):
662         session_ids = self._default_session(cr, uid, context) 
663         if session_ids:
664             session_record = self.pool.get('pos.session').browse(cr, uid, session_ids, context=context)
665             return session_record.config_id.pricelist_id and session_record.config_id.pricelist_id.id or False
666         return False
667
668     _defaults = {
669         'user_id': lambda self, cr, uid, context: uid,
670         'state': 'draft',
671         'name': '/', 
672         'date_order': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
673         'nb_print': 0,
674         'session_id': _default_session,
675         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
676         'pricelist_id': _default_pricelist,
677     }
678
679     def create(self, cr, uid, values, context=None):
680         values['name'] = self.pool.get('ir.sequence').get(cr, uid, 'pos.order')
681         return super(pos_order, self).create(cr, uid, values, context=context)
682
683     def test_paid(self, cr, uid, ids, context=None):
684         """A Point of Sale is paid when the sum
685         @return: True
686         """
687         for order in self.browse(cr, uid, ids, context=context):
688             if order.lines and not order.amount_total:
689                 return True
690             if (not order.lines) or (not order.statement_ids) or \
691                 (abs(order.amount_total-order.amount_paid) > 0.00001):
692                 return False
693         return True
694
695     def create_picking(self, cr, uid, ids, context=None):
696         """Create a picking for each order and validate it."""
697         picking_obj = self.pool.get('stock.picking')
698         partner_obj = self.pool.get('res.partner')
699         move_obj = self.pool.get('stock.move')
700
701         for order in self.browse(cr, uid, ids, context=context):
702             if not order.state=='draft':
703                 continue
704             addr = order.partner_id and partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery']) or {}
705             picking_id = picking_obj.create(cr, uid, {
706                 'origin': order.name,
707                 'partner_id': addr.get('delivery',False),
708                 'type': 'out',
709                 'company_id': order.company_id.id,
710                 'move_type': 'direct',
711                 'note': order.note or "",
712                 'invoice_state': 'none',
713                 'auto_picking': True,
714             }, context=context)
715             self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context)
716             location_id = order.warehouse_id.lot_stock_id.id
717             output_id = order.warehouse_id.lot_output_id.id
718
719             for line in order.lines:
720                 if line.product_id and line.product_id.type == 'service':
721                     continue
722                 if line.qty < 0:
723                     location_id, output_id = output_id, location_id
724
725                 move_obj.create(cr, uid, {
726                     'name': line.name,
727                     'product_uom': line.product_id.uom_id.id,
728                     'product_uos': line.product_id.uom_id.id,
729                     'picking_id': picking_id,
730                     'product_id': line.product_id.id,
731                     'product_uos_qty': abs(line.qty),
732                     'product_qty': abs(line.qty),
733                     'tracking_id': False,
734                     'state': 'draft',
735                     'location_id': location_id,
736                     'location_dest_id': output_id,
737                 }, context=context)
738                 if line.qty < 0:
739                     location_id, output_id = output_id, location_id
740             
741             picking_obj.signal_button_confirm(cr, uid, [picking_id])
742             picking_obj.force_assign(cr, uid, [picking_id], context)
743         return True
744
745     def cancel_order(self, cr, uid, ids, context=None):
746         """ Changes order state to cancel
747         @return: True
748         """
749         stock_picking_obj = self.pool.get('stock.picking')
750         wf_service = netsvc.LocalService("workflow")
751         for order in self.browse(cr, uid, ids, context=context):
752             stock_picking_obj.signal_button_cancel(cr, uid, [order.picking_id.id])
753             if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel':
754                 raise osv.except_osv(_('Error!'), _('Unable to cancel the picking.'))
755         self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
756         return True
757
758     def add_payment(self, cr, uid, order_id, data, context=None):
759         """Create a new payment for the order"""
760         if not context:
761             context = {}
762         statement_line_obj = self.pool.get('account.bank.statement.line')
763         property_obj = self.pool.get('ir.property')
764         order = self.browse(cr, uid, order_id, context=context)
765         args = {
766             'amount': data['amount'],
767             'date': data.get('payment_date', time.strftime('%Y-%m-%d')),
768             'name': order.name + ': ' + (data.get('payment_name', '') or ''),
769         }
770
771         account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context)
772         args['account_id'] = (order.partner_id and order.partner_id.property_account_receivable \
773                              and order.partner_id.property_account_receivable.id) or (account_def and account_def.id) or False
774         args['partner_id'] = order.partner_id and order.partner_id.id or None
775
776         if not args['account_id']:
777             if not args['partner_id']:
778                 msg = _('There is no receivable account defined to make payment.')
779             else:
780                 msg = _('There is no receivable account defined to make payment for the partner: "%s" (id:%d).') % (order.partner_id.name, order.partner_id.id,)
781             raise osv.except_osv(_('Configuration Error!'), msg)
782
783         context.pop('pos_session_id', False)
784
785         journal_id = data.get('journal', False)
786         statement_id = data.get('statement_id', False)
787         assert journal_id or statement_id, "No statement_id or journal_id passed to the method!"
788
789         for statement in order.session_id.statement_ids:
790             if statement.id == statement_id:
791                 journal_id = statement.journal_id.id
792                 break
793             elif statement.journal_id.id == journal_id:
794                 statement_id = statement.id
795                 break
796
797         if not statement_id:
798             raise osv.except_osv(_('Error!'), _('You have to open at least one cashbox.'))
799
800         args.update({
801             'statement_id' : statement_id,
802             'pos_statement_id' : order_id,
803             'journal_id' : journal_id,
804             'type' : 'customer',
805             'ref' : order.session_id.name,
806         })
807
808         statement_line_obj.create(cr, uid, args, context=context)
809
810         return statement_id
811
812     def refund(self, cr, uid, ids, context=None):
813         """Create a copy of order  for refund order"""
814         clone_list = []
815         line_obj = self.pool.get('pos.order.line')
816         
817         for order in self.browse(cr, uid, ids, context=context):
818             current_session_ids = self.pool.get('pos.session').search(cr, uid, [
819                 ('state', '!=', 'closed'),
820                 ('user_id', '=', uid)], context=context)
821             if not current_session_ids:
822                 raise osv.except_osv(_('Error!'), _('To return product(s), you need to open a session that will be used to register the refund.'))
823
824             clone_id = self.copy(cr, uid, order.id, {
825                 'name': order.name + ' REFUND', # not used, name forced by create
826                 'session_id': current_session_ids[0],
827                 'date_order': time.strftime('%Y-%m-%d %H:%M:%S'),
828             }, context=context)
829             clone_list.append(clone_id)
830
831         for clone in self.browse(cr, uid, clone_list, context=context):
832             for order_line in clone.lines:
833                 line_obj.write(cr, uid, [order_line.id], {
834                     'qty': -order_line.qty
835                 }, context=context)
836
837         new_order = ','.join(map(str,clone_list))
838         abs = {
839             #'domain': "[('id', 'in', ["+new_order+"])]",
840             'name': _('Return Products'),
841             'view_type': 'form',
842             'view_mode': 'form',
843             'res_model': 'pos.order',
844             'res_id':clone_list[0],
845             'view_id': False,
846             'context':context,
847             'type': 'ir.actions.act_window',
848             'nodestroy': True,
849             'target': 'current',
850         }
851         return abs
852
853     def action_invoice_state(self, cr, uid, ids, context=None):
854         return self.write(cr, uid, ids, {'state':'invoiced'}, context=context)
855
856     def action_invoice(self, cr, uid, ids, context=None):
857         inv_ref = self.pool.get('account.invoice')
858         inv_line_ref = self.pool.get('account.invoice.line')
859         product_obj = self.pool.get('product.product')
860         inv_ids = []
861
862         for order in self.pool.get('pos.order').browse(cr, uid, ids, context=context):
863             if order.invoice_id:
864                 inv_ids.append(order.invoice_id.id)
865                 continue
866
867             if not order.partner_id:
868                 raise osv.except_osv(_('Error!'), _('Please provide a partner for the sale.'))
869
870             acc = order.partner_id.property_account_receivable.id
871             inv = {
872                 'name': order.name,
873                 'origin': order.name,
874                 'account_id': acc,
875                 'journal_id': order.sale_journal.id or None,
876                 'type': 'out_invoice',
877                 'reference': order.name,
878                 'partner_id': order.partner_id.id,
879                 'comment': order.note or '',
880                 'currency_id': order.pricelist_id.currency_id.id, # considering partner's sale pricelist's currency
881             }
882             inv.update(inv_ref.onchange_partner_id(cr, uid, [], 'out_invoice', order.partner_id.id)['value'])
883             if not inv.get('account_id', None):
884                 inv['account_id'] = acc
885             inv_id = inv_ref.create(cr, uid, inv, context=context)
886
887             self.write(cr, uid, [order.id], {'invoice_id': inv_id, 'state': 'invoiced'}, context=context)
888             inv_ids.append(inv_id)
889             for line in order.lines:
890                 inv_line = {
891                     'invoice_id': inv_id,
892                     'product_id': line.product_id.id,
893                     'quantity': line.qty,
894                 }
895                 inv_name = product_obj.name_get(cr, uid, [line.product_id.id], context=context)[0][1]
896                 inv_line.update(inv_line_ref.product_id_change(cr, uid, [],
897                                                                line.product_id.id,
898                                                                line.product_id.uom_id.id,
899                                                                line.qty, partner_id = order.partner_id.id,
900                                                                fposition_id=order.partner_id.property_account_position.id)['value'])
901                 if line.product_id.description_sale:
902                     inv_line['note'] = line.product_id.description_sale
903                 inv_line['price_unit'] = line.price_unit
904                 inv_line['discount'] = line.discount
905                 inv_line['name'] = inv_name
906                 inv_line['invoice_line_tax_id'] = [(6, 0, [x.id for x in line.product_id.taxes_id] )]
907                 inv_line_ref.create(cr, uid, inv_line, context=context)
908             inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context)
909             self.signal_invoice(cr, uid, [order.id])
910             inv_ref.signal_validate(cr, uid, [inv_id])
911
912         if not inv_ids: return {}
913
914         mod_obj = self.pool.get('ir.model.data')
915         res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
916         res_id = res and res[1] or False
917         return {
918             'name': _('Customer Invoice'),
919             'view_type': 'form',
920             'view_mode': 'form',
921             'view_id': [res_id],
922             'res_model': 'account.invoice',
923             'context': "{'type':'out_invoice'}",
924             'type': 'ir.actions.act_window',
925             'nodestroy': True,
926             'target': 'current',
927             'res_id': inv_ids and inv_ids[0] or False,
928         }
929
930     def create_account_move(self, cr, uid, ids, context=None):
931         return self._create_account_move_line(cr, uid, ids, None, None, context=context)
932
933     def _create_account_move_line(self, cr, uid, ids, session=None, move_id=None, context=None):
934         # Tricky, via the workflow, we only have one id in the ids variable
935         """Create a account move line of order grouped by products or not."""
936         account_move_obj = self.pool.get('account.move')
937         account_move_line_obj = self.pool.get('account.move.line')
938         account_period_obj = self.pool.get('account.period')
939         account_tax_obj = self.pool.get('account.tax')
940         user_proxy = self.pool.get('res.users')
941         property_obj = self.pool.get('ir.property')
942         cur_obj = self.pool.get('res.currency')
943
944         period = account_period_obj.find(cr, uid, context=context)[0]
945
946         #session_ids = set(order.session_id for order in self.browse(cr, uid, ids, context=context))
947
948         if session and not all(session.id == order.session_id.id for order in self.browse(cr, uid, ids, context=context)):
949             raise osv.except_osv(_('Error!'), _('Selected orders do not have the same session!'))
950
951         current_company = user_proxy.browse(cr, uid, uid, context=context).company_id
952
953         grouped_data = {}
954         have_to_group_by = session and session.config_id.group_by or False
955
956         def compute_tax(amount, tax, line):
957             if amount > 0:
958                 tax_code_id = tax['base_code_id']
959                 tax_amount = line.price_subtotal * tax['base_sign']
960             else:
961                 tax_code_id = tax['ref_base_code_id']
962                 tax_amount = line.price_subtotal * tax['ref_base_sign']
963
964             return (tax_code_id, tax_amount,)
965
966         for order in self.browse(cr, uid, ids, context=context):
967             if order.account_move:
968                 continue
969             if order.state != 'paid':
970                 continue
971
972             user_company = user_proxy.browse(cr, order.user_id.id, order.user_id.id).company_id
973
974             group_tax = {}
975             account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context)
976
977             order_account = order.partner_id and \
978                             order.partner_id.property_account_receivable and \
979                             order.partner_id.property_account_receivable.id or \
980                             account_def and account_def.id or current_company.account_receivable.id
981
982             if move_id is None:
983                 # Create an entry for the sale
984                 move_id = account_move_obj.create(cr, uid, {
985                     'ref' : order.name,
986                     'journal_id': order.sale_journal.id,
987                 }, context=context)
988
989             def insert_data(data_type, values):
990                 # if have_to_group_by:
991
992                 sale_journal_id = order.sale_journal.id
993
994                 # 'quantity': line.qty,
995                 # 'product_id': line.product_id.id,
996                 values.update({
997                     'date': order.date_order[:10],
998                     'ref': order.name,
999                     'journal_id' : sale_journal_id,
1000                     'period_id' : period,
1001                     'move_id' : move_id,
1002                     'company_id': user_company and user_company.id or False,
1003                 })
1004
1005                 if data_type == 'product':
1006                     key = ('product', values['partner_id'], values['product_id'], values['debit'] > 0)
1007                 elif data_type == 'tax':
1008                     key = ('tax', values['partner_id'], values['tax_code_id'], values['debit'] > 0)
1009                 elif data_type == 'counter_part':
1010                     key = ('counter_part', values['partner_id'], values['account_id'], values['debit'] > 0)
1011                 else:
1012                     return
1013
1014                 grouped_data.setdefault(key, [])
1015
1016                 # if not have_to_group_by or (not grouped_data[key]):
1017                 #     grouped_data[key].append(values)
1018                 # else:
1019                 #     pass
1020
1021                 if have_to_group_by:
1022                     if not grouped_data[key]:
1023                         grouped_data[key].append(values)
1024                     else:
1025                         current_value = grouped_data[key][0]
1026                         current_value['quantity'] = current_value.get('quantity', 0.0) +  values.get('quantity', 0.0)
1027                         current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
1028                         current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
1029                         current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0)
1030                 else:
1031                     grouped_data[key].append(values)
1032
1033             #because of the weird way the pos order is written, we need to make sure there is at least one line, 
1034             #because just after the 'for' loop there are references to 'line' and 'income_account' variables (that 
1035             #are set inside the for loop)
1036             #TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack
1037             assert order.lines, _('The POS order must have lines when calling this method')
1038             # Create an move for each order line
1039
1040             cur = order.pricelist_id.currency_id
1041             for line in order.lines:
1042                 tax_amount = 0
1043                 taxes = [t for t in line.product_id.taxes_id]
1044                 computed_taxes = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty)['taxes']
1045
1046                 for tax in computed_taxes:
1047                     tax_amount += cur_obj.round(cr, uid, cur, tax['amount'])
1048                     group_key = (tax['tax_code_id'], tax['base_code_id'], tax['account_collected_id'], tax['id'])
1049
1050                     group_tax.setdefault(group_key, 0)
1051                     group_tax[group_key] += cur_obj.round(cr, uid, cur, tax['amount'])
1052
1053                 amount = line.price_subtotal
1054
1055                 # Search for the income account
1056                 if  line.product_id.property_account_income.id:
1057                     income_account = line.product_id.property_account_income.id
1058                 elif line.product_id.categ_id.property_account_income_categ.id:
1059                     income_account = line.product_id.categ_id.property_account_income_categ.id
1060                 else:
1061                     raise osv.except_osv(_('Error!'), _('Please define income '\
1062                         'account for this product: "%s" (id:%d).') \
1063                         % (line.product_id.name, line.product_id.id, ))
1064
1065                 # Empty the tax list as long as there is no tax code:
1066                 tax_code_id = False
1067                 tax_amount = 0
1068                 while computed_taxes:
1069                     tax = computed_taxes.pop(0)
1070                     tax_code_id, tax_amount = compute_tax(amount, tax, line)
1071
1072                     # If there is one we stop
1073                     if tax_code_id:
1074                         break
1075
1076                 # Create a move for the line
1077                 insert_data('product', {
1078                     'name': line.product_id.name,
1079                     'quantity': line.qty,
1080                     'product_id': line.product_id.id,
1081                     'account_id': income_account,
1082                     'credit': ((amount>0) and amount) or 0.0,
1083                     'debit': ((amount<0) and -amount) or 0.0,
1084                     'tax_code_id': tax_code_id,
1085                     'tax_amount': tax_amount,
1086                     'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
1087                 })
1088
1089                 # For each remaining tax with a code, whe create a move line
1090                 for tax in computed_taxes:
1091                     tax_code_id, tax_amount = compute_tax(amount, tax, line)
1092                     if not tax_code_id:
1093                         continue
1094
1095                     insert_data('tax', {
1096                         'name': _('Tax'),
1097                         'product_id':line.product_id.id,
1098                         'quantity': line.qty,
1099                         'account_id': income_account,
1100                         'credit': 0.0,
1101                         'debit': 0.0,
1102                         'tax_code_id': tax_code_id,
1103                         'tax_amount': tax_amount,
1104                         'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
1105                     })
1106
1107             # Create a move for each tax group
1108             (tax_code_pos, base_code_pos, account_pos, tax_id)= (0, 1, 2, 3)
1109
1110             for key, tax_amount in group_tax.items():
1111                 tax = self.pool.get('account.tax').browse(cr, uid, key[tax_id], context=context)
1112                 insert_data('tax', {
1113                     'name': _('Tax') + ' ' + tax.name,
1114                     'quantity': line.qty,
1115                     'product_id': line.product_id.id,
1116                     'account_id': key[account_pos] or income_account,
1117                     'credit': ((tax_amount>0) and tax_amount) or 0.0,
1118                     'debit': ((tax_amount<0) and -tax_amount) or 0.0,
1119                     'tax_code_id': key[tax_code_pos],
1120                     'tax_amount': tax_amount,
1121                     'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
1122                 })
1123
1124             # counterpart
1125             insert_data('counter_part', {
1126                 'name': _("Trade Receivables"), #order.name,
1127                 'account_id': order_account,
1128                 'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
1129                 'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
1130                 'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
1131             })
1132
1133             order.write({'state':'done', 'account_move': move_id})
1134
1135         all_lines = []
1136         for group_key, group_data in grouped_data.iteritems():
1137             for value in group_data:
1138                 all_lines.append((0, 0, value),)
1139         if move_id: #In case no order was changed
1140             self.pool.get("account.move").write(cr, uid, [move_id], {'line_id':all_lines}, context=context)
1141
1142         return True
1143
1144     def action_payment(self, cr, uid, ids, context=None):
1145         return self.write(cr, uid, ids, {'state': 'payment'}, context=context)
1146
1147     def action_paid(self, cr, uid, ids, context=None):
1148         self.create_picking(cr, uid, ids, context=context)
1149         self.write(cr, uid, ids, {'state': 'paid'}, context=context)
1150         return True
1151
1152     def action_cancel(self, cr, uid, ids, context=None):
1153         self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
1154         return True
1155
1156     def action_done(self, cr, uid, ids, context=None):
1157         self.create_account_move(cr, uid, ids, context=context)
1158         return True
1159
1160 class account_bank_statement(osv.osv):
1161     _inherit = 'account.bank.statement'
1162     _columns= {
1163         'user_id': fields.many2one('res.users', 'User', readonly=True),
1164     }
1165     _defaults = {
1166         'user_id': lambda self,cr,uid,c={}: uid
1167     }
1168
1169 class account_bank_statement_line(osv.osv):
1170     _inherit = 'account.bank.statement.line'
1171     _columns= {
1172         'pos_statement_id': fields.many2one('pos.order', ondelete='cascade'),
1173     }
1174
1175
1176 class pos_order_line(osv.osv):
1177     _name = "pos.order.line"
1178     _description = "Lines of Point of Sale"
1179     _rec_name = "product_id"
1180
1181     def _amount_line_all(self, cr, uid, ids, field_names, arg, context=None):
1182         res = dict([(i, {}) for i in ids])
1183         account_tax_obj = self.pool.get('account.tax')
1184         cur_obj = self.pool.get('res.currency')
1185         for line in self.browse(cr, uid, ids, context=context):
1186             taxes_ids = [ tax for tax in line.product_id.taxes_id if tax.company_id.id == line.order_id.company_id.id ]
1187             price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
1188             taxes = account_tax_obj.compute_all(cr, uid, taxes_ids, price, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
1189
1190             cur = line.order_id.pricelist_id.currency_id
1191             res[line.id]['price_subtotal'] = cur_obj.round(cr, uid, cur, taxes['total'])
1192             res[line.id]['price_subtotal_incl'] = cur_obj.round(cr, uid, cur, taxes['total_included'])
1193         return res
1194
1195     def onchange_product_id(self, cr, uid, ids, pricelist, product_id, qty=0, partner_id=False, context=None):
1196        context = context or {}
1197        if not product_id:
1198             return {}
1199        if not pricelist:
1200            raise osv.except_osv(_('No Pricelist!'),
1201                _('You have to select a pricelist in the sale form !\n' \
1202                'Please set one before choosing a product.'))
1203
1204        price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1205                product_id, qty or 1.0, partner_id)[pricelist]
1206
1207        result = self.onchange_qty(cr, uid, ids, product_id, 0.0, qty, price, context=context)
1208        result['value']['price_unit'] = price
1209        return result
1210
1211     def onchange_qty(self, cr, uid, ids, product, discount, qty, price_unit, context=None):
1212         result = {}
1213         if not product:
1214             return result
1215         account_tax_obj = self.pool.get('account.tax')
1216         cur_obj = self.pool.get('res.currency')
1217
1218         prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
1219
1220         price = price_unit * (1 - (discount or 0.0) / 100.0)
1221         taxes = account_tax_obj.compute_all(cr, uid, prod.taxes_id, price, qty, product=prod, partner=False)
1222
1223         result['price_subtotal'] = taxes['total']
1224         result['price_subtotal_incl'] = taxes['total_included']
1225         return {'value': result}
1226
1227     _columns = {
1228         'company_id': fields.many2one('res.company', 'Company', required=True),
1229         'name': fields.char('Line No', size=32, required=True),
1230         'notice': fields.char('Discount Notice', size=128),
1231         'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, change_default=True),
1232         'price_unit': fields.float(string='Unit Price', digits=(16, 2)),
1233         'qty': fields.float('Quantity', digits=(16, 2)),
1234         'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal w/o Tax', store=True),
1235         'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal', store=True),
1236         'discount': fields.float('Discount (%)', digits=(16, 2)),
1237         'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'),
1238         'create_date': fields.datetime('Creation Date', readonly=True),
1239     }
1240
1241     _defaults = {
1242         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'pos.order.line'),
1243         'qty': lambda *a: 1,
1244         'discount': lambda *a: 0.0,
1245         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1246     }
1247
1248     def copy_data(self, cr, uid, id, default=None, context=None):
1249         if not default:
1250             default = {}
1251         default.update({
1252             'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order.line')
1253         })
1254         return super(pos_order_line, self).copy_data(cr, uid, id, default, context=context)
1255
1256 import io, StringIO
1257
1258 class ean_wizard(osv.osv_memory):
1259     _name = 'pos.ean_wizard'
1260     _columns = {
1261         'ean13_pattern': fields.char('Reference', size=32, required=True, translate=True),
1262     }
1263     def sanitize_ean13(self, cr, uid, ids, context):
1264         for r in self.browse(cr,uid,ids):
1265             ean13 = openerp.addons.product.product.sanitize_ean13(r.ean13_pattern)
1266             m = context.get('active_model')
1267             m_id =  context.get('active_id')
1268             self.pool[m].write(cr,uid,[m_id],{'ean13':ean13})
1269         return { 'type' : 'ir.actions.act_window_close' }
1270
1271 class product_product(osv.osv):
1272     _inherit = 'product.product'
1273
1274
1275     #def _get_small_image(self, cr, uid, ids, prop, unknow_none, context=None):
1276     #    result = {}
1277     #    for obj in self.browse(cr, uid, ids, context=context):
1278     #        if not obj.product_image:
1279     #            result[obj.id] = False
1280     #            continue
1281
1282     #        image_stream = io.BytesIO(obj.product_image.decode('base64'))
1283     #        img = Image.open(image_stream)
1284     #        img.thumbnail((120, 100), Image.ANTIALIAS)
1285     #        img_stream = StringIO.StringIO()
1286     #        img.save(img_stream, "JPEG")
1287     #        result[obj.id] = img_stream.getvalue().encode('base64')
1288     #    return result
1289
1290     _columns = {
1291         '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."),
1292         '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."),
1293         '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'), 
1294         'to_weight' : fields.boolean('To Weight', help="Check if the product should be weighted (mainly used with self check-out interface)."),
1295     }
1296
1297     _defaults = {
1298         'to_weight' : False,
1299         'available_in_pos': True,
1300     }
1301
1302     def edit_ean(self, cr, uid, ids, context):
1303         return {
1304             'name': _("Assign a Custom EAN"),
1305             'type': 'ir.actions.act_window',
1306             'view_type': 'form',
1307             'view_mode': 'form',
1308             'res_model': 'pos.ean_wizard',
1309             'target' : 'new',
1310             'view_id': False,
1311             'context':context,
1312         }
1313
1314 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: