-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
10.0 Add
sale_project_fixed_price_task_completed_invoicing
(#4)
* Add sale_project_fixed_price_task_completed_invoicing
- Loading branch information
1 parent
5ca3423
commit b0272d2
Showing
12 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
56 changes: 56 additions & 0 deletions
56
sale_project_fixed_price_task_completed_invoicing/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
The difference with sale_timesheet is that 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 corresponding task click on the | ||
button 'Invoiceable'. The linked sale.order.line will be considered as shipped. | ||
|
||
|
||
Bug Tracker | ||
=========== | ||
|
||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/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 <[email protected]> | ||
|
||
|
||
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. |
4 changes: 4 additions & 0 deletions
4
sale_project_fixed_price_task_completed_invoicing/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# -*- coding: utf-8 -*- | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from . import models |
21 changes: 21 additions & 0 deletions
21
sale_project_fixed_price_task_completed_invoicing/__manifest__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} |
8 changes: 8 additions & 0 deletions
8
sale_project_fixed_price_task_completed_invoicing/models/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
19 changes: 19 additions & 0 deletions
19
sale_project_fixed_price_task_completed_invoicing/models/procurement.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
25 changes: 25 additions & 0 deletions
25
sale_project_fixed_price_task_completed_invoicing/models/product.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
50 changes: 50 additions & 0 deletions
50
sale_project_fixed_price_task_completed_invoicing/models/project_task.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# -*- 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') or | ||
task.sale_line_id.invoice_status in ('invoiced',)): | ||
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) | ||
|
||
@api.model | ||
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) |
30 changes: 30 additions & 0 deletions
30
sale_project_fixed_price_task_completed_invoicing/models/sale_order.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# -*- 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: | ||
order._create_analytic_and_tasks() | ||
return res | ||
|
||
@api.multi | ||
def _create_analytic_and_tasks(self): | ||
for order in self: | ||
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}) |
25 changes: 25 additions & 0 deletions
25
sale_project_fixed_price_task_completed_invoicing/models/sale_order_line.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 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() | ||
return line | ||
|
||
@api.multi | ||
def _check_delivered_qty(self): | ||
for line in self: | ||
tasks = self.env['project.task'].search( | ||
[('sale_line_id', '=', line.id)]) | ||
if len(tasks) == len(tasks.filtered('invoiceable')): | ||
line.qty_delivered = line.product_uom_qty |
2 changes: 2 additions & 0 deletions
2
sale_project_fixed_price_task_completed_invoicing/tests/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# -*- coding: utf-8 -*- | ||
from . import test_sale_project_fixed_price_task_completed_invoicing |
56 changes: 56 additions & 0 deletions
56
..._task_completed_invoicing/tests/test_sale_project_fixed_price_task_completed_invoicing.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# -*- coding: utf-8 -*- | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from odoo.addons.sale.tests.test_sale_common import TestSale | ||
from odoo.exceptions import UserError # , ValidationError | ||
|
||
|
||
class TestSaleProjectFixedPrice(TestSale): | ||
|
||
def test_sale_project_fixed_price(self): | ||
# The product comes from 'sale_timesheet' the only modification to add | ||
# is for the track_service | ||
prod_task = self.env.ref('product.product_product_1') | ||
prd_vals = { | ||
'track_service': 'completed_task', | ||
} | ||
prod_task.write(prd_vals) | ||
so_vals = { | ||
'partner_id': self.partner.id, | ||
'partner_invoice_id': self.partner.id, | ||
'partner_shipping_id': self.partner.id, | ||
'order_line': [(0, 0, {'name': prod_task.name, | ||
'product_id': prod_task.id, | ||
'product_uom_qty': 1, | ||
'product_uom': prod_task.uom_id.id, | ||
'price_unit': prod_task.list_price})], | ||
'pricelist_id': self.env.ref('product.list0').id, | ||
} | ||
so = self.env['sale.order'].create(so_vals) | ||
so.action_confirm() | ||
|
||
# check task creation | ||
project = self.env.ref('sale_timesheet.project_GAP') | ||
task = project.task_ids.filtered( | ||
lambda t: t.name == '%s:%s' % (so.name, prod_task.name)) | ||
self.assertTrue(task, 'Sale Service: task is not created') | ||
self.assertEqual(task.partner_id, so.partner_id, | ||
'Sale Service: customer should be the same on task ' | ||
'and on SO') | ||
self.assertTrue(task.fixed_price) | ||
|
||
# check Task validation. It should update the delivered quantity | ||
line = so.order_line | ||
self.assertFalse(line.product_uom_qty == line.qty_delivered, | ||
'Sale Service: line should be invoiced completely') | ||
task.toggle_invoiceable() | ||
self.assertTrue(task.invoiceable, 'The task should be invoiceable') | ||
self.assertTrue(line.product_uom_qty == line.qty_delivered, | ||
'Sale Service: line should be invoiced completely') | ||
|
||
# Impossible to change task invoicable after validation of soline | ||
so.action_invoice_create() | ||
self.assertEqual(so.invoice_status, | ||
'invoiced', 'SO should be invoiced') | ||
with self.assertRaises(UserError): | ||
task.toggle_invoiceable() |
24 changes: 24 additions & 0 deletions
24
sale_project_fixed_price_task_completed_invoicing/views/project_views.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
|
||
<record id="edit_project_task_track" model="ir.ui.view"> | ||
<field name="name">project.task.form.track</field> | ||
<field name="model">project.task</field> | ||
<field name="inherit_id" ref="project.view_task_form2"/> | ||
<field name="arch" type="xml"> | ||
|
||
<xpath expr="//field[@name='planned_hours']" position="after"> | ||
<field name="invoiceable" readonly="1"/> | ||
<field name="fixed_price" invisible="1"/> | ||
</xpath> | ||
|
||
<div name="button_box" position="inside"> | ||
<button class="oe_stat_button" name="toggle_invoiceable" type="object" icon="fa-file" attrs="{'invisible': [('fixed_price', '=', False)]}"> | ||
<span>Invoiceable</span> | ||
</button> | ||
</div> | ||
|
||
</field> | ||
</record> | ||
|
||
</odoo> |