Invoice shipped product instead of sold product

issue6841
review43601002
This commit is contained in:
Cédric Krier committed 2017-11-11 16:09:07 +01:00
1 parent fec7e94ee3
commit 3d8aa609b8
5 files changed
+181 -35

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
* Invoice shipped product instead of sold product
Version 4.6.0 - 2017-10-30
* Bug fixes (see mercurial logs for details)
* Plan backorder moves to today
+65 -35
View File
@@ -1,6 +1,7 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
from collections import defaultdict
from decimal import Decimal
from itertools import groupby, chain
from functools import partial
@@ -1344,54 +1345,83 @@ class SaleLine(sequence_ordered(), ModelSQL, ModelView):
'Return a list of invoice lines for sale line'
pool = Pool()
InvoiceLine = pool.get('account.invoice.line')
Product = pool.get('product.product')
Uom = pool.get('product.uom')
AccountConfiguration = pool.get('account.configuration')
account_config = AccountConfiguration(1)
invoice_line = InvoiceLine()
invoice_line.type = self.type
invoice_line.description = self.description
invoice_line.note = self.note
invoice_line.origin = self
def _invoice_line():
invoice_line = InvoiceLine()
invoice_line.type = self.type
invoice_line.description = self.description
invoice_line.note = self.note
invoice_line.origin = self
return invoice_line
if self.type != 'line':
if self._get_invoice_not_line():
return [invoice_line]
return [_invoice_line()]
else:
return []
quantity = (self._get_invoice_line_quantity()
- self._get_invoiced_quantity())
if self.unit:
quantity = self.unit.round(quantity)
invoice_line.quantity = quantity
stock_moves = self._get_invoice_line_moves()
if not invoice_line.quantity:
return []
invoice_line.unit = self.unit
invoice_line.product = self.product
invoice_line.unit_price = self.unit_price
invoice_line.taxes = self.taxes
invoice_line.invoice_type = 'out'
if self.product:
invoice_line.account = self.product.account_revenue_used
if not invoice_line.account:
self.raise_user_error('missing_account_revenue', {
'sale': self.sale.rec_name,
'product': self.product.rec_name,
})
products_quantity = defaultdict(lambda: 0)
if stock_moves and any(m.product != self.product for m in stock_moves):
for move in stock_moves:
move_quantity = move.quantity - move.invoiced_quantity
products_quantity[move.product] += (
Uom.compute_qty(move.uom, move_quantity, self.unit))
assert sum(products_quantity.itervalues()) == quantity
else:
for name in ['default_product_account_revenue',
'default_category_account_revenue']:
invoice_line.account = account_config.get_multivalue(name)
if invoice_line.account:
break
if not invoice_line.account:
self.raise_user_error('missing_default_account_revenue', {
'sale': self.sale.rec_name,
})
invoice_line.stock_moves = self._get_invoice_line_moves()
return [invoice_line]
products_quantity[self.product] = quantity
context = {}
if self.sale.party.lang:
context['language'] = self.sale.party.lang.code
lines = []
for product, quantity in products_quantity.iteritems():
invoice_line = _invoice_line()
if self.unit:
quantity = self.unit.round(quantity)
invoice_line.quantity = quantity
if not invoice_line.quantity:
continue
if product != self.product:
with Transaction().set_context(context):
invoice_line.description = Product(product.id).rec_name
invoice_line.unit = self.unit
invoice_line.product = product
invoice_line.unit_price = self.unit_price
invoice_line.taxes = self.taxes
invoice_line.invoice_type = 'out'
if product:
invoice_line.account = product.account_revenue_used
if not invoice_line.account:
self.raise_user_error('missing_account_revenue', {
'sale': self.sale.rec_name,
'product': product.rec_name,
})
else:
for name in ['default_product_account_revenue',
'default_category_account_revenue']:
invoice_line.account = account_config.get_multivalue(name)
if invoice_line.account:
break
if not invoice_line.account:
self.raise_user_error('missing_default_account_revenue', {
'sale': self.sale.rec_name,
})
invoice_line.stock_moves = [
m for m in stock_moves if m.product == product]
lines.append(invoice_line)
return lines
def _get_invoice_not_line(self):
'Return if the not line should be invoiced'
+1
View File
@@ -126,5 +126,6 @@ setup(name=name,
convert_2to3_doctests=[
'tests/scenario_sale.rst',
'tests/scenario_sale_empty.rst',
'tests/scenario_sale_ship_other_product.rst',
],
)
+108
View File
@@ -0,0 +1,108 @@
================================
Sale Ship Other Product Scenario
================================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model, Wizard
>>> from trytond.tests.tools import activate_modules
>>> from trytond.modules.company.tests.tools import create_company, \
... get_company
>>> from trytond.modules.account.tests.tools import create_fiscalyear, \
... create_chart, get_accounts
Install sale::
>>> config = activate_modules('sale')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
>>> revenue = accounts['revenue']
Create parties::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.save()
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.salable = True
>>> template.list_price = Decimal('10')
>>> template.account_revenue = revenue
>>> product = template.products.new()
>>> template.save()
>>> product1, product2 = template.products
>>> product1.code = '1'
>>> product1.save()
>>> product2.code = '2'
>>> product2.save()
Sale 5 products::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.invoice_method = 'shipment'
>>> line = sale.lines.new()
>>> line.product = product1
>>> line.quantity = 5
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.click('process')
Replace 2 products and ship::
>>> shipment, = sale.shipments
>>> shipment.click('draft')
>>> outgoing_move1, = shipment.outgoing_moves
>>> outgoing_move1.quantity = 3
>>> outgoing_move1.save()
>>> outgoing_move2, = outgoing_move1.duplicate()
>>> outgoing_move2.quantity = 2
>>> outgoing_move2.product = product2
>>> outgoing_move2.save()
>>> shipment.click('wait')
>>> shipment.click('assign_force')
>>> shipment.click('pack')
>>> shipment.click('done')
Check invoice has 2 products::
>>> sale.reload()
>>> invoice, = sale.invoices
>>> len(invoice.lines)
2
>>> line1, = [l for l in invoice.lines if l.product == product1]
>>> line2, = [l for l in invoice.lines if l.product == product2]
>>> line1.quantity
3.0
>>> line1.stock_moves == [outgoing_move1]
True
>>> line1.description
u'[1] product'
>>> line2.quantity
2.0
>>> line2.stock_moves == [outgoing_move2]
True
>>> line2.description
u'[2] product'
+5
View File
@@ -23,4 +23,9 @@ def suite():
tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE,
checker=doctest_checker))
suite.addTests(doctest.DocFileSuite(
'scenario_sale_ship_other_product.rst',
tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE,
checker=doctest_checker))
return suite