From b05a7b8dae61642485de5e43516ab4aa5e79b736 Mon Sep 17 00:00:00 2001 From: Carlos Dauden Date: Wed, 3 Aug 2016 23:13:56 +0200 Subject: [PATCH] [9.0][NEW] sale_timesheet_invoice_description: New module (#154) [ADD] sale_timesheet_invoice_description: New module --- sale_timesheet_invoice_description/README.rst | 77 +++++++++++++++++++ .../__init__.py | 3 + .../__openerp__.py | 19 +++++ .../models/__init__.py | 4 + .../models/res_config.py | 34 ++++++++ .../models/sale.py | 63 +++++++++++++++ .../tests/__init__.py | 3 + .../tests/test_sale_timesheet_description.py | 47 +++++++++++ .../views/res_config_view.xml | 15 ++++ .../views/sale_view.xml | 15 ++++ 10 files changed, 280 insertions(+) create mode 100644 sale_timesheet_invoice_description/README.rst create mode 100644 sale_timesheet_invoice_description/__init__.py create mode 100644 sale_timesheet_invoice_description/__openerp__.py create mode 100644 sale_timesheet_invoice_description/models/__init__.py create mode 100644 sale_timesheet_invoice_description/models/res_config.py create mode 100644 sale_timesheet_invoice_description/models/sale.py create mode 100644 sale_timesheet_invoice_description/tests/__init__.py create mode 100644 sale_timesheet_invoice_description/tests/test_sale_timesheet_description.py create mode 100644 sale_timesheet_invoice_description/views/res_config_view.xml create mode 100644 sale_timesheet_invoice_description/views/sale_view.xml diff --git a/sale_timesheet_invoice_description/README.rst b/sale_timesheet_invoice_description/README.rst new file mode 100644 index 00000000000..91448ce9504 --- /dev/null +++ b/sale_timesheet_invoice_description/README.rst @@ -0,0 +1,77 @@ +.. 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 + +========================= +Timesheet details invoice +========================= + +Add timesheet details in invoice line to invoices related with timesheets. + +Configuration +============= + +To configure this module, you need to: + +#. Go to *Sales -> Configuration -> Settings -> Quotations & Sales* and set + Default Timesheet Invoice Description. + + +Usage +===== + +To use this module, you need to: + +#. Go to *Sales -> Sales Orders* and create a new Sales Orders. +#. Add line selecting a product with + + - *Invoicing Policy* -> **Delivered quantities** + + - *Track Service* -> **Timesheets on contract** + + e.g. *Support Contract (on timesheet)* +#. Confirm Sale +#. Go to *Timesheets -> Activities* and create line with same project of SO +#. Go to Sales Orders and select *Other Information* -> **Timesheet invoice + description** +#. *Create Invoice* + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/95/9.0 + + +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 +------------ + +* Carlos Dauden +* Pedro M. Baeza + + +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 https://odoo-community.org. diff --git a/sale_timesheet_invoice_description/__init__.py b/sale_timesheet_invoice_description/__init__.py new file mode 100644 index 00000000000..cde864bae21 --- /dev/null +++ b/sale_timesheet_invoice_description/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/sale_timesheet_invoice_description/__openerp__.py b/sale_timesheet_invoice_description/__openerp__.py new file mode 100644 index 00000000000..f1cc962652b --- /dev/null +++ b/sale_timesheet_invoice_description/__openerp__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Timesheet details invoice', + 'summary': 'Add timesheet details in invoice line', + 'version': '9.0.1.0.0', + 'category': 'Sales Management', + 'license': 'AGPL-3', + 'author': 'Tecnativa, Odoo Community Association (OCA)', + 'website': 'https://www.tecnativa.com', + 'depends': ['sale_timesheet'], + 'data': [ + 'views/sale_view.xml', + 'views/res_config_view.xml', + ], + 'installable': True, +} diff --git a/sale_timesheet_invoice_description/models/__init__.py b/sale_timesheet_invoice_description/models/__init__.py new file mode 100644 index 00000000000..89fff28256f --- /dev/null +++ b/sale_timesheet_invoice_description/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import sale +from . import res_config diff --git a/sale_timesheet_invoice_description/models/res_config.py b/sale_timesheet_invoice_description/models/res_config.py new file mode 100644 index 00000000000..2d5ccfa801a --- /dev/null +++ b/sale_timesheet_invoice_description/models/res_config.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import api, fields, models + + +class SaleConfiguration(models.TransientModel): + _inherit = 'sale.config.settings' + + default_timesheet_invoice_description = fields.Selection( + '_get_timesheet_invoice_description', + "Timesheet Invoice Description") + + @api.model + def _get_timesheet_invoice_description(self): + return self.env['sale.order']._get_timesheet_invoice_description() + + @api.model + def get_default_sale_config(self, fields): + default_timesheet_inv_desc = self.env['ir.values'].get_default( + 'sale.order', 'timesheet_invoice_description') or '111' + return { + 'default_timesheet_invoice_description': + default_timesheet_inv_desc, + } + + @api.multi + def set_sale_defaults(self): + self.ensure_one() + self.env['ir.values'].sudo().set_default( + 'sale.order', 'timesheet_invoice_description', + self.default_timesheet_invoice_description) + res = super(SaleConfiguration, self).set_sale_defaults() + return res diff --git a/sale_timesheet_invoice_description/models/sale.py b/sale_timesheet_invoice_description/models/sale.py new file mode 100644 index 00000000000..d50fc8da063 --- /dev/null +++ b/sale_timesheet_invoice_description/models/sale.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import api, fields, models, _ +from openerp.tools import config + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + timesheet_invoice_description = fields.Selection( + '_get_timesheet_invoice_description', default='000') + + @api.model + def _get_timesheet_invoice_description(self): + return [ + ('000', _('None')), + ('111', _('Date - Time spent - Description')), + ('101', _('Date - Description')), + ('001', _('Description')), + ('011', _('Time spent - Description')), + ] + + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + @api.multi + def _prepare_invoice_line_details(self, line, desc_rule): + details = [] + if desc_rule[0] == '1': + details.append(line.date) + if desc_rule[1] == '1': + details.append( + "%s %s" % (line.unit_amount, line.product_uom_id.name)) + if desc_rule[2] == '1': + details.append(line.name) + return details + + @api.multi + def _prepare_invoice_line(self, qty): + res = super(SaleOrderLine, self)._prepare_invoice_line(qty) + desc_rule = self.order_id.timesheet_invoice_description + if not desc_rule or desc_rule == '000': + return res + note = [] + domain = [('so_line', '=', self.id)] + last_invoice = self.invoice_lines.sorted(lambda x: x.create_date)[-1:] + if last_invoice: + domain.append(('create_date', '>', last_invoice.create_date)) + for line in self.env['account.analytic.line'].search( + domain, order='date, id'): + details = self._prepare_invoice_line_details(line, desc_rule) + note.append( + u' - '.join(map(lambda x: unicode(x) or '', details))) + # This is for not breaking possible tests that expects to create the + # invoices lines the standard way + if note and (not config['test_enable'] or self.env.context.get( + 'timesheet_description')): + res['name'] += "\n" + ( + "\n".join(map(lambda x: unicode(x) or '', note))) + return res diff --git a/sale_timesheet_invoice_description/tests/__init__.py b/sale_timesheet_invoice_description/tests/__init__.py new file mode 100644 index 00000000000..2bdaaa1e810 --- /dev/null +++ b/sale_timesheet_invoice_description/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_sale_timesheet_description diff --git a/sale_timesheet_invoice_description/tests/test_sale_timesheet_description.py b/sale_timesheet_invoice_description/tests/test_sale_timesheet_description.py new file mode 100644 index 00000000000..16ca2e312d8 --- /dev/null +++ b/sale_timesheet_invoice_description/tests/test_sale_timesheet_description.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.addons.sale.tests.test_sale_common import TestSale + + +class TestSaleTimesheetDescription(TestSale): + def test_sale_timesheet_description(self): + """ Test invoice description """ + self.SaleConfigSetting = self.env['sale.config.settings'] + inv_obj = self.env['account.invoice'] + # intial sale order + prod_ts = self.env.ref('product.product_product_2') + sale_order_vals = { + 'partner_id': self.partner.id, + 'partner_invoice_id': self.partner.id, + 'partner_shipping_id': self.partner.id, + 'pricelist_id': self.env.ref('product.list0').id, + 'timesheet_invoice_description': '111', + 'order_line': [(0, 0, { + 'name': prod_ts.name, + 'product_id': prod_ts.id, + 'product_uom_qty': 5, + 'product_uom': prod_ts.uom_id.id, + 'price_unit': prod_ts.list_price})], + } + sale_order = self.env['sale.order'].create(sale_order_vals) + sale_order.action_confirm() + # let's log some timesheets + self.env['account.analytic.line'].create({ + 'name': 'Test description 1234567890', + 'account_id': sale_order.project_id.id, + 'unit_amount': 10.5, + 'user_id': self.manager.id, + 'is_timesheet': True, + }) + invoice_id = sale_order.with_context( + timesheet_description=True).action_invoice_create() + invoice = inv_obj.browse(invoice_id) + description = invoice.invoice_line_ids[0].name + self.assertIn('Test description 1234567890', description) + + self.default_timesheet_invoice_description = ( + self.SaleConfigSetting.create({})) + + self.default_timesheet_invoice_description.execute() diff --git a/sale_timesheet_invoice_description/views/res_config_view.xml b/sale_timesheet_invoice_description/views/res_config_view.xml new file mode 100644 index 00000000000..5cdd162c75b --- /dev/null +++ b/sale_timesheet_invoice_description/views/res_config_view.xml @@ -0,0 +1,15 @@ + + + + + sale settings + sale.config.settings + + + + + + + + diff --git a/sale_timesheet_invoice_description/views/sale_view.xml b/sale_timesheet_invoice_description/views/sale_view.xml new file mode 100644 index 00000000000..2aa7df4f7b1 --- /dev/null +++ b/sale_timesheet_invoice_description/views/sale_view.xml @@ -0,0 +1,15 @@ + + + + + sale.order.form.invoice.description + sale.order + + + + + + + +