[FIX] point_of_sale, stock_account: POS Anglo-Saxon Accounting

Anglo-Saxon Accounting entries are not created when the POS session
was closed with button "Close and post entries

opw:802727
This commit is contained in:
Goffin Simon
2018-02-13 15:46:21 +01:00
parent 79f137f342
commit 2d73ea9bed
6 changed files with 240 additions and 68 deletions
+25 -18
View File
@@ -859,24 +859,7 @@ class AccountInvoice(models.Model):
@api.model
def line_get_convert(self, line, part):
return {
'date_maturity': line.get('date_maturity', False),
'partner_id': part,
'name': line['name'],
'debit': line['price'] > 0 and line['price'],
'credit': line['price'] < 0 and -line['price'],
'account_id': line['account_id'],
'analytic_line_ids': line.get('analytic_line_ids', []),
'amount_currency': line['price'] > 0 and abs(line.get('amount_currency', False)) or -abs(line.get('amount_currency', False)),
'currency_id': line.get('currency_id', False),
'quantity': line.get('quantity', 1.00),
'product_id': line.get('product_id', False),
'product_uom_id': line.get('uom_id', False),
'analytic_account_id': line.get('account_analytic_id', False),
'invoice_id': line.get('invoice_id', False),
'tax_ids': line.get('tax_ids', False),
'tax_line_id': line.get('tax_line_id', False),
}
return self.env['product.product']._convert_prepared_anglosaxon_line(line, part)
@api.multi
def action_cancel(self):
@@ -1455,3 +1438,27 @@ class MailComposeMessage(models.Model):
invoice.sent = True
invoice.message_post(body=_("Invoice sent"))
return super(MailComposeMessage, self).send_mail(auto_commit=auto_commit)
class ProductProduct(models.Model):
_inherit = "product.product"
@api.model
def _convert_prepared_anglosaxon_line(self, line, partner):
return {
'date_maturity': line.get('date_maturity', False),
'partner_id': partner,
'name': line['name'],
'debit': line['price'] > 0 and line['price'],
'credit': line['price'] < 0 and -line['price'],
'account_id': line['account_id'],
'analytic_line_ids': line.get('analytic_line_ids', []),
'amount_currency': line['price'] > 0 and abs(line.get('amount_currency', False)) or -abs(line.get('amount_currency', False)),
'currency_id': line.get('currency_id', False),
'quantity': line.get('quantity', 1.00),
'product_id': line.get('product_id', False),
'product_uom_id': line.get('uom_id', False),
'analytic_account_id': line.get('account_analytic_id', False),
'invoice_id': line.get('invoice_id', False),
'tax_ids': line.get('tax_ids', False),
'tax_line_id': line.get('tax_line_id', False),
}
+1
View File
@@ -9,3 +9,4 @@ import res_users
import res_partner
import wizard
import res_config
import tests
+36
View File
@@ -1319,7 +1319,40 @@ class pos_order(osv.osv):
grouped_data = {}
have_to_group_by = session and session.config_id.group_by or False
rounding_method = session and session.config_id.company_id.tax_calculation_rounding_method
def add_anglosaxon_lines(grouped_data):
Product = self.pool['product.product']
Analytic = self.pool['account.analytic.account']
for product_key in list(grouped_data.keys()):
if product_key[0] == "product":
line = grouped_data[product_key][0]
product = Product.browse(cr, uid, line['product_id'], context=context)
# In the SO part, the entries will be inverted by function compute_invoice_totals
price_unit = - product._get_anglo_saxon_price_unit()
account_analytic = Analytic.browse(cr, uid, line.get('analytic_account_id'), context=context)
res = Product._anglo_saxon_sale_move_lines(cr, uid,
line['name'], product, product.uom_id, line['quantity'], price_unit,
fiscal_position=order.fiscal_position_id,
account_analytic=account_analytic, context=context)
if res:
line1, line2 = res
line1 = Product._convert_prepared_anglosaxon_line(cr, uid, line1, order.partner_id, context=context)
insert_data('counter_part', {
'name': line1['name'],
'account_id': line1['account_id'],
'credit': line1['credit'] or 0.0,
'debit': line1['debit'] or 0.0,
'partner_id': line1['partner_id']
})
line2 = Product._convert_prepared_anglosaxon_line(cr, uid, line2, order.partner_id, context=context)
insert_data('counter_part', {
'name': line2['name'],
'account_id': line2['account_id'],
'credit': line2['credit'] or 0.0,
'debit': line2['debit'] or 0.0,
'partner_id': line2['partner_id']
})
for order in self.browse(cr, uid, ids, context=context):
if order.account_move:
continue
@@ -1463,6 +1496,9 @@ class pos_order(osv.osv):
order.write({'state':'done', 'account_move': move_id})
if ids and order.company_id.anglo_saxon_accounting:
add_anglosaxon_lines(grouped_data)
all_lines = []
for group_key, group_data in grouped_data.iteritems():
for value in group_data:
+4
View File
@@ -0,0 +1,4 @@
#Accounting tests written in python should extend the class AccountingTestCase.
#See its doc for more info.
from . import test_anglo_saxon
@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
import openerp
import time
from openerp import fields
from openerp.tests import common
class TestAngloSaxonCommon(common.TransactionCase):
def setUp(self):
super(TestAngloSaxonCommon, self).setUp()
self.PosMakePayment = self.env['pos.make.payment']
self.PosOrder = self.env['pos.order']
self.Statement = self.env['account.bank.statement']
self.company = self.env.ref('base.main_company')
self.product = self.env.ref('product.product_product_3')
self.partner = self.env.ref('base.res_partner_1')
self.category = self.env.ref('product.product_category_1')
self.category = self.category.copy({'name': 'New category','property_valuation': 'real_time'})
account_type = self.env['account.account.type'].create({'name': 'RCV type', 'type': 'receivable'})
self.account = self.env['account.account'].create({'name': 'Receivable', 'code': 'RCV00' , 'user_type_id': account_type.id, 'reconcile': True})
account_expense = self.env['account.account'].create({'name': 'Expense', 'code': 'EXP00' , 'user_type_id': account_type.id, 'reconcile': True})
account_output = self.env['account.account'].create({'name': 'Output', 'code': 'OUT00' , 'user_type_id': account_type.id, 'reconcile': True})
self.partner.property_account_receivable_id = self.account
self.category.property_account_income_categ_id = self.account
self.category.property_account_expense_categ_id = account_expense
self.category.property_stock_account_input_categ_id = self.account
self.category.property_stock_account_output_categ_id = account_output
self.category.property_stock_valuation_account_id = self.account
self.category.property_stock_journal = self.env['account.journal'].create({'name': 'Stock journal', 'type': 'sale', 'code': 'STK00'})
self.pos_config = self.env.ref('point_of_sale.pos_config_main')
self.pos_config = self.pos_config.copy({'name': 'New POS config'})
self.product = self.product.copy({'name': 'New product','standard_price': 100})
self.company.anglo_saxon_accounting = True
self.product.categ_id = self.category
sale_journal = self.env['account.journal'].create({'name': 'POS journal', 'type': 'sale', 'code': 'POS00'})
self.pos_config.journal_id = sale_journal
self.cash_journal = self.env['account.journal'].create({'name': 'CASH journal', 'type': 'cash', 'code': 'CSH00'})
class TestAngloSaxonFlow(TestAngloSaxonCommon):
def test_create_account_move_line(self):
# This test will check that the correct journal entries are created when a product in real time valuation
# is sold in a company using anglo-saxon
self.pos_config.open_session_cb()
self.pos_config.current_session_id.write({'journal_ids': [(6, 0, [self.cash_journal.id])]})
self.cash_journal.loss_account_id = self.account
self.pos_statement = self.Statement.create({
'balance_start': 0.0,
'balance_end_real': 0.0,
'date': time.strftime('%Y-%m-%d'),
'journal_id': self.cash_journal.id,
'company_id': self.company.id,
'name': 'pos session test',
})
self.pos_config.current_session_id.write({'statement_ids': [(6, 0, [self.pos_statement.id])]})
# I create a PoS order with 1 unit of New product at 450 EUR
self.pos_order_pos0 = self.PosOrder.create({
'company_id': self.company.id,
'partner_id': self.partner.id,
'pricelist_id': self.company.partner_id.property_product_pricelist.id,
'session_id': self.pos_config.current_session_id.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product.id,
'price_unit': 450,
'discount': 0.0,
'qty': 1.0
})]
})
# I make a payment to fully pay the order
context_make_payment = {"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({
'amount': 450.0
})
# I click on the validate button to register the payment.
context_payment = {'active_id': self.pos_order_pos0.id}
self.pos_make_payment_0.with_context(context_payment).check()
# I check that the order is marked as paid
self.assertEqual(self.pos_order_pos0.state, 'paid', 'Order should be in paid state.')
# I close the current session to generate the journal entries
self.pos_config.current_session_id.wkf_action_close()
# I test that the generated journal entries are correct.
account_output = self.category.property_stock_account_output_categ_id
expense_account = self.category.property_account_expense_categ_id
aml = self.pos_order_pos0.account_move.line_ids
aml_output = aml.filtered(lambda l: l.account_id.id == account_output.id)
aml_expense = aml.filtered(lambda l: l.account_id.id == expense_account.id)
self.assertEqual(aml_output.credit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_expense.debit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
+75 -50
View File
@@ -24,17 +24,78 @@ class stock_inventory(osv.osv):
ctx['force_period_date'] = inv.accounting_date
return super(stock_inventory, self).post_inventory(cr, uid, inv, context=ctx)
class ProductProduct(models.Model):
_inherit = "product.product"
@api.model
def _anglo_saxon_sale_move_lines(self, name, product, uom, qty, price_unit, currency=False, amount_currency=False, fiscal_position=False, account_analytic=False):
"""Prepare dicts describing new journal COGS journal items for a product sale.
Returns a dict that should be passed to `_convert_prepared_anglosaxon_line()` to
obtain the creation value for the new journal items.
:param Model product: a product.product record of the product being sold
:param Model uom: a product.uom record of the UoM of the sale line
:param Integer qty: quantity of the product being sold
:param Integer price_unit: unit price of the product being sold
:param Model currency: a res.currency record from the order of the product being sold
:param Interger amount_currency: unit price in the currency from the order of the product being sold
:param Model fiscal_position: a account.fiscal.position record from the order of the product being sold
:param Model account_analytic: a account.account.analytic record from the line of the product being sold
"""
if product.type == 'product' and product.valuation == 'real_time':
accounts = product.product_tmpl_id.get_product_accounts(fiscal_pos=fiscal_position)
# debit account dacc will be the output account
dacc = accounts['stock_output'].id
# credit account cacc will be the expense account
cacc = accounts['expense'].id
if dacc and cacc:
return [
{
'type': 'src',
'name': name[:64],
'price_unit': price_unit,
'quantity': qty,
'price': price_unit * qty,
'currency_id': currency and currency.id,
'amount_currency': amount_currency,
'account_id': dacc,
'product_id': product.id,
'uom_id': uom.id,
'account_analytic_id': account_analytic and account_analytic.id,
},
{
'type': 'src',
'name': name[:64],
'price_unit': price_unit,
'quantity': qty,
'price': -1 * price_unit * qty,
'currency_id': currency and currency.id,
'amount_currency': -1 * amount_currency,
'account_id': cacc,
'product_id': product.id,
'uom_id': uom.id,
'account_analytic_id': account_analytic and account_analytic.id,
},
]
return []
@api.model
def _get_anglo_saxon_price_unit(self, uom=False):
price = self.standard_price
if not uom or self.uom_id.id == uom.id:
return price
return self.uom_id._compute_price(self.uom_id.id, price, to_uom_id=uom.id)
class account_invoice_line(osv.osv):
_inherit = "account.invoice.line"
def _get_anglo_saxon_price_unit(self):
self.ensure_one()
price = self.product_id.standard_price
if not self.uom_id or self.product_id.uom_id == self.uom_id:
return price
else:
return self.product_id.uom_id._compute_price(self.product_id.uom_id.id, price, to_uom_id=self.uom_id.id)
return self.product_id._get_anglo_saxon_price_unit(uom=self.uom_id)
def _get_price(self, cr, uid, inv, company_currency, i_line, price_unit):
cur_obj = self.pool.get('res.currency')
@@ -72,53 +133,17 @@ class account_invoice(osv.osv):
res: The move line entries produced so far by the parent move_line_get.
"""
inv = i_line.invoice_id
company_currency = inv.company_id.currency_id.id
company_currency = inv.company_id.currency_id
price_unit = i_line._get_anglo_saxon_price_unit()
if inv.currency_id != company_currency:
currency_id = inv.currency_id.id
amount_currency = i_line._get_price(inv, company_currency.id, i_line, price_unit)
else:
currency_id = False
amount_currency = False
if i_line.product_id.type in ('product', 'consu') and i_line.product_id.valuation == 'real_time':
fpos = i_line.invoice_id.fiscal_position_id
accounts = i_line.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=fpos)
# debit account dacc will be the output account
dacc = accounts['stock_output'].id
# credit account cacc will be the expense account
cacc = accounts['expense'].id
if dacc and cacc:
price_unit = i_line._get_anglo_saxon_price_unit()
if inv.currency_id.id != company_currency:
currency_id = inv.currency_id.id
amount_currency = self.env['account.invoice.line']._get_price(inv, company_currency, i_line, price_unit)
else:
currency_id = False
amount_currency = False
return [
{
'type':'src',
'name': i_line.name[:64],
'price_unit': price_unit,
'quantity': i_line.quantity,
'price': price_unit * i_line.quantity,
'currency_id': currency_id,
'amount_currency': amount_currency,
'account_id':dacc,
'product_id':i_line.product_id.id,
'uom_id':i_line.uom_id.id,
'account_analytic_id': i_line.account_analytic_id.id,
},
return self.env['product.product']._anglo_saxon_sale_move_lines(i_line.name, i_line.product_id, i_line.uom_id, i_line.quantity, price_unit, currency=currency_id, amount_currency=amount_currency, fiscal_position=inv.fiscal_position_id, account_analytic=i_line.account_analytic_id)
{
'type':'src',
'name': i_line.name[:64],
'price_unit': price_unit,
'quantity': i_line.quantity,
'price': -1 * price_unit * i_line.quantity,
'currency_id': currency_id,
'amount_currency': -1 * amount_currency,
'account_id':cacc,
'product_id':i_line.product_id.id,
'uom_id':i_line.uom_id.id,
'account_analytic_id': i_line.account_analytic_id.id,
},
]
return []
#----------------------------------------------------------
# Stock Location