X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fpoint_of_sale%2Fpoint_of_sale.py;h=81f86fd1ff504a3af9b9b2ef2250848f78245bcb;hb=7ab7cf2149c84f5d3c9a7866b4320be271fac97c;hp=a39ab3bdf6c624693530474c4b44e7f9a04856df;hpb=f19775e62779775de30d170fbf30673cc1314a05;p=odoo%2Fodoo.git diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index a39ab3b..81f86fd 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -45,24 +45,39 @@ class pos_config(osv.osv): ('deprecated', 'Deprecated') ] + def _get_currency(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, False) + for pos_config in self.browse(cr, uid, ids, context=context): + if pos_config.journal_id: + currency_id = pos_config.journal_id.currency.id or pos_config.journal_id.company_id.currency_id.id + else: + currency_id = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id.id + result[pos_config.id] = currency_id + return result + _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 ), ('type', 'in', ['bank', 'cash'])]",), - 'shop_id' : fields.many2one('sale.shop', 'Shop', + 'warehouse_id' : fields.many2one('stock.warehouse', 'Warehouse', required=True), 'journal_id' : fields.many2one('account.journal', 'Sale Journal', domain=[('type', '=', 'sale')], help="Accounting journal used to post sales entries."), + 'currency_id' : fields.function(_get_currency, type="many2one", string="Currency", relation="res.currency"), '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'), + 'iface_cashdrawer' : fields.boolean('Cashdrawer',help="Automatically open the cashdrawer"), + 'iface_payment_terminal' : fields.boolean('Payment Terminal', help="Enables Payment Terminal integration"), + 'iface_electronic_scale' : fields.boolean('Electronic Scale', help="Enables Electronic Scale integration"), + 'iface_vkeyboard' : fields.boolean('Virtual KeyBoard', help="Enables an integrated Virtual Keyboard"), + 'iface_print_via_proxy' : fields.boolean('Print via Proxy', help="Bypass browser printing and prints via the hardware proxy"), + 'iface_invoicing': fields.boolean('Invoicing',help='Enables invoice generation from the Point of Sale'), + 'iface_big_scrollbars': fields.boolean('Large Scrollbars',help='For imprecise industrial touchscreens'), + 'receipt_header': fields.text('Receipt Header',help="A short text that will be inserted as a header in the printed receipt"), + 'receipt_footer': fields.text('Receipt Footer',help="A short text that will be inserted as a footer in the printed receipt"), 'state' : fields.selection(POS_CONFIG_STATE, 'Status', required=True, readonly=True), 'sequence_id' : fields.many2one('ir.sequence', 'Order IDs Sequence', readonly=True, @@ -70,6 +85,7 @@ class pos_config(osv.osv): "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"), + 'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True) } def _check_cash_control(self, cr, uid, ids, context=None): @@ -109,23 +125,26 @@ class pos_config(osv.osv): return result def _default_sale_journal(self, cr, uid, context=None): - res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale')], limit=1) + company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id + res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', company_id)], limit=1, context=context) return res and res[0] or False - def _default_shop(self, cr, uid, context=None): - res = self.pool.get('sale.shop').search(cr, uid, []) + def _default_warehouse(self, cr, uid, context=None): + user = self.pool.get('res.users').browse(cr, uid, uid, context) + res = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', user.company_id.id)], limit=1, context=context) return res and res[0] or False - def _default_payment_method(self, cr, uid, context=None): - res = self.pool.get('account.journal').search(cr, uid, [('journal_user','=', True)], context=context) - return res or False + def _default_pricelist(self, cr, uid, context=None): + res = self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'sale')], limit=1, context=context) + return res and res[0] or False _defaults = { 'state' : POS_CONFIG_STATE[0][0], - 'shop_id': _default_shop, + 'warehouse_id': _default_warehouse, 'journal_id': _default_sale_journal, - 'journal_ids': _default_payment_method, 'group_by' : True, + 'pricelist_id': _default_pricelist, + 'iface_invoicing': True, } def set_active(self, cr, uid, ids, context=None): @@ -197,6 +216,7 @@ class pos_session(osv.osv): readonly=True, states={'opening_control' : [('readonly', False)]} ), + 'currency_id' : fields.related('config_id', 'currency_id', type="many2one", relation='res.currency', string="Currnecy"), 'start_at' : fields.datetime('Opening Date', readonly=True), 'stop_at' : fields.datetime('Closing Date', readonly=True), @@ -227,27 +247,28 @@ class pos_session(osv.osv): type='float', digits_compute=dp.get_precision('Account'), string="Ending Balance", - help="Computed using the cash control lines", + help="Total of closing 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.", + help="Total of opening cash control lines.", readonly=True), 'cash_register_total_entry_encoding' : fields.related('cash_register_id', 'total_entry_encoding', string='Total Cash Transaction', - readonly=True), + readonly=True, + help="Total of all paid sale orders"), '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.", + string="Theoretical Closing Balance", + help="Sum of opening balance and transactions.", 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.", + help="Difference between the theoretical closing balance and the real closing balance.", readonly=True), 'journal_ids' : fields.related('config_id', 'journal_ids', @@ -275,7 +296,7 @@ class pos_session(osv.osv): # 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) + ('user_id', '=', session.user_id.id) ] count = self.search_count(cr, uid, domain, context=context) if count>1: @@ -311,7 +332,7 @@ class pos_session(osv.osv): # 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) - context.update({'company_id': pos_config.shop_id.company_id.id}) + context.update({'company_id': pos_config.warehouse_id.company_id.id}) if not pos_config.journal_id: jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id'] if jid: @@ -338,7 +359,7 @@ class pos_session(osv.osv): bank_values = { 'journal_id' : journal.id, 'user_id' : uid, - 'company_id' : pos_config.shop_id.company_id.id + 'company_id' : pos_config.warehouse_id.company_id.id } statement_id = self.pool.get('account.bank.statement').create(cr, uid, bank_values, context=context) bank_statement_ids.append(statement_id) @@ -374,10 +395,9 @@ class pos_session(osv.osv): context.update(active_id=this_record.id) return { - 'type' : 'ir.actions.client', - 'name' : _('Start Point Of Sale'), - 'tag' : 'pos.ui', - 'context' : context, + 'type' : 'ir.actions.act_url', + 'url' : '/pos/web/', + 'target': 'self', } def wkf_action_open(self, cr, uid, ids, context=None): @@ -412,7 +432,7 @@ class pos_session(osv.osv): # 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)) + _("Your ending balance is too different from the theoretical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff)) if (st.journal_id.type not in ['bank', 'cash']): raise osv.except_osv(_('Error!'), _("The type of the journal for your payment method should be bank or cash ")) @@ -473,12 +493,16 @@ class pos_session(osv.osv): context = {} if not ids: return {} + for session in self.browse(cr, uid, ids, context=context): + if session.user_id.id != uid: + raise osv.except_osv( + _('Error!'), + _("You cannot use the session of another users. This session is owned by %s. Please first close this one to use this point of sale." % session.user_id.name)) context.update({'active_id': ids[0]}) return { - 'type' : 'ir.actions.client', - 'name' : _('Start Point Of Sale'), - 'tag' : 'pos.ui', - 'context' : context, + 'type' : 'ir.actions.act_url', + 'target': 'self', + 'url': '/pos/web/', } class pos_order(osv.osv): @@ -490,15 +514,18 @@ class pos_order(osv.osv): #_logger.info("orders: %r", orders) order_ids = [] for tmp_order in orders: + to_invoice = tmp_order['to_invoice'] 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'], - 'pos_reference':order['name'] + 'pos_reference':order['name'], + 'partner_id': order['partner_id'] or False }, context) - for payments in order['statement_ids']: payment = payments[2] self.add_payment(cr, uid, order_id, { @@ -527,6 +554,12 @@ class pos_order(osv.osv): }, context=context) order_ids.append(order_id) self.signal_paid(cr, uid, [order_id]) + + if to_invoice: + self.action_invoice(cr, uid, [order_id], context) + order_obj = self.browse(cr, uid, order_id, context) + self.pool['account.invoice'].signal_invoice_open(cr, uid, [order_obj.invoice_id.id]) + return order_ids def write(self, cr, uid, ids, vals, context=None): @@ -550,7 +583,7 @@ class pos_order(osv.osv): 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.')) + 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): @@ -599,13 +632,13 @@ class pos_order(osv.osv): _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), + 'warehouse_id': fields.related('session_id', 'config_id', 'warehouse_id', relation='stock.warehouse', type='many2one', string='Warehouse', 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_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Account'), 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'), + 'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits_compute=dp.get_precision('Account'), multi='all'), + 'amount_return': fields.function(_amount_all, 'Returned', digits_compute=dp.get_precision('Account'), 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), @@ -643,8 +676,7 @@ class pos_order(osv.osv): session_ids = self._default_session(cr, uid, context) if session_ids: session_record = self.pool.get('pos.session').browse(cr, uid, session_ids, context=context) - shop = self.pool.get('sale.shop').browse(cr, uid, session_record.config_id.shop_id.id, context=context) - return shop.pricelist_id and shop.pricelist_id.id or False + return session_record.config_id.pricelist_id and session_record.config_id.pricelist_id.id or False return False _defaults = { @@ -676,7 +708,7 @@ class pos_order(osv.osv): 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') + picking_obj = self.pool.get('stock.picking.out') partner_obj = self.pool.get('res.partner') move_obj = self.pool.get('stock.move') @@ -695,14 +727,15 @@ class pos_order(osv.osv): '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 + location_id = order.warehouse_id.lot_stock_id.id + if order.partner_id: + destination_id = order.partner_id.property_stock_customer.id + else: + destination_id = partner_obj.default_get(cr, uid, ['property_stock_customer'], context=context)['property_stock_customer'] 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, @@ -714,11 +747,9 @@ class pos_order(osv.osv): 'product_qty': abs(line.qty), 'tracking_id': False, 'state': 'draft', - 'location_id': location_id, - 'location_dest_id': output_id, + 'location_id': location_id if line.qty >= 0 else destination_id, + 'location_dest_id': destination_id if line.qty >= 0 else location_id, }, context=context) - if line.qty < 0: - location_id, output_id = output_id, location_id picking_obj.signal_button_confirm(cr, uid, [picking_id]) picking_obj.force_assign(cr, uid, [picking_id], context) @@ -729,7 +760,6 @@ class pos_order(osv.osv): @return: True """ stock_picking_obj = self.pool.get('stock.picking') - wf_service = netsvc.LocalService("workflow") for order in self.browse(cr, uid, ids, context=context): stock_picking_obj.signal_button_cancel(cr, uid, [order.picking_id.id]) if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel': @@ -795,9 +825,18 @@ class pos_order(osv.osv): """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): + current_session_ids = self.pool.get('pos.session').search(cr, uid, [ + ('state', '!=', 'closed'), + ('user_id', '=', uid)], context=context) + if not current_session_ids: + raise osv.except_osv(_('Error!'), _('To return product(s), you need to open a session that will be used to register the refund.')) + clone_id = self.copy(cr, uid, order.id, { - 'name': order.name + ' REFUND', + 'name': order.name + ' REFUND', # not used, name forced by create + 'session_id': current_session_ids[0], + 'date_order': time.strftime('%Y-%m-%d %H:%M:%S'), }, context=context) clone_list.append(clone_id) @@ -880,6 +919,7 @@ class pos_order(osv.osv): inv_line_ref.create(cr, uid, inv_line, context=context) inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context) self.signal_invoice(cr, uid, [order.id]) + inv_ref.signal_validate(cr, uid, [inv_id]) if not inv_ids: return {} @@ -944,11 +984,12 @@ class pos_order(osv.osv): 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 + account_def = property_obj.get(cr, uid, 'property_account_receivable', 'res.partner', context=context) 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 + order.partner_id.property_account_receivable.id or \ + account_def and account_def.id or current_company.account_receivable.id if move_id is None: # Create an entry for the sale @@ -974,11 +1015,11 @@ class pos_order(osv.osv): }) if data_type == 'product': - key = ('product', values['partner_id'], values['product_id']) + key = ('product', values['partner_id'], values['product_id'], values['debit'] > 0) elif data_type == 'tax': - key = ('tax', values['partner_id'], values['tax_code_id'],) + key = ('tax', values['partner_id'], values['tax_code_id'], values['debit'] > 0) elif data_type == 'counter_part': - key = ('counter_part', values['partner_id'], values['account_id']) + key = ('counter_part', values['partner_id'], values['account_id'], values['debit'] > 0) else: return @@ -1168,7 +1209,7 @@ class pos_order_line(osv.osv): if not product_id: return {} if not pricelist: - raise osv.except_osv(_('No 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.')) @@ -1200,11 +1241,11 @@ class pos_order_line(osv.osv): '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_unit': fields.float(string='Unit Price', digits_compute=dp.get_precision('Account')), + 'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoS')), '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)), + 'discount': fields.float('Discount (%)', digits_compute=dp.get_precision('Account')), 'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'), 'create_date': fields.datetime('Creation Date', readonly=True), } @@ -1337,7 +1378,7 @@ class product_product(osv.osv): 'expense_pdt': fields.boolean('Point of Sale Cash Out', help="Check if, this is a product you can use to take cash from a statement for the point of sale backend, example: money lost, transfer to bank, etc."), 'available_in_pos': fields.boolean('Available in the Point of Sale', help='Check if you want this product to appear in the Point of Sale'), 'pos_categ_id': fields.many2one('pos.category','Point of Sale Category', - help="The Point of Sale Category this products belongs to. Those categories are used to group similar products and are specific to the Point of Sale."), + help="These products belong to those categories that are used to group similar products and are specific to the Point of Sale."), 'to_weight' : fields.boolean('To Weight', help="Check if the product should be weighted (mainly used with self check-out interface)."), }