Browse Source

Merge pull request #99 from StefanRijnhart/8.0-pos_pricelist_improve_fiscal_position

[FIX] Honour mapped taxes when creating move lines
pull/183/head
Rafael Blasco 7 years ago
committed by GitHub
parent
commit
d467577f91
  1. 13
      pos_pricelist/README.rst
  2. 1
      pos_pricelist/__openerp__.py
  3. 1
      pos_pricelist/models/__init__.py
  4. 10
      pos_pricelist/models/point_of_sale.py
  5. 234
      pos_pricelist/models/pos_order_patch.py

13
pos_pricelist/README.rst

@ -52,9 +52,9 @@ The POS will recognize it and will compute the price according to the rule defin
- Implemented Rules are :
1. (-1) : Rule based on other pricelist
2. (-2) : Rule based on supplierinfo
3. (default) : Any price type which is set on the product form
1. (-1) : Rule based on other pricelist
2. (-2) : Rule based on supplierinfo
3. (default) : Any price type which is set on the product form
3. An new option is introduced in the POS config to let the user show price with taxes in product widget.
the UI is updated when we change the customer in order to adapt the prices.
@ -71,12 +71,12 @@ Implemented features at backend
1. Tax details
- Tax details per order line
- Tax details aggregated by tax at order level
- Tax details per order line
- Tax details aggregated by tax at order level
2. Ticket
- Tax details table added at end of printed ticket
- Tax details table added at end of printed ticket
Known issues / Roadmap
@ -86,6 +86,7 @@ Missing features
----------------
* As you may know, product template is not fully implemented in the POS, so I decided to drop it from this module.
* Applying a fiscal position on a product with inclusive taxes is not yet supported. In this case, the mapped taxes will be applied to the price incuding taxes.
Bug Tracker

1
pos_pricelist/__openerp__.py

@ -31,4 +31,5 @@
],
'post_init_hook': "set_pos_line_taxes",
'installable': True,
'license': 'AGPL-3',
}

1
pos_pricelist/models/__init__.py

@ -6,3 +6,4 @@
from . import account_fiscal_position
from . import pos_pricelist
from . import point_of_sale
from . import pos_order_patch

10
pos_pricelist/models/point_of_sale.py

@ -18,9 +18,12 @@
#
##############################################################################
from openerp import models, fields, api
from openerp.addons import decimal_precision as dp
from openerp.addons.point_of_sale.point_of_sale import pos_order as base_order
from openerp.addons.pos_pricelist.models.pos_order_patch import (
_create_account_move_line)
import logging
_logger = logging.getLogger(__name__)
@ -158,3 +161,8 @@ class PosOrder(models.Model):
# Compute tax detail
orders.compute_tax_detail()
_logger.info("%d orders computed installing module.", len(orders))
def _register_hook(self, cr):
res = super(PosOrder, self)._register_hook(cr)
base_order._create_account_move_line = _create_account_move_line
return res

234
pos_pricelist/models/pos_order_patch.py

@ -0,0 +1,234 @@
# coding: utf-8
# Copyright: Odoo S.A.
# License: AGPL-3
# flake8: noqa
# pylint: skip-file
from openerp.tools.translate import _
def _create_account_move_line(self, cr, uid, ids, session=None, move_id=None, context=None):
""" Monkeypatch for this method's version on pos.order in the point_of_sale
module. Only change is to refer to the line's taxes instead of the
product's taxes (change below is marked with 'pos_pricelist'). Keep in a
separate file so that it can be excluded from flake8 inspection. """
if True: # Keep indentation level for reference purposes
# 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_period_obj = self.pool.get('account.period')
account_tax_obj = self.pool.get('account.tax')
property_obj = self.pool.get('ir.property')
cur_obj = self.pool.get('res.currency')
#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!'))
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 = abs(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
current_company = order.sale_journal.company_id
group_tax = {}
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 and account_def.id
if move_id is None:
# Create an entry for the sale
move_id = self._create_account_move(cr, uid, order.session_id.start_at, order.name, order.sale_journal.id, order.company_id.id, context=context)
move = account_move_obj.browse(cr, uid, move_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,
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False,
'journal_id' : sale_journal_id,
'period_id': move.period_id.id,
'move_id' : move_id,
'company_id': current_company.id,
})
if data_type == 'product':
key = ('product', values['partner_id'], values['product_id'], values['analytic_account_id'], values['debit'] > 0)
elif data_type == 'tax':
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'], values['debit'] > 0)
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:
for line in grouped_data[key]:
if line.get('tax_code_id') == values.get('tax_code_id'):
current_value = line
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)
break
else:
grouped_data[key].append(values)
else:
grouped_data[key].append(values)
#because of the weird way the pos order is written, we need to make sure there is at least one line,
#because just after the 'for' loop there are references to 'line' and 'income_account' variables (that
#are set inside the for loop)
#TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack
assert order.lines, _('The POS order must have lines when calling this method')
# Create an move for each order line
cur = order.pricelist_id.currency_id
round_per_line = True
if order.company_id.tax_calculation_rounding_method == 'round_globally':
round_per_line = False
for line in order.lines:
tax_amount = 0
taxes = []
# [pos_pricelist] Only change in the next line:
# for t in line.product_id.taxes_id:
for t in line.tax_ids if 'tax_ids' in line._fields else line.product_id.taxes_id:
if t.company_id.id == current_company.id:
taxes.append(t)
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 += cur_obj.round(cr, uid, cur, tax['amount']) if round_per_line else tax['amount']
if tax_amount < 0:
group_key = (tax['ref_tax_code_id'], tax['base_code_id'], tax['account_collected_id'], tax['id'])
else:
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] += cur_obj.round(cr, uid, cur, tax['amount']) if round_per_line else tax['amount']
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,
'analytic_account_id': self._prepare_analytic_account(cr, uid, line, context=context),
'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 self.pool.get("res.partner")._find_accounting_partner(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,
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
})
# 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] or income_account,
'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': abs(tax_amount) * tax.tax_sign if tax_amount>=0 else abs(tax_amount) * tax.ref_tax_sign,
'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
})
# 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 self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False
})
order.write({'state':'done', 'account_move': move_id})
all_lines = []
for group_key, group_data in grouped_data.iteritems():
for value in group_data:
all_lines.append((0, 0, value),)
if move_id: #In case no order was changed
self.pool.get("account.move").write(cr, uid, [move_id], {'line_id':all_lines}, context=context)
return True
Loading…
Cancel
Save