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>

Reply via email to