fix: address ask-user findings from review

- Move unit loading to use real /api/tickets/units endpoint
- Match unit code format to backend seed (0101E style)
- Send unit_id in create ticket payload
- Fix tech IDs to match seed order (9-15 for Prosper-Afful)
- Add customer_name and phone fields to TicketCreate schema
- Map form customer_name/phone into API payload
This commit is contained in:
root
2026-07-23 19:08:49 +00:00
parent 3dc383e62d
commit 7fab1ede51
4 changed files with 48 additions and 17 deletions
+16
View File
@@ -16,6 +16,7 @@ from app.core.database import get_db
from app.core.security import get_current_user from app.core.security import get_current_user
from app.models.category import Category from app.models.category import Category
from app.models.ticket import Ticket, TicketPhoto from app.models.ticket import Ticket, TicketPhoto
from app.models.unit import Unit
from app.models.user import User from app.models.user import User
from app.schemas.ticket import ( from app.schemas.ticket import (
CategoryOut, CategoryOut,
@@ -27,6 +28,7 @@ from app.schemas.ticket import (
TicketOut, TicketOut,
TicketPhotoOut, TicketPhotoOut,
TicketUpdate, TicketUpdate,
UnitOut,
) )
from app.services import ticket as ticket_service from app.services import ticket as ticket_service
from app.services.sla import get_sla_status from app.services.sla import get_sla_status
@@ -38,6 +40,20 @@ UPLOADS_DIR = settings.BASE_DIR / "uploads"
UPLOADS_DIR.mkdir(parents=True, exist_ok=True) UPLOADS_DIR.mkdir(parents=True, exist_ok=True)
# ── Units ──────────────────────────────────────────────────────────
@router.get("/units", response_model=list[UnitOut])
async def list_units(
db: Annotated[AsyncSession, Depends(get_db)],
property_filter: str | None = Query(None, alias="property"),
) -> list[Unit]:
"""List all units, optionally filtered by property (East/West)."""
query = select(Unit).order_by(Unit.apartment_code)
if property_filter:
query = query.where(Unit.property == property_filter)
result = await db.execute(query)
return list(result.scalars().all())
# ── Categories ────────────────────────────────────────────────────── # ── Categories ──────────────────────────────────────────────────────
async def _build_category_tree(db: AsyncSession, parent_id: int | None = None) -> list[CategoryTreeOut]: async def _build_category_tree(db: AsyncSession, parent_id: int | None = None) -> list[CategoryTreeOut]:
"""Build a nested category tree.""" """Build a nested category tree."""
+13
View File
@@ -8,6 +8,17 @@ from decimal import Decimal
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
# ── Unit ─────────────────────────────────────────────────────────────
class UnitOut(BaseModel):
id: int
property: str
apartment_code: str
building: str | None = None
floor: int | None = None
model_config = {"from_attributes": True}
# ── Category ───────────────────────────────────────────────────────── # ── Category ─────────────────────────────────────────────────────────
class CategoryOut(BaseModel): class CategoryOut(BaseModel):
id: int id: int
@@ -32,6 +43,8 @@ class TicketCreate(BaseModel):
reported_via: str | None = None # whatsapp, phone, walk-in, qr, agent reported_via: str | None = None # whatsapp, phone, walk-in, qr, agent
description: str | None = None description: str | None = None
assigned_to: int | None = None assigned_to: int | None = None
customer_name: str | None = None
phone: str | None = None
class TicketUpdate(BaseModel): class TicketUpdate(BaseModel):
+7 -6
View File
@@ -334,12 +334,13 @@
// Users list not exposed via API directly, so use a known list // Users list not exposed via API directly, so use a known list
// In a real system we'd have GET /api/users // In a real system we'd have GET /api/users
this.technicians = [ this.technicians = [
{ id: 8, full_name: 'Prosper' }, { id: 9, full_name: 'Prosper' },
{ id: 9, full_name: 'Sam' }, { id: 10, full_name: 'Sam' },
{ id: 10, full_name: 'Steven' }, { id: 11, full_name: 'Steven' },
{ id: 11, full_name: 'Junior (Samuel)' }, { id: 12, full_name: 'Junior (Samuel)' },
{ id: 12, full_name: 'Francis' }, { id: 13, full_name: 'Francis' },
{ id: 13, full_name: 'Desmond Afful' }, { id: 14, full_name: 'Desmond Afful' },
{ id: 15, full_name: 'Afful' },
]; ];
} catch (e) { console.error('Tech load error', e); } } catch (e) { console.error('Tech load error', e); }
}, },
+12 -11
View File
@@ -196,17 +196,12 @@
this.form.apartment_code = ''; this.form.apartment_code = '';
this.units = []; this.units = [];
if (!this.form.property) return; if (!this.form.property) return;
// Units aren't directly exposed via API, so we'll construct from known patterns try {
const prefix = this.form.property === 'East' ? 'E' : 'W'; const data = await app().apiGet(`/api/tickets/units?property=${encodeURIComponent(this.form.property)}`);
const units = []; this.units = data || [];
const buildings = ['A', 'B', 'C', 'D', 'E', 'F']; } catch (e) {
for (let floor = 1; floor <= 10; floor++) { console.error('Units load error', e);
for (const bld of buildings) {
const code = `${floor}0${bld}${prefix}`;
units.push({ id: code, apartment_code: code });
} }
}
this.units = units;
}, },
handlePhotos(e) { handlePhotos(e) {
@@ -240,13 +235,19 @@
return; return;
} }
// Resolve unit_id from selected apartment_code
const selectedUnit = this.units.find(u => u.apartment_code === this.form.apartment_code);
// Create the ticket // Create the ticket
const payload = { const payload = {
description: this.form.description, description: this.form.description,
priority: this.form.priority || null, priority: this.form.priority || null,
reporter: this.form.reporter || app().user.full_name, reporter: this.form.reporter || this.form.customer_name || app().user.full_name,
reported_via: this.form.reported_via || 'walk-in', reported_via: this.form.reported_via || 'walk-in',
category_id: this.form.category_id ? parseInt(this.form.category_id) : (this.form.category_main ? parseInt(this.form.category_main) : null), category_id: this.form.category_id ? parseInt(this.form.category_id) : (this.form.category_main ? parseInt(this.form.category_main) : null),
unit_id: selectedUnit ? selectedUnit.id : null,
customer_name: this.form.customer_name || null,
phone: this.form.phone || null,
}; };
const ticket = await app().apiPost('/api/tickets', payload); const ticket = await app().apiPost('/api/tickets', payload);