fix: harden week-two ordering, artwork and operations
This commit is contained in:
@@ -3,9 +3,10 @@ import hashlib
|
||||
import os
|
||||
import secrets
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response
|
||||
from psycopg.types.json import Jsonb
|
||||
|
||||
from ..core import db
|
||||
@@ -65,15 +66,44 @@ def board(user=Depends(operator)):
|
||||
finished = c.execute("SELECT * FROM dtf_local.orders WHERE state='fin' ORDER BY created_at DESC LIMIT %s",
|
||||
(BOARD_FINISHED_LIMIT,)).fetchall()
|
||||
finished_total = c.execute("SELECT count(*) AS n FROM dtf_local.orders WHERE state='fin'").fetchone()['n']
|
||||
quotes = c.execute('''SELECT q.* FROM dtf_local.quotes q
|
||||
LEFT JOIN dtf_local.orders o ON o.quote_id=q.id WHERE o.id IS NULL
|
||||
ORDER BY q.created_at LIMIT %s''', (BOARD_QUOTE_LIMIT,)).fetchall()
|
||||
pending = c.execute('''SELECT q.* FROM dtf_local.quotes q
|
||||
LEFT JOIN dtf_local.orders o ON o.quote_id=q.id
|
||||
WHERE o.id IS NULL AND q.approved IS NULL
|
||||
ORDER BY q.created_at DESC,q.id DESC LIMIT %s''', (BOARD_QUOTE_LIMIT,)).fetchall()
|
||||
approved = c.execute('''SELECT q.* FROM dtf_local.quotes q
|
||||
LEFT JOIN dtf_local.orders o ON o.quote_id=q.id
|
||||
WHERE o.id IS NULL AND q.approved IS NOT NULL
|
||||
ORDER BY q.created_at DESC,q.id DESC LIMIT 20''').fetchall()
|
||||
pending_total = c.execute('''SELECT count(*) AS n FROM dtf_local.quotes q
|
||||
LEFT JOIN dtf_local.orders o ON o.quote_id=q.id
|
||||
WHERE o.id IS NULL AND q.approved IS NULL''').fetchone()['n']
|
||||
approved_total = c.execute('''SELECT count(*) AS n FROM dtf_local.quotes q
|
||||
LEFT JOIN dtf_local.orders o ON o.quote_id=q.id
|
||||
WHERE o.id IS NULL AND q.approved IS NOT NULL''').fetchone()['n']
|
||||
return {'states': STATES, 'transitions': TRANSITIONS,
|
||||
'orders': active + list(reversed(finished)),
|
||||
'finished_shown': len(finished), 'finished_total': finished_total,
|
||||
'quotes': [quote_view(c, q) for q in quotes],
|
||||
'quotes': [quote_view(c, q) for q in pending + approved],
|
||||
'pending_total': pending_total, 'approved_total': approved_total,
|
||||
'events': c.execute('SELECT * FROM dtf_local.outbox ORDER BY id DESC LIMIT 100').fetchall()}
|
||||
|
||||
@router.get('/api/operator/quotes')
|
||||
def quote_page(kind: Literal['pending','approved'], before_created_at: datetime | None = None,
|
||||
before_id: UUID | None = None, limit: int = Query(default=50, ge=1, le=100),
|
||||
user=Depends(operator)):
|
||||
if (before_created_at is None) != (before_id is None):
|
||||
raise HTTPException(422, 'Both quote cursor fields are required')
|
||||
approved_filter = 'q.approved IS NULL' if kind == 'pending' else 'q.approved IS NOT NULL'
|
||||
cursor = 'AND (q.created_at,q.id)<(%s,%s)' if before_created_at else ''
|
||||
params = ((before_created_at,before_id) if before_created_at else ()) + (limit+1,)
|
||||
with db.connect() as c:
|
||||
rows = c.execute(f'''SELECT q.* FROM dtf_local.quotes q
|
||||
LEFT JOIN dtf_local.orders o ON o.quote_id=q.id
|
||||
WHERE o.id IS NULL AND {approved_filter} {cursor}
|
||||
ORDER BY q.created_at DESC,q.id DESC LIMIT %s''', params).fetchall()
|
||||
return {'quotes':[quote_view(c,row) for row in rows[:limit]],
|
||||
'has_more':len(rows)>limit}
|
||||
|
||||
@router.post('/api/operator/quotes/{uid}/approve')
|
||||
def approve(uid: UUID, body: Review, user=Depends(operator)):
|
||||
with db.connect() as c:
|
||||
@@ -83,15 +113,22 @@ def approve(uid: UUID, body: Review, user=Depends(operator)):
|
||||
if row['approved']:
|
||||
raise HTTPException(409, 'Approved quotes are immutable; request a new quote')
|
||||
draft = row['draft']
|
||||
if any(item.get('production', {}).get('version') != 2 for item in draft['items']):
|
||||
raise HTTPException(409, 'Quote uses an obsolete production layout; customer must request a new quote')
|
||||
if len(body.items) != len(draft['items']):
|
||||
raise HTTPException(422, 'Review must cover every item')
|
||||
items = []
|
||||
for item, original in zip(body.items, draft['items']):
|
||||
if item.mode != original['mode'] or list(map(str,item.uploads)) != original['uploads']:
|
||||
raise HTTPException(422, 'Product mode and attached files cannot change during review')
|
||||
if item.production.model_dump(mode='json') != original['production'] or item.quality_status != original['quality_status'] or item.quality_acknowledged != original['quality_acknowledged']:
|
||||
raise HTTPException(422, 'Production instructions and customer acknowledgement cannot change during commercial review')
|
||||
for upload_id in item.uploads:
|
||||
require_clean(upload_row(c, upload_id, row['owner']))
|
||||
items.append({**price(item.mode, str(item.metres), item.grade), 'uploads': original['uploads']})
|
||||
items.append({**price(item.mode, str(item.metres), item.grade),
|
||||
'uploads': original['uploads'], 'production': original['production'],
|
||||
'quality_status': original['quality_status'],
|
||||
'quality_acknowledged': original['quality_acknowledged']})
|
||||
quoted_freight = freight.quote(**draft['freight'])
|
||||
approved = {'customer': draft['customer'], 'items': items, 'freight': quoted_freight,
|
||||
'total_cents': sum(i['total_cents'] for i in items)+quoted_freight['total_cents']}
|
||||
|
||||
Reference in New Issue
Block a user