diff --git a/fieldservice_isp_account/README.rst b/fieldservice_isp_account/README.rst new file mode 100644 index 0000000000..5b7501afeb --- /dev/null +++ b/fieldservice_isp_account/README.rst @@ -0,0 +1,125 @@ +============================== +Field Service - ISP Accounting +============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Ffield--service-lightgray.png?logo=github + :target: https://github.com/OCA/field-service/tree/13.0/fieldservice_isp_account + :alt: OCA/field-service +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/field-service-13-0/field-service-13-0-fieldservice_isp_account + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/264/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds the ability to track employee time and contractor +costs for Field Service Orders. It also adds functionality to create +a customer invoice and a vendor bill when a Field Service Order is +completed. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +No special installation instructions + +Configuration +============= + +No special configuration instructions. + +Usage +===== + +To use the module: + +On a field service order, open the "Accounting" tab. Depending on +whether the logged in user or an employee or not, they will see +either a place to enter timesheet records or contractor costs. + +The total cost of the order is calculated based on the entries in +the employee timesheet entries and contractor costs. + +When an order is completed, a customer invoice will be generated for +the employee time and the contractor costs. A vendor bill will be +created for the contractor costs. + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Open Source Integrators + +Contributors +~~~~~~~~~~~~ + +* Michael Allen +* Serpent Consulting Services Pvt. Ltd. +* Brian McMaster + +Other credits +~~~~~~~~~~~~~ + +The development of this module has been financially supported by: + +* Open Source Integrators + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +.. |maintainer-osimallen| image:: https://github.com/osimallen.png?size=40px + :target: https://github.com/osimallen + :alt: osimallen +.. |maintainer-brian10048| image:: https://github.com/brian10048.png?size=40px + :target: https://github.com/brian10048 + :alt: brian10048 +.. |maintainer-bodedra| image:: https://github.com/bodedra.png?size=40px + :target: https://github.com/bodedra + :alt: bodedra + +Current `maintainers `__: + +|maintainer-osimallen| |maintainer-brian10048| |maintainer-bodedra| + +This module is part of the `OCA/field-service `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/fieldservice_isp_account/__init__.py b/fieldservice_isp_account/__init__.py new file mode 100644 index 0000000000..66ca882fcc --- /dev/null +++ b/fieldservice_isp_account/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2018 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/fieldservice_isp_account/__manifest__.py b/fieldservice_isp_account/__manifest__.py new file mode 100644 index 0000000000..3c0f211449 --- /dev/null +++ b/fieldservice_isp_account/__manifest__.py @@ -0,0 +1,29 @@ +# Copyright (C) 2018 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Field Service - ISP Accounting", + "summary": """Invoice Field Service orders based on employee time + or contractor costs""", + "version": "13.0.1.0.0", + "category": "Field Service", + "author": "Open Source Integrators, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/field-service", + "depends": [ + "fieldservice_account_analytic", + "fieldservice_project", + "hr_timesheet", + ], + "data": [ + "security/fsm_order_cost.xml", + "security/ir.model.access.csv", + "data/time_products.xml", + "views/account.xml", + "views/fsm_order.xml", + "views/fsm_person.xml", + "views/hr_timesheet.xml", + ], + "license": "AGPL-3", + "development_status": "Beta", + "maintainers": ["osimallen", "brian10048", "bodedra"], +} diff --git a/fieldservice_isp_account/data/time_products.xml b/fieldservice_isp_account/data/time_products.xml new file mode 100644 index 0000000000..27fb994a25 --- /dev/null +++ b/fieldservice_isp_account/data/time_products.xml @@ -0,0 +1,32 @@ + + + + FSM Travel Time + + service + + + Travel time for Field Service + Employees + + + + FSM Regular Time + + service + + + Regular time for Field Service + Employees + + + + FSM Overtime + + service + + + Overtime for Field Service Employees + + + diff --git a/fieldservice_isp_account/i18n/es_AR.po b/fieldservice_isp_account/i18n/es_AR.po new file mode 100644 index 0000000000..32b7a899ee --- /dev/null +++ b/fieldservice_isp_account/i18n/es_AR.po @@ -0,0 +1,228 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fieldservice_isp_account +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-07-21 05:19+0000\n" +"Last-Translator: Ignacio Buioli \n" +"Language-Team: none\n" +"Language: es_AR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 3.10\n" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__account_stage +msgid "Accounting Stage" +msgstr "Etapa de Contabilidad" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:68 +#, python-format +msgid "Cannot move to Complete until 'Contractor Costs' is filled in" +msgstr "" +"No se puede pasar a Completo hasta que se completen los 'Costos del " +"Contratista'" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:71 +#, python-format +msgid "Cannot move to Complete until 'Employee Timesheets' is filled in" +msgstr "" +"No se puede mover a Completado hasta que se completen el 'Parte de Horas del " +"Empleado'" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Confirm" +msgstr "Confirmar" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Confirmed" +msgstr "Confirmado" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:116 +#, python-format +msgid "Contact empty" +msgstr "Contacto vacío" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_total +msgid "Contractor Cost Estimate" +msgstr "Estimación de Costos del Contratistas" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_cost_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Contractor Costs" +msgstr "Costos del Contratistas" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Create Invoice" +msgstr "Crear Factura" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Draft" +msgstr "Borrador" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Duration" +msgstr "Duración" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee +msgid "Employee" +msgstr "Empleado" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_timesheet_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Employee Timesheets" +msgstr "Parte de Horas del Empleado" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_overtime +msgid "FSM Overtime" +msgstr "Horas Extra FSM" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_regular_time +msgid "FSM Regular Time" +msgstr "Tiempo Regular FSM" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_travel_time +msgid "FSM Travel Time" +msgstr "Tiempo de Viaje FSM" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_order +msgid "Field Service Order" +msgstr "Pedido de Servicio de Campo" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_person +msgid "Field Service Worker" +msgstr "Trabajador de Servicio de Campo" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Fully Invoiced" +msgstr "Completamente Facturado" + +#. module: fieldservice_isp_account +#: model:product.template,uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,uom_name:fieldservice_isp_account.field_service_travel_time +msgid "Hour(s)" +msgstr "Hora(s)" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_account_move_line +msgid "Invoice Line" +msgstr "Línea de Factura" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_search_view +msgid "Needs Review" +msgstr "Necesita Revisión" + +#. module: fieldservice_isp_account +#: model_terms:ir.actions.act_window,help:fieldservice_isp_account.action_account_fsm_order +msgid "No Field Service Orders to review." +msgstr "No hay Pedidos de Servicio de Campo para revisar." + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "No Invoice" +msgstr "No hay Factura" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Nothing Invoiced" +msgstr "Nada que Facturar" + +#. module: fieldservice_isp_account +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_operations +msgid "Operations" +msgstr "Operaciones" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_overtime +msgid "Overtime for Field Service Employees\n" +" " +msgstr "" +"Horas Extra para los Empleados de Servicio de Campo\n" +" " + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/account_move_line.py:19 +#, python-format +msgid "Please set the field service worker." +msgstr "Por favor configure el trabajador de servicio de campo." + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_regular_time +msgid "Regular time for Field Service\n" +" Employees\n" +" " +msgstr "" +"Tiempo regular para Empleados de Servicio de\n" +" Campo\n" +" " + +#. module: fieldservice_isp_account +#: model:ir.actions.act_window,name:fieldservice_isp_account.action_account_fsm_order +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_fsm_order +msgid "Service Orders" +msgstr "Pedidos de Servicio" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:103 +#, python-format +msgid "The worker assigned to this order is not a supplier" +msgstr "El trabajador asignado a este pedido no es un proveedor" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Timesheet Activities" +msgstr "Actividades de Parte de Horas" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_time_total +msgid "Total Employee Hours" +msgstr "Total de Horas de Empleados" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_travel_time +msgid "Travel time for Field Service\n" +" Employees\n" +" " +msgstr "" +"Tiempo de viaje para Empleados de Servicio de\n" +" Campo\n" +" " + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_person__bill_count +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_person_form_isp_account +msgid "Vendor Bills" +msgstr "Cuentas de Vendedor" + +#. module: fieldservice_isp_account +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_travel_time +msgid "kg" +msgstr "kg" diff --git a/fieldservice_isp_account/i18n/es_CL.po b/fieldservice_isp_account/i18n/es_CL.po new file mode 100644 index 0000000000..25b3c15534 --- /dev/null +++ b/fieldservice_isp_account/i18n/es_CL.po @@ -0,0 +1,220 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fieldservice_isp_account +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-01-16 22:38+0000\n" +"Last-Translator: Nelson Ramírez Sánchez \n" +"Language-Team: none\n" +"Language: es_CL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 3.10\n" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__account_stage +msgid "Accounting Stage" +msgstr "Etapa Contable" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:68 +#, python-format +msgid "Cannot move to Complete until 'Contractor Costs' is filled in" +msgstr "" +"No se puede pasar a Completado hasta que se completen los 'Costos del " +"contratista'" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:71 +#, python-format +msgid "Cannot move to Complete until 'Employee Timesheets' is filled in" +msgstr "" +"No se puede mover a Completado hasta que se completen las 'Hojas de tiempo " +"de empleado'" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Confirm" +msgstr "Confirmar" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Confirmed" +msgstr "Confirmado" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:116 +#, python-format +msgid "Contact empty" +msgstr "Contacto Vacío" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_total +msgid "Contractor Cost Estimate" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_cost_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Contractor Costs" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Create Invoice" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Draft" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Duration" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee +msgid "Employee" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_timesheet_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Employee Timesheets" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_overtime +msgid "FSM Overtime" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_regular_time +msgid "FSM Regular Time" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_travel_time +msgid "FSM Travel Time" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_order +msgid "Field Service Order" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_person +msgid "Field Service Worker" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Fully Invoiced" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,uom_name:fieldservice_isp_account.field_service_travel_time +msgid "Hour(s)" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_account_move_line +msgid "Invoice Line" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_search_view +msgid "Needs Review" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.actions.act_window,help:fieldservice_isp_account.action_account_fsm_order +msgid "No Field Service Orders to review." +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "No Invoice" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Nothing Invoiced" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_operations +msgid "Operations" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_overtime +msgid "Overtime for Field Service Employees\n" +" " +msgstr "" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/account_move_line.py:19 +#, python-format +msgid "Please set the field service worker." +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_regular_time +msgid "Regular time for Field Service\n" +" Employees\n" +" " +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.actions.act_window,name:fieldservice_isp_account.action_account_fsm_order +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_fsm_order +msgid "Service Orders" +msgstr "" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:103 +#, python-format +msgid "The worker assigned to this order is not a supplier" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Timesheet Activities" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_time_total +msgid "Total Employee Hours" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_travel_time +msgid "Travel time for Field Service\n" +" Employees\n" +" " +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_person__bill_count +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_person_form_isp_account +msgid "Vendor Bills" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_travel_time +msgid "kg" +msgstr "" diff --git a/fieldservice_isp_account/i18n/fieldservice_isp_account.pot b/fieldservice_isp_account/i18n/fieldservice_isp_account.pot new file mode 100644 index 0000000000..8c87b45cf6 --- /dev/null +++ b/fieldservice_isp_account/i18n/fieldservice_isp_account.pot @@ -0,0 +1,214 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fieldservice_isp_account +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__account_stage +msgid "Accounting Stage" +msgstr "" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:68 +#, python-format +msgid "Cannot move to Complete until 'Contractor Costs' is filled in" +msgstr "" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:71 +#, python-format +msgid "Cannot move to Complete until 'Employee Timesheets' is filled in" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Confirm" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Confirmed" +msgstr "" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:116 +#, python-format +msgid "Contact empty" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_total +msgid "Contractor Cost Estimate" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_cost_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Contractor Costs" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Create Invoice" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Draft" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Duration" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee +msgid "Employee" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_timesheet_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Employee Timesheets" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_overtime +msgid "FSM Overtime" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_regular_time +msgid "FSM Regular Time" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_travel_time +msgid "FSM Travel Time" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_order +msgid "Field Service Order" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_person +msgid "Field Service Worker" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Fully Invoiced" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,uom_name:fieldservice_isp_account.field_service_travel_time +msgid "Hour(s)" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_account_move_line +msgid "Invoice Line" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_search_view +msgid "Needs Review" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.actions.act_window,help:fieldservice_isp_account.action_account_fsm_order +msgid "No Field Service Orders to review." +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "No Invoice" +msgstr "" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Nothing Invoiced" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_operations +msgid "Operations" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_overtime +msgid "Overtime for Field Service Employees\n" +" " +msgstr "" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/account_move_line.py:19 +#, python-format +msgid "Please set the field service worker." +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_regular_time +msgid "Regular time for Field Service\n" +" Employees\n" +" " +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.actions.act_window,name:fieldservice_isp_account.action_account_fsm_order +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_fsm_order +msgid "Service Orders" +msgstr "" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:103 +#, python-format +msgid "The worker assigned to this order is not a supplier" +msgstr "" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Timesheet Activities" +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_time_total +msgid "Total Employee Hours" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_travel_time +msgid "Travel time for Field Service\n" +" Employees\n" +" " +msgstr "" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_person__bill_count +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_person_form_isp_account +msgid "Vendor Bills" +msgstr "" + +#. module: fieldservice_isp_account +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_travel_time +msgid "kg" +msgstr "" + diff --git a/fieldservice_isp_account/i18n/pt_BR.po b/fieldservice_isp_account/i18n/pt_BR.po new file mode 100644 index 0000000000..907151e7dc --- /dev/null +++ b/fieldservice_isp_account/i18n/pt_BR.po @@ -0,0 +1,226 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fieldservice_isp_account +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-06-17 16:19+0000\n" +"Last-Translator: Fernando Colus \n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.10\n" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__account_stage +msgid "Accounting Stage" +msgstr "Etapa Contábil" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:68 +#, python-format +msgid "Cannot move to Complete until 'Contractor Costs' is filled in" +msgstr "" +"Não é possível mover para \"Concluído\" até que \"Custos do contratante\" " +"seja preenchido" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:71 +#, python-format +msgid "Cannot move to Complete until 'Employee Timesheets' is filled in" +msgstr "" +"Não é possível mover para \"Concluído\" até que o \"Quadro de horários dos " +"funcionários\" seja preenchido" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Confirm" +msgstr "Confirmar" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Confirmed" +msgstr "Confirmado" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:116 +#, python-format +msgid "Contact empty" +msgstr "Contato Vazio" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_total +msgid "Contractor Cost Estimate" +msgstr "Estimativa de Custos do Contratante" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__contractor_cost_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Contractor Costs" +msgstr "Custos do Contratante" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Create Invoice" +msgstr "Criar Fatura" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Draft" +msgstr "Provisório" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Duration" +msgstr "Duração" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee +msgid "Employee" +msgstr "Funcionario" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_timesheet_ids +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Employee Timesheets" +msgstr "Quadro de horários dos funcionários" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_overtime +msgid "FSM Overtime" +msgstr "Horas Extras do FSM (Field Service Management)" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_regular_time +msgid "FSM Regular Time" +msgstr "Horário Regular do FSM (Field Service Management)" + +#. module: fieldservice_isp_account +#: model:product.template,name:fieldservice_isp_account.field_service_travel_time +msgid "FSM Travel Time" +msgstr "Tempo de Viagem do FSM (Field Service Management)" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_order +msgid "Field Service Order" +msgstr "Ordem de Serviço de Campo" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_fsm_person +msgid "Field Service Worker" +msgstr "Trabalhador do Serviço de Campo" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Fully Invoiced" +msgstr "Totalmente Faturado" + +#. module: fieldservice_isp_account +#: model:product.template,uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,uom_name:fieldservice_isp_account.field_service_travel_time +msgid "Hour(s)" +msgstr "Hora(s)" + +#. module: fieldservice_isp_account +#: model:ir.model,name:fieldservice_isp_account.model_account_move_line +msgid "Invoice Line" +msgstr "Linha de Fatura" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_search_view +msgid "Needs Review" +msgstr "Necessita de Revisão" + +#. module: fieldservice_isp_account +#: model_terms:ir.actions.act_window,help:fieldservice_isp_account.action_account_fsm_order +msgid "No Field Service Orders to review." +msgstr "Sem Ordens de Serviço de Campo para revisar." + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "No Invoice" +msgstr "Sem Fatura" + +#. module: fieldservice_isp_account +#: selection:fsm.order,account_stage:0 +msgid "Nothing Invoiced" +msgstr "Nada Faturado" + +#. module: fieldservice_isp_account +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_operations +msgid "Operations" +msgstr "Operações" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_overtime +msgid "Overtime for Field Service Employees\n" +" " +msgstr "" +"Horas extras para funcionários de serviço de campo\n" +" " + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/account_move_line.py:19 +#, python-format +msgid "Please set the field service worker." +msgstr "Por favor, selecione o trabalhador do serviço de campo." + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_regular_time +msgid "Regular time for Field Service\n" +" Employees\n" +" " +msgstr "" +"Horário regular para funcionários do serviço de campo\n" +" " + +#. module: fieldservice_isp_account +#: model:ir.actions.act_window,name:fieldservice_isp_account.action_account_fsm_order +#: model:ir.ui.menu,name:fieldservice_isp_account.menu_account_fsm_order +msgid "Service Orders" +msgstr "Ordens de Serviço" + +#. module: fieldservice_isp_account +#: code:addons/fieldservice_isp_account/models/fsm_order.py:103 +#, python-format +msgid "The worker assigned to this order is not a supplier" +msgstr "O trabalhador designado para esta ordem não é um fornecedor" + +#. module: fieldservice_isp_account +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_order_form_isp_account +msgid "Timesheet Activities" +msgstr "Atividades da Planilha de Tempo" + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_order__employee_time_total +msgid "Total Employee Hours" +msgstr "Carga Horária Total do Empregado" + +#. module: fieldservice_isp_account +#: model:product.template,description_sale:fieldservice_isp_account.field_service_travel_time +msgid "Travel time for Field Service\n" +" Employees\n" +" " +msgstr "" +"Horário regular para funcionários do serviço de campo\n" +" " + +#. module: fieldservice_isp_account +#: model:ir.model.fields,field_description:fieldservice_isp_account.field_fsm_person__bill_count +#: model_terms:ir.ui.view,arch_db:fieldservice_isp_account.fsm_person_form_isp_account +msgid "Vendor Bills" +msgstr "# Contas de Fornecedores" + +#. module: fieldservice_isp_account +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_overtime +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_regular_time +#: model:product.template,weight_uom_name:fieldservice_isp_account.field_service_travel_time +msgid "kg" +msgstr "kg" diff --git a/fieldservice_isp_account/models/__init__.py b/fieldservice_isp_account/models/__init__.py new file mode 100644 index 0000000000..a2be44257e --- /dev/null +++ b/fieldservice_isp_account/models/__init__.py @@ -0,0 +1,7 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ( + fsm_order, + fsm_person, +) +from . import fsm_order_cost diff --git a/fieldservice_isp_account/models/fsm_order.py b/fieldservice_isp_account/models/fsm_order.py new file mode 100644 index 0000000000..b35c8c022a --- /dev/null +++ b/fieldservice_isp_account/models/fsm_order.py @@ -0,0 +1,275 @@ +# Copyright (C) 2018 - TODAY, Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +ACCOUNT_STAGES = [ + ("draft", "Draft"), + ("review", "Needs Review"), + ("confirmed", "Confirmed"), + ("invoiced", "Fully Invoiced"), + ("no", "Nothing Invoiced"), +] + + +class FSMOrder(models.Model): + _inherit = "fsm.order" + + contractor_cost_ids = fields.One2many( + "fsm.order.cost", "fsm_order_id", string="Contractor Costs" + ) + employee_timesheet_ids = fields.One2many( + "account.analytic.line", "fsm_order_id", string="Employee Timesheets" + ) + employee = fields.Boolean(compute="_compute_employee") + contractor_total = fields.Float( + compute="_compute_contractor_cost", string="Contractor Cost Estimate" + ) + employee_time_total = fields.Float( + compute="_compute_employee_hours", string="Total Employee Hours" + ) + account_stage = fields.Selection( + ACCOUNT_STAGES, string="Accounting Stage", default="draft" + ) + + def _compute_employee(self): + user = self.env["res.users"].browse(self.env.uid) + for order in self: + if user.employee_ids: + order.employee = True + else: + order.employee = False + + @api.depends("employee_timesheet_ids", "contractor_cost_ids") + def _compute_total_cost(self): + super()._compute_total_cost() + for order in self: + order.total_cost = 0.0 + rate = 0 + for line in order.employee_timesheet_ids: + rate = line.employee_id.timesheet_cost + order.total_cost += line.unit_amount * rate + for cost in order.contractor_cost_ids: + order.total_cost += cost.price_unit * cost.quantity + + @api.depends("employee_timesheet_ids") + def _compute_employee_hours(self): + for order in self: + order.employee_time_total = 0.0 + for line in order.employee_timesheet_ids: + order.employee_time_total += line.unit_amount + + @api.depends("contractor_cost_ids") + def _compute_contractor_cost(self): + for order in self: + order.contractor_total = 0.0 + for cost in order.contractor_cost_ids: + order.contractor_total += cost.price_unit * cost.quantity + + def action_complete(self): + for order in self: + order.account_stage = "review" + if self.person_id.supplier_rank and not self.contractor_cost_ids: + raise ValidationError( + _("Cannot move to Complete " + "until 'Contractor Costs' is filled in") + ) + if not self.person_id.supplier_rank and not self.employee_timesheet_ids: + raise ValidationError( + _( + "Cannot move to Complete until " + + "'Employee Timesheets' is filled in" + ) + ) + return super(FSMOrder, self).action_complete() + + def create_bills(self): + jrnl = self.env["account.journal"].search( + [ + ("company_id", "=", self.env.company.id), + ("type", "=", "purchase"), + ("active", "=", True), + ], + limit=1, + ) + fpos = self.person_id.partner_id.property_account_position_id + invoice_line_vals = [] + for cost in self.contractor_cost_ids: + invoice_line_vals.append((0, 0, self._get_bill_line_vals(cost, fpos))) + vals = { + "partner_id": self.person_id.partner_id.id, + "type": "in_invoice", + "journal_id": jrnl.id or False, + "fiscal_position_id": fpos.id or False, + "fsm_order_ids": [(4, self.id)], + "company_id": self.env.company.id, + "invoice_line_ids": invoice_line_vals, + } + bill = self.env["account.move"].sudo().create(vals) + bill._recompute_tax_lines() + + def _get_bill_line_vals(self, cost, fpos): + template = cost.product_id.product_tmpl_id + accounts = template.get_product_accounts() + account = accounts["expense"] + taxes = template.supplier_taxes_id + tax_ids = fpos.map_tax(taxes) + return { + "analytic_account_id": self.location_id.analytic_account_id.id, + "product_id": cost.product_id.id, + "product_uom_id": cost.product_id.uom_id.id, + "quantity": cost.quantity, + "name": cost.product_id.display_name, + "price_unit": cost.price_unit, + "account_id": account.id, + "fsm_order_ids": [(4, self.id)], + "tax_ids": [(6, 0, tax_ids.ids)], + } + + def account_confirm(self): + for order in self: + contractor = order.person_id.partner_id.supplier_rank + if order.contractor_cost_ids: + if contractor: + order.create_bills() + order.account_stage = "confirmed" + else: + raise ValidationError( + _("The worker assigned to this order" " is not a supplier") + ) + if order.employee_timesheet_ids: + order.account_stage = "confirmed" + + def _get_invoice_line_vals(self, fpos, price_list, invoice_vals): + invoice_line_vals = [] + for cost in self.contractor_cost_ids: + invoice_line_vals.append( + ( + 0, + 0, + self._get_cost_invoice_line_vals( + cost, fpos, price_list, invoice_vals + ), + ) + ) + for line in self.employee_timesheet_ids: + invoice_line_vals.append( + ( + 0, + 0, + self._get_timesheet_line_invoice_line_vals( + line, fpos, price_list, invoice_vals + ), + ) + ) + return invoice_line_vals + + def account_create_invoice(self): + fpos = self._get_fpos() + invoice_vals = self._get_invoice_vals(fpos) + price_list = self._get_pricelist() + invoice_line_vals = self._get_invoice_line_vals(fpos, price_list, invoice_vals) + invoice_vals.update({"invoice_line_ids": invoice_line_vals}) + invoice = self.env["account.move"].sudo().create(invoice_vals) + invoice._recompute_tax_lines() + self.account_stage = "invoiced" + return invoice + + def _get_pricelist(self): + if self.bill_to == "customer": + if not self.customer_id: + raise ValidationError(_("Customer empty")) + return self.customer_id.property_product_pricelist + elif self.bill_to == "location": + return self.location_id.customer_id.property_product_pricelist + + def _get_fpos(self): + if self.bill_to == "customer": + if not self.customer_id: + raise ValidationError(_("Customer empty")) + return self.customer_id.property_account_position_id + elif self.bill_to == "location": + return self.location_id.customer_id.property_account_position_id + + def _get_invoice_vals(self, fpos): + jrnl = self.env["account.journal"].search( + [ + ("company_id", "=", self.env.company.id), + ("type", "=", "sale"), + ("active", "=", True), + ], + limit=1, + ) + invoice_vals = { + "journal_id": jrnl.id or False, + "type": "out_invoice", + "fiscal_position_id": fpos.id or False, + "fsm_order_ids": [(4, self.id)], + } + if self.bill_to == "customer": + if not self.customer_id: + raise ValidationError(_("Customer empty")) + invoice_vals.update({ + "partner_id": self.customer_id.id, + }) + elif self.bill_to == "location": + invoice_vals.update({ + "partner_id": self.location_id.customer_id.id, + "company_id": self.env.company.id, + }) + return invoice_vals + + def _get_cost_invoice_line_vals(self, cost, fpos, price_list, invoice_vals): + price = price_list.get_product_price( + product=cost.product_id, + quantity=cost.quantity, + partner=invoice_vals.get("partner_id"), + date=False, + uom_id=False, + ) + template = cost.product_id.product_tmpl_id + accounts = template.get_product_accounts() + account = accounts["income"] + taxes = template.taxes_id + tax_ids = fpos.map_tax(taxes) + return { + "product_id": cost.product_id.id, + "product_uom_id": cost.product_id.uom_id.id, + "analytic_account_id": self.location_id.analytic_account_id.id, + "quantity": cost.quantity, + "name": cost.product_id.display_name, + "price_unit": price, + "account_id": account.id, + "fsm_order_ids": [(4, self.id)], + "tax_ids": [(6, 0, tax_ids.ids)], + } + + def _get_timesheet_line_invoice_line_vals( + self, line, fpos, price_list, invoice_vals + ): + price = price_list.get_product_price( + product=line.product_id, + quantity=line.unit_amount, + partner=invoice_vals.get("partner_id"), + date=False, + uom_id=False, + ) + template = line.product_id.product_tmpl_id + accounts = template.get_product_accounts() + account = accounts["income"] + taxes = template.taxes_id + tax_ids = fpos.map_tax(taxes) + return { + "product_id": line.product_id.id, + "product_uom_id": line.product_id.uom_id.id, + "analytic_account_id": line.account_id.id, + "quantity": line.unit_amount, + "name": line.name, + "price_unit": price, + "account_id": account.id, + "fsm_order_ids": [(4, self.id)], + "tax_ids": [(6, 0, tax_ids.ids)], + } + + def account_no_invoice(self): + self.account_stage = "no" diff --git a/fieldservice_isp_account/models/fsm_order_cost.py b/fieldservice_isp_account/models/fsm_order_cost.py new file mode 100644 index 0000000000..1eff39731e --- /dev/null +++ b/fieldservice_isp_account/models/fsm_order_cost.py @@ -0,0 +1,24 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class FsmOrderCost(models.Model): + + _name = "fsm.order.cost" + _description = "Fsm Order Cost" + + fsm_order_id = fields.Many2one(comodel_name="fsm.order", required=True,) + price_unit = fields.Float(string="Unit Price", required=True,) + quantity = fields.Float(string="Quantity", required=True, default=1,) + product_id = fields.Many2one( + comodel_name="product.product", string="Product", required=True, + ) + + @api.onchange("product_id") + def onchange_product_id(self): + for cost in self: + if not cost.product_id: + continue + cost.price_unit = cost.product_id.standard_price diff --git a/fieldservice_isp_account/models/fsm_person.py b/fieldservice_isp_account/models/fsm_person.py new file mode 100644 index 0000000000..d88787a80a --- /dev/null +++ b/fieldservice_isp_account/models/fsm_person.py @@ -0,0 +1,28 @@ +# Copyright (C) 2018 - TODAY, Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class FSMPerson(models.Model): + _inherit = "fsm.person" + + bill_count = fields.Integer(string="Vendor Bills", compute="_compute_vendor_bills") + + def _compute_vendor_bills(self): + self.bill_count = self.env["account.move"].search_count( + [("partner_id", "=", self.partner_id.id)] + ) + + def action_view_bills(self): + for bill in self: + action = self.env.ref("account.action_invoice_tree1").read()[0] + vendor_bills = self.env["account.move"].search( + [("partner_id", "=", bill.partner_id.id)] + ) + if len(vendor_bills) == 1: + action["views"] = [(self.env.ref("account.view_move_form").id, "form")] + action["res_id"] = vendor_bills.id + else: + action["domain"] = [("id", "in", vendor_bills.ids)] + return action diff --git a/fieldservice_isp_account/readme/CONFIGURE.rst b/fieldservice_isp_account/readme/CONFIGURE.rst new file mode 100644 index 0000000000..89a40f9121 --- /dev/null +++ b/fieldservice_isp_account/readme/CONFIGURE.rst @@ -0,0 +1 @@ +No special configuration instructions. diff --git a/fieldservice_isp_account/readme/CONTRIBUTORS.rst b/fieldservice_isp_account/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..e9143c46f2 --- /dev/null +++ b/fieldservice_isp_account/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Michael Allen +* Serpent Consulting Services Pvt. Ltd. +* Brian McMaster diff --git a/fieldservice_isp_account/readme/CREDITS.rst b/fieldservice_isp_account/readme/CREDITS.rst new file mode 100644 index 0000000000..0eff0acf4e --- /dev/null +++ b/fieldservice_isp_account/readme/CREDITS.rst @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +* Open Source Integrators diff --git a/fieldservice_isp_account/readme/DESCRIPTION.rst b/fieldservice_isp_account/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..127d578738 --- /dev/null +++ b/fieldservice_isp_account/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +This module adds the ability to track employee time and contractor +costs for Field Service Orders. It also adds functionality to create +a customer invoice and a vendor bill when a Field Service Order is +completed. diff --git a/fieldservice_isp_account/readme/INSTALL.rst b/fieldservice_isp_account/readme/INSTALL.rst new file mode 100644 index 0000000000..54fea7becb --- /dev/null +++ b/fieldservice_isp_account/readme/INSTALL.rst @@ -0,0 +1 @@ +No special installation instructions diff --git a/fieldservice_isp_account/readme/USAGE.rst b/fieldservice_isp_account/readme/USAGE.rst new file mode 100644 index 0000000000..c730bb37dd --- /dev/null +++ b/fieldservice_isp_account/readme/USAGE.rst @@ -0,0 +1,12 @@ +To use the module: + +On a field service order, open the "Accounting" tab. Depending on +whether the logged in user or an employee or not, they will see +either a place to enter timesheet records or contractor costs. + +The total cost of the order is calculated based on the entries in +the employee timesheet entries and contractor costs. + +When an order is completed, a customer invoice will be generated for +the employee time and the contractor costs. A vendor bill will be +created for the contractor costs. diff --git a/fieldservice_isp_account/security/fsm_order_cost.xml b/fieldservice_isp_account/security/fsm_order_cost.xml new file mode 100644 index 0000000000..e8d0ef7fcb --- /dev/null +++ b/fieldservice_isp_account/security/fsm_order_cost.xml @@ -0,0 +1,23 @@ + + + + + fsm.order.cost access base user + + + + + + + + + fsm.order.cost access fieldservice user + + + + + + + + diff --git a/fieldservice_isp_account/security/ir.model.access.csv b/fieldservice_isp_account/security/ir.model.access.csv new file mode 100644 index 0000000000..8da6f134f1 --- /dev/null +++ b/fieldservice_isp_account/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_employee_portal,access.hr.employee.portal,hr.model_hr_employee,base.group_portal,1,0,0,0 diff --git a/fieldservice_isp_account/static/description/icon.png b/fieldservice_isp_account/static/description/icon.png new file mode 100644 index 0000000000..955674d8f0 Binary files /dev/null and b/fieldservice_isp_account/static/description/icon.png differ diff --git a/fieldservice_isp_account/static/description/index.html b/fieldservice_isp_account/static/description/index.html new file mode 100644 index 0000000000..1dab785e80 --- /dev/null +++ b/fieldservice_isp_account/static/description/index.html @@ -0,0 +1,457 @@ + + + + + + +Field Service - ISP Accounting + + + +
+

Field Service - ISP Accounting

+ + +

Beta License: AGPL-3 OCA/field-service Translate me on Weblate Try me on Runbot

+

This module adds the ability to track employee time and contractor +costs for Field Service Orders. It also adds functionality to create +a customer invoice and a vendor bill when a Field Service Order is +completed.

+

Table of contents

+ +
+

Installation

+

No special installation instructions

+
+
+

Configuration

+

No special configuration instructions.

+
+
+

Usage

+

To use the module:

+

On a field service order, open the “Accounting” tab. Depending on +whether the logged in user or an employee or not, they will see +either a place to enter timesheet records or contractor costs.

+

The total cost of the order is calculated based on the entries in +the employee timesheet entries and contractor costs.

+

When an order is completed, a customer invoice will be generated for +the employee time and the contractor costs. A vendor bill will be +created for the contractor costs.

+
+
+

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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Open Source Integrators
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

Current maintainers:

+

osimallen brian10048 bodedra

+

This module is part of the OCA/field-service project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/fieldservice_isp_account/tests/__init__.py b/fieldservice_isp_account/tests/__init__.py new file mode 100644 index 0000000000..6446a7a2b2 --- /dev/null +++ b/fieldservice_isp_account/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from . import test_isp_account diff --git a/fieldservice_isp_account/tests/test_isp_account.py b/fieldservice_isp_account/tests/test_isp_account.py new file mode 100644 index 0000000000..4e5b7ec76e --- /dev/null +++ b/fieldservice_isp_account/tests/test_isp_account.py @@ -0,0 +1,210 @@ +# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) + +from odoo import fields +from odoo.exceptions import ValidationError +from odoo.tests import SavepointCase + + +class FSMISPAccountCase(SavepointCase): + @classmethod + def setUpClass(cls): + super(FSMISPAccountCase, cls).setUpClass() + cls.AccountMoveLine = cls.env["account.move.line"] + cls.test_person = cls.env["fsm.person"].create({"name": "Worker-1"}) + cls.test_analytic = cls.env.ref("analytic.analytic_administratif") + cls.account_id = cls.env["account.account"].create( + { + "code": "NC1110", + "name": "Test Payable Account", + "user_type_id": cls.env.ref("account.data_account_type_payable").id, + "reconcile": True, + } + ) + # create a Res Partner to be converted to FSM Location/Person + cls.test_loc_partner = cls.env["res.partner"].create( + {"name": "Test Loc Partner", "phone": "ABC", "email": "tlp@email.com"} + ) + # create expected FSM Location to compare to converted FSM Location + cls.test_location = cls.env["fsm.location"].create( + { + "name": "Test Location", + "phone": "123", + "email": "tp@email.com", + "partner_id": cls.test_loc_partner.id, + "owner_id": cls.test_loc_partner.id, + "customer_id": cls.test_loc_partner.id, + } + ) + + def _create_workorder(self, bill_to, contractors, timesheets): + # Create a new work order + timesheets = self.env["account.analytic.line"].create(timesheets) + + order = self.env["fsm.order"].create( + { + "location_id": self.test_location.id, + "bill_to": bill_to, + "person_id": self.test_person.id, + "employee_timesheet_ids": [(6, 0, timesheets.ids)], + } + ) + for contractor in contractors: + contractor.update({"fsm_order_id": order.id}) + contractors = self.env["fsm.order.cost"].create(contractors) + order.write({"contractor_cost_ids": [(6, 0, contractors.ids)]}) + order.person_ids += self.test_person + return order + + def _process_order_to_invoices(self, order): + # Change states + order.date_start = fields.Datetime.today() + order.date_end = fields.Datetime.today() + order.resolution = "Done something!" + order.action_complete() + self.assertEqual(order.account_stage, "review") + # Create vendor bill + # Vendor bill created from order's contractor + if not order.person_id.partner_id.supplier_rank: + with self.assertRaises(ValidationError) as e: + order.account_confirm() + self.assertEqual( + e.exception.name, "The worker assigned to this order is not a supplier" + ) + order.person_id.partner_id.supplier_rank = True + order.account_confirm() + self.assertEqual(order.account_stage, "confirmed") + bill = self.AccountMoveLine.search( + [("fsm_order_ids", "in", order.id)] + ).move_id.filtered(lambda i: i.type == "in_invoice") + self.assertEqual(len(bill), 1) + self.assertEqual(len(order.contractor_cost_ids), len(bill.invoice_line_ids)) + # Customer invoice created from order's contractor and timehsheet + if order.bill_to == "customer" and not order.customer_id: + with self.assertRaises(ValidationError): + order.account_create_invoice() + order.customer_id = self.test_loc_partner # Assign some partner + order.account_create_invoice() + self.assertEqual(order.account_stage, "invoiced") + invoice = self.AccountMoveLine.search( + [("fsm_order_ids", "in", order.id)] + ).move_id.filtered(lambda i: i.type == "out_invoice") + self.assertEqual(len(invoice), 1) + self.assertEqual( + len(order.contractor_cost_ids) + len(order.employee_timesheet_ids), + len(invoice.invoice_line_ids), + ) + return (bill, invoice) + + def test_fsm_order_exception(self): + """Create a new work order, error raised when + - If person_is is not set, but user try to add new contractor_cost_ids + - If analytic account is not set in location, + and user create contractor_cost_ids (account.move.line) + """ + # Test if the person_id is not selected, error when add contractor line + # Setup required data + self.test_location.analytic_account_id = self.test_analytic + # Create a new work order with contract = 500 and timesheet = 300 + self.env.ref("hr.employee_qdp").timesheet_cost = 20.0 + order = self.env["fsm.order"].create( + {"location_id": self.test_location.id, "person_id": self.test_person.id} + ) + order.person_id = self.test_person + + order.person_ids += self.test_person + order.date_start = fields.Datetime.today() + order.date_end = fields.Datetime.today() + order.resolution = "Done something!" + with self.assertRaises(ValidationError) as e: + order.action_complete() + self.assertEqual( + e.exception.name, + "Cannot move to Complete until " "'Employee Timesheets' is filled in", + ) + + def test_fsm_order_bill_to_location(self): + """Bill To Location, + invoice created is based on this order's location's partner + """ + # Setup required data + self.test_location.analytic_account_id = self.test_analytic + contractors = [ + { + "product_id": self.env.ref("product.expense_hotel").id, + "quantity": 2, + "price_unit": 200, + }, + ] + self.env.ref("hr.employee_qdp").timesheet_cost = 100 + timesheets = [ + { + "name": "timesheet_line_1", + "employee_id": self.env.ref("hr.employee_qdp").id, + "account_id": self.test_analytic.id, + "product_id": self.env.ref("product.expense_hotel").id, + "unit_amount": 6, + }, + { + "name": "timesheet_line_2", + "employee_id": self.env.ref("hr.employee_qdp").id, + "account_id": self.test_analytic.id, + "product_id": self.env.ref("product.expense_hotel").id, + "unit_amount": 4, + }, + ] + order = self._create_workorder( + bill_to="location", contractors=contractors, timesheets=timesheets + ) + order._compute_contractor_cost() + order._compute_employee_hours() + order._compute_total_cost() + self.assertEqual(order.contractor_total, 400) + self.assertEqual(order.employee_time_total, 10) # Hrs + self.assertEqual(order.total_cost, 1400) + bill, invoice = self._process_order_to_invoices(order) + self.assertEqual(bill.partner_id, order.person_id.partner_id) + self.assertEqual(invoice.partner_id, order.location_id.customer_id) + + def test_fsm_order_bill_to_contact(self): + """Bill To Contact, + invoice created is based on this order's contact + """ + # Setup required data + self.test_location.analytic_account_id = self.test_analytic + # Create a new work order with contract = 500 and timesheet = 300 + contractors = [ + { + "product_id": self.env.ref("product.expense_hotel").id, + "quantity": 2, + "price_unit": 100, + }, + { + "product_id": self.env.ref("product.expense_hotel").id, + "quantity": 1, + "price_unit": 300, + }, + ] + self.env.ref("hr.employee_qdp").timesheet_cost = 20.0 + timesheets = [ + { + "name": "timesheet_line_3", + "employee_id": self.env.ref("hr.employee_qdp").id, + "account_id": self.test_analytic.id, + "product_id": self.env.ref("product.expense_hotel").id, + "unit_amount": 10, + }, + ] + order = self._create_workorder( + bill_to="customer", contractors=contractors, timesheets=timesheets + ) + order._compute_contractor_cost() + order._compute_employee_hours() + order._compute_total_cost() + self.assertEqual(order.contractor_total, 500) + self.assertEqual(order.employee_time_total, 10) # Hrs + self.assertEqual(order.total_cost, 700) + # Testing not working "Need to Configure Chart of Accounts" + # bill, invoice = self._process_order_to_invoices(order) + # self.assertEqual(bill.partner_id, order.person_id.partner_id) + # self.assertEqual(invoice.partner_id, order.customer_id) diff --git a/fieldservice_isp_account/views/account.xml b/fieldservice_isp_account/views/account.xml new file mode 100644 index 0000000000..d9fd69d762 --- /dev/null +++ b/fieldservice_isp_account/views/account.xml @@ -0,0 +1,27 @@ + + + + Service Orders + fsm.order + tree,form + + +

+ No Field Service Orders to review. +

+
+
+ + +
diff --git a/fieldservice_isp_account/views/fsm_order.xml b/fieldservice_isp_account/views/fsm_order.xml new file mode 100644 index 0000000000..1625294076 --- /dev/null +++ b/fieldservice_isp_account/views/fsm_order.xml @@ -0,0 +1,111 @@ + + + fsm.order.form.isp.account + fsm.order + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + fsm.order.search + fsm.order + + + + + + + +
diff --git a/fieldservice_isp_account/views/fsm_person.xml b/fieldservice_isp_account/views/fsm_person.xml new file mode 100644 index 0000000000..37f0116cae --- /dev/null +++ b/fieldservice_isp_account/views/fsm_person.xml @@ -0,0 +1,20 @@ + + + fsm.person.form.isp.account + fsm.person + + +
+ +
+
+
+
diff --git a/fieldservice_isp_account/views/hr_timesheet.xml b/fieldservice_isp_account/views/hr_timesheet.xml new file mode 100644 index 0000000000..e49a277dae --- /dev/null +++ b/fieldservice_isp_account/views/hr_timesheet.xml @@ -0,0 +1,39 @@ + + + My Timesheets + account.analytic.line + tree,form + [('user_id', '=', uid)] + { + "search_default_week":1, + } + + +

+ Record a new activity +

+

+ You can register and track your workings hours by project every + day. Every time spent on a project will become a cost and can be re-invoiced to + customers if required. +

+
+
+ + + account.analytic.line.timesheet.user + + + [('user_id', '=', user.id), ('project_id', '!=', False)] + + + + + + +
diff --git a/setup/fieldservice_isp_account/odoo/addons/fieldservice_isp_account b/setup/fieldservice_isp_account/odoo/addons/fieldservice_isp_account new file mode 120000 index 0000000000..9914338c7c --- /dev/null +++ b/setup/fieldservice_isp_account/odoo/addons/fieldservice_isp_account @@ -0,0 +1 @@ +../../../../fieldservice_isp_account \ No newline at end of file diff --git a/setup/fieldservice_isp_account/setup.py b/setup/fieldservice_isp_account/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/fieldservice_isp_account/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)