From 70028097b9683696d9b26e5782df71b0414eeae3 Mon Sep 17 00:00:00 2001 From: "Ravish Murari (OpenERP)" Date: Mon, 16 Jul 2012 18:40:00 +0530 Subject: [PATCH 01/40] [FIX] recurring models for account not showing company lp bug: https://launchpad.net/bugs/999315 fixed bzr revid: rmu@tinyerp.com-20120716131000-tvwhhz4xcmwpubaj --- addons/account/account_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 80202d97d6c..9fd37ea94fe 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -1547,7 +1547,7 @@ - + @@ -1567,7 +1567,7 @@ - + From d397e599f546329ed659c4355f893e9a240fbee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 22 Aug 2012 15:55:46 +0200 Subject: [PATCH 02/40] [BRANCH] locadis: first commit : start of the branch bzr revid: fva@openerp.com-20120822135546-kwbo893th1pv2vbs --- addons/locadis/__init__.py | 25 + addons/locadis/__openerp__.py | 56 ++ addons/locadis/point_of_sale.py | 1303 +++++++++++++++++++++++++ addons/locadis/static/src/css/pos.css | 4 + addons/locadis/static/src/js/hello.js | 12 + addons/locadis/static/src/js/main.js | 24 + addons/locadis/static/src/xml/pos.xml | 15 + 7 files changed, 1439 insertions(+) create mode 100644 addons/locadis/__init__.py create mode 100644 addons/locadis/__openerp__.py create mode 100644 addons/locadis/point_of_sale.py create mode 100644 addons/locadis/static/src/css/pos.css create mode 100644 addons/locadis/static/src/js/hello.js create mode 100644 addons/locadis/static/src/js/main.js create mode 100644 addons/locadis/static/src/xml/pos.xml diff --git a/addons/locadis/__init__.py b/addons/locadis/__init__.py new file mode 100644 index 00000000000..e57b362135b --- /dev/null +++ b/addons/locadis/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +#import point_of_sale + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/locadis/__openerp__.py b/addons/locadis/__openerp__.py new file mode 100644 index 00000000000..056b8da7fcf --- /dev/null +++ b/addons/locadis/__openerp__.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + + +{ + 'name': 'Locadis Point Of Sale', + 'version': '1.0.1', + 'category': 'Point Of Sale', + "sequence": 6, + "summary": "Point of sale extensions for Locadis", + 'description': """ +FIXME: product description + """, + 'author': 'OpenERP SA', + 'images': ['images/cash_registers.jpeg', 'images/pos_analysis.jpeg','images/register_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'], + 'depends': ['point_of_sale'], + 'init_xml': [], + 'update_xml': [ + ], + 'demo_xml': [ + ], + 'test': [ + ], + 'installable': True, + 'application': True, + # Web client + 'js': [ + 'static/src/js/main.js' + ], + 'css': [ + 'static/src/css/pos.css' + ], + 'qweb': [ + 'static/src/xml/pos.xml' + ], + 'auto_install': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/locadis/point_of_sale.py b/addons/locadis/point_of_sale.py new file mode 100644 index 00000000000..f4d32985688 --- /dev/null +++ b/addons/locadis/point_of_sale.py @@ -0,0 +1,1303 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +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 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_payment_journal(self, cr, uid, context=None): + res = self.pool.get('account.journal').search(cr, uid, [('type', 'in', ('bank','cash'))], limit=2) + return res or [] + + 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, + 'journal_ids': _default_payment_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_journal_id(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, False) + for record in self.browse(cr, uid, ids, context=context): + for st in record.statement_ids: + if st.journal_id.type == 'cash': + result[record.id] = st.journal_id.id + break + return result + + def _compute_cash_register_id(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, False) + for record in self.browse(cr, uid, ids, context=context): + for st in record.statement_ids: + if st.journal_id.type == 'cash': + result[record.id] = st.id + break + return result + + def _compute_controls(self, cr, uid, ids, fieldnames, args, context=None): + result = {} + + for record in self.browse(cr, uid, ids, context=context): + has_opening_control = False + has_closing_control = False + + for journal in record.config_id.journal_ids: + if journal.opening_control == True: + has_opening_control = True + if journal.closing_control == True: + has_closing_control = True + + if has_opening_control and has_closing_control: + break + + values = { + 'has_opening_control': has_opening_control, + 'has_closing_control': has_closing_control, + } + result[record.id] = values + + 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_journal_id' : fields.function(_compute_cash_journal_id, method=True, + type='many2one', relation='account.journal', + string='Cash Journal', store=True), + 'cash_register_id' : fields.function(_compute_cash_register_id, method=True, + 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), + 'has_opening_control' : fields.function(_compute_controls, string='Has Opening Control', multi='control', type='boolean'), + 'has_closing_control' : fields.function(_compute_controls, string='Has Closing Control', multi='control', type='boolean'), + } + + _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: + cashids = self.pool.get('account.journal').search(cr, uid, [('journal_user','=',True)], context=context) + if not cashids: + cashids = self.pool.get('account.journal').search(cr, uid, [('type','=','cash')], context=context) + self.pool.get('account.journal').write(cr, uid, cashids, {'journal_user': True}) + 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.id <> session.cash_register_id.id: + if 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: + 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) + + 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 != 'paid': + 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), + 'journal': payment['journal_id'] + }, context=context) + + if order['amount_return']: + session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context) + self.add_payment(cr, uid, order_id, { + 'amount': -order['amount_return'], + 'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'), + 'payment_name': _('return'), + 'journal': session.cash_journal_id.id + }, context=context) + order_ids.append(order_id) + wf_service = netsvc.LocalService("workflow") + wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr) + return order_ids + + def unlink(self, cr, uid, ids, context=None): + for rec in self.browse(cr, uid, ids, context=context): + if rec.state not in ('draft','cancel'): + raise osv.except_osv(_('Unable to Delete !'), _('In order to delete a sale, it must be new or cancelled.')) + return super(pos_order, self).unlink(cr, uid, ids, context=context) + + def onchange_partner_id(self, cr, uid, ids, part=False, context=None): + if not part: + return {'value': {}} + pricelist = self.pool.get('res.partner').browse(cr, uid, part, context=context).property_product_pricelist.id + return {'value': {'pricelist_id': pricelist}} + + def _amount_all(self, cr, uid, ids, name, args, context=None): + tax_obj = self.pool.get('account.tax') + cur_obj = self.pool.get('res.currency') + res = {} + for order in self.browse(cr, uid, ids, context=context): + res[order.id] = { + 'amount_paid': 0.0, + 'amount_return':0.0, + 'amount_tax':0.0, + } + val1 = val2 = 0.0 + cur = order.pricelist_id.currency_id + for payment in order.statement_ids: + res[order.id]['amount_paid'] += payment.amount + res[order.id]['amount_return'] += (payment.amount < 0 and payment.amount or 0) + for line in order.lines: + val1 += line.price_subtotal_incl + val2 += line.price_subtotal + res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val1-val2) + res[order.id]['amount_total'] = cur_obj.round(cr, uid, cur, val1) + return res + + def copy(self, cr, uid, id, default=None, context=None): + if not default: + default = {} + d = { + 'state': 'draft', + 'invoice_id': False, + 'account_move': False, + 'picking_id': False, + 'statement_ids': [], + 'nb_print': 0, + 'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order'), + } + d.update(default) + return super(pos_order, self).copy(cr, uid, id, d, context=context) + + _columns = { + 'name': fields.char('Order Ref', size=64, required=True, readonly=True), + 'company_id':fields.many2one('res.company', 'Company', required=True, readonly=True), + 'shop_id': fields.related('session_id', 'config_id', 'shop_id', relation='sale.shop', type='many2one', string='Shop', store=True, readonly=True), + 'date_order': fields.datetime('Order Date', readonly=True, select=True), + '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."), + 'amount_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Point Of Sale'), multi='all'), + 'amount_total': fields.function(_amount_all, string='Total', multi='all'), + 'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits_compute=dp.get_precision('Point Of Sale'), multi='all'), + 'amount_return': fields.function(_amount_all, 'Returned', digits_compute=dp.get_precision('Point Of Sale'), multi='all'), + 'lines': fields.one2many('pos.order.line', 'order_id', 'Order Lines', states={'draft': [('readonly', False)]}, readonly=True), + 'statement_ids': fields.one2many('account.bank.statement.line', 'pos_statement_id', 'Payments', states={'draft': [('readonly', False)]}, readonly=True), + 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True), + 'partner_id': fields.many2one('res.partner', 'Customer', change_default=True, select=1, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]}), + + 'session_id' : fields.many2one('pos.session', 'Session', + #required=True, + select=1, + domain="[('state', '=', 'opened')]", + states={'draft' : [('readonly', False)]}, + readonly=True), + + 'state': fields.selection([('draft', 'New'), + ('cancel', 'Cancelled'), + ('paid', 'Paid'), + ('done', 'Posted'), + ('invoiced', 'Invoiced')], + 'Status', readonly=True), + + 'invoice_id': fields.many2one('account.invoice', 'Invoice'), + 'account_move': fields.many2one('account.move', 'Journal Entry', readonly=True), + 'picking_id': fields.many2one('stock.picking', 'Picking', readonly=True), + 'note': fields.text('Internal Notes'), + 'nb_print': fields.integer('Number of Print', readonly=True), + + 'sale_journal': fields.related('session_id', 'config_id', 'journal_id', relation='account.journal', type='many2one', string='Sale Journal', store=True, readonly=True), + } + + def _default_session(self, cr, uid, context=None): + so = self.pool.get('pos.session') + session_ids = so.search(cr, uid, [('state','=', 'opened'), ('user_id','=',uid)], context=context) + return session_ids and session_ids[0] or False + + def _default_pricelist(self, cr, uid, context=None): + res = self.pool.get('sale.shop').search(cr, uid, [], context=context) + if res: + shop = self.pool.get('sale.shop').browse(cr, uid, res[0], context=context) + return shop.pricelist_id and shop.pricelist_id.id or False + return False + + _defaults = { + 'user_id': lambda self, cr, uid, context: uid, + 'state': 'draft', + 'name': '/', + 'date_order': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), + 'nb_print': 0, + 'session_id': _default_session, + 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, + 'pricelist_id': _default_pricelist, + } + + def create(self, cr, uid, values, context=None): + values['name'] = self.pool.get('ir.sequence').get(cr, uid, 'pos.order') + return super(pos_order, self).create(cr, uid, values, context=context) + + def test_paid(self, cr, uid, ids, context=None): + """A Point of Sale is paid when the sum + @return: True + """ + for order in self.browse(cr, uid, ids, context=context): + if order.lines and not order.amount_total: + return True + if (not order.lines) or (not order.statement_ids) or \ + (abs(order.amount_total-order.amount_paid) > 0.00001): + return False + return True + + def create_picking(self, cr, uid, ids, context=None): + """Create a picking for each order and validate it.""" + picking_obj = self.pool.get('stock.picking') + partner_obj = self.pool.get('res.partner') + move_obj = self.pool.get('stock.move') + + for order in self.browse(cr, uid, ids, context=context): + if not order.state=='draft': + continue + addr = order.partner_id and partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery']) or {} + picking_id = picking_obj.create(cr, uid, { + 'origin': order.name, + 'partner_id': addr.get('delivery',False), + 'type': 'out', + 'company_id': order.company_id.id, + 'move_type': 'direct', + 'note': order.note or "", + 'invoice_state': 'none', + 'auto_picking': True, + }, context=context) + self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context) + location_id = order.shop_id.warehouse_id.lot_stock_id.id + output_id = order.shop_id.warehouse_id.lot_output_id.id + + for line in order.lines: + if line.product_id and line.product_id.type == 'service': + continue + if line.qty < 0: + location_id, output_id = output_id, location_id + + move_obj.create(cr, uid, { + 'name': line.name, + 'product_uom': line.product_id.uom_id.id, + 'product_uos': line.product_id.uom_id.id, + 'picking_id': picking_id, + 'product_id': line.product_id.id, + 'product_uos_qty': abs(line.qty), + 'product_qty': abs(line.qty), + 'tracking_id': False, + 'state': 'draft', + 'location_id': location_id, + 'location_dest_id': output_id, + }, context=context) + if line.qty < 0: + location_id, output_id = output_id, location_id + + wf_service = netsvc.LocalService("workflow") + wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) + picking_obj.force_assign(cr, uid, [picking_id], context) + return True + + def cancel_order(self, cr, uid, ids, context=None): + """ Changes order state to cancel + @return: True + """ + stock_picking_obj = self.pool.get('stock.picking') + for order in self.browse(cr, uid, ids, context=context): + wf_service.trg_validate(uid, 'stock.picking', order.picking_id.id, 'button_cancel', cr) + if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel': + raise osv.except_osv(_('Error!'), _('Unable to cancel the picking.')) + self.write(cr, uid, ids, {'state': 'cancel'}, context=context) + return True + + def add_payment(self, cr, uid, order_id, data, context=None): + """Create a new payment for the order""" + if not context: + context = {} + statement_obj = self.pool.get('account.bank.statement') + statement_line_obj = self.pool.get('account.bank.statement.line') + prod_obj = self.pool.get('product.product') + property_obj = self.pool.get('ir.property') + curr_c = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id + curr_company = curr_c.id + order = self.browse(cr, uid, order_id, context=context) + args = { + 'amount': data['amount'], + } + if 'payment_date' in data: + args['date'] = data['payment_date'] + args['name'] = order.name + if data.get('payment_name', False): + args['name'] = args['name'] + ': ' + data['payment_name'] + account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context) + args['account_id'] = (order.partner_id and order.partner_id.property_account_receivable \ + and order.partner_id.property_account_receivable.id) or (account_def and account_def.id) or False + args['partner_id'] = order.partner_id and order.partner_id.id or None + + if not args['account_id']: + if not args['partner_id']: + msg = _('There is no receivable account defined to make payment.') + else: + msg = _('There is no receivable account defined to make payment for the partner: "%s" (id:%d).') % (order.partner_id.name, order.partner_id.id,) + raise osv.except_osv(_('Configuration Error!'), msg) + + context.pop('pos_session_id', False) + + try: + journal_id = long(data['journal']) + except Exception: + journal_id = False + + statement_id = False + for statement in order.session_id.statement_ids: + if statement.journal_id.id == journal_id: + statement_id = statement.id + break + + if not statement_id: + raise osv.except_osv(_('Error!'), _('You have to open at least one cashbox.')) + + args.update({ + 'statement_id' : statement_id, + 'pos_statement_id' : order_id, + 'journal_id' : journal_id, + 'type' : 'customer', + 'ref' : order.name, + }) + + statement_line_obj.create(cr, uid, args, context=context) + + wf_service = netsvc.LocalService("workflow") + wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr) + wf_service.trg_write(uid, 'pos.order', order_id, cr) + + return statement_id + + def refund(self, cr, uid, ids, context=None): + """Create a copy of order for refund order""" + clone_list = [] + line_obj = self.pool.get('pos.order.line') + for order in self.browse(cr, uid, ids, context=context): + clone_id = self.copy(cr, uid, order.id, { + 'name': order.name + ' REFUND', + }, context=context) + clone_list.append(clone_id) + + for clone in self.browse(cr, uid, clone_list, context=context): + for order_line in clone.lines: + line_obj.write(cr, uid, [order_line.id], { + 'qty': -order_line.qty + }, context=context) + + new_order = ','.join(map(str,clone_list)) + abs = { + #'domain': "[('id', 'in', ["+new_order+"])]", + 'name': _('Return Products'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'pos.order', + 'res_id':clone_list[0], + 'view_id': False, + 'context':context, + 'type': 'ir.actions.act_window', + 'nodestroy': True, + 'target': 'current', + } + return abs + + def action_invoice_state(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state':'invoiced'}, context=context) + + def action_invoice(self, cr, uid, ids, context=None): + wf_service = netsvc.LocalService("workflow") + inv_ref = self.pool.get('account.invoice') + inv_line_ref = self.pool.get('account.invoice.line') + product_obj = self.pool.get('product.product') + inv_ids = [] + + for order in self.pool.get('pos.order').browse(cr, uid, ids, context=context): + if order.invoice_id: + inv_ids.append(order.invoice_id.id) + continue + + if not order.partner_id: + raise osv.except_osv(_('Error!'), _('Please provide a partner for the sale.')) + + acc = order.partner_id.property_account_receivable.id + inv = { + 'name': order.name, + 'origin': order.name, + 'account_id': acc, + 'journal_id': order.sale_journal.id or None, + 'type': 'out_invoice', + 'reference': order.name, + 'partner_id': order.partner_id.id, + 'comment': order.note or '', + 'currency_id': order.pricelist_id.currency_id.id, # considering partner's sale pricelist's currency + } + inv.update(inv_ref.onchange_partner_id(cr, uid, [], 'out_invoice', order.partner_id.id)['value']) + if not inv.get('account_id', None): + inv['account_id'] = acc + inv_id = inv_ref.create(cr, uid, inv, context=context) + + self.write(cr, uid, [order.id], {'invoice_id': inv_id, 'state': 'invoiced'}, context=context) + inv_ids.append(inv_id) + for line in order.lines: + inv_line = { + 'invoice_id': inv_id, + 'product_id': line.product_id.id, + 'quantity': line.qty, + } + inv_name = product_obj.name_get(cr, uid, [line.product_id.id], context=context)[0][1] + inv_line.update(inv_line_ref.product_id_change(cr, uid, [], + line.product_id.id, + line.product_id.uom_id.id, + line.qty, partner_id = order.partner_id.id, + fposition_id=order.partner_id.property_account_position.id)['value']) + if line.product_id.description_sale: + inv_line['note'] = line.product_id.description_sale + inv_line['price_unit'] = line.price_unit + inv_line['discount'] = line.discount + inv_line['name'] = inv_name + inv_line['invoice_line_tax_id'] = ('invoice_line_tax_id' in inv_line)\ + and [(6, 0, inv_line['invoice_line_tax_id'])] or [] + inv_line_ref.create(cr, uid, inv_line, context=context) + inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context) + wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr) + + if not inv_ids: return {} + + mod_obj = self.pool.get('ir.model.data') + res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form') + res_id = res and res[1] or False + return { + 'name': _('Customer Invoice'), + 'view_type': 'form', + 'view_mode': 'form', + 'view_id': [res_id], + 'res_model': 'account.invoice', + 'context': "{'type':'out_invoice'}", + 'type': 'ir.actions.act_window', + 'nodestroy': True, + 'target': 'current', + 'res_id': inv_ids and inv_ids[0] or False, + } + + def create_account_move(self, cr, uid, ids, context=None): + return self._create_account_move_line(cr, uid, ids, None, None, context=context) + + def _create_account_move_line(self, cr, uid, ids, session=None, move_id=None, context=None): + # Tricky, via the workflow, we only have one id in the ids variable + """Create a account move line of order grouped by products or not.""" + account_move_obj = self.pool.get('account.move') + account_move_line_obj = self.pool.get('account.move.line') + account_period_obj = self.pool.get('account.period') + account_tax_obj = self.pool.get('account.tax') + user_proxy = self.pool.get('res.users') + property_obj = self.pool.get('ir.property') + + period = account_period_obj.find(cr, uid, context=context)[0] + + #session_ids = set(order.session_id for order in self.browse(cr, uid, ids, context=context)) + + if session and not all(session.id == order.session_id.id for order in self.browse(cr, uid, ids, context=context)): + raise osv.except_osv(_('Error!'), _('Selected orders do not have the same session!')) + + current_company = user_proxy.browse(cr, uid, uid, context=context).company_id + + grouped_data = {} + have_to_group_by = session and session.config_id.group_by or False + + def compute_tax(amount, tax, line): + if amount > 0: + tax_code_id = tax['base_code_id'] + tax_amount = line.price_subtotal * tax['base_sign'] + else: + tax_code_id = tax['ref_base_code_id'] + tax_amount = line.price_subtotal * tax['ref_base_sign'] + + return (tax_code_id, tax_amount,) + + for order in self.browse(cr, uid, ids, context=context): + if order.account_move: + continue + if order.state != 'paid': + continue + + user_company = user_proxy.browse(cr, order.user_id.id, order.user_id.id).company_id + + group_tax = {} + account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context).id + + order_account = order.partner_id and \ + order.partner_id.property_account_receivable and \ + order.partner_id.property_account_receivable.id or account_def or current_company.account_receivable.id + + if move_id is None: + # Create an entry for the sale + move_id = account_move_obj.create(cr, uid, { + 'ref' : order.name, + 'journal_id': order.sale_journal.id, + }, context=context) + + def insert_data(data_type, values): + # if have_to_group_by: + + sale_journal_id = order.sale_journal.id + + # 'quantity': line.qty, + # 'product_id': line.product_id.id, + values.update({ + 'date': order.date_order[:10], + 'ref': order.name, + 'journal_id' : sale_journal_id, + 'period_id' : period, + 'move_id' : move_id, + 'company_id': user_company and user_company.id or False, + }) + + if data_type == 'product': + key = ('product', values['product_id'],) + elif data_type == 'tax': + key = ('tax', values['tax_code_id'],) + elif data_type == 'counter_part': + key = ('counter_part', values['partner_id'], values['account_id']) + else: + return + + grouped_data.setdefault(key, []) + + # if not have_to_group_by or (not grouped_data[key]): + # grouped_data[key].append(values) + # else: + # pass + + if have_to_group_by: + if not grouped_data[key]: + grouped_data[key].append(values) + else: + current_value = grouped_data[key][0] + current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0) + current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0) + current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0) + current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0) + else: + grouped_data[key].append(values) + + # Create an move for each order line + + for line in order.lines: + tax_amount = 0 + taxes = [t for t in line.product_id.taxes_id] + computed_taxes = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty)['taxes'] + + for tax in computed_taxes: + tax_amount += round(tax['amount'], 2) + group_key = (tax['tax_code_id'], tax['base_code_id'], tax['account_collected_id'], tax['id']) + + group_tax.setdefault(group_key, 0) + group_tax[group_key] += round(tax['amount'], 2) + + amount = line.price_subtotal + + # Search for the income account + if line.product_id.property_account_income.id: + income_account = line.product_id.property_account_income.id + elif line.product_id.categ_id.property_account_income_categ.id: + income_account = line.product_id.categ_id.property_account_income_categ.id + else: + raise osv.except_osv(_('Error!'), _('Please define income '\ + 'account for this product: "%s" (id:%d).') \ + % (line.product_id.name, line.product_id.id, )) + + # Empty the tax list as long as there is no tax code: + tax_code_id = False + tax_amount = 0 + while computed_taxes: + tax = computed_taxes.pop(0) + tax_code_id, tax_amount = compute_tax(amount, tax, line) + + # If there is one we stop + if tax_code_id: + break + + # Create a move for the line + insert_data('product', { + 'name': line.product_id.name, + 'quantity': line.qty, + 'product_id': line.product_id.id, + 'account_id': income_account, + 'credit': ((amount>0) and amount) or 0.0, + 'debit': ((amount<0) and -amount) or 0.0, + 'tax_code_id': tax_code_id, + 'tax_amount': tax_amount, + 'partner_id': order.partner_id and order.partner_id.id or False + }) + + # For each remaining tax with a code, whe create a move line + for tax in computed_taxes: + tax_code_id, tax_amount = compute_tax(amount, tax, line) + if not tax_code_id: + continue + + insert_data('tax', { + 'name': _('Tax'), + 'product_id':line.product_id.id, + 'quantity': line.qty, + 'account_id': income_account, + 'credit': 0.0, + 'debit': 0.0, + 'tax_code_id': tax_code_id, + 'tax_amount': tax_amount, + }) + + # Create a move for each tax group + (tax_code_pos, base_code_pos, account_pos, tax_id)= (0, 1, 2, 3) + + for key, tax_amount in group_tax.items(): + tax = self.pool.get('account.tax').browse(cr, uid, key[tax_id], context=context) + insert_data('tax', { + 'name': _('Tax') + ' ' + tax.name, + 'quantity': line.qty, + 'product_id': line.product_id.id, + 'account_id': key[account_pos], + 'credit': ((tax_amount>0) and tax_amount) or 0.0, + 'debit': ((tax_amount<0) and -tax_amount) or 0.0, + 'tax_code_id': key[tax_code_pos], + 'tax_amount': tax_amount, + }) + + # counterpart + insert_data('counter_part', { + 'name': _("Trade Receivables"), #order.name, + 'account_id': order_account, + 'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0, + 'debit': ((order.amount_total > 0) and order.amount_total) or 0.0, + 'partner_id': order.partner_id and order.partner_id.id or False + }) + + order.write({'state':'done', 'account_move': move_id}) + + for group_key, group_data in grouped_data.iteritems(): + for value in group_data: + account_move_line_obj.create(cr, uid, value, context=context) + + return True + + def action_payment(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state': 'payment'}, context=context) + + def action_paid(self, cr, uid, ids, context=None): + self.create_picking(cr, uid, ids, context=context) + self.write(cr, uid, ids, {'state': 'paid'}, context=context) + return True + + def action_cancel(self, cr, uid, ids, context=None): + self.write(cr, uid, ids, {'state': 'cancel'}, context=context) + return True + + def action_done(self, cr, uid, ids, context=None): + self.create_account_move(cr, uid, ids, context=context) + return True + +class account_bank_statement(osv.osv): + _inherit = 'account.bank.statement' + _columns= { + 'user_id': fields.many2one('res.users', 'User', readonly=True), + } + _defaults = { + 'user_id': lambda self,cr,uid,c={}: uid + } +account_bank_statement() + +class account_bank_statement_line(osv.osv): + _inherit = 'account.bank.statement.line' + _columns= { + 'pos_statement_id': fields.many2one('pos.order', ondelete='cascade'), + } +account_bank_statement_line() + +class pos_order_line(osv.osv): + _name = "pos.order.line" + _description = "Lines of Point of Sale" + _rec_name = "product_id" + + def _amount_line_all(self, cr, uid, ids, field_names, arg, context=None): + res = dict([(i, {}) for i in ids]) + account_tax_obj = self.pool.get('account.tax') + cur_obj = self.pool.get('res.currency') + for line in self.browse(cr, uid, ids, context=context): + taxes = line.product_id.taxes_id + price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) + taxes = account_tax_obj.compute_all(cr, uid, line.product_id.taxes_id, price, line.qty, product=line.product_id, partner=line.order_id.partner_id or False) + + cur = line.order_id.pricelist_id.currency_id + res[line.id]['price_subtotal'] = cur_obj.round(cr, uid, cur, taxes['total']) + res[line.id]['price_subtotal_incl'] = cur_obj.round(cr, uid, cur, taxes['total_included']) + return res + + def onchange_product_id(self, cr, uid, ids, pricelist, product_id, qty=0, partner_id=False, context=None): + context = context or {} + if not product_id: + return {} + if not pricelist: + raise osv.except_osv(_('No Pricelist !'), + _('You have to select a pricelist in the sale form !\n' \ + 'Please set one before choosing a product.')) + + price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist], + product_id, qty or 1.0, partner_id)[pricelist] + + result = self.onchange_qty(cr, uid, ids, product_id, 0.0, qty, price, context=context) + result['value']['price_unit'] = price + return result + + def onchange_qty(self, cr, uid, ids, product, discount, qty, price_unit, context=None): + result = {} + if not product: + return result + account_tax_obj = self.pool.get('account.tax') + cur_obj = self.pool.get('res.currency') + + prod = self.pool.get('product.product').browse(cr, uid, product, context=context) + + taxes = prod.taxes_id + price = price_unit * (1 - (discount or 0.0) / 100.0) + taxes = account_tax_obj.compute_all(cr, uid, prod.taxes_id, price, qty, product=prod, partner=False) + + result['price_subtotal'] = taxes['total'] + result['price_subtotal_incl'] = taxes['total_included'] + return {'value': result} + + _columns = { + 'company_id': fields.many2one('res.company', 'Company', required=True), + 'name': fields.char('Line No', size=32, required=True), + 'notice': fields.char('Discount Notice', size=128), + 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, change_default=True), + 'price_unit': fields.float(string='Unit Price', digits=(16, 2)), + 'qty': fields.float('Quantity', digits=(16, 2)), + 'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal w/o Tax', store=True), + 'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal', store=True), + 'discount': fields.float('Discount (%)', digits=(16, 2)), + 'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'), + 'create_date': fields.datetime('Creation Date', readonly=True), + } + + _defaults = { + 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'pos.order.line'), + 'qty': lambda *a: 1, + 'discount': lambda *a: 0.0, + 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, + } + + def copy_data(self, cr, uid, id, default=None, context=None): + if not default: + default = {} + default.update({ + 'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order.line') + }) + return super(pos_order_line, self).copy_data(cr, uid, id, default, context=context) + +class pos_category(osv.osv): + _name = 'pos.category' + _description = "Point of Sale Category" + _order = "sequence, name" + def _check_recursion(self, cr, uid, ids, context=None): + level = 100 + while len(ids): + cr.execute('select distinct parent_id from pos_category where id IN %s',(tuple(ids),)) + ids = filter(None, map(lambda x:x[0], cr.fetchall())) + if not level: + return False + level -= 1 + return True + + _constraints = [ + (_check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id']) + ] + + def name_get(self, cr, uid, ids, context=None): + if not len(ids): + return [] + reads = self.read(cr, uid, ids, ['name','parent_id'], context=context) + res = [] + for record in reads: + name = record['name'] + if record['parent_id']: + name = record['parent_id'][1]+' / '+name + res.append((record['id'], name)) + return res + + def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None): + res = self.name_get(cr, uid, ids, context=context) + return dict(res) + + def _get_image(self, cr, uid, ids, name, args, context=None): + result = dict.fromkeys(ids, False) + for obj in self.browse(cr, uid, ids, context=context): + result[obj.id] = tools.image_get_resized_images(obj.image) + return result + + def _set_image(self, cr, uid, id, name, value, args, context=None): + return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) + + _columns = { + 'name': fields.char('Name', size=64, required=True, translate=True), + 'complete_name': fields.function(_name_get_fnc, type="char", string='Name'), + 'parent_id': fields.many2one('pos.category','Parent Category', select=True), + 'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'), + 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."), + + # NOTE: there is no 'default image', because by default we don't show thumbnails for categories. However if we have a thumbnail + # for at least one category, then we display a default image on the other, so that the buttons have consistent styling. + # In this case, the default image is set by the js code. + + 'image': fields.binary("Image", + help="This field holds the image used for the category. "\ + "The image is base64 encoded, and PIL-supported. "\ + "It is limited to a 1024x1024 px image."), + 'image_medium': fields.function(_get_image, fnct_inv=_set_image, + string="Medium-sized image", type="binary", multi="_get_image", + store = { + 'pos.category': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), + }, + help="Medium-sized image of the category. It is automatically "\ + "resized as a 180x180 px image, with aspect ratio preserved. "\ + "Use this field in form views or some kanban views."), + 'image_small': fields.function(_get_image, fnct_inv=_set_image, + string="Smal-sized image", type="binary", multi="_get_image", + store = { + 'pos.category': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), + }, + help="Small-sized image of the category. It is automatically "\ + "resized as a 50x50 px image, with aspect ratio preserved. "\ + "Use this field anywhere a small image is required."), + } + +import io, StringIO + +class ean_wizard(osv.osv_memory): + _name = 'pos.ean_wizard' + _columns = { + 'ean13_pattern': fields.char('Ean13 Pattern', size=32, required=True, translate=True), + } + def sanitize_ean13(self, cr, uid, ids, context): + for r in self.browse(cr,uid,ids): + ean13 = openerp.addons.product.product.sanitize_ean13(r.ean13_pattern) + m = context.get('active_model') + m_id = context.get('active_id') + self.pool.get(m).write(cr,uid,[m_id],{'ean13':ean13}) + return { 'type' : 'ir.actions.act_window_close' } + +class product_product(osv.osv): + _inherit = 'product.product' + + + #def _get_small_image(self, cr, uid, ids, prop, unknow_none, context=None): + # result = {} + # for obj in self.browse(cr, uid, ids, context=context): + # if not obj.product_image: + # result[obj.id] = False + # continue + + # image_stream = io.BytesIO(obj.product_image.decode('base64')) + # img = Image.open(image_stream) + # img.thumbnail((120, 100), Image.ANTIALIAS) + # img_stream = StringIO.StringIO() + # img.save(img_stream, "JPEG") + # result[obj.id] = img_stream.getvalue().encode('base64') + # return result + + _columns = { + 'income_pdt': fields.boolean('Point of Sale Cash In', help="This is a product you can use to put cash into a statement for the point of sale backend."), + 'expense_pdt': fields.boolean('Point of Sale Cash Out', help="This is a product you can use to take cash from a statement for the point of sale backend, exemple: money lost, transfer to bank, etc."), + 'pos_categ_id': fields.many2one('pos.category','Point of Sale Category', + help="If you want to sell this product through the point of sale, select the category it belongs to."), + 'to_weight' : fields.boolean('To Weight', help="This category contains products that should be weighted, mainly used for the self-checkout interface"), + } + _defaults = { + 'to_weight' : False, + } + + def edit_ean(self, cr, uid, ids, context): + return { + 'name': "Edit Ean", + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'pos.ean_wizard', + 'target' : 'new', + 'view_id': False, + 'context':context, + } + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/locadis/static/src/css/pos.css b/addons/locadis/static/src/css/pos.css new file mode 100644 index 00000000000..d9ec3ba7cee --- /dev/null +++ b/addons/locadis/static/src/css/pos.css @@ -0,0 +1,4 @@ + +.product-list-empty{ + text-align:center; +} diff --git a/addons/locadis/static/src/js/hello.js b/addons/locadis/static/src/js/hello.js new file mode 100644 index 00000000000..7deacfa994c --- /dev/null +++ b/addons/locadis/static/src/js/hello.js @@ -0,0 +1,12 @@ + +openerp.locadis = function(instance){ + console.log('Hello World!:',instance); + instance.point_of_sale.PosWidget = instance.point_of_sale.PosWidget.extend({ + init:function(parent,options){ + console.log('Tadaaam!',parent,options); + this._super(parent,options); + }, + }); + + /* REMOVE PRODUCTS FROM FIRST LEVEL IN SELF CHECKOUT */ +}; diff --git a/addons/locadis/static/src/js/main.js b/addons/locadis/static/src/js/main.js new file mode 100644 index 00000000000..1d0f41d8360 --- /dev/null +++ b/addons/locadis/static/src/js/main.js @@ -0,0 +1,24 @@ +openerp.locadis = function(instance){ + + var module = instance.point_of_sale; + var QWeb = instance.web.qweb; + + module.ProductListWidget = module.ProductListWidget.extend({ + template_empty: 'ProductEmptyListWidget', + get_category: function(){ + return this.getParent().product_categories_widget.category; + }, + renderElement: function(){ + var ss = this.pos_widget.screen_selector; + console.log('ss',ss); + if(this.get_category().name === 'Root' && ss && ss.get_user_mode() === 'cashier'){ + this.replaceElement(_.str.trim(QWeb.render(this.template_empty,{widget:this}))); + }else{ + this._super(); + } + }, + }); +}; + + + diff --git a/addons/locadis/static/src/xml/pos.xml b/addons/locadis/static/src/xml/pos.xml new file mode 100644 index 00000000000..f69c3b788df --- /dev/null +++ b/addons/locadis/static/src/xml/pos.xml @@ -0,0 +1,15 @@ + + + + +
+
+

Please scan an item

+ +
+
+
+
+ + +
From 0d70cdaaf51993a13acbf6964d34e71e601b0525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 23 Aug 2012 14:18:35 +0200 Subject: [PATCH 03/40] [IMP] locadis: dont_vidange and extra fields added to products bzr revid: fva@openerp.com-20120823121835-o3119dcmu9s0048t --- addons/locadis/__init__.py | 2 +- addons/locadis/__openerp__.py | 10 +++------- addons/locadis/static/src/css/pos.css | 4 +++- addons/locadis/static/src/xml/pos.xml | 3 +-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/addons/locadis/__init__.py b/addons/locadis/__init__.py index e57b362135b..60ffcac8393 100644 --- a/addons/locadis/__init__.py +++ b/addons/locadis/__init__.py @@ -19,7 +19,7 @@ # ############################################################################## -#import point_of_sale +import locadis # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/locadis/__openerp__.py b/addons/locadis/__openerp__.py index 056b8da7fcf..4ec8527fc7a 100644 --- a/addons/locadis/__openerp__.py +++ b/addons/locadis/__openerp__.py @@ -30,14 +30,10 @@ FIXME: product description """, 'author': 'OpenERP SA', - 'images': ['images/cash_registers.jpeg', 'images/pos_analysis.jpeg','images/register_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'], + 'images': [], 'depends': ['point_of_sale'], - 'init_xml': [], - 'update_xml': [ - ], - 'demo_xml': [ - ], - 'test': [ + 'data':[ + 'locadis_views.xml', ], 'installable': True, 'application': True, diff --git a/addons/locadis/static/src/css/pos.css b/addons/locadis/static/src/css/pos.css index d9ec3ba7cee..075b4dccc27 100644 --- a/addons/locadis/static/src/css/pos.css +++ b/addons/locadis/static/src/css/pos.css @@ -1,4 +1,6 @@ - +.point-of-sale .screen p{ + font-size: 24px; +} .product-list-empty{ text-align:center; } diff --git a/addons/locadis/static/src/xml/pos.xml b/addons/locadis/static/src/xml/pos.xml index f69c3b788df..5ce23396db8 100644 --- a/addons/locadis/static/src/xml/pos.xml +++ b/addons/locadis/static/src/xml/pos.xml @@ -4,12 +4,11 @@
-

Please scan an item

+

Please scan your products

- From 1597d57466e525f0a3de08714e37dc74674a1edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 3 Sep 2012 15:02:44 +0200 Subject: [PATCH 04/40] [FIX] locadis: probably forgot to add these files bzr revid: fva@openerp.com-20120903130244-j2l901o2x5pztpu8 --- addons/locadis/locadis.py | 51 ++++++++++++++++++++++++++++++++ addons/locadis/locadis_views.xml | 16 ++++++++++ 2 files changed, 67 insertions(+) create mode 100644 addons/locadis/locadis.py create mode 100644 addons/locadis/locadis_views.xml diff --git a/addons/locadis/locadis.py b/addons/locadis/locadis.py new file mode 100644 index 00000000000..fc079ab4288 --- /dev/null +++ b/addons/locadis/locadis.py @@ -0,0 +1,51 @@ + +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +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 product_product(osv.osv): + _inherit = 'product.product' + _columns = { + 'dont_vidange': fields.float('Dont Vidange', help="Le prix de ce produit inclus ce montant de vidange"), + 'extra': fields.char('Information Supplémentaire', help="Permet de stocker des informations supplémentaires sur le produit",size=256), + } + _defaults = { + 'dont_vidange': 0.0, + } + diff --git a/addons/locadis/locadis_views.xml b/addons/locadis/locadis_views.xml new file mode 100644 index 00000000000..c1c99100a8d --- /dev/null +++ b/addons/locadis/locadis_views.xml @@ -0,0 +1,16 @@ + + + + + product.locadis + product.product + + + + + + + + + + From c6e0e905b9c3bc8e452fde41579a6178afa4a6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 3 Sep 2012 16:34:45 +0200 Subject: [PATCH 05/40] [WIP] locadis: wip, need to merge bzr revid: fva@openerp.com-20120903143445-xf5cl43c9sijqdtw --- addons/locadis/locadis.py | 1 - addons/locadis/locadis_views.xml | 1 - addons/locadis/static/src/js/main.js | 130 +++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/addons/locadis/locadis.py b/addons/locadis/locadis.py index fc079ab4288..4d04825cba3 100644 --- a/addons/locadis/locadis.py +++ b/addons/locadis/locadis.py @@ -43,7 +43,6 @@ class product_product(osv.osv): _inherit = 'product.product' _columns = { 'dont_vidange': fields.float('Dont Vidange', help="Le prix de ce produit inclus ce montant de vidange"), - 'extra': fields.char('Information Supplémentaire', help="Permet de stocker des informations supplémentaires sur le produit",size=256), } _defaults = { 'dont_vidange': 0.0, diff --git a/addons/locadis/locadis_views.xml b/addons/locadis/locadis_views.xml index c1c99100a8d..fc8473b1c3c 100644 --- a/addons/locadis/locadis_views.xml +++ b/addons/locadis/locadis_views.xml @@ -8,7 +8,6 @@ - diff --git a/addons/locadis/static/src/js/main.js b/addons/locadis/static/src/js/main.js index 1d0f41d8360..fcd9ee78bf6 100644 --- a/addons/locadis/static/src/js/main.js +++ b/addons/locadis/static/src/js/main.js @@ -3,6 +3,136 @@ openerp.locadis = function(instance){ var module = instance.point_of_sale; var QWeb = instance.web.qweb; + console.log('PosModel?', module.PosModel); + + module.PosModel = module.PosModel.extend({ + // all this copy paste just to load the product.dont_vidange field ... :( + load_server_data: function(){ + console.log('loading'); + var self = this; + + var loaded = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]]) + .pipe(function(users){ + self.set('user',users[0]); + + return fetch('res.company', + [ + 'currency_id', + 'email', + 'website', + 'company_registry', + 'vat', + 'name', + 'phone', + 'partner_id', + ], + [['id','=',users[0].company_id[0]]]); + }).pipe(function(companies){ + self.set('company',companies[0]); + + return fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]); + }).pipe(function(company_partners){ + self.get('company').contact_address = company_partners[0].contact_address; + + return fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]); + }).pipe(function(currencies){ + self.set('currency',currencies[0]); + + return fetch('product.uom', null, null); + }).pipe(function(units){ + self.set('units',units); + var units_by_id = {}; + for(var i = 0, len = units.length; i < len; i++){ + units_by_id[units[i].id] = units[i]; + } + self.set('units_by_id',units_by_id); + + return fetch('product.packaging', null, null); + }).pipe(function(packagings){ + self.set('product.packaging',packagings); + + return fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]); + }).pipe(function(users){ + self.set('user_list',users); + + return fetch('account.tax', ['amount', 'price_include', 'type']); + }).pipe(function(taxes){ + self.set('taxes', taxes); + + return fetch( + 'pos.session', + ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'], + [['state', '=', 'opened'], ['user_id', '=', self.session.uid]] + ); + }).pipe(function(sessions){ + self.set('pos_session', sessions[0]); + + return fetch( + 'pos.config', + ['name','journal_ids','shop_id','journal_id', + 'iface_self_checkout', 'iface_led', 'iface_cashdrawer', + 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard', + 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'], + [['id','=', self.get('pos_session').config_id[0]]] + ); + }).pipe(function(configs){ + var pos_config = configs[0]; + self.set('pos_config', pos_config); + self.iface_electronic_scale = !!pos_config.iface_electronic_scale; + self.iface_print_via_proxy = !!pos_config.iface_print_via_proxy; + self.iface_vkeyboard = !!pos_config.iface_vkeyboard; + self.iface_self_checkout = !!pos_config.iface_self_checkout; + self.iface_cashdrawer = !!pos_config.iface_cashdrawer; + + return fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]); + }).pipe(function(shops){ + self.set('shop',shops[0]); + + return fetch('pos.category', ['id','name','parent_id','child_id','image']) + }).pipe(function(categories){ + self.db.add_categories(categories); + + return fetch( + 'product.product', + ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', + 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', + 'description_sale', 'description','dont_vidange'], + [['pos_categ_id','!=', false],['sale_ok','=',true]], + {pricelist: self.get('shop').pricelist_id[0]} // context for price + ); + }).pipe(function(products){ + self.db.add_products(products); + + return fetch( + 'account.bank.statement', + ['account_id','currency','journal_id','state','name','user_id','pos_session_id'], + [['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]] + ); + }).pipe(function(bank_statements){ + self.set('bank_statements', bank_statements); + + return fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]); + }).pipe(function(journals){ + self.set('journals',journals); + + // associate the bank statements with their journals. + var bank_statements = self.get('bank_statements'); + for(var i = 0, ilen = bank_statements.length; i < ilen; i++){ + for(var j = 0, jlen = journals.length; j < jlen; j++){ + if(bank_statements[i].journal_id[0] === journals[j].id){ + bank_statements[i].journal = journals[j]; + bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method; + } + } + } + self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))}); + self.log_loaded_data(); + }); + + + return loaded; + }, + }); module.ProductListWidget = module.ProductListWidget.extend({ template_empty: 'ProductEmptyListWidget', get_category: function(){ From e0d5b6ac8bf8607eb8511e3a956a09cab069da06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 3 Sep 2012 16:43:54 +0200 Subject: [PATCH 06/40] [IMP] point_of_sale: fetch is now a method bzr revid: fva@openerp.com-20120903144354-8563uwq7x0cvqvrp --- addons/point_of_sale/static/src/js/models.js | 37 ++++++++++---------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 93860697492..cb258187b2a 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1,9 +1,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sale var QWeb = instance.web.qweb; - var fetch = function(model, fields, domain, ctx){ - return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all() - }; // The PosModel contains the Point Of Sale's representation of the backend. // Since the PoS must work in standalone ( Without connection to the server ) @@ -70,15 +67,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }); }, + // helper function to load data from the server + fetch: function(model, fields, domain, ctx){ + return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all() + }; // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded. load_server_data: function(){ var self = this; - var loaded = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]]) + var loaded = self.fetch('res.users',['name','company_id'],[['id','=',this.session.uid]]) .pipe(function(users){ self.set('user',users[0]); - return fetch('res.company', + return self.fetch('res.company', [ 'currency_id', 'email', @@ -93,15 +94,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }).pipe(function(companies){ self.set('company',companies[0]); - return fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]); + return self.fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]); }).pipe(function(company_partners){ self.get('company').contact_address = company_partners[0].contact_address; - return fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]); + return self.fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]); }).pipe(function(currencies){ self.set('currency',currencies[0]); - return fetch('product.uom', null, null); + return self.fetch('product.uom', null, null); }).pipe(function(units){ self.set('units',units); var units_by_id = {}; @@ -110,19 +111,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } self.set('units_by_id',units_by_id); - return fetch('product.packaging', null, null); + return self.fetch('product.packaging', null, null); }).pipe(function(packagings){ self.set('product.packaging',packagings); - return fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]); + return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]); }).pipe(function(users){ self.set('user_list',users); - return fetch('account.tax', ['amount', 'price_include', 'type']); + return self.fetch('account.tax', ['amount', 'price_include', 'type']); }).pipe(function(taxes){ self.set('taxes', taxes); - return fetch( + return self.fetch( 'pos.session', ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'], [['state', '=', 'opened'], ['user_id', '=', self.session.uid]] @@ -130,7 +131,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }).pipe(function(sessions){ self.set('pos_session', sessions[0]); - return fetch( + return self.fetch( 'pos.config', ['name','journal_ids','shop_id','journal_id', 'iface_self_checkout', 'iface_led', 'iface_cashdrawer', @@ -147,15 +148,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal self.iface_self_checkout = !!pos_config.iface_self_checkout; self.iface_cashdrawer = !!pos_config.iface_cashdrawer; - return fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]); + return self.fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]); }).pipe(function(shops){ self.set('shop',shops[0]); - return fetch('pos.category', ['id','name','parent_id','child_id','image']) + return self.fetch('pos.category', ['id','name','parent_id','child_id','image']) }).pipe(function(categories){ self.db.add_categories(categories); - return fetch( + return self.fetch( 'product.product', ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description'], @@ -165,7 +166,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }).pipe(function(products){ self.db.add_products(products); - return fetch( + return self.fetch( 'account.bank.statement', ['account_id','currency','journal_id','state','name','user_id','pos_session_id'], [['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]] @@ -173,7 +174,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }).pipe(function(bank_statements){ self.set('bank_statements', bank_statements); - return fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]); + return self.fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]); }).pipe(function(journals){ self.set('journals',journals); From d66519fda2935caaadc17ad5b10266af0cbc7870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 3 Sep 2012 16:59:58 +0200 Subject: [PATCH 07/40] [FIX] point_of_sale: I should have tested this more bzr revid: fva@openerp.com-20120903145958-c7i93lp8q8w8ilop --- addons/point_of_sale/static/src/js/models.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index cb258187b2a..caf3868482a 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -70,7 +70,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // helper function to load data from the server fetch: function(model, fields, domain, ctx){ return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all() - }; + }, // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded. load_server_data: function(){ var self = this; From ee07aa224ca5a338e34358cc1842e5a759b9f2d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 4 Sep 2012 17:46:03 +0200 Subject: [PATCH 08/40] [IMP] point_of_sale: added console ean input in debug mode bzr revid: fva@openerp.com-20120904154603-khymfg996jdgn9xr --- addons/point_of_sale/static/src/js/devices.js | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index e36070414b6..1c1902cdf8f 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -236,7 +236,20 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.price_prefix_set = attributes.price_prefix_set || {'23':''}; this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''}; this.client_prefix_set = attributes.client_prefix_set || {'042':''}; + + if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){ + var self = this; + window.simulate_ean = function(ean,strict){ + ean = ean.toString(); + if(!strict){ + ean = self.sanitize_ean(ean); + } + console.log('SIMULATE EAN: ',ean); + self.on_ean(ean); + } + } }, + save_callbacks: function(){ var callbacks = {}; for(name in this.action_callback){ @@ -244,6 +257,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } this.action_callback_stack.push(callbacks); }, + restore_callbacks: function(){ if(this.action_callback_stack.length){ var callbacks = this.action_callback_stack.pop(); @@ -380,6 +394,23 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal return parse_result; }, + on_ean: function(ean){ + var parse_result = this.parse_ean(ean); + + if (parse_result.type === 'error') { //most likely a checksum error, raise warning + console.warn('WARNING: barcode checksum error:',parse_result); + }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product + if(this.action_callback['product']){ + this.action_callback['product'](parse_result); + } + //this.trigger("codebar",parse_result ); + }else{ + if(this.action_callback[parse_result.type]){ + this.action_callback[parse_result.type](parse_result); + } + } + }, + // starts catching keyboard events and tries to interpret codebar // calling the callbacks when needed. connect: function(){ @@ -410,21 +441,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal lastTimeStamp = new Date().getTime(); if (codeNumbers.length == 13) { //We have found what seems to be a valid codebar - var parse_result = self.parse_ean(codeNumbers.join('')); - - if (parse_result.type === 'error') { //most likely a checksum error, raise warning - console.warn('WARNING: barcode checksum error:',parse_result); - }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product - if(self.action_callback['product']){ - self.action_callback['product'](parse_result); - } - //this.trigger("codebar",parse_result ); - }else{ - if(self.action_callback[parse_result.type]){ - self.action_callback[parse_result.type](parse_result); - } - } - + self.on_ean(codeNumbers.join('')); codeNumbers = []; } } else { From f24667b1f9e0175be293c0a219f1b366dd95d9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 5 Sep 2012 11:20:53 +0200 Subject: [PATCH 09/40] [FIX] point_of_sale: ean argument was wrongly passed to the proxy bzr revid: fva@openerp.com-20120905092053-9hj9sbscqrjbg0h0 --- addons/point_of_sale/controllers/main.py | 8 ++++---- addons/point_of_sale/static/src/js/devices.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index 425a7e3d7f8..5e4d8f8a75a 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -72,19 +72,19 @@ class PointOfSaleController(openerpweb.Controller): return getattr(self, method)(request, **kwargs) @openerpweb.jsonrequest - def scan_item_success(self, request): + def scan_item_success(self, request, ean): """ A product has been scanned with success """ - print 'scan_item_success' + print 'scan_item_success: ' + str(ean) return @openerpweb.jsonrequest - def scan_item_error_unrecognized(self, request): + def scan_item_error_unrecognized(self, request, ean): """ A product has been scanned without success """ - print 'scan_item_error_unrecognized' + print 'scan_item_error_unrecognized: ' + str(ean) return @openerpweb.jsonrequest diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 1c1902cdf8f..34efb993db3 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -57,13 +57,13 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal //a product has been scanned and recognized with success // ean is a parsed ean object scan_item_success: function(ean){ - this.message('scan_item_success',ean); + this.message('scan_item_success',{ean: ean}); }, // a product has been scanned but not recognized // ean is a parsed ean object scan_item_error_unrecognized: function(ean){ - this.message('scan_item_error_unrecognized',ean); + this.message('scan_item_error_unrecognized',{ean: ean}); }, //the client is asking for help From 49b1605b9846cc56d51998e5dc6c4178ab464c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 5 Sep 2012 13:50:00 +0200 Subject: [PATCH 10/40] [FIX] point_of_sale: css fixes related to ul/li semantic css changes bzr revid: fva@openerp.com-20120905115000-dx2exlr3cnn2iszy --- addons/point_of_sale/static/src/css/pos.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index bfe2ef7f9dc..c43d5641ed6 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -367,7 +367,7 @@ /* ********* The product list ********* */ .point-of-sale .product-list { - padding:10px; + padding:10px !important; } .point-of-sale .product-list-scroller{ @@ -464,7 +464,7 @@ } .point-of-sale .category-list{ - padding:10px; + padding:10px !important; } /* d) the category button */ @@ -473,7 +473,7 @@ vertical-align: top; display: inline-block; font-size: 11px; - margin: 5px; + margin: 5px !important; width: 120px; height:120px; background:#fff; @@ -560,7 +560,7 @@ display: inline-block; line-height: 100px; font-size: 11px; - margin: 5px; + margin: 5px !important; width: 120px; height:120px; background:#fff; From 4b0cc507d78da894b41b18cdd2ff0637ea4f04be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 5 Sep 2012 14:20:22 +0200 Subject: [PATCH 11/40] [FIX] fixes locadis views bzr revid: fva@openerp.com-20120905122022-x9ist4f080vp9sx3 --- addons/locadis/locadis_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/locadis/locadis_views.xml b/addons/locadis/locadis_views.xml index fc8473b1c3c..3b9b9d87195 100644 --- a/addons/locadis/locadis_views.xml +++ b/addons/locadis/locadis_views.xml @@ -4,7 +4,7 @@ product.locadis product.product - + From 2601dcd9f821ad4897bb8be69cd34df3d0d75215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 5 Sep 2012 14:55:17 +0200 Subject: [PATCH 12/40] [FIX] show vidange info in order widget bzr revid: fva@openerp.com-20120905125517-jbrugjta39z9qxsm --- addons/locadis/static/src/js/main.js | 1 - addons/locadis/static/src/xml/pos.xml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/locadis/static/src/js/main.js b/addons/locadis/static/src/js/main.js index 59e53e7ae1a..e1c78f1b341 100644 --- a/addons/locadis/static/src/js/main.js +++ b/addons/locadis/static/src/js/main.js @@ -126,7 +126,6 @@ openerp.locadis = function(instance){ } } self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))}); - self.log_loaded_data(); }); diff --git a/addons/locadis/static/src/xml/pos.xml b/addons/locadis/static/src/xml/pos.xml index 0a5ce169048..ad983b96721 100644 --- a/addons/locadis/static/src/xml/pos.xml +++ b/addons/locadis/static/src/xml/pos.xml @@ -14,9 +14,9 @@ - +
  • - Vidange: + Vidange:
  • From bff5b81db3b5da0077e5d60902ea26fea81878b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 5 Sep 2012 18:42:21 +0200 Subject: [PATCH 13/40] [FIX] fixing some regressions bzr revid: fva@openerp.com-20120905164221-hcigfd0kxdckgz94 --- addons/point_of_sale/controllers/main.py | 5 ++ addons/point_of_sale/static/src/css/pos.css | 4 +- addons/point_of_sale/static/src/js/db.js | 3 + addons/point_of_sale/static/src/js/devices.js | 5 ++ addons/point_of_sale/static/src/js/models.js | 16 +++++ addons/point_of_sale/static/src/js/screens.js | 64 ++++++++++++++++--- addons/point_of_sale/static/src/js/widgets.js | 13 ++-- addons/point_of_sale/static/src/xml/pos.xml | 8 +-- 8 files changed, 99 insertions(+), 19 deletions(-) diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index 5e4d8f8a75a..e6976dd319c 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -166,4 +166,9 @@ class PointOfSaleController(openerpweb.Controller): print 'print_receipt' + str(receipt) return + @openerpweb.jsonrequest + def print_pdf_invoice(self, request, pdfinvoice): + print 'print_pdf_invoice' + str(pdfinvoice) + return + diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index c43d5641ed6..3f8b91a7df0 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -803,6 +803,8 @@ } .point-of-sale .scale-screen .product-picture img{ + max-width: 178px; + max-height:178px; vertical-align: middle; cursor:pointer; } @@ -854,7 +856,7 @@ .point-of-sale .goodbye-message{ position: absolute; left:50%; - top:30%; + top:40%; width:500px; height:400px; margin-left: -250px; diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index 77f7307b48f..58ffa3373d6 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -223,9 +223,12 @@ function openerp_pos_db(instance, module){ }, remove_order: function(order_id){ var orders = this.load('orders',[]); + console.log('Remove order:',order_id); + console.log('Order count:',orders.length); orders = _.filter(orders, function(order){ return order.id !== order_id; }); + console.log('Order count:',orders.length); this.save('orders',orders); }, get_orders: function(){ diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 34efb993db3..46d11938128 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -212,6 +212,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal print_receipt: function(receipt){ this.message('print_receipt',{receipt: receipt}); }, + + // asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server ) + print_pdf_invoice: function(pdfinvoice){ + this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice}); + }, }); // this module interfaces with the barcode reader. It assumes the barcode reader diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index caf3868482a..5928caf7c77 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -227,6 +227,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order push_order: function(record) { + console.log('PUSHING NEW ORDER:',record); this.db.add_order(record); this.flush(); }, @@ -242,10 +243,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // and remove the successfully sent ones from the db once // it has been confirmed that they have been sent correctly. flush: function() { + //TODO make the mutex work + console.log('FLUSH'); //this makes sure only one _int_flush is called at the same time + /* return this.flush_mutex.exec(_.bind(function() { return this._flush(0); }, this)); + */ + this._flush(0); }, // attempts to send an order of index 'index' in the list of order to send. The index // is used to skip orders that failed. do not call this method outside the mutex provided @@ -254,6 +260,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var self = this; var orders = this.db.get_orders(); self.set('nbr_pending_operations',orders.length); + console.log('TRYING TO FLUSH ORDER:',index,'Of',orders.length); var order = orders[index]; if(!order){ @@ -269,6 +276,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }) .done(function(){ //remove from db if success + console.log('Order successfully sent'); self.db.remove_order(order.id); self._flush(index); }); @@ -547,6 +555,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.pos = attributes.pos; this.selected_orderline = undefined; this.screen_data = {}; // see ScreenSelector + this.receipt_type = 'receipt'; // 'receipt' || 'invoice' return this; }, generateUniqueId: function() { @@ -618,6 +627,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal getDueLeft: function() { return this.getTotal() - this.getPaidTotal(); }, + // sets the type of receipt 'receipt'(default) or 'invoice' + set_receipt_type: function(type){ + this.receipt_type = type; + }, + get_receipt_type: function(){ + return this.receipt_type; + }, // the client related to the current order. set_client: function(client){ this.set('client',client); diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 9a2071c6335..6dcce2f75f9 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -357,6 +357,35 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); + module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({ + template:'ChooseReceiptPopupWidget', + show: function(){ + console.log('show'); + this._super(); + this.renderElement(); + var self = this; + var currentOrder = self.pos.get('selectedOrder'); + + this.$('.button.receipt').off('click').click(function(){ + currentOrder.set_receipt_type('receipt'); + self.pos_widget.screen_selector.set_current_screen('products'); + }); + + this.$('.button.invoice').off('click').click(function(){ + currentOrder.set_receipt_type('invoice'); + self.pos_widget.screen_selector.set_current_screen('products'); + }); + }, + get_client_name: function(){ + var client = this.pos.get('selectedOrder').get_client(); + if( client ){ + return client.name; + }else{ + return ''; + } + }, + }); + module.ErrorPopupWidget = module.PopUpWidget.extend({ template:'ErrorPopupWidget', show: function(){ @@ -492,9 +521,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa var product = this.get_product(); return (product ? product.get('list_price') : 0) || 0; }, - get_product_image: function(){ + get_product_image_url: function() { var product = this.get_product(); - return product ? product.get('image') : undefined; + if(!product){ + return ""; + } + return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+product.get('id'); }, get_product_weight: function(){ return this.weight || 0; @@ -536,12 +568,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa var cashregister = selfCheckoutRegisters[0] || this.pos.get('cashRegisters').models[0]; currentOrder.addPaymentLine(cashregister); - - self.pos.push_order(currentOrder.exportAsJSON()).then(function() { - currentOrder.destroy(); - self.pos.proxy.transaction_end(); - self.pos_widget.screen_selector.set_current_screen(self.next_screen); - }); + self.pos.push_order(currentOrder.exportAsJSON()) + currentOrder.destroy(); + self.pos.proxy.transaction_end(); + self.pos_widget.screen_selector.set_current_screen(self.next_screen); }else if(payment === 'payment_rejected'){ clearInterval(this.intervalID); //TODO show a tryagain thingie ? @@ -571,19 +601,33 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_numpad: false, show_leftpane: false, + barcode_product_action: function(ean){ + self.pos.proxy.transaction_start(); + this._super(ean); + }, barcode_client_action: function(ean){ + self.pos.proxy.transaction_start(); this._super(ean); - this.pos_widget.screen_selector.set_current_screen(this.next_screen); + this.pos_widget.screen_selector.show_popup('choose-receipt'); }, show: function(){ this._super(); var self = this; + + this.add_action_button({ + label: 'help', + icon: '/point_of_sale/static/src/img/icons/png48/help.png', + click: function(){ + $('.goodbye-message').css({opacity:1}).hide(); + }, + }); + $('.goodbye-message').css({opacity:1}).show(); setTimeout(function(){ $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').hide();}); - },3000); + },5000); }, }); diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 6de31c88dfd..79e9d3398bc 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -667,7 +667,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, show: function(){ this.$el.show(); }, hide: function(){ this.$el.hide(); }, - }); @@ -758,6 +757,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa self.screen_selector.set_default_screen(); + window.screen_selector = self.screen_selector; + self.pos.barcode_reader.connect(); instance.webclient.set_content_full_screen(true); @@ -831,6 +832,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.error_session_popup = new module.ErrorNoSessionPopupWidget(this, {}); this.error_session_popup.appendTo($('.point-of-sale')); + this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {}); + this.choose_receipt_popup.appendTo($('.point-of-sale')); + // -------- Misc --------- this.notification = new module.SynchNotificationWidget(this,{}); @@ -890,6 +894,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa 'error': this.error_popup, 'error-product': this.error_product_popup, 'error-session': this.error_session_popup, + 'choose-receipt': this.choose_receipt_popup, }, default_client_screen: 'welcome', default_cashier_screen: 'products', @@ -965,9 +970,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, try_close: function() { var self = this; - self.pos.flush().then(function() { - self.close(); - }); + //TODO : do the close after the flush... + self.pos.flush() + self.close(); }, close: function() { var self = this; diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index ab5f99a149e..a7276e680b4 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -185,8 +185,8 @@ - - + + @@ -313,10 +313,10 @@ - + From b2cb4b841dae962f6752770fb07bb4bd65a5249d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 5 Sep 2012 23:07:51 +0200 Subject: [PATCH 16/40] [FIX] point_of_sale: unrecognized_items error handling bzr revid: fva@openerp.com-20120905210751-7lbzkypzefy6zb3f --- addons/point_of_sale/static/src/js/screens.js | 4 ++-- addons/point_of_sale/static/src/xml/pos.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index df5b611b42d..e10633f999b 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -188,7 +188,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa return true; } } - this.pos.proxy.scan_item_unrecognized(ean); + this.pos.proxy.scan_item_error_unrecognized(ean); return false; }, @@ -206,7 +206,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa return true; } } - this.pos.proxy.scan_item_unrecognized(ean); + this.pos.proxy.scan_item_error_unrecognized(ean); return false; //TODO start the transaction }, diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 424f817c442..a3a75ab05e5 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -346,7 +346,7 @@ From 87f5de0b0772d088c671ffc8523c712b7acd3d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 5 Sep 2012 23:09:27 +0200 Subject: [PATCH 17/40] [WIP] locadis: temporary disabling of the cust barcode schemas bzr revid: fva@openerp.com-20120905210927-8su2f4yd0eyvyzuu --- addons/locadis/static/src/js/hello.js | 12 ------------ addons/locadis/static/src/js/main.js | 5 ++--- 2 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 addons/locadis/static/src/js/hello.js diff --git a/addons/locadis/static/src/js/hello.js b/addons/locadis/static/src/js/hello.js deleted file mode 100644 index 7deacfa994c..00000000000 --- a/addons/locadis/static/src/js/hello.js +++ /dev/null @@ -1,12 +0,0 @@ - -openerp.locadis = function(instance){ - console.log('Hello World!:',instance); - instance.point_of_sale.PosWidget = instance.point_of_sale.PosWidget.extend({ - init:function(parent,options){ - console.log('Tadaaam!',parent,options); - this._super(parent,options); - }, - }); - - /* REMOVE PRODUCTS FROM FIRST LEVEL IN SELF CHECKOUT */ -}; diff --git a/addons/locadis/static/src/js/main.js b/addons/locadis/static/src/js/main.js index e1c78f1b341..db7bd1f5ded 100644 --- a/addons/locadis/static/src/js/main.js +++ b/addons/locadis/static/src/js/main.js @@ -3,12 +3,9 @@ openerp.locadis = function(instance){ var module = instance.point_of_sale; var QWeb = instance.web.qweb; - console.log('PosModel?', module.PosModel); - module.PosModel = module.PosModel.extend({ // all this copy paste just to load the product.dont_vidange field ... :( load_server_data: function(){ - console.log('loading'); var self = this; var loaded = self.fetch('res.users',['name','company_id'],[['id','=',this.session.uid]]) @@ -155,6 +152,7 @@ openerp.locadis = function(instance){ }, }); + /* module.BarcodeReader = module.BarcodeReader.extend({ parse_ean: function(ean){ var parse_result = { @@ -213,4 +211,5 @@ openerp.locadis = function(instance){ return parse_result; }, }); + */ }; From 6acf8ed15d59581228467886c1e026c557346523 Mon Sep 17 00:00:00 2001 From: "Twinkle Christian (OpenERP)" Date: Fri, 7 Sep 2012 17:38:04 +0530 Subject: [PATCH 18/40] [IMP]Remove serverwarning bzr revid: tch@tinyerp.com-20120907060555-554i3iemfwfh6lwt bzr revid: tch@tinyerp.com-20120907120804-e4z6k6iuzukaipz2 --- openerp/addons/base/base_data.xml | 637 +++++++++++++++++- openerp/addons/base/i18n/es_MX.po | 2 +- openerp/addons/base/res/res_users.py | 3 + .../addons/test_impex/tests/test_export.py | 2 +- .../addons/test_impex/tests/test_import.py | 4 +- 5 files changed, 643 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index 37c360b5cd8..6f4af968ec0 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -1110,9 +1110,644 @@
    + + - + + USD + $ + 0.01 + 4 + + + + 1.2834 + + + + + + + VEF + Bs.F + 0.0001 + 4 + + + + 5.864 + + + + + + CAD + $ + 0.01 + 4 + + + + 1.3388 + + + + + + + CHF + CHF + 0.01 + 4 + + + + 1.3086 + + + + + + BRL + R$ + 0.01 + 4 + + + + 2.2344 + + + + + + CNY + ¥ + 0.01 + 4 + + + + 8.7556 + + + + + + + COP + $ + 0.01 + 4 + + + + 2933.8378 + + + + + + CZK + Kč + 0.01 + 4 + + + + 26.5634 + + + + + + DKK + kr + 0.01 + 4 + + + + 7.4445 + + + + + + + HUF + Ft + 0.01 + 4 + + + + 271.5621 + + + + + + IDR + Rp + 0.01 + 4 + + + + 14352.00 + + + + + 11796.39 + + + + + + LVL + Ls + 0.01 + 4 + + + + 0.7086 + + + + + + + NOK + kr + 0.01 + 4 + + + + 7.8668 + + + + + + XPF + XPF + 1.00 + 4 + + + + 119.331742 + + + + + + PAB + B/. + 0.01 + 4 + + + + 1.2676 + + + + + + PLN + zł + 0.01 + 4 + + + + 4.1005 + + + + + + SEK + kr + 0.01 + 4 + + + + 10.3004 + + + + + + GBP + £ + 0.01 + 4 + + + + 0.8333 + + + + + + ARS + $ + 0.01 + 4 + + + + 5.0881 + + + + + + INR + ₹ + 0.01 + 4 + + + + 59.9739 + + + + + + AUD + $ + 0.01 + 4 + + + + 1.4070 + + + + + + UAH + ₴ + 0.01 + 4 + + + + 10.1969 + + + + + + VND + ₫ + 0.01 + 4 + + + + 26330.01 + + + + + + HKD + $ + 0.01 + 4 + + + + 11.1608 + + + + + + JPY + ¥ + 0.01 + 4 + + + + 133.62 + + + + + + BGN + лв + 0.01 + 4 + + + + 1.9558 + + + + + + LTL + Lt + 0.01 + 4 + + + + 3.4528 + + + + + + RON + lei + 0.01 + 4 + + + + 4.2253 + + + + + + HRK + kn + 0.01 + 4 + + + + 7.2936 + + + + + + RUB + руб + 0.01 + 4 + + + + 43.16 + + + + + + TRY + TL + 0.01 + 4 + + + + 2.1411 + + + + + + KRW + ₩ + 0.01 + 4 + + + + 1662.37 + + + + + + MXN + $ + 0.01 + 4 + + + + 18.6664 + + + + + + MYR + RM + 0.01 + 4 + + + + 4.8887 + + + + + + NZD + $ + 0.01 + 4 + + + + 1.9764 + + + + + + PHP + Php + 0.01 + 4 + + + + 66.1 + + + + + + SGD + $ + 0.01 + 4 + + + + 2.0126 + + + + + + THB + ฿ + 0.01 + 4 + + + + 47.779 + + + + + + ZAR + R + 0.01 + 4 + + + + 10.5618 + + + + + + International Bank + + + + CRC + 0.01 + 4 + ¢ + + + + 691.3153 + + + + + + localhost + localhost + + + + + + MUR + Rs + 0.01 + 4 + + + + 40.28 + + + + + + XOF + CFA + 1 + 4 + + + + 655.957 + + + + + + XAF + FCFA + 1 + 4 + + + + 655.957 + + + + + + UGX + USh + 1 + 4 + + + + 3401.91388 + + + + + + HNL + L + 0.01 + 4 + + + + 25 + + + + + + + CLP + $ + 0.01 + 4 + + + + 710 + + + + + + + UYU + $ + 0.01 + 4 + + + + + + 28.36 + + diff --git a/openerp/addons/base/i18n/es_MX.po b/openerp/addons/base/i18n/es_MX.po index cc9d519e705..dbc674d754a 100644 --- a/openerp/addons/base/i18n/es_MX.po +++ b/openerp/addons/base/i18n/es_MX.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-09-08 04:54+0000\n" +"X-Launchpad-Export-Date: 2012-09-07 04:55+0000\n" "X-Generator: Launchpad (build 15914)\n" #. module: base diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index d0f07daa004..8a80a171612 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -157,6 +157,9 @@ class res_users(osv.osv): 'user_email': fields.related('email', type='char', deprecated='Use the email field instead of user_email. This field will be removed with OpenERP 7.1.'), } + _sql_constraints = [ + ('email_uniq', 'UNIQUE (user_email)', 'You can not have two users with the same email!') + ] def on_change_company_id(self, cr, uid, ids, company_id): return {'warning' : { diff --git a/openerp/tests/addons/test_impex/tests/test_export.py b/openerp/tests/addons/test_impex/tests/test_export.py index 7e67ba581a1..26219b36d3b 100644 --- a/openerp/tests/addons/test_impex/tests/test_export.py +++ b/openerp/tests/addons/test_impex/tests/test_export.py @@ -245,7 +245,7 @@ class test_selection(CreatorCase): 'translatable': True, 'date_format': '%d.%m.%Y', 'decimal_point': ',', - 'thousand_sep': ' ', + 'thousands_sep': ' ', }) Translations = self.registry('ir.translation') for source, value in self.translations_fr: diff --git a/openerp/tests/addons/test_impex/tests/test_import.py b/openerp/tests/addons/test_impex/tests/test_import.py index 4712d53feff..a186323c754 100644 --- a/openerp/tests/addons/test_impex/tests/test_import.py +++ b/openerp/tests/addons/test_impex/tests/test_import.py @@ -393,7 +393,7 @@ class test_selection(ImporterCase): 'translatable': True, 'date_format': '%d.%m.%Y', 'decimal_point': ',', - 'thousand_sep': ' ', + 'thousands_sep': ' ', }) Translations = self.registry('ir.translation') for source, value in self.translations_fr: @@ -466,7 +466,7 @@ class test_selection_function(ImporterCase): 'translatable': True, 'date_format': '%d.%m.%Y', 'decimal_point': ',', - 'thousand_sep': ' ', + 'thousands_sep': ' ', }) Translations = self.registry('ir.translation') for source, value in self.translations_fr: From 83fc106eaad1b364aa301d872691e9340b9f60db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 10 Sep 2012 17:48:28 +0200 Subject: [PATCH 19/40] [FIX] point_of_sale: coherent image url handling bzr revid: fva@openerp.com-20120910154828-4k699hi3lu8lgxvu --- addons/point_of_sale/static/src/js/models.js | 6 ++++-- addons/point_of_sale/static/src/js/screens.js | 7 ------- addons/point_of_sale/static/src/js/widgets.js | 5 +---- addons/point_of_sale/static/src/xml/pos.xml | 7 ++++--- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 5928caf7c77..b5792f0124d 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -48,7 +48,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 'units': null, 'units_by_id': null, - 'selectedOrder': undefined, + 'selectedOrder': null, }); this.get('orders').bind('remove', function(){ self.on_removed_order(); }); @@ -310,6 +310,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }); module.Product = Backbone.Model.extend({ + get_image_url: function(){ + return '/web/binary/image?session_id='instance.session.session_id+'&model=product.product&field=image&id='+this.get('id'); + }, }); module.ProductCollection = Backbone.Collection.extend({ @@ -339,7 +342,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal get_discount: function(){ return this.discount; }, - // FIXME get_product_type: function(){ return this.type; }, diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index e10633f999b..2ead7333bed 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -521,13 +521,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa var product = this.get_product(); return (product ? product.get('list_price') : 0) || 0; }, - get_product_image_url: function() { - var product = this.get_product(); - if(!product){ - return ""; - } - return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+product.get('id'); - }, get_product_weight: function(){ return this.weight || 0; }, diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 79e9d3398bc..2aaf9098329 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -284,12 +284,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.click_product_action = options.click_product_action; }, // returns the url of the product thumbnail - get_image_url: function() { - return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+this.model.get('id'); - }, renderElement: function() { this._super(); - this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.get_image_url())); + this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.model.get_image_url())); var self = this; $("a", this.$el).click(function(e){ if(self.click_product_action){ diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index a3a75ab05e5..46dc4712937 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -185,8 +185,8 @@ - - + + @@ -346,7 +346,8 @@ From 4db2bc0c43706bf05525851f7d1c45a31abe92b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 11 Sep 2012 12:18:30 +0200 Subject: [PATCH 20/40] [FIX] point_of_sale: help wasn't always cancelled when popup closed bzr revid: fva@openerp.com-20120911101830-dvxi9sg5oan5r5nc --- addons/point_of_sale/static/src/js/models.js | 2 +- addons/point_of_sale/static/src/js/screens.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index b5792f0124d..49521377ccb 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -311,7 +311,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal module.Product = Backbone.Model.extend({ get_image_url: function(){ - return '/web/binary/image?session_id='instance.session.session_id+'&model=product.product&field=image&id='+this.get('id'); + return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+this.get('id'); }, }); diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 2ead7333bed..3b580868e6b 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -65,6 +65,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, close_popup: function(){ if(this.current_popup){ + this.current_popup.close(); this.current_popup.hide(); this.current_popup = null; } @@ -336,6 +337,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.$el.show(); } }, + /* called before hide, when a popup is closed */ + close: function(){ + }, + /* hides the popup. keep in mind that this is called in the initialization pass of the + * pos instantiation, so you don't want to do anything fancy in here */ hide: function(){ if(this.$el){ this.$el.hide(); @@ -352,9 +358,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.$el.find('.button').off('click').click(function(){ self.pos_widget.screen_selector.close_popup(); - self.pos.proxy.help_canceled(); }); }, + close:function(){ + this.pos.proxy.help_canceled(); + }, }); module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({ From 6bbc6a90689914db16592c4c80d8f450e9a27410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 11 Sep 2012 12:23:52 +0200 Subject: [PATCH 21/40] [FIX] point_of_sale: the locadis module should not have been merged here ... bzr revid: fva@openerp.com-20120911102352-lia0c930ao1t19nw --- addons/locadis/__init__.py | 25 - addons/locadis/__openerp__.py | 52 - addons/locadis/locadis.py | 50 - addons/locadis/locadis_views.xml | 15 - addons/locadis/point_of_sale.py | 1303 ------------------------- addons/locadis/static/src/css/pos.css | 6 - addons/locadis/static/src/js/main.js | 215 ---- addons/locadis/static/src/xml/pos.xml | 25 - 8 files changed, 1691 deletions(-) delete mode 100644 addons/locadis/__init__.py delete mode 100644 addons/locadis/__openerp__.py delete mode 100644 addons/locadis/locadis.py delete mode 100644 addons/locadis/locadis_views.xml delete mode 100644 addons/locadis/point_of_sale.py delete mode 100644 addons/locadis/static/src/css/pos.css delete mode 100644 addons/locadis/static/src/js/main.js delete mode 100644 addons/locadis/static/src/xml/pos.xml diff --git a/addons/locadis/__init__.py b/addons/locadis/__init__.py deleted file mode 100644 index 60ffcac8393..00000000000 --- a/addons/locadis/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## - -import locadis - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/addons/locadis/__openerp__.py b/addons/locadis/__openerp__.py deleted file mode 100644 index 4ec8527fc7a..00000000000 --- a/addons/locadis/__openerp__.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## - - -{ - 'name': 'Locadis Point Of Sale', - 'version': '1.0.1', - 'category': 'Point Of Sale', - "sequence": 6, - "summary": "Point of sale extensions for Locadis", - 'description': """ -FIXME: product description - """, - 'author': 'OpenERP SA', - 'images': [], - 'depends': ['point_of_sale'], - 'data':[ - 'locadis_views.xml', - ], - 'installable': True, - 'application': True, - # Web client - 'js': [ - 'static/src/js/main.js' - ], - 'css': [ - 'static/src/css/pos.css' - ], - 'qweb': [ - 'static/src/xml/pos.xml' - ], - 'auto_install': False, -} -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/locadis/locadis.py b/addons/locadis/locadis.py deleted file mode 100644 index 4d04825cba3..00000000000 --- a/addons/locadis/locadis.py +++ /dev/null @@ -1,50 +0,0 @@ - -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## - -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 product_product(osv.osv): - _inherit = 'product.product' - _columns = { - 'dont_vidange': fields.float('Dont Vidange', help="Le prix de ce produit inclus ce montant de vidange"), - } - _defaults = { - 'dont_vidange': 0.0, - } - diff --git a/addons/locadis/locadis_views.xml b/addons/locadis/locadis_views.xml deleted file mode 100644 index 3b9b9d87195..00000000000 --- a/addons/locadis/locadis_views.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - product.locadis - product.product - - - - - - - - - diff --git a/addons/locadis/point_of_sale.py b/addons/locadis/point_of_sale.py deleted file mode 100644 index f4d32985688..00000000000 --- a/addons/locadis/point_of_sale.py +++ /dev/null @@ -1,1303 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## - -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 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_payment_journal(self, cr, uid, context=None): - res = self.pool.get('account.journal').search(cr, uid, [('type', 'in', ('bank','cash'))], limit=2) - return res or [] - - 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, - 'journal_ids': _default_payment_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_journal_id(self, cr, uid, ids, fieldnames, args, context=None): - result = dict.fromkeys(ids, False) - for record in self.browse(cr, uid, ids, context=context): - for st in record.statement_ids: - if st.journal_id.type == 'cash': - result[record.id] = st.journal_id.id - break - return result - - def _compute_cash_register_id(self, cr, uid, ids, fieldnames, args, context=None): - result = dict.fromkeys(ids, False) - for record in self.browse(cr, uid, ids, context=context): - for st in record.statement_ids: - if st.journal_id.type == 'cash': - result[record.id] = st.id - break - return result - - def _compute_controls(self, cr, uid, ids, fieldnames, args, context=None): - result = {} - - for record in self.browse(cr, uid, ids, context=context): - has_opening_control = False - has_closing_control = False - - for journal in record.config_id.journal_ids: - if journal.opening_control == True: - has_opening_control = True - if journal.closing_control == True: - has_closing_control = True - - if has_opening_control and has_closing_control: - break - - values = { - 'has_opening_control': has_opening_control, - 'has_closing_control': has_closing_control, - } - result[record.id] = values - - 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_journal_id' : fields.function(_compute_cash_journal_id, method=True, - type='many2one', relation='account.journal', - string='Cash Journal', store=True), - 'cash_register_id' : fields.function(_compute_cash_register_id, method=True, - 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), - 'has_opening_control' : fields.function(_compute_controls, string='Has Opening Control', multi='control', type='boolean'), - 'has_closing_control' : fields.function(_compute_controls, string='Has Closing Control', multi='control', type='boolean'), - } - - _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: - cashids = self.pool.get('account.journal').search(cr, uid, [('journal_user','=',True)], context=context) - if not cashids: - cashids = self.pool.get('account.journal').search(cr, uid, [('type','=','cash')], context=context) - self.pool.get('account.journal').write(cr, uid, cashids, {'journal_user': True}) - 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.id <> session.cash_register_id.id: - if 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: - 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) - - 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 != 'paid': - 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), - 'journal': payment['journal_id'] - }, context=context) - - if order['amount_return']: - session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context) - self.add_payment(cr, uid, order_id, { - 'amount': -order['amount_return'], - 'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'), - 'payment_name': _('return'), - 'journal': session.cash_journal_id.id - }, context=context) - order_ids.append(order_id) - wf_service = netsvc.LocalService("workflow") - wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr) - return order_ids - - def unlink(self, cr, uid, ids, context=None): - for rec in self.browse(cr, uid, ids, context=context): - if rec.state not in ('draft','cancel'): - raise osv.except_osv(_('Unable to Delete !'), _('In order to delete a sale, it must be new or cancelled.')) - return super(pos_order, self).unlink(cr, uid, ids, context=context) - - def onchange_partner_id(self, cr, uid, ids, part=False, context=None): - if not part: - return {'value': {}} - pricelist = self.pool.get('res.partner').browse(cr, uid, part, context=context).property_product_pricelist.id - return {'value': {'pricelist_id': pricelist}} - - def _amount_all(self, cr, uid, ids, name, args, context=None): - tax_obj = self.pool.get('account.tax') - cur_obj = self.pool.get('res.currency') - res = {} - for order in self.browse(cr, uid, ids, context=context): - res[order.id] = { - 'amount_paid': 0.0, - 'amount_return':0.0, - 'amount_tax':0.0, - } - val1 = val2 = 0.0 - cur = order.pricelist_id.currency_id - for payment in order.statement_ids: - res[order.id]['amount_paid'] += payment.amount - res[order.id]['amount_return'] += (payment.amount < 0 and payment.amount or 0) - for line in order.lines: - val1 += line.price_subtotal_incl - val2 += line.price_subtotal - res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val1-val2) - res[order.id]['amount_total'] = cur_obj.round(cr, uid, cur, val1) - return res - - def copy(self, cr, uid, id, default=None, context=None): - if not default: - default = {} - d = { - 'state': 'draft', - 'invoice_id': False, - 'account_move': False, - 'picking_id': False, - 'statement_ids': [], - 'nb_print': 0, - 'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order'), - } - d.update(default) - return super(pos_order, self).copy(cr, uid, id, d, context=context) - - _columns = { - 'name': fields.char('Order Ref', size=64, required=True, readonly=True), - 'company_id':fields.many2one('res.company', 'Company', required=True, readonly=True), - 'shop_id': fields.related('session_id', 'config_id', 'shop_id', relation='sale.shop', type='many2one', string='Shop', store=True, readonly=True), - 'date_order': fields.datetime('Order Date', readonly=True, select=True), - '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."), - 'amount_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Point Of Sale'), multi='all'), - 'amount_total': fields.function(_amount_all, string='Total', multi='all'), - 'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits_compute=dp.get_precision('Point Of Sale'), multi='all'), - 'amount_return': fields.function(_amount_all, 'Returned', digits_compute=dp.get_precision('Point Of Sale'), multi='all'), - 'lines': fields.one2many('pos.order.line', 'order_id', 'Order Lines', states={'draft': [('readonly', False)]}, readonly=True), - 'statement_ids': fields.one2many('account.bank.statement.line', 'pos_statement_id', 'Payments', states={'draft': [('readonly', False)]}, readonly=True), - 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True), - 'partner_id': fields.many2one('res.partner', 'Customer', change_default=True, select=1, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]}), - - 'session_id' : fields.many2one('pos.session', 'Session', - #required=True, - select=1, - domain="[('state', '=', 'opened')]", - states={'draft' : [('readonly', False)]}, - readonly=True), - - 'state': fields.selection([('draft', 'New'), - ('cancel', 'Cancelled'), - ('paid', 'Paid'), - ('done', 'Posted'), - ('invoiced', 'Invoiced')], - 'Status', readonly=True), - - 'invoice_id': fields.many2one('account.invoice', 'Invoice'), - 'account_move': fields.many2one('account.move', 'Journal Entry', readonly=True), - 'picking_id': fields.many2one('stock.picking', 'Picking', readonly=True), - 'note': fields.text('Internal Notes'), - 'nb_print': fields.integer('Number of Print', readonly=True), - - 'sale_journal': fields.related('session_id', 'config_id', 'journal_id', relation='account.journal', type='many2one', string='Sale Journal', store=True, readonly=True), - } - - def _default_session(self, cr, uid, context=None): - so = self.pool.get('pos.session') - session_ids = so.search(cr, uid, [('state','=', 'opened'), ('user_id','=',uid)], context=context) - return session_ids and session_ids[0] or False - - def _default_pricelist(self, cr, uid, context=None): - res = self.pool.get('sale.shop').search(cr, uid, [], context=context) - if res: - shop = self.pool.get('sale.shop').browse(cr, uid, res[0], context=context) - return shop.pricelist_id and shop.pricelist_id.id or False - return False - - _defaults = { - 'user_id': lambda self, cr, uid, context: uid, - 'state': 'draft', - 'name': '/', - 'date_order': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), - 'nb_print': 0, - 'session_id': _default_session, - 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, - 'pricelist_id': _default_pricelist, - } - - def create(self, cr, uid, values, context=None): - values['name'] = self.pool.get('ir.sequence').get(cr, uid, 'pos.order') - return super(pos_order, self).create(cr, uid, values, context=context) - - def test_paid(self, cr, uid, ids, context=None): - """A Point of Sale is paid when the sum - @return: True - """ - for order in self.browse(cr, uid, ids, context=context): - if order.lines and not order.amount_total: - return True - if (not order.lines) or (not order.statement_ids) or \ - (abs(order.amount_total-order.amount_paid) > 0.00001): - return False - return True - - def create_picking(self, cr, uid, ids, context=None): - """Create a picking for each order and validate it.""" - picking_obj = self.pool.get('stock.picking') - partner_obj = self.pool.get('res.partner') - move_obj = self.pool.get('stock.move') - - for order in self.browse(cr, uid, ids, context=context): - if not order.state=='draft': - continue - addr = order.partner_id and partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery']) or {} - picking_id = picking_obj.create(cr, uid, { - 'origin': order.name, - 'partner_id': addr.get('delivery',False), - 'type': 'out', - 'company_id': order.company_id.id, - 'move_type': 'direct', - 'note': order.note or "", - 'invoice_state': 'none', - 'auto_picking': True, - }, context=context) - self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context) - location_id = order.shop_id.warehouse_id.lot_stock_id.id - output_id = order.shop_id.warehouse_id.lot_output_id.id - - for line in order.lines: - if line.product_id and line.product_id.type == 'service': - continue - if line.qty < 0: - location_id, output_id = output_id, location_id - - move_obj.create(cr, uid, { - 'name': line.name, - 'product_uom': line.product_id.uom_id.id, - 'product_uos': line.product_id.uom_id.id, - 'picking_id': picking_id, - 'product_id': line.product_id.id, - 'product_uos_qty': abs(line.qty), - 'product_qty': abs(line.qty), - 'tracking_id': False, - 'state': 'draft', - 'location_id': location_id, - 'location_dest_id': output_id, - }, context=context) - if line.qty < 0: - location_id, output_id = output_id, location_id - - wf_service = netsvc.LocalService("workflow") - wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr) - picking_obj.force_assign(cr, uid, [picking_id], context) - return True - - def cancel_order(self, cr, uid, ids, context=None): - """ Changes order state to cancel - @return: True - """ - stock_picking_obj = self.pool.get('stock.picking') - for order in self.browse(cr, uid, ids, context=context): - wf_service.trg_validate(uid, 'stock.picking', order.picking_id.id, 'button_cancel', cr) - if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel': - raise osv.except_osv(_('Error!'), _('Unable to cancel the picking.')) - self.write(cr, uid, ids, {'state': 'cancel'}, context=context) - return True - - def add_payment(self, cr, uid, order_id, data, context=None): - """Create a new payment for the order""" - if not context: - context = {} - statement_obj = self.pool.get('account.bank.statement') - statement_line_obj = self.pool.get('account.bank.statement.line') - prod_obj = self.pool.get('product.product') - property_obj = self.pool.get('ir.property') - curr_c = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id - curr_company = curr_c.id - order = self.browse(cr, uid, order_id, context=context) - args = { - 'amount': data['amount'], - } - if 'payment_date' in data: - args['date'] = data['payment_date'] - args['name'] = order.name - if data.get('payment_name', False): - args['name'] = args['name'] + ': ' + data['payment_name'] - account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context) - args['account_id'] = (order.partner_id and order.partner_id.property_account_receivable \ - and order.partner_id.property_account_receivable.id) or (account_def and account_def.id) or False - args['partner_id'] = order.partner_id and order.partner_id.id or None - - if not args['account_id']: - if not args['partner_id']: - msg = _('There is no receivable account defined to make payment.') - else: - msg = _('There is no receivable account defined to make payment for the partner: "%s" (id:%d).') % (order.partner_id.name, order.partner_id.id,) - raise osv.except_osv(_('Configuration Error!'), msg) - - context.pop('pos_session_id', False) - - try: - journal_id = long(data['journal']) - except Exception: - journal_id = False - - statement_id = False - for statement in order.session_id.statement_ids: - if statement.journal_id.id == journal_id: - statement_id = statement.id - break - - if not statement_id: - raise osv.except_osv(_('Error!'), _('You have to open at least one cashbox.')) - - args.update({ - 'statement_id' : statement_id, - 'pos_statement_id' : order_id, - 'journal_id' : journal_id, - 'type' : 'customer', - 'ref' : order.name, - }) - - statement_line_obj.create(cr, uid, args, context=context) - - wf_service = netsvc.LocalService("workflow") - wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr) - wf_service.trg_write(uid, 'pos.order', order_id, cr) - - return statement_id - - def refund(self, cr, uid, ids, context=None): - """Create a copy of order for refund order""" - clone_list = [] - line_obj = self.pool.get('pos.order.line') - for order in self.browse(cr, uid, ids, context=context): - clone_id = self.copy(cr, uid, order.id, { - 'name': order.name + ' REFUND', - }, context=context) - clone_list.append(clone_id) - - for clone in self.browse(cr, uid, clone_list, context=context): - for order_line in clone.lines: - line_obj.write(cr, uid, [order_line.id], { - 'qty': -order_line.qty - }, context=context) - - new_order = ','.join(map(str,clone_list)) - abs = { - #'domain': "[('id', 'in', ["+new_order+"])]", - 'name': _('Return Products'), - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'pos.order', - 'res_id':clone_list[0], - 'view_id': False, - 'context':context, - 'type': 'ir.actions.act_window', - 'nodestroy': True, - 'target': 'current', - } - return abs - - def action_invoice_state(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state':'invoiced'}, context=context) - - def action_invoice(self, cr, uid, ids, context=None): - wf_service = netsvc.LocalService("workflow") - inv_ref = self.pool.get('account.invoice') - inv_line_ref = self.pool.get('account.invoice.line') - product_obj = self.pool.get('product.product') - inv_ids = [] - - for order in self.pool.get('pos.order').browse(cr, uid, ids, context=context): - if order.invoice_id: - inv_ids.append(order.invoice_id.id) - continue - - if not order.partner_id: - raise osv.except_osv(_('Error!'), _('Please provide a partner for the sale.')) - - acc = order.partner_id.property_account_receivable.id - inv = { - 'name': order.name, - 'origin': order.name, - 'account_id': acc, - 'journal_id': order.sale_journal.id or None, - 'type': 'out_invoice', - 'reference': order.name, - 'partner_id': order.partner_id.id, - 'comment': order.note or '', - 'currency_id': order.pricelist_id.currency_id.id, # considering partner's sale pricelist's currency - } - inv.update(inv_ref.onchange_partner_id(cr, uid, [], 'out_invoice', order.partner_id.id)['value']) - if not inv.get('account_id', None): - inv['account_id'] = acc - inv_id = inv_ref.create(cr, uid, inv, context=context) - - self.write(cr, uid, [order.id], {'invoice_id': inv_id, 'state': 'invoiced'}, context=context) - inv_ids.append(inv_id) - for line in order.lines: - inv_line = { - 'invoice_id': inv_id, - 'product_id': line.product_id.id, - 'quantity': line.qty, - } - inv_name = product_obj.name_get(cr, uid, [line.product_id.id], context=context)[0][1] - inv_line.update(inv_line_ref.product_id_change(cr, uid, [], - line.product_id.id, - line.product_id.uom_id.id, - line.qty, partner_id = order.partner_id.id, - fposition_id=order.partner_id.property_account_position.id)['value']) - if line.product_id.description_sale: - inv_line['note'] = line.product_id.description_sale - inv_line['price_unit'] = line.price_unit - inv_line['discount'] = line.discount - inv_line['name'] = inv_name - inv_line['invoice_line_tax_id'] = ('invoice_line_tax_id' in inv_line)\ - and [(6, 0, inv_line['invoice_line_tax_id'])] or [] - inv_line_ref.create(cr, uid, inv_line, context=context) - inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context) - wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr) - - if not inv_ids: return {} - - mod_obj = self.pool.get('ir.model.data') - res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form') - res_id = res and res[1] or False - return { - 'name': _('Customer Invoice'), - 'view_type': 'form', - 'view_mode': 'form', - 'view_id': [res_id], - 'res_model': 'account.invoice', - 'context': "{'type':'out_invoice'}", - 'type': 'ir.actions.act_window', - 'nodestroy': True, - 'target': 'current', - 'res_id': inv_ids and inv_ids[0] or False, - } - - def create_account_move(self, cr, uid, ids, context=None): - return self._create_account_move_line(cr, uid, ids, None, None, context=context) - - def _create_account_move_line(self, cr, uid, ids, session=None, move_id=None, context=None): - # Tricky, via the workflow, we only have one id in the ids variable - """Create a account move line of order grouped by products or not.""" - account_move_obj = self.pool.get('account.move') - account_move_line_obj = self.pool.get('account.move.line') - account_period_obj = self.pool.get('account.period') - account_tax_obj = self.pool.get('account.tax') - user_proxy = self.pool.get('res.users') - property_obj = self.pool.get('ir.property') - - period = account_period_obj.find(cr, uid, context=context)[0] - - #session_ids = set(order.session_id for order in self.browse(cr, uid, ids, context=context)) - - if session and not all(session.id == order.session_id.id for order in self.browse(cr, uid, ids, context=context)): - raise osv.except_osv(_('Error!'), _('Selected orders do not have the same session!')) - - current_company = user_proxy.browse(cr, uid, uid, context=context).company_id - - grouped_data = {} - have_to_group_by = session and session.config_id.group_by or False - - def compute_tax(amount, tax, line): - if amount > 0: - tax_code_id = tax['base_code_id'] - tax_amount = line.price_subtotal * tax['base_sign'] - else: - tax_code_id = tax['ref_base_code_id'] - tax_amount = line.price_subtotal * tax['ref_base_sign'] - - return (tax_code_id, tax_amount,) - - for order in self.browse(cr, uid, ids, context=context): - if order.account_move: - continue - if order.state != 'paid': - continue - - user_company = user_proxy.browse(cr, order.user_id.id, order.user_id.id).company_id - - group_tax = {} - account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context).id - - order_account = order.partner_id and \ - order.partner_id.property_account_receivable and \ - order.partner_id.property_account_receivable.id or account_def or current_company.account_receivable.id - - if move_id is None: - # Create an entry for the sale - move_id = account_move_obj.create(cr, uid, { - 'ref' : order.name, - 'journal_id': order.sale_journal.id, - }, context=context) - - def insert_data(data_type, values): - # if have_to_group_by: - - sale_journal_id = order.sale_journal.id - - # 'quantity': line.qty, - # 'product_id': line.product_id.id, - values.update({ - 'date': order.date_order[:10], - 'ref': order.name, - 'journal_id' : sale_journal_id, - 'period_id' : period, - 'move_id' : move_id, - 'company_id': user_company and user_company.id or False, - }) - - if data_type == 'product': - key = ('product', values['product_id'],) - elif data_type == 'tax': - key = ('tax', values['tax_code_id'],) - elif data_type == 'counter_part': - key = ('counter_part', values['partner_id'], values['account_id']) - else: - return - - grouped_data.setdefault(key, []) - - # if not have_to_group_by or (not grouped_data[key]): - # grouped_data[key].append(values) - # else: - # pass - - if have_to_group_by: - if not grouped_data[key]: - grouped_data[key].append(values) - else: - current_value = grouped_data[key][0] - current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0) - current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0) - current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0) - current_value['tax_amount'] = current_value.get('tax_amount', 0.0) + values.get('tax_amount', 0.0) - else: - grouped_data[key].append(values) - - # Create an move for each order line - - for line in order.lines: - tax_amount = 0 - taxes = [t for t in line.product_id.taxes_id] - computed_taxes = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty)['taxes'] - - for tax in computed_taxes: - tax_amount += round(tax['amount'], 2) - group_key = (tax['tax_code_id'], tax['base_code_id'], tax['account_collected_id'], tax['id']) - - group_tax.setdefault(group_key, 0) - group_tax[group_key] += round(tax['amount'], 2) - - amount = line.price_subtotal - - # Search for the income account - if line.product_id.property_account_income.id: - income_account = line.product_id.property_account_income.id - elif line.product_id.categ_id.property_account_income_categ.id: - income_account = line.product_id.categ_id.property_account_income_categ.id - else: - raise osv.except_osv(_('Error!'), _('Please define income '\ - 'account for this product: "%s" (id:%d).') \ - % (line.product_id.name, line.product_id.id, )) - - # Empty the tax list as long as there is no tax code: - tax_code_id = False - tax_amount = 0 - while computed_taxes: - tax = computed_taxes.pop(0) - tax_code_id, tax_amount = compute_tax(amount, tax, line) - - # If there is one we stop - if tax_code_id: - break - - # Create a move for the line - insert_data('product', { - 'name': line.product_id.name, - 'quantity': line.qty, - 'product_id': line.product_id.id, - 'account_id': income_account, - 'credit': ((amount>0) and amount) or 0.0, - 'debit': ((amount<0) and -amount) or 0.0, - 'tax_code_id': tax_code_id, - 'tax_amount': tax_amount, - 'partner_id': order.partner_id and order.partner_id.id or False - }) - - # For each remaining tax with a code, whe create a move line - for tax in computed_taxes: - tax_code_id, tax_amount = compute_tax(amount, tax, line) - if not tax_code_id: - continue - - insert_data('tax', { - 'name': _('Tax'), - 'product_id':line.product_id.id, - 'quantity': line.qty, - 'account_id': income_account, - 'credit': 0.0, - 'debit': 0.0, - 'tax_code_id': tax_code_id, - 'tax_amount': tax_amount, - }) - - # Create a move for each tax group - (tax_code_pos, base_code_pos, account_pos, tax_id)= (0, 1, 2, 3) - - for key, tax_amount in group_tax.items(): - tax = self.pool.get('account.tax').browse(cr, uid, key[tax_id], context=context) - insert_data('tax', { - 'name': _('Tax') + ' ' + tax.name, - 'quantity': line.qty, - 'product_id': line.product_id.id, - 'account_id': key[account_pos], - 'credit': ((tax_amount>0) and tax_amount) or 0.0, - 'debit': ((tax_amount<0) and -tax_amount) or 0.0, - 'tax_code_id': key[tax_code_pos], - 'tax_amount': tax_amount, - }) - - # counterpart - insert_data('counter_part', { - 'name': _("Trade Receivables"), #order.name, - 'account_id': order_account, - 'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0, - 'debit': ((order.amount_total > 0) and order.amount_total) or 0.0, - 'partner_id': order.partner_id and order.partner_id.id or False - }) - - order.write({'state':'done', 'account_move': move_id}) - - for group_key, group_data in grouped_data.iteritems(): - for value in group_data: - account_move_line_obj.create(cr, uid, value, context=context) - - return True - - def action_payment(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state': 'payment'}, context=context) - - def action_paid(self, cr, uid, ids, context=None): - self.create_picking(cr, uid, ids, context=context) - self.write(cr, uid, ids, {'state': 'paid'}, context=context) - return True - - def action_cancel(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state': 'cancel'}, context=context) - return True - - def action_done(self, cr, uid, ids, context=None): - self.create_account_move(cr, uid, ids, context=context) - return True - -class account_bank_statement(osv.osv): - _inherit = 'account.bank.statement' - _columns= { - 'user_id': fields.many2one('res.users', 'User', readonly=True), - } - _defaults = { - 'user_id': lambda self,cr,uid,c={}: uid - } -account_bank_statement() - -class account_bank_statement_line(osv.osv): - _inherit = 'account.bank.statement.line' - _columns= { - 'pos_statement_id': fields.many2one('pos.order', ondelete='cascade'), - } -account_bank_statement_line() - -class pos_order_line(osv.osv): - _name = "pos.order.line" - _description = "Lines of Point of Sale" - _rec_name = "product_id" - - def _amount_line_all(self, cr, uid, ids, field_names, arg, context=None): - res = dict([(i, {}) for i in ids]) - account_tax_obj = self.pool.get('account.tax') - cur_obj = self.pool.get('res.currency') - for line in self.browse(cr, uid, ids, context=context): - taxes = line.product_id.taxes_id - price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) - taxes = account_tax_obj.compute_all(cr, uid, line.product_id.taxes_id, price, line.qty, product=line.product_id, partner=line.order_id.partner_id or False) - - cur = line.order_id.pricelist_id.currency_id - res[line.id]['price_subtotal'] = cur_obj.round(cr, uid, cur, taxes['total']) - res[line.id]['price_subtotal_incl'] = cur_obj.round(cr, uid, cur, taxes['total_included']) - return res - - def onchange_product_id(self, cr, uid, ids, pricelist, product_id, qty=0, partner_id=False, context=None): - context = context or {} - if not product_id: - return {} - if not pricelist: - raise osv.except_osv(_('No Pricelist !'), - _('You have to select a pricelist in the sale form !\n' \ - 'Please set one before choosing a product.')) - - price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist], - product_id, qty or 1.0, partner_id)[pricelist] - - result = self.onchange_qty(cr, uid, ids, product_id, 0.0, qty, price, context=context) - result['value']['price_unit'] = price - return result - - def onchange_qty(self, cr, uid, ids, product, discount, qty, price_unit, context=None): - result = {} - if not product: - return result - account_tax_obj = self.pool.get('account.tax') - cur_obj = self.pool.get('res.currency') - - prod = self.pool.get('product.product').browse(cr, uid, product, context=context) - - taxes = prod.taxes_id - price = price_unit * (1 - (discount or 0.0) / 100.0) - taxes = account_tax_obj.compute_all(cr, uid, prod.taxes_id, price, qty, product=prod, partner=False) - - result['price_subtotal'] = taxes['total'] - result['price_subtotal_incl'] = taxes['total_included'] - return {'value': result} - - _columns = { - 'company_id': fields.many2one('res.company', 'Company', required=True), - 'name': fields.char('Line No', size=32, required=True), - 'notice': fields.char('Discount Notice', size=128), - 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, change_default=True), - 'price_unit': fields.float(string='Unit Price', digits=(16, 2)), - 'qty': fields.float('Quantity', digits=(16, 2)), - 'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal w/o Tax', store=True), - 'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal', store=True), - 'discount': fields.float('Discount (%)', digits=(16, 2)), - 'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'), - 'create_date': fields.datetime('Creation Date', readonly=True), - } - - _defaults = { - 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'pos.order.line'), - 'qty': lambda *a: 1, - 'discount': lambda *a: 0.0, - 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, - } - - def copy_data(self, cr, uid, id, default=None, context=None): - if not default: - default = {} - default.update({ - 'name': self.pool.get('ir.sequence').get(cr, uid, 'pos.order.line') - }) - return super(pos_order_line, self).copy_data(cr, uid, id, default, context=context) - -class pos_category(osv.osv): - _name = 'pos.category' - _description = "Point of Sale Category" - _order = "sequence, name" - def _check_recursion(self, cr, uid, ids, context=None): - level = 100 - while len(ids): - cr.execute('select distinct parent_id from pos_category where id IN %s',(tuple(ids),)) - ids = filter(None, map(lambda x:x[0], cr.fetchall())) - if not level: - return False - level -= 1 - return True - - _constraints = [ - (_check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id']) - ] - - def name_get(self, cr, uid, ids, context=None): - if not len(ids): - return [] - reads = self.read(cr, uid, ids, ['name','parent_id'], context=context) - res = [] - for record in reads: - name = record['name'] - if record['parent_id']: - name = record['parent_id'][1]+' / '+name - res.append((record['id'], name)) - return res - - def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None): - res = self.name_get(cr, uid, ids, context=context) - return dict(res) - - def _get_image(self, cr, uid, ids, name, args, context=None): - result = dict.fromkeys(ids, False) - for obj in self.browse(cr, uid, ids, context=context): - result[obj.id] = tools.image_get_resized_images(obj.image) - return result - - def _set_image(self, cr, uid, id, name, value, args, context=None): - return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) - - _columns = { - 'name': fields.char('Name', size=64, required=True, translate=True), - 'complete_name': fields.function(_name_get_fnc, type="char", string='Name'), - 'parent_id': fields.many2one('pos.category','Parent Category', select=True), - 'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'), - 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."), - - # NOTE: there is no 'default image', because by default we don't show thumbnails for categories. However if we have a thumbnail - # for at least one category, then we display a default image on the other, so that the buttons have consistent styling. - # In this case, the default image is set by the js code. - - 'image': fields.binary("Image", - help="This field holds the image used for the category. "\ - "The image is base64 encoded, and PIL-supported. "\ - "It is limited to a 1024x1024 px image."), - 'image_medium': fields.function(_get_image, fnct_inv=_set_image, - string="Medium-sized image", type="binary", multi="_get_image", - store = { - 'pos.category': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), - }, - help="Medium-sized image of the category. It is automatically "\ - "resized as a 180x180 px image, with aspect ratio preserved. "\ - "Use this field in form views or some kanban views."), - 'image_small': fields.function(_get_image, fnct_inv=_set_image, - string="Smal-sized image", type="binary", multi="_get_image", - store = { - 'pos.category': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10), - }, - help="Small-sized image of the category. It is automatically "\ - "resized as a 50x50 px image, with aspect ratio preserved. "\ - "Use this field anywhere a small image is required."), - } - -import io, StringIO - -class ean_wizard(osv.osv_memory): - _name = 'pos.ean_wizard' - _columns = { - 'ean13_pattern': fields.char('Ean13 Pattern', size=32, required=True, translate=True), - } - def sanitize_ean13(self, cr, uid, ids, context): - for r in self.browse(cr,uid,ids): - ean13 = openerp.addons.product.product.sanitize_ean13(r.ean13_pattern) - m = context.get('active_model') - m_id = context.get('active_id') - self.pool.get(m).write(cr,uid,[m_id],{'ean13':ean13}) - return { 'type' : 'ir.actions.act_window_close' } - -class product_product(osv.osv): - _inherit = 'product.product' - - - #def _get_small_image(self, cr, uid, ids, prop, unknow_none, context=None): - # result = {} - # for obj in self.browse(cr, uid, ids, context=context): - # if not obj.product_image: - # result[obj.id] = False - # continue - - # image_stream = io.BytesIO(obj.product_image.decode('base64')) - # img = Image.open(image_stream) - # img.thumbnail((120, 100), Image.ANTIALIAS) - # img_stream = StringIO.StringIO() - # img.save(img_stream, "JPEG") - # result[obj.id] = img_stream.getvalue().encode('base64') - # return result - - _columns = { - 'income_pdt': fields.boolean('Point of Sale Cash In', help="This is a product you can use to put cash into a statement for the point of sale backend."), - 'expense_pdt': fields.boolean('Point of Sale Cash Out', help="This is a product you can use to take cash from a statement for the point of sale backend, exemple: money lost, transfer to bank, etc."), - 'pos_categ_id': fields.many2one('pos.category','Point of Sale Category', - help="If you want to sell this product through the point of sale, select the category it belongs to."), - 'to_weight' : fields.boolean('To Weight', help="This category contains products that should be weighted, mainly used for the self-checkout interface"), - } - _defaults = { - 'to_weight' : False, - } - - def edit_ean(self, cr, uid, ids, context): - return { - 'name': "Edit Ean", - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'pos.ean_wizard', - 'target' : 'new', - 'view_id': False, - 'context':context, - } - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/locadis/static/src/css/pos.css b/addons/locadis/static/src/css/pos.css deleted file mode 100644 index 075b4dccc27..00000000000 --- a/addons/locadis/static/src/css/pos.css +++ /dev/null @@ -1,6 +0,0 @@ -.point-of-sale .screen p{ - font-size: 24px; -} -.product-list-empty{ - text-align:center; -} diff --git a/addons/locadis/static/src/js/main.js b/addons/locadis/static/src/js/main.js deleted file mode 100644 index db7bd1f5ded..00000000000 --- a/addons/locadis/static/src/js/main.js +++ /dev/null @@ -1,215 +0,0 @@ -openerp.locadis = function(instance){ - - var module = instance.point_of_sale; - var QWeb = instance.web.qweb; - - module.PosModel = module.PosModel.extend({ - // all this copy paste just to load the product.dont_vidange field ... :( - load_server_data: function(){ - var self = this; - - var loaded = self.fetch('res.users',['name','company_id'],[['id','=',this.session.uid]]) - .pipe(function(users){ - self.set('user',users[0]); - - return self.fetch('res.company', - [ - 'currency_id', - 'email', - 'website', - 'company_registry', - 'vat', - 'name', - 'phone', - 'partner_id', - ], - [['id','=',users[0].company_id[0]]]); - }).pipe(function(companies){ - self.set('company',companies[0]); - - return self.fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]); - }).pipe(function(company_partners){ - self.get('company').contact_address = company_partners[0].contact_address; - - return self.fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]); - }).pipe(function(currencies){ - self.set('currency',currencies[0]); - - return self.fetch('product.uom', null, null); - }).pipe(function(units){ - self.set('units',units); - var units_by_id = {}; - for(var i = 0, len = units.length; i < len; i++){ - units_by_id[units[i].id] = units[i]; - } - self.set('units_by_id',units_by_id); - - return self.fetch('product.packaging', null, null); - }).pipe(function(packagings){ - self.set('product.packaging',packagings); - - return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]); - }).pipe(function(users){ - self.set('user_list',users); - - return self.fetch('account.tax', ['amount', 'price_include', 'type']); - }).pipe(function(taxes){ - self.set('taxes', taxes); - - return self.fetch( - 'pos.session', - ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'], - [['state', '=', 'opened'], ['user_id', '=', self.session.uid]] - ); - }).pipe(function(sessions){ - self.set('pos_session', sessions[0]); - - return self.fetch( - 'pos.config', - ['name','journal_ids','shop_id','journal_id', - 'iface_self_checkout', 'iface_led', 'iface_cashdrawer', - 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard', - 'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'], - [['id','=', self.get('pos_session').config_id[0]]] - ); - }).pipe(function(configs){ - var pos_config = configs[0]; - self.set('pos_config', pos_config); - self.iface_electronic_scale = !!pos_config.iface_electronic_scale; - self.iface_print_via_proxy = !!pos_config.iface_print_via_proxy; - self.iface_vkeyboard = !!pos_config.iface_vkeyboard; - self.iface_self_checkout = !!pos_config.iface_self_checkout; - self.iface_cashdrawer = !!pos_config.iface_cashdrawer; - - return self.fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]); - }).pipe(function(shops){ - self.set('shop',shops[0]); - - return self.fetch('pos.category', ['id','name','parent_id','child_id','image']) - }).pipe(function(categories){ - self.db.add_categories(categories); - - return self.fetch( - 'product.product', - ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', - 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', - 'description_sale', 'description','dont_vidange'], - [['pos_categ_id','!=', false],['sale_ok','=',true]], - {pricelist: self.get('shop').pricelist_id[0]} // context for price - ); - }).pipe(function(products){ - self.db.add_products(products); - - return self.fetch( - 'account.bank.statement', - ['account_id','currency','journal_id','state','name','user_id','pos_session_id'], - [['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]] - ); - }).pipe(function(bank_statements){ - self.set('bank_statements', bank_statements); - - return self.fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]); - }).pipe(function(journals){ - self.set('journals',journals); - - // associate the bank statements with their journals. - var bank_statements = self.get('bank_statements'); - for(var i = 0, ilen = bank_statements.length; i < ilen; i++){ - for(var j = 0, jlen = journals.length; j < jlen; j++){ - if(bank_statements[i].journal_id[0] === journals[j].id){ - bank_statements[i].journal = journals[j]; - bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method; - } - } - } - self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))}); - }); - - - return loaded; - }, - }); - - module.Orderline = module.Orderline.extend({ - export_for_printing: function(){ - var json = this._super(); - json.dont_vidange = this.get_product().get('dont_vidange'); - }, - }); - - module.ProductListWidget = module.ProductListWidget.extend({ - template_empty: 'ProductEmptyListWidget', - get_category: function(){ - return this.getParent().product_categories_widget.category; - }, - renderElement: function(){ - var ss = this.pos_widget.screen_selector; - if(this.get_category().name === 'Root' && ss && ss.get_user_mode() !== 'cashier'){ - this.replaceElement(_.str.trim(QWeb.render(this.template_empty,{widget:this}))); - }else{ - this._super(); - } - }, - }); - - /* - module.BarcodeReader = module.BarcodeReader.extend({ - parse_ean: function(ean){ - var parse_result = { - type:'unknown', // - prefix:'', - ean:ean, - base_ean: ean, - id:'', - value: 0, - unit: 'none', - }; - - function match_prefix(prefix_list, type){ - for(var i = 0; i < prefix_list.length; i++){ - var prefix = prefix_list[i]; - if(ean.substring(0,prefix.length) === prefix){ - parse_result.prefix = prefix; - parse_result.type = type; - return true; - } - } - return false; - } - if(!this.check_ean(ean)){ - parse_result.type = 'error'; - }else if(match_prefix( ['275'], 'cashier')){ - parse_result.id = ean.substring(0,7); - }else if(match_prefix( ['278'], 'client')){ - parse_result.id = ean.substring(0,7); - }else if(match_prefix( ['24'], 'weight')){ - parse_result.id = ean.substring(0,8); - parse_result.value = Number(ean.substring(8,12))/100.0; - parse_result.base_ean = this.sanitize_ean(ean.substring(0,7)); - parse_result.unit = 'Kg'; - }else if(match_prefix( ['291','292','295'],'price')){ - parse_result.id = ean.substring(0,7); - parse_result.value = Number(ean.substring(7,12))/100.0; - parse_result.base_ean = this.sanitize_ean(ean.substring(0,7)); - parse_result.unit = 'euro'; - }else if(match_prefix( ['295'],'price')){ - parse_result.id = ean.substring(0,8); - parse_result.value = Number(ean.substring(8,12))/100.0; - parse_result.base_ean = this.sanitize_ean(ean.substring(0,8)); - parse_result.unit = 'euro'; - }else if(match_prefix(['980'], 'price')){ - // bons de vidanges, prix negatif. - parse_result.id = ean.substring(0,8); - parse_result.value = - Number(ean.substring(8,12))/100.0; - parse_result.base_ean = this.sanitize_ean(ean.substring(0,8)); - parse_result.unit = 'euro'; - }else{ - parse_result.type = 'unit'; - parse_result.prefix = ''; - parse_result.id = ean; - } - return parse_result; - }, - }); - */ -}; diff --git a/addons/locadis/static/src/xml/pos.xml b/addons/locadis/static/src/xml/pos.xml deleted file mode 100644 index ad983b96721..00000000000 --- a/addons/locadis/static/src/xml/pos.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - -
    -
    - -

    Please scan your products

    -
    -
    -
    -
    - - - - - -
  • - Vidange: -
  • -
    -
    -
    - -
    From 56197a71b116690bf3f5543e2b8fc9f3b832109b Mon Sep 17 00:00:00 2001 From: "Turkesh Patel (Open ERP)" Date: Wed, 12 Sep 2012 10:52:27 +0530 Subject: [PATCH 22/40] [IMP] remove rml_footer1 and rml_footer2 from demo data. bzr revid: tpa@tinyerp.com-20120912052227-2kfaifr0hilas2lk --- openerp/addons/base/base_data.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index 2fa9910758d..4c429fe56ff 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -62,8 +62,6 @@ Your Company Your Company Slogan - Web: http://www.yourcompany.com - Tel: +33 1 49 51 23 94 - Bank Accounts... to be configured in menu Settings > Accounting/Invoicing. From a5bed8994c913064e2d501de4667536a24c6cb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 12 Sep 2012 18:12:29 +0200 Subject: [PATCH 23/40] [IMP] point_of_sale: debug window bzr revid: fva@openerp.com-20120912161229-4lgcgt4xt2cdzceo --- addons/point_of_sale/static/src/css/pos.css | 75 +++++++++++++ addons/point_of_sale/static/src/js/devices.js | 99 +++++++++-------- addons/point_of_sale/static/src/js/models.js | 2 + addons/point_of_sale/static/src/js/screens.js | 13 +-- addons/point_of_sale/static/src/js/widgets.js | 102 +++++++++++++++--- addons/point_of_sale/static/src/xml/pos.xml | 49 +++++++++ 6 files changed, 270 insertions(+), 70 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index dbe79703329..a727986bb1c 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -1102,6 +1102,81 @@ .point-of-sale .pos-actionbar .button.rightalign{ float:right; } +/* ********* The Debug Widget ********* */ + +.point-of-sale .debug-widget{ + z-index:100000; + position: absolute; + right: 10px; + top: 10px; + width: 200px; + font-size: 10px; + + background: rgba(0,0,0,0.82); + color: white; + text-shadow: none; + padding-bottom: 10px; + box-shadow: 0px 3px 20px rgba(0,0,0,0.3); + cursor:move; +} +.point-of-sale .debug-widget .toggle{ + position: absolute; + font-size: 16px; + cursor:pointer; + top:0px; + right:0px; + padding:10px; + padding-right:15px; +} +.point-of-sale .debug-widget .content{ + overflow: hidden; +} +.point-of-sale .debug-widget h1{ + background:black; + padding-top: 10px; + padding-left: 10px; + margin-top:0; + margin-bottom:0; +} +.point-of-sale .debug-widget .category{ + background: black; + padding-left: 10px; + margin: 0px; + font-weight: bold; + padding-top:3px; + padding-bottom:3px; +} +.point-of-sale .debug-widget .button{ + padding: 5px; + padding-left: 15px; + display: block; + cursor:pointer; +} +.point-of-sale .debug-widget .button:hover{ + background: rgba(96,21,177,0.45); +} +.point-of-sale .debug-widget input{ + margin-left:10px; + margin-top:7px; +} +.point-of-sale .debug-widget .status{ + padding: 5px; + padding-left: 15px; + display: block; + cursor:default; +} +.point-of-sale .debug-widget .status.on{ + padding: 5px; + padding-left: 15px; + display: block; + background-color: green; +} +.point-of-sale .debug-widget .status.on{ + padding: 5px; + padding-left: 15px; + display: block; + background-color: #6cd11d; +} /* ********* The PopupWidgets ********* */ diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 46d11938128..65a28b5366a 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -1,26 +1,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sale - var debug_devices = new (instance.web.Class.extend({ - active: false, - payment_status: 'waiting_for_payment', - weight: 0, - activate: function(){ - this.active = true; - }, - deactivate: function(){ - this.active = false; - }, - set_weight: function(weight){ this.activate(); this.weight = weight; }, - accept_payment: function(){ this.activate(); this.payment_status = 'payment_accepted'; }, - reject_payment: function(){ this.activate(); this.payment_status = 'payment_rejected'; }, - delay_payment: function(){ this.activate(); this.payment_status = 'waiting_for_payment'; }, - }))(); - - if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){ - window.debug_devices = debug_devices; - } - // this object interfaces with the local proxy to communicate to the various hardware devices // connected to the Point of Sale. As the communication only goes from the POS to the proxy, // methods are used both to signal an event, and to fetch information. @@ -38,20 +18,34 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.connection = new instance.web.JsonRPC(); this.connection.setup(url); + + this.bypass_proxy = false; + this.notifications = {}; }, message : function(name,params,success_callback, error_callback){ success_callback = success_callback || function(){}; error_callback = error_callback || function(){}; - if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){ console.log('PROXY:',name,params); } - if(!(debug_devices && debug_devices.active)){ - this.connection.rpc('/pos/'+name, params || {}, success_callback, error_callback); + var callbacks = this.notifications[name] || []; + for(var i = 0; i < callbacks.length; i++){ + callbacks[i](params); } + + this.connection.rpc('/pos/'+name, params || {}, success_callback, error_callback); + }, + + // this allows the client to be notified when a proxy call is made. The notification + // callback will be executed with the same arguments as the proxy call + add_notification: function(name, callback){ + if(!this.notifications[name]){ + this.notifications[name] = []; + } + this.notifications[name].push(callback); }, //a product has been scanned and recognized with success @@ -78,12 +72,12 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal //the client is starting to weight weighting_start: function(){ - this.weight = 0; - if(debug_devices){ - debug_devices.weigth = 0; + if(!this.weighting){ + this.weight = 0; + this.weighting = true; + this.bypass_proxy = false; + this.message('weighting_start'); } - this.weighting = true; - this.message('weighting_start'); }, //returns the weight on the scale. @@ -91,22 +85,29 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // and a weighting_end() weighting_read_kg: function(){ var self = this; - if(debug_devices && debug_devices.active){ - return debug_devices.weight; + if(this.bypass_proxy){ + return this.weight; }else{ this.message('weighting_read_kg',{},function(weight){ - if(self.weighting){ + if(self.weighting && !self.bypass_proxy){ self.weight = weight; } }); - return self.weight; + return this.weight; } }, + // sets a custom weight, ignoring the proxy returned value until the next weighting_end + debug_set_weight: function(kg){ + this.bypass_proxy = true; + this.weight = kg; + }, + // the client has finished weighting products weighting_end: function(){ this.weight = 0; this.weighting = false; + this.bypass_proxy = false; this.message('weighting_end'); }, @@ -116,9 +117,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal payment_request: function(price, method, info){ this.paying = true; this.payment_status = 'waiting_for_payment'; - if(debug_devices){ - debug_devices.payment_status = 'waiting_for_payment'; - } this.message('payment_request',{'price':price,'method':method,'info':info}); }, @@ -127,18 +125,30 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // returns 'waiting_for_payment' | 'payment_accepted' | 'payment_rejected' is_payment_accepted: function(){ var self = this; - if(debug_devices.active){ - return debug_devices.payment_status; + if(this.bypass_proxy){ + this.bypass_proxy = false; + return this.payment_status; }else{ this.message('is_payment_accepted', {}, function(payment_status){ if(self.paying){ self.payment_status = payment_status; } }); - return self.payment_status; + return this.payment_status; } }, + + // override what the proxy says and accept the payment + debug_accept_payment: function(){ + this.bypass_proxy = true; + this.payment_status = 'payment_accepted'; + }, + // override what the proxy says and reject the payment + debug_reject_payment: function(){ + this.bypass_proxy = true; + this.payment_status = 'payment_rejected'; + }, // the client cancels his payment payment_canceled: function(){ this.paying = false; @@ -241,18 +251,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.price_prefix_set = attributes.price_prefix_set || {'23':''}; this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''}; this.client_prefix_set = attributes.client_prefix_set || {'042':''}; - - if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){ - var self = this; - window.simulate_ean = function(ean,strict){ - ean = ean.toString(); - if(!strict){ - ean = self.sanitize_ean(ean); - } - console.log('SIMULATE EAN: ',ean); - self.on_ean(ean); - } - } }, save_callbacks: function(){ @@ -356,7 +354,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal value: 0, unit: 'none', }; - console.log('ean',ean); function match_prefix(prefix_set, type){ for(prefix in prefix_set){ diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 49521377ccb..5fbba5088d4 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -24,6 +24,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy this.db = new module.PosLS(); // a database used to store the products and categories this.db.clear('products','categories'); + this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode + // default attributes values. If null, it will be loaded below. this.set({ diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 3b580868e6b..d2c79f19928 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -560,21 +560,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa //we get the first cashregister marked as self-checkout var selfCheckoutRegisters = []; - for(var i = 0; i < this.pos.get('cashRegisters').models.length; i++){ - var cashregister = this.pos.get('cashRegisters').models[i]; + for(var i = 0; i < self.pos.get('cashRegisters').models.length; i++){ + var cashregister = self.pos.get('cashRegisters').models[i]; if(cashregister.self_checkout_payment_method){ selfCheckoutRegisters.push(cashregister); } } - var cashregister = selfCheckoutRegisters[0] || this.pos.get('cashRegisters').models[0]; + var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0]; currentOrder.addPaymentLine(cashregister); self.pos.push_order(currentOrder.exportAsJSON()) currentOrder.destroy(); self.pos.proxy.transaction_end(); self.pos_widget.screen_selector.set_current_screen(self.next_screen); }else if(payment === 'payment_rejected'){ - clearInterval(this.intervalID); + clearInterval(self.intervalID); //TODO show a tryagain thingie ? } },500); @@ -603,13 +603,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_numpad: false, show_leftpane: false, barcode_product_action: function(ean){ - self.pos.proxy.transaction_start(); + this.pos.proxy.transaction_start(); this._super(ean); }, barcode_client_action: function(ean){ - self.pos.proxy.transaction_start(); + this.pos.proxy.transaction_start(); this._super(ean); + $('.goodbye-message').hide(); this.pos_widget.screen_selector.show_popup('choose-receipt'); }, diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 2aaf9098329..c8e03ccff08 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -694,6 +694,91 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, }); + module.DebugWidget = module.PosBaseWidget.extend({ + template: "DebugWidget", + eans:{ + admin_badge: '0410300000004', + client_badge: '0420100000005', + invalid_ean: '1232456', + soda_33cl: '5449000000996', + oranges_kg: '2100002031410', + lemon_price: '2301000001560', + unknown_product: '9900000000004', + }, + events:[ + 'scan_item_success', + 'scan_item_error_unrecognized', + 'payment_request', + 'open_cashbox', + 'print_receipt', + 'print_pdf_invoice', + ], + minimized: false, + start: function(){ + var self = this; + + this.$el.draggable(); + this.$('.toggle').click(function(){ + var content = self.$('.content'); + var bg = self.$el; + if(!self.minimized){ + content.animate({'height':'0'},200); + }else{ + content.css({'height':'auto'}); + } + self.minimized = !self.minimized; + }); + this.$('.button.accept_payment').click(function(){ + self.pos.proxy.debug_accept_payment(); + }); + this.$('.button.reject_payment').click(function(){ + self.pos.proxy.debug_reject_payment(); + }); + this.$('.button.set_weight').click(function(){ + var kg = Number(self.$('input.weight').val()); + if(!Number.isNaN(kg)){ + self.pos.proxy.debug_set_weight(kg); + } + }); + this.$('.button.custom_ean').click(function(){ + var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0'); + self.$('input.ean').val(ean); + self.pos.barcode_reader.on_ean(ean); + }); + _.each(this.eans, function(ean, name){ + self.$('.button.'+name).click(function(){ + self.$('input.ean').val(ean); + self.pos.barcode_reader.on_ean(ean); + }); + }); + _.each(this.events, function(name){ + self.pos.proxy.add_notification(name,function(){ + console.log('Notification:',name); + self.$('.status.'+name).stop().clearQueue().css({'background-color':'#6CD11D'}); + self.$('.status.'+name).animate({'background-color':'black'},2000); + }); + }); + self.pos.proxy.add_notification('help_needed',function(){ + self.$('.status.help_needed').addClass('on'); + }); + self.pos.proxy.add_notification('help_canceled',function(){ + self.$('.status.help_needed').removeClass('on'); + }); + self.pos.proxy.add_notification('transaction_start',function(){ + self.$('.status.transaction').addClass('on'); + }); + self.pos.proxy.add_notification('transaction_end',function(){ + self.$('.status.transaction').removeClass('on'); + }); + self.pos.proxy.add_notification('weighting_start',function(){ + self.$('.status.weighting').addClass('on'); + }); + self.pos.proxy.add_notification('weighting_end',function(){ + self.$('.status.weighting').removeClass('on'); + }); + }, + }); + // The PosWidget is the main widget that contains all other widgets in the PointOfSale. // It is mainly composed of : // - a header, containing the list of orders @@ -718,14 +803,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.leftpane_width = '440px'; this.cashier_controls_visible = true; this.image_cache = new module.ImageCache(); // for faster products image display - - /* - //Epileptic mode - setInterval(function(){ - $('body').css({'-webkit-filter':'hue-rotate('+Math.random()*360+'deg)' }); - },100); - */ - }, start: function() { @@ -769,11 +846,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();}); self.$('.loader img').hide(); - if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){ - window.pos = self.pos; - window.pos_widget = self.pos_widget; - } - },function(){ // error when loading models data from the backend self.$('.loader img').hide(); return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id']) @@ -898,6 +970,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier', }); + if(this.pos.debug){ + this.debug_widget = new module.DebugWidget(this); + this.debug_widget.appendTo(this.$('#content')); + } }, changed_pending_operations: function () { diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index a0b129da2f9..7c8b18a9bc3 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -414,6 +414,55 @@
    + +
    +

    Debug Window

    +
    ▾
    +
    +

    Payment Terminal

    + +
      +
    • Accept Payment
    • +
    • Reject Payment
    • +
    +

    Electronic Scale

    +
      +
    • +
    • Set Weight
    • +
    + +

    Barcode Scanner

    +
      +
    • +
    • Custom Ean13
    • +
    • Admin Badge
    • +
    • Client Badge
    • +
    • Soda 33cl
    • +
    • 3.141Kg Oranges
    • +
    • 1.54€ Lemon
    • +
    • Unknown Product
    • +
    • Invalid Ean
    • +
    + +

    Hardware Status

    +
      +
    • Help needed
    • +
    • Weighting
    • +
    • In Transaction
    • +
    +

    Hardware Events

    +
      +
    • Scan Item Success
    • +
    • Scan Item Unrecognized
    • +
    • Payment Request
    • +
    • Open Cashbox
    • + + +
    +
    +
    +
    +
  • From 9dfca35b19d4e2cbc3ddbc74e36a4b5b0703275f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 12 Sep 2012 18:26:53 +0200 Subject: [PATCH 24/40] [IMP] point_of_sale: put debug window code in the right place bzr revid: fva@openerp.com-20120912162653-fnbvqwg4l51lveal --- addons/point_of_sale/static/src/js/widgets.js | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index c8e03ccff08..0ae127e0b1b 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -666,34 +666,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa hide: function(){ this.$el.hide(); }, }); - -// ---------- Main Point of Sale Widget ---------- - - // this is used to notify the user that data is being synchronized on the network - module.SynchNotificationWidget = module.PosBaseWidget.extend({ - template: "SynchNotificationWidget", - init: function(parent, options){ - options = options || {}; - this._super(parent, options); - }, - renderElement: function() { - var self = this; - this._super(); - this.$('.oe_pos_synch-notification-button').click(function(){ - self.pos.flush(); - }); - }, - start: function(){ - var self = this; - this.pos.bind('change:nbr_pending_operations', function(){ - self.renderElement(); - }); - }, - get_nbr_pending: function(){ - return this.pos.get('nbr_pending_operations'); - }, - }); - + // The debug widget lets the user control and monitor the hardware and software status + // without the use of the proxy module.DebugWidget = module.PosBaseWidget.extend({ template: "DebugWidget", eans:{ @@ -779,6 +753,34 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, }); +// ---------- Main Point of Sale Widget ---------- + + // this is used to notify the user that data is being synchronized on the network + module.SynchNotificationWidget = module.PosBaseWidget.extend({ + template: "SynchNotificationWidget", + init: function(parent, options){ + options = options || {}; + this._super(parent, options); + }, + renderElement: function() { + var self = this; + this._super(); + this.$('.oe_pos_synch-notification-button').click(function(){ + self.pos.flush(); + }); + }, + start: function(){ + var self = this; + this.pos.bind('change:nbr_pending_operations', function(){ + self.renderElement(); + }); + }, + get_nbr_pending: function(){ + return this.pos.get('nbr_pending_operations'); + }, + }); + + // The PosWidget is the main widget that contains all other widgets in the PointOfSale. // It is mainly composed of : // - a header, containing the list of orders From 5de520e4dad39b002ab6bbe18ecfc1d8bdd23e89 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 13 Sep 2012 12:06:28 +0200 Subject: [PATCH 25/40] [FIX] remove previous erroneous bugfix bzr revid: abo@openerp.com-20120913100628-oojngjznaeu0txyb --- addons/account/account_view.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index d11a6bc88bd..75359b4f207 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -112,7 +112,7 @@