[FIX+IMP] intrastat_product: Fixes and imps:

* Improve logs and messages
* total_amount is a sum of integers, so it should be an integer
* Add transport mode in computation tree view
* FIX intrastat_country for invoices without src_dest_country_id
* FIX wrong model for seach method
* Use stock_picking_invoice_link for a better identification of the intrastat region

  With this commit, we now support the following scenario: I order to my supplier a quantity of 50 kg and he delivers/invoices 52kg ; odoo will create an additional invoice line of 2kg which is linked to the stock move, but not to any PO line.
* Modularise a piece of code
* Add ACL on hs.code to financial manager (I can't do it in product_harmonized_system because it doesn't depend on account)
* Handle scenario where an invoice has products with 0 value (samples for example) and shipping costs (accessory costs) with value > 0.
This commit is contained in:
Alexis de Lattre
2016-01-12 22:25:01 +01:00
committed by João Marques
parent bbe2781fd4
commit c5eb9f5ba8
6 changed files with 102 additions and 48 deletions

View File

@@ -32,6 +32,7 @@
'depends': [ 'depends': [
'intrastat_base', 'intrastat_base',
'product_harmonized_system', 'product_harmonized_system',
'stock_picking_invoice_link',
'sale_stock', 'sale_stock',
'purchase', 'purchase',
], ],

View File

@@ -44,12 +44,20 @@ class AccountInvoice(models.Model):
'res.country', string='Origin/Destination Country', 'res.country', string='Origin/Destination Country',
ondelete='restrict') ondelete='restrict')
intrastat_country = fields.Boolean( intrastat_country = fields.Boolean(
related='src_dest_country_id.intrastat', compute='_compute_intrastat_country',
store=True, string='Intrastat Country', readonly=True) store=True, string='Intrastat Country', readonly=True)
intrastat = fields.Char( intrastat = fields.Char(
string='Intrastat Declaration', string='Intrastat Declaration',
related='company_id.intrastat', readonly=True) related='company_id.intrastat', readonly=True)
@api.multi
@api.depends('src_dest_country_id', 'partner_id.country_id')
def _compute_intrastat_country(self):
for inv in self:
country = inv.src_dest_country_id \
or inv.partner_id.country_id
inv.intrastat_country = country.intrastat
@api.model @api.model
def _default_intrastat_transaction(self): def _default_intrastat_transaction(self):
""" placeholder for localisation modules """ """ placeholder for localisation modules """

View File

@@ -158,9 +158,8 @@ class IntrastatProductDeclaration(models.Model):
num_decl_lines = fields.Integer( num_decl_lines = fields.Integer(
compute='_compute_numbers', string='Number of Declaration Lines', compute='_compute_numbers', string='Number of Declaration Lines',
store=True, track_visibility='onchange') store=True, track_visibility='onchange')
total_amount = fields.Float( total_amount = fields.Integer(
compute='_compute_numbers', digits=dp.get_precision('Account'), compute='_compute_numbers', string='Total Fiscal Amount', store=True,
string='Total Fiscal Amount', store=True,
help="Total fiscal amount in company currency of the declaration.") help="Total fiscal amount in company currency of the declaration.")
currency_id = fields.Many2one( currency_id = fields.Many2one(
'res.currency', related='company_id.currency_id', readonly=True, 'res.currency', related='company_id.currency_id', readonly=True,
@@ -269,7 +268,7 @@ class IntrastatProductDeclaration(models.Model):
note = "\n" + _( note = "\n" + _(
"Missing unit of measure on the line with %d " "Missing unit of measure on the line with %d "
"product(s) '%s' on invoice '%s'." "product(s) '%s' on invoice '%s'."
) % (line_qty, product.name, invoice.number) ) % (line_qty, product.name_get()[0][1], invoice.number)
note += "\n" + _( note += "\n" + _(
"Please adjust this line manually.") "Please adjust this line manually.")
self._note += note self._note += note
@@ -330,8 +329,8 @@ class IntrastatProductDeclaration(models.Model):
else: else:
note = "\n" + _( note = "\n" + _(
"Conversion from unit of measure '%s' to 'Kg' " "Conversion from unit of measure '%s' to 'Kg' "
"is not implemented yet." "is not implemented yet. It is needed for product '%s'."
) % source_uom.name ) % (source_uom.name, product.name_get()[0][1])
note += "\n" + _( note += "\n" + _(
"Please correct the unit of measure settings and " "Please correct the unit of measure settings and "
"regenerate the lines or adjust the impacted lines " "regenerate the lines or adjust the impacted lines "
@@ -351,25 +350,39 @@ class IntrastatProductDeclaration(models.Model):
def _get_region(self, inv_line): def _get_region(self, inv_line):
""" """
Logic copied from standard addons For supplier invoices/refunds: if the invoice line is linked
to a stock move, use the destination stock location ;
otherwise, get the PO (which is linked to a stock location)
and then get the warehouse.
It is important to take into account the following scenario:
I order a qty of 50 kg and my suppliers delivers and invoices 52 kg
(quite common in some industries where the order qty cannot be exact
due to some operational constraints) ; in this case, I have a qty of
2 kg which is not linked to a PO, but it is linked to a stock move.
If purchase, comes from purchase order, linked to a location, For customer invoices/refunds: if the invoice line is linked to a
which is linked to the warehouse. stock move, use the source stock location ;
otherwise, get the sale order, which is linked to the warehouse.
If sales, the sale order is linked to the warehouse. If none found, get the company's default intrastat region.
If sales, from a delivery order, linked to a location,
which is linked to the warehouse.
If none found, get the company one.
""" """
region = False region = False
if inv_line.invoice_id.type in ('in_invoice', 'in_refund'): if inv_line.invoice_id.type in ('in_invoice', 'in_refund'):
if inv_line.move_line_ids:
region = inv_line.move_line_ids[0].location_dest_id.\
get_intrastat_region()
else:
po_lines = self.env['purchase.order.line'].search( po_lines = self.env['purchase.order.line'].search(
[('invoice_lines', 'in', inv_line.id)]) [('invoice_lines', 'in', inv_line.id)])
if po_lines: if po_lines:
po = po_lines.order_id po = po_lines.order_id
region = po.location_id.get_intrastat_region() region = po.location_id.get_intrastat_region()
elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'): elif inv_line.invoice_id.type in ('out_invoice', 'out_refund'):
if inv_line.move_line_ids:
region = inv_line.move_line_ids[0].location_id.\
get_intrastat_region()
else:
so_lines = self.env['sale.order.line'].search( so_lines = self.env['sale.order.line'].search(
[('invoice_lines', 'in', inv_line.id)]) [('invoice_lines', 'in', inv_line.id)])
if so_lines: if so_lines:
@@ -411,20 +424,32 @@ class IntrastatProductDeclaration(models.Model):
def _handle_invoice_accessory_cost( def _handle_invoice_accessory_cost(
self, invoice, lines_current_invoice, self, invoice, lines_current_invoice,
total_inv_accessory_costs_cc, total_inv_product_cc): total_inv_accessory_costs_cc, total_inv_product_cc,
total_inv_weight):
""" """
Affect accessory costs pro-rata of the value. Affect accessory costs pro-rata of the value
(or pro-rata of the weight is the goods of the invoice
have no value)
This method allows to implement a different logic This method allows to implement a different logic
in the localization modules. in the localization modules.
E.g. in Belgium accessory cost should not be added. E.g. in Belgium accessory cost should not be added.
""" """
if total_inv_accessory_costs_cc and total_inv_product_cc: if total_inv_accessory_costs_cc:
if total_inv_product_cc:
# pro-rata of the value
for ac_line_vals in lines_current_invoice: for ac_line_vals in lines_current_invoice:
ac_line_vals['amount_accessory_cost_company_currency'] = ( ac_line_vals['amount_accessory_cost_company_currency'] = (
total_inv_accessory_costs_cc * total_inv_accessory_costs_cc *
ac_line_vals['amount_company_currency'] / ac_line_vals['amount_company_currency'] /
total_inv_product_cc) total_inv_product_cc)
else:
# pro-rata of the weight
for ac_line_vals in lines_current_invoice:
ac_line_vals['amount_accessory_cost_company_currency'] = (
total_inv_accessory_costs_cc *
ac_line_vals['weight'] /
total_inv_weight)
def _prepare_invoice_domain(self): def _prepare_invoice_domain(self):
start_date = date(self.year, self.month, 1) start_date = date(self.year, self.month, 1)
@@ -441,6 +466,14 @@ class IntrastatProductDeclaration(models.Model):
domain.append(('type', 'in', ('out_invoice', 'out_refund'))) domain.append(('type', 'in', ('out_invoice', 'out_refund')))
return domain return domain
def _is_product(self, invoice_line):
if (
invoice_line.product_id and
invoice_line.product_id.type in ('product', 'consu')):
return True
else:
return False
def _gather_invoices(self): def _gather_invoices(self):
lines = [] lines = []
@@ -454,6 +487,7 @@ class IntrastatProductDeclaration(models.Model):
lines_current_invoice = [] lines_current_invoice = []
total_inv_accessory_costs_cc = 0.0 # in company currency total_inv_accessory_costs_cc = 0.0 # in company currency
total_inv_product_cc = 0.0 # in company currency total_inv_product_cc = 0.0 # in company currency
total_inv_weight = 0.0
for inv_line in invoice.invoice_line: for inv_line in invoice.invoice_line:
if ( if (
@@ -474,18 +508,12 @@ class IntrastatProductDeclaration(models.Model):
'of invoice %s. Reason: qty = 0' 'of invoice %s. Reason: qty = 0'
% (inv_line.name, inv_line.quantity, invoice.number)) % (inv_line.name, inv_line.quantity, invoice.number))
continue continue
if not inv_line.price_subtotal:
_logger.info(
'Skipping invoice line %s qty %s '
'of invoice %s. Reason: price_subtotal = 0'
% (inv_line.name, inv_line.quantity, invoice.number))
continue
partner_country = self._get_partner_country(inv_line) partner_country = self._get_partner_country(inv_line)
if not partner_country: if not partner_country:
_logger.info( _logger.info(
'Skipping invoice line %s qty %s' 'Skipping invoice line %s qty %s '
'of invoice %s. Reason: not partner_country' 'of invoice %s. Reason: no partner_country'
% (inv_line.name, inv_line.quantity, invoice.number)) % (inv_line.name, inv_line.quantity, invoice.number))
continue continue
@@ -501,9 +529,7 @@ class IntrastatProductDeclaration(models.Model):
if inv_line.hs_code_id: if inv_line.hs_code_id:
hs_code = inv_line.hs_code_id hs_code = inv_line.hs_code_id
elif ( elif inv_line.product_id and self._is_product(inv_line):
inv_line.product_id and
inv_line.product_id.type in ('product', 'consu')):
hs_code = inv_line.product_id.product_tmpl_id.\ hs_code = inv_line.product_id.product_tmpl_id.\
get_hs_code_recursively() get_hs_code_recursively()
if not hs_code: if not hs_code:
@@ -526,6 +552,7 @@ class IntrastatProductDeclaration(models.Model):
weight, suppl_unit_qty = self._get_weight_and_supplunits( weight, suppl_unit_qty = self._get_weight_and_supplunits(
inv_line, hs_code) inv_line, hs_code)
total_inv_weight += weight
amount_company_currency = self._get_amount(inv_line) amount_company_currency = self._get_amount(inv_line)
total_inv_product_cc += amount_company_currency total_inv_product_cc += amount_company_currency
@@ -564,9 +591,24 @@ class IntrastatProductDeclaration(models.Model):
self._handle_invoice_accessory_cost( self._handle_invoice_accessory_cost(
invoice, lines_current_invoice, invoice, lines_current_invoice,
total_inv_accessory_costs_cc, total_inv_product_cc) total_inv_accessory_costs_cc, total_inv_product_cc,
total_inv_weight)
lines += lines_current_invoice for line_vals in lines_current_invoice:
if (
not line_vals['amount_company_currency'] and
not
line_vals['amount_accessory_cost_company_currency']):
inv_line = self.env['account.invoice.line'].browse(
line_vals['invoice_line_id'])
_logger.info(
'Skipping invoice line %s qty %s '
'of invoice %s. Reason: price_subtotal = 0 '
'and accessory costs = 0'
% (inv_line.name, inv_line.quantity,
inv_line.invoice_id.number))
continue
lines.append(line_vals)
return lines return lines

View File

@@ -40,8 +40,8 @@ class StockLocation(models.Model):
locations = self.search( locations = self.search(
[('parent_left', '<=', self.parent_left), [('parent_left', '<=', self.parent_left),
('parent_right', '>=', self.parent_right)]) ('parent_right', '>=', self.parent_right)])
warehouses = self.search( warehouses = self.env['stock.warehouse'].search([
[('lot_stock_id', 'in', [x.id for x in locations]), ('lot_stock_id', 'in', [x.id for x in locations]),
('region_id', '!=', False)]) ('region_id', '!=', False)])
if warehouses: if warehouses:
return warehouses[0].region_id return warehouses[0].region_id

View File

@@ -7,6 +7,7 @@ access_intrastat_transport_mode_read,Read access on Intrastat Transport Modes to
access_intrastat_transport_mode_full,Full access on Intrastat Transport Modes to Finance manager,model_intrastat_transport_mode,account.group_account_manager,1,1,1,1 access_intrastat_transport_mode_full,Full access on Intrastat Transport Modes to Finance manager,model_intrastat_transport_mode,account.group_account_manager,1,1,1,1
access_intrastat_region_read,Read access on Intrastat Regions,model_intrastat_region,,1,0,0,0 access_intrastat_region_read,Read access on Intrastat Regions,model_intrastat_region,,1,0,0,0
access_intrastat_region_full,Full access on Intrastat Regions,model_intrastat_region,account.group_account_manager,1,1,1,1 access_intrastat_region_full,Full access on Intrastat Regions,model_intrastat_region,account.group_account_manager,1,1,1,1
access_hs_code_financial_mgr_full,Full access on H.S. Code to financial mgr,product_harmonized_system.model_hs_code,account.group_account_manager,1,1,1,1
access_intrastat_product_declaration,Full access on Intrastat Product Declarations to Accountant,model_intrastat_product_declaration,account.group_account_user,1,1,1,1 access_intrastat_product_declaration,Full access on Intrastat Product Declarations to Accountant,model_intrastat_product_declaration,account.group_account_user,1,1,1,1
access_intrastat_product_computation_line,Full access on Intrastat Product Computation Lines to Accountant,model_intrastat_product_computation_line,account.group_account_user,1,1,1,1 access_intrastat_product_computation_line,Full access on Intrastat Product Computation Lines to Accountant,model_intrastat_product_computation_line,account.group_account_user,1,1,1,1
access_intrastat_product_declaration_line,Full access on Intrastat Product Declaration Lines to Accountant,model_intrastat_product_declaration_line,account.group_account_user,1,1,1,1 access_intrastat_product_declaration_line,Full access on Intrastat Product Declaration Lines to Accountant,model_intrastat_product_declaration_line,account.group_account_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
7 access_intrastat_transport_mode_full Full access on Intrastat Transport Modes to Finance manager model_intrastat_transport_mode account.group_account_manager 1 1 1 1
8 access_intrastat_region_read Read access on Intrastat Regions model_intrastat_region 1 0 0 0
9 access_intrastat_region_full Full access on Intrastat Regions model_intrastat_region account.group_account_manager 1 1 1 1
10 access_hs_code_financial_mgr_full Full access on H.S. Code to financial mgr product_harmonized_system.model_hs_code account.group_account_manager 1 1 1 1
11 access_intrastat_product_declaration Full access on Intrastat Product Declarations to Accountant model_intrastat_product_declaration account.group_account_user 1 1 1 1
12 access_intrastat_product_computation_line Full access on Intrastat Product Computation Lines to Accountant model_intrastat_product_computation_line account.group_account_user 1 1 1 1
13 access_intrastat_product_declaration_line Full access on Intrastat Product Declaration Lines to Accountant model_intrastat_product_declaration_line account.group_account_user 1 1 1 1

View File

@@ -188,6 +188,8 @@
<field name="suppl_unit_qty" <field name="suppl_unit_qty"
attrs="{'invisible': [('intrastat_unit_id', '=', False)], 'required': [('intrastat_unit_id', '!=', False)]}"/> attrs="{'invisible': [('intrastat_unit_id', '=', False)], 'required': [('intrastat_unit_id', '!=', False)]}"/>
<field name="intrastat_unit_id"/> <field name="intrastat_unit_id"/>
<field name="transport_id"
attrs="{'required': [('reporting_level', '=', 'extended')], 'invisible': [('reporting_level', '!=', 'extended')]}"/>
<field name="region_id" invisible="1"/> <field name="region_id" invisible="1"/>
<field name="product_origin_country_id" invisible="1" string="Product C/O"/> <field name="product_origin_country_id" invisible="1" string="Product C/O"/>
<field name="invoice_id"/> <field name="invoice_id"/>