Files
accounted/app/api/sales-orders/route.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

63 lines
2.9 KiB
TypeScript

import { NextResponse } from 'next/server'
import { ensureInitialized } from '@/lib/init'
import { withRouteContext } from '@/lib/api/with-route-context'
import { validateBody, validateQuery } from '@/lib/api/validate'
import { CreateSalesOrderSchema, SalesOrderListQuerySchema } from '@/lib/api/schemas'
import { fetchAllRows } from '@/lib/supabase/fetch-all'
import { maskEmbeddedCustomer } from '@/lib/customers/protect-personal-number'
import { decorate, fetchInvoicedQuantities } from '@/lib/sales-orders/load'
import { createSalesOrder } from '@/lib/sales-orders/write'
import { serviceFailureResponse } from '@/lib/sales-orders/respond'
import { errorResponse } from '@/lib/errors/get-structured-error'
import type { SalesOrder } from '@/types'
ensureInitialized()
/**
* GET /api/sales-orders: the company's kundorder with customer, lines and
* the derived delivery/invoicing progress. Filters: status, customer_id, q
* (order number; the list page matches customer names client-side over the
* embedded customer).
*/
export const GET = withRouteContext('sales_order.list', async (request, { supabase, companyId, log, requestId }) => {
const query = validateQuery(request, SalesOrderListQuerySchema)
if (!query.success) return query.response
const { status, customer_id, q } = query.data
let orders: SalesOrder[]
try {
orders = await fetchAllRows<SalesOrder>(({ from, to }) => {
let qb = supabase
.from('sales_orders')
.select('*, customer:customers(id, name, customer_number, customer_type), items:sales_order_items!sales_order_items_sales_order_id_fkey(*)')
.eq('company_id', companyId)
if (status) qb = qb.eq('status', status)
if (customer_id) qb = qb.eq('customer_id', customer_id)
if (q) qb = qb.ilike('order_number', `%${q}%`)
return qb.order('order_date', { ascending: false }).order('created_at', { ascending: false }).range(from, to)
})
} catch (err) {
return errorResponse(err, log, { requestId })
}
const invoiced = await fetchInvoicedQuantities(supabase, orders.map((o) => o.id))
if (!invoiced.ok) return errorResponse(invoiced.dbError, log, { requestId })
const data = orders.map((o) => decorate(maskEmbeddedCustomer(o), invoiced.byItem))
return NextResponse.json({ data })
})
/** POST /api/sales-orders: create a draft order with lines. */
export const POST = withRouteContext(
'sales_order.create',
async (request, { supabase, user, companyId, log, requestId }) => {
const validation = await validateBody(request, CreateSalesOrderSchema, { log, operation: 'sales_order.create' })
if (!validation.success) return validation.response
const result = await createSalesOrder(supabase, { companyId, userId: user.id, input: validation.data })
if (!result.ok) return serviceFailureResponse(result, log, requestId)
return NextResponse.json({ data: result.order }, { status: 201 })
},
{ requireWrite: true },
)