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:
@@ -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."""
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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); }
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user