Invoice shipped product instead of sold product
issue6841 review43601002
This commit is contained in:
1 parent
fec7e94ee3
commit
3d8aa609b8
5 files changed
+181
-35
No files matched your search
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
)
|
||||
@@ -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'
|
||||
@@ -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
|
||||
Reference in new issue
Block a user