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:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user