#49 feat: builders de grafos de provenance (proveedores-organizaciones y territorios)

This commit is contained in:
2026-08-15 17:14:42 -05:00
parent 21376151f0
commit 8335ad2313
2 changed files with 263 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
/**
* Builders de grafos de provenance (específicos del dominio).
*
* Convierten el payload `product_provenance` del resumen de compra/pedido en
* un grafo en capas usando el servicio genérico `services/graph/graph-layout`.
*/
import { computeColumnLayout } from '@/services/graph/graph-layout'
const SUPPLIER_ORG_COLUMNS = { product: 0, supplier: 1, organization: 2 }
const TERRITORY_COLUMNS = { product: 0, supplier: 1, municipality: 2, department: 3, country: 4 }
function makeCollector () {
const nodes = []
const edges = []
const seen = new Map()
return {
nodes,
edges,
addNode (kind, entity) {
const id = `${kind}:${entity.id}`
if (!seen.has(id)) {
const node = {
id,
kind,
label: entity.name,
image: entity.catalogue_images?.[0] || null,
entity: { ...entity, kind },
}
seen.set(id, node)
nodes.push(node)
}
return seen.get(id)
},
addEdge (from, to) {
edges.push({ from, to })
},
}
}
function layout (collector, columns) {
return computeColumnLayout(collector.nodes, collector.edges, {
columnOf: node => columns[node.kind] ?? 0,
columnWidth: 220,
rowHeight: 72,
padding: 24,
})
}
export function buildSupplierOrganizationGraph (provenance) {
const g = makeCollector()
for (const entry of provenance || []) {
if (!entry.product) continue
const product = g.addNode('product', entry.product)
for (const rel of entry.suppliers || []) {
if (!rel.supplier) continue
const supplier = g.addNode('supplier', rel.supplier)
g.addEdge(product.id, supplier.id)
if (rel.organization) {
const organization = g.addNode('organization', rel.organization)
g.addEdge(supplier.id, organization.id)
}
}
}
return layout(g, SUPPLIER_ORG_COLUMNS)
}
export function buildTerritoryGraph (provenance) {
const g = makeCollector()
for (const entry of provenance || []) {
if (!entry.product) continue
const product = g.addNode('product', entry.product)
for (const rel of entry.suppliers || []) {
if (!rel.supplier) continue
const supplier = g.addNode('supplier', rel.supplier)
g.addEdge(product.id, supplier.id)
if (rel.municipality) {
const municipality = g.addNode('municipality', rel.municipality)
g.addEdge(supplier.id, municipality.id)
if (rel.department) {
const department = g.addNode('department', rel.department)
g.addEdge(municipality.id, department.id)
if (rel.country) {
const country = g.addNode('country', rel.country)
g.addEdge(department.id, country.id)
}
}
}
}
}
return layout(g, TERRITORY_COLUMNS)
}
export function hasAnySupplier (provenance) {
return (provenance || []).some(entry => (entry.suppliers || []).length > 0)
}
export function hasAnyTerritory (provenance) {
return (provenance || []).some(entry =>
(entry.suppliers || []).some(rel =>
rel.municipality || rel.department || rel.country
)
)
}