refactor(fusion_tasks): strip all sync references from task model

Remove cross-instance sync fields (x_fc_sync_source, x_fc_sync_remote_id,
x_fc_sync_uuid, x_fc_is_shadow, x_fc_sync_client_name, x_fc_sync_client_phone,
x_fc_source_label), sync push calls in create/write/action methods, shadow
task logic in constraints and email guards, and x_fc_tech_sync_id from
res.users. Also remove task_sync import from models/__init__.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-12 20:26:23 -04:00
parent bc72486808
commit e10bf9d8fd
3 changed files with 13 additions and 117 deletions

View File

@@ -8,6 +8,5 @@ from . import res_company
from . import res_users from . import res_users
from . import res_config_settings from . import res_config_settings
from . import technician_task from . import technician_task
from . import task_sync
from . import technician_location from . import technician_location
from . import push_subscription from . import push_subscription

View File

@@ -18,9 +18,3 @@ class ResUsers(models.Model):
readonly=False, readonly=False,
string='Start Location', string='Start Location',
) )
x_fc_tech_sync_id = fields.Char(
string='Tech Sync ID',
help='Shared identifier for this technician across Odoo instances. '
'Must be the same value on all instances for the same person.',
copy=False,
)

View File

@@ -14,7 +14,6 @@ from odoo.osv import expression
from markupsafe import Markup from markupsafe import Markup
import logging import logging
import json import json
import uuid
import requests import requests
from datetime import datetime as dt_datetime, timedelta from datetime import datetime as dt_datetime, timedelta
import urllib.parse import urllib.parse
@@ -33,7 +32,7 @@ class FusionTechnicianTask(models.Model):
"""Richer display name: Client - Type | 9:00 AM - 10:00 AM [+2 techs].""" """Richer display name: Client - Type | 9:00 AM - 10:00 AM [+2 techs]."""
type_labels = dict(self._fields['task_type'].selection) type_labels = dict(self._fields['task_type'].selection)
for task in self: for task in self:
client = task.x_fc_sync_client_name if task.x_fc_sync_source else (task.partner_id.name or '') client = task.partner_id.name or ''
ttype = type_labels.get(task.task_type, task.task_type or '') ttype = type_labels.get(task.task_type, task.task_type or '')
start = self._float_to_time_str(task.time_start) start = self._float_to_time_str(task.time_start)
end = self._float_to_time_str(task.time_end) end = self._float_to_time_str(task.time_end)
@@ -74,32 +73,6 @@ class FusionTechnicianTask(models.Model):
) )
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
# Cross-instance sync fields
x_fc_sync_source = fields.Char(
'Source Instance', readonly=True, index=True,
help='Origin instance ID if this is a synced shadow task (e.g. westin, mobility)',
)
x_fc_sync_remote_id = fields.Integer(
'Remote Task ID', readonly=True,
help='ID of the task on the remote instance',
)
x_fc_sync_uuid = fields.Char(
'Sync UUID', readonly=True, index=True, copy=False,
help='Unique ID for cross-instance deduplication',
)
x_fc_is_shadow = fields.Boolean(
'Shadow Task', compute='_compute_is_shadow', store=True,
help='True if this task was synced from another instance',
)
x_fc_sync_client_name = fields.Char(
'Synced Client Name', readonly=True,
help='Client name from the remote instance (shadow tasks only)',
)
x_fc_sync_client_phone = fields.Char(
'Synced Client Phone', readonly=True,
help='Client phone from the remote instance (shadow tasks only)',
)
client_display_name = fields.Char( client_display_name = fields.Char(
compute='_compute_client_display', string='Client Name (Display)', compute='_compute_client_display', string='Client Name (Display)',
) )
@@ -107,28 +80,11 @@ class FusionTechnicianTask(models.Model):
compute='_compute_client_display', string='Client Phone (Display)', compute='_compute_client_display', string='Client Phone (Display)',
) )
x_fc_source_label = fields.Char( @api.depends('partner_id')
'Source', compute='_compute_is_shadow', store=True,
)
@api.depends('x_fc_sync_source')
def _compute_is_shadow(self):
local_id = self.env['ir.config_parameter'].sudo().get_param(
'fusion_claims.sync_instance_id', '')
for task in self:
task.x_fc_is_shadow = bool(task.x_fc_sync_source)
task.x_fc_source_label = task.x_fc_sync_source or local_id
@api.depends('x_fc_sync_source', 'x_fc_sync_client_name',
'x_fc_sync_client_phone', 'partner_id')
def _compute_client_display(self): def _compute_client_display(self):
for task in self: for task in self:
if task.x_fc_sync_source: task.client_display_name = task.partner_id.name if task.partner_id else ''
task.client_display_name = task.x_fc_sync_client_name or task.name or '' task.client_display_phone = task.partner_id.phone if task.partner_id else ''
task.client_display_phone = task.x_fc_sync_client_phone or ''
else:
task.client_display_name = task.partner_id.name if task.partner_id else ''
task.client_display_phone = task.partner_id.phone if task.partner_id else ''
technician_id = fields.Many2one( technician_id = fields.Many2one(
'res.users', 'res.users',
@@ -1101,8 +1057,6 @@ class FusionTechnicianTask(models.Model):
def _check_address_required(self): def _check_address_required(self):
"""Non-in-store tasks must have a geocoded address.""" """Non-in-store tasks must have a geocoded address."""
for task in self: for task in self:
if task.x_fc_sync_source:
continue
if task.is_in_store: if task.is_in_store:
continue continue
if not task.address_street: if not task.address_street:
@@ -1121,8 +1075,6 @@ class FusionTechnicianTask(models.Model):
for task in self: for task in self:
if task.status == 'cancelled': if task.status == 'cancelled':
continue continue
if task.x_fc_sync_source:
continue
# Validate time range # Validate time range
if task.time_start >= task.time_end: if task.time_start >= task.time_end:
raise ValidationError(_("Start time must be before end time.")) raise ValidationError(_("Start time must be before end time."))
@@ -1134,8 +1086,8 @@ class FusionTechnicianTask(models.Model):
raise ValidationError(_( raise ValidationError(_(
"Tasks must be scheduled within store hours (%s - %s)." "Tasks must be scheduled within store hours (%s - %s)."
) % (open_str, close_str)) ) % (open_str, close_str))
# Validate not in the past (only for new/scheduled local tasks) # Validate not in the past (only for new/scheduled tasks)
if task.status == 'scheduled' and task.scheduled_date and not task.x_fc_sync_source: if task.status == 'scheduled' and task.scheduled_date:
local_now = self._local_now() local_now = self._local_now()
today = local_now.date() today = local_now.date()
if task.scheduled_date < today: if task.scheduled_date < today:
@@ -1408,8 +1360,6 @@ class FusionTechnicianTask(models.Model):
for vals in vals_list: for vals in vals_list:
if vals.get('name', _('New')) == _('New'): if vals.get('name', _('New')) == _('New'):
vals['name'] = self.env['ir.sequence'].next_by_code('fusion.technician.task') or _('New') vals['name'] = self.env['ir.sequence'].next_by_code('fusion.technician.task') or _('New')
if not vals.get('x_fc_sync_uuid') and not vals.get('x_fc_sync_source'):
vals['x_fc_sync_uuid'] = str(uuid.uuid4())
# In-store tasks: auto-fill company address # In-store tasks: auto-fill company address
if vals.get('is_in_store') and not vals.get('address_street'): if vals.get('is_in_store') and not vals.get('address_street'):
company_partner = self.env.company.partner_id company_partner = self.env.company.partner_id
@@ -1417,7 +1367,7 @@ class FusionTechnicianTask(models.Model):
self._fill_address_vals(vals, company_partner) self._fill_address_vals(vals, company_partner)
else: else:
vals['address_street'] = self.env.company.name or 'In Store' vals['address_street'] = self.env.company.name or 'In Store'
# Hook: fill address from linked records (overridden by fusion_claims) # Hook: fill address from linked records
self._create_vals_fill(vals) self._create_vals_fill(vals)
records = super().create(vals_list) records = super().create(vals_list)
# Hook: post-create actions for linked records # Hook: post-create actions for linked records
@@ -1428,10 +1378,6 @@ class FusionTechnicianTask(models.Model):
# Send "Appointment Scheduled" email # Send "Appointment Scheduled" email
for rec in records: for rec in records:
rec._send_task_scheduled_email() rec._send_task_scheduled_email()
# Push new local tasks to remote instances
local_records = records.filtered(lambda r: not r.x_fc_sync_source)
if local_records and not self.env.context.get('skip_task_sync'):
self.env['fusion.task.sync.config']._push_tasks(local_records, 'create')
# Sync to calendar for external calendar integrations # Sync to calendar for external calendar integrations
records._sync_calendar_event() records._sync_calendar_event()
return records return records
@@ -1457,16 +1403,7 @@ class FusionTechnicianTask(models.Model):
def write(self, vals): def write(self, vals):
if self.env.context.get('skip_travel_recalc'): if self.env.context.get('skip_travel_recalc'):
res = super().write(vals) return super().write(vals)
if ('status' in vals and vals['status'] in ('completed', 'cancelled')
and not self.env.context.get('skip_task_sync')):
shadow_records = self.filtered(lambda r: r.x_fc_sync_source)
if shadow_records:
self.env['fusion.task.sync.config']._push_shadow_status(shadow_records)
local_records = self.filtered(lambda r: not r.x_fc_sync_source)
if local_records:
self.env['fusion.task.sync.config']._push_tasks(local_records, 'write')
return res
# Safety: ensure time_end is consistent when start/duration change # Safety: ensure time_end is consistent when start/duration change
# but time_end wasn't sent (readonly field in view may not save) # but time_end wasn't sent (readonly field in view may not save)
@@ -1526,20 +1463,6 @@ class FusionTechnicianTask(models.Model):
old_start=old['time_start'], old_start=old['time_start'],
old_end=old['time_end'], old_end=old['time_end'],
) )
# Push updates to remote instances for local tasks
sync_fields = {'technician_id', 'additional_technician_ids',
'scheduled_date', 'time_start', 'time_end',
'duration_hours', 'status', 'task_type', 'address_street',
'address_city', 'address_zip', 'address_lat', 'address_lng',
'partner_id'}
if sync_fields & set(vals.keys()) and not self.env.context.get('skip_task_sync'):
local_records = self.filtered(lambda r: not r.x_fc_sync_source)
if local_records:
self.env['fusion.task.sync.config']._push_tasks(local_records, 'write')
if 'status' in vals and vals['status'] in ('completed', 'cancelled'):
shadow_records = self.filtered(lambda r: r.x_fc_sync_source)
if shadow_records:
self.env['fusion.task.sync.config']._push_shadow_status(shadow_records)
# Re-sync calendar event when schedule fields change # Re-sync calendar event when schedule fields change
cal_fields = {'scheduled_date', 'time_start', 'time_end', cal_fields = {'scheduled_date', 'time_start', 'time_end',
'duration_hours', 'technician_id', 'task_type', 'duration_hours', 'technician_id', 'task_type',
@@ -1928,12 +1851,6 @@ class FusionTechnicianTask(models.Model):
task._send_task_en_route_email() task._send_task_en_route_email()
# Recalculate travel from tech's current location to THIS task # Recalculate travel from tech's current location to THIS task
task._recalculate_travel_from_current_location() task._recalculate_travel_from_current_location()
if task.x_fc_sync_source:
try:
self.env['fusion.task.sync.config']._push_shadow_status(task)
except Exception:
_logger.exception(
"Failed to push en_route for shadow %s", task.name)
try: try:
remaining = self.sudo().search_count([ remaining = self.sudo().search_count([
('technician_id', '=', task.technician_id.id), ('technician_id', '=', task.technician_id.id),
@@ -2012,14 +1929,7 @@ class FusionTechnicianTask(models.Model):
task.status = 'cancelled' task.status = 'cancelled'
task._write_action_location() task._write_action_location()
task._post_status_message('cancelled') task._post_status_message('cancelled')
if task.x_fc_sync_source: task._on_cancel_extra()
try:
self.env['fusion.task.sync.config']._push_shadow_status(task)
except Exception:
_logger.exception(
"Failed to push cancel for shadow %s", task.name)
else:
task._on_cancel_extra()
def _on_cancel_extra(self): def _on_cancel_extra(self):
"""Hook: additional side-effects after task cancellation. """Hook: additional side-effects after task cancellation.
@@ -2079,14 +1989,8 @@ class FusionTechnicianTask(models.Model):
pass pass
def _notify_scheduler_on_completion(self): def _notify_scheduler_on_completion(self):
"""Send an Odoo notification to the person who scheduled the task. """Send an Odoo notification to the person who scheduled the task."""
Shadow tasks skip this -- the push-back to the source instance
triggers the notification there where the real scheduler exists.
"""
self.ensure_one() self.ensure_one()
if self.x_fc_sync_source:
return
recipient = None recipient = None
order = self._get_linked_order() order = self._get_linked_order()
@@ -2224,7 +2128,7 @@ class FusionTechnicianTask(models.Model):
def _send_task_en_route_email(self): def _send_task_en_route_email(self):
"""Email the client that the technician is on the way.""" """Email the client that the technician is on the way."""
self.ensure_one() self.ensure_one()
if self.x_fc_sync_source or not self.x_fc_send_client_updates: if not self.x_fc_send_client_updates:
return False return False
if not self.partner_id or not self.partner_id.email: if not self.partner_id or not self.partner_id.email:
return False return False
@@ -2312,7 +2216,7 @@ class FusionTechnicianTask(models.Model):
- Standard thank-you without review request - Standard thank-you without review request
""" """
self.ensure_one() self.ensure_one()
if self.x_fc_sync_source or not self.x_fc_send_client_updates: if not self.x_fc_send_client_updates:
return False return False
if not self.partner_id or not self.partner_id.email: if not self.partner_id or not self.partner_id.email:
return False return False
@@ -2457,7 +2361,7 @@ class FusionTechnicianTask(models.Model):
'address_lat', 'address_lng', 'address_display', 'address_lat', 'address_lng', 'address_display',
'time_start', 'time_end', 'time_start_display', 'time_end_display', 'time_start', 'time_end', 'time_start_display', 'time_end_display',
'status', 'scheduled_date', 'travel_time_minutes', 'status', 'scheduled_date', 'travel_time_minutes',
'x_fc_sync_client_name', 'x_fc_is_shadow', 'x_fc_sync_source'], ],
order='scheduled_date asc NULLS LAST, time_start asc', order='scheduled_date asc NULLS LAST, time_start asc',
limit=500, limit=500,
) )
@@ -2802,7 +2706,6 @@ class FusionTechnicianTask(models.Model):
('status', '=', 'scheduled'), ('status', '=', 'scheduled'),
('time_start', '<', current_hour), ('time_start', '<', current_hour),
('x_fc_late_notified', '=', False), ('x_fc_late_notified', '=', False),
('x_fc_sync_source', '=', False),
('technician_id', '!=', False), ('technician_id', '!=', False),
]) ])