1. EBS INV 库存三量计算到底难在哪
EBS INV 模块里,现有量、保留量、可用量这三个数字看着简单,实际写接口时经常对不上账。现有量(On-hand Quantity)是物理上躺在子库里的数量,保留量(Reserved Quantity)是被订单、工单、调拨单等需求占住的数量,可用量(Available Quantity)才是真正能拿来分配的部分。三者关系一句话:现有量 = 可用量 + 保留量。但落到 Oracle EBS 的标准 API 上,这个等式会因为批次、子库、货位、序列号、成本组等维度拆得七零八落。
面向 Oracle EBS 开发与集成场景,这篇聚焦一件事:用标准 API 把这三个量查准,并且把调用链路接到 TaoToken 统一 Key 通道上,让本地脚本、CI 环境、Agent 工具都能用同一套凭证访问模型能力做联调辅助。适合正在做 INV 接口联调的 EBS 开发、集成工程师,以及需要把库存查询封装成服务对外输出的团队。
我试过直接拼 SQL 查 mtl_onhand_quantities,结果和界面数字差一截,原因是没走 inv_quantity_tree_pub 的树形汇总逻辑。标准 API 的价值就在这里:它把批次、货位、子库的层级关系算好了再返回。下面从环境准备开始,一步步把可复制的配置和验证动作给出来。
2. TaoToken 前置:统一 Key 通道准备
在写 PL/SQL 调用之前,先把模型侧的访问通道理清楚。做 EBS 接口联调时,经常需要让脚本或 Agent 帮忙解释报错、生成测试数据、比对返回结构,这时候一个统一的 Key 通道能省掉到处配环境变量的麻烦。
TaoToken 的定位是统一模型接入通道,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。你需要先在控制台创建 API Key,控制台地址 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。拿到 Key 之后,不管是本地 VS Code 里的 Claude Code 插件,还是自己写的 Python 联调脚本,都指向同一个 base_url,不用为每个工具单独申请。
这里要区分两个概念:EBS 的 INV API 是 Oracle 数据库层的 PL/SQL 包,TaoToken 是模型访问通道,两者不在一层。TaoToken 的作用是让你在写 INV 接口时,有个稳定的模型侧入口来辅助生成代码、排查 ORA 报错、解释 API 返回结构。别把 TaoToken 当成 EBS 的数据库连接代理,它不是干这个的。
如果你用的是 Claude Code 做 EBS 开发辅助,可以参考 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 的接入说明。需要长期跑编码任务或 Agent 流程的,看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。只是想验证模型返回是否符合预期的,用模型对话 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 快速试。
3. 可复制配置:settings.json 与 API 调用骨架
3.1 settings.json 统一 Key 配置
把 Key 写进项目级 settings.json,避免每次手动 export。下面这份配置可以直接复制,把sk-你的Key替换成控制台生成的值:
{ "env": { "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_MODEL": "claude-sonnet-4-20250514" }, "permissions": { "allow": [ "Bash(sqlplus:*)", "Read(//eBS/**)" ] } }这份配置放在项目根目录的.claude/settings.json或对应工具的配置路径下。TAOTOKEN_BASE_URL固定指向 https://taotoken.net/api ,不要加 UTM 参数,那是给网页跳转用的。Key 只放环境变量,别硬编码进 PL/SQL 或提交到 Git。
3.2 INV 现有量查询骨架
现有量走inv_quantity_tree_pub.query_quantities,核心是把批次维度传对。下面这段是可直接在 SQL*Plus 或 PL/SQL Developer 里跑的匿名块骨架:
DECLARE l_return_status VARCHAR2(1); l_msg_count NUMBER; l_msg_data VARCHAR2(2000); l_qoh NUMBER; l_rqoh NUMBER; l_qr NUMBER; l_qs NUMBER; l_att NUMBER; l_atr NUMBER; l_onhand NUMBER := 0; p_organization_id NUMBER := 207; p_inventory_item_id NUMBER := 10001; p_subinventory_code VARCHAR2(30) := 'FG-STORE'; BEGIN FOR rec_loc IN ( SELECT mln.lot_number, mln.expiration_date FROM mtl_lot_numbers mln WHERE mln.inventory_item_id = p_inventory_item_id AND mln.organization_id = p_organization_id ) LOOP inv_quantity_tree_pub.query_quantities( p_api_version_number => 1.0, p_init_msg_lst => NULL, x_return_status => l_return_status, x_msg_count => l_msg_count, x_msg_data => l_msg_data, p_organization_id => p_organization_id, p_inventory_item_id => p_inventory_item_id, p_tree_mode => 3, p_is_revision_control => FALSE, p_is_lot_control => TRUE, p_is_serial_control => FALSE, p_revision => NULL, p_lot_number => rec_loc.lot_number, p_lot_expiration_date => rec_loc.expiration_date, p_subinventory_code => p_subinventory_code, p_locator_id => NULL, p_cost_group_id => NULL, p_onhand_source => inv_quantity_tree_pvt.g_all_subs, x_qoh => l_qoh, x_rqoh => l_rqoh, x_qr => l_qr, x_qs => l_qs, x_att => l_att, x_atr => l_atr ); IF l_return_status = fnd_api.g_ret_sts_success THEN l_onhand := l_onhand + NVL(l_qoh, 0); ELSE DBMS_OUTPUT.PUT_LINE('Error: ' || l_msg_data); END IF; END LOOP; DBMS_OUTPUT.PUT_LINE('On-hand total: ' || l_onhand); END; /几个参数要盯紧:p_tree_mode => 3表示按批次汇总;p_is_lot_control => TRUE必须和物料是否启用批次控制一致,否则返回 0;p_onhand_source => inv_quantity_tree_pvt.g_all_subs表示包含所有子库来源。x_qoh是现有量,x_rqoh是可保留量,x_qr是保留量,x_qs是可用量,后三个别搞混。
3.3 保留量查询骨架
保留量在标准 API 里没有单一函数直接给全,通常要拼mtl_reservations和mtl_material_transactions_temp两张表。下面这段是常见写法:
SELECT NVL(SUM(qty), 0) INTO l_reserv FROM ( SELECT mr.reservation_quantity qty FROM mtl_reservations mr, mtl_system_items_b msi WHERE mr.demand_source_type_id <> 2 AND mr.inventory_item_id = msi.inventory_item_id AND mr.organization_id = msi.organization_id AND mr.subinventory_code = p_subinventory_code AND msi.inventory_item_id = p_inventory_item_id AND msi.organization_id = p_organization_id UNION SELECT mmt.transaction_quantity qty FROM mtl_material_transactions_temp mmt, mtl_system_items_b msi WHERE mmt.transaction_source_type_id <> 2 AND mmt.subinventory_code = p_subinventory_code AND mmt.inventory_item_id = msi.inventory_item_id AND mmt.organization_id = msi.organization_id AND msi.inventory_item_id = p_inventory_item_id AND msi.organization_id = p_organization_id );demand_source_type_id <> 2是排除销售订单来源,因为 SO 的保留逻辑在 OM 模块单独算。如果你的场景要包含 SO 保留,把这个条件去掉,但要注意和 OM 的接口对账。
3.4 可用量计算
可用量 = 现有量 - 保留量。但 EBS 里可用量还受 ATP 规则、子库净需求影响,严格场景要用inv_quantity_tree_pub返回的x_qs。简单场景直接减:
l_available := NVL(l_onhand, 0) - NVL(l_reserv, 0);如果x_qs和这个减法结果对不上,说明有 ATP 规则或子库属性在起作用,要去查mtl_parameters和mtl_item_sub_inventories的availability_type。
4. 验证请求与成功结果
配置写完,先做最小验证。第一步,确认 TaoToken 通道通:
curl -s https://taotoken.net/api/v1/models \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ | head -c 300返回 JSON 里能看到模型列表,说明 Key 和 base_url 都对。这一步不通,后面 PL/SQL 报错时你分不清是 EBS 问题还是通道问题。
第二步,跑 3.2 的匿名块,把p_organization_id、p_inventory_item_id、p_subinventory_code换成你环境里的真实值。成功时DBMS_OUTPUT会打印On-hand total: 数字,l_return_status为S。如果返回E,看l_msg_data里的具体错误。
第三步,拿界面数字对账。在 EBS 的「物料现有量查询」界面输入同样的组织、物料、子库,把界面显示的现有量、保留量、可用量和 API 返回值逐项比对。差异在 0.001 以内算正常,差得多说明维度没对齐。
第四步,用模型对话快速解释返回结构。把 API 返回的x_qoh、x_rqoh、x_qr、x_qs贴到 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,让它帮你确认每个字段的业务含义,比自己翻文档快。
5. 本篇常见错排查
5.1 现有量返回 0 但界面有数
最常见原因是p_is_lot_control传错。物料启用了批次控制,你传FALSE,API 就按无批次维度查,结果自然是 0。反过来物料没启用批次,你传TRUE并传了p_lot_number,也会查不到。去mtl_system_items_b看lot_control_flag,1是启用,2是不启用。
5.2 保留量偏大
检查demand_source_type_id的排除条件。如果你的业务要排除 SO 保留,但漏了<> 2,SO 的保留会被重复计入。另外mtl_material_transactions_temp里可能有未处理的事务临时记录,这些也会被算进保留量,联调阶段可以先加AND mmt.transaction_status <> 3过滤掉已取消的。
5.3 ORA-06502 或字符集报错
l_msg_data长度不够,或者p_lot_number传了超出字段长度的值。把l_msg_data声明成VARCHAR2(2000)以上,p_lot_number做SUBSTR截断。EBS 的mtl_lot_numbers.lot_number一般是VARCHAR2(80),别超。
5.4 TaoToken 返回 401
Key 没读到或过期。检查settings.json里的TAOTOKEN_API_KEY是否被 shell 环境覆盖,echo $TAOTOKEN_API_KEY看实际值。Key 管理页 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 可以重新生成。注意 base_url 不要带末尾斜杠,https://taotoken.net/api后面直接接/v1/models。
5.5 可用量和现有量减保留量对不上
这是 ATP 规则在起作用。查mtl_item_sub_inventories.availability_type,如果是2(ATP 可用),可用量要按 ATP 规则算,不能简单相减。这种场景直接用x_qs返回值,别自己减。
6. 后续联调与通道选择
现有量、保留量、可用量三个数查准之后,下一步是把这套逻辑封装成可复用的存储过程或 REST 服务。封装时建议把组织、物料、子库、批次作为入参,返回结构里同时带上x_qoh、x_qr、x_qs,让调用方自己决定用哪个。
联调过程中如果遇到 ORA 报错或返回结构看不懂,用模型对话 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 快速问。需要把 INV 接口接入文档整理成规范格式的,看接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。长期做 EBS 接口开发和 Agent 自动化的,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 能省掉反复配 Key 的功夫。
最后提醒一句:inv_quantity_tree_pub.query_quantities在批次多的时候循环调用会慢,生产环境建议先批量取批次列表,再一次性传数组,或者直接用inv_quantity_tree_pvt的批量接口。这个坑我在数据量大的组织里踩过,单批次循环查 2000 个批次能跑几分钟,改成批量后降到秒级。