mirror of
https://github.com/NaN-tic/trytond-account_invoice_milestone-zz.git
synced 2023-12-14 05:02:58 +01:00
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:
parent
9fa83607a6
commit
0ffadce2ce
12 changed files with 143 additions and 122 deletions
|
@ -17,7 +17,6 @@ def register():
|
|||
AccountInvoiceMilestone,
|
||||
AccountInvoiceMilestoneSaleLine,
|
||||
AccountInvoiceMilestoneRemainderSale,
|
||||
StockMove,
|
||||
Sale,
|
||||
SaleLine,
|
||||
Invoice,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
90
milestone.py
90
milestone.py
|
@ -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)
|
||||
|
|
|
@ -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
70
sale.py
|
@ -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
34
sale.xml
Normal 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>
|
|
@ -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')
|
||||
|
|
|
@ -7,3 +7,4 @@ extra_depends:
|
|||
commission_party
|
||||
xml:
|
||||
milestone.xml
|
||||
sale.xml
|
||||
|
|
|
@ -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
8
view/sale_line_tree.xml
Normal 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>
|
8
view/sale_line_tree_sequence.xml
Normal file
8
view/sale_line_tree_sequence.xml
Normal 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>
|
Loading…
Reference in a new issue