diff --git a/backend/app/api/routers/orders.py b/backend/app/api/routers/orders.py index e39cac8..e3011c8 100644 --- a/backend/app/api/routers/orders.py +++ b/backend/app/api/routers/orders.py @@ -1097,6 +1097,32 @@ async def dispatch_single_line_render( return {"dispatched": True, "line_id": str(line.id)} +class BatchMaterialOverrideBody(BaseModel): + material_override: str | None = None + + +@router.post("/{order_id}/batch-material-override") +async def batch_material_override( + order_id: uuid.UUID, + body: BatchMaterialOverrideBody, + user: User = Depends(require_admin_or_pm), + db: AsyncSession = Depends(get_db), +): + """Set material_override on ALL lines of an order at once.""" + result = await db.execute(select(Order).where(Order.id == order_id)) + if not result.scalar_one_or_none(): + raise HTTPException(404, detail="Order not found") + + from sqlalchemy import update as sql_update + res = await db.execute( + sql_update(OrderLine) + .where(OrderLine.order_id == order_id) + .values(material_override=body.material_override) + ) + await db.commit() + return {"updated": res.rowcount, "material_override": body.material_override} + + class PatchLineBody(BaseModel): material_override: str | None = None diff --git a/frontend/src/api/orders.ts b/frontend/src/api/orders.ts index fb83dcd..5271422 100644 --- a/frontend/src/api/orders.ts +++ b/frontend/src/api/orders.ts @@ -244,6 +244,14 @@ export async function dispatchLineRender(orderId: string, lineId: string) { return res.data } +export async function batchMaterialOverride(orderId: string, materialOverride: string | null) { + const res = await api.post<{ updated: number; material_override: string | null }>( + `/orders/${orderId}/batch-material-override`, + { material_override: materialOverride } + ) + return res.data +} + export async function patchOrderLine(orderId: string, lineId: string, data: { material_override?: string | null }) { const res = await api.patch<{ updated: boolean; line_id: string }>( `/orders/${orderId}/lines/${lineId}`, diff --git a/frontend/src/pages/OrderDetail.tsx b/frontend/src/pages/OrderDetail.tsx index b044ae1..7447e50 100644 --- a/frontend/src/pages/OrderDetail.tsx +++ b/frontend/src/pages/OrderDetail.tsx @@ -12,7 +12,7 @@ import { XCircle, RotateCw, Info, } from 'lucide-react' import { toast } from 'sonner' -import { getOrder, submitOrder, deleteOrder, unlinkCadFile, regenerateItemThumbnail, patchOrderItem, removeOrderLine, dispatchRenders, cancelLineRender, dispatchLineRender, cancelOrderRenders, splitMissingStep, generateLinesFromItems, downloadOrderRenders, rejectOrder, resubmitOrder, rejectOrderLine, patchOrderLine } from '../api/orders' +import { getOrder, submitOrder, deleteOrder, unlinkCadFile, regenerateItemThumbnail, patchOrderItem, removeOrderLine, dispatchRenders, cancelLineRender, dispatchLineRender, cancelOrderRenders, splitMissingStep, generateLinesFromItems, downloadOrderRenders, rejectOrder, resubmitOrder, rejectOrderLine, patchOrderLine, batchMaterialOverride } from '../api/orders' import { checkOrderMaterials, listMaterials, type UnmappedMaterial, type Material } from '../api/materials' import UnmappedMaterialsDialog from '../components/orders/UnmappedMaterialsDialog' import type { OrderItem, OrderLine } from '../api/orders' @@ -128,6 +128,18 @@ export default function OrderDetailPage() { } } + const { data: matList } = useQuery({ queryKey: ['materials'], queryFn: listMaterials }) + const orderLibMats = (matList ?? []).filter((m: Material) => m.schaeffler_code !== null).sort((a: Material, b: Material) => a.name.localeCompare(b.name)) + + const batchOverrideMut = useMutation({ + mutationFn: (val: string | null) => batchMaterialOverride(id!, val), + onSuccess: (data) => { + toast.success(`Material override ${data.material_override ? 'set' : 'cleared'} on ${data.updated} lines`) + qc.invalidateQueries({ queryKey: ['order', id] }) + }, + onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'), + }) + const cancelAllMut = useMutation({ mutationFn: () => cancelOrderRenders(id!), onSuccess: (data) => { @@ -606,6 +618,30 @@ export default function OrderDetailPage() { )} + {(order.lines?.length ?? 0) > 0 && isPrivileged && ( +