[REF] stock_landed_costs: create from vendor bill
Add a many2one on the landed cost to the vendor bill. Allow to create a landed cost from a vendor bill if the product is a service. It will use the price_subtotal of the account move line. Add a is_landed_costs_line to separate products of landed costs on the account move line. It's a checkbox. Ticking the checkbox will change the account of the account move if the company is in anglo saxon accounting. Tweak the accounts used by the landed costs so that everything is well balanced (see the added tests). Auto reconcile AML when possible. task-59011
This commit is contained in:
@@ -8,6 +8,44 @@ from odoo.fields import Datetime
|
||||
from odoo.tests.common import Form, SavepointCase
|
||||
|
||||
|
||||
def _create_accounting_data(env):
|
||||
"""Create the accounts and journals used in stock valuation.
|
||||
|
||||
:param env: environment used to create the records
|
||||
:return: an input account, an output account, a valuation account, an expense account, a stock journal
|
||||
"""
|
||||
stock_input_account = env['account.account'].create({
|
||||
'name': 'Stock Input',
|
||||
'code': 'StockIn',
|
||||
'user_type_id': env.ref('account.data_account_type_current_assets').id,
|
||||
'reconcile': True,
|
||||
})
|
||||
stock_output_account = env['account.account'].create({
|
||||
'name': 'Stock Output',
|
||||
'code': 'StockOut',
|
||||
'user_type_id': env.ref('account.data_account_type_current_assets').id,
|
||||
'reconcile': True,
|
||||
})
|
||||
stock_valuation_account = env['account.account'].create({
|
||||
'name': 'Stock Valuation',
|
||||
'code': 'Stock Valuation',
|
||||
'user_type_id': env.ref('account.data_account_type_current_assets').id,
|
||||
'reconcile': True,
|
||||
})
|
||||
expense_account = env['account.account'].create({
|
||||
'name': 'Expense Account',
|
||||
'code': 'Expense Account',
|
||||
'user_type_id': env.ref('account.data_account_type_expenses').id,
|
||||
'reconcile': True,
|
||||
})
|
||||
stock_journal = env['account.journal'].create({
|
||||
'name': 'Stock Journal',
|
||||
'code': 'STJTEST',
|
||||
'type': 'general',
|
||||
})
|
||||
return stock_input_account, stock_output_account, stock_valuation_account, expense_account, stock_journal
|
||||
|
||||
|
||||
class TestStockValuation(SavepointCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -37,34 +75,9 @@ class TestStockValuation(SavepointCase):
|
||||
'groups_id': [(6, 0, [cls.env.ref('stock.group_stock_user').id])]
|
||||
})
|
||||
|
||||
cls.stock_input_account, cls.stock_output_account, cls.stock_valuation_account, cls.expense_account, cls.stock_journal = _create_accounting_data(cls.env)
|
||||
cls.product1.categ_id.property_valuation = 'real_time'
|
||||
cls.product2.categ_id.property_valuation = 'real_time'
|
||||
Account = cls.env['account.account']
|
||||
cls.stock_input_account = Account.create({
|
||||
'name': 'Stock Input',
|
||||
'code': 'StockIn',
|
||||
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
cls.stock_output_account = Account.create({
|
||||
'name': 'Stock Output',
|
||||
'code': 'StockOut',
|
||||
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
cls.stock_valuation_account = Account.create({
|
||||
'name': 'Stock Valuation',
|
||||
'code': 'Stock Valuation',
|
||||
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
cls.expense_account = Account.create({
|
||||
'name': 'Expense Account',
|
||||
'code': 'Expense Account',
|
||||
'user_type_id': cls.env.ref('account.data_account_type_expenses').id,
|
||||
})
|
||||
cls.stock_journal = cls.env['account.journal'].create({
|
||||
'name': 'Stock Journal',
|
||||
'code': 'STJTEST',
|
||||
'type': 'general',
|
||||
})
|
||||
cls.product1.write({
|
||||
'property_account_expense_id': cls.expense_account.id,
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
""" Implementation of "INVENTORY VALUATION TESTS (With valuation layers)" spreadsheet. """
|
||||
|
||||
from odoo.addons.stock_account.tests.test_stockvaluation import _create_accounting_data
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.tests.common import SavepointCase, TransactionCase
|
||||
|
||||
@@ -709,8 +710,22 @@ class TestStockValuationChangeCostMethod(TestStockValuationCommon):
|
||||
self.assertEqual(self.product1.quantity_svl, 19)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestStockValuationChangeValuation(TestStockValuationCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestStockValuationChangeValuation, cls).setUpClass()
|
||||
cls.stock_input_account, cls.stock_output_account, cls.stock_valuation_account, cls.expense_account, cls.stock_journal = _create_accounting_data(cls.env)
|
||||
cls.product1.categ_id.property_valuation = 'real_time'
|
||||
cls.product1.write({
|
||||
'property_account_expense_id': cls.expense_account.id,
|
||||
})
|
||||
cls.product1.categ_id.write({
|
||||
'property_stock_account_input_categ_id': cls.stock_input_account.id,
|
||||
'property_stock_account_output_categ_id': cls.stock_output_account.id,
|
||||
'property_stock_valuation_account_id': cls.stock_valuation_account.id,
|
||||
'property_stock_journal': cls.stock_journal.id,
|
||||
})
|
||||
|
||||
def test_standard_manual_to_auto_1(self):
|
||||
self.product1.product_tmpl_id.categ_id.property_cost_method = 'standard'
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'manual_periodic'
|
||||
@@ -743,9 +758,16 @@ class TestStockValuationChangeValuation(TestStockValuationCommon):
|
||||
self.assertEqual(len(self.product1.stock_valuation_layer_ids.mapped('account_move_id')), 0)
|
||||
self.assertEqual(len(self.product1.stock_valuation_layer_ids), 1)
|
||||
|
||||
cat2 = self.env['product.category'].create({'name': 'fifo'})
|
||||
cat2 = self.env['product.category'].create({'name': 'standard auto'})
|
||||
cat2.property_cost_method = 'standard'
|
||||
cat2.property_valuation = 'real_time'
|
||||
cat2.write({
|
||||
'property_stock_account_input_categ_id': self.stock_input_account.id,
|
||||
'property_stock_account_output_categ_id': self.stock_output_account.id,
|
||||
'property_stock_valuation_account_id': self.stock_valuation_account.id,
|
||||
'property_stock_journal': self.stock_journal.id,
|
||||
})
|
||||
|
||||
self.product1.categ_id = cat2
|
||||
|
||||
self.assertEqual(self.product1.value_svl, 100)
|
||||
|
||||
@@ -19,6 +19,7 @@ This module allows you to easily add extra costs on pickings and decide the spli
|
||||
'security/ir.model.access.csv',
|
||||
'security/stock_landed_cost_security.xml',
|
||||
'data/stock_landed_cost_data.xml',
|
||||
'views/account_move_views.xml',
|
||||
'views/product_views.xml',
|
||||
'views/stock_landed_cost_views.xml',
|
||||
'views/stock_valuation_layer_views.xml',
|
||||
|
||||
@@ -5,5 +5,6 @@ from . import product
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import stock_landed_cost
|
||||
from . import account_move
|
||||
from . import stock_valuation_layer
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
landed_costs_ids = fields.One2many('stock.landed.cost', 'vendor_bill_id', string='Landed Costs')
|
||||
landed_costs_visible = fields.Boolean(compute='_compute_landed_costs_visible')
|
||||
|
||||
@api.depends('line_ids', 'line_ids.is_landed_costs_line')
|
||||
def _compute_landed_costs_visible(self):
|
||||
for account_move in self:
|
||||
if account_move.landed_costs_ids:
|
||||
account_move.landed_costs_visible = False
|
||||
else:
|
||||
account_move.landed_costs_visible = any(line.is_landed_costs_line for line in account_move.line_ids)
|
||||
|
||||
def button_create_landed_costs(self):
|
||||
"""Create a `stock.landed.cost` record associated to the account move of `self`, each
|
||||
`stock.landed.costs` lines mirroring the current `account.move.line` of self.
|
||||
"""
|
||||
self.ensure_one()
|
||||
landed_costs_lines = self.line_ids.filtered(lambda line: line.is_landed_costs_line)
|
||||
|
||||
landed_costs = self.env['stock.landed.cost'].create({
|
||||
'vendor_bill_id': self.id,
|
||||
'cost_lines': [(0, 0, {
|
||||
'product_id': l.product_id.id,
|
||||
'name': l.product_id.name,
|
||||
'account_id': l.product_id.product_tmpl_id.get_product_accounts()['stock_input'].id,
|
||||
'price_unit': l.price_subtotal,
|
||||
'split_method': 'equal',
|
||||
}) for l in landed_costs_lines],
|
||||
})
|
||||
action = self.env.ref('stock_landed_costs.action_stock_landed_cost').read()[0]
|
||||
return dict(action, view_mode='form', res_id=landed_costs.id, views=[(False, 'form')])
|
||||
|
||||
def action_view_landed_costs(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('stock_landed_costs.action_stock_landed_cost').read()[0]
|
||||
domain = [('id', 'in', self.landed_costs_ids.ids)]
|
||||
context = dict(self.env.context, default_vendor_bill_id=self.id)
|
||||
views = [(self.env.ref('stock_landed_costs.view_stock_landed_cost_tree2').id, 'tree'), (False, 'form'), (False, 'kanban')]
|
||||
return dict(action, domain=domain, context=context, views=views)
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
product_type = fields.Selection(related='product_id.type', readonly=True)
|
||||
is_landed_costs_line = fields.Boolean()
|
||||
|
||||
@api.onchange('is_landed_costs_line')
|
||||
def _onchange_is_landed_costs_line(self):
|
||||
"""Mark an invoice line as a landed cost line and adapt `self.account_id`. The default
|
||||
value can be set according to `self.product_id.landed_cost_ok`."""
|
||||
if self.product_id:
|
||||
accounts = self.product_id.product_tmpl_id._get_product_accounts()
|
||||
if self.product_type != 'service':
|
||||
self.account_id = accounts['expense']
|
||||
self.is_landed_costs_line = False
|
||||
elif self.is_landed_costs_line and self.move_id.company_id.anglo_saxon_accounting:
|
||||
self.account_id = accounts['stock_input']
|
||||
else:
|
||||
self.account_id = accounts['expense']
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_is_landed_costs_line_product(self):
|
||||
if self.product_id.landed_cost_ok:
|
||||
self.is_landed_costs_line = True
|
||||
else:
|
||||
self.is_landed_costs_line = False
|
||||
@@ -118,6 +118,7 @@ class LandedCost(models.Model):
|
||||
'date': cost.date,
|
||||
'ref': cost.name,
|
||||
'line_ids': [],
|
||||
'type': 'entry',
|
||||
}
|
||||
for line in cost.valuation_adjustment_lines.filtered(lambda line: line.move_id):
|
||||
remaining_qty = sum(line.move_id.stock_valuation_layer_ids.mapped('remaining_qty'))
|
||||
@@ -155,6 +156,13 @@ class LandedCost(models.Model):
|
||||
move = move.create(move_vals)
|
||||
cost.write({'state': 'done', 'account_move_id': move.id})
|
||||
move.post()
|
||||
|
||||
if cost.vendor_bill_id and cost.vendor_bill_id.state == 'posted' and cost.company_id.anglo_saxon_accounting:
|
||||
all_amls = cost.vendor_bill_id.line_ids | cost.account_move_id.line_ids
|
||||
for product in cost.cost_lines.product_id:
|
||||
accounts = product.product_tmpl_id.get_product_accounts()
|
||||
input_account = accounts['stock_input']
|
||||
all_amls.filtered(lambda aml: aml.account_id == input_account).reconcile()
|
||||
return True
|
||||
|
||||
def _check_sum(self):
|
||||
@@ -285,7 +293,8 @@ class LandedCostLine(models.Model):
|
||||
self.name = self.product_id.name or ''
|
||||
self.split_method = 'equal'
|
||||
self.price_unit = self.product_id.standard_price or 0.0
|
||||
self.account_id = self.product_id.property_account_expense_id.id or self.product_id.categ_id.property_account_expense_categ_id.id
|
||||
accounts_data = self.product_id.product_tmpl_id.get_product_accounts()
|
||||
self.account_id = accounts_data['stock_input']
|
||||
|
||||
|
||||
class AdjustmentLines(models.Model):
|
||||
@@ -341,7 +350,7 @@ class AdjustmentLines(models.Model):
|
||||
if self.move_id._is_dropshipped():
|
||||
debit_account_id = accounts.get('expense') and accounts['expense'].id or False
|
||||
already_out_account_id = accounts['stock_output'].id
|
||||
credit_account_id = self.cost_line_id.account_id.id or cost_product.property_account_expense_id.id or cost_product.categ_id.property_account_expense_categ_id.id
|
||||
credit_account_id = self.cost_line_id.account_id.id or cost_product.categ_id.property_stock_account_input_categ_id.id
|
||||
|
||||
if not credit_account_id:
|
||||
raise UserError(_('Please configure Stock Expense Account for product: %s.') % (cost_product.name))
|
||||
@@ -394,12 +403,12 @@ class AdjustmentLines(models.Model):
|
||||
AccountMoveLine.append([0, 0, debit_line])
|
||||
AccountMoveLine.append([0, 0, credit_line])
|
||||
|
||||
# TDE FIXME: oh dear
|
||||
if self.env.company.anglo_saxon_accounting:
|
||||
expense_account_id = self.product_id.product_tmpl_id.get_product_accounts()['expense'].id
|
||||
debit_line = dict(base_line,
|
||||
name=(self.name + ": " + str(qty_out) + _(' already out')),
|
||||
quantity=0,
|
||||
account_id=credit_account_id)
|
||||
account_id=expense_account_id)
|
||||
credit_line = dict(base_line,
|
||||
name=(self.name + ": " + str(qty_out) + _(' already out')),
|
||||
quantity=0,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.addons.stock_account.tests.test_stockvaluationlayer import TestStockValuationCommon
|
||||
from odoo.addons.stock_account.tests.test_stockvaluation import _create_accounting_data
|
||||
|
||||
|
||||
class TestStockValuationLC(TestStockValuationCommon):
|
||||
@@ -16,11 +17,55 @@ class TestStockValuationLC(TestStockValuationCommon):
|
||||
'type': 'service',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
})
|
||||
cls.stock_input_account, cls.stock_output_account, cls.stock_valuation_account, cls.expense_account, cls.stock_journal = _create_accounting_data(cls.env)
|
||||
cls.product1.write({
|
||||
'property_account_expense_id': cls.expense_account.id,
|
||||
})
|
||||
cls.productlc1.write({
|
||||
'property_account_expense_id': cls.expense_account.id,
|
||||
})
|
||||
cls.product1.categ_id.write({
|
||||
'property_stock_account_input_categ_id': cls.stock_input_account.id,
|
||||
'property_stock_account_output_categ_id': cls.stock_output_account.id,
|
||||
'property_stock_valuation_account_id': cls.stock_valuation_account.id,
|
||||
'property_stock_journal': cls.stock_journal.id,
|
||||
})
|
||||
cls.payable_account = cls.env['account.account'].create({
|
||||
'name': 'payable',
|
||||
'code': 'payable',
|
||||
'user_type_id': cls.env.ref('account.data_account_type_payable').id,
|
||||
'reconcile': True,
|
||||
})
|
||||
|
||||
def _get_stock_input_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.stock_input_account.id),
|
||||
], order='date, id')
|
||||
|
||||
def _get_stock_output_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.stock_output_account.id),
|
||||
], order='date, id')
|
||||
|
||||
def _get_stock_valuation_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.stock_valuation_account.id),
|
||||
], order='date, id')
|
||||
|
||||
def _get_payable_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.payable_account.id),
|
||||
], order='id')
|
||||
|
||||
def _get_expense_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.expense_account.id),
|
||||
], order='id')
|
||||
|
||||
def _make_lc(self, move, amount):
|
||||
picking = move.picking_id
|
||||
lc = Form(self.env['stock.landed.cost'])
|
||||
lc.account_journal_id = self.env['account.journal'].search([('name', 'ilike', '%misc%')], limit=1)
|
||||
lc.account_journal_id = self.stock_journal
|
||||
lc.picking_ids.add(move.picking_id)
|
||||
with lc.cost_lines.new() as cost_line:
|
||||
cost_line.product_id = self.productlc1
|
||||
@@ -31,7 +76,6 @@ class TestStockValuationLC(TestStockValuationCommon):
|
||||
return lc
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestStockValuationLCFIFO(TestStockValuationLC):
|
||||
def setUp(self):
|
||||
super(TestStockValuationLCFIFO, self).setUp()
|
||||
@@ -96,7 +140,6 @@ class TestStockValuationLCFIFO(TestStockValuationLC):
|
||||
self.assertEqual(in_svl.value, 225)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestStockValuationLCAVCO(TestStockValuationLC):
|
||||
def setUp(self):
|
||||
super(TestStockValuationLCAVCO, self).setUp()
|
||||
@@ -137,3 +180,189 @@ class TestStockValuationLCAVCO(TestStockValuationLC):
|
||||
|
||||
self.assertEqual(self.product1.value_svl, 375)
|
||||
self.assertEqual(self.product1.quantity_svl, 19)
|
||||
|
||||
|
||||
class TestStockValuationLCFIFOVB(TestStockValuationLC):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestStockValuationLCFIFOVB, cls).setUpClass()
|
||||
cls.vendor1 = cls.env['res.partner'].search([], limit=1)
|
||||
cls.vendor1.property_account_payable_id = cls.payable_account
|
||||
cls.vendor2 = cls.env['res.partner'].search([], limit=2)[-1]
|
||||
cls.vendor2.property_account_payable_id = cls.payable_account
|
||||
cls.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
||||
cls.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
||||
|
||||
def test_vendor_bill_flow_anglo_saxon_1(self):
|
||||
"""In anglo saxon accounting, receive 10@10 and invoice. Then invoice 1@50 as a landed costs
|
||||
and create a linked landed costs record.
|
||||
"""
|
||||
self.env.company.anglo_saxon_accounting = True
|
||||
|
||||
# Create an RFQ for self.product1, 10@10
|
||||
rfq = Form(self.env['purchase.order'])
|
||||
rfq.partner_id = self.vendor1
|
||||
|
||||
with rfq.order_line.new() as po_line:
|
||||
po_line.product_id = self.product1
|
||||
po_line.price_unit = 10
|
||||
po_line.product_qty = 10
|
||||
po_line.taxes_id.clear()
|
||||
|
||||
rfq = rfq.save()
|
||||
rfq.button_confirm()
|
||||
|
||||
# Process the receipt
|
||||
receipt = rfq.picking_ids
|
||||
wiz = receipt.button_validate()
|
||||
wiz = self.env['stock.immediate.transfer'].browse(wiz['res_id']).process()
|
||||
self.assertEqual(rfq.order_line.qty_received, 10)
|
||||
|
||||
input_aml = self._get_stock_input_move_lines()[-1]
|
||||
self.assertEqual(input_aml.debit, 0)
|
||||
self.assertEqual(input_aml.credit, 100)
|
||||
valuation_aml = self._get_stock_valuation_move_lines()[-1]
|
||||
self.assertEqual(valuation_aml.debit, 100)
|
||||
self.assertEqual(valuation_aml.credit, 0)
|
||||
|
||||
# Create a vebdor bill for the RFQ
|
||||
action = rfq.action_view_invoice()
|
||||
vb = Form(self.env['account.move'].with_context(action['context']))
|
||||
vb = vb.save()
|
||||
vb.post()
|
||||
|
||||
input_aml = self._get_stock_input_move_lines()[-1]
|
||||
self.assertEqual(input_aml.debit, 100)
|
||||
self.assertEqual(input_aml.credit, 0)
|
||||
payable_aml = self._get_payable_move_lines()[-1]
|
||||
self.assertEqual(payable_aml.debit, 0)
|
||||
self.assertEqual(payable_aml.credit, 100)
|
||||
|
||||
# Create a vendor bill for a landed cost product, post it and validate a landed cost
|
||||
# linked to this vendor bill. LC; 1@50
|
||||
lcvb = Form(self.env['account.move'].with_context(default_type='in_invoice'))
|
||||
lcvb.partner_id = self.vendor2
|
||||
with lcvb.invoice_line_ids.new() as inv_line:
|
||||
inv_line.product_id = self.productlc1
|
||||
inv_line.price_unit = 50
|
||||
inv_line.is_landed_costs_line = True
|
||||
with lcvb.invoice_line_ids.edit(0) as inv_line:
|
||||
inv_line.tax_ids.clear()
|
||||
lcvb = lcvb.save()
|
||||
lcvb.post()
|
||||
|
||||
input_aml = self._get_stock_input_move_lines()[-1]
|
||||
self.assertEqual(input_aml.debit, 50)
|
||||
self.assertEqual(input_aml.credit, 0)
|
||||
payable_aml = self._get_payable_move_lines()[-1]
|
||||
self.assertEqual(payable_aml.debit, 0)
|
||||
self.assertEqual(payable_aml.credit, 50)
|
||||
|
||||
action = lcvb.button_create_landed_costs()
|
||||
lc = Form(self.env[action['res_model']].browse(action['res_id']))
|
||||
lc.picking_ids.add(receipt)
|
||||
lc = lc.save()
|
||||
lc.button_validate()
|
||||
|
||||
self.assertEqual(lc.cost_lines.price_unit, 50)
|
||||
self.assertEqual(lc.cost_lines.product_id, self.productlc1)
|
||||
|
||||
input_aml = self._get_stock_input_move_lines()[-1]
|
||||
self.assertEqual(input_aml.debit, 0)
|
||||
self.assertEqual(input_aml.credit, 50)
|
||||
valuation_aml = self._get_stock_valuation_move_lines()[-1]
|
||||
self.assertEqual(valuation_aml.debit, 50)
|
||||
self.assertEqual(valuation_aml.credit, 0)
|
||||
|
||||
# Check reconciliation of input aml of lc
|
||||
lc_input_aml = lc.account_move_id.line_ids.filtered(lambda aml: aml.account_id == self.stock_input_account)
|
||||
self.assertTrue(len(lc_input_aml.full_reconcile_id), 1)
|
||||
|
||||
self.assertEqual(self.product1.quantity_svl, 10)
|
||||
self.assertEqual(self.product1.value_svl, 150)
|
||||
|
||||
def test_vendor_bill_flow_continental_1(self):
|
||||
"""In continental accounting, receive 10@10 and invoice. Then invoice 1@50 as a landed costs
|
||||
and create a linked landed costs record.
|
||||
"""
|
||||
self.env.company.anglo_saxon_accounting = False
|
||||
|
||||
# Create an RFQ for self.product1, 10@10
|
||||
rfq = Form(self.env['purchase.order'])
|
||||
rfq.partner_id = self.vendor1
|
||||
|
||||
with rfq.order_line.new() as po_line:
|
||||
po_line.product_id = self.product1
|
||||
po_line.price_unit = 10
|
||||
po_line.product_qty = 10
|
||||
po_line.taxes_id.clear()
|
||||
|
||||
rfq = rfq.save()
|
||||
rfq.button_confirm()
|
||||
|
||||
# Process the receipt
|
||||
receipt = rfq.picking_ids
|
||||
wiz = receipt.button_validate()
|
||||
wiz = self.env['stock.immediate.transfer'].browse(wiz['res_id']).process()
|
||||
self.assertEqual(rfq.order_line.qty_received, 10)
|
||||
|
||||
input_aml = self._get_stock_input_move_lines()[-1]
|
||||
self.assertEqual(input_aml.debit, 0)
|
||||
self.assertEqual(input_aml.credit, 100)
|
||||
valuation_aml = self._get_stock_valuation_move_lines()[-1]
|
||||
self.assertEqual(valuation_aml.debit, 100)
|
||||
self.assertEqual(valuation_aml.credit, 0)
|
||||
|
||||
# Create a vebdor bill for the RFQ
|
||||
action = rfq.action_view_invoice()
|
||||
vb = Form(self.env['account.move'].with_context(action['context']))
|
||||
vb = vb.save()
|
||||
vb.post()
|
||||
|
||||
expense_aml = self._get_expense_move_lines()[-1]
|
||||
self.assertEqual(expense_aml.debit, 100)
|
||||
self.assertEqual(expense_aml.credit, 0)
|
||||
|
||||
payable_aml = self._get_payable_move_lines()[-1]
|
||||
self.assertEqual(payable_aml.debit, 0)
|
||||
self.assertEqual(payable_aml.credit, 100)
|
||||
|
||||
# Create a vendor bill for a landed cost product, post it and validate a landed cost
|
||||
# linked to this vendor bill. LC; 1@50
|
||||
lcvb = Form(self.env['account.move'].with_context(default_type='in_invoice'))
|
||||
lcvb.partner_id = self.vendor2
|
||||
with lcvb.invoice_line_ids.new() as inv_line:
|
||||
inv_line.product_id = self.productlc1
|
||||
inv_line.price_unit = 50
|
||||
inv_line.is_landed_costs_line = True
|
||||
with lcvb.invoice_line_ids.edit(0) as inv_line:
|
||||
inv_line.tax_ids.clear()
|
||||
lcvb = lcvb.save()
|
||||
lcvb.post()
|
||||
|
||||
expense_aml = self._get_expense_move_lines()[-1]
|
||||
self.assertEqual(expense_aml.debit, 50)
|
||||
self.assertEqual(expense_aml.credit, 0)
|
||||
payable_aml = self._get_payable_move_lines()[-1]
|
||||
self.assertEqual(payable_aml.debit, 0)
|
||||
self.assertEqual(payable_aml.credit, 50)
|
||||
|
||||
action = lcvb.button_create_landed_costs()
|
||||
lc = Form(self.env[action['res_model']].browse(action['res_id']))
|
||||
lc.picking_ids.add(receipt)
|
||||
lc = lc.save()
|
||||
lc.button_validate()
|
||||
|
||||
self.assertEqual(lc.cost_lines.price_unit, 50)
|
||||
self.assertEqual(lc.cost_lines.product_id, self.productlc1)
|
||||
|
||||
input_aml = self._get_stock_input_move_lines()[-1]
|
||||
self.assertEqual(input_aml.debit, 0)
|
||||
self.assertEqual(input_aml.credit, 50)
|
||||
valuation_aml = self._get_stock_valuation_move_lines()[-1]
|
||||
self.assertEqual(valuation_aml.debit, 50)
|
||||
self.assertEqual(valuation_aml.credit, 0)
|
||||
|
||||
self.assertEqual(self.product1.quantity_svl, 10)
|
||||
self.assertEqual(self.product1.value_svl, 150)
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="account_view_move_form_inherited" model="ir.ui.view">
|
||||
<field name="name">account.view.move.form.inherited</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<field name="landed_costs_ids" invisible="1"/>
|
||||
<button string="Landed Costs" type="object"
|
||||
name="action_view_landed_costs"
|
||||
class="oe_stat_button" icon="fa-plus-square" groups="stock.group_stock_manager"
|
||||
attrs="{'invisible': [('landed_costs_ids', '=', [])]}" />
|
||||
</xpath>
|
||||
|
||||
<field name="state" position="before">
|
||||
<field name="landed_costs_visible" invisible="1"/>
|
||||
<button name="button_create_landed_costs" class="oe_highlight" string="Create Landed Costs" type="object" groups="account.group_account_invoice" attrs="{'invisible': [('landed_costs_visible', '!=', True)]}"/>
|
||||
</field>
|
||||
|
||||
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='name']" position="after">
|
||||
<field name="product_type" invisible="1"/>
|
||||
<field name="is_landed_costs_line" string="Landed Costs" attrs="{'readonly': [('product_type', '!=', 'service')], 'column_invisible': [('parent.type', '!=', 'in_invoice')]}" optional="show"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='name']" position="after">
|
||||
<field name="product_type" invisible="1"/>
|
||||
<field name="is_landed_costs_line" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -34,6 +34,7 @@
|
||||
<label for="account_journal_id" string="Journal"/>
|
||||
<field name="account_journal_id" nolabel="1"/>
|
||||
<field name="account_move_id" attrs="{'invisible': [('account_move_id', '=', False)]}"/>
|
||||
<field name="vendor_bill_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
@@ -119,6 +120,22 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='view_stock_landed_cost_tree2' model='ir.ui.view'>
|
||||
<field name="name">stock.landed.cost.tree</field>
|
||||
<field name="model">stock.landed.cost</field>
|
||||
<field name="priority">1000</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Landed Costs">
|
||||
<field name="name"/>
|
||||
<field name="date"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="amount_total" widget="monetary"/>
|
||||
<field name="state"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='stock_landed_cost_view_kanban' model='ir.ui.view'>
|
||||
<field name="name">stock.landed.cost.kanban</field>
|
||||
<field name="model">stock.landed.cost</field>
|
||||
|
||||
Reference in New Issue
Block a user