feat(workorders): move address to contact and auto-compute zone
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from . import dsrpt_communication_type
|
||||
from . import dsrpt_contact
|
||||
from . import dsrpt_contact_address
|
||||
from . import dsrpt_contact_communication
|
||||
from . import contact_event
|
||||
from . import contact_source
|
||||
|
||||
@@ -38,6 +38,11 @@ class Contact(models.Model):
|
||||
'contact_id',
|
||||
string='Events'
|
||||
)
|
||||
address_ids = fields.One2many(
|
||||
'dsrpt.contact.address',
|
||||
'contact_id',
|
||||
string='Addresses'
|
||||
)
|
||||
# call_ids moved to dsrpt_calls module to avoid circular dependencies
|
||||
|
||||
# Computed fields
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ContactAddress(models.Model):
|
||||
_name = "dsrpt.contact.address"
|
||||
_description = "Contact Address"
|
||||
_order = "id desc"
|
||||
_rec_name = "description"
|
||||
|
||||
contact_id = fields.Many2one("dsrpt.contact", required=True, ondelete="cascade", index=True)
|
||||
description = fields.Char(required=True)
|
||||
latitude = fields.Float(digits=(10, 6))
|
||||
longitude = fields.Float(digits=(10, 6))
|
||||
@@ -5,6 +5,8 @@ access_dsrpt_communication_type_user,dsrpt.communication.type.user,model_dsrpt_c
|
||||
access_dsrpt_communication_type_admin,dsrpt.communication.type.admin,model_dsrpt_communication_type,group_dsrpt_address_book_admin,1,1,1,1
|
||||
access_dsrpt_contact_communication_user,dsrpt.contact.communication.user,model_dsrpt_contact_communication,group_dsrpt_address_book_user,1,1,1,1
|
||||
access_dsrpt_contact_communication_admin,dsrpt.contact.communication.admin,model_dsrpt_contact_communication,group_dsrpt_address_book_admin,1,1,1,1
|
||||
access_dsrpt_contact_address_user,dsrpt.contact.address.user,model_dsrpt_contact_address,group_dsrpt_address_book_user,1,1,1,1
|
||||
access_dsrpt_contact_address_admin,dsrpt.contact.address.admin,model_dsrpt_contact_address,group_dsrpt_address_book_admin,1,1,1,1
|
||||
access_contact_event_user,contact.event.user,model_contact_event,group_dsrpt_address_book_user,1,1,1,0
|
||||
access_contact_event_admin,contact.event.admin,model_contact_event,group_dsrpt_address_book_admin,1,1,1,1
|
||||
access_contact_source_user,contact.source.user,model_contact_source,group_dsrpt_address_book_user,1,1,1,0
|
||||
|
||||
|
@@ -96,7 +96,18 @@
|
||||
<group name="requests_placeholder"/>
|
||||
</group>
|
||||
|
||||
<!-- Third row: Communications (full width) -->
|
||||
<!-- Third row: Addresses (full width) -->
|
||||
<group col="1" string="Addresses">
|
||||
<field name="address_ids" nolabel="1" context="{'default_contact_id': id}">
|
||||
<list editable="bottom">
|
||||
<field name="description"/>
|
||||
<field name="latitude"/>
|
||||
<field name="longitude"/>
|
||||
</list>
|
||||
</field>
|
||||
</group>
|
||||
|
||||
<!-- Fourth row: Communications (full width) -->
|
||||
<group col="1" string="Communications">
|
||||
<field name="communication_ids" nolabel="1" context="{'default_contact_id': id}">
|
||||
<list editable="bottom">
|
||||
@@ -107,7 +118,7 @@
|
||||
</field>
|
||||
</group>
|
||||
|
||||
<!-- Fourth row: Calls (full width) - will be added by dsrpt_calls module -->
|
||||
<!-- Fifth row: Calls (full width) - will be added by dsrpt_calls module -->
|
||||
<group col="1" name="calls_section"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
|
||||
@@ -10,10 +10,20 @@ class RepairWorkOrder(models.Model):
|
||||
|
||||
name = fields.Char(default="New", copy=False, readonly=True, tracking=True)
|
||||
contact_id = fields.Many2one("dsrpt.contact", required=True, tracking=True)
|
||||
service_address = fields.Char(tracking=True)
|
||||
service_latitude = fields.Float(digits=(10, 6), tracking=True)
|
||||
service_longitude = fields.Float(digits=(10, 6), tracking=True)
|
||||
zone_id = fields.Many2one("repair.fsm.zone", string="FSM Zone", tracking=True)
|
||||
contact_address_id = fields.Many2one(
|
||||
"dsrpt.contact.address",
|
||||
string="Address",
|
||||
tracking=True,
|
||||
domain="[('contact_id', '=', contact_id)]",
|
||||
)
|
||||
zone_id = fields.Many2one(
|
||||
"repair.fsm.zone",
|
||||
string="FSM Zone",
|
||||
compute="_compute_zone_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
tracking=True,
|
||||
)
|
||||
description = fields.Text(tracking=True)
|
||||
requested_datetime = fields.Datetime(default=fields.Datetime.now, tracking=True)
|
||||
scheduled_datetime = fields.Datetime(tracking=True)
|
||||
@@ -42,15 +52,7 @@ class RepairWorkOrder(models.Model):
|
||||
for vals in vals_list:
|
||||
if vals.get("name", "New") == "New":
|
||||
vals["name"] = self.env["ir.sequence"].next_by_code("repair.work.order") or "New"
|
||||
records = super().create(vals_list)
|
||||
for record, vals in zip(records, vals_list):
|
||||
if vals.get("zone_id"):
|
||||
continue
|
||||
if record._has_service_point():
|
||||
zone = record._find_zone_for_point(record.service_latitude, record.service_longitude)
|
||||
if zone and record.zone_id != zone:
|
||||
record.zone_id = zone.id
|
||||
return records
|
||||
return super().create(vals_list)
|
||||
|
||||
def _group_expand_states(self, states, domain, order):
|
||||
return [key for key, _label in self._fields["state"].selection]
|
||||
@@ -61,44 +63,30 @@ class RepairWorkOrder(models.Model):
|
||||
rec.total_time_hours = sum(rec.time_line_ids.mapped("hours"))
|
||||
rec.total_material_cost = sum(rec.material_line_ids.mapped("subtotal"))
|
||||
|
||||
def _has_service_point(self):
|
||||
self.ensure_one()
|
||||
return self.service_latitude is not False and self.service_longitude is not False
|
||||
|
||||
def _find_zone_for_point(self, latitude, longitude):
|
||||
zones = self.env["repair.fsm.zone"].search([("state", "=", "active")])
|
||||
if latitude is False or longitude is False or latitude is None or longitude is None:
|
||||
return self.env["repair.fsm.zone"]
|
||||
zone_domain = [("state", "=", "active")] if "state" in self.env["repair.fsm.zone"]._fields else []
|
||||
zones = self.env["repair.fsm.zone"].search(zone_domain)
|
||||
for zone in zones:
|
||||
if zone.contains_point(latitude, longitude):
|
||||
return zone
|
||||
return self.env["repair.fsm.zone"]
|
||||
|
||||
@api.onchange("service_latitude", "service_longitude")
|
||||
def _onchange_service_coordinates(self):
|
||||
@api.depends("contact_address_id.latitude", "contact_address_id.longitude")
|
||||
def _compute_zone_id(self):
|
||||
for rec in self:
|
||||
if rec._has_service_point():
|
||||
zone = rec._find_zone_for_point(rec.service_latitude, rec.service_longitude)
|
||||
if zone:
|
||||
rec.zone_id = zone
|
||||
if not rec.contact_address_id:
|
||||
rec.zone_id = False
|
||||
continue
|
||||
zone = rec._find_zone_for_point(rec.contact_address_id.latitude, rec.contact_address_id.longitude)
|
||||
rec.zone_id = zone.id if zone else False
|
||||
|
||||
def action_detect_zone(self):
|
||||
@api.onchange("contact_id")
|
||||
def _onchange_contact_id(self):
|
||||
for rec in self:
|
||||
if not rec._has_service_point():
|
||||
raise ValidationError("Service coordinates are required to detect FSM Zone.")
|
||||
zone = rec._find_zone_for_point(rec.service_latitude, rec.service_longitude)
|
||||
if not zone:
|
||||
raise ValidationError("No active FSM Zone contains this point.")
|
||||
rec.zone_id = zone.id
|
||||
return True
|
||||
|
||||
def write(self, vals):
|
||||
result = super().write(vals)
|
||||
if ("service_latitude" in vals or "service_longitude" in vals) and "zone_id" not in vals:
|
||||
for rec in self:
|
||||
if rec._has_service_point():
|
||||
zone = rec._find_zone_for_point(rec.service_latitude, rec.service_longitude)
|
||||
if zone and rec.zone_id != zone:
|
||||
super(RepairWorkOrder, rec).write({"zone_id": zone.id})
|
||||
return result
|
||||
if rec.contact_address_id and rec.contact_address_id.contact_id != rec.contact_id:
|
||||
rec.contact_address_id = False
|
||||
|
||||
def action_confirm(self):
|
||||
self.write({"state": "confirmed"})
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<header>
|
||||
<button name="action_confirm" type="object" string="Confirm" class="btn-primary" invisible="state != 'draft'"/>
|
||||
<button name="action_assign_to_me" type="object" string="Assign to me" class="btn-primary" invisible="state not in ('confirmed','assigned')"/>
|
||||
<button name="action_detect_zone" type="object" string="Detect Zone" invisible="service_latitude == False or service_longitude == False"/>
|
||||
<button name="action_start" type="object" string="Start" invisible="state != 'assigned'"/>
|
||||
<button name="action_done" type="object" string="Done" class="btn-primary" invisible="state not in ('in_progress','assigned')"/>
|
||||
<button name="action_cancel" type="object" string="Cancel" invisible="state in ('done','cancelled')"/>
|
||||
@@ -20,10 +19,8 @@
|
||||
<group>
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="contact_id"/>
|
||||
<field name="service_address"/>
|
||||
<field name="service_latitude"/>
|
||||
<field name="service_longitude"/>
|
||||
<field name="zone_id"/>
|
||||
<field name="contact_address_id" domain="[('contact_id', '=', contact_id)]" options="{'no_create': True, 'no_create_edit': True}"/>
|
||||
<field name="zone_id" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="requested_datetime"/>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<kanban default_group_by="state">
|
||||
<field name="name"/>
|
||||
<field name="contact_id"/>
|
||||
<field name="service_address"/>
|
||||
<field name="contact_address_id"/>
|
||||
<field name="scheduled_datetime"/>
|
||||
<field name="technician_id"/>
|
||||
<field name="state"/>
|
||||
@@ -21,7 +21,7 @@
|
||||
<field name="contact_id"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="service_address"/>
|
||||
<field name="contact_address_id"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="scheduled_datetime"/>
|
||||
|
||||
@@ -7,15 +7,13 @@
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="contact_id" optional="show"/>
|
||||
<field name="service_address" optional="show"/>
|
||||
<field name="contact_address_id" optional="show"/>
|
||||
<field name="zone_id" optional="show"/>
|
||||
<field name="scheduled_datetime" optional="show"/>
|
||||
<field name="technician_id" optional="show"/>
|
||||
<field name="state" widget="badge" optional="show"/>
|
||||
<field name="total_time_hours" optional="show"/>
|
||||
<field name="total_material_cost" optional="show"/>
|
||||
<field name="service_latitude" optional="hide"/>
|
||||
<field name="service_longitude" optional="hide"/>
|
||||
<field name="assigned_user_id" optional="hide"/>
|
||||
<field name="requested_datetime" optional="hide"/>
|
||||
</list>
|
||||
|
||||
Reference in New Issue
Block a user