Replace moves_to_invoice by sale_lines_to_invoice

Also added quantity_to_invoice functional field to sale lines.
Fill sale_lines_to_invoice with sale's lines when milestone group is computed
on sale confirmation
This commit is contained in:
Guillem Barba 2015-03-10 16:03:17 +01:00
parent 9fa83607a6
commit 0ffadce2ce
12 changed files with 143 additions and 122 deletions

View file

@ -17,7 +17,6 @@ def register():
AccountInvoiceMilestone,
AccountInvoiceMilestoneSaleLine,
AccountInvoiceMilestoneRemainderSale,
StockMove,
Sale,
SaleLine,
Invoice,

View file

@ -262,9 +262,9 @@ msgctxt "field:account.invoice.milestone,months:"
msgid "Number of Months"
msgstr "Número de mesos"
msgctxt "field:account.invoice.milestone,moves_to_invoice:"
msgid "Moves to Invoice"
msgstr "Moviments a facturar"
msgctxt "field:account.invoice.milestone,sale_lines_to_invoice:"
msgid "Sale Lines to Invoice"
msgstr "Línies a facturar"
msgctxt "field:account.invoice.milestone,party:"
msgid "Party"

View file

@ -261,9 +261,9 @@ msgctxt "field:account.invoice.milestone,months:"
msgid "Number of Months"
msgstr "Número de meses"
msgctxt "field:account.invoice.milestone,moves_to_invoice:"
msgid "Moves to Invoice"
msgstr "Movimientos a facturar"
msgctxt "field:account.invoice.milestone,sale_lines_to_invoice:"
msgid "Sale Lines to Invoice"
msgstr "Líneas a facturar"
msgctxt "field:account.invoice.milestone,party:"
msgid "Party"

View file

@ -10,8 +10,7 @@ from trytond.transaction import Transaction
__all__ = ['AccountInvoiceMilestoneGroupType', 'AccountInvoiceMilestoneType',
'AccountInvoiceMilestoneGroup', 'AccountInvoiceMilestone',
'AccountInvoiceMilestoneSaleLine', 'AccountInvoiceMilestoneRemainderSale',
'StockMove']
'AccountInvoiceMilestoneSaleLine', 'AccountInvoiceMilestoneRemainderSale']
__metaclass__ = PoolMeta
@ -295,7 +294,10 @@ class AccountInvoiceMilestoneType(ModelSQL, ModelView):
sale.untaxed_amount * self.percentage)
else:
milestone.invoice_method = self.invoice_method
if self.invoice_method == 'remainder':
if self.invoice_method == 'shipped_goods':
milestone.sale_lines_to_invoice = [l for l in sale.lines
if l.type == 'line']
elif self.invoice_method == 'remainder':
milestone.sales_to_invoice = [sale]
for fname in ('day', 'month', 'weekday', 'months', 'weeks', 'days'):
@ -447,7 +449,7 @@ class AccountInvoiceMilestoneGroup(ModelSQL, ModelView):
continue
if (milestone.state in ('draft', 'confirmed')
and ((milestone.invoice_method == 'shipped_goods'
and not milestone.moves_to_invoice)
and not milestone.sale_lines_to_invoice)
or (milestone.invoice_method == 'remainder'
and not milestone.sales_to_invoice))):
return 'to_assign'
@ -554,17 +556,20 @@ class AccountInvoiceMilestoneGroup(ModelSQL, ModelView):
if ({'amount_to_assign', 'assigned_amount'} & names_set
and milestone.invoice_method == 'shipped_goods'):
for move in milestone.moves_to_invoice:
if (move.state != 'cancel'
and move not in move.origin.moves_ignored
and move not in move.origin.moves_recreated):
sign = (Decimal('1.0')
if move.to_location.type == 'customer'
else Decimal('-1.0'))
move_qty = Uom.compute_qty(move.uom, move.quantity,
move.origin.unit)
res['assigned_amount'] += (Decimal(str(move_qty))
* move.unit_price * sign)
for sale_line in milestone.sale_lines_to_invoice:
for move in sale_line.moves:
if (move.state != 'cancel'
and move not in sale_line.moves_ignored
and move not in sale_line.moves_recreated):
sign = (Decimal('1.0')
if move.to_location.type == 'customer'
else Decimal('-1.0'))
move_qty = Uom.compute_qty(move.uom,
move.quantity, sale_line.unit)
res['assigned_amount'] += (
Decimal(str(move_qty))
* move.unit_price
* sign)
if 'amount_to_invoice' in names:
res['amount_to_invoice'] = (res['total_amount']
@ -750,9 +755,6 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
'on_change_with_currency_digits')
party = fields.Function(fields.Many2One('party.party', 'Party'),
'on_change_with_party', searcher='search_party')
group_sales_moves = fields.Function(fields.Many2Many('stock.move', None,
None, 'Group Sales Moves'),
'on_change_with_group_sales_moves')
code = fields.Char('Code', required=True, readonly=True)
description = fields.Char('Description', states=_STATES, depends=_DEPENDS,
@ -807,9 +809,10 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
'required': Eval('invoice_method') == 'amount',
'invisible': Eval('invoice_method') != 'amount',
}, depends=['currency_digits', 'state', 'invoice_method'])
moves_to_invoice = fields.One2Many('stock.move', 'milestone',
'Moves to Invoice', domain=[
('id', 'in', Eval('group_sales_moves', [])),
sale_lines_to_invoice = fields.One2Many('sale.line', 'milestone',
'Sale Lines to Invoice', domain=[
('type', '=', 'line'),
('sale.milestone_group', '=', Eval('group', -1)),
# company domain is "inherit" from milestone_group
If(~Bool(Eval('invoice', 0)),
('invoice_lines', '=', None),
@ -825,7 +828,7 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
(Eval('invoice_method') == 'shipped_goods')),
'invisible': Eval('invoice_method') != 'shipped_goods',
},
depends=['invoice', 'group_sales_moves', 'state', 'invoice_method'])
depends=['invoice', 'group', 'state', 'invoice_method'])
sales_to_invoice = fields.Many2Many(
'account.invoice.milestone-remainder-sale.sale', 'milestone', 'sale',
'Sales to Invoice', domain=[
@ -971,12 +974,6 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
def search_party(cls, name, clause):
return [('group.party',) + tuple(clause[1:])]
@fields.depends('group')
def on_change_with_group_sales_moves(self, name=None):
if self.group:
return [m.id for s in self.group.sales for m in s.moves]
return []
@staticmethod
def default_state():
return 'draft'
@ -1153,7 +1150,7 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
lines.append(line)
else:
if self.invoice_method == 'shipped_goods':
lines += self._get_moves_invoice_lines()
lines += self._get_shipped_goods_invoice_lines()
else: # remainder
for sale in self.sales_to_invoice:
inv_line_desc = self.calc_invoice_line_description([sale])
@ -1215,28 +1212,9 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
return invoice_line
def _get_moves_invoice_lines(self):
pool = Pool()
SaleLine = pool.get('sale.line')
moves_by_sale_line = {}
for move in self.moves_to_invoice:
if move.state == 'cancel':
continue
if move.state != 'done':
self.raise_user_error('invoice_not_done_move', {
'milestone': self.rec_name,
'move': move.rec_name,
})
if not move.origin or not isinstance(move.origin, SaleLine):
raise NotImplementedError()
moves_by_sale_line.setdefault(move.origin, []).append(move)
def _get_shipped_goods_invoice_lines(self):
invoice_lines = []
for sale_line, moves in moves_by_sale_line.iteritems():
old_sale_line_moves = sale_line.moves
sale_line.moves = moves
for sale_line in self.sale_lines_to_invoice:
invoice_type = ('out_credit_note' if sale_line.quantity < 0.0
else 'out_invoice')
inv_line_desc = self.calc_invoice_line_description(
@ -1244,7 +1222,6 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
with Transaction().set_context(
milestone_invoice_line_description=inv_line_desc):
invoice_lines += sale_line.get_invoice_line(invoice_type)
sale_line.moves = old_sale_line_moves
return invoice_lines
def calc_invoice_line_description(self, sales):
@ -1252,8 +1229,6 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
or ('sale_reference' not in self.description
and 'sale_description' not in self.description)):
return self.description
sales = list(set(m.origin.sale for m in self.moves_to_invoice))
if not sales:
return self.description
@ -1317,7 +1292,7 @@ class AccountInvoiceMilestone(Workflow, ModelSQL, ModelView):
default.setdefault('code', None)
default.setdefault('processed_date', None)
default.setdefault('trigger_lines', [])
default.setdefault('moves_to_invoice', [])
default.setdefault('sale_lines_to_invoice', [])
default.setdefault('sales_to_invoice', [])
default.setdefault('planned_invoice_date', None)
default.setdefault('invoice', None)
@ -1357,10 +1332,3 @@ class AccountInvoiceMilestoneRemainderSale(ModelSQL):
select=True)
sale = fields.Many2One('sale.sale', 'Sale', ondelete='CASCADE',
required=True, select=True)
class StockMove:
__name__ = 'stock.move'
milestone = fields.Many2One('account.invoice.milestone', 'Milestone',
readonly=True)

View file

@ -426,21 +426,6 @@
<field name="name">invoice_form</field>
</record>
<!-- sale.sale -->
<record model="ir.ui.view" id="sale_sale_view_form">
<field name="model">sale.sale</field>
<field name="type">form</field>
<field name="inherit" ref="sale.sale_view_form"/>
<field name="name">sale_sale_form</field>
</record>
<record model="ir.ui.view" id="sale_sale_view_list">
<field name="model">sale.sale</field>
<field name="type">tree</field>
<field name="inherit" ref="sale.sale_view_tree"/>
<field name="name">sale_sale_list</field>
</record>
<!-- Menus -->
<menuitem id="menu_milestone_configuration" name="Milestones"
parent="account.menu_account_configuration" sequence="30"/>

70
sale.py
View file

@ -139,6 +139,46 @@ class Sale:
class SaleLine:
__name__ = 'sale.line'
milestone = fields.Many2One('account.invoice.milestone', 'Milestone',
readonly=True)
quantity_to_invoice = fields.Function(fields.Float('Quantity to invoice',
digits=(16, Eval('unit_digits', 2)),
states={
'invisible': Eval('type') != 'line',
},
depends=['type', 'unit_digits']),
'get_quantity_to_invoice')
def get_quantity_to_invoice(self, name):
pool = Pool()
Uom = pool.get('product.uom')
if (self.sale.invoice_method == 'order'
or not self.product
or self.product.type == 'service'):
quantity = abs(self.quantity)
else:
quantity = 0.0
for move in self.moves:
if move.state == 'done':
quantity += Uom.compute_qty(move.uom, move.quantity,
self.unit)
invoice_type = ('out_invoice' if self.quantity >= 0
else 'out_credit_note')
skip_ids = set(l.id for i in self.sale.invoices_recreated
for l in i.lines)
for invoice_line in self.invoice_lines:
if invoice_line.type != 'line':
continue
if invoice_line.id not in skip_ids:
sign = (1.0 if invoice_type == invoice_line.invoice_type
else -1.0)
quantity -= Uom.compute_qty(invoice_line.unit,
sign * invoice_line.quantity, self.unit)
rounding = self.unit.rounding if self.unit else 0.01
return Uom.round(quantity, rounding)
@property
def shipped_amount(self):
@ -164,17 +204,6 @@ class SaleLine:
l.description = line_description
return res
def get_move(self, shipment_type):
move = super(SaleLine, self).get_move(shipment_type)
if move and self.sale.milestone_group:
if self.moves:
milestones = list(set(m.milestone for m in self.moves
if m.milestone))
if (len(milestones) == 1
and milestones[0].state in ('draft', 'confirmed')):
move.milestone = milestones[0]
return move
@classmethod
def write(cls, *args):
pool = Pool()
@ -185,21 +214,14 @@ class SaleLine:
for records, values in zip(actions, actions):
for action, moves_ignored in values.get('moves_ignored', []):
if action == 'add' and moves_ignored:
sale_lines_to_check += [r.id for r in records]
sale_lines_to_check += [r.id for r in records
if r.milestone]
super(SaleLine, cls).write(*args)
if sale_lines_to_check:
milestones_done = []
milestones_to_cancel = []
milestones_to_cancel = set()
for sale_line in cls.browse(list(set(sale_lines_to_check))):
for move in sale_line.moves_ignored:
if (not move.milestone
or move.milestone.id in milestones_done):
continue
milestones_done.append(move.milestone.id)
if all(m in m.origin.moves_ignored
for m in move.milestone.moves_to_invoice):
# all moves_to_invoice are ignored
milestones_to_cancel.append(move.milestone)
if sale_line.quantity_to_invoice <= 0:
milestones_to_cancel.add(sale_line.milestone)
if milestones_to_cancel:
Milestone.cancel(milestones_to_cancel)
Milestone.cancel(list(milestones_to_cancel))

34
sale.xml Normal file
View file

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<!-- sale.sale -->
<record model="ir.ui.view" id="sale_sale_view_form">
<field name="model">sale.sale</field>
<field name="type">form</field>
<field name="inherit" ref="sale.sale_view_form"/>
<field name="name">sale_sale_form</field>
</record>
<record model="ir.ui.view" id="sale_sale_view_list">
<field name="model">sale.sale</field>
<field name="type">tree</field>
<field name="inherit" ref="sale.sale_view_tree"/>
<field name="name">sale_sale_list</field>
</record>
<!-- sale.line -->
<record model="ir.ui.view" id="sale_line_view_tree">
<field name="model">sale.line</field>
<field name="inherit" ref="sale.sale_line_view_tree"/>
<field name="name">sale_line_tree</field>
</record>
<record model="ir.ui.view" id="sale_line_view_tree_sequence">
<field name="model">sale.line</field>
<field name="inherit" ref="sale.sale_line_view_tree_sequence"/>
<field name="name">sale_line_tree_sequence</field>
</record>
</data>
</tryton>

View file

@ -350,8 +350,8 @@ Create a second milestone based on the shipment of goods::
>>> second_milestone = group.lines.new()
>>> second_milestone.invoice_method = 'goods'
>>> second_milestone.trigger = 'manual'
>>> second_milestone.moves_to_invoice.append(
... SaleLine(goods_line.moves[0].id))
>>> second_milestone.sale_lines_to_invoice.append(
... SaleLine(goods_line.id))
>>> group.save()
>>> group.reload()
>>> group.amount_to_assign
@ -584,10 +584,8 @@ Make a partial sale with milestone and close the milestone::
u'goods'
>>> new_milestone.trigger
u'system'
>>> stock_move, = new_milestone.moves_to_invoice
>>> stock_move.state
u'draft'
>>> stock_move.quantity
>>> sale_line, = new_milestone.sale_lines_to_invoice
>>> sale_line.quantity
10.0
Make a partial sale with milestone and check invoices are correctly linked
@ -651,7 +649,7 @@ to stock moves::
>>> sorted([m.state for m in group.lines])
[u'processing', u'processing']
>>> _, second_milestone = group.lines
>>> len(second_milestone.moves_to_invoice)
>>> len(second_milestone.sale_lines_to_invoice)
2
>>> _, new_shipment = sale.shipments
>>> new_shipment.click('wait')
@ -673,13 +671,11 @@ to stock moves::
u'goods'
>>> new_milestone.invoice == new_invoice
True
>>> move, = new_milestone.moves_to_invoice
>>> move.quantity
>>> sale_line, = new_milestone.sale_lines_to_invoice
>>> sale_line.quantity
15.0
>>> move.product == product
>>> sale_line.product == product
True
>>> move.state
u'done'
Create a milestone group type with there diferent milestone types::
@ -871,8 +867,8 @@ Create a new milestone to invoice it::
>>> milestone = group.lines.new()
>>> milestone.kind = 'manual'
>>> milestone.invoice_method = 'goods'
>>> milestone.moves_to_invoice.extend([Move(x.id)
... for x in return_sale.moves])
>>> milestone.sale_lines_to_invoice.extend([SaleLine(x.id)
... for x in return_sale.lines if x.type == 'line'])
>>> group.save()
>>> _, _, _, milestone = group.lines
>>> milestone.click('confirm')

View file

@ -7,3 +7,4 @@ extra_depends:
commission_party
xml:
milestone.xml
sale.xml

View file

@ -29,7 +29,7 @@
<field name="invoice_method"/>
<label name="amount"/>
<field name="amount"/>
<field name="moves_to_invoice" colspan="4"/>
<field name="sale_lines_to_invoice" colspan="4"/>
<field name="sales_to_invoice" colspan="4"/>
<group id="planned_invoice_date_calculator" colspan="2" col="4"

8
view/sale_line_tree.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='quantity']" position="after">
<field name="quantity_to_invoice"/>
</xpath>
</data>

View file

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='quantity']" position="after">
<field name="quantity_to_invoice"/>
</xpath>
</data>