changeset 4f609f29aa70 in modules/sale_complaint:default
details:
https://hg.tryton.org/modules/sale_complaint?cmd=changeset;node=4f609f29aa70
description:
Allow partial return sale and credit invoice
issue9204
review309251002
diffstat:
CHANGELOG | 1 +
complaint.py | 160 +++++++++++++++++++++++++++++++------
complaint.xml | 80 +++++++++++++++++++
tests/scenario_sale_complaint.rst | 60 ++++++++++++++
view/action_form.xml | 4 +-
view/action_line_form.xml | 15 +++
view/action_line_list.xml | 10 ++
7 files changed, 299 insertions(+), 31 deletions(-)
diffs (450 lines):
diff -r 604af81f8104 -r 4f609f29aa70 CHANGELOG
--- a/CHANGELOG Mon Apr 13 12:21:55 2020 +0200
+++ b/CHANGELOG Mon Apr 13 17:29:36 2020 +0200
@@ -1,3 +1,4 @@
+* Allow partial return sale and credit invoice
* Allow entering origin before customer
Version 5.4.0 - 2019-11-04
diff -r 604af81f8104 -r 4f609f29aa70 complaint.py
--- a/complaint.py Mon Apr 13 12:21:55 2020 +0200
+++ b/complaint.py Mon Apr 13 17:29:36 2020 +0200
@@ -359,9 +359,8 @@
('credit_note', 'Create Credit Note'),
], 'Action', states=_states)
- sale_lines = fields.Many2Many('sale.complaint.action-sale.line',
- 'action', 'line', 'Sale Lines',
- domain=[('sale', '=', Eval('_parent_complaint', {}).get('origin_id'))],
+ sale_lines = fields.One2Many(
+ 'sale.complaint.action-sale.line', 'action', "Sale Lines",
states={
'invisible': Eval('_parent_complaint', {}
).get('origin_model', 'sale.sale') != 'sale.sale',
@@ -370,11 +369,9 @@
depends=_depends,
help='Leave empty for all lines.')
- invoice_lines = fields.Many2Many(
- 'sale.complaint.action-account.invoice.line', 'action', 'line',
- 'Invoice Lines',
- domain=[('invoice', '=', Eval('_parent_complaint', {}
- ).get('origin_id'))],
+ invoice_lines = fields.One2Many(
+ 'sale.complaint.action-account.invoice.line', 'action',
+ "Invoice Lines",
states={
'invisible': Eval('_parent_complaint', {}
).get('origin_model', 'account.invoice.line'
@@ -391,8 +388,8 @@
unit = fields.Function(fields.Many2One('product.uom', 'Unit',
states=_line_states, depends=_line_depends),
'on_change_with_unit')
- unit_digits = fields.Function(fields.Integer('Unit Digits'),
- 'get_unit_digits')
+ unit_digits = fields.Function(
+ fields.Integer('Unit Digits'), 'on_change_with_unit_digits')
unit_price = fields.Numeric('Unit Price', digits=price_digits,
states=_line_states, depends=_line_depends,
help='Leave empty for the same price.')
@@ -411,8 +408,9 @@
'sale.line', 'account.invoice.line'}):
return self.complaint.origin.unit.id
- @fields.depends('complaint')
- def get_unit_digits(self, name=None):
+ @fields.depends('complaint',
+ '_parent_complaint.origin_model', '_parent_complaint.origin')
+ def on_change_with_unit_digits(self, name=None):
if (self.complaint
and self.complaint.origin_model in {
'sale.line', 'account.invoice.line'}):
@@ -456,7 +454,18 @@
default = {}
if isinstance(self.complaint.origin, Sale):
sale = self.complaint.origin
- sale_lines = self.sale_lines or sale.lines
+ if self.sale_lines:
+ sale_lines = [l.line for l in self.sale_lines]
+ line2qty = {l.line.id: l.quantity
+ if l.quantity is not None else l.line.quantity
+ for l in self.sale_lines}
+ line2price = {l.line.id: l.unit_price
+ if l.unit_price is not None else l.line.unit_price
+ for l in self.sale_lines}
+ default['quantity'] = lambda o: line2qty.get(o['id'])
+ default['unit_price'] = lambda o: line2price.get(o['id'])
+ else:
+ sale_lines = sale.lines
elif isinstance(self.complaint.origin, Line):
sale_line = self.complaint.origin
sale = sale_line.sale
@@ -483,24 +492,35 @@
Line = pool.get('account.invoice.line')
if isinstance(self.complaint.origin, (Invoice, Line)):
+ line2qty = line2price = {}
if isinstance(self.complaint.origin, Invoice):
invoice = self.complaint.origin
- invoice_lines = self.invoice_lines or invoice.lines
+ if self.invoice_lines:
+ invoice_lines = [l.line for l in self.invoice_lines]
+ line2qty = {l.line: l.quantity
+ for l in self.invoice_lines}
+ line2price = {l.line: l.unit_price
+ for l in self.invoice_lines}
+ else:
+ invoice_lines = invoice.lines
elif isinstance(self.complaint.origin, Line):
invoice_line = self.complaint.origin
invoice = invoice_line.invoice
invoice_lines = [invoice_line]
+ if self.quantity is not None:
+ line2qty = {invoice_line: self.quantity}
+ if self.unit_price is not None:
+ line2price = {invoice_line: self.unit_price}
credit_note = invoice._credit()
credit_lines = []
for invoice_line in invoice_lines:
credit_line = invoice_line._credit()
credit_lines.append(credit_line)
credit_line.origin = self.complaint
- if isinstance(self.complaint.origin, Line):
- if self.quantity is not None:
- credit_lines[0].quantity = -self.quantity
- if self.unit_price is not None:
- credit_lines[0].unit_price = self.unit_price
+ if invoice_line in line2qty:
+ credit_line.quantity = -line2qty[invoice_line]
+ if invoice_line in line2price:
+ credit_line.unit_price = line2price[invoice_line]
credit_note.lines = credit_lines
credit_note.taxes = None
credit_note.save()
@@ -519,21 +539,103 @@
super(Action, cls).delete(actions)
-class Action_SaleLine(ModelSQL):
- 'Customer Complaint Action - Sale Line'
- __name__ = 'sale.complaint.action-sale.line'
+class _Action_Line:
+
+ _states = {
+ 'readonly': (
+ (Eval('complaint_state') != 'draft')
+ | Bool(Eval('_parent_action.result', True))),
+ }
+ _depends = ['complaint_state']
action = fields.Many2One('sale.complaint.action', 'Action',
ondelete='CASCADE', select=True, required=True)
- line = fields.Many2One('sale.line', 'Sale Line', ondelete='RESTRICT',
- required=True)
+ quantity = fields.Float(
+ "Quantity",
+ digits=(16, Eval('unit_digits', 2)),
+ states=_states,
+ depends=_depends + ['unit_digits'])
+ unit = fields.Function(
+ fields.Many2One('product.uom', "Unit"), 'on_change_with_unit')
+ unit_digits = fields.Function(
+ fields.Integer("Unit Digits"), 'on_change_with_unit_digits')
+ unit_price = fields.Numeric(
+ "Unit Price", digits=price_digits, states=_states, depends=_depends,
+ help='Leave empty for the same price.')
+
+ complaint_state = fields.Function(
+ fields.Selection('get_complaint_states', "Complaint State"),
+ 'on_change_with_complaint_state')
+ complaint_origin_id = fields.Function(
+ fields.Integer("Complaint Origin ID"),
+ 'on_change_with_complaint_origin_id')
+
+ def on_change_with_unit(self, name=None):
+ raise NotImplementedError
+
+ def on_change_with_unit_digits(self, name=None):
+ raise NotImplementedError
+
+ @classmethod
+ def get_complaint_states(cls):
+ pool = Pool()
+ Complaint = pool.get('sale.complaint')
+ return Complaint.fields_get(['state'])['state']['selection']
+
+ @fields.depends('action', '_parent_action.complaint',
+ '_parent_action._parent_complaint.state')
+ def on_change_with_complaint_state(self, name=None):
+ if self.action and self.action.complaint:
+ return self.action.complaint.state
+
+ @fields.depends('action', '_parent_action.complaint',
+ '_parent_action._parent_complaint.origin_id')
+ def on_change_with_complaint_origin_id(self, name=None):
+ if self.action and self.action.complaint:
+ return self.action.complaint.origin_id
-class Action_InvoiceLine(ModelSQL):
+class Action_SaleLine(_Action_Line, ModelView, ModelSQL):
+ 'Customer Complaint Action - Sale Line'
+ __name__ = 'sale.complaint.action-sale.line'
+
+ line = fields.Many2One(
+ 'sale.line', "Sale Line",
+ ondelete='RESTRICT', required=True,
+ domain=[
+ ('sale', '=', Eval('complaint_origin_id', -1)),
+ ],
+ depends=['complaint_origin_id'])
+
+ @fields.depends('line')
+ def on_change_with_unit(self, name=None):
+ if self.line:
+ return self.line.unit.id
+
+ @fields.depends('line')
+ def on_change_with_unit_digits(self, name=None):
+ if self.line:
+ return self.line.unit.digits
+
+
+class Action_InvoiceLine(_Action_Line, ModelView, ModelSQL):
'Customer Complaint Action - Invoice Line'
__name__ = 'sale.complaint.action-account.invoice.line'
- action = fields.Many2One('sale.complaint.action', 'Action',
- ondelete='CASCADE', select=True, required=True)
- line = fields.Many2One('account.invoice.line', 'Invoice Line',
- ondelete='RESTRICT', required=True)
+ line = fields.Many2One(
+ 'account.invoice.line', 'Invoice Line',
+ ondelete='RESTRICT', required=True,
+ domain=[
+ ('invoice', '=', Eval('complaint_origin_id', -1)),
+ ],
+ depends=['complaint_origin_id'])
+
+ @fields.depends('line')
+ def on_change_with_unit(self, name=None):
+ if self.line:
+ return self.line.unit.id
+
+ @fields.depends('line')
+ def on_change_with_unit_digits(self, name=None):
+ if self.line:
+ return self.line.unit.digits
diff -r 604af81f8104 -r 4f609f29aa70 complaint.xml
--- a/complaint.xml Mon Apr 13 12:21:55 2020 +0200
+++ b/complaint.xml Mon Apr 13 17:29:36 2020 +0200
@@ -296,5 +296,85 @@
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
+
+ <record model="ir.ui.view" id="action_sale_line_view_form">
+ <field name="model">sale.complaint.action-sale.line</field>
+ <field name="type">form</field>
+ <field name="name">action_line_form</field>
+ </record>
+ <record model="ir.ui.view" id="action_sale_line_view_list">
+ <field name="model">sale.complaint.action-sale.line</field>
+ <field name="type">tree</field>
+ <field name="name">action_line_list</field>
+ </record>
+
+ <record model="ir.rule.group" id="rule_group_action_sale_line">
+ <field name="name">User in complaint's company</field>
+ <field name="model" search="[('model', '=',
'sale.complaint.action-sale.line')]"/>
+ <field name="global_p" eval="True"/>
+ </record>
+ <record model="ir.rule" id="rule_action_sale_line1">
+ <field
+ name="domain"
+ eval="[('action.complaint.company', '=', Eval('user',
{}).get('company', None))]"
+ pyson="1"/>
+ <field name="rule_group" ref="rule_group_action_sale_line"/>
+ </record>
+
+ <record model="ir.model.access" id="access_action_sale_line">
+ <field name="model" search="[('model', '=',
'sale.complaint.action-sale.line')]"/>
+ <field name="perm_read" eval="False"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_delete" eval="False"/>
+ </record>
+ <record model="ir.model.access" id="access_action_sale_line_sale">
+ <field name="model" search="[('model', '=',
'sale.complaint.action-sale.line')]"/>
+ <field name="group" ref="sale.group_sale"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="True"/>
+ <field name="perm_create" eval="True"/>
+ <field name="perm_delete" eval="True"/>
+ </record>
+
+ <record model="ir.ui.view" id="action_invoice_line_view_form">
+ <field
name="model">sale.complaint.action-account.invoice.line</field>
+ <field name="type">form</field>
+ <field name="name">action_line_form</field>
+ </record>
+ <record model="ir.ui.view" id="action_invoice_line_view_list">
+ <field
name="model">sale.complaint.action-account.invoice.line</field>
+ <field name="type">tree</field>
+ <field name="name">action_line_list</field>
+ </record>
+
+ <record model="ir.rule.group" id="rule_group_action_invoice_line">
+ <field name="name">User in complaint's company</field>
+ <field name="model" search="[('model', '=',
'sale.complaint.action-account.invoice.line')]"/>
+ <field name="global_p" eval="True"/>
+ </record>
+ <record model="ir.rule" id="rule_action_invoice_line1">
+ <field
+ name="domain"
+ eval="[('action.complaint.company', '=', Eval('user',
{}).get('company', None))]"
+ pyson="1"/>
+ <field name="rule_group" ref="rule_group_action_invoice_line"/>
+ </record>
+
+ <record model="ir.model.access" id="access_action_invoice_line">
+ <field name="model" search="[('model', '=',
'sale.complaint.action-account.invoice.line')]"/>
+ <field name="perm_read" eval="False"/>
+ <field name="perm_write" eval="False"/>
+ <field name="perm_create" eval="False"/>
+ <field name="perm_delete" eval="False"/>
+ </record>
+ <record model="ir.model.access" id="access_action_invoice_line_sale">
+ <field name="model" search="[('model', '=',
'sale.complaint.action-account.invoice.line')]"/>
+ <field name="group" ref="sale.group_sale"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="True"/>
+ <field name="perm_create" eval="True"/>
+ <field name="perm_delete" eval="True"/>
+ </record>
</data>
</tryton>
diff -r 604af81f8104 -r 4f609f29aa70 tests/scenario_sale_complaint.rst
--- a/tests/scenario_sale_complaint.rst Mon Apr 13 12:21:55 2020 +0200
+++ b/tests/scenario_sale_complaint.rst Mon Apr 13 17:29:36 2020 +0200
@@ -137,6 +137,39 @@
>>> sum(l.quantity for l in return_sale.lines)
-5.0
+Create a complaint to return partially the sale::
+
+ >>> Complaint = Model.get('sale.complaint')
+ >>> complaint = Complaint()
+ >>> complaint.customer = customer
+ >>> complaint.type = sale_type
+ >>> complaint.origin = sale
+ >>> action = complaint.actions.new()
+ >>> action.action = 'sale_return'
+ >>> sale_line = action.sale_lines.new()
+ >>> sale_line.line = sale.lines[0]
+ >>> sale_line.quantity = 1
+ >>> sale_line.unit_price = Decimal('5')
+ >>> sale_line = action.sale_lines.new()
+ >>> sale_line.line = sale.lines[1]
+ >>> complaint.save()
+ >>> complaint.state
+ 'draft'
+ >>> complaint.click('wait')
+ >>> complaint.state
+ 'waiting'
+ >>> complaint.click('approve')
+ >>> complaint.state
+ 'done'
+ >>> action, = complaint.actions
+ >>> return_sale = action.result
+ >>> len(return_sale.lines)
+ 2
+ >>> sum(l.quantity for l in return_sale.lines)
+ -3.0
+ >>> return_sale.total_amount
+ Decimal('-25.00')
+
Create a complaint to return a sale line::
>>> complaint = Complaint()
@@ -177,6 +210,33 @@
>>> sum(l.quantity for l in credit_note.lines)
-5.0
+Create a complaint to credit partially the invoice::
+
+ >>> complaint = Complaint()
+ >>> complaint.customer = customer
+ >>> complaint.type = invoice_type
+ >>> complaint.origin = invoice
+ >>> action = complaint.actions.new()
+ >>> action.action = 'credit_note'
+ >>> invoice_line = action.invoice_lines.new()
+ >>> invoice_line.line = invoice.lines[0]
+ >>> invoice_line.quantity = 1
+ >>> invoice_line.unit_price = Decimal('5')
+ >>> complaint.click('wait')
+ >>> complaint.click('approve')
+ >>> complaint.state
+ 'done'
+ >>> action, = complaint.actions
+ >>> credit_note = action.result
+ >>> credit_note.type
+ 'out'
+ >>> len(credit_note.lines)
+ 1
+ >>> sum(l.quantity for l in credit_note.lines)
+ -1.0
+ >>> credit_note.total_amount
+ Decimal('-5.00')
+
Create a complaint to credit a invoice line::
>>> complaint = Complaint()
diff -r 604af81f8104 -r 4f609f29aa70 view/action_form.xml
--- a/view/action_form.xml Mon Apr 13 12:21:55 2020 +0200
+++ b/view/action_form.xml Mon Apr 13 17:29:36 2020 +0200
@@ -8,8 +8,8 @@
<field name="action"/>
<label name="result"/>
<field name="result"/>
- <field name="sale_lines" colspan="4"/>
- <field name="invoice_lines" colspan="4"/>
+ <field name="sale_lines" colspan="4" product="line"/>
+ <field name="invoice_lines" colspan="4" product="line"/>
<group colspan="4" col="6" id="line">
<label name="quantity"/>
<field name="quantity"/>
diff -r 604af81f8104 -r 4f609f29aa70 view/action_line_form.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/view/action_line_form.xml Mon Apr 13 17:29:36 2020 +0200
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<form>
+ <label name="line"/>
+ <field name="line"/>
+ <label name="action"/>
+ <field name="action"/>
+ <label name="quantity"/>
+ <field name="quantity"/>
+ <label name="unit"/>
+ <field name="unit"/>
+ <label name="unit_price"/>
+ <field name="unit_price"/>
+</form>
diff -r 604af81f8104 -r 4f609f29aa70 view/action_line_list.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/view/action_line_list.xml Mon Apr 13 17:29:36 2020 +0200
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tree editable="1">
+ <field name="line"/>
+ <field name="action"/>
+ <field name="quantity"/>
+ <field name="unit"/>
+ <field name="unit_price"/>
+</tree>