feat: add Pending status for delivery/technician tasks
- New 'pending' status allows tasks to be created without a schedule, acting as a queue for unscheduled work that gets assigned later - Pending group appears in the Delivery Map sidebar with amber color - Other modules can create tasks in pending state for scheduling - scheduled_date no longer required (null for pending tasks) - New Pending Tasks menu item under Field Service - Pending filter added to search view Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -169,7 +169,6 @@ class FusionTechnicianTask(models.Model):
|
||||
# ------------------------------------------------------------------
|
||||
scheduled_date = fields.Date(
|
||||
string='Scheduled Date',
|
||||
required=True,
|
||||
tracking=True,
|
||||
default=fields.Date.context_today,
|
||||
index=True,
|
||||
@@ -265,6 +264,7 @@ class FusionTechnicianTask(models.Model):
|
||||
# STATUS
|
||||
# ------------------------------------------------------------------
|
||||
status = fields.Selection([
|
||||
('pending', 'Pending'),
|
||||
('scheduled', 'Scheduled'),
|
||||
('en_route', 'En Route'),
|
||||
('in_progress', 'In Progress'),
|
||||
@@ -851,6 +851,7 @@ class FusionTechnicianTask(models.Model):
|
||||
@api.depends('status')
|
||||
def _compute_color(self):
|
||||
color_map = {
|
||||
'pending': 5, # purple
|
||||
'scheduled': 0, # grey
|
||||
'en_route': 4, # blue
|
||||
'in_progress': 2, # orange
|
||||
@@ -2126,7 +2127,7 @@ class FusionTechnicianTask(models.Model):
|
||||
'time_start', 'time_start_display', 'time_end_display',
|
||||
'status', 'scheduled_date', 'travel_time_minutes',
|
||||
'x_fc_sync_client_name', 'x_fc_is_shadow', 'x_fc_sync_source'],
|
||||
order='scheduled_date asc, time_start asc',
|
||||
order='scheduled_date asc NULLS LAST, time_start asc',
|
||||
limit=500,
|
||||
)
|
||||
locations = self.env['fusion.technician.location'].get_latest_locations()
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
|
||||
// ── Constants ───────────────────────────────────────────────────────
|
||||
const STATUS_COLORS = {
|
||||
pending: "#f59e0b",
|
||||
scheduled: "#3b82f6",
|
||||
en_route: "#f59e0b",
|
||||
in_progress: "#8b5cf6",
|
||||
@@ -34,6 +35,7 @@ const STATUS_COLORS = {
|
||||
rescheduled: "#f97316",
|
||||
};
|
||||
const STATUS_LABELS = {
|
||||
pending: "Pending",
|
||||
scheduled: "Scheduled",
|
||||
en_route: "En Route",
|
||||
in_progress: "In Progress",
|
||||
@@ -42,6 +44,7 @@ const STATUS_LABELS = {
|
||||
rescheduled: "Rescheduled",
|
||||
};
|
||||
const STATUS_ICONS = {
|
||||
pending: "fa-hourglass-half",
|
||||
scheduled: "fa-clock-o",
|
||||
en_route: "fa-truck",
|
||||
in_progress: "fa-wrench",
|
||||
@@ -51,12 +54,14 @@ const STATUS_ICONS = {
|
||||
};
|
||||
|
||||
// Date group keys
|
||||
const GROUP_PENDING = "pending";
|
||||
const GROUP_YESTERDAY = "yesterday";
|
||||
const GROUP_TODAY = "today";
|
||||
const GROUP_TOMORROW = "tomorrow";
|
||||
const GROUP_THIS_WEEK = "this_week";
|
||||
const GROUP_LATER = "later";
|
||||
const GROUP_LABELS = {
|
||||
[GROUP_PENDING]: "Pending",
|
||||
[GROUP_YESTERDAY]: "Yesterday",
|
||||
[GROUP_TODAY]: "Today",
|
||||
[GROUP_TOMORROW]: "Tomorrow",
|
||||
@@ -66,6 +71,7 @@ const GROUP_LABELS = {
|
||||
|
||||
// Pin colours by day group
|
||||
const DAY_COLORS = {
|
||||
[GROUP_PENDING]: "#f59e0b", // Amber
|
||||
[GROUP_YESTERDAY]: "#9ca3af", // Gray
|
||||
[GROUP_TODAY]: "#ef4444", // Red
|
||||
[GROUP_TOMORROW]: "#3b82f6", // Blue
|
||||
@@ -73,6 +79,7 @@ const DAY_COLORS = {
|
||||
[GROUP_LATER]: "#a855f7", // Purple
|
||||
};
|
||||
const DAY_ICONS = {
|
||||
[GROUP_PENDING]: "fa-hourglass-half",
|
||||
[GROUP_YESTERDAY]: "fa-history",
|
||||
[GROUP_TODAY]: "fa-exclamation-circle",
|
||||
[GROUP_TOMORROW]: "fa-arrow-right",
|
||||
@@ -137,9 +144,14 @@ function floatToTime12(flt) {
|
||||
return `${h12}:${String(m).padStart(2, "0")} ${ampm}`;
|
||||
}
|
||||
|
||||
/** Classify a "YYYY-MM-DD" string into one of our group keys */
|
||||
/** Classify a task into one of our group keys based on status and date */
|
||||
function classifyTask(task) {
|
||||
if (task.status === "pending") return GROUP_PENDING;
|
||||
return classifyDate(task.scheduled_date);
|
||||
}
|
||||
|
||||
function classifyDate(dateStr) {
|
||||
if (!dateStr) return GROUP_LATER;
|
||||
if (!dateStr) return GROUP_PENDING;
|
||||
const now = new Date();
|
||||
const todayStr = localDateStr(now);
|
||||
|
||||
@@ -151,7 +163,6 @@ function classifyDate(dateStr) {
|
||||
tmr.setDate(tmr.getDate() + 1);
|
||||
const tomorrowStr = localDateStr(tmr);
|
||||
|
||||
// End of this week (Sunday)
|
||||
const endOfWeek = new Date(now);
|
||||
endOfWeek.setDate(endOfWeek.getDate() + (7 - endOfWeek.getDay()));
|
||||
const endOfWeekStr = localDateStr(endOfWeek);
|
||||
@@ -160,7 +171,7 @@ function classifyDate(dateStr) {
|
||||
if (dateStr === todayStr) return GROUP_TODAY;
|
||||
if (dateStr === tomorrowStr) return GROUP_TOMORROW;
|
||||
if (dateStr <= endOfWeekStr && dateStr > tomorrowStr) return GROUP_THIS_WEEK;
|
||||
if (dateStr < yesterdayStr) return GROUP_YESTERDAY; // older lumped with yesterday
|
||||
if (dateStr < yesterdayStr) return GROUP_YESTERDAY;
|
||||
return GROUP_LATER;
|
||||
}
|
||||
|
||||
@@ -180,7 +191,7 @@ function groupTasks(tasksData, localInstanceId) {
|
||||
});
|
||||
|
||||
const groups = {};
|
||||
const order = [GROUP_YESTERDAY, GROUP_TODAY, GROUP_TOMORROW, GROUP_THIS_WEEK, GROUP_LATER];
|
||||
const order = [GROUP_PENDING, GROUP_YESTERDAY, GROUP_TODAY, GROUP_TOMORROW, GROUP_THIS_WEEK, GROUP_LATER];
|
||||
for (const key of order) {
|
||||
groups[key] = {
|
||||
key,
|
||||
@@ -195,7 +206,7 @@ function groupTasks(tasksData, localInstanceId) {
|
||||
let globalIdx = 0;
|
||||
for (const task of sorted) {
|
||||
globalIdx++;
|
||||
const g = classifyDate(task.scheduled_date);
|
||||
const g = classifyTask(task);
|
||||
task._scheduleNum = globalIdx;
|
||||
task._group = g;
|
||||
task._dayColor = DAY_COLORS[g] || "#6b7280"; // Pin colour by day
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
</button>
|
||||
<span class="border-start mx-1" style="height:20px;"/>
|
||||
<span class="text-muted fw-bold" style="font-size:11px;">Pins:</span>
|
||||
<span style="font-size:11px;"><i class="fa fa-map-marker me-1" style="color:#f59e0b;"/>Pending</span>
|
||||
<span style="font-size:11px;"><i class="fa fa-map-marker me-1" style="color:#ef4444;"/>Today</span>
|
||||
<span style="font-size:11px;"><i class="fa fa-map-marker me-1" style="color:#3b82f6;"/>Tomorrow</span>
|
||||
<span style="font-size:11px;"><i class="fa fa-map-marker me-1" style="color:#10b981;"/>This Week</span>
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
domain="[('scheduled_date', '>=', (context_today() - datetime.timedelta(days=context_today().weekday())).strftime('%Y-%m-%d')),
|
||||
('scheduled_date', '<=', (context_today() + datetime.timedelta(days=6-context_today().weekday())).strftime('%Y-%m-%d'))]"/>
|
||||
<separator/>
|
||||
<filter string="Pending" name="filter_pending" domain="[('status', '=', 'pending')]"/>
|
||||
<filter string="Scheduled" name="filter_scheduled" domain="[('status', '=', 'scheduled')]"/>
|
||||
<filter string="En Route" name="filter_en_route" domain="[('status', '=', 'en_route')]"/>
|
||||
<filter string="In Progress" name="filter_in_progress" domain="[('status', '=', 'in_progress')]"/>
|
||||
@@ -105,7 +106,7 @@
|
||||
class="btn-secondary o_fc_calculate_travel" icon="fa-car"
|
||||
invisible="x_fc_is_shadow"/>
|
||||
<field name="status" widget="statusbar"
|
||||
statusbar_visible="scheduled,en_route,in_progress,completed"/>
|
||||
statusbar_visible="pending,scheduled,en_route,in_progress,completed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<!-- Shadow task banner -->
|
||||
@@ -447,6 +448,15 @@
|
||||
<field name="context">{'search_default_filter_my_tasks': 1, 'search_default_filter_active': 1}</field>
|
||||
</record>
|
||||
|
||||
<!-- Pending Tasks Action -->
|
||||
<record id="action_technician_tasks_pending" model="ir.actions.act_window">
|
||||
<field name="name">Pending Tasks</field>
|
||||
<field name="res_model">fusion.technician.task</field>
|
||||
<field name="view_mode">list,kanban,form</field>
|
||||
<field name="search_view_id" ref="view_technician_task_search"/>
|
||||
<field name="context">{'search_default_filter_pending': 1}</field>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- MENU ITEMS -->
|
||||
<!-- ================================================================== -->
|
||||
@@ -478,6 +488,12 @@
|
||||
action="action_technician_tasks"
|
||||
sequence="20"/>
|
||||
|
||||
<menuitem id="menu_technician_tasks_pending"
|
||||
name="Pending Tasks"
|
||||
parent="menu_technician_management"
|
||||
action="action_technician_tasks_pending"
|
||||
sequence="13"/>
|
||||
|
||||
<menuitem id="menu_technician_tasks_today"
|
||||
name="Today's Tasks"
|
||||
parent="menu_technician_management"
|
||||
|
||||
Reference in New Issue
Block a user