diff --git a/.isort.cfg b/.isort.cfg index 5751c40dd2..98b216f744 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -9,4 +9,4 @@ line_length=88 known_odoo=odoo known_odoo_addons=odoo.addons sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER -known_third_party= +known_third_party=setuptools diff --git a/fieldservice/README.rst b/fieldservice/README.rst index 287ca2641f..ed7a4d08c3 100644 --- a/fieldservice/README.rst +++ b/fieldservice/README.rst @@ -14,13 +14,13 @@ Field Service :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/12.0/fieldservice + :target: https://github.com/OCA/field-service/tree/13.0/fieldservice :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-12-0/field-service-12-0-fieldservice + :target: https://translation.odoo-community.org/projects/field-service-13-0/field-service-13-0-fieldservice :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/12.0 + :target: https://runbot.odoo-community.org/runbot/264/13.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -190,7 +190,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -246,6 +246,6 @@ Current `maintainers `__: |maintainer-wolfhall| |maintainer-max3903| -This module is part of the `OCA/field-service `_ project on GitHub. +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/__manifest__.py b/fieldservice/__manifest__.py index f0f6f29f39..594b0f5341 100644 --- a/fieldservice/__manifest__.py +++ b/fieldservice/__manifest__.py @@ -2,62 +2,57 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - 'name': 'Field Service', - 'summary': 'Manage Field Service Locations, Workers and Orders', - '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': [ - 'base_geolocalize', - 'web_timeline', - 'resource', - 'contacts', - 'partner_fax' + "name": "Field Service", + "summary": "Manage Field Service Locations, Workers and Orders", + "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": [ + "base_territory", + "base_geolocalize", + "web_timeline", + "resource", + "contacts", + "partner_fax", ], - 'data': [ - 'data/ir_sequence.xml', - 'data/mail_message_subtype.xml', - 'data/module_category.xml', - 'data/fsm_stage.xml', - 'data/fsm_team.xml', - 'security/res_groups.xml', - 'security/ir.model.access.csv', - 'security/ir_rule.xml', - 'report/fsm_order_report_template.xml', - 'views/res_config_settings.xml', - 'views/fsm_territory.xml', - 'views/fsm_stage.xml', - 'views/fsm_tag.xml', - 'views/res_partner.xml', - 'views/fsm_location.xml', - 'views/fsm_location_person.xml', - 'views/fsm_person.xml', - 'views/fsm_order.xml', - 'views/fsm_order_type.xml', - 'views/fsm_route.xml', - 'views/fsm_schedule.xml', - 'views/fsm_category.xml', - 'views/fsm_equipment.xml', - 'views/fsm_template.xml', - 'views/fsm_team.xml', - 'views/menu.xml', - 'wizard/fsm_wizard.xml', + "data": [ + "data/ir_sequence.xml", + "data/mail_message_subtype.xml", + "data/module_category.xml", + "data/fsm_stage.xml", + "data/fsm_team.xml", + "security/res_groups.xml", + "security/ir.model.access.csv", + "security/ir_rule.xml", + "report/fsm_order_report_template.xml", + "views/res_config_settings.xml", + "views/res_territory.xml", + "views/fsm_stage.xml", + "views/fsm_tag.xml", + "views/res_partner.xml", + "views/fsm_location.xml", + "views/fsm_location_person.xml", + "views/fsm_person.xml", + "views/fsm_order.xml", + "views/fsm_order_type.xml", + "views/fsm_schedule.xml", + "views/fsm_category.xml", + "views/fsm_equipment.xml", + "views/fsm_template.xml", + "views/fsm_team.xml", + "views/menu.xml", + "wizard/fsm_wizard.xml", ], - 'demo': [ - 'demo/fsm_demo.xml', - 'demo/fsm_equipment.xml', - 'demo/fsm_location.xml', - 'demo/fsm_person.xml', - ], - 'qweb': [ - 'static/src/xml/*.xml', - ], - 'application': True, - 'license': 'AGPL-3', - 'development_status': 'Beta', - 'maintainers': [ - 'wolfhall', - 'max3903', + "demo": [ + "demo/fsm_demo.xml", + "demo/fsm_equipment.xml", + "demo/fsm_location.xml", + "demo/fsm_person.xml", ], + "qweb": ["static/src/xml/*.xml"], + "application": True, + "license": "AGPL-3", + "development_status": "Beta", + "maintainers": ["wolfhall", "max3903"], } diff --git a/fieldservice/data/ir_sequence.xml b/fieldservice/data/ir_sequence.xml index 37b4f5cea6..5f6310c670 100644 --- a/fieldservice/data/ir_sequence.xml +++ b/fieldservice/data/ir_sequence.xml @@ -9,13 +9,4 @@ - - - FSM Route - fsm.route - SR - 3 - - - diff --git a/fieldservice/demo/fsm_location.xml b/fieldservice/demo/fsm_location.xml index 9e89869109..0d1bfb1ead 100644 --- a/fieldservice/demo/fsm_location.xml +++ b/fieldservice/demo/fsm_location.xml @@ -25,10 +25,10 @@ - - - - + + + + New the old inventory 14 Tower A, main street Bangkok, Thailand diff --git a/fieldservice/models/__init__.py b/fieldservice/models/__init__.py index 1e7ea394cf..3b28d3665f 100644 --- a/fieldservice/models/__init__.py +++ b/fieldservice/models/__init__.py @@ -5,7 +5,7 @@ res_config_settings, fsm_category, fsm_template, - fsm_territory, + res_territory, fsm_tag, fsm_stage, fsm_team, @@ -15,5 +15,5 @@ res_partner, fsm_equipment, fsm_order, - fsm_route, + fsm_order_type, ) diff --git a/fieldservice/models/fsm_category.py b/fieldservice/models/fsm_category.py index 48f764cc70..bb62bd0980 100644 --- a/fieldservice/models/fsm_category.py +++ b/fieldservice/models/fsm_category.py @@ -1,30 +1,32 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields, models class FSMCategory(models.Model): - _name = 'fsm.category' - _description = 'Field Service Worker Category' + _name = "fsm.category" + _description = "Field Service Worker Category" - name = fields.Char(string='Name', required='True') - parent_id = fields.Many2one('fsm.category', string='Parent') - color = fields.Integer('Color Index', default=10) + name = fields.Char(string="Name", required="True") + parent_id = fields.Many2one("fsm.category", string="Parent") + color = fields.Integer("Color Index", default=10) full_name = fields.Char(string="Full Name", compute="_compute_full_name") - description = fields.Char(string='Description') + description = fields.Char(string="Description") company_id = fields.Many2one( - 'res.company', string='Company', required=True, index=True, + "res.company", + string="Company", + required=True, + index=True, default=lambda self: self.env.user.company_id, - help="Company related to this category") + help="Company related to this category", + ) - _sql_constraints = [ - ('name_uniq', 'unique (name)', "Category name already exists!"), - ] + _sql_constraints = [("name_uniq", "unique (name)", "Category name already exists!")] def _compute_full_name(self): for record in self: if record.parent_id: - record.full_name = (record.parent_id.name + '/' + record.name) + record.full_name = record.parent_id.name + "/" + record.name else: record.full_name = record.name diff --git a/fieldservice/models/fsm_equipment.py b/fieldservice/models/fsm_equipment.py index 8733c3c6a9..88808276d3 100644 --- a/fieldservice/models/fsm_equipment.py +++ b/fieldservice/models/fsm_equipment.py @@ -1,98 +1,106 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models, api +from odoo import api, fields, models class FSMEquipment(models.Model): - _name = 'fsm.equipment' - _description = 'Field Service Equipment' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = "fsm.equipment" + _description = "Field Service Equipment" + _inherit = ["mail.thread", "mail.activity.mixin"] - name = fields.Char(string='Name', required='True') - person_id = fields.Many2one('fsm.person', string='Assigned Operator') - location_id = fields.Many2one('fsm.location', string='Assigned Location') - notes = fields.Text(string='Notes') - territory_id = fields.Many2one('res.territory', string='Territory') - branch_id = fields.Many2one('fsm.branch', string='Branch') - district_id = fields.Many2one('fsm.district', string='District') - region_id = fields.Many2one('fsm.region', string='Region') - current_location_id = fields.Many2one('fsm.location', - string='Current Location', - required=True) + name = fields.Char(string="Name", required="True") + person_id = fields.Many2one("fsm.person", string="Assigned Operator") + location_id = fields.Many2one("fsm.location", string="Assigned Location") + notes = fields.Text(string="Notes") + territory_id = fields.Many2one("res.territory", string="Territory") + branch_id = fields.Many2one("res.branch", string="Branch") + district_id = fields.Many2one("res.district", string="District") + region_id = fields.Many2one("res.region", string="Region") + current_location_id = fields.Many2one( + "fsm.location", string="Current Location", required=True + ) - managed_by_id = fields.Many2one('res.partner', string='Managed By') - owned_by_id = fields.Many2one('res.partner', string='Owned By') - parent_id = fields.Many2one('fsm.equipment', string='Parent') - child_ids = fields.One2many('fsm.equipment', 'parent_id', - string='Children') - stage_id = fields.Many2one('fsm.stage', string='Stage', - track_visibility='onchange', - index=True, copy=False, - group_expand='_read_group_stage_ids', - default=lambda self: self._default_stage_id()) + managed_by_id = fields.Many2one("res.partner", string="Managed By") + owned_by_id = fields.Many2one("res.partner", string="Owned By") + parent_id = fields.Many2one("fsm.equipment", string="Parent") + child_ids = fields.One2many("fsm.equipment", "parent_id", string="Children") + stage_id = fields.Many2one( + "fsm.stage", + string="Stage", + track_visibility="onchange", + index=True, + copy=False, + group_expand="_read_group_stage_ids", + default=lambda self: self._default_stage_id(), + ) hide = fields.Boolean(default=False) - color = fields.Integer('Color Index') + color = fields.Integer("Color Index") company_id = fields.Many2one( - 'res.company', string='Company', required=True, index=True, + "res.company", + string="Company", + required=True, + index=True, default=lambda self: self.env.user.company_id, - help="Company related to this equipment") + help="Company related to this equipment", + ) _sql_constraints = [ - ('name_uniq', 'unique (name)', "Equipment name already exists!"), + ("name_uniq", "unique (name)", "Equipment name already exists!") ] - @api.onchange('location_id') + @api.onchange("location_id") def _onchange_location_id(self): self.territory_id = self.location_id.territory_id - @api.onchange('territory_id') + @api.onchange("territory_id") def _onchange_territory_id(self): self.branch_id = self.territory_id.branch_id - @api.onchange('branch_id') + @api.onchange("branch_id") def _onchange_branch_id(self): self.district_id = self.branch_id.district_id - @api.onchange('district_id') + @api.onchange("district_id") def _onchange_district_id(self): self.region_id = self.district_id.region_id @api.model def _read_group_stage_ids(self, stages, domain, order): - stage_ids = self.env['fsm.stage'].search([('stage_type', - '=', 'equipment')]) + stage_ids = self.env["fsm.stage"].search([("stage_type", "=", "equipment")]) return stage_ids def _default_stage_id(self): - return self.env['fsm.stage'].search([('stage_type', '=', 'equipment'), - ('sequence', '=', '1')]) + return self.env["fsm.stage"].search( + [("stage_type", "=", "equipment"), ("sequence", "=", "1")] + ) def next_stage(self): seq = self.stage_id.sequence - next_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'equipment'), ('sequence', '>', seq)], - order="sequence asc") + next_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "equipment"), ("sequence", ">", seq)], + order="sequence asc", + ) if next_stage: self.stage_id = next_stage[0] self._onchange_stage_id() def previous_stage(self): seq = self.stage_id.sequence - prev_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'equipment'), ('sequence', '<', seq)], - order="sequence desc") + prev_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "equipment"), ("sequence", "<", seq)], + order="sequence desc", + ) if prev_stage: self.stage_id = prev_stage[0] self._onchange_stage_id() - @api.onchange('stage_id') + @api.onchange("stage_id") def _onchange_stage_id(self): # get last stage - heighest_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'equipment')], - order='sequence desc', - limit=1) + heighest_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "equipment")], order="sequence desc", limit=1 + ) if self.stage_id.name == heighest_stage.name: self.hide = True else: diff --git a/fieldservice/models/fsm_location.py b/fieldservice/models/fsm_location.py index 931a864458..5d53696c9f 100644 --- a/fieldservice/models/fsm_location.py +++ b/fieldservice/models/fsm_location.py @@ -1,89 +1,110 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ +from odoo import _, api, fields, models from odoo.exceptions import ValidationError class FSMLocation(models.Model): - _name = 'fsm.location' - _inherits = {'res.partner': 'partner_id'} - _inherit = ['mail.thread', 'mail.activity.mixin'] - _description = 'Field Service Location' - - ref = fields.Char(string='Internal Reference', copy=False) - direction = fields.Char(string='Directions') - partner_id = fields.Many2one('res.partner', string='Related Partner', - required=True, ondelete='restrict', - delegate=True, auto_join=True) - owner_id = fields.Many2one('res.partner', string='Related Owner', - required=True, ondelete='restrict', - auto_join=True) - customer_id = fields.Many2one('res.partner', string='Billed Customer', - required=True, ondelete='restrict', - auto_join=True, - track_visibility='onchange') - contact_id = fields.Many2one('res.partner', string='Primary Contact', - domain="[('is_company', '=', False)," - " ('fsm_location', '=', False)]", - index=True) - description = fields.Char(string='Description') - territory_id = fields.Many2one('res.territory', string='Territory') - branch_id = fields.Many2one('fsm.branch', string='Branch') - district_id = fields.Many2one('fsm.district', string='District') - region_id = fields.Many2one('fsm.region', string='Region') - territory_manager_id = fields.Many2one(string='Primary Assignment', - related='territory_id.person_id') - district_manager_id = fields.Many2one(string='District Manager', - related='district_id.partner_id') - region_manager_id = fields.Many2one(string='Region Manager', - related='region_id.partner_id') - branch_manager_id = fields.Many2one(string='Branch Manager', - related='branch_id.partner_id') - - calendar_id = fields.Many2one('resource.calendar', - string='Office Hours') - fsm_parent_id = fields.Many2one('fsm.location', string='Parent', - index=True) + _name = "fsm.location" + _inherits = {"res.partner": "partner_id"} + _inherit = ["mail.thread", "mail.activity.mixin"] + _description = "Field Service Location" + + ref = fields.Char(string="Internal Reference", copy=False) + direction = fields.Char(string="Directions") + partner_id = fields.Many2one( + "res.partner", + string="Related Partner", + required=True, + ondelete="restrict", + delegate=True, + auto_join=True, + ) + owner_id = fields.Many2one( + "res.partner", + string="Related Owner", + required=True, + ondelete="restrict", + auto_join=True, + ) + customer_id = fields.Many2one( + "res.partner", + string="Billed Customer", + required=True, + ondelete="restrict", + auto_join=True, + track_visibility="onchange", + ) + contact_id = fields.Many2one( + "res.partner", + string="Primary Contact", + domain="[('is_company', '=', False)," " ('fsm_location', '=', False)]", + index=True, + ) + description = fields.Char(string="Description") + territory_id = fields.Many2one("res.territory", string="Territory") + branch_id = fields.Many2one("res.branch", string="Branch") + district_id = fields.Many2one("res.district", string="District") + region_id = fields.Many2one("res.region", string="Region") + territory_manager_id = fields.Many2one( + string="Primary Assignment", related="territory_id.person_id" + ) + district_manager_id = fields.Many2one( + string="District Manager", related="district_id.partner_id" + ) + region_manager_id = fields.Many2one( + string="Region Manager", related="region_id.partner_id" + ) + branch_manager_id = fields.Many2one( + string="Branch Manager", related="branch_id.partner_id" + ) + + calendar_id = fields.Many2one("resource.calendar", string="Office Hours") + fsm_parent_id = fields.Many2one("fsm.location", string="Parent", index=True) notes = fields.Text(string="Location Notes") - person_ids = fields.One2many('fsm.location.person', - 'location_id', - string='Workers') - contact_count = fields.Integer(string='Contacts Count', - compute='_compute_contact_ids') - equipment_count = fields.Integer(string='Equipment', - compute='_compute_equipment_ids') - sublocation_count = fields.Integer(string='Sub Locations', - compute='_compute_sublocation_ids') - complete_name = fields.Char(string='Complete Name', - compute='_compute_complete_name', - store=True) + person_ids = fields.One2many("fsm.location.person", "location_id", string="Workers") + contact_count = fields.Integer( + string="Contacts Count", compute="_compute_contact_ids" + ) + equipment_count = fields.Integer( + string="Equipment", compute="_compute_equipment_ids" + ) + sublocation_count = fields.Integer( + string="Sub Locations", compute="_compute_sublocation_ids" + ) + complete_name = fields.Char( + string="Complete Name", compute="_compute_complete_name", store=True + ) hide = fields.Boolean(default=False) - stage_id = fields.Many2one('fsm.stage', string='Stage', - track_visibility='onchange', - index=True, copy=False, - group_expand='_read_group_stage_ids', - default=lambda self: self._default_stage_id()) - - @api.depends('partner_id.name', 'fsm_parent_id.complete_name') + stage_id = fields.Many2one( + "fsm.stage", + string="Stage", + track_visibility="onchange", + index=True, + copy=False, + group_expand="_read_group_stage_ids", + default=lambda self: self._default_stage_id(), + ) + + @api.depends("partner_id.name", "fsm_parent_id.complete_name") def _compute_complete_name(self): for loc in self: if loc.fsm_parent_id: if loc.ref: - loc.complete_name = '%s / [%s] %s' % ( - loc.fsm_parent_id.complete_name, loc.ref, - loc.partner_id.name) + loc.complete_name = "{} / [{}] {}".format( + loc.fsm_parent_id.complete_name, loc.ref, loc.partner_id.name + ) else: - loc.complete_name = '%s / %s' % ( - loc.fsm_parent_id.complete_name, loc.partner_id.name) + loc.complete_name = "{} / {}".format( + loc.fsm_parent_id.complete_name, loc.partner_id.name + ) else: if loc.ref: - loc.complete_name = '[%s] %s' % (loc.ref, - loc.partner_id.name) + loc.complete_name = "[{}] {}".format(loc.ref, loc.partner_id.name) else: loc.complete_name = loc.partner_id.name - @api.multi def name_get(self): results = [] for rec in self: @@ -91,59 +112,65 @@ def name_get(self): return results @api.model - def name_search(self, name, args=None, operator='ilike', limit=100): + def name_search(self, name, args=None, operator="ilike", limit=100): args = args or [] recs = self.browse() if name: - recs = self.search([('ref', 'ilike', name)] + args, limit=limit) + recs = self.search([("ref", "ilike", name)] + args, limit=limit) if not recs: - recs = self.search([('name', operator, name)] + args, limit=limit) + recs = self.search([("name", operator, name)] + args, limit=limit) return recs.name_get() - _sql_constraints = [('fsm_location_ref_uniq', 'unique (ref)', - 'This internal reference already exists!')] + _sql_constraints = [ + ( + "fsm_location_ref_uniq", + "unique (ref)", + "This internal reference already exists!", + ) + ] @api.model def _read_group_stage_ids(self, stages, domain, order): - stage_ids = self.env['fsm.stage'].search([('stage_type', - '=', 'location')]) + stage_ids = self.env["fsm.stage"].search([("stage_type", "=", "location")]) return stage_ids def _default_stage_id(self): - return self.env['fsm.stage'].search([('stage_type', '=', 'location'), - ('sequence', '=', '1')]) + return self.env["fsm.stage"].search( + [("stage_type", "=", "location"), ("sequence", "=", "1")] + ) def next_stage(self): seq = self.stage_id.sequence - next_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'location'), ('sequence', '>', seq)], - order="sequence asc") + next_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "location"), ("sequence", ">", seq)], + order="sequence asc", + ) if next_stage: self.stage_id = next_stage[0] self._onchange_stage_id() def previous_stage(self): seq = self.stage_id.sequence - prev_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'location'), ('sequence', '<', seq)], - order="sequence desc") + prev_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "location"), ("sequence", "<", seq)], + order="sequence desc", + ) if prev_stage: self.stage_id = prev_stage[0] self._onchange_stage_id() - @api.onchange('stage_id') + @api.onchange("stage_id") def _onchange_stage_id(self): # get last stage - heighest_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'location')], - order='sequence desc', - limit=1) + heighest_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "location")], order="sequence desc", limit=1 + ) if self.stage_id.name == heighest_stage.name: self.hide = True else: self.hide = False - @api.onchange('fsm_parent_id') + @api.onchange("fsm_parent_id") def _onchange_fsm_parent_id(self): self.owner_id = self.fsm_parent_id.owner_id or False self.customer_id = self.fsm_parent_id.customer_id or False @@ -158,62 +185,63 @@ def _onchange_fsm_parent_id(self): self.tz = self.fsm_parent_id.tz or False self.territory_id = self.fsm_parent_id.territory_id or False - @api.onchange('territory_id') + @api.onchange("territory_id") def _onchange_territory_id(self): self.territory_manager_id = self.territory_id.person_id or False self.branch_id = self.territory_id.branch_id or False if self.env.user.company_id.auto_populate_persons_on_location: for person in self.territory_id.person_ids: - self.env['fsm.location.person'].create({ - 'location_id': self.id, - 'person_id': person.id, - 'sequence': 10, - }) + self.env["fsm.location.person"].create( + {"location_id": self.id, "person_id": person.id, "sequence": 10} + ) - @api.onchange('branch_id') + @api.onchange("branch_id") def _onchange_branch_id(self): - self.branch_manager_id = \ - self.territory_id.branch_id.partner_id or False + self.branch_manager_id = self.territory_id.branch_id.partner_id or False self.district_id = self.branch_id.district_id or False - @api.onchange('district_id') + @api.onchange("district_id") def _onchange_district_id(self): - self.district_manager_id = \ - self.branch_id.district_id.partner_id or False + self.district_manager_id = self.branch_id.district_id.partner_id or False self.region_id = self.district_id.region_id or False - @api.onchange('region_id') + @api.onchange("region_id") def _onchange_region_id(self): self.region_manager_id = self.region_id.partner_id or False def comp_count(self, contact, equipment, loc): if equipment: for child in loc: - child_locs = self.env['fsm.location'].\ - search([('fsm_parent_id', '=', child.id)]) - equip = self.env['fsm.equipment'].\ - search_count([('location_id', '=', child.id)]) + child_locs = self.env["fsm.location"].search( + [("fsm_parent_id", "=", child.id)] + ) + equip = self.env["fsm.equipment"].search_count( + [("location_id", "=", child.id)] + ) if child_locs: for loc in child_locs: equip += loc.comp_count(0, 1, loc) return equip elif contact: for child in loc: - child_locs = self.env['fsm.location'].\ - search([('fsm_parent_id', '=', child.id)]) - con = self.env['res.partner'].\ - search_count([('service_location_id', - '=', child.id)]) + child_locs = self.env["fsm.location"].search( + [("fsm_parent_id", "=", child.id)] + ) + con = self.env["res.partner"].search_count( + [("service_location_id", "=", child.id)] + ) if child_locs: for loc in child_locs: con += loc.comp_count(1, 0, loc) return con else: for child in loc: - child_locs = self.env['fsm.location'].\ - search([('fsm_parent_id', '=', child.id)]) - subloc = self.env['fsm.location'].\ - search_count([('fsm_parent_id', '=', child.id)]) + child_locs = self.env["fsm.location"].search( + [("fsm_parent_id", "=", child.id)] + ) + subloc = self.env["fsm.location"].search_count( + [("fsm_parent_id", "=", child.id)] + ) if child_locs: for loc in child_locs: subloc += loc.comp_count(0, 0, loc) @@ -222,142 +250,142 @@ def comp_count(self, contact, equipment, loc): def get_action_views(self, contact, equipment, loc): if equipment: for child in loc: - child_locs = self.env['fsm.location'].\ - search([('fsm_parent_id', '=', child.id)]) - equip = self.env['fsm.equipment'].\ - search([('location_id', '=', child.id)]) + child_locs = self.env["fsm.location"].search( + [("fsm_parent_id", "=", child.id)] + ) + equip = self.env["fsm.equipment"].search( + [("location_id", "=", child.id)] + ) if child_locs: for loc in child_locs: equip += loc.get_action_views(0, 1, loc) return equip elif contact: for child in loc: - child_locs = self.env['fsm.location'].\ - search([('fsm_parent_id', '=', child.id)]) - con = self.env['res.partner'].\ - search([('service_location_id', '=', child.id)]) + child_locs = self.env["fsm.location"].search( + [("fsm_parent_id", "=", child.id)] + ) + con = self.env["res.partner"].search( + [("service_location_id", "=", child.id)] + ) if child_locs: for loc in child_locs: con += loc.get_action_views(1, 0, loc) return con else: for child in loc: - child_locs = self.env['fsm.location'].\ - search([('fsm_parent_id', '=', child.id)]) + child_locs = self.env["fsm.location"].search( + [("fsm_parent_id", "=", child.id)] + ) subloc = child_locs if child_locs: for loc in child_locs: subloc += loc.get_action_views(0, 0, loc) return subloc - @api.multi def action_view_contacts(self): - ''' + """ This function returns an action that display existing contacts of given fsm location id and its child locations. It can either be a in a list or in a form view, if there is only one contact to show. - ''' + """ for location in self: - action = self.env.ref('contacts.action_contacts').\ - read()[0] + action = self.env.ref("contacts.action_contacts").read()[0] contacts = self.get_action_views(1, 0, location) - action['context'] = self.env.context.copy() - action['context'].update({'default_service_location_id': self.id}) + action["context"] = self.env.context.copy() + action["context"].update({"default_service_location_id": self.id}) if len(contacts) == 1: - action['views'] = [(self.env.ref('base.view_partner_form').id, - 'form')] - action['res_id'] = contacts.id - action['context'].update({'active_id': contacts.id}) + action["views"] = [(self.env.ref("base.view_partner_form").id, "form")] + action["res_id"] = contacts.id + action["context"].update({"active_id": contacts.id}) else: - action['domain'] = [('id', 'in', contacts.ids)] - action['context'].update({'active_ids': contacts.ids}) - action['context'].update({'active_id': ''}) + action["domain"] = [("id", "in", contacts.ids)] + action["context"].update({"active_ids": contacts.ids}) + action["context"].update({"active_id": ""}) return action - @api.multi def _compute_contact_ids(self): for loc in self: contacts = self.comp_count(1, 0, loc) loc.contact_count = contacts - @api.multi def action_view_equipment(self): - ''' + """ This function returns an action that display existing equipment of given fsm location id. It can either be a in a list or in a form view, if there is only one equipment to show. - ''' + """ for location in self: - action = self.env.ref('fieldservice.action_fsm_equipment').\ - read()[0] + action = self.env.ref("fieldservice.action_fsm_equipment").read()[0] equipment = self.get_action_views(0, 1, location) - action['context'] = self.env.context.copy() - action['context'].update({'default_location_id': self.id}) + action["context"] = self.env.context.copy() + action["context"].update({"default_location_id": self.id}) if len(equipment) == 0 or len(equipment) > 1: - action['domain'] = [('id', 'in', equipment.ids)] + action["domain"] = [("id", "in", equipment.ids)] elif equipment: - action['views'] = [(self.env. - ref('fieldservice.' + - 'fsm_equipment_form_view').id, - 'form')] - action['res_id'] = equipment.id + action["views"] = [ + ( + self.env.ref("fieldservice." + "fsm_equipment_form_view").id, + "form", + ) + ] + action["res_id"] = equipment.id return action - @api.multi def _compute_sublocation_ids(self): for loc in self: sublocation = self.comp_count(0, 0, loc) loc.sublocation_count = sublocation - @api.multi def action_view_sublocation(self): - ''' + """ This function returns an action that display existing sub-locations of a given fsm location id. It can either be a in a list or in a form view, if there is only one sub-location to show. - ''' + """ for location in self: - action = self.env.ref('fieldservice.action_fsm_location').read()[0] + action = self.env.ref("fieldservice.action_fsm_location").read()[0] sublocation = self.get_action_views(0, 0, location) - action['context'] = self.env.context.copy() - action['context'].update({'default_fsm_parent_id': self.id}) + action["context"] = self.env.context.copy() + action["context"].update({"default_fsm_parent_id": self.id}) if len(sublocation) > 1 or len(sublocation) == 0: - action['domain'] = [('id', 'in', sublocation.ids)] + action["domain"] = [("id", "in", sublocation.ids)] elif sublocation: - action['views'] = [(self.env. - ref('fieldservice.' + - 'fsm_location_form_view').id, - 'form')] - action['res_id'] = sublocation.id + action["views"] = [ + ( + self.env.ref("fieldservice." + "fsm_location_form_view").id, + "form", + ) + ] + action["res_id"] = sublocation.id return action - @api.multi def _compute_equipment_ids(self): for loc in self: equipment = self.comp_count(0, 1, loc) loc.equipment_count = equipment - @api.constrains('fsm_parent_id') + @api.constrains("fsm_parent_id") def _check_location_recursion(self): - if not self._check_recursion(parent='fsm_parent_id'): - raise ValidationError(_('You cannot create recursive location.')) + if not self._check_recursion(parent="fsm_parent_id"): + raise ValidationError(_("You cannot create recursive location.")) return True - @api.onchange('country_id') + @api.onchange("country_id") def _onchange_country_id(self): if self.country_id and self.country_id != self.state_id.country_id: self.state_id = False - @api.onchange('state_id') + @api.onchange("state_id") def _onchange_state(self): if self.state_id.country_id: self.country_id = self.state_id.country_id class FSMPerson(models.Model): - _inherit = 'fsm.person' + _inherit = "fsm.person" - location_ids = fields.One2many('fsm.location.person', - 'person_id', - string='Linked Locations') + location_ids = fields.One2many( + "fsm.location.person", "person_id", string="Linked Locations" + ) diff --git a/fieldservice/models/fsm_location_person.py b/fieldservice/models/fsm_location_person.py index 546babd080..1ef8bb8949 100644 --- a/fieldservice/models/fsm_location_person.py +++ b/fieldservice/models/fsm_location_person.py @@ -1,30 +1,32 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields, models class FSMLocationPerson(models.Model): - _name = 'fsm.location.person' - _description = 'Field Service Location Person Info' - _rec_name = 'location_id' - _order = 'sequence' + _name = "fsm.location.person" + _description = "Field Service Location Person Info" + _rec_name = "location_id" + _order = "sequence" - location_id = fields.Many2one('fsm.location', string='Location', - required=True, index=True) - person_id = fields.Many2one('fsm.person', string='Worker', required=True, - index=True) + location_id = fields.Many2one( + "fsm.location", string="Location", required=True, index=True + ) + person_id = fields.Many2one( + "fsm.person", string="Worker", required=True, index=True + ) sequence = fields.Integer(string="Sequence", required=True, default="10") - phone = fields.Char(related='person_id.phone', string='Phone') - email = fields.Char(related='person_id.email', string='Email') - owner_id = fields.Many2one(related='location_id.owner_id', string='Owner') - customer_id = fields.Many2one(related='location_id.customer_id', - string='Customer') - contact_id = fields.Many2one(related='location_id.contact_id', - string='Contact') + phone = fields.Char(related="person_id.phone", string="Phone") + email = fields.Char(related="person_id.email", string="Email") + owner_id = fields.Many2one(related="location_id.owner_id", string="Owner") + customer_id = fields.Many2one(related="location_id.customer_id", string="Customer") + contact_id = fields.Many2one(related="location_id.contact_id", string="Contact") _sql_constraints = [ - ('location_person_uniq', - 'unique(location_id,person_id)', - 'The worker is already linked to this location.') + ( + "location_person_uniq", + "unique(location_id,person_id)", + "The worker is already linked to this location.", + ) ] diff --git a/fieldservice/models/fsm_order.py b/fieldservice/models/fsm_order.py index cb7b80b284..4a4a7e63c6 100644 --- a/fieldservice/models/fsm_order.py +++ b/fieldservice/models/fsm_order.py @@ -1,167 +1,208 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import datetime, timedelta -from odoo import api, fields, models, _ -from . import fsm_stage + +from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from . import fsm_stage + class FSMOrder(models.Model): - _name = 'fsm.order' - _description = 'Field Service Order' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = "fsm.order" + _description = "Field Service Order" + _inherit = ["mail.thread", "mail.activity.mixin"] def _default_stage_id(self): - stage_ids = self.env['fsm.stage'].\ - search([('stage_type', '=', 'order'), - ('is_default', '=', True), - ('company_id', 'in', (self.env.user.company_id.id, - False))], - order='sequence asc', limit=1) + stage_ids = self.env["fsm.stage"].search( + [ + ("stage_type", "=", "order"), + ("is_default", "=", True), + ("company_id", "in", (self.env.user.company_id.id, False)), + ], + order="sequence asc", + limit=1, + ) if stage_ids: return stage_ids[0] else: - raise ValidationError(_( - "You must create an FSM order stage first.")) + raise ValidationError(_("You must create an FSM order stage first.")) def _default_team_id(self): - team_ids = self.env['fsm.team'].\ - search([('company_id', 'in', (self.env.user.company_id.id, - False))], - order='sequence asc', limit=1) + team_ids = self.env["fsm.team"].search( + [("company_id", "in", (self.env.user.company_id.id, False))], + order="sequence asc", + limit=1, + ) if team_ids: return team_ids[0] else: - raise ValidationError(_( - "You must create an FSM team first.")) + raise ValidationError(_("You must create an FSM team first.")) - @api.depends('date_start', 'date_end') + @api.depends("date_start", "date_end") def _compute_duration(self): + duration = 0.0 for rec in self: if rec.date_start and rec.date_end: start = fields.Datetime.from_string(rec.date_start) end = fields.Datetime.from_string(rec.date_end) delta = end - start - rec.duration = delta.total_seconds() / 3600 + duration = delta.total_seconds() / 3600 + rec.duration = duration - @api.depends('stage_id') + @api.depends("stage_id") def _get_stage_color(self): """ Get stage color""" - self.custom_color = self.stage_id.custom_color or '#FFFFFF' - - stage_id = fields.Many2one('fsm.stage', string='Stage', - track_visibility='onchange', - index=True, copy=False, - group_expand='_read_group_stage_ids', - default=lambda self: self._default_stage_id()) - priority = fields.Selection(fsm_stage.AVAILABLE_PRIORITIES, - string='Priority', - index=True, - default=fsm_stage.AVAILABLE_PRIORITIES[0][0]) - tag_ids = fields.Many2many('fsm.tag', 'fsm_order_tag_rel', - 'fsm_order_id', - 'tag_id', string='Tags', - help="Classify and analyze your orders") - color = fields.Integer('Color Index', default=0) - team_id = fields.Many2one('fsm.team', string='Team', - default=lambda self: self._default_team_id(), - index=True, required=True, - track_visibility='onchange') + self.custom_color = self.stage_id.custom_color or "#FFFFFF" + + stage_id = fields.Many2one( + "fsm.stage", + string="Stage", + track_visibility="onchange", + index=True, + copy=False, + group_expand="_read_group_stage_ids", + default=lambda self: self._default_stage_id(), + ) + priority = fields.Selection( + fsm_stage.AVAILABLE_PRIORITIES, + string="Priority", + index=True, + default=fsm_stage.AVAILABLE_PRIORITIES[0][0], + ) + tag_ids = fields.Many2many( + "fsm.tag", + "fsm_order_tag_rel", + "fsm_order_id", + "tag_id", + string="Tags", + help="Classify and analyze your orders", + ) + color = fields.Integer("Color Index", default=0) + team_id = fields.Many2one( + "fsm.team", + string="Team", + default=lambda self: self._default_team_id(), + index=True, + required=True, + track_visibility="onchange", + ) # Request - name = fields.Char(string='Name', required=True, index=True, copy=False, - default=lambda self: _('New')) - customer_id = fields.Many2one('res.partner', string='Contact', - domain=[('customer', '=', True)], - change_default=True, - index=True, - track_visibility='always') - location_id = fields.Many2one('fsm.location', string='Location', - index=True, required=True) - location_directions = fields.Char(string='Location Directions') - request_early = fields.Datetime(string='Earliest Request Date', - default=datetime.now()) - color = fields.Integer('Color Index') + name = fields.Char( + string="Name", + required=True, + index=True, + copy=False, + default=lambda self: _("New"), + ) + customer_id = fields.Many2one( + "res.partner", + string="Contact", + domain=[("customer", "=", True)], + change_default=True, + index=True, + track_visibility="always", + ) + location_id = fields.Many2one( + "fsm.location", string="Location", index=True, required=True + ) + location_directions = fields.Char(string="Location Directions") + request_early = fields.Datetime( + string="Earliest Request Date", default=datetime.now() + ) + color = fields.Integer("Color Index") company_id = fields.Many2one( - 'res.company', string='Company', required=True, index=True, + "res.company", + string="Company", + required=True, + index=True, default=lambda self: self.env.user.company_id, - help="Company related to this order") + help="Company related to this order", + ) def _compute_request_late(self, vals): - if vals.get('priority') == '0': - if vals.get('request_early'): - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(days=3) + if vals.get("priority") == "0": + if vals.get("request_early"): + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(days=3) else: - vals['request_late'] = datetime.now() + timedelta(days=3) - elif vals.get('priority') == '1': - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(days=2) - elif vals.get('priority') == '2': - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(days=1) - elif vals.get('priority') == '3': - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(hours=8) + vals["request_late"] = datetime.now() + timedelta(days=3) + elif vals.get("priority") == "1": + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(days=2) + elif vals.get("priority") == "2": + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(days=1) + elif vals.get("priority") == "3": + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(hours=8) return vals - request_late = fields.Datetime(string='Latest Request Date') - description = fields.Text(string='Description') + request_late = fields.Datetime(string="Latest Request Date") + description = fields.Text(string="Description") - person_ids = fields.Many2many('fsm.person', - string='Field Service Workers') + person_ids = fields.Many2many("fsm.person", string="Field Service Workers") - @api.onchange('location_id') + @api.onchange("location_id") def _onchange_location_id_customer(self): if self.location_id: - return {'domain': {'customer_id': [('service_location_id', '=', - self.location_id.name)]}} + return { + "domain": { + "customer_id": [("service_location_id", "=", self.location_id.name)] + } + } else: - return {'domain': {'customer_id': [('id', '!=', None)]}} + return {"domain": {"customer_id": [("id", "!=", None)]}} - @api.onchange('customer_id') + @api.onchange("customer_id") def _onchange_customer_id_location(self): if self.customer_id: self.location_id = self.customer_id.service_location_id # Planning - person_id = fields.Many2one('fsm.person', string='Assigned To', - index=True) - person_phone = fields.Char(related="person_id.phone", - string="Worker Phone") - route_id = fields.Many2one('fsm.route', string='Route', index=True) - scheduled_date_start = fields.Datetime(string='Scheduled Start (ETA)') - scheduled_duration = fields.Float(string='Scheduled duration', - help='Scheduled duration of the work in' - ' hours') + person_id = fields.Many2one("fsm.person", string="Assigned To", index=True) + person_phone = fields.Char(related="person_id.phone", string="Worker Phone") + scheduled_date_start = fields.Datetime(string="Scheduled Start (ETA)") + scheduled_duration = fields.Float( + string="Scheduled duration", help="Scheduled duration of the work in" " hours" + ) scheduled_date_end = fields.Datetime(string="Scheduled End") - sequence = fields.Integer(string='Sequence', default=10) - todo = fields.Text(string='Instructions') + sequence = fields.Integer(string="Sequence", default=10) + todo = fields.Text(string="Instructions") # Execution - resolution = fields.Text(string='Resolution', - placeholder="Resolution of the order") - date_start = fields.Datetime(string='Actual Start') - date_end = fields.Datetime(string='Actual End') - duration = fields.Float(string='Actual duration', - compute=_compute_duration, - help='Actual duration in hours') + resolution = fields.Text(string="Resolution", placeholder="Resolution of the order") + date_start = fields.Datetime(string="Actual Start") + date_end = fields.Datetime(string="Actual End") + duration = fields.Float( + string="Actual duration", + compute=_compute_duration, + help="Actual duration in hours", + ) current_date = fields.Datetime(default=fields.datetime.now(), store=True) # Location - territory_id = fields.Many2one('res.territory', string="Territory", - related='location_id.territory_id', - store=True) - branch_id = fields.Many2one('fsm.branch', string='Branch', - related='location_id.branch_id', - store=True) - district_id = fields.Many2one('fsm.district', string='District', - related='location_id.district_id', - store=True) - region_id = fields.Many2one('fsm.region', string='Region', - related='location_id.region_id', - store=True) + territory_id = fields.Many2one( + "res.territory", + string="Territory", + related="location_id.territory_id", + store=True, + ) + branch_id = fields.Many2one( + "res.branch", string="Branch", related="location_id.branch_id", store=True + ) + district_id = fields.Many2one( + "res.district", string="District", related="location_id.district_id", store=True + ) + region_id = fields.Many2one( + "res.region", string="Region", related="location_id.region_id", store=True + ) # Fields for Geoengine Identify display_name = fields.Char(related="name", string="Order") @@ -169,212 +210,248 @@ def _onchange_customer_id_location(self): street2 = fields.Char(related="location_id.street2") zip = fields.Char(related="location_id.zip") city = fields.Char(related="location_id.city", string="City") - state_name = fields.Char(related="location_id.state_id.name", - string='State', ondelete='restrict') - country_name = fields.Char(related="location_id.country_id.name", - string='Country', ondelete='restrict') + state_name = fields.Char( + related="location_id.state_id.name", string="State", ondelete="restrict" + ) + country_name = fields.Char( + related="location_id.country_id.name", string="Country", ondelete="restrict" + ) phone = fields.Char(related="location_id.phone", string="Location Phone") mobile = fields.Char(related="location_id.mobile") stage_name = fields.Char(related="stage_id.name", string="Stage Name") # Field for Stage Color - custom_color = fields.Char(related="stage_id.custom_color", - string='Stage Color') + custom_color = fields.Char(related="stage_id.custom_color", string="Stage Color") # Template - template_id = fields.Many2one('fsm.template', string="Template") - category_ids = fields.Many2many('fsm.category', string="Categories") + template_id = fields.Many2one("fsm.template", string="Template") + category_ids = fields.Many2many("fsm.category", string="Categories") # Equipment used for Maintenance and Repair Orders - equipment_id = fields.Many2one('fsm.equipment', string='Equipment') + equipment_id = fields.Many2one("fsm.equipment", string="Equipment") # Equipment used for all other Service Orders - equipment_ids = fields.Many2many('fsm.equipment', string='Equipments') - type = fields.Selection([], string='Type') + equipment_ids = fields.Many2many("fsm.equipment", string="Equipments") + type = fields.Selection([], string="Type") @api.model def _read_group_stage_ids(self, stages, domain, order): - stage_ids = self.env['fsm.stage'].\ - search([('stage_type', '=', 'order'), - ('company_id', '=', self.env.user.company_id.id)]) + stage_ids = self.env["fsm.stage"].search( + [ + ("stage_type", "=", "order"), + ("company_id", "=", self.env.user.company_id.id), + ] + ) return stage_ids @api.model def create(self, vals): - if vals.get('name', _('New')) == _('New'): - vals['name'] = self.env['ir.sequence'].next_by_code('fsm.order') \ - or _('New') - if vals.get('request_early', False) and not vals.get( - 'scheduled_date_start'): - req_date = fields.Datetime.from_string(vals['request_early']) + if vals.get("name", _("New")) == _("New"): + vals["name"] = self.env["ir.sequence"].next_by_code("fsm.order") or _("New") + if vals.get("request_early", False) and not vals.get("scheduled_date_start"): + req_date = fields.Datetime.from_string(vals["request_early"]) # Round scheduled date start req_date = req_date.replace(minute=0, second=0) - vals.update({'scheduled_date_start': str(req_date), - 'request_early': str(req_date)}) + vals.update( + {"scheduled_date_start": str(req_date), "request_early": str(req_date)} + ) self._calc_scheduled_dates(vals) - if not vals.get('request_late'): - if vals.get('priority') == '0': - if vals.get('request_early'): - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(days=3) + if not vals.get("request_late"): + if vals.get("priority") == "0": + if vals.get("request_early"): + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(days=3) else: - vals['request_late'] = datetime.now() + timedelta(days=3) - elif vals.get('priority') == '1': - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(days=2) - elif vals.get('priority') == '2': - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(days=1) - elif vals.get('priority') == '3': - vals['request_late'] = fields.Datetime.\ - from_string(vals.get('request_early')) + timedelta(hours=8) + vals["request_late"] = datetime.now() + timedelta(days=3) + elif vals.get("priority") == "1": + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(days=2) + elif vals.get("priority") == "2": + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(days=1) + elif vals.get("priority") == "3": + vals["request_late"] = fields.Datetime.from_string( + vals.get("request_early") + ) + timedelta(hours=8) return super(FSMOrder, self).create(vals) - @api.multi def write(self, vals): self._calc_scheduled_dates(vals) res = super(FSMOrder, self).write(vals) for order in self: - if 'customer_id' not in vals and order.customer_id is False: + if "customer_id" not in vals and order.customer_id is False: order.customer_id = order.location_id.customer_id.id return res def _calc_scheduled_dates(self, vals): """Calculate scheduled dates and duration""" - if (vals.get('scheduled_duration') - or vals.get('scheduled_date_start') - or vals.get('scheduled_date_end')): - date_end = '' - date_start = '' - if vals.get('scheduled_duration', False): + if ( + vals.get("scheduled_duration") + or vals.get("scheduled_date_start") + or vals.get("scheduled_date_end") + ): + date_end = "" + date_start = "" + if vals.get("scheduled_duration", False): date_to_with_delta = fields.Datetime.from_string( - vals.get('scheduled_date_start', - self.scheduled_date_start)) + timedelta( - hours=vals.get('scheduled_duration', False)) + vals.get("scheduled_date_start", self.scheduled_date_start) + ) + timedelta(hours=vals.get("scheduled_duration", False)) date_end = str(date_to_with_delta) - if (vals.get('scheduled_date_start', False) - and self.scheduled_date_start != vals.get( - 'scheduled_date_start', False) - and vals.get('scheduled_date_end', False) - and self.scheduled_date_end != vals.get( - 'scheduled_date_end', False)): + if ( + vals.get("scheduled_date_start", False) + and self.scheduled_date_start != vals.get("scheduled_date_start", False) + and vals.get("scheduled_date_end", False) + and self.scheduled_date_end != vals.get("scheduled_date_end", False) + ): new_date_start = fields.Datetime.from_string( - vals.get('scheduled_date_start', False)) + vals.get("scheduled_date_start", False) + ) new_date_end = fields.Datetime.from_string( - vals.get('scheduled_date_end', False)) - hours = new_date_end.replace(second=0) - new_date_start.replace(second=0) + vals.get("scheduled_date_end", False) + ) + hours = new_date_end.replace(second=0) - new_date_start.replace( + second=0 + ) hrs = hours.total_seconds() / 3600 self.scheduled_duration = float(hrs) - elif (vals.get('scheduled_date_start', False) - and self.scheduled_date_start != vals.get( - 'scheduled_date_start', False)): + elif vals.get( + "scheduled_date_start", False + ) and self.scheduled_date_start != vals.get("scheduled_date_start", False): old_date_start = fields.Datetime.from_string(self.scheduled_date_start) new_date_start = fields.Datetime.from_string( - vals.get('scheduled_date_start', False)) + vals.get("scheduled_date_start", False) + ) date_end = fields.Datetime.from_string(self.scheduled_date_end) if old_date_start and new_date_start: if old_date_start > new_date_start: - hours = old_date_start.replace(second=0) - new_date_start.replace(second=0) + hours = old_date_start.replace( + second=0 + ) - new_date_start.replace(second=0) hrs = hours.total_seconds() / 3600 self.scheduled_duration += float(hrs) elif old_date_start < new_date_start: - hours = new_date_start.replace(second=0) - old_date_start.replace(second=0) + hours = new_date_start.replace( + second=0 + ) - old_date_start.replace(second=0) hrs = hours.total_seconds() / 3600 if date_end and not new_date_start >= date_end: self.scheduled_duration -= float(hrs) - hrs = vals.get('scheduled_duration', False) or self.scheduled_duration or 0 + hrs = ( + vals.get("scheduled_duration", False) + or self.scheduled_duration + or 0 + ) date_to_with_delta = fields.Datetime.from_string( - vals.get('scheduled_date_start', False)) + timedelta(hours=hrs) + vals.get("scheduled_date_start", False) + ) + timedelta(hours=hrs) date_end = str(date_to_with_delta) - elif (vals.get('scheduled_date_end', False) - and self.scheduled_date_end != vals.get( - 'scheduled_date_end', False)): + elif vals.get( + "scheduled_date_end", False + ) and self.scheduled_date_end != vals.get("scheduled_date_end", False): old_date_end = fields.Datetime.from_string(self.scheduled_date_end) new_date_end = fields.Datetime.from_string( - vals.get('scheduled_date_end', False)) + vals.get("scheduled_date_end", False) + ) date_start = fields.Datetime.from_string(self.scheduled_date_start) if old_date_end and new_date_end: if old_date_end > new_date_end: - hours = old_date_end.replace(second=0) - new_date_end.replace(second=0) + hours = old_date_end.replace(second=0) - new_date_end.replace( + second=0 + ) hrs = hours.total_seconds() / 3600 if date_start and not new_date_end <= date_start: self.scheduled_duration -= hrs else: - hours = new_date_end.replace(second=0) - old_date_end.replace(second=0) + hours = new_date_end.replace(second=0) - old_date_end.replace( + second=0 + ) hrs = hours.total_seconds() / 3600 self.scheduled_duration += hrs - hrs = vals.get('scheduled_duration', False) or self.scheduled_duration or 0 + hrs = ( + vals.get("scheduled_duration", False) + or self.scheduled_duration + or 0 + ) date_to_with_delta = fields.Datetime.from_string( - vals.get('scheduled_date_end', False)) - timedelta(hours=hrs) + vals.get("scheduled_date_end", False) + ) - timedelta(hours=hrs) date_start = str(date_to_with_delta) if date_end: - vals['scheduled_date_end'] = date_end + vals["scheduled_date_end"] = date_end if date_start: - vals['scheduled_date_start'] = date_start + vals["scheduled_date_start"] = date_start def action_complete(self): - return self.write({'stage_id': self.env.ref( - 'fieldservice.fsm_stage_completed').id}) + return self.write( + {"stage_id": self.env.ref("fieldservice.fsm_stage_completed").id} + ) def action_cancel(self): - return self.write({'stage_id': self.env.ref( - 'fieldservice.fsm_stage_cancelled').id}) + return self.write( + {"stage_id": self.env.ref("fieldservice.fsm_stage_cancelled").id} + ) - @api.onchange('scheduled_date_end') + @api.onchange("scheduled_date_end") def onchange_scheduled_date_end(self): if self.scheduled_date_end: date_to_with_delta = fields.Datetime.from_string( - self.scheduled_date_end) - \ - timedelta(hours=self.scheduled_duration) + self.scheduled_date_end + ) - timedelta(hours=self.scheduled_duration) self.date_start = str(date_to_with_delta) - @api.onchange('scheduled_duration') + @api.onchange("scheduled_duration") def onchange_scheduled_duration(self): - if (self.scheduled_duration and self.scheduled_date_start): + if self.scheduled_duration and self.scheduled_date_start: date_to_with_delta = fields.Datetime.from_string( - self.scheduled_date_start) + \ - timedelta(hours=self.scheduled_duration) + self.scheduled_date_start + ) + timedelta(hours=self.scheduled_duration) self.scheduled_date_end = str(date_to_with_delta) def copy_notes(self): self.description = "" - if self.type and self.type.name not in ['repair', 'maintenance']: + if self.type and self.type.name not in ["repair", "maintenance"]: for equipment_id in self.equipment_ids: if equipment_id: if equipment_id.notes is not False: if self.description is not False: - self.description = (self.description + - equipment_id.notes + '\n ') + self.description = ( + self.description + equipment_id.notes + "\n " + ) else: - self.description = (equipment_id.notes + '\n ') + self.description = equipment_id.notes + "\n " else: if self.equipment_id: if self.equipment_id.notes is not False: if self.description is not False: - self.description = (self.description + - self.equipment_id.notes + '\n ') + self.description = ( + self.description + self.equipment_id.notes + "\n " + ) else: - self.description = (self.equipment_id.notes + '\n ') + self.description = self.equipment_id.notes + "\n " if self.location_id: s = self.location_id.direction - if s is not False and s != '


': - s = s.replace('

', '') - s = s.replace('
', '') - s = s.replace('

', '\n') + if s is not False and s != "


": + s = s.replace("

", "") + s = s.replace("
", "") + s = s.replace("

", "\n") if self.location_directions is not False: - self.location_directions = (self.location_directions + - '\n' + s + '\n') + self.location_directions = ( + self.location_directions + "\n" + s + "\n" + ) else: - self.location_directions = (s + '\n ') + self.location_directions = s + "\n " if self.template_id: self.todo = self.template_id.instructions - @api.onchange('location_id') + @api.onchange("location_id") def onchange_location_id(self): if self.location_id: self.territory_id = self.location_id.territory_id or False @@ -383,11 +460,11 @@ def onchange_location_id(self): self.region_id = self.location_id.region_id or False self.copy_notes() - @api.onchange('equipment_ids') + @api.onchange("equipment_ids") def onchange_equipment_ids(self): self.copy_notes() - @api.onchange('template_id') + @api.onchange("template_id") def _onchange_template_id(self): if self.template_id: self.category_ids = self.template_id.category_ids @@ -398,8 +475,11 @@ def _onchange_template_id(self): class FSMTeam(models.Model): - _inherit = 'fsm.team' + _inherit = "fsm.team" order_ids = fields.One2many( - 'fsm.order', 'team_id', string='Orders', - domain=[('stage_id.is_closed', '=', False)]) + "fsm.order", + "team_id", + string="Orders", + domain=[("stage_id.is_closed", "=", False)], + ) diff --git a/fieldservice/models/fsm_order_type.py b/fieldservice/models/fsm_order_type.py new file mode 100644 index 0000000000..7b9b246c8b --- /dev/null +++ b/fieldservice/models/fsm_order_type.py @@ -0,0 +1,10 @@ +# Copyright (C) 2020 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class FSMOrderType(models.Model): + _name = "fsm.order.type" + _description = "Field Service Order Type" + + name = fields.Char(string="Name") diff --git a/fieldservice/models/fsm_person.py b/fieldservice/models/fsm_person.py index f21e313110..4a4256345c 100644 --- a/fieldservice/models/fsm_person.py +++ b/fieldservice/models/fsm_person.py @@ -1,116 +1,139 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import api, fields, models class FSMPerson(models.Model): - _name = 'fsm.person' - _inherits = {'res.partner': 'partner_id'} - _description = 'Field Service Worker' + _name = "fsm.person" + _inherits = {"res.partner": "partner_id"} + _description = "Field Service Worker" - partner_id = fields.Many2one('res.partner', string='Related Partner', - required=True, ondelete='restrict', - delegate=True, auto_join=True) - category_ids = fields.Many2many('fsm.category', string='Categories') - calendar_id = fields.Many2one('resource.calendar', - string='Working Schedule') - stage_id = fields.Many2one('fsm.stage', string='Stage', - track_visibility='onchange', - index=True, copy=False, - group_expand='_read_group_stage_ids', - default=lambda self: self._default_stage_id()) + partner_id = fields.Many2one( + "res.partner", + string="Related Partner", + required=True, + ondelete="restrict", + delegate=True, + auto_join=True, + ) + category_ids = fields.Many2many("fsm.category", string="Categories") + calendar_id = fields.Many2one("resource.calendar", string="Working Schedule") + stage_id = fields.Many2one( + "fsm.stage", + string="Stage", + track_visibility="onchange", + index=True, + copy=False, + group_expand="_read_group_stage_ids", + default=lambda self: self._default_stage_id(), + ) hide = fields.Boolean(default=False) mobile = fields.Char(string="Mobile") - territory_ids = fields.Many2many('res.territory', string='Territories') + territory_ids = fields.Many2many("res.territory", string="Territories") @api.model - def _search(self, args, offset=0, limit=None, order=None, count=False, - access_rights_uid=None): + def _search( + self, + args, + offset=0, + limit=None, + order=None, + count=False, + access_rights_uid=None, + ): res = super(FSMPerson, self)._search( - args=args, offset=offset, limit=limit, order=order, count=count, - access_rights_uid=access_rights_uid) + args=args, + offset=offset, + limit=limit, + order=order, + count=count, + access_rights_uid=access_rights_uid, + ) # Check for args first having location_ids as default filter for arg in args: if isinstance(arg, (list)): - if arg[0] == 'location_ids': + if arg[0] == "location_ids": # If given int search ID, else search name if isinstance(arg[2], int): - self.env.cr.execute("SELECT person_id " - "FROM fsm_location_person " - "WHERE location_id=%s", (arg[2],)) + self.env.cr.execute( + "SELECT person_id " + "FROM fsm_location_person " + "WHERE location_id=%s", + (arg[2],), + ) else: - arg[2] = '%' + arg[2] + '%' - self.env.cr.execute("SELECT id " - "FROM fsm_location " - "WHERE complete_name like %s", - (arg[2],)) + arg[2] = "%" + arg[2] + "%" + self.env.cr.execute( + "SELECT id " + "FROM fsm_location " + "WHERE complete_name like %s", + (arg[2],), + ) location_ids = self.env.cr.fetchall() if location_ids: - location_ids = \ - [location[0] for location in location_ids] - self.env.cr.execute("SELECT DISTINCT person_id " - "FROM fsm_location_person " - "WHERE location_id in %s", - [tuple(location_ids)]) + location_ids = [location[0] for location in location_ids] + self.env.cr.execute( + "SELECT DISTINCT person_id " + "FROM fsm_location_person " + "WHERE location_id in %s", + [tuple(location_ids)], + ) workers_ids = self.env.cr.fetchall() if workers_ids: - preferred_workers_list = \ - [worker[0] for worker in workers_ids] + preferred_workers_list = [worker[0] for worker in workers_ids] return preferred_workers_list return res @api.model def create(self, vals): - vals.update({'fsm_person': True}) + vals.update({"fsm_person": True}) return super(FSMPerson, self).create(vals) - @api.multi def get_person_information(self, vals): # get person ids - person_ids = self.search([('id', '!=', 0), ('active', '=', True)]) + person_ids = self.search([("id", "!=", 0), ("active", "=", True)]) person_information_dict = [] for person in person_ids: - person_information_dict.append({ - 'id': person.id, - 'name': person.name}) + person_information_dict.append({"id": person.id, "name": person.name}) return person_information_dict @api.model def _read_group_stage_ids(self, stages, domain, order): - stage_ids = self.env['fsm.stage'].search([('stage_type', - '=', 'worker')]) + stage_ids = self.env["fsm.stage"].search([("stage_type", "=", "worker")]) return stage_ids def _default_stage_id(self): - return self.env['fsm.stage'].search([('stage_type', '=', 'worker'), - ('sequence', '=', '1')]) + return self.env["fsm.stage"].search( + [("stage_type", "=", "worker"), ("sequence", "=", "1")] + ) def next_stage(self): seq = self.stage_id.sequence - next_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'worker'), ('sequence', '>', seq)], - order="sequence asc") + next_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "worker"), ("sequence", ">", seq)], + order="sequence asc", + ) if next_stage: self.stage_id = next_stage[0] self._onchange_stage_id() def previous_stage(self): seq = self.stage_id.sequence - prev_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'worker'), ('sequence', '<', seq)], - order="sequence desc") + prev_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "worker"), ("sequence", "<", seq)], + order="sequence desc", + ) if prev_stage: self.stage_id = prev_stage[0] self._onchange_stage_id() - @api.onchange('stage_id') + @api.onchange("stage_id") def _onchange_stage_id(self): # get last stage - heighest_stage = self.env['fsm.stage'].search( - [('stage_type', '=', 'worker')], - order='sequence desc', - limit=1) + heighest_stage = self.env["fsm.stage"].search( + [("stage_type", "=", "worker")], order="sequence desc", limit=1 + ) if self.stage_id.name == heighest_stage.name: self.hide = True else: diff --git a/fieldservice/models/fsm_route.py b/fieldservice/models/fsm_route.py deleted file mode 100644 index df5c4934c0..0000000000 --- a/fieldservice/models/fsm_route.py +++ /dev/null @@ -1,36 +0,0 @@ -# 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, _ - - -class FSMRoute(models.Model): - _name = 'fsm.route' - _description = 'Field Service Route' - _order = 'date, name' - - name = fields.Char(string='Name', required=True, - default=lambda self: _('New')) - order_ids = fields.One2many('fsm.order', 'route_id', - string='Orders') - person_id = fields.Many2one('fsm.person', - string='Assigned To', - required=True) - date = fields.Date(string='Date', required=True) - company_id = fields.Many2one( - 'res.company', string='Company', required=True, index=True, - default=lambda self: self.env.user.company_id, - help="Company related to this route") - - _sql_constraints = [ - ('fsm_route_person_date_uniq', - 'unique (person_id, date)', - "You cannot create 2 routes for the same worker on the same day!"), - ] - - @api.model - def create(self, vals): - if vals.get('name', _('New')) == _('New'): - vals['name'] = self.env['ir.sequence'].next_by_code('fsm.route')\ - or _('New') - return super(FSMRoute, self).create(vals) diff --git a/fieldservice/models/fsm_stage.py b/fieldservice/models/fsm_stage.py index ee72dafa99..994102b854 100644 --- a/fieldservice/models/fsm_stage.py +++ b/fieldservice/models/fsm_stage.py @@ -1,77 +1,103 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ +from odoo import _, api, fields, models from odoo.exceptions import ValidationError - -AVAILABLE_PRIORITIES = [ - ('0', 'Normal'), - ('1', 'Low'), - ('2', 'High'), - ('3', 'Urgent'), -] +AVAILABLE_PRIORITIES = [("0", "Normal"), ("1", "Low"), ("2", "High"), ("3", "Urgent")] class FSMStage(models.Model): - _name = 'fsm.stage' - _description = 'Field Service Stage' - _order = 'sequence, name, id' + _name = "fsm.stage" + _description = "Field Service Stage" + _order = "sequence, name, id" - name = fields.Char(string='Name', required=True) - sequence = fields.Integer('Sequence', default=1, - help="Used to order stages. Lower is better.") - legend_priority = fields.Text('Priority Management Explanation', - translate=True, - help='Explanation text to help users using' - ' the star and priority mechanism on' - ' stages or orders that are in this' - ' stage.') - fold = fields.Boolean('Folded in Kanban', - help='This stage is folded in the kanban view when ' - 'there are no record in that stage to display.') - is_closed = fields.Boolean('Is a close stage', - help='Services in this stage are considered ' - 'as closed.') - is_default = fields.Boolean('Is a default stage', - help='Used a default stage') - custom_color = fields.Char("Color Code", default="#FFFFFF", - help="Use Hex Code only Ex:-#FFFFFF") + name = fields.Char(string="Name", required=True) + sequence = fields.Integer( + "Sequence", default=1, help="Used to order stages. Lower is better." + ) + legend_priority = fields.Text( + "Priority Management Explanation", + translate=True, + help="Explanation text to help users using" + " the star and priority mechanism on" + " stages or orders that are in this" + " stage.", + ) + fold = fields.Boolean( + "Folded in Kanban", + help="This stage is folded in the kanban view when " + "there are no record in that stage to display.", + ) + is_closed = fields.Boolean( + "Is a close stage", help="Services in this stage are considered " "as closed." + ) + is_default = fields.Boolean("Is a default stage", help="Used a default stage") + custom_color = fields.Char( + "Color Code", default="#FFFFFF", help="Use Hex Code only Ex:-#FFFFFF" + ) description = fields.Text(translate=True) - stage_type = fields.Selection([('order', 'Order'), - ('equipment', 'Equipment'), - ('location', 'Location'), - ('worker', 'Worker')], 'Type', - required=True) + stage_type = fields.Selection( + [ + ("order", "Order"), + ("equipment", "Equipment"), + ("location", "Location"), + ("worker", "Worker"), + ], + "Type", + required=True, + ) + company_id = fields.Many2one( + "res.company", + string="Company", + default=lambda self: self.env.user.company_id.id, + ) + team_ids = fields.Many2many( + "fsm.team", + "order_team_stage_rel", + "stage_id", + "team_id", + string="Teams", + default=lambda self: self._default_team_ids(), + ) - @api.multi def get_color_information(self): # get stage ids stage_ids = self.search([]) color_information_dict = [] for stage in stage_ids: - color_information_dict.append({ - 'color': stage.custom_color, - 'field': 'stage_id', - 'opt': '==', - 'value': stage.name, - }) + color_information_dict.append( + { + "color": stage.custom_color, + "field": "stage_id", + "opt": "==", + "value": stage.name, + } + ) return color_information_dict @api.model def create(self, vals): - stages = self.env['fsm.stage'].search([]) + stages = self.env["fsm.stage"].search([]) for stage in stages: - if stage.stage_type == vals['stage_type'] and \ - stage.sequence == vals['sequence']: - raise ValidationError(_("Cannot create FSM Stage because " - "it has the same Type and Sequence " - "of an existing FSM Stage.")) + if ( + stage.stage_type == vals["stage_type"] + and stage.sequence == vals["sequence"] + ): + raise ValidationError( + _( + "Cannot create FSM Stage because " + "it has the same Type and Sequence " + "of an existing FSM Stage." + ) + ) return super(FSMStage, self).create(vals) - @api.constrains('custom_color') + @api.constrains("custom_color") def _check_custom_color_hex_code(self): - if self.custom_color and not self.custom_color.startswith( - '#') or len(self.custom_color) != 7: - raise ValidationError( - _('Color code should be Hex Code. Ex:-#FFFFFF')) + if ( + self.custom_color + and not self.custom_color.startswith("#") + or len(self.custom_color) != 7 + ): + raise ValidationError(_("Color code should be Hex Code. Ex:-#FFFFFF")) diff --git a/fieldservice/models/fsm_tag.py b/fieldservice/models/fsm_tag.py index 0162e85b8e..ee6a313840 100644 --- a/fieldservice/models/fsm_tag.py +++ b/fieldservice/models/fsm_tag.py @@ -1,29 +1,31 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields, models class FSMTag(models.Model): - _name = 'fsm.tag' - _description = 'Field Service Tag' + _name = "fsm.tag" + _description = "Field Service Tag" - name = fields.Char(string='Name', required=True) - parent_id = fields.Many2one('fsm.tag', string='Parent') - color = fields.Integer('Color Index', default=10) - full_name = fields.Char(string='Full Name', compute='_compute_full_name') + name = fields.Char(string="Name", required=True) + parent_id = fields.Many2one("fsm.tag", string="Parent") + color = fields.Integer("Color Index", default=10) + full_name = fields.Char(string="Full Name", compute="_compute_full_name") company_id = fields.Many2one( - 'res.company', string='Company', required=True, index=True, + "res.company", + string="Company", + required=True, + index=True, default=lambda self: self.env.user.company_id, - help="Company related to this tag") + help="Company related to this tag", + ) - _sql_constraints = [ - ('name_uniq', 'unique (name)', "Tag name already exists!"), - ] + _sql_constraints = [("name_uniq", "unique (name)", "Tag name already exists!")] def _compute_full_name(self): for record in self: if record.parent_id: - record.full_name = (record.parent_id.name + '/' + record.name) + record.full_name = record.parent_id.name + "/" + record.name else: record.full_name = record.name diff --git a/fieldservice/models/fsm_team.py b/fieldservice/models/fsm_team.py index 9fe7525673..e22f67b974 100644 --- a/fieldservice/models/fsm_team.py +++ b/fieldservice/models/fsm_team.py @@ -1,80 +1,92 @@ -# Copyright (C) 2018 - TODAY, Brian McMaster +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields, models class FSMTeam(models.Model): - _name = 'fsm.team' - _description = 'Field Service Team' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = "fsm.team" + _description = "Field Service Team" + _inherit = ["mail.thread", "mail.activity.mixin"] def _default_stages(self): - return self.env['fsm.stage'].search([('is_default', '=', True)]) + return self.env["fsm.stage"].search([("is_default", "=", True)]) def _compute_order_count(self): - order_data = self.env['fsm.order'].read_group( - [('team_id', 'in', self.ids), ('stage_id.is_closed', '=', False)], - ['team_id'], ['team_id']) - result = {data['team_id'][0]: int(data['team_id_count']) - for data in order_data} + order_data = self.env["fsm.order"].read_group( + [("team_id", "in", self.ids), ("stage_id.is_closed", "=", False)], + ["team_id"], + ["team_id"], + ) + result = {data["team_id"][0]: int(data["team_id_count"]) for data in order_data} for team in self: team.order_count = result.get(team.id, 0) def _compute_order_need_assign_count(self): - order_data = self.env['fsm.order'].read_group( - [('team_id', 'in', self.ids), ('person_id', '=', False)], - ['team_id'], ['team_id']) - result = {data['team_id'][0]: int(data['team_id_count']) - for data in order_data} + order_data = self.env["fsm.order"].read_group( + [("team_id", "in", self.ids), ("person_id", "=", False)], + ["team_id"], + ["team_id"], + ) + result = {data["team_id"][0]: int(data["team_id_count"]) for data in order_data} for team in self: team.order_need_assign_count = result.get(team.id, 0) def _compute_order_need_schedule_count(self): - order_data = self.env['fsm.order'].read_group( - [('team_id', 'in', self.ids), - ('scheduled_date_start', '=', False)], - ['team_id'], ['team_id']) - result = {data['team_id'][0]: int(data['team_id_count']) - for data in order_data} + order_data = self.env["fsm.order"].read_group( + [("team_id", "in", self.ids), ("scheduled_date_start", "=", False)], + ["team_id"], + ["team_id"], + ) + result = {data["team_id"][0]: int(data["team_id_count"]) for data in order_data} for team in self: team.order_need_schedule_count = result.get(team.id, 0) name = fields.Char(required=True, translation=True) description = fields.Text(translation=True) - color = fields.Integer('Color Index') + color = fields.Integer("Color Index") stage_ids = fields.Many2many( - 'fsm.stage', 'order_team_stage_rel', 'team_id', 'stage_id', - string='Stages', default=_default_stages) - order_count = fields.Integer( - compute='_compute_order_count', - string="Orders Count") + "fsm.stage", + "order_team_stage_rel", + "team_id", + "stage_id", + string="Stages", + default=_default_stages, + ) + order_count = fields.Integer(compute="_compute_order_count", string="Orders Count") order_need_assign_count = fields.Integer( - compute='_compute_order_need_assign_count', - string="Orders to Assign") + compute="_compute_order_need_assign_count", string="Orders to Assign" + ) order_need_schedule_count = fields.Integer( - compute='_compute_order_need_schedule_count', - string="Orders to Schedule") - sequence = fields.Integer('Sequence', default=1, - help="Used to sort teams. Lower is better.") + compute="_compute_order_need_schedule_count", string="Orders to Schedule" + ) + sequence = fields.Integer( + "Sequence", default=1, help="Used to sort teams. Lower is better." + ) company_id = fields.Many2one( - 'res.company', string='Company', required=True, index=True, + "res.company", + string="Company", + required=True, + index=True, default=lambda self: self.env.user.company_id, - help="Company related to this team") + help="Company related to this team", + ) - _sql_constraints = [ - ('name_uniq', 'unique (name)', "Team name already exists!"), - ] + _sql_constraints = [("name_uniq", "unique (name)", "Team name already exists!")] class FSMStage(models.Model): - _inherit = 'fsm.stage' + _inherit = "fsm.stage" def _default_team_ids(self): - default_team_id = self.env.context.get('default_team_id') + default_team_id = self.env.context.get("default_team_id") return [default_team_id] if default_team_id else None team_ids = fields.Many2many( - 'fsm.team', 'order_team_stage_rel', 'stage_id', 'team_id', - string='Teams', - default=lambda self: self._default_team_ids()) + "fsm.team", + "order_team_stage_rel", + "stage_id", + "team_id", + string="Teams", + default=lambda self: self._default_team_ids(), + ) diff --git a/fieldservice/models/fsm_template.py b/fieldservice/models/fsm_template.py index 2977e2c30c..5f59abd64a 100644 --- a/fieldservice/models/fsm_template.py +++ b/fieldservice/models/fsm_template.py @@ -1,22 +1,28 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields, models class FSMTemplate(models.Model): - _name = 'fsm.template' - _description = 'Field Service Order Template' + _name = "fsm.template" + _description = "Field Service Order Template" - name = fields.Char(string='Name', required=True) - instructions = fields.Text(string='Instructions') - category_ids = fields.Many2many('fsm.category', string='Categories') - hours = fields.Float(string='Hours') + name = fields.Char(string="Name", required=True) + instructions = fields.Text(string="Instructions") + category_ids = fields.Many2many("fsm.category", string="Categories") + hours = fields.Float(string="Hours") company_id = fields.Many2one( - 'res.company', string='Company', required=True, index=True, + "res.company", + string="Company", + required=True, + index=True, default=lambda self: self.env.user.company_id, - help='Company related to this template') - type_id = fields.Many2one('fsm.order.type', string='Type') + help="Company related to this template", + ) + type_id = fields.Many2one("fsm.order.type", string="Type") team_id = fields.Many2one( - 'fsm.team', string='Team', - help='Choose a team to be set on orders of this template') + "fsm.team", + string="Team", + help="Choose a team to be set on orders of this template", + ) diff --git a/fieldservice/models/fsm_territory.py b/fieldservice/models/fsm_territory.py deleted file mode 100644 index 13c5d5bacb..0000000000 --- a/fieldservice/models/fsm_territory.py +++ /dev/null @@ -1,11 +0,0 @@ -# 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 ResTerritory(models.Model): - _inherit = 'res.territory' - - person_ids = fields.Many2many('fsm.person', string='Field Service Workers') - person_id = fields.Many2one('fsm.person', string='Primary Assignment') diff --git a/fieldservice/models/res_company.py b/fieldservice/models/res_company.py index a1d7eb4753..9f0aac4eb0 100644 --- a/fieldservice/models/res_company.py +++ b/fieldservice/models/res_company.py @@ -1,11 +1,12 @@ -# Copyright (C) 2019 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields, models class ResCompany(models.Model): - _inherit = 'res.company' + _inherit = "res.company" auto_populate_persons_on_location = fields.Boolean( - string='Auto-populate Workers on Location based on Territory') + string="Auto-populate Workers on Location based on Territory" + ) diff --git a/fieldservice/models/res_config_settings.py b/fieldservice/models/res_config_settings.py index 5b6c9a1470..f05828c881 100644 --- a/fieldservice/models/res_config_settings.py +++ b/fieldservice/models/res_config_settings.py @@ -1,87 +1,80 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import api, fields, models class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' + _inherit = "res.config.settings" # Groups group_fsm_team = fields.Boolean( - string='Manage Teams', - implied_group='fieldservice.group_fsm_team') + string="Manage Teams", implied_group="fieldservice.group_fsm_team" + ) group_fsm_category = fields.Boolean( - string='Manage Categories', - implied_group='fieldservice.group_fsm_category') + string="Manage Categories", implied_group="fieldservice.group_fsm_category" + ) group_fsm_tag = fields.Boolean( - string='Manage Tags', - implied_group='fieldservice.group_fsm_tag') + string="Manage Tags", implied_group="fieldservice.group_fsm_tag" + ) group_fsm_substatus = fields.Boolean( - string='Manage Substatus', - implied_group='fieldservice.group_fsm_substatus') + string="Manage Substatus", implied_group="fieldservice.group_fsm_substatus" + ) group_fsm_equipment = fields.Boolean( - string='Manage Equipment', - implied_group='fieldservice.group_fsm_equipment') + string="Manage Equipment", implied_group="fieldservice.group_fsm_equipment" + ) group_fsm_template = fields.Boolean( - string='Manage Template', - implied_group='fieldservice.group_fsm_template') + string="Manage Template", implied_group="fieldservice.group_fsm_template" + ) # Modules - module_fieldservice_account = fields.Boolean( - string='Invoice your FSM orders') - module_fieldservice_activity = fields.Boolean( - string='Manage FSM Activities') - module_fieldservice_agreement = fields.Boolean( - string='Manage Agreements') - module_fieldservice_distribution = fields.Boolean( - string='Manage Distribution') - module_fieldservice_geoengine = fields.Boolean( - string='Use GeoEngine') + module_fieldservice_account = fields.Boolean(string="Invoice your FSM orders") + module_fieldservice_activity = fields.Boolean(string="Manage FSM Activities") + module_fieldservice_agreement = fields.Boolean(string="Manage Agreements") + module_fieldservice_distribution = fields.Boolean(string="Manage Distribution") + module_fieldservice_geoengine = fields.Boolean(string="Use GeoEngine") module_fieldservice_maintenance = fields.Boolean( - string='Link FSM orders to maintenance requests') + string="Link FSM orders to maintenance requests" + ) module_fieldservice_purchase = fields.Boolean( - string='Manage subcontractors and their pricelists') + string="Manage subcontractors and their pricelists" + ) module_fieldservice_repair = fields.Boolean( - string='Link FSM orders to MRP Repair orders') - module_fieldservice_skill = fields.Boolean( - string='Manage Skills') - module_fieldservice_stock = fields.Boolean( - string='Use Odoo Logistics') - module_fieldservice_vehicle = fields.Boolean( - string='Manage Vehicles') - module_fieldservice_substatus = fields.Boolean( - string='Manage Sub-Statuses') - module_fieldservice_recurring = fields.Boolean( - string='Manage Recurring Orders') + string="Link FSM orders to MRP Repair orders" + ) + module_fieldservice_skill = fields.Boolean(string="Manage Skills") + module_fieldservice_stock = fields.Boolean(string="Use Odoo Logistics") + module_fieldservice_vehicle = fields.Boolean(string="Manage Vehicles") + module_fieldservice_substatus = fields.Boolean(string="Manage Sub-Statuses") + module_fieldservice_recurring = fields.Boolean(string="Manage Recurring Orders") auto_populate_persons_on_location = fields.Boolean( - string='Auto-populate Workers on Location based on Territory', - related='company_id.auto_populate_persons_on_location', - readonly=False) - module_fieldservice_project = fields.Boolean( - string='Projects and Tasks') - module_fieldservice_crm = fields.Boolean( - string='CRM') + string="Auto-populate Workers on Location based on Territory", + related="company_id.auto_populate_persons_on_location", + readonly=False, + ) + module_fieldservice_project = fields.Boolean(string="Projects and Tasks") + module_fieldservice_crm = fields.Boolean(string="CRM") # Companies auto_populate_persons_on_location = fields.Boolean( - string='Auto-populate Workers on Location based on Territory', - related='company_id.auto_populate_persons_on_location', - readonly=False) + string="Auto-populate Workers on Location based on Territory", + related="company_id.auto_populate_persons_on_location", + readonly=False, + ) # Dependencies - @api.onchange('module_fieldservice_repair') + @api.onchange("module_fieldservice_repair") def _onchange_module_fieldservice_repair(self): if self.module_fieldservice_repair: self.group_fsm_equipment = True - @api.onchange('module_fieldservice_stock') + @api.onchange("module_fieldservice_stock") def _onchange_module_fieldservice_stock(self): if self.module_fieldservice_stock: self.group_stock_production_lot = True self.group_stock_request_order = True - @api.onchange('module_fieldservice_purchase') + @api.onchange("module_fieldservice_purchase") def _onchange_module_fieldservice_purchase(self): if self.module_fieldservice_purchase: self.group_manage_vendor_price = True diff --git a/fieldservice/models/res_partner.py b/fieldservice/models/res_partner.py index de366fe59e..29a06fac30 100644 --- a/fieldservice/models/res_partner.py +++ b/fieldservice/models/res_partner.py @@ -1,45 +1,44 @@ -# Copyright (C) 2018 - TODAY, Open Source Integrators +# Copyright (C) 2020 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import fields, models class ResPartner(models.Model): - _inherit = 'res.partner' + _inherit = "res.partner" - fsm_location = fields.Boolean('Is a FS Location') - fsm_person = fields.Boolean('Is a FS Worker') - service_location_id = fields.Many2one('fsm.location', - string='Primary Service Location') + fsm_location = fields.Boolean("Is a FS Location") + fsm_person = fields.Boolean("Is a FS Worker") + service_location_id = fields.Many2one( + "fsm.location", string="Primary Service Location" + ) owned_location_ids = fields.One2many( - 'fsm.location', - 'owner_id', - string='Owned Locations', - domain=[('fsm_parent_id', '=', False)]) + "fsm.location", + "owner_id", + string="Owned Locations", + domain=[("fsm_parent_id", "=", False)], + ) owned_location_count = fields.Integer( - compute='_compute_owned_location_count', - string="# of Owned Locations") + compute="_compute_owned_location_count", string="# of Owned Locations" + ) - @api.multi def _compute_owned_location_count(self): for partner in self: - res = self.env['fsm.location'].search_count( - [('owner_id', '=', partner.id)]) + res = self.env["fsm.location"].search_count([("owner_id", "=", partner.id)]) partner.owned_location_count = res - @api.multi def action_open_owned_locations(self): for partner in self: - owned_location_ids = self.env['fsm.location'].search( - [('owner_id', '=', partner.id)]) - action = self.env.ref( - 'fieldservice.action_fsm_location').read()[0] - action['context'] = {} + owned_location_ids = self.env["fsm.location"].search( + [("owner_id", "=", partner.id)] + ) + action = self.env.ref("fieldservice.action_fsm_location").read()[0] + action["context"] = {} if len(owned_location_ids) > 1: - action['domain'] = [('id', 'in', owned_location_ids.ids)] + action["domain"] = [("id", "in", owned_location_ids.ids)] elif len(owned_location_ids) == 1: - action['views'] = [( - self.env.ref( - 'fieldservice.fsm_location_form_view').id, 'form')] - action['res_id'] = owned_location_ids.ids[0] + action["views"] = [ + (self.env.ref("fieldservice.fsm_location_form_view").id, "form") + ] + action["res_id"] = owned_location_ids.ids[0] return action diff --git a/fieldservice/models/res_territory.py b/fieldservice/models/res_territory.py new file mode 100644 index 0000000000..0b2c01128c --- /dev/null +++ b/fieldservice/models/res_territory.py @@ -0,0 +1,11 @@ +# Copyright (C) 2020 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResTerritory(models.Model): + _inherit = "res.territory" + + person_ids = fields.Many2many("fsm.person", string="Field Service Workers") + person_id = fields.Many2one("fsm.person", string="Primary Assignment") diff --git a/fieldservice/readme/CONFIGURE.rst b/fieldservice/readme/CONFIGURE.rst index 54e1be38f9..66777ea67e 100644 --- a/fieldservice/readme/CONFIGURE.rst +++ b/fieldservice/readme/CONFIGURE.rst @@ -22,7 +22,7 @@ You need to add attribute mention below with the tag as base element. condition (JS syntax) is met. * custom_color (optional): it allows to set custom color for fsm.stages example custom_color = "true". And there is minor condition to follow to - implement this as. Define any one stage color condition like + implement this as. Define any one stage color condition like colors="#ffffff:stage_id=='New';" Field Service Areas diff --git a/fieldservice/report/fsm_order_report_template.xml b/fieldservice/report/fsm_order_report_template.xml index 7bce6a923b..188c110e28 100644 --- a/fieldservice/report/fsm_order_report_template.xml +++ b/fieldservice/report/fsm_order_report_template.xml @@ -6,7 +6,7 @@
-
+
Date Completed:

diff --git a/fieldservice/security/ir.model.access.csv b/fieldservice/security/ir.model.access.csv index 035eb641bb..6ac32521b1 100644 --- a/fieldservice/security/ir.model.access.csv +++ b/fieldservice/security/ir.model.access.csv @@ -9,8 +9,6 @@ access_fsm_location_user,fsm.location.user,model_fsm_location,fieldservice.group access_fsm_location_dispatcher,fsm.location.dispatcher,model_fsm_location,fieldservice.group_fsm_dispatcher,1,1,1,0 access_fsm_order_user,fsm.order.user,model_fsm_order,fieldservice.group_fsm_user,1,1,0,0 access_fsm_order_dispatcher,fsm.order.dispatcher,model_fsm_order,fieldservice.group_fsm_dispatcher,1,1,1,0 -access_fsm_route_user,fsm.route.user,model_fsm_route,fieldservice.group_fsm_user,1,1,0,0 -access_fsm_route_dispatcher,fsm.route.dispatcher,model_fsm_route,fieldservice.group_fsm_dispatcher,1,1,1,1 access_fsm_equipment_fsm_user,fsm.equipment.user,model_fsm_equipment,fieldservice.group_fsm_user,1,0,0,0 access_fsm_equipment_fsm_manager,fsm.equipment.manager,model_fsm_equipment,fieldservice.group_fsm_manager,1,1,1,1 access_fsm_category_user,fsm.category.user,model_fsm_category,fieldservice.group_fsm_user,1,0,0,0 @@ -21,3 +19,5 @@ access_fsm_team_user,fsm.team.user,model_fsm_team,fieldservice.group_fsm_user,1, access_fsm_team_manager,fsm.team.manager,model_fsm_team,fieldservice.group_fsm_manager,1,1,1,1 access_fsm_location_person_user,fsm.location.person.user,model_fsm_location_person,fieldservice.group_fsm_user,1,1,0,0 access_fsm_location_person_manager,fsm.location.person.manager,model_fsm_location_person,fieldservice.group_fsm_manager,1,1,1,1 +access_fsm_order_type_user,fsm.order.type.user,model_fsm_order_type,fieldservice.group_fsm_user,1,1,0,0 +access_fsm_order_type_manager,fsm.order.type.manager,model_fsm_order_type,fieldservice.group_fsm_manager,1,1,1,1 diff --git a/fieldservice/security/ir_rule.xml b/fieldservice/security/ir_rule.xml index ce879b1381..954acac569 100644 --- a/fieldservice/security/ir_rule.xml +++ b/fieldservice/security/ir_rule.xml @@ -17,13 +17,6 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - - FSM Routes Entry - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - - FSM Teams Entry diff --git a/fieldservice/static/description/index.html b/fieldservice/static/description/index.html index 9fe613a42e..3938dcb656 100644 --- a/fieldservice/static/description/index.html +++ b/fieldservice/static/description/index.html @@ -3,7 +3,7 @@ - + Field Service