diff --git a/sale_usability/__manifest__.py b/sale_usability/__manifest__.py index e03f307..baf32ea 100644 --- a/sale_usability/__manifest__.py +++ b/sale_usability/__manifest__.py @@ -23,6 +23,7 @@ 'views/account_move.xml', 'views/res_company.xml', "views/res_partner.xml", + "views/sale_template.xml", 'wizards/sale_invoice_discount_all_lines_view.xml', 'security/ir.model.access.csv', ], diff --git a/sale_usability/models/sale_order.py b/sale_usability/models/sale_order.py index c218d4f..cf870d1 100644 --- a/sale_usability/models/sale_order.py +++ b/sale_usability/models/sale_order.py @@ -135,3 +135,29 @@ class SaleOrderLine(models.Model): if no_product_code_param and no_product_code_param == 'True': product = product.with_context(display_default_code=False) return super().get_sale_order_line_multiline_description_sale(product) + + # In v12, I developped the 3 modules service_line_qty_update_base, service_line_qty_update_purchase + # and service_line_qty_update_sale that add a wizard to update service lines and track the changes + # in the chatter. + # In v14, you can edit the quantity of the service lines directly and the purchase module + # tracks changes in the chatter... but the sale module doesn't track the changes of 'qty_delivered' + # So I "ported" that native feature of the purchase module to sale.order.line... here it is ! + # We can remove that code if this feature is added in the sale module (it's NOT the case in + # odoo v17) + def write(self, vals): + if 'qty_delivered' in vals: + for line in self: + line._track_qty_delivered(vals['qty_delivered']) + return super().write(vals) + + def _track_qty_delivered(self, new_qty): + self.ensure_one() + prec = self.env['decimal.precision'].precision_get('Product Unit of Measure') + if ( + float_compare(new_qty, self.qty_delivered, precision_digits=prec) and + self.order_id.state == 'sale'): + self.order_id.message_post_with_view( + 'sale_usability.track_so_line_qty_delivered_template', + values={'line': self, 'qty_delivered': new_qty}, + subtype_id=self.env.ref('mail.mt_note').id + ) diff --git a/sale_usability/views/sale_template.xml b/sale_usability/views/sale_template.xml new file mode 100644 index 0000000..cd34e17 --- /dev/null +++ b/sale_usability/views/sale_template.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/service_line_qty_update_base/__init__.py b/service_line_qty_update_base/__init__.py deleted file mode 100644 index 4027237..0000000 --- a/service_line_qty_update_base/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import wizard diff --git a/service_line_qty_update_base/__manifest__.py b/service_line_qty_update_base/__manifest__.py deleted file mode 100644 index 4245897..0000000 --- a/service_line_qty_update_base/__manifest__.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com) -# @author Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -{ - 'name': 'Service Line Qty Update Base', - 'version': '12.0.1.0.0', - 'category': 'Tools', - 'license': 'AGPL-3', - 'summary': 'Update delivery qty on service lines - Base module', - 'author': 'Akretion', - 'website': 'http://www.akretion.com', - 'depends': ['product'], - 'data': [ - 'wizard/service_qty_update_view.xml', - ], - 'installable': False, -} diff --git a/service_line_qty_update_base/wizard/__init__.py b/service_line_qty_update_base/wizard/__init__.py deleted file mode 100644 index c0ac3f4..0000000 --- a/service_line_qty_update_base/wizard/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import service_qty_update diff --git a/service_line_qty_update_base/wizard/service_qty_update.py b/service_line_qty_update_base/wizard/service_qty_update.py deleted file mode 100644 index 711587d..0000000 --- a/service_line_qty_update_base/wizard/service_qty_update.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) -# @author: Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - - -from odoo import _, api, fields, models -from odoo.tools import float_compare, float_is_zero -import odoo.addons.decimal_precision as dp -from odoo.exceptions import UserError - - -class ServiceQtyUpdate(models.TransientModel): - _name = 'service.qty.update' - _description = 'Wizard to update delivery qty on service lines' - - line_ids = fields.One2many('service.qty.update.line', 'parent_id', string="Lines") - - def run(self): - self.ensure_one() - prec = self.env['decimal.precision'].precision_get('Product Unit of Measure') - for line in self.line_ids: - if float_compare(line.post_delivered_qty, line.order_qty, precision_digits=prec) > 0: - raise UserError(_( - "On line '%s', the total delivered qty (%s) is superior to the ordered qty (%s).") % (line.name, line.post_delivered_qty, line.order_qty)) - fc_added = float_compare(line.added_delivered_qty, 0, precision_digits=prec) - if fc_added < 0: - raise UserError(_( - "On line '%s', the added quantity is negative.") % line.name) - if fc_added > 0: - line.process_line() - return True - - -class ServiceQtyUpdateLine(models.TransientModel): - _name = 'service.qty.update.line' - _description = 'Lines of the wizard that updates delivery qty on service lines' - - parent_id = fields.Many2one( - 'service.qty.update', string='Wizard', ondelete='cascade') - product_id = fields.Many2one('product.product', string='Product', readonly=True) - name = fields.Char() - name_readonly = fields.Char(related='name', string='Description') - order_qty = fields.Float( - string='Order Qty', - digits=dp.get_precision('Product Unit of Measure')) - order_qty_readonly = fields.Float(related='order_qty', string='Product Unit of Measure') - pre_delivered_qty = fields.Float( - digits=dp.get_precision('Product Unit of Measure')) - pre_delivered_qty_readonly = fields.Float(related='pre_delivered_qty', string='Current Delivered Qty') - added_delivered_qty = fields.Float( - string='Added Delivered Qty', - digits=dp.get_precision('Product Unit of Measure')) - post_delivered_qty = fields.Float( - compute='_compute_post_delivered_qty', - string='Total Delivered Qty', - digits=dp.get_precision('Product Unit of Measure')) - uom_id = fields.Many2one('uom.uom', string='UoM', readonly=True) - comment = fields.Char(string='Comment') - - @api.depends('pre_delivered_qty', 'added_delivered_qty') - def _compute_post_delivered_qty(self): - for line in self: - line.post_delivered_qty = line.pre_delivered_qty + line.added_delivered_qty - - def process_line(self): - # Write and message_post - return - - # sale : qty_delivered - # purchase : qty_received diff --git a/service_line_qty_update_base/wizard/service_qty_update_view.xml b/service_line_qty_update_base/wizard/service_qty_update_view.xml deleted file mode 100644 index 2593996..0000000 --- a/service_line_qty_update_base/wizard/service_qty_update_view.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - service.qty.update - -
- - - - - - - - - - - - - - - - - -
-
-
-
-
- - - Service Order Lines - Update Delivered Qty - service.qty.update - form - new - - - -
diff --git a/service_line_qty_update_purchase/__init__.py b/service_line_qty_update_purchase/__init__.py deleted file mode 100644 index 9b42961..0000000 --- a/service_line_qty_update_purchase/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import models -from . import wizard diff --git a/service_line_qty_update_purchase/__manifest__.py b/service_line_qty_update_purchase/__manifest__.py deleted file mode 100644 index 91f0aae..0000000 --- a/service_line_qty_update_purchase/__manifest__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com) -# @author Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -{ - 'name': 'Service Line Qty Update Purchase', - 'version': '12.0.1.0.0', - 'category': 'Tools', - 'license': 'AGPL-3', - 'summary': 'Update delivery qty on service lines - Purchase module', - 'author': 'Akretion', - 'website': 'http://www.akretion.com', - 'depends': [ - 'purchase', - 'service_line_qty_update_base', - 'purchase_reception_status', - ], - 'data': [ - 'views/purchase_order.xml', - ], - 'installable': False, -} diff --git a/service_line_qty_update_purchase/models/__init__.py b/service_line_qty_update_purchase/models/__init__.py deleted file mode 100644 index 9f03530..0000000 --- a/service_line_qty_update_purchase/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import purchase_order diff --git a/service_line_qty_update_purchase/models/purchase_order.py b/service_line_qty_update_purchase/models/purchase_order.py deleted file mode 100644 index 9cb17cd..0000000 --- a/service_line_qty_update_purchase/models/purchase_order.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com) -# @author Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models - - -class PurchaseOrder(models.Model): - _inherit = 'purchase.order' - - has_service = fields.Boolean(compute='_compute_has_service') - - @api.depends('order_line.product_id.type') - def _compute_has_service(self): - for order in self: - has_service = False - for l in order.order_line: - if l.product_id.type == 'service': - has_service = True - break - order.has_service = has_service diff --git a/service_line_qty_update_purchase/views/purchase_order.xml b/service_line_qty_update_purchase/views/purchase_order.xml deleted file mode 100644 index d0e0dc1..0000000 --- a/service_line_qty_update_purchase/views/purchase_order.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - purchase.order.form - purchase.order - - - - - 1 - - - - - - diff --git a/service_line_qty_update_purchase/wizard/__init__.py b/service_line_qty_update_purchase/wizard/__init__.py deleted file mode 100644 index c0ac3f4..0000000 --- a/service_line_qty_update_purchase/wizard/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import service_qty_update diff --git a/service_line_qty_update_purchase/wizard/service_qty_update.py b/service_line_qty_update_purchase/wizard/service_qty_update.py deleted file mode 100644 index de31e0c..0000000 --- a/service_line_qty_update_purchase/wizard/service_qty_update.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) -# @author: Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - - -from odoo import _, api, fields, models -from odoo.tools import float_compare -from odoo.exceptions import UserError - - -class ServiceQtyUpdate(models.TransientModel): - _inherit = 'service.qty.update' - - @api.model - def default_get(self, fields_list): - res = super().default_get(fields_list) - prec = self.env['decimal.precision'].precision_get('Product Unit of Measure') - if self._context.get('active_model') == 'purchase.order' and self._context.get('active_id'): - lines = [] - order = self.env['purchase.order'].browse(self._context['active_id']) - for l in order.order_line.filtered(lambda x: x.product_id.type == 'service'): - if float_compare(l.product_qty, l.qty_received, precision_digits=prec) > 0: - lines.append((0, 0, { - 'purchase_line_id': l.id, - 'product_id': l.product_id.id, - 'name': l.name, - 'name_readonly': l.name, - 'order_qty': l.product_qty, - 'order_qty_readonly': l.product_qty, - 'pre_delivered_qty': l.qty_received, - 'pre_delivered_qty_readonly': l.qty_received, - 'uom_id': l.product_uom.id, - })) - if lines: - res['line_ids'] = lines - else: - raise UserError(_( - "All service lines are fully received.")) - return res - - -class ServiceQtyUpdateLine(models.TransientModel): - _inherit = 'service.qty.update.line' - - purchase_line_id = fields.Many2one('purchase.order.line', string='Purchase Line', readonly=True) - - def process_line(self): - po_line = self.purchase_line_id - if po_line: - new_qty = po_line.qty_received + self.added_delivered_qty - po_line.write({'qty_received': new_qty}) - body = """ -

Received qty updated on service line %s: -

    -
  • Added received qty: %s
  • -
  • Total received qty: %s
  • -

- """ % (self.name, self.added_delivered_qty, new_qty) - if self.comment: - body += '

Comment: %s

' % self.comment - po_line.order_id.message_post(body=body) - return super().process_line() diff --git a/service_line_qty_update_sale/__init__.py b/service_line_qty_update_sale/__init__.py deleted file mode 100644 index 9b42961..0000000 --- a/service_line_qty_update_sale/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import models -from . import wizard diff --git a/service_line_qty_update_sale/__manifest__.py b/service_line_qty_update_sale/__manifest__.py deleted file mode 100644 index 8142b93..0000000 --- a/service_line_qty_update_sale/__manifest__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com) -# @author Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -{ - 'name': 'Service Line Qty Update Sale', - 'version': '12.0.1.0.0', - 'category': 'Tools', - 'license': 'AGPL-3', - 'summary': 'Update delivery qty on service lines - Sale module', - 'author': 'Akretion', - 'website': 'http://www.akretion.com', - 'depends': [ - 'sale', - 'service_line_qty_update_base', - # 'purchase_reception_status', - ], - 'data': [ - 'views/sale_order.xml', - ], - 'installable': False, -} diff --git a/service_line_qty_update_sale/models/__init__.py b/service_line_qty_update_sale/models/__init__.py deleted file mode 100644 index 6aacb75..0000000 --- a/service_line_qty_update_sale/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import sale_order diff --git a/service_line_qty_update_sale/models/sale_order.py b/service_line_qty_update_sale/models/sale_order.py deleted file mode 100644 index 802625f..0000000 --- a/service_line_qty_update_sale/models/sale_order.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com) -# @author Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models - - -class SaleOrder(models.Model): - _inherit = 'sale.order' - - has_service = fields.Boolean(compute='_compute_has_service') - - @api.depends('order_line.product_id.type') - def _compute_has_service(self): - for order in self: - has_service = False - for l in order.order_line: - if l.product_id.type == 'service': - has_service = True - break - order.has_service = has_service diff --git a/service_line_qty_update_sale/views/sale_order.xml b/service_line_qty_update_sale/views/sale_order.xml deleted file mode 100644 index 04ebf16..0000000 --- a/service_line_qty_update_sale/views/sale_order.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - sale.order - - - - - 1 - - - - - - diff --git a/service_line_qty_update_sale/wizard/__init__.py b/service_line_qty_update_sale/wizard/__init__.py deleted file mode 100644 index c0ac3f4..0000000 --- a/service_line_qty_update_sale/wizard/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import service_qty_update diff --git a/service_line_qty_update_sale/wizard/service_qty_update.py b/service_line_qty_update_sale/wizard/service_qty_update.py deleted file mode 100644 index 8be21ec..0000000 --- a/service_line_qty_update_sale/wizard/service_qty_update.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2020 Akretion France (http://www.akretion.com/) -# @author: Alexis de Lattre -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - - -from odoo import _, api, fields, models -from odoo.tools import float_compare -from odoo.exceptions import UserError - - -class ServiceQtyUpdate(models.TransientModel): - _inherit = 'service.qty.update' - - @api.model - def default_get(self, fields_list): - res = super().default_get(fields_list) - prec = self.env['decimal.precision'].precision_get('Product Unit of Measure') - if self._context.get('active_model') == 'sale.order' and self._context.get('active_id'): - lines = [] - order = self.env['sale.order'].browse(self._context['active_id']) - for l in order.order_line.filtered(lambda x: x.product_id.type == 'service'): - if float_compare(l.product_qty, l.qty_delivered, precision_digits=prec) > 0: - lines.append((0, 0, { - 'sale_line_id': l.id, - 'product_id': l.product_id.id, - 'name': l.name, - 'name_readonly': l.name, - 'order_qty': l.product_uom_qty, - 'order_qty_readonly': l.product_uom_qty, - 'pre_delivered_qty': l.qty_delivered, - 'pre_delivered_qty_readonly': l.qty_delivered, - 'uom_id': l.product_uom.id, - })) - if lines: - res['line_ids'] = lines - else: - raise UserError(_( - "All service lines are fully delivered.")) - return res - - -class ServiceQtyUpdateLine(models.TransientModel): - _inherit = 'service.qty.update.line' - - sale_line_id = fields.Many2one('sale.order.line', string='Sale Line', readonly=True) - - def process_line(self): - so_line = self.sale_line_id - if so_line: - new_qty = so_line.qty_delivered + self.added_delivered_qty - so_line.write({'qty_delivered': new_qty}) - body = """ -

Delivered qty updated on service line %s: -

    -
  • Added delivered qty: %s
  • -
  • Total delivered qty: %s
  • -

- """ % (self.name, self.added_delivered_qty, new_qty) - if self.comment: - body += '

Comment: %s

' % self.comment - so_line.order_id.message_post(body=body) - return super().process_line()