<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import pdb
import openerp
import addons
import openerp.addons.product.product

import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
import logging

import netsvc
from osv import fields, osv
import tools
from tools.translate import _
from decimal import Decimal
import decimal_precision as dp

_logger = logging.getLogger(__name__)

class pos_config(osv.osv):
    _name = 'pos.config'

    POS_CONFIG_STATE = [
        ('active', 'Active'),
        ('inactive', 'Inactive'),
        ('deprecated', 'Deprecated')
    ]

    _columns = {
        'name' : fields.char('Point of Sale Name', size=32, select=1,
             required=True, help="An internal identification of the point of sale"),
        'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', 
             'pos_config_id', 'journal_id', 'Available Payment Methods',
             domain="[('journal_user', '=', True )]",),
        'shop_id' : fields.many2one('sale.shop', 'Shop',
             required=True),
        'journal_id' : fields.many2one('account.journal', 'Sale Journal',
             domain=[('type', '=', 'sale')],
             help="Accounting journal used to post sales entries."),
        'iface_self_checkout' : fields.boolean('Self Checkout Mode',
             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."),
        'iface_cashdrawer' : fields.boolean('Cashdrawer Interface'),
        'iface_payment_terminal' : fields.boolean('Payment Terminal Interface'),
        'iface_electronic_scale' : fields.boolean('Electronic Scale Interface'),
        'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'),
        'iface_print_via_proxy' : fields.boolean('Print via Proxy'),

        'state' : fields.selection(POS_CONFIG_STATE, 'State', required=True, readonly=True),
        'sequence_id' : fields.many2one('ir.sequence', 'Order IDs Sequence', readonly=True,
            help="This sequence is automatically created by OpenERP but you can change it "\
                "to customize the reference numbers of your orders."),
        'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
        'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
    }

    def _check_cash_control(self, cr, uid, ids, context=None):
        return all(
            (sum(int(journal.cash_control) for journal in record.journal_ids) <= 1)
            for record in self.browse(cr, uid, ids, context=context)
        )

    _constraints = [
        (_check_cash_control, "You cannot have two cash controls in one Point Of Sale !", ['journal_ids']),
    ]

    def copy(self, cr, uid, id, default=None, context=None):
        if not default:
            default = {}
        d = {
            'sequence_id' : False,
        }
        d.update(default)
        return super(pos_order, self).copy(cr, uid, id, d, context=context)


    def name_get(self, cr, uid, ids, context=None):
        result = []
        states = {
            'opening_control': _('Opening Control'),
            'opened': _('In Progress'),
            'closing_control': _('Closing Control'),
            'closed': _('Closed & Posted'),
        }
        for record in self.browse(cr, uid, ids, context=context):
            if (not record.session_ids) or (record.session_ids[0].state=='closed'):
                result.append((record.id, record.name+' ('+_('not used')+')'))
                continue
            session = record.session_ids[0]
            result.append((record.id, record.name + ' ('+session.user_id.name+')')) #, '+states[session.state]+')'))
        return result

    def _default_sale_journal(self, cr, uid, context=None):
        res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale')], limit=1)
        return res and res[0] or False

    def _default_shop(self, cr, uid, context=None):
        res = self.pool.get('sale.shop').search(cr, uid, [])
        return res and res[0] or False

    _defaults = {
        'state' : POS_CONFIG_STATE[0][0],
        'shop_id': _default_shop,
        'journal_id': _default_sale_journal,
        'group_by' : True,
    }

    def set_active(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'active'}, context=context)

    def set_inactive(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'inactive'}, context=context)

    def set_deprecate(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'deprecated'}, context=context)

    def create(self, cr, uid, values, context=None):
        proxy = self.pool.get('ir.sequence')
        sequence_values = dict(
            name='PoS %s' % values['name'],
            padding=5,
            prefix="%s/"  % values['name'],
        )
        sequence_id = proxy.create(cr, uid, sequence_values, context=context)
        values['sequence_id'] = sequence_id
        return super(pos_config, self).create(cr, uid, values, context=context)

    def unlink(self, cr, uid, ids, context=None):
        for obj in self.browse(cr, uid, ids, context=context):
            if obj.sequence_id:
                obj.sequence_id.unlink()
        return super(pos_config, self).unlink(cr, uid, ids, context=context)

class pos_session(osv.osv):
    _name = 'pos.session'
    _order = 'id desc'

    POS_SESSION_STATE = [
        ('opening_control', 'Opening Control'),  # Signal open
        ('opened', 'In Progress'),                    # Signal closing
        ('closing_control', 'Closing Control'),  # Signal close
        ('closed', 'Closed & Posted'),
    ]

    def _compute_cash_all(self, cr, uid, ids, fieldnames, args, context=None):
        result = dict()

        for record in self.browse(cr, uid, ids, context=context):
            result[record.id] = {
                'cash_journal_id' : False,
                'cash_register_id' : False,
                'cash_control' : False,
            }
            for st in record.statement_ids:
                if st.journal_id.cash_control == True:
                    result[record.id]['cash_control'] = True
                    result[record.id]['cash_journal_id'] = st.journal_id.id
                    result[record.id]['cash_register_id'] = st.id

        return result

    _columns = {
        'config_id' : fields.many2one('pos.config', 'Point of Sale',
                                      help="The physical point of sale you will use.",
                                      required=True,
                                      select=1,
                                      domain="[('state', '=', 'active')]",
                                     ),

        'name' : fields.char('Session ID', size=32, required=True, readonly=True),
        'user_id' : fields.many2one('res.users', 'Responsible',
                                    required=True,
                                    select=1,
                                    readonly=True,
                                    states={'opening_control' : [('readonly', False)]}
                                   ),
        'start_at' : fields.datetime('Opening Date', readonly=True), 
        'stop_at' : fields.datetime('Closing Date', readonly=True),

        'state' : fields.selection(POS_SESSION_STATE, 'State',
                required=True, readonly=True,
                select=1),

        'cash_control' : fields.function(_compute_cash_all,
                                         multi='cash',
                                         type='boolean', string='Has Cash Control'),
        'cash_journal_id' : fields.function(_compute_cash_all,
                                            multi='cash',
                                            type='many2one', relation='account.journal',
                                            string='Cash Journal', store=True),
        'cash_register_id' : fields.function(_compute_cash_all,
                                             multi='cash',
                                             type='many2one', relation='account.bank.statement',
                                             string='Cash Register', store=True),

        'opening_details_ids' : fields.related('cash_register_id', 'opening_details_ids', 
                type='one2many', relation='account.cashbox.line',
                string='Opening Cash Control'),
        'details_ids' : fields.related('cash_register_id', 'details_ids', 
                type='one2many', relation='account.cashbox.line',
                string='Cash Control'),

        'cash_register_balance_end_real' : fields.related('cash_register_id', 'balance_end_real',
                type='float',
                digits_compute=dp.get_precision('Account'),
                string="Ending Balance",
                help="Computed using the cash control lines",
                readonly=True),
        'cash_register_balance_start' : fields.related('cash_register_id', 'balance_start',
                type='float',
                digits_compute=dp.get_precision('Account'),
                string="Starting Balance",
                help="Computed using the cash control at the opening.",
                readonly=True),
        'cash_register_total_entry_encoding' : fields.related('cash_register_id', 'total_entry_encoding',
                string='Total Cash Transaction',
                readonly=True),
        'cash_register_balance_end' : fields.related('cash_register_id', 'balance_end',
                type='float',
                digits_compute=dp.get_precision('Account'),
                string="Computed Balance",
                help="Computed with the initial cash control and the sum of all payments.",
                readonly=True),
        'cash_register_difference' : fields.related('cash_register_id', 'difference',
                type='float',
                string='Difference',
                help="Difference between the counted cash control at the closing and the computed balance.",
                readonly=True),

        'journal_ids' : fields.related('config_id', 'journal_ids',
                                       type='many2many',
                                       readonly=True,
                                       relation='account.journal',
                                       string='Available Payment Methods'),
        'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'),

        'statement_ids' : fields.one2many('account.bank.statement', 'pos_session_id', 'Bank Statement', readonly=True),
    }

    _defaults = {
        'name' : '/',
        'user_id' : lambda obj, cr, uid, context: uid,
        'state' : 'opening_control',
    }

    _sql_constraints = [
        ('uniq_name', 'unique(name)', "The name of this POS Session must be unique !"),
    ]

    def _check_unicity(self, cr, uid, ids, context=None):
        for session in self.browse(cr, uid, ids, context=None):
            # open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
            domain = [
                ('state', 'not in', ('closed','closing_control')),
                ('user_id', '=', uid)
            ]
            count = self.search_count(cr, uid, domain, context=context)
            if count>1:
                return False
        return True

    def _check_pos_config(self, cr, uid, ids, context=None):
        for session in self.browse(cr, uid, ids, context=None):
            domain = [
                ('state', '!=', 'closed'),
                ('config_id', '=', session.config_id.id)
            ]
            count = self.search_count(cr, uid, domain, context=context)
            if count>1:
                return False
        return True

    _constraints = [
        (_check_unicity, "You cannot create two active sessions with the same responsible!", ['user_id', 'state']),
        (_check_pos_config, "You cannot create two active sessions related to the same point of sale!", ['config_id']),
    ]

    def create(self, cr, uid, values, context=None):
        config_id = values.get('config_id', False) or False
        if config_id:
            # journal_id is not required on the pos_config because it does not
            # exists at the installation. If nothing is configured at the
            # installation we do the minimal configuration. Impossible to do in
            # the .xml files as the CoA is not yet installed.
            jobj = self.pool.get('pos.config')
            pos_config = jobj.browse(cr, uid, config_id, context=context)
            if not pos_config.journal_id:
                jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id']
                if jid:
                    jobj.write(cr, uid, [pos_config.id], {'journal_id': jid}, context=context)
                else:
                    raise osv.except_osv( _('error!'),
                        _("Unable to open the session. You have to assign a sale journal to your point of sale."))

            # define some cash journal if no payment method exists
            if not pos_config.journal_ids:
                journal_proxy = self.pool.get('account.journal')
                cashids = journal_proxy.search(cr, uid, [('journal_user', '=', True), ('type','=','cash')], context=context)
                if not cashids:
                    cashids = journal_proxy.search(cr, uid, [('type', '=', 'cash')], context=context)
                    if not cashids:
                        cashids = journal_proxy.search(cr, uid, [('journal_user','=',True)], context=context)

                jobj.write(cr, uid, [pos_config.id], {'journal_ids': [(6,0, cashids)]})


            pos_config = jobj.browse(cr, uid, config_id, context=context)
            bank_statement_ids = []
            for journal in pos_config.journal_ids:
                bank_values = {
                    'journal_id' : journal.id,
                    'user_id' : uid,
                }
                statement_id = self.pool.get('account.bank.statement').create(cr, uid, bank_values, context=context)
                bank_statement_ids.append(statement_id)

            values.update({
                'name' : pos_config.sequence_id._next(),
                'statement_ids' : [(6, 0, bank_statement_ids)],
                'config_id': config_id
            })

        return super(pos_session, self).create(cr, uid, values, context=context)

    def unlink(self, cr, uid, ids, context=None):
        for obj in self.browse(cr, uid, ids, context=context):
            for statement in obj.statement_ids:
                statement.unlink(context=context)
        return True

    def wkf_action_open(self, cr, uid, ids, context=None):
        # second browse because we need to refetch the data from the DB for cash_register_id
        for record in self.browse(cr, uid, ids, context=context):
            values = {}
            if not record.start_at:
                values['start_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
            values['state'] = 'opened'
            record.write(values, context=context)
            for st in record.statement_ids:
                st.button_open(context=context)

        return self.open_frontend_cb(cr, uid, ids, context=context)

    def wkf_action_opening_control(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'opening_control'}, context=context)

    def wkf_action_closing_control(self, cr, uid, ids, context=None):
        for session in self.browse(cr, uid, ids, context=context):
            for statement in session.statement_ids:
                if statement != session.cash_register_id and statement.balance_end != statement.balance_end_real:
                    self.pool.get('account.bank.statement').write(cr, uid, [statement.id], {'balance_end_real': statement.balance_end})
        return self.write(cr, uid, ids, {'state' : 'closing_control', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)

    def wkf_action_close(self, cr, uid, ids, context=None):
        # Close CashBox
        bsl = self.pool.get('account.bank.statement.line')
        for record in self.browse(cr, uid, ids, context=context):
            for st in record.statement_ids:
                if abs(st.difference) > st.journal_id.amount_authorized_diff:
                    # The pos manager can close statements with maximums.
                    if not self.pool.get('ir.model.access').check_groups(cr, uid, "point_of_sale.group_pos_manager"):
                        raise osv.except_osv( _('Error!'),
                            _("Your ending balance is too different from the theorical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff))
                if st.difference and st.journal_id.cash_control == True:
                    if st.difference > 0.0:
                        name= _('Point of Sale Profit')
                        account_id = st.journal_id.profit_account_id.id
                    else:
                        account_id = st.journal_id.loss_account_id.id
                        name= _('Point of Sale Loss')
                    if not account_id:
                        raise osv.except_osv( _('Error!'),
                        _("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))
                    bsl.create(cr, uid, {
                        'statement_id': st.id,
                        'amount': st.difference,
                        'ref': record.name,
                        'name': name,
                        'account_id': account_id
                    }, context=context)

                if st.journal_id.type == 'bank':
                    st.write({'balance_end_real' : st.balance_end})

                getattr(st, 'button_confirm_%s' % st.journal_id.type)(context=context)
        self._confirm_orders(cr, uid, ids, context=context)
        self.write(cr, uid, ids, {'state' : 'closed'}, context=context)

        obj = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'point_of_sale', 'menu_point_root')[1]
        return {
            'type' : 'ir.actions.client',
            'name' : 'Point of Sale Menu',
            'tag' : 'reload',
            'params' : {'menu_id': obj},
        }

    def _confirm_orders(self, cr, uid, ids, context=None):
        wf_service = netsvc.LocalService("workflow")

        for session in self.browse(cr, uid, ids, context=context):
            order_ids = [order.id for order in session.order_ids if order.state == 'paid']

            move_id = self.pool.get('account.move').create(cr, uid, {'ref' : session.name, 'journal_id' : session.config_id.journal_id.id, }, context=context)

            self.pool.get('pos.order')._create_account_move_line(cr, uid, order_ids, session, move_id, context=context)

            for order in session.order_ids:
                if order.state not in ('paid', 'invoiced'):
                    raise osv.except_osv(
                        _('Error!'),
                        _("You cannot confirm all orders of this session, because they have not the 'paid' status"))
                else:
                    wf_service.trg_validate(uid, 'pos.order', order.id, 'done', cr)

        return True

    def open_frontend_cb(self, cr, uid, ids, context=None):
        if not context:
            context = {}
        if not ids:
            return {}
        context.update({'session_id' : ids[0]})
        return {
            'type' : 'ir.actions.client',
            'name' : 'Start Point Of Sale',
            'tag' : 'pos.ui',
            'context' : context,
        }

class pos_order(osv.osv):
    _name = "pos.order"
    _description = "Point of Sale"
    _order = "id desc"

    def create_from_ui(self, cr, uid, orders, context=None):
        #_logger.info("orders: %r", orders)
        order_ids = []
        for tmp_order in orders:
            order = tmp_order['data']
            order_id = self.create(cr, uid, {
                'name': order['name'],
                'user_id': order['user_id'] or False,
                'session_id': order['pos_session_id'],
                'lines': order['lines']
            }, context)

            for payments in order['statement_ids']:
                payment = payments[2]
                self.add_payment(cr, uid, order_id, {
                    'amount': payment['amount'] or 0.0,
                    'payment_date': payment['name'],
                    'payment_name': payment.get('note', False),<commit_msg>[FIX] point_of_sale: return the session id in the context for the call of the pos.ui<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import pdb
import openerp
import addons
import openerp.addons.product.product

import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
import logging

import netsvc
from osv import fields, osv
import tools
from tools.translate import _
from decimal import Decimal
import decimal_precision as dp

_logger = logging.getLogger(__name__)

class pos_config(osv.osv):
    _name = 'pos.config'

    POS_CONFIG_STATE = [
        ('active', 'Active'),
        ('inactive', 'Inactive'),
        ('deprecated', 'Deprecated')
    ]

    _columns = {
        'name' : fields.char('Point of Sale Name', size=32, select=1,
             required=True, help="An internal identification of the point of sale"),
        'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', 
             'pos_config_id', 'journal_id', 'Available Payment Methods',
             domain="[('journal_user', '=', True )]",),
        'shop_id' : fields.many2one('sale.shop', 'Shop',
             required=True),
        'journal_id' : fields.many2one('account.journal', 'Sale Journal',
             domain=[('type', '=', 'sale')],
             help="Accounting journal used to post sales entries."),
        'iface_self_checkout' : fields.boolean('Self Checkout Mode',
             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."),
        'iface_cashdrawer' : fields.boolean('Cashdrawer Interface'),
        'iface_payment_terminal' : fields.boolean('Payment Terminal Interface'),
        'iface_electronic_scale' : fields.boolean('Electronic Scale Interface'),
        'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'),
        'iface_print_via_proxy' : fields.boolean('Print via Proxy'),

        'state' : fields.selection(POS_CONFIG_STATE, 'State', required=True, readonly=True),
        'sequence_id' : fields.many2one('ir.sequence', 'Order IDs Sequence', readonly=True,
            help="This sequence is automatically created by OpenERP but you can change it "\
                "to customize the reference numbers of your orders."),
        'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
        'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
    }

    def _check_cash_control(self, cr, uid, ids, context=None):
        return all(
            (sum(int(journal.cash_control) for journal in record.journal_ids) <= 1)
            for record in self.browse(cr, uid, ids, context=context)
        )

    _constraints = [
        (_check_cash_control, "You cannot have two cash controls in one Point Of Sale !", ['journal_ids']),
    ]

    def copy(self, cr, uid, id, default=None, context=None):
        if not default:
            default = {}
        d = {
            'sequence_id' : False,
        }
        d.update(default)
        return super(pos_order, self).copy(cr, uid, id, d, context=context)


    def name_get(self, cr, uid, ids, context=None):
        result = []
        states = {
            'opening_control': _('Opening Control'),
            'opened': _('In Progress'),
            'closing_control': _('Closing Control'),
            'closed': _('Closed & Posted'),
        }
        for record in self.browse(cr, uid, ids, context=context):
            if (not record.session_ids) or (record.session_ids[0].state=='closed'):
                result.append((record.id, record.name+' ('+_('not used')+')'))
                continue
            session = record.session_ids[0]
            result.append((record.id, record.name + ' ('+session.user_id.name+')')) #, '+states[session.state]+')'))
        return result

    def _default_sale_journal(self, cr, uid, context=None):
        res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale')], limit=1)
        return res and res[0] or False

    def _default_shop(self, cr, uid, context=None):
        res = self.pool.get('sale.shop').search(cr, uid, [])
        return res and res[0] or False

    _defaults = {
        'state' : POS_CONFIG_STATE[0][0],
        'shop_id': _default_shop,
        'journal_id': _default_sale_journal,
        'group_by' : True,
    }

    def set_active(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'active'}, context=context)

    def set_inactive(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'inactive'}, context=context)

    def set_deprecate(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'deprecated'}, context=context)

    def create(self, cr, uid, values, context=None):
        proxy = self.pool.get('ir.sequence')
        sequence_values = dict(
            name='PoS %s' % values['name'],
            padding=5,
            prefix="%s/"  % values['name'],
        )
        sequence_id = proxy.create(cr, uid, sequence_values, context=context)
        values['sequence_id'] = sequence_id
        return super(pos_config, self).create(cr, uid, values, context=context)

    def unlink(self, cr, uid, ids, context=None):
        for obj in self.browse(cr, uid, ids, context=context):
            if obj.sequence_id:
                obj.sequence_id.unlink()
        return super(pos_config, self).unlink(cr, uid, ids, context=context)

class pos_session(osv.osv):
    _name = 'pos.session'
    _order = 'id desc'

    POS_SESSION_STATE = [
        ('opening_control', 'Opening Control'),  # Signal open
        ('opened', 'In Progress'),                    # Signal closing
        ('closing_control', 'Closing Control'),  # Signal close
        ('closed', 'Closed & Posted'),
    ]

    def _compute_cash_all(self, cr, uid, ids, fieldnames, args, context=None):
        result = dict()

        for record in self.browse(cr, uid, ids, context=context):
            result[record.id] = {
                'cash_journal_id' : False,
                'cash_register_id' : False,
                'cash_control' : False,
            }
            for st in record.statement_ids:
                if st.journal_id.cash_control == True:
                    result[record.id]['cash_control'] = True
                    result[record.id]['cash_journal_id'] = st.journal_id.id
                    result[record.id]['cash_register_id'] = st.id

        return result

    _columns = {
        'config_id' : fields.many2one('pos.config', 'Point of Sale',
                                      help="The physical point of sale you will use.",
                                      required=True,
                                      select=1,
                                      domain="[('state', '=', 'active')]",
                                     ),

        'name' : fields.char('Session ID', size=32, required=True, readonly=True),
        'user_id' : fields.many2one('res.users', 'Responsible',
                                    required=True,
                                    select=1,
                                    readonly=True,
                                    states={'opening_control' : [('readonly', False)]}
                                   ),
        'start_at' : fields.datetime('Opening Date', readonly=True), 
        'stop_at' : fields.datetime('Closing Date', readonly=True),

        'state' : fields.selection(POS_SESSION_STATE, 'State',
                required=True, readonly=True,
                select=1),

        'cash_control' : fields.function(_compute_cash_all,
                                         multi='cash',
                                         type='boolean', string='Has Cash Control'),
        'cash_journal_id' : fields.function(_compute_cash_all,
                                            multi='cash',
                                            type='many2one', relation='account.journal',
                                            string='Cash Journal', store=True),
        'cash_register_id' : fields.function(_compute_cash_all,
                                             multi='cash',
                                             type='many2one', relation='account.bank.statement',
                                             string='Cash Register', store=True),

        'opening_details_ids' : fields.related('cash_register_id', 'opening_details_ids', 
                type='one2many', relation='account.cashbox.line',
                string='Opening Cash Control'),
        'details_ids' : fields.related('cash_register_id', 'details_ids', 
                type='one2many', relation='account.cashbox.line',
                string='Cash Control'),

        'cash_register_balance_end_real' : fields.related('cash_register_id', 'balance_end_real',
                type='float',
                digits_compute=dp.get_precision('Account'),
                string="Ending Balance",
                help="Computed using the cash control lines",
                readonly=True),
        'cash_register_balance_start' : fields.related('cash_register_id', 'balance_start',
                type='float',
                digits_compute=dp.get_precision('Account'),
                string="Starting Balance",
                help="Computed using the cash control at the opening.",
                readonly=True),
        'cash_register_total_entry_encoding' : fields.related('cash_register_id', 'total_entry_encoding',
                string='Total Cash Transaction',
                readonly=True),
        'cash_register_balance_end' : fields.related('cash_register_id', 'balance_end',
                type='float',
                digits_compute=dp.get_precision('Account'),
                string="Computed Balance",
                help="Computed with the initial cash control and the sum of all payments.",
                readonly=True),
        'cash_register_difference' : fields.related('cash_register_id', 'difference',
                type='float',
                string='Difference',
                help="Difference between the counted cash control at the closing and the computed balance.",
                readonly=True),

        'journal_ids' : fields.related('config_id', 'journal_ids',
                                       type='many2many',
                                       readonly=True,
                                       relation='account.journal',
                                       string='Available Payment Methods'),
        'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'),

        'statement_ids' : fields.one2many('account.bank.statement', 'pos_session_id', 'Bank Statement', readonly=True),
    }

    _defaults = {
        'name' : '/',
        'user_id' : lambda obj, cr, uid, context: uid,
        'state' : 'opening_control',
    }

    _sql_constraints = [
        ('uniq_name', 'unique(name)', "The name of this POS Session must be unique !"),
    ]

    def _check_unicity(self, cr, uid, ids, context=None):
        for session in self.browse(cr, uid, ids, context=None):
            # open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
            domain = [
                ('state', 'not in', ('closed','closing_control')),
                ('user_id', '=', uid)
            ]
            count = self.search_count(cr, uid, domain, context=context)
            if count>1:
                return False
        return True

    def _check_pos_config(self, cr, uid, ids, context=None):
        for session in self.browse(cr, uid, ids, context=None):
            domain = [
                ('state', '!=', 'closed'),
                ('config_id', '=', session.config_id.id)
            ]
            count = self.search_count(cr, uid, domain, context=context)
            if count>1:
                return False
        return True

    _constraints = [
        (_check_unicity, "You cannot create two active sessions with the same responsible!", ['user_id', 'state']),
        (_check_pos_config, "You cannot create two active sessions related to the same point of sale!", ['config_id']),
    ]

    def create(self, cr, uid, values, context=None):
        config_id = values.get('config_id', False) or False
        if config_id:
            # journal_id is not required on the pos_config because it does not
            # exists at the installation. If nothing is configured at the
            # installation we do the minimal configuration. Impossible to do in
            # the .xml files as the CoA is not yet installed.
            jobj = self.pool.get('pos.config')
            pos_config = jobj.browse(cr, uid, config_id, context=context)
            if not pos_config.journal_id:
                jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id']
                if jid:
                    jobj.write(cr, uid, [pos_config.id], {'journal_id': jid}, context=context)
                else:
                    raise osv.except_osv( _('error!'),
                        _("Unable to open the session. You have to assign a sale journal to your point of sale."))

            # define some cash journal if no payment method exists
            if not pos_config.journal_ids:
                journal_proxy = self.pool.get('account.journal')
                cashids = journal_proxy.search(cr, uid, [('journal_user', '=', True), ('type','=','cash')], context=context)
                if not cashids:
                    cashids = journal_proxy.search(cr, uid, [('type', '=', 'cash')], context=context)
                    if not cashids:
                        cashids = journal_proxy.search(cr, uid, [('journal_user','=',True)], context=context)

                jobj.write(cr, uid, [pos_config.id], {'journal_ids': [(6,0, cashids)]})


            pos_config = jobj.browse(cr, uid, config_id, context=context)
            bank_statement_ids = []
            for journal in pos_config.journal_ids:
                bank_values = {
                    'journal_id' : journal.id,
                    'user_id' : uid,
                }
                statement_id = self.pool.get('account.bank.statement').create(cr, uid, bank_values, context=context)
                bank_statement_ids.append(statement_id)

            values.update({
                'name' : pos_config.sequence_id._next(),
                'statement_ids' : [(6, 0, bank_statement_ids)],
                'config_id': config_id
            })

        return super(pos_session, self).create(cr, uid, values, context=context)

    def unlink(self, cr, uid, ids, context=None):
        for obj in self.browse(cr, uid, ids, context=context):
            for statement in obj.statement_ids:
                statement.unlink(context=context)
        return True

    def wkf_action_open(self, cr, uid, ids, context=None):
        # second browse because we need to refetch the data from the DB for cash_register_id
        for record in self.browse(cr, uid, ids, context=context):
            values = {}
            if not record.start_at:
                values['start_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
            values['state'] = 'opened'
            record.write(values, context=context)
            for st in record.statement_ids:
                st.button_open(context=context)

        return self.open_frontend_cb(cr, uid, ids, context=context)

    def wkf_action_opening_control(self, cr, uid, ids, context=None):
        return self.write(cr, uid, ids, {'state' : 'opening_control'}, context=context)

    def wkf_action_closing_control(self, cr, uid, ids, context=None):
        for session in self.browse(cr, uid, ids, context=context):
            for statement in session.statement_ids:
                if statement != session.cash_register_id and statement.balance_end != statement.balance_end_real:
                    self.pool.get('account.bank.statement').write(cr, uid, [statement.id], {'balance_end_real': statement.balance_end})
        return self.write(cr, uid, ids, {'state' : 'closing_control', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)

    def wkf_action_close(self, cr, uid, ids, context=None):
        # Close CashBox
        bsl = self.pool.get('account.bank.statement.line')
        for record in self.browse(cr, uid, ids, context=context):
            for st in record.statement_ids:
                if abs(st.difference) > st.journal_id.amount_authorized_diff:
                    # The pos manager can close statements with maximums.
                    if not self.pool.get('ir.model.access').check_groups(cr, uid, "point_of_sale.group_pos_manager"):
                        raise osv.except_osv( _('Error!'),
                            _("Your ending balance is too different from the theorical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff))
                if st.difference and st.journal_id.cash_control == True:
                    if st.difference > 0.0:
                        name= _('Point of Sale Profit')
                        account_id = st.journal_id.profit_account_id.id
                    else:
                        account_id = st.journal_id.loss_account_id.id
                        name= _('Point of Sale Loss')
                    if not account_id:
                        raise osv.except_osv( _('Error!'),
                        _("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))
                    bsl.create(cr, uid, {
                        'statement_id': st.id,
                        'amount': st.difference,
                        'ref': record.name,
                        'name': name,
                        'account_id': account_id
                    }, context=context)

                if st.journal_id.type == 'bank':
                    st.write({'balance_end_real' : st.balance_end})

                getattr(st, 'button_confirm_%s' % st.journal_id.type)(context=context)
        self._confirm_orders(cr, uid, ids, context=context)
        self.write(cr, uid, ids, {'state' : 'closed'}, context=context)

        obj = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'point_of_sale', 'menu_point_root')[1]
        return {
            'type' : 'ir.actions.client',
            'name' : 'Point of Sale Menu',
            'tag' : 'reload',
            'params' : {'menu_id': obj},
        }

    def _confirm_orders(self, cr, uid, ids, context=None):
        wf_service = netsvc.LocalService("workflow")

        for session in self.browse(cr, uid, ids, context=context):
            order_ids = [order.id for order in session.order_ids if order.state == 'paid']

            move_id = self.pool.get('account.move').create(cr, uid, {'ref' : session.name, 'journal_id' : session.config_id.journal_id.id, }, context=context)

            self.pool.get('pos.order')._create_account_move_line(cr, uid, order_ids, session, move_id, context=context)

            for order in session.order_ids:
                if order.state not in ('paid', 'invoiced'):
                    raise osv.except_osv(
                        _('Error!'),
                        _("You cannot confirm all orders of this session, because they have not the 'paid' status"))
                else:
                    wf_service.trg_validate(uid, 'pos.order', order.id, 'done', cr)

        return True

    def open_frontend_cb(self, cr, uid, ids, context=None):
        if not context:
            context = {}
        if not ids:
            return {}
        context.update({'active_id': ids[0]})
        return {
            'type' : 'ir.actions.client',
            'name' : _('Start Point Of Sale'),
            'tag' : 'pos.ui',
            'context' : context,
        }

class pos_order(osv.osv):
    _name = "pos.order"
    _description = "Point of Sale"
    _order = "id desc"

    def create_from_ui(self, cr, uid, orders, context=None):
        #_logger.info("orders: %r", orders)
        order_ids = []
        for tmp_order in orders:
            order = tmp_order['data']
            order_id = self.create(cr, uid, {
                'name': order['name'],
                'user_id': order['user_id'] or False,
                'session_id': order['pos_session_id'],
                'lines': order['lines']
            }, context)

            for payments in order['statement_ids']:
                payment = payments[2]
                self.add_payment(cr, uid, order_id, {
                    'amount': payment['amount'] or 0.0,
                    'payment_date': payment['name'],
                    'payment_name': payment.get('note', False),
{'fraction_non_alphanumeric': 0.12558435074660737, 'fraction_numerical': 0.0025870285480869603, 'mean_word_length': 2.0783793223891025, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 60, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2121670', 'n_tokens_mistral': 12451, 'n_tokens_neox': 11782, 'n_words': 3149}