From 66af3bdbd02ccb72ab80ac847836d2512bbc66c7 Mon Sep 17 00:00:00 2001 From: Denis Leemann Date: Tue, 30 May 2017 14:25:37 +0200 Subject: [PATCH] Add sale_project_fixed_price_task_completed_invoicing --- .../README.md | 56 +++++++++++++++++++ .../__init__.py | 4 ++ .../__manifest__.py | 21 +++++++ .../models/__init__.py | 8 +++ .../models/procurement.py | 19 +++++++ .../models/product.py | 25 +++++++++ .../models/project_task.py | 55 ++++++++++++++++++ .../models/sale_order.py | 25 +++++++++ .../models/sale_order_line.py | 35 ++++++++++++ .../views/project_views.xml | 24 ++++++++ 10 files changed, 272 insertions(+) create mode 100644 sale_project_fixed_price_task_completed_invoicing/README.md create mode 100644 sale_project_fixed_price_task_completed_invoicing/__init__.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/__manifest__.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/models/__init__.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/models/procurement.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/models/product.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/models/project_task.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/models/sale_order.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/models/sale_order_line.py create mode 100644 sale_project_fixed_price_task_completed_invoicing/views/project_views.xml diff --git a/sale_project_fixed_price_task_completed_invoicing/README.md b/sale_project_fixed_price_task_completed_invoicing/README.md new file mode 100644 index 00000000000..176d13c06c4 --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/README.md @@ -0,0 +1,56 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +Sale Project Fixed Price Task Completed Invoicing +================================================= + +The main goal of this module is to add the possibility to link a sale.order.line +to a project.task considering the delivery. +Unless sale_timesheet, the quantity shipped won't be linked to a Timesheet nor +to the time spent on the task. The price is fixed on the sale.order.line and it +will be considered as shipped once the task is accomplished. + +Usage +===== + +Create a product with product.type 'Service' and track_service 'Completed Task'. + +Use it in a Sale Order. Once you validate the Sale Order, it will create a linked +project and task. + +Once the task is finished, on the form view of the corrisponding task click on the +button 'Invoiceable'. The linked sale.order.line will be considered as shipped. + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. + + +Credits +======= + +Contributors +------------ + +* Denis Leemann + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/sale_project_fixed_price_task_completed_invoicing/__init__.py b/sale_project_fixed_price_task_completed_invoicing/__init__.py new file mode 100644 index 00000000000..a77a6fcbc5d --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/sale_project_fixed_price_task_completed_invoicing/__manifest__.py b/sale_project_fixed_price_task_completed_invoicing/__manifest__.py new file mode 100644 index 00000000000..42f806f5cad --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/__manifest__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Sale project fixed price task completed invoicing", + "version": "10.0.1.0.0", + "depends": [ + 'product', + 'project', + 'sale', + 'sale_timesheet', + ], + "author": "Camptocamp,Odoo Community Association (OCA)", + "website": "http://www.camptocamp.com", + "license": "AGPL-3", + "category": "Sale", + "data": [ + 'views/project_views.xml', + ], + 'installable': True, +} diff --git a/sale_project_fixed_price_task_completed_invoicing/models/__init__.py b/sale_project_fixed_price_task_completed_invoicing/models/__init__.py new file mode 100644 index 00000000000..00edd76c6db --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import product +from . import project_task +from . import sale_order +from . import sale_order_line +from . import procurement diff --git a/sale_project_fixed_price_task_completed_invoicing/models/procurement.py b/sale_project_fixed_price_task_completed_invoicing/models/procurement.py new file mode 100644 index 00000000000..e83ad6e02b6 --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/models/procurement.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class ProcurementOrder(models.Model): + _inherit = 'procurement.order' + + def _is_procurement_task(self): + return (self.product_id.type == 'service' and + self.product_id.track_service in ('task', 'completed_task')) + + def _create_service_task(self): + task = super(ProcurementOrder, self)._create_service_task() + if self.product_id.track_service == 'completed_task': + task.fixed_price = True + return task diff --git a/sale_project_fixed_price_task_completed_invoicing/models/product.py b/sale_project_fixed_price_task_completed_invoicing/models/product.py new file mode 100644 index 00000000000..37d2ececc0d --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/models/product.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + track_service = fields.Selection(selection_add=[ + ('completed_task', 'Completed Task')] + ) + + +class ProductProduct(models.Model): + _inherit = 'product.product' + + @api.multi + def _need_procurement(self): + for product in self: + if (product.type == 'service' and + product.track_service == 'completed_task'): + return True + return super(ProductProduct, self)._need_procurement() diff --git a/sale_project_fixed_price_task_completed_invoicing/models/project_task.py b/sale_project_fixed_price_task_completed_invoicing/models/project_task.py new file mode 100644 index 00000000000..e7524e243cc --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/models/project_task.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError, UserError + + +class ProjectTask(models.Model): + _inherit = 'project.task' + + invoiceable = fields.Boolean( + string='Invoiceable', + ) + fixed_price = fields.Boolean( + string='Fixed Price', + ) + + @api.multi + def toggle_invoiceable(self): + for task in self: + # We dont' want to modify when the related SOLine is invoiced + if (not task.sale_line_id or + task.sale_line_id.state in ('done', 'cancel')): + raise UserError(_("You cannot modify the status if there is " + "no Sale Order Line or if it has been " + "invoiced.")) + task.invoiceable = not task.invoiceable + task.sale_line_id._check_delivered_qty() + + @api.multi + def write(self, vals): + for task in self: + if (vals.get('sale_line_id') and + task.sale_line_id.state in ('done', 'cancel')): + raise ValidationError(_('You cannot modify the Sale Order ' + 'Line of the task once it is invoiced') + ) + return super(ProjectTask, self).write(vals) + + def create(self, vals): + SOLine = self.env['sale.order.line'] + so_line = SOLine.browse(vals.get('sale_line_id')) + # We don't want to add a project.task to an already invoiced line + if so_line and so_line.state in ('done', 'cancel'): + raise ValidationError(_('You cannot add a task to and invoiced ' + 'Sale Order Line')) + return super(ProjectTask, self).create(vals) + + @api.onchange('invoiceable') + def _onchange_invoiceable(self): + for task in self: + if not task.invoiceable: + continue + task.sale_line_id._check_delivered_qty() diff --git a/sale_project_fixed_price_task_completed_invoicing/models/sale_order.py b/sale_project_fixed_price_task_completed_invoicing/models/sale_order.py new file mode 100644 index 00000000000..f1fa88ce18b --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/models/sale_order.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + @api.multi + def action_confirm(self): + res = super(SaleOrder, self).action_confirm() + for order in self: + if not order.project_project_id: + for line in order.order_line: + if (line.product_id.track_service in ('completed_task', + 'timesheet')): + if not order.project_id: + order._create_analytic_account( + prefix=line.product_id.default_code or None) + order.project_id.project_create( + {'name': order.project_id.name, + 'use_tasks': True}) + return res diff --git a/sale_project_fixed_price_task_completed_invoicing/models/sale_order_line.py b/sale_project_fixed_price_task_completed_invoicing/models/sale_order_line.py new file mode 100644 index 00000000000..ad58d6608be --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/models/sale_order_line.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models, _ +from odoo.exceptions import ValidationError + + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + @api.model + def create(self, vals): + line = super(SaleOrderLine, self).create(vals) + if (line.state == 'sale' and not line.order_id.project_id and + line.product_id.track_service in ('completed_task', )): + line.order_id._create_analytic_account() + + @api.constrains('product_id') + def _onchange_product_id(self): + for line in self: + if ('completed_task' == line.product_id.track_service and + line.product_uom_qty != 1.0): + raise ValidationError(_("The quantity for 'Complete Task' " + "products must be exactly one")) + + @api.multi + def _check_delivered_qty(self): + for line in self: + tasks_count = self.env['project.task'].search_count( + [('sale_line_id', '=', line.id)]) + task_invoiced_count = self.env['project.task'].search_count( + [('sale_line_id', '=', line.id), ('invoiceable', '=', True)]) + if tasks_count == task_invoiced_count: + line.qty_delivered = 1.0 diff --git a/sale_project_fixed_price_task_completed_invoicing/views/project_views.xml b/sale_project_fixed_price_task_completed_invoicing/views/project_views.xml new file mode 100644 index 00000000000..155d4ca8426 --- /dev/null +++ b/sale_project_fixed_price_task_completed_invoicing/views/project_views.xml @@ -0,0 +1,24 @@ + + + + + project.task.form.track + project.task + + + + + + + + +
+ +
+ +
+
+ +