Add work types and in-form technician slot suggestions

This commit is contained in:
Ruslan Bakiev
2026-02-13 18:36:40 +07:00
parent 2b7a9457dd
commit 8dd533f89a
21 changed files with 424 additions and 34 deletions

View File

@@ -15,6 +15,10 @@
"views/repair_fsm_zone_view_form.xml",
"views/repair_fsm_zone_view_kanban.xml",
"views/repair_fsm_zone_action_main.xml",
"views/repair_work_type_view_list.xml",
"views/repair_work_type_view_form.xml",
"views/repair_work_type_view_kanban.xml",
"views/repair_work_type_action_main.xml",
"views/menu.xml",
],
"assets": {

View File

@@ -1 +1,2 @@
from . import fsm_zone
from . import work_type

View File

@@ -0,0 +1,12 @@
from odoo import fields, models
class RepairWorkType(models.Model):
_name = "repair.work.type"
_description = "Repair Work Type"
_order = "name"
_inherit = ["mail.thread", "mail.activity.mixin"]
name = fields.Char(required=True, tracking=True)
duration_min = fields.Integer(default=120, required=True, tracking=True)
active = fields.Boolean(default=True, tracking=True)

View File

@@ -1,3 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_repair_fsm_zone_user,repair.fsm.zone user,model_repair_fsm_zone,dsrpt_repair_config.group_dsrpt_repair_config_user,1,1,1,0
access_repair_fsm_zone_manager,repair.fsm.zone manager,model_repair_fsm_zone,dsrpt_repair_config.group_dsrpt_repair_config_manager,1,1,1,1
access_repair_work_type_user,repair.work.type user,model_repair_work_type,dsrpt_repair_config.group_dsrpt_repair_config_user,1,1,1,0
access_repair_work_type_manager,repair.work.type manager,model_repair_work_type,dsrpt_repair_config.group_dsrpt_repair_config_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_repair_fsm_zone_user repair.fsm.zone user model_repair_fsm_zone dsrpt_repair_config.group_dsrpt_repair_config_user 1 1 1 0
3 access_repair_fsm_zone_manager repair.fsm.zone manager model_repair_fsm_zone dsrpt_repair_config.group_dsrpt_repair_config_manager 1 1 1 1
4 access_repair_work_type_user repair.work.type user model_repair_work_type dsrpt_repair_config.group_dsrpt_repair_config_user 1 1 1 0
5 access_repair_work_type_manager repair.work.type manager model_repair_work_type dsrpt_repair_config.group_dsrpt_repair_config_manager 1 1 1 1

View File

@@ -13,4 +13,11 @@
<field name="action" eval="'ir.actions.act_window,%d' % ref('action_repair_fsm_zone')"/>
<field name="sequence">10</field>
</record>
<record id="menu_repair_work_type" model="ir.ui.menu">
<field name="name">Work Types</field>
<field name="parent_id" ref="dsrpt_repair_config.menu_repair_root"/>
<field name="action" eval="'ir.actions.act_window,%d' % ref('action_repair_work_type')"/>
<field name="sequence">20</field>
</record>
</odoo>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_repair_work_type" model="ir.actions.act_window">
<field name="name">Work Types</field>
<field name="res_model">repair.work.type</field>
<field name="view_mode">list,kanban,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_repair_work_type_form" model="ir.ui.view">
<field name="name">repair.work.type.form</field>
<field name="model">repair.work.type</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="duration_min"/>
<field name="active"/>
</group>
</sheet>
<chatter/>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_repair_work_type_kanban" model="ir.ui.view">
<field name="name">repair.work.type.kanban</field>
<field name="model">repair.work.type</field>
<field name="arch" type="xml">
<kanban>
<field name="name"/>
<field name="duration_min"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="o_kanban_record_title">
<strong><field name="name"/></strong>
</div>
<div>
<field name="duration_min"/> min
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_repair_work_type_list" model="ir.ui.view">
<field name="name">repair.work.type.list</field>
<field name="model">repair.work.type</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="duration_min" optional="show"/>
<field name="active" optional="hide"/>
</list>
</field>
</record>
</odoo>

View File

@@ -11,6 +11,8 @@ class RepairTechnician(models.Model):
name = fields.Char(required=True, tracking=True)
user_id = fields.Many2one("res.users", string="User", tracking=True)
zone_ids = fields.Many2many("repair.fsm.zone", string="FSM Zones", tracking=True)
work_type_ids = fields.Many2many("repair.work.type", string="Work Types", tracking=True)
available_until = fields.Date(string="Available Until", tracking=True)
state = fields.Selection(
selection=[
("draft", "Draft"),

View File

@@ -5,3 +5,5 @@ access_repair_technician_schedule_user,repair.technician.schedule user,model_rep
access_repair_technician_schedule_manager,repair.technician.schedule manager,model_repair_technician_schedule,dsrpt_repair_technicians.group_dsrpt_repair_technicians_manager,1,1,1,1
access_repair_technician_exception_user,repair.technician.exception user,model_repair_technician_exception,dsrpt_repair_technicians.group_dsrpt_repair_technicians_user,1,1,1,0
access_repair_technician_exception_manager,repair.technician.exception manager,model_repair_technician_exception,dsrpt_repair_technicians.group_dsrpt_repair_technicians_manager,1,1,1,1
access_repair_work_type_for_technician_user,repair.work.type for technician user,model_repair_work_type,dsrpt_repair_technicians.group_dsrpt_repair_technicians_user,1,0,0,0
access_repair_work_type_for_technician_manager,repair.work.type for technician manager,model_repair_work_type,dsrpt_repair_technicians.group_dsrpt_repair_technicians_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_repair_technician_schedule_manager repair.technician.schedule manager model_repair_technician_schedule dsrpt_repair_technicians.group_dsrpt_repair_technicians_manager 1 1 1 1
6 access_repair_technician_exception_user repair.technician.exception user model_repair_technician_exception dsrpt_repair_technicians.group_dsrpt_repair_technicians_user 1 1 1 0
7 access_repair_technician_exception_manager repair.technician.exception manager model_repair_technician_exception dsrpt_repair_technicians.group_dsrpt_repair_technicians_manager 1 1 1 1
8 access_repair_work_type_for_technician_user repair.work.type for technician user model_repair_work_type dsrpt_repair_technicians.group_dsrpt_repair_technicians_user 1 0 0 0
9 access_repair_work_type_for_technician_manager repair.work.type for technician manager model_repair_work_type dsrpt_repair_technicians.group_dsrpt_repair_technicians_manager 1 0 0 0

View File

@@ -16,6 +16,8 @@
<field name="name"/>
<field name="user_id"/>
<field name="zone_ids" widget="many2many_tags"/>
<field name="work_type_ids" widget="many2many_tags"/>
<field name="available_until"/>
<field name="active"/>
</group>
<notebook>

View File

@@ -8,6 +8,7 @@
<field name="name"/>
<field name="user_id"/>
<field name="zone_ids"/>
<field name="work_type_ids"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
@@ -21,6 +22,9 @@
<div>
<field name="zone_ids" widget="many2many_tags"/>
</div>
<div>
<field name="work_type_ids" widget="many2many_tags"/>
</div>
</div>
</t>
</templates>

View File

@@ -8,6 +8,8 @@
<field name="name"/>
<field name="user_id" optional="show"/>
<field name="zone_ids" widget="many2many_tags" optional="show"/>
<field name="work_type_ids" widget="many2many_tags" optional="show"/>
<field name="available_until" optional="show"/>
<field name="state" widget="badge" optional="show"/>
<field name="active" optional="hide"/>
</list>

View File

@@ -16,6 +16,7 @@
"data": [
"security/groups.xml",
"security/ir.model.access.csv",
"data/slot_horizon_settings.xml",
"data/sequence.xml",
"views/repair_work_order_view_list.xml",
"views/repair_work_order_view_form.xml",

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo noupdate="1">
<record id="param_slot_horizon_days" model="ir.config_parameter">
<field name="key">dsrpt_repair_work_orders.slot_horizon_days</field>
<field name="value">15</field>
</record>
</odoo>

View File

@@ -1,4 +1,8 @@
from odoo import api, fields, models
from datetime import datetime, time, timedelta
import pytz
from odoo import Command, api, fields, models
from odoo.exceptions import ValidationError
@@ -24,11 +28,20 @@ class RepairWorkOrder(models.Model):
readonly=True,
tracking=True,
)
work_type_id = fields.Many2one("repair.work.type", string="Work Type", tracking=True)
description = fields.Text(tracking=True)
requested_datetime = fields.Datetime(default=fields.Datetime.now, tracking=True)
scheduled_datetime = fields.Datetime(tracking=True)
scheduled_datetime = fields.Datetime(string="Scheduled Start", tracking=True)
scheduled_end = fields.Datetime(string="Scheduled End", tracking=True)
technician_id = fields.Many2one("repair.technician", tracking=True)
assigned_user_id = fields.Many2one("res.users", related="technician_id.user_id", store=True)
slot_day = fields.Date(default=fields.Date.context_today, tracking=True)
available_slot_ids = fields.One2many(
"repair.work.order.slot",
"work_order_id",
string="Available Slots",
copy=False,
)
state = fields.Selection(
selection=[
("draft", "Draft"),
@@ -52,7 +65,24 @@ 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"
return super().create(vals_list)
records = super().create(vals_list)
records._refresh_available_slots_saved()
return records
def write(self, vals):
result = super().write(vals)
trigger_fields = {
"work_type_id",
"slot_day",
"contact_address_id",
"zone_id",
"scheduled_datetime",
"scheduled_end",
"technician_id",
}
if trigger_fields.intersection(vals):
self._refresh_available_slots_saved()
return result
def _group_expand_states(self, states, domain, order):
return [key for key, _label in self._fields["state"].selection]
@@ -66,8 +96,7 @@ class RepairWorkOrder(models.Model):
def _find_zone_for_point(self, latitude, longitude):
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)
zones = self.env["repair.fsm.zone"].search([("state", "=", "active")])
for zone in zones:
if zone.contains_point(latitude, longitude):
return zone
@@ -88,6 +117,189 @@ class RepairWorkOrder(models.Model):
if rec.contact_address_id and rec.contact_address_id.contact_id != rec.contact_id:
rec.contact_address_id = False
@api.onchange("work_type_id", "slot_day", "contact_address_id", "zone_id")
def _onchange_recompute_available_slots(self):
for rec in self:
slot_values = rec._build_available_slot_values()
rec.available_slot_ids = [Command.clear(), *[Command.create(vals) for vals in slot_values]]
@staticmethod
def _merge_intervals(intervals):
if not intervals:
return []
sorted_intervals = sorted(intervals, key=lambda item: item[0])
merged = [sorted_intervals[0]]
for start_dt, end_dt in sorted_intervals[1:]:
last_start, last_end = merged[-1]
if start_dt <= last_end:
merged[-1] = (last_start, max(last_end, end_dt))
else:
merged.append((start_dt, end_dt))
return merged
@staticmethod
def _subtract_intervals(source, cutouts):
if not source:
return []
if not cutouts:
return source
result = []
for src_start, src_end in source:
segments = [(src_start, src_end)]
for cut_start, cut_end in cutouts:
next_segments = []
for seg_start, seg_end in segments:
if cut_end <= seg_start or cut_start >= seg_end:
next_segments.append((seg_start, seg_end))
continue
if cut_start > seg_start:
next_segments.append((seg_start, min(cut_start, seg_end)))
if cut_end < seg_end:
next_segments.append((max(cut_end, seg_start), seg_end))
segments = next_segments
if not segments:
break
result.extend(segments)
return RepairWorkOrder._merge_intervals(result)
def _get_slot_horizon_days(self):
raw_value = self.env["ir.config_parameter"].sudo().get_param("dsrpt_repair_work_orders.slot_horizon_days", "15")
try:
parsed = int(raw_value)
except (TypeError, ValueError):
parsed = 15
return max(parsed, 1)
def _is_day_in_horizon(self, day_value):
if not day_value:
return False
today = fields.Date.context_today(self)
horizon_end = today + timedelta(days=self._get_slot_horizon_days())
return today <= day_value <= horizon_end
def _day_bounds_utc(self, day_value):
user_tz_name = self.env.user.tz or "UTC"
user_tz = pytz.timezone(user_tz_name)
local_start = user_tz.localize(datetime.combine(day_value, time.min))
local_end = local_start + timedelta(days=1)
utc_start = local_start.astimezone(pytz.UTC).replace(tzinfo=None)
utc_end = local_end.astimezone(pytz.UTC).replace(tzinfo=None)
return utc_start, utc_end, user_tz
@staticmethod
def _hour_to_utc(day_value, float_hour, user_tz):
minutes = int(round(float(float_hour or 0.0) * 60))
local_dt = user_tz.localize(datetime.combine(day_value, time.min) + timedelta(minutes=minutes))
return local_dt.astimezone(pytz.UTC).replace(tzinfo=None)
def _technician_day_availability(self, technician, day_value, day_start_utc, day_end_utc, user_tz):
weekday = str(day_value.weekday())
base_intervals = []
for schedule in technician.schedule_ids.filtered(lambda rec: rec.day_of_week == weekday):
start_dt = self._hour_to_utc(day_value, schedule.hour_from, user_tz)
end_dt = self._hour_to_utc(day_value, schedule.hour_to, user_tz)
if end_dt > start_dt:
base_intervals.append((start_dt, end_dt))
available = self._merge_intervals(base_intervals)
if not available:
return []
exc_domain = [
("technician_id", "=", technician.id),
("start_datetime", "<", day_end_utc),
("end_datetime", ">", day_start_utc),
]
exceptions = self.env["repair.technician.exception"].search(exc_domain)
negative = []
positive = []
for exc in exceptions:
interval = (max(exc.start_datetime, day_start_utc), min(exc.end_datetime, day_end_utc))
if interval[1] <= interval[0]:
continue
if exc.exception_type == "negative":
negative.append(interval)
else:
positive.append(interval)
available = self._subtract_intervals(available, self._merge_intervals(negative))
available = self._merge_intervals([*available, *positive])
busy_domain = [
("technician_id", "=", technician.id),
("scheduled_datetime", "!=", False),
("scheduled_datetime", "<", day_end_utc),
("state", "not in", ["cancelled"]),
]
if self.id:
busy_domain.append(("id", "!=", self.id))
busy_orders = self.env["repair.work.order"].search(busy_domain)
busy = []
for order in busy_orders:
start_dt = order.scheduled_datetime
duration_min = order.work_type_id.duration_min or self.work_type_id.duration_min or 120
end_dt = order.scheduled_end or (start_dt + timedelta(minutes=duration_min))
if end_dt <= day_start_utc or start_dt >= day_end_utc:
continue
busy.append((max(start_dt, day_start_utc), min(end_dt, day_end_utc)))
return self._subtract_intervals(available, self._merge_intervals(busy))
def _candidate_technicians(self, day_value):
domain = [("state", "=", "active")]
if self.zone_id:
domain.append(("zone_ids", "in", self.zone_id.id))
if self.work_type_id:
domain.append(("work_type_ids", "in", self.work_type_id.id))
if day_value:
domain.extend(["|", ("available_until", "=", False), ("available_until", ">=", day_value)])
return self.env["repair.technician"].search(domain)
def _build_available_slot_values(self):
self.ensure_one()
if not self.work_type_id or not self.zone_id or not self.slot_day:
return []
if not self._is_day_in_horizon(self.slot_day):
return []
duration = timedelta(minutes=max(self.work_type_id.duration_min or 0, 15))
step = timedelta(minutes=30)
day_start_utc, day_end_utc, user_tz = self._day_bounds_utc(self.slot_day)
technicians = self._candidate_technicians(self.slot_day)
candidates = []
for technician in technicians:
free_intervals = self._technician_day_availability(technician, self.slot_day, day_start_utc, day_end_utc, user_tz)
for interval_start, interval_end in free_intervals:
cursor = interval_start
while cursor + duration <= interval_end:
candidates.append(
{
"technician_id": technician.id,
"start_datetime": cursor,
"end_datetime": cursor + duration,
"duration_min": int(duration.total_seconds() // 60),
}
)
cursor += step
candidates.sort(key=lambda item: (item["start_datetime"], item["technician_id"]))
return candidates[:3]
def _refresh_available_slots_saved(self):
for rec in self:
if not rec.id:
continue
values = rec._build_available_slot_values()
rec.available_slot_ids.unlink()
if values:
self.env["repair.work.order.slot"].create(
[
{
"work_order_id": rec.id,
"sequence": index,
**slot_vals,
}
for index, slot_vals in enumerate(values, start=1)
]
)
def action_confirm(self):
self.write({"state": "confirmed"})
@@ -110,6 +322,42 @@ class RepairWorkOrder(models.Model):
self.write({"state": "draft"})
class RepairWorkOrderSlot(models.Model):
_name = "repair.work.order.slot"
_description = "Repair Work Order Available Slot"
_order = "sequence, start_datetime, id"
work_order_id = fields.Many2one("repair.work.order", required=True, ondelete="cascade")
sequence = fields.Integer(default=10)
technician_id = fields.Many2one("repair.technician", required=True)
start_datetime = fields.Datetime(required=True)
end_datetime = fields.Datetime(required=True)
duration_min = fields.Integer(required=True)
@api.constrains("start_datetime", "end_datetime")
def _check_datetime_order(self):
for rec in self:
if rec.end_datetime <= rec.start_datetime:
raise ValidationError("Slot end must be after slot start.")
def action_book(self):
self.ensure_one()
order = self.work_order_id
next_state = order.state
if order.state in ("draft", "confirmed"):
next_state = "assigned"
order.write(
{
"technician_id": self.technician_id.id,
"scheduled_datetime": self.start_datetime,
"scheduled_end": self.end_datetime,
"state": next_state,
}
)
order._refresh_available_slots_saved()
return {"type": "ir.actions.client", "tag": "reload"}
class RepairWorkOrderTime(models.Model):
_name = "repair.work.order.time"
_description = "Repair Work Order Time"

View File

@@ -5,3 +5,7 @@ access_repair_work_order_time_user,repair.work.order.time user,model_repair_work
access_repair_work_order_time_manager,repair.work.order.time manager,model_repair_work_order_time,dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager,1,1,1,1
access_repair_work_order_material_user,repair.work.order.material user,model_repair_work_order_material,dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_user,1,1,1,0
access_repair_work_order_material_manager,repair.work.order.material manager,model_repair_work_order_material,dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager,1,1,1,1
access_repair_work_order_slot_user,repair.work.order.slot user,model_repair_work_order_slot,dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_user,1,1,1,1
access_repair_work_order_slot_manager,repair.work.order.slot manager,model_repair_work_order_slot,dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager,1,1,1,1
access_repair_work_type_for_work_orders_user,repair.work.type for work orders user,model_repair_work_type,dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_user,1,0,0,0
access_repair_work_type_for_work_orders_manager,repair.work.type for work orders manager,model_repair_work_type,dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_repair_work_order_time_manager repair.work.order.time manager model_repair_work_order_time dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager 1 1 1 1
6 access_repair_work_order_material_user repair.work.order.material user model_repair_work_order_material dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_user 1 1 1 0
7 access_repair_work_order_material_manager repair.work.order.material manager model_repair_work_order_material dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager 1 1 1 1
8 access_repair_work_order_slot_user repair.work.order.slot user model_repair_work_order_slot dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_user 1 1 1 1
9 access_repair_work_order_slot_manager repair.work.order.slot manager model_repair_work_order_slot dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager 1 1 1 1
10 access_repair_work_type_for_work_orders_user repair.work.type for work orders user model_repair_work_type dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_user 1 0 0 0
11 access_repair_work_type_for_work_orders_manager repair.work.type for work orders manager model_repair_work_type dsrpt_repair_work_orders.group_dsrpt_repair_work_orders_manager 1 0 0 0

View File

@@ -15,24 +15,41 @@
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,assigned,in_progress,done,cancelled" options="{'clickable': '1'}"/>
</header>
<sheet>
<group>
<group col="3">
<group>
<field name="name" readonly="1"/>
<field name="contact_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"/>
<field name="work_type_id"/>
</group>
<group>
<field name="requested_datetime"/>
<field name="scheduled_datetime"/>
<field name="scheduled_end"/>
<field name="technician_id"/>
</group>
<group>
<field name="zone_id" readonly="1"/>
<field name="slot_day"/>
<field name="assigned_user_id" readonly="1"/>
<field name="description"/>
</group>
</group>
<notebook>
<page string="Time Logs">
<group string="Available Slots">
<field name="available_slot_ids" nolabel="1" context="{'default_work_order_id': id}">
<list create="false" edit="false" delete="false">
<field name="sequence" optional="hide"/>
<field name="technician_id"/>
<field name="start_datetime"/>
<field name="end_datetime"/>
<field name="duration_min"/>
<button name="action_book" type="object" string="Book" class="btn-primary"/>
</list>
</field>
</group>
<group string="Time Logs">
<field name="time_line_ids" context="{'default_work_order_id': id, 'default_technician_id': technician_id}">
<list editable="bottom">
<field name="description"/>
@@ -40,8 +57,9 @@
<field name="hours"/>
</list>
</field>
</page>
<page string="Material Logs">
</group>
<group string="Material Logs">
<field name="material_line_ids" context="{'default_work_order_id': id}">
<list editable="bottom">
<field name="material_id"/>
@@ -50,14 +68,12 @@
<field name="subtotal" readonly="1"/>
</list>
</field>
</page>
<page string="Totals">
<group>
</group>
<group string="Summary">
<field name="total_time_hours" readonly="1"/>
<field name="total_material_cost" readonly="1"/>
</group>
</page>
</notebook>
</sheet>
<chatter/>
</form>

View File

@@ -8,7 +8,9 @@
<field name="name"/>
<field name="contact_id"/>
<field name="contact_address_id"/>
<field name="work_type_id"/>
<field name="scheduled_datetime"/>
<field name="scheduled_end"/>
<field name="technician_id"/>
<field name="state"/>
<templates>
@@ -23,9 +25,15 @@
<div>
<field name="contact_address_id"/>
</div>
<div>
<field name="work_type_id"/>
</div>
<div>
<field name="scheduled_datetime"/>
</div>
<div>
<field name="scheduled_end"/>
</div>
<div>
<field name="technician_id"/>
</div>

View File

@@ -8,8 +8,10 @@
<field name="name"/>
<field name="contact_id" optional="show"/>
<field name="contact_address_id" optional="show"/>
<field name="work_type_id" optional="show"/>
<field name="zone_id" optional="show"/>
<field name="scheduled_datetime" optional="show"/>
<field name="scheduled_end" optional="show"/>
<field name="technician_id" optional="show"/>
<field name="state" widget="badge" optional="show"/>
<field name="total_time_hours" optional="show"/>