原文地址: https://apex.oracle.com/pls/apex/r/nidhiblog/nidhi/view-blog-post?p16_id=321 # 使用PL/SQL和ORDS在Oracle EBS中构建生产级供应商360 API 在企业级Oracle EBS环境中,供应商数据分散在多个表中。财务和采购团队需要包含站点、采购订单、发票和付款的完整360度视图。 ## 目标 构建基于结构化JSON的Supplier 360 API,并使用PL/SQL通过ORDS公开它。 ## 为什么需要供应商360视图? - 整合供应商信息 - 生成仪表板就绪的JSON输出 - 通过REST(ORDS)公开 - 支持CFO级别报表 - 减少多重查询依赖 ## 架构 数据库 → PL/SQL JSON构建器 → ORDS → REST API → 仪表板/外部系统 ```sql CREATE OR REPLACE PACKAGE xxqst_supplier_360_api_pkg AS FUNCTION get_supplier_360 ( p_supplier_id IN NUMBER ) RETURN CLOB; END xxqst_supplier_360_api_pkg; ``` ```sql CREATE OR REPLACE PACKAGE BODY xxqst_supplier_360_api_pkg AS FUNCTION get_supplier_360 ( p_supplier_id IN NUMBER ) RETURN CLOB IS l_json CLOB; l_first BOOLEAN := TRUE; BEGIN DBMS_LOB.createtemporary(l_json, TRUE); DBMS_LOB.append(l_json, '['); FOR s IN ( SELECT vendor_id, vendor_name, segment1 supplier_number, creation_date FROM ap_suppliers WHERE vendor_id = p_supplier_id ) LOOP IF NOT l_first THEN DBMS_LOB.append(l_json, ','); END IF; l_first := FALSE; DBMS_LOB.append(l_json, '{'); ------------------------------------------------- -- 供应商头部信息 ------------------------------------------------- DBMS_LOB.append(l_json, '"supplier_header":{'); DBMS_LOB.append(l_json, '"supplier_id":"' || s.vendor_id || '",'); DBMS_LOB.append(l_json, '"supplier_name":"' || REPLACE(s.vendor_name,'"','') || '",'); DBMS_LOB.append(l_json, '"supplier_number":"' || s.supplier_number || '",'); DBMS_LOB.append(l_json, '"creation_date":"' || TO_CHAR(s.creation_date,'YYYY-MM-DD') || '"'); DBMS_LOB.append(l_json, '},'); ------------------------------------------------- -- 站点信息 ------------------------------------------------- DBMS_LOB.append(l_json, '"sites":['); DECLARE l_first_site BOOLEAN := TRUE; BEGIN FOR site_rec IN ( SELECT vendor_site_id, vendor_site_code, city FROM ap_supplier_sites_all WHERE vendor_id = s.vendor_id ) LOOP IF NOT l_first_site THEN DBMS_LOB.append(l_json, ','); END IF; l_first_site := FALSE; DBMS_LOB.append(l_json, '{'); DBMS_LOB.append(l_json, '"site_id":"' || site_rec.vendor_site_id || '",'); DBMS_LOB.append(l_json, '"site_code":"' || site_rec.vendor_site_code || '",'); DBMS_LOB.append(l_json, '"city":"' || NVL(site_rec.city,'') || '"'); DBMS_LOB.append(l_json, '}'); END LOOP; END; DBMS_LOB.append(l_json, '],'); ------------------------------------------------- -- 采购订单 ------------------------------------------------- DBMS_LOB.append(l_json, '"purchase_orders":['); DECLARE l_first_po BOOLEAN := TRUE; BEGIN FOR po_rec IN ( SELECT segment1, creation_date, NVL(amount_limit,0) amount_limit FROM po_headers_all WHERE vendor_id = s.vendor_id ) LOOP IF NOT l_first_po THEN DBMS_LOB.append(l_json, ','); END IF; l_first_po := FALSE; DBMS_LOB.append(l_json, '{'); DBMS_LOB.append(l_json, '"po_number":"' || po_rec.segment1 || '",'); DBMS_LOB.append(l_json, '"po_date":"' || TO_CHAR(po_rec.creation_date,'YYYY-MM-DD') || '",'); DBMS_LOB.append(l_json, '"po_amount":"' || po_rec.amount_limit || '"'); DBMS_LOB.append(l_json, '}'); END LOOP; END; DBMS_LOB.append(l_json, '],'); ------------------------------------------------- -- 发票+付款 ------------------------------------------------- DBMS_LOB.append(l_json, '"invoices":['); DECLARE l_first_inv BOOLEAN := TRUE; BEGIN FOR inv IN ( SELECT invoice_id, invoice_num, invoice_amount, invoice_date FROM ap_invoices_all WHERE vendor_id = s.vendor_id ) LOOP IF NOT l_first_inv THEN DBMS_LOB.append(l_json, ','); END IF; l_first_inv := FALSE; DBMS_LOB.append(l_json, '{'); DBMS_LOB.append(l_json, '"invoice_number":"' || inv.invoice_num || '",'); DBMS_LOB.append(l_json, '"invoice_date":"' || TO_CHAR(inv.invoice_date,'YYYY-MM-DD') || '",'); DBMS_LOB.append(l_json, '"invoice_amount":"' || inv.invoice_amount || '",'); DBMS_LOB.append(l_json, '"payments":['); DECLARE l_first_pay BOOLEAN := TRUE; BEGIN FOR pay IN ( SELECT ac.check_number, ac.check_date, aip.amount FROM ap_invoice_payments_all aip JOIN ap_checks_all ac ON ac.check_id = aip.check_id WHERE aip.invoice_id = inv.invoice_id ) LOOP IF NOT l_first_pay THEN DBMS_LOB.append(l_json, ','); END IF; l_first_pay := FALSE; DBMS_LOB.append(l_json, '{'); DBMS_LOB.append(l_json, '"payment_number":"' || pay.check_number || '",'); DBMS_LOB.append(l_json, '"payment_date":"' || TO_CHAR(pay.check_date,'YYYY-MM-DD') || '",'); DBMS_LOB.append(l_json, '"payment_amount":"' || pay.amount || '"'); DBMS_LOB.append(l_json, '}'); END LOOP; END; DBMS_LOB.append(l_json, ']'); DBMS_LOB.append(l_json, '}'); END LOOP; END; DBMS_LOB.append(l_json, ']'); DBMS_LOB.append(l_json, '}'); END LOOP; DBMS_LOB.append(l_json, ']'); RETURN l_json; END get_supplier_360; END xxqst_supplier_360_api_pkg; / ``` ## 核心功能 ```sql FUNCTION get_supplier_360 ( p_supplier_id IN NUMBER ) RETURN CLOB; ``` ```sql SELECT JSON_SERIALIZE( xxqst_supplier_360_api_pkg.get_supplier_360(1001) RETURNING CLOB PRETTY ) AS formatted_json FROM dual; ``` 该函数使用DBMS_LOB手动构建结构化JSON,以避免4000字符限制和嵌套JSON性能问题。 ## 返回的JSON结构 ```json [ { "supplier_header" : { "supplier_id" : "1001", "supplier_name" : "India TDS Tax Authority", "supplier_number" : "1001", "creation_date" : "2024-07-08" }, "sites" : [ { "site_id" : "1001", "site_code" : "GST_DELHI", "city" : "Delhi" } ], "purchase_orders" : [ ], "invoices" : [ { "invoice_number" : "110001-TDS-SI-10400", "invoice_date" : "2025-09-01", "invoice_amount" : "1000", "payments" : [ ] } ] } ] ``` ## 数据来源 - 供应商头部 → AP_SUPPLIERS - 站点 → AP_SUPPLIER_SITES_ALL - 采购订单 → PO_HEADERS_ALL - 发票 → AP_INVOICES_ALL - 付款 → AP_INVOICE_PAYMENTS_ALL + AP_CHECKS_ALL ## 使用方法 SQL测试: ```sql SELECT xxqst_supplier_360_api_pkg.get_supplier_360(1001) FROM dual; ``` ORDS处理程序: ```sql BEGIN :response := xxqst_supplier_360_api_pkg.get_supplier_360(:supplier_id); END; ``` 通过REST调用: ``` http://server/ords/apps/supplier360/get/1001 ``` ## 业务价值 - 供应商绩效仪表板 - 未结项跟踪 - PO与发票可见性 - 付款监控 - 外部ERP集成 ## 关键学习点 - 始终使用DESC验证EBS表结构 - 避免在别名中使用保留关键字 - 使用DBMS_LOB处理大型JSON输出 - 独立测试每个模块 - Supplier 360 API不仅是一个集成层——它是一个财务可见性引擎 使用Oracle EBS | PL/SQL | ORDS | 企业REST架构开发