EOD and Carryover tickets added
This commit is contained in:
+73
-24
@@ -9,7 +9,7 @@ pattern rather than blocking the UI.
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
from typing import List, TypedDict
|
||||
from typing import List, Tuple, TypedDict
|
||||
|
||||
from PyQt6.QtCore import QThread, pyqtSignal
|
||||
from sqlalchemy import select
|
||||
@@ -17,8 +17,10 @@ from sqlalchemy import select
|
||||
from app import config
|
||||
from app.database import get_session
|
||||
from app.models import Order
|
||||
from app.schedule import get_cutoff_time, is_past_cutoff
|
||||
from app.services.base import OrderService, NormalizedOrder
|
||||
from app.status_rules import parse_status_list, status_in
|
||||
from app.tracking import get_fulfilled_statuses
|
||||
|
||||
|
||||
class StatusChange(TypedDict):
|
||||
@@ -69,6 +71,11 @@ def save_orders(orders: List[NormalizedOrder]) -> SaveResult:
|
||||
existing JIRA row for that ticket. If no such row exists locally yet,
|
||||
the ticket number is reported back as unmatched instead of silently
|
||||
dropped.
|
||||
|
||||
Whenever a JIRA ticket's status transitions INTO a fulfilled status
|
||||
(Waiting For Return / Device Return Not Needed), fulfilled_at is
|
||||
stamped - that's what lets the dashboard show "fulfilled today" and
|
||||
what moves it into the Done pile.
|
||||
"""
|
||||
session = get_session()
|
||||
new_count = 0
|
||||
@@ -76,6 +83,7 @@ def save_orders(orders: List[NormalizedOrder]) -> SaveResult:
|
||||
enriched_count = 0
|
||||
status_changes: List[StatusChange] = []
|
||||
unmatched_tracking_tickets: List[str] = []
|
||||
fulfilled_statuses = get_fulfilled_statuses()
|
||||
|
||||
try:
|
||||
for order in orders:
|
||||
@@ -103,6 +111,8 @@ def save_orders(orders: List[NormalizedOrder]) -> SaveResult:
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
new_status = order["status"]
|
||||
|
||||
if existing is None:
|
||||
session.add(
|
||||
Order(
|
||||
@@ -112,15 +122,17 @@ def save_orders(orders: List[NormalizedOrder]) -> SaveResult:
|
||||
company=order.get("company", "Unknown"),
|
||||
skus=order.get("skus", []),
|
||||
summary=order["summary"],
|
||||
status=order["status"],
|
||||
status=new_status,
|
||||
source_created_at=order["source_created_at"],
|
||||
raw_data=order["raw_data"],
|
||||
fulfilled_at=(
|
||||
dt.datetime.utcnow() if status_in(new_status, fulfilled_statuses) else None
|
||||
),
|
||||
)
|
||||
)
|
||||
new_count += 1
|
||||
else:
|
||||
old_status = existing.status
|
||||
new_status = order["status"]
|
||||
if old_status != new_status:
|
||||
status_changes.append(
|
||||
StatusChange(
|
||||
@@ -129,6 +141,10 @@ def save_orders(orders: List[NormalizedOrder]) -> SaveResult:
|
||||
new_status=new_status,
|
||||
)
|
||||
)
|
||||
if status_in(new_status, fulfilled_statuses) and not status_in(
|
||||
old_status, fulfilled_statuses
|
||||
):
|
||||
existing.fulfilled_at = dt.datetime.utcnow()
|
||||
|
||||
existing.ticket_number = order.get("ticket_number")
|
||||
existing.company = order.get("company", "Unknown")
|
||||
@@ -162,46 +178,59 @@ def load_all_orders() -> List[Order]:
|
||||
session.close()
|
||||
|
||||
|
||||
def split_active_and_done(orders: List[Order]) -> Tuple[List[Order], List[Order]]:
|
||||
"""
|
||||
Active = still being worked (includes cancelled - those still need
|
||||
eyes on them). Done = fulfilled (Waiting For Return / Device Return
|
||||
Not Needed) - archived out of the working view on purpose, per how
|
||||
the team wants to keep the active list focused on what's at hand.
|
||||
"""
|
||||
fulfilled_statuses = get_fulfilled_statuses()
|
||||
active, done = [], []
|
||||
for order in orders:
|
||||
(done if status_in(order.status, fulfilled_statuses) else active).append(order)
|
||||
return active, done
|
||||
|
||||
|
||||
def load_active_and_done_orders() -> Tuple[List[Order], List[Order]]:
|
||||
return split_active_and_done(load_all_orders())
|
||||
|
||||
|
||||
def get_dashboard_stats() -> dict:
|
||||
"""
|
||||
Counts for the dashboard: totals by company, how many are cancelled,
|
||||
fulfilled, still-open-from-a-previous-day (carryover), and how many
|
||||
have had tracking numbers pulled yet.
|
||||
Counts for the dashboard. "Active Orders" and the company breakdown
|
||||
reflect only the active workload (fulfilled tickets have moved to
|
||||
the Done pile and don't clutter this). Fulfilled-today and
|
||||
past-cutoff-today look across ALL of today's tickets regardless of
|
||||
which pile they're in now, since both are about what happened today.
|
||||
|
||||
Carryover counts any still-open ticket that's already known to spill
|
||||
into tomorrow - either it's genuinely left over from a prior day, or
|
||||
it arrived today but after the cutoff (same effect, just known a day
|
||||
earlier).
|
||||
"""
|
||||
orders = load_all_orders()
|
||||
active_orders, _done_orders = split_active_and_done(orders)
|
||||
|
||||
cancelled_statuses = parse_status_list(
|
||||
config.get("CANCELLED_STATUSES", config.DEFAULT_CANCELLED_STATUSES)
|
||||
)
|
||||
fulfilled_statuses = parse_status_list(
|
||||
",".join(
|
||||
[
|
||||
config.get("FULFILLED_STATUS_WITH_RETURN", config.DEFAULT_FULFILLED_WITH_RETURN),
|
||||
config.get(
|
||||
"FULFILLED_STATUS_WITHOUT_RETURN", config.DEFAULT_FULFILLED_WITHOUT_RETURN
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
terminal_statuses = parse_status_list(
|
||||
config.get("JIRA_TERMINAL_STATUSES", config.DEFAULT_TERMINAL_STATUSES)
|
||||
)
|
||||
|
||||
cutoff = get_cutoff_time()
|
||||
today = dt.date.today()
|
||||
|
||||
by_company: dict[str, int] = {}
|
||||
cancelled_count = 0
|
||||
fulfilled_count = 0
|
||||
carryover_count = 0
|
||||
tracking_received_count = 0
|
||||
|
||||
for order in orders:
|
||||
for order in active_orders:
|
||||
by_company[order.company] = by_company.get(order.company, 0) + 1
|
||||
|
||||
if status_in(order.status, cancelled_statuses):
|
||||
cancelled_count += 1
|
||||
if status_in(order.status, fulfilled_statuses):
|
||||
fulfilled_count += 1
|
||||
if order.tracking_numbers:
|
||||
tracking_received_count += 1
|
||||
|
||||
@@ -209,14 +238,34 @@ def get_dashboard_stats() -> dict:
|
||||
created_before_today = bool(
|
||||
order.source_created_at and order.source_created_at.date() < today
|
||||
)
|
||||
if still_open and created_before_today:
|
||||
# A ticket that arrived today but after the cutoff won't get worked
|
||||
# today either - it's known carryover before the date even rolls
|
||||
# over, not just once tomorrow arrives.
|
||||
arrived_past_cutoff_today = bool(
|
||||
order.source_created_at
|
||||
and order.source_created_at.date() == today
|
||||
and is_past_cutoff(order.source_created_at, cutoff)
|
||||
)
|
||||
if still_open and (created_before_today or arrived_past_cutoff_today):
|
||||
carryover_count += 1
|
||||
|
||||
fulfilled_today_count = sum(
|
||||
1 for o in orders if o.fulfilled_at and o.fulfilled_at.date() == today
|
||||
)
|
||||
past_cutoff_today_count = sum(
|
||||
1
|
||||
for o in orders
|
||||
if o.source_created_at
|
||||
and o.source_created_at.date() == today
|
||||
and is_past_cutoff(o.source_created_at, cutoff)
|
||||
)
|
||||
|
||||
return {
|
||||
"total": len(orders),
|
||||
"total": len(active_orders),
|
||||
"by_company": by_company,
|
||||
"cancelled_count": cancelled_count,
|
||||
"fulfilled_count": fulfilled_count,
|
||||
"carryover_count": carryover_count,
|
||||
"tracking_received_count": tracking_received_count,
|
||||
"fulfilled_today_count": fulfilled_today_count,
|
||||
"past_cutoff_today_count": past_cutoff_today_count,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user