Skip to content

Commit

Permalink
10.0 Add sale_project_fixed_price_task_completed_invoicing (#4)
Browse files Browse the repository at this point in the history
* Add sale_project_fixed_price_task_completed_invoicing
  • Loading branch information
grindtildeath authored and simahawk committed Mar 12, 2018
1 parent 5ca3423 commit b0272d2
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 0 deletions.
56 changes: 56 additions & 0 deletions sale_project_fixed_price_task_completed_invoicing/README.md
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 sale_project_fixed_price_task_completed_invoicing/__init__.py
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 sale_project_fixed_price_task_completed_invoicing/__manifest__.py
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,
}
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
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
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()
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)
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})
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
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
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()
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>

0 comments on commit b0272d2

Please sign in to comment.