Remove Odoo order fallback
All checks were successful
Build Docker Image / build (push) Successful in 5m6s
All checks were successful
Build Docker Image / build (push) Successful in 5m6s
This commit is contained in:
@@ -9,8 +9,6 @@ import {
|
|||||||
import { requireScopes, type AuthContext } from '../auth.js'
|
import { requireScopes, type AuthContext } from '../auth.js'
|
||||||
import { prisma } from '../db.js'
|
import { prisma } from '../db.js'
|
||||||
|
|
||||||
const ODOO_INTERNAL_URL = process.env.ODOO_INTERNAL_URL || 'odoo:8069'
|
|
||||||
|
|
||||||
const quotationInclude = {
|
const quotationInclude = {
|
||||||
selectedTariff: true,
|
selectedTariff: true,
|
||||||
changes: {
|
changes: {
|
||||||
@@ -27,82 +25,6 @@ const orderInclude = {
|
|||||||
type QuotationWithRelations = Prisma.QuotationGetPayload<{ include: typeof quotationInclude }>
|
type QuotationWithRelations = Prisma.QuotationGetPayload<{ include: typeof quotationInclude }>
|
||||||
type OrderWithRelations = Prisma.OrderGetPayload<{ include: typeof orderInclude }>
|
type OrderWithRelations = Prisma.OrderGetPayload<{ include: typeof orderInclude }>
|
||||||
|
|
||||||
interface OdooCompany {
|
|
||||||
uuid?: string
|
|
||||||
name?: string
|
|
||||||
tax_id?: string
|
|
||||||
country?: string
|
|
||||||
country_code?: string
|
|
||||||
active?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OdooTrip {
|
|
||||||
uuid?: string
|
|
||||||
name?: string
|
|
||||||
sequence?: number
|
|
||||||
company?: OdooCompany
|
|
||||||
planned_loading_date?: string
|
|
||||||
actual_loading_date?: string
|
|
||||||
real_loading_date?: string
|
|
||||||
planned_unloading_date?: string
|
|
||||||
actual_unloading_date?: string
|
|
||||||
planned_weight?: number
|
|
||||||
weight_at_loading?: number
|
|
||||||
weight_at_unloading?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OdooStage {
|
|
||||||
uuid?: string
|
|
||||||
name?: string
|
|
||||||
sequence?: number
|
|
||||||
stage_type?: string
|
|
||||||
transport_type?: string
|
|
||||||
source_location_name?: string
|
|
||||||
source_latitude?: number
|
|
||||||
source_longitude?: number
|
|
||||||
destination_location_name?: string
|
|
||||||
destination_latitude?: number
|
|
||||||
destination_longitude?: number
|
|
||||||
location_name?: string
|
|
||||||
location_latitude?: number
|
|
||||||
location_longitude?: number
|
|
||||||
selected_company?: OdooCompany
|
|
||||||
trips?: OdooTrip[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OdooOrderLine {
|
|
||||||
uuid?: string
|
|
||||||
product_uuid?: string
|
|
||||||
product_name?: string
|
|
||||||
quantity?: number
|
|
||||||
unit?: string
|
|
||||||
price_unit?: number
|
|
||||||
subtotal?: number
|
|
||||||
currency?: string
|
|
||||||
notes?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OdooOrder {
|
|
||||||
uuid?: string
|
|
||||||
name?: string
|
|
||||||
team_uuid?: string
|
|
||||||
user_id?: string
|
|
||||||
status?: string
|
|
||||||
total_amount?: number
|
|
||||||
currency?: string
|
|
||||||
source_location_uuid?: string
|
|
||||||
source_location_name?: string
|
|
||||||
source_latitude?: number
|
|
||||||
source_longitude?: number
|
|
||||||
destination_location_uuid?: string
|
|
||||||
destination_location_name?: string
|
|
||||||
created_at?: string
|
|
||||||
updated_at?: string
|
|
||||||
notes?: string
|
|
||||||
order_lines?: OdooOrderLine[]
|
|
||||||
stages?: OdooStage[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TariffMatchResult {
|
interface TariffMatchResult {
|
||||||
tariff: PrismaTariffReference
|
tariff: PrismaTariffReference
|
||||||
score: number
|
score: number
|
||||||
@@ -491,95 +413,6 @@ function assertTeamAccess(ctx: AuthContext): { teamUuid: string; userId: string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapCompany(c?: OdooCompany | null) {
|
|
||||||
if (!c) return null
|
|
||||||
return {
|
|
||||||
uuid: c.uuid ?? '',
|
|
||||||
name: c.name ?? '',
|
|
||||||
taxId: c.tax_id ?? '',
|
|
||||||
country: c.country ?? '',
|
|
||||||
countryCode: c.country_code ?? '',
|
|
||||||
active: c.active ?? true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapTrip(t: OdooTrip) {
|
|
||||||
return {
|
|
||||||
uuid: t.uuid,
|
|
||||||
name: t.name,
|
|
||||||
sequence: t.sequence,
|
|
||||||
company: mapCompany(t.company),
|
|
||||||
plannedLoadingDate: t.planned_loading_date,
|
|
||||||
actualLoadingDate: t.actual_loading_date,
|
|
||||||
realLoadingDate: t.real_loading_date,
|
|
||||||
plannedUnloadingDate: t.planned_unloading_date,
|
|
||||||
actualUnloadingDate: t.actual_unloading_date,
|
|
||||||
plannedWeight: t.planned_weight,
|
|
||||||
weightAtLoading: t.weight_at_loading,
|
|
||||||
weightAtUnloading: t.weight_at_unloading,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStage(s: OdooStage) {
|
|
||||||
return {
|
|
||||||
uuid: s.uuid,
|
|
||||||
name: s.name,
|
|
||||||
sequence: s.sequence,
|
|
||||||
stageType: s.stage_type,
|
|
||||||
transportType: s.transport_type,
|
|
||||||
sourceLocationName: s.source_location_name,
|
|
||||||
sourceLatitude: s.source_latitude,
|
|
||||||
sourceLongitude: s.source_longitude,
|
|
||||||
destinationLocationName: s.destination_location_name,
|
|
||||||
destinationLatitude: s.destination_latitude,
|
|
||||||
destinationLongitude: s.destination_longitude,
|
|
||||||
locationName: s.location_name,
|
|
||||||
locationLatitude: s.location_latitude,
|
|
||||||
locationLongitude: s.location_longitude,
|
|
||||||
selectedCompany: mapCompany(s.selected_company),
|
|
||||||
trips: (s.trips ?? []).map(mapTrip),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapOdooOrder(o: OdooOrder) {
|
|
||||||
const stages = o.stages ?? []
|
|
||||||
const firstStage = stages[0]
|
|
||||||
return {
|
|
||||||
uuid: o.uuid,
|
|
||||||
quotationUuid: null,
|
|
||||||
name: o.name,
|
|
||||||
teamUuid: o.team_uuid,
|
|
||||||
userId: o.user_id,
|
|
||||||
status: o.status ?? 'active',
|
|
||||||
totalAmount: o.total_amount,
|
|
||||||
currency: o.currency,
|
|
||||||
sourceCountryCode: null,
|
|
||||||
sourceLocationUuid: o.source_location_uuid,
|
|
||||||
sourceLocationName: o.source_location_name,
|
|
||||||
sourceLatitude: o.source_latitude || firstStage?.source_latitude,
|
|
||||||
sourceLongitude: o.source_longitude || firstStage?.source_longitude,
|
|
||||||
destinationCountryCode: null,
|
|
||||||
destinationLocationUuid: o.destination_location_uuid,
|
|
||||||
destinationLocationName: o.destination_location_name,
|
|
||||||
etaDays: null,
|
|
||||||
createdAt: o.created_at,
|
|
||||||
updatedAt: o.updated_at,
|
|
||||||
notes: o.notes ?? '',
|
|
||||||
orderLines: (o.order_lines ?? []).map(l => ({
|
|
||||||
uuid: l.uuid,
|
|
||||||
productUuid: l.product_uuid,
|
|
||||||
productName: l.product_name,
|
|
||||||
quantity: l.quantity,
|
|
||||||
unit: l.unit,
|
|
||||||
priceUnit: l.price_unit,
|
|
||||||
subtotal: l.subtotal,
|
|
||||||
currency: l.currency,
|
|
||||||
notes: l.notes ?? '',
|
|
||||||
})),
|
|
||||||
stages: stages.map(mapStage),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapTariffReference(item: PrismaTariffReference) {
|
function mapTariffReference(item: PrismaTariffReference) {
|
||||||
return {
|
return {
|
||||||
uuid: item.uuid,
|
uuid: item.uuid,
|
||||||
@@ -693,53 +526,6 @@ function mapLocalOrder(item: OrderWithRelations) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchOdoo(path: string): Promise<Response> {
|
|
||||||
const controller = new AbortController()
|
|
||||||
const timeout = setTimeout(() => controller.abort(), 10000)
|
|
||||||
try {
|
|
||||||
return await fetch(`http://${ODOO_INTERNAL_URL}${path}`, { signal: controller.signal })
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchTeamOdooOrders(teamUuid: string) {
|
|
||||||
try {
|
|
||||||
const res = await fetchOdoo(`/fastapi/orders/orders/team/${teamUuid}`)
|
|
||||||
if (!res.ok) return []
|
|
||||||
const data = (await res.json()) as OdooOrder[]
|
|
||||||
return data.map(mapOdooOrder)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error calling Odoo:', error)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchSingleOdooOrder(orderUuid: string, teamUuid: string) {
|
|
||||||
try {
|
|
||||||
const res = await fetchOdoo(`/fastapi/orders/orders/${orderUuid}`)
|
|
||||||
if (!res.ok) return null
|
|
||||||
const data = (await res.json()) as OdooOrder
|
|
||||||
if (data.team_uuid !== teamUuid) {
|
|
||||||
throw new GraphQLError('Access denied: order belongs to different team')
|
|
||||||
}
|
|
||||||
return mapOdooOrder(data)
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof GraphQLError) throw error
|
|
||||||
console.error('Error calling Odoo:', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareByCreatedAtDesc(
|
|
||||||
left: { createdAt: string | null | undefined },
|
|
||||||
right: { createdAt: string | null | undefined },
|
|
||||||
) {
|
|
||||||
const leftTime = left.createdAt ? new Date(left.createdAt).getTime() : 0
|
|
||||||
const rightTime = right.createdAt ? new Date(right.createdAt).getTime() : 0
|
|
||||||
return rightTime - leftTime
|
|
||||||
}
|
|
||||||
|
|
||||||
function requiredString(value: unknown, label: string): string {
|
function requiredString(value: unknown, label: string): string {
|
||||||
const next = normalizeString(value)
|
const next = normalizeString(value)
|
||||||
if (!next) {
|
if (!next) {
|
||||||
@@ -1098,8 +884,7 @@ export const teamResolvers = {
|
|||||||
getTeamOrders: async (_: unknown, __: unknown, ctx: AuthContext) => {
|
getTeamOrders: async (_: unknown, __: unknown, ctx: AuthContext) => {
|
||||||
const { teamUuid } = assertTeamAccess(ctx)
|
const { teamUuid } = assertTeamAccess(ctx)
|
||||||
|
|
||||||
const [localOrders, odooOrders] = await Promise.all([
|
const localOrders = await prisma.order.findMany({
|
||||||
prisma.order.findMany({
|
|
||||||
where: {
|
where: {
|
||||||
teamUuid,
|
teamUuid,
|
||||||
},
|
},
|
||||||
@@ -1107,17 +892,15 @@ export const teamResolvers = {
|
|||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
fetchTeamOdooOrders(teamUuid),
|
|
||||||
])
|
|
||||||
|
|
||||||
return [...localOrders.map(mapLocalOrder), ...odooOrders].sort(compareByCreatedAtDesc)
|
return localOrders.map(mapLocalOrder)
|
||||||
},
|
},
|
||||||
|
|
||||||
getOrder: async (_: unknown, args: { orderUuid: string }, ctx: AuthContext) => {
|
getOrder: async (_: unknown, args: { orderUuid: string }, ctx: AuthContext) => {
|
||||||
const { teamUuid } = assertTeamAccess(ctx)
|
const { teamUuid } = assertTeamAccess(ctx)
|
||||||
|
|
||||||
const localOrder = await prisma.order.findFirst({
|
const order = await prisma.order.findFirst({
|
||||||
where: {
|
where: {
|
||||||
uuid: args.orderUuid,
|
uuid: args.orderUuid,
|
||||||
teamUuid,
|
teamUuid,
|
||||||
@@ -1125,11 +908,7 @@ export const teamResolvers = {
|
|||||||
include: orderInclude,
|
include: orderInclude,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (localOrder) {
|
return order ? mapLocalOrder(order) : null
|
||||||
return mapLocalOrder(localOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await fetchSingleOdooOrder(args.orderUuid, teamUuid)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
quotations: async (_: unknown, args: { status?: string | null }, ctx: AuthContext) => {
|
quotations: async (_: unknown, args: { status?: string | null }, ctx: AuthContext) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user