U8-ERP-CO-API用友 U8+ 业务组件 HTTP API

U8 行为说明#

本文记录桥所依赖的 U8+ V18.0 行为:业务组件的调用形状、按引用参数、必填字段、写入的表、事务行为和已知陷阱。除另行注明外,各条均已在测试账套实测。升级 U8 或打补丁后,先在测试账套上复核(见 CONTRIBUTING.md)。

本文不是用友官方文档。组件名、方法名和参数来自已安装 U8 的运行时行为,官方未承诺其稳定性。

1. 名词#

名字 说明
CO(业务 COM 组件) 各业务模块的 32 位 VB6 进程内 COM,如 VoucherCO_Sa.ClsVoucherCO_SA(销售)、VoucherCO_PU.clsVoucherCO_PU(采购)、USERPCO.VoucherCO(库存)、UFAPBO(应收应付)。数据以 ADO 行集持久化的 XML DOM 传递,即二次开发常说的「CO 接口」
U8 API 官方 .NET 框架:U8EnvContext + U8ApiComBroker + U8API/<对象>/<动作>。本项目只在生产订单审核上使用
EAI U8 的 XML 导入导出(ufinterface 报文)。本项目用于导入总账凭证和基础档案
OpenAPI 用友开放平台云端接口,需单独购买开通。本项目不使用

这些组件只能在装有同版本 U8 的 Windows 32 位进程里调用。许可问题见 faq.md。

2. 登录#

U8Login.clsLogin.Login(子系统, 账套, 年度, 操作员, 口令, 日期 yyyy-MM-dd, 应用服务器, "")

子系统:销售和质量 SA,库存 ST,采购 PU,应收 AR,应付 AP,总账 GL,基础档案 AS,生产订单审核、新增、删除 MO。

许可点数(加密点数)#

「加密点数已饱和」表示加密服务器(许可服务器)拒绝了本次登录要占用的点。U8 按「工作站 × 子系统」占点,同一产品包内的模块共用该包的点数,计数方式以 U8「许可管理」显示的为准。本项目经 U8 业务组件登录,与 U8 客户端一样占用许可点数。

3. COM 调用通则#

按引用参数#

调用 by-ref 下标
VoucherCO_Sa.Save(头, 体, 模式, vNewID) {3}
VoucherCO_PU.VoucherSave2(头, 体, 模式, 主键) {3}
Delete(头, 体)(销售、采购) {0,1}
销售订单 OrderClose(头, bClose) / OrderClose(头, bClose, iSOsID) {0,1} / {0,1,2}
销售 GetVoucherNO(头, no, err) {0,1,2}
采购 GetVoucherNO(头, 卡片, err, no) {0,1,2,3}
库存 Update(12 个参数) {4,5,6,7,8}
库存 MakeOutVouch {1,2,3,4,5}
调拨单 Verify(12 个参数) {2,3,4,5,6,7,8,10,11}
采购 ClosePOItems / OpenPOItems {2,3}
到货单 CloseArrItems / OpenArrItems {2}(第 4 个参数 bleCloseAll 按值)
采购 GetVoucherDataById(头, 体, "", id, "") {0,1,4}
销售发票 VerifyVouch(头, bool) {0,1}

签名自检#

DOM#

时间戳 ufts#

不要用的调用#

调用 原因
MakeVouchByWhere 各种参数形状均返回「类型不匹配」。销售出库用 MakeOutVouch
AutoAddBodyRecord(UFAPBO) 报「未设置对象变量」
VouchCanSign(UFAPBO) 对已制单、已核销只回笼统的失败原因。改为调用前自行查库
SubmitApplicationMessage2(审批代理) 能建流程实例,但不改单据的审批状态
GetUnAuditTasks、GetTasks2 读的是消息中心提醒,有待办时也返回空。待办直接查 Table_Task
EAI proc="delete"(总账) U8 回「暂不提供此项功能」
EAI proc="edit"(档案) 整张替换,需重新校验全部必填
EAI proc="Query" 把结果文件写进 U8 安装目录的 Logs

4. 金额#

CO 的 Save、VoucherSave2、Insert、Update 都不重算价税,金额和辅数量由调用方(桥)填好。

销售:单行 BodyCheck#

BodyCheck(键, 表体, 表头, 1) 只能对单行表体调用;多行时 U8 读第 R 行、写第 0 行。做法:

  1. cloneNode(true) 复制表体 DOM,删掉其他 z:row,写上要触发的属性。
  2. 普通调用 BodyCheck。返回空串为成功,非空为 U8 原文。
  3. 把临时行的全部属性拷回真实行。

一行的顺序:先 iquantity,再调用方给的单价键(有 itaxunitprice 用它,否则 iunitprice),调用方改了税率才调 itaxrate,然后再调一次单价键。iquantity 重算件数(数量 / 换算率)和扣率;行上已有单价时还重算无税单价、金额、税额、价税合计和本币各列。

单价键(itaxunitprice、iunitprice、itaxrate)的 BodyCheck 有时不改行,而是返回 <R K="…" V="…"/>,需把这些 K/V 写回真实行。

例:数量 100、含税单价 11、税率 13%,得到无税单价 9.7345、金额 973.45、税额 126.55、价税合计 1100。

参照生单时,先按本次数量 / 来源数量缩放来源行的金额列(保留 2 位),件数 = 数量 / 换算率,再跑一次单行 BodyCheck("iquantity")。

采购#

采购的 BodyCheck、BodyCheckEx 不算金额。单价 6 位、金额 2 位,舍入 AwayFromZero。r 为行税率 ipertaxrate / 100,e 为表头汇率 nflat。

行上 btaxcost 算法
1(给了含税单价) isum = round(数量 × itaxprice, 2);imoney = round(isum / (1+r), 2);itax = isum − imoney;iunitprice = round(itaxprice / (1+r), 6)
0(给了无税单价) imoney = round(数量 × iunitprice, 2);itax = round(imoney × r, 2);isum = imoney + itax;itaxprice = round(iunitprice × (1+r), 6)

本币:inatunitprice = round(iunitprice × e, 6),inatmoney、inattax、inatsum 同样乘 e,保留 2 位。

库存#

其他入库、其他出库按调用方本次送来的字段:只送 iPrice 时反算 iUnitCost = round(iPrice / 数量, 6);否则行上有单位成本时 iPrice = round(数量 × iUnitCost, 2)。调拨单数量变化且行上已有 iTVACost / iTVPCost 时重算对应金额。

有计量单位组(Inventory.iGroupType <> 0)的存货必须带辅计量:辅计量单位(调用方给的,否则存货的库存辅计量,否则主辅计量)、换算率(ComputationUnit.iChangRate)、件数 = round(数量 / 换算率, 6)。缺失时 U8 返回「固定换算率的存货[x]的辅计量单位不允许为空!」。改了数量而调用方未送件数时按换算率重算件数;换存货或换辅计量单位时,先清掉已加载的单位、换算率和件数再重填。

5. 应收应付(UFAPBO)#

类型由 cFlag 和 cVouchType 区分:收款单 Ap_CloseBill 上 AR/48,付款单 AP/49,应收单 Ap_Vouch 上 AR/R0,应付单 AP/P0。同表还有供应商退款(AP/48,卡片 AP48,ap_refund)和客户退款(AR/49,卡片 AR49,ar_refund),组件和调用同收付款单,模板条件按本类型发(应付下 cVouchType='48'、应收下 '49';48 / 49 与 AR / AP 不是一一对应)。

操作 调用 说明
初始化 clsAccount_AP.Init(login, "AR"或"AP");收付款单 clsCloseBill.Init(login, conn, acc, 子系统) by-ref {0,1,2,3};应收应付单 clsAPVouch.Init(login, conn, acc) by-ref {0,1,2} clsAccount_AP.Init 返回 false 也可用,不看返回值
读取 GetVouchData(条件, 头, 体, vt, err) by-ref {1,2,3,4}。收付款单条件 <condition keytype='1' iID='…'/>;应收应付单 <condition keytype='1' cLink='…'/>,cLink = cVouchType + cVouchID
空白模板 GetVouchData("<condition cVouchType='48'/>", …)(应收应付单用 R0 / P0) 只有 schema,表头表体行都自行挂
新增 SaveVouch(头, 体, err, true)。收付款单 by-ref {0,1,2};应收应付单两个 DOM 按值,只有 {2} U8 把新主键和单号写回表头 DOM。在请求连接的事务里可回滚,但单号记录(VoucherHistory)回滚后不退,只留断号
审核 收付款单 Sign("<condition type='0' iID='…'/>", msg);应收应付单 Sign("<condition type='0' cLink='…' cVouchType='…' cVouchID='…'/>", msg)。by-ref {1} 写审核人 cCheckMan、审核日期,并写往来明细 Ar_Detail / Ap_Detail
弃审 收付款单 CancelSign("<condition type='0' cVouchType='48' cVouchID='…' iID='…'/>", msg);应收应付单同审核条件。by-ref {1} 删除往来明细
删除 收付款单 DeleteVouch("<condition keytype='2' iID='…'/>", err);应收应付单 <condition keytype='2' cVouchType='…' cVouchID='…'/>。by-ref {1} 尾部可选的 Ufts 不传
修改 GetVouchData 读入,改表头和表体(改的行 editprop=M),SaveVouch(头, 体, "", false),by-ref 同新增 返回 true,留在请求连接的事务里;不写往来明细,不写修改人

票据登记:分包票据#

分包票据(AP_Note.bsubpackage=1)与非分包票据的登记只有以下差别,其余列、收款单表体(iType=0、客户控制科目、金额 = 票面)、往来明细、条码 csysbarcode(不带区间)都相同:

票据退回 9C(notes/process 的 return)#

U8 的退回先 Ap_Proc_CancelNo(CL / AR,处理号 CLAR 补零到 17 位,如 CLAR0000000000001),再经 UFAPBO.clsAPVouch.SaveVouch 存一张应收单,然后写处理行和往来明细。桥同样经 clsAPVouch.SaveVouch(与 vouchers/create 的 ar_bill 同一路径;组件在开事务之前打开并取模板,保存在请求连接的事务里)生成应收单,再补写其余列、写处理行和往来明细,全部在一个事务里。Ap_Vouch.Auto_ID 不是自增列也无缺省值,主键由 U8 组件分配,桥不能自行 INSERT 应收单。

应付票据(flag AP:登记、删除、结算 9A、退回 9C)#

应付票据与应收票据由同一套 U8 逻辑处理,cFlag 在运行时决定 48 / 49、Ar_Detail / Ap_Detail。桥只实现与应收票据对称的部分,属第二级写入(复现 U8 界面的 SQL):缺省关闭,enableReplicatedWrites 打开后也只对 testAccounts 中的账套开放(登录前和入队后各查一次;处理制单、取消处理、删除同样),见 configuration.md。AccInformation 的票据科目 pjkm 应付一侧(cApCode)可能为空,此时请求必须给 note_km。

发票的应付审核、应收审核#

采购发票在应付款管理中的审核(cPBVVerifier)、销售发票在应收款管理中的审核(cVerifier)不走 clsAPVouch,而是 UFAPBO.clsPub_AP,登录子系统 AP / AR。U8 界面的封装(UFAPSuite.clsPuAP.Sign_PurBill / clsSaAR.Sign_SaleBill)自开连接和事务,桥不用它,直接调 UFAPBO:

步骤 调用 说明
初始化 acc = clsAccount_AP,acc.Init(login, "AP"或"AR") 按值,不看返回值;pub = clsPub_AP,pub.Init(login, conn, acc, "AP"或"AR") by-ref {0,1,2,3},conn 为请求连接 省略 conn 报 DISP_E_PARAMNOTFOUND;传请求连接使审核落在桥的事务里
预检 采购 PUVouchCanSign("01"或"02", cPBVCode, bCancel, msg),销售 SAVouchCanSign("26"或"27", cSBVCode, bCancel, msg),by-ref {3},msg 以空串起 true 可审核;false 时 msg 为原因。弃审时 bCancel=true
审核 Sign_PurBill(条件, msg) / Sign_SaleBill(条件, msg),by-ref {1},msg 以空串起 true 为成功;false 时 msg 为 U8 原文
弃审 CancelSign_PurBill(条件, msg) / CancelSign_SaleBill(条件, msg),同一条件,by-ref {1} 清审核人、审核日期,删除原始往来明细行

核销(U8ApCancel.cLsCancel)#

应收(应付)款管理的手工核销:收款单(付款单)对销售发票、应收单(采购发票、应付单)。组件为 VB6 的 U8ApCancel.cLsCancel(ufcomsql 目录),U8 界面和信用证模块都是拼一段 XML 交给它的 Save。.NET 侧无核销接口。

步骤 调用 说明
初始化 hx.Init(login, "AR"或"AP", conn) by-ref {0,2},conn 为请求连接 返回 true。第二个参数只能是 AR / AP,不要传 clsAccount_AP
核销 hx.Save(xml) by-ref {0} true 为成功。执行后参数被改写为 <root/>

报文用 DOM 生成:

<canceldata cdwcode="C900001" crpflag="AR" dhxdate="2026-01-28" bsamehx="True">
  <close cdwcode="C900001" cexchname="人民币" cjsdnum="SK0000000001" crpflag="AR" djsddate="2026-01-05"
         bprepay="0" iexchrate="1" id="<Ap_CloseBills.ID>" ijsdhxamount_f="6.00" ijsdhxamount="6.00">
    <vouch cvouchtype="26" cvouchcode="SOZP0000000001" dvouchdate="2026-01-07" cinvcode="" cdigest=""
           iinvid="<iBVid>" cexchname="人民币" iexchrate="1" icancelamount_f="6.00" icancelamount="6.00"
           bcancelall="false"/>
  </close>
</canceldata>

自动核销(不用 cLsCancel.AutoCancel)#

取消核销#

制单、取消制单(桥拼分录 + U8PzInsert)#

坏账处理#

坏账计提、发生、收回及其取消、制单属第二级写入(复现 U8 界面的 SQL,与 U8 界面执行的 SQL 一致,实测核对):缺省关闭,enableReplicatedWrites 打开后也只对 testAccounts 中的账套开放,见 configuration.md。正式账套请在 U8 客户端操作。

6. 销售#

登录 SA。USSAServer.clsSystem:Init(login)、INIMySAInfor();然后 VoucherCO_Sa.ClsVoucherCO_SA.Init(vt, login, conn, "CS", clsSystem),之后才设 bManualTrans = true。

单据 VT 卡片 数据来源
销售订单 12 17 SaleOrderQ(saleorderq.id=?)
蓝字发货单 9 01 Sales_FHD_T(sales_fhd_t.dlid=?)
退货单(红字发货单) 10 03 同蓝字发货单(卡片 03 与 01 的扩展列相同)
销售专用发票(cVouchType 26) 0 07 SaleBillVouchZT
销售普通发票(27) 2 13 同上
退货申请单 34 SA31 SA_ReturnsApplyMain / SA_ReturnsApplyDetail(GetVoucherData 可读,桥直接查表)

扩展列:select cextendfield, cextendjoin from voucherextendinfo where cardnumber=? and cextendtype='T' and bextend='2',SQL 前加 '' as editprop。

操作 调用 要点
读取 GetVoucherData(头, 体, id, true, "", (short)0) 第四个参数让 U8 按操作员数据权限过滤
审核、弃审 VerifyVouch(dom, bool) 空串为成功。「已审」看审核人 cVerifier 和审核日期 dverifydate 均非空;iverifystate 不能作审核标志
订单新增 GetDefaultVoucherDom 取模板,GetVoucherNO 取号,Save(头, 体, 0, vNewID) 表体要有预完工日期 dPreMoDate;组件不补预发货日期 dPreDate(不带则存空),桥按表头预发货、单据日期补;多计量单位存货要补计量单位组、单位、换算率、件数
订单修改 GetVoucherData 读入,按 editprop 标行,Save(头, 体, (short)1, vNewID) U8 不写修改人、修改日期,需自行写
订单关闭、打开 OrderClose(头, bClose)(整单)或 OrderClose(头, bClose, iSOsID)(一行;多行逐行调,每次重新装表头) U8 对未审核订单也会关闭,调用前自行拒绝。整单写关闭人和全部行的关闭人
发货单 ← 销售订单 来源视图 sale_RefSOVouch_T(id=?)、sale_RefSOVouch_B(isosid=?),只拷目标 schema 中有的属性,跳过主键、单号、ufts、制单审核关闭人、累计数量等列。表头 cvouchtype=05、breturnflag=0、editprop=A,GetVoucherNO 取号后 Save(…, 0) CO 自行增加订单行累计发货数 iFHQuantity
发货单删除 Delete(头, 体),删除前把每行 editprop 标为 D 累计发货数回退
发货单修改 VT 9 的 GetVoucherData 读入,表头、改的行 editprop=M,Save(头, 体, (short)1, "") by-ref {3} 返回 null 为成功,留在请求连接的事务里;U8 按数量差调整订单行 iFHQuantity,但不重算金额、不写修改人,由桥处理
退货单 ← 蓝字发货单 VT 10 的 GetNegaVouchData("05", 头, 体, 蓝字 DLID, err, true),by-ref {0,1,2,3,4,5},返回 U8 生成的整张红字 DOM(breturnflag=1、bneedbill=1,ivtid 为蓝字模板,表体为原行全数取负,autoid / autoid2 / dlid 为原单主键,不挂订单)。可保存的 DOM:表头 editprop=A、dlid=""、去掉 ufts、ivtid 按卡片 03 重取、cdlcode 取号、csocode = 订单号、bneedbill 0 或 1;表体只留要退的行,editprop=A、autoid / dlid 空,iquantity / inum 为负的退货数,funsignquantity / funsignnum 为正,iretquantity / fretqtywkp / fretqtyykp 为 0,icorid = 原 iDLsID、ccorcode = 原单号,isosid = 原 iSOsID、csocode = cordercode = 订单号、iorderrowno = 订单行号。GetVoucherNO + Save(…, 0) 保存后 U8 对原发货行检查(在回写之后计算):CASE WHEN ABS(iSettleQuantity) - (ABS(iQuantity) - ABS(fretqtywkp)) > 0 THEN ABS(iQuantity)-ABS(fretqtywkp) ELSE ABS(iSettleQuantity) END - ABS(fretqtyykp) < 0 时报「退货数量不能大于应发货数量」。表头 bneedbill=1 按已开票退货,退货数加到原行 fretqtyykp;bneedbill=0 按未开票退货,加到 fretqtywkp。U8 还回写原行 iRetQuantity 加、订单行 iFHQuantity 减、fretquantity 加,都在本事务里,桥提交前核对
退货单审核、删除 同发货单,VT 10;删除前每行 editprop=D
销售发票 ← 发货单 来源视图 Sales_FHD_T、Sales_FHD_W(idlsid=?)。表头必须带 sbvid=""、idisp=1,表体 cbdlcode = 发货单号 DispatchList.cDLCode CO 写发货行累计开票数 iSettleQuantity。来源视图无 idisp,不写则为列缺省 0,U8 视为先开票,之后修改报「先开票不可以参照发货单」
红字销售发票 ← 退货单 VT 1(26)/ 3(27)。先试 GetNegaVouchData("26" 或 "27", 头, 体, 退货单 DLID, err, true),by-ref {0,1,2,3,4,5};返回 false、没有表头或表体 idlsid 不含全部请求行时,改用同蓝字的 GetDefaultVoucherDom(卡片 07 / 13)加参照视图 Sales_FHD_T、Sales_FHD_W(两视图都含退货单,数量为负)。表头 sbvid=""、cvouchtype、idisp=1、breturnflag=1、cdlcode = 退货单号;表体 idlsid = 退货行、cbdlcode = 退货单号、iquantity 为负。GetVoucherNO + Save(…, 0) 红票 iVTid 同蓝字。退货行 iSettleQuantity 等于发票数量(负);原蓝字发货行 fretqtyykp 在退货单保存时已写,红票不再加;订单行 iKPQuantity 为净数(蓝票 − |红票|),在保存时回写,复核、弃复不再变
红字销售发票 ← 蓝字发票(红冲) Init(1 或 3, …)(26 → VT 1,27 → VT 3),GetNegaVouchData("26" 或 "27", 头, 体, 蓝字 SBVID, err, true),by-ref {0,1,2,3,4,5}。传蓝字发票的 SBVID 返回 true 和整张红字发票 DOM:表头 sbvid=0、csbvcode 空、breturnflag=1、cvouchtype 和客户同蓝字;表体为蓝字行取负,autoid 是蓝字行的主键(需清掉),idlsid 为空。传对应发货单 DLID 返回 false。桥按请求保留行、改数量,GetVoucherNO + Save(…, 0) U8 无可用的红蓝发票关联列(iSBVID 通常为 0):桥在红字行 SaleBillVouchs.iSBVID 写蓝字行 AutoID 作关联,剩余数量按它算,保存后核对;蓝字发票被红冲后桥拒绝弃复、删除、修改。发货行 iSettleQuantity、fVeriBillQty,订单行 iKPQuantity 只记审计;U8 是否另生成发货单未验证
退货申请单新增、修改、删除、审核、弃审 Init(34, login, conn, "CS", sys);读入 GetVoucherData(头, 体, ID, true, "", 0)(表体 iquantity 为负,带 idlsid / cdlcode);新增取空白模板挂发货行(idlsid、icorid、cdlcode),GetVoucherNO + Save(头, 体, 0, "");修改 Save(…, 1, "");删除每行 editprop=D 后 Delete(头, 体);审核 VerifyVouch(头, bool) VT 34 的 Save、Delete、VerifyVouch 转给 .NET 的 SaVoucherService.clsSaVoucherService,它在传入连接上自行 BeginTrans / Commit,不看 bManualTrans;外层包事务时 Save 报「SaveVoucher:无法在此会话中启动更多的事务。」。所以该卡片的写入不包事务,按自行提交的组件处理(检查在调用前做完,之后核对失败一律 504)。数量在卡片上为负
退货单 ← 退货申请单 同退货单 ← 蓝字发货单(VT 10,GetNegaVouchData("05", …, 申请行所挂的蓝字 DLID, …)),只保留申请行对应的发货行,表体另写 irtnappid = 申请行 AutoID、crtnappcode = 申请单号,表头 bneedbill 取申请行的 U8 保存时回写申请行已退数量 fretqty(负数,更负),在本事务里,桥提交前核对;删除退货单时退回。参照申请单生成的退货行改数量、删行时 U8 如何处理 fretqty 未验证,桥拒绝
销售发票复核、弃复 表头 SQL SaleBillVouchZT … where sbvid=?,VerifyVouch(头, bool) 写复核人 cChecker;cVerifier 属应收审核(见「发票的应付审核、应收审核」)。应收已审的发票弃复时 U8 返回「单据已经在应收系统审核」,需先应收弃审
销售发票删除 Delete 前把每行 editprop 标为 D 不标 D 也能删,但 iSettleQuantity 不回退
无来源发货单 VT 9 的 GetDefaultVoucherDom(conn, "01", 头, 体) 取模板,调用方字段写进种子行,单行 BodyCheck 算价税,getDefaltVTID + 制单人,GetVoucherNO + Save(头, 体, 0, vNewID) by-ref {3} 表头 cbustype=普通销售、cvouchtype=05、breturnflag=0,表体不写 isosid。销售选项 SA.bMustSO_ptxs(普通销售必有订单)为 TRUE 时桥 409
先开票销售发票 同上,VT 0 / 卡片 07(26)或 VT 2 / 卡片 13(27);表头 idisp=0、sbvid="",表体不写 idlsid U8 在发票保存时生成发货单(DispatchList.SBVID 指回发票),无需复核;删除发票时一并删除该发货单。选项同无来源发货单
销售发票修改 同发货单修改,VT 按 cVouchType(26 → 0,27 → 2) 参照发货单的发票若 iDisp=0,保存时 U8 报「单据[..]第1行 存货[..]的记录不正确,先开票不可以参照发货单!」;修改时写 idisp=1 并补表体 cbdlcode

销售出库(USERPCO.VoucherCO,登录 ST):MakeOutVouch(DLID, new DispatchWrapper(null), errMsg, conn, domMsg, true) 返回布尔,一次生成该发货单剩余的全部行,不能指定部分数量;跨仓库时一张发货单生成多张出库单。vouchers/generate 的 sale_out 带 lines 时桥按行部分生成:先整单生成并提交,再在另一事务里改小、删掉未请求的行,失败则删除生成的单据补偿(见「销售出库按行部分出库」);不带 lines 时同样把超出剩余数量的部分改回。新单据按发货单号 cDLCode 在 rdrecord32 上找:调用前记下 max(ID),调用后取更大的 ID,没有则说明 U8 未生成。

无来源销售出库(仅当账套未启用销售管理时)走 USERPCO.VoucherCO 的 Insert("32", …),同其他出库单:表头 cvouchtype=32、cbustype=普通销售、csource=库存、brdflag=0、vt_id=87,表体不写 iDLsID。账套已启用销售管理(AccInformation 的 SA / dSaleStartDate 非空)或库存选项 ST.bSAcreat(销售出库单由销售系统生成)打开时桥拒绝。修改销售出库单时桥一律不允许换仓库(不依赖 ST.bCanModifySaleoutWh)。

7. 采购#

登录 PU。先初始化 Info_PU.ClsS_Infor,再 VoucherCO_PU.Init。

操作 调用 要点
订单、到货单读取、审核、弃审 GetVoucherDataById,ConfirmPO / CancelconfirmPO / ConfirmArr / CancelconfirmArr 空串为成功
订单新增 空白 DOM:zpurpoheader、zPurpotail 视图 where 1=2 各加一行。GetVoucherNO(头, "88", err, no) 取号,VoucherSave2(头, 体, (short)2, "") 第 4 个参数返回新主键
订单修改 GetVoucherDataById 读入,editprop 同销售,VoucherSave2(头, 体, (short)1, "<POID>") 改过和新增的行重算金额
订单关闭、打开 ClosePOItems(头, 体, errMsg, 整单?) / OpenPOItems 返回处理的行数(short),errMsg 空且大于 0 才算成功。按行时先从 DOM 删掉其他行,第 4 个参数 false。全部行关闭后才写表头关闭人
到货单删除 Init 的 VT 为 2,读入后 Delete U8 自行回退订单累计到货
到货单关闭、打开 Init 的 VT 为 2,GetVoucherDataById 读入,CloseArrItems(头, 体, strErr, 整单?) / OpenArrItems,by-ref {2} 签名同 ClosePOItems,但第 4 个参数按值。无专用存储过程。桥不看返回值,strErr 非空作 409;提交前在同一事务里核对行关闭人 cbcloser 到达目标状态,按行时另核对未选行未被改动。表头写 ccloser、dclosedate(无关闭时间列),行写 cbcloser、dlineclosedate;关闭人为操作员姓名。按行关闭后表头何时写关闭人未验证
到货单修改 Info_PU.ClsS_Infor.Init(login, "普通采购", "CG"),VoucherCO_PU.Init(2, login, conn, info, true, "0", "普通采购", 0, "", "CG"),bOutTrans=true;GetVoucherDataById(头, 体, "", id, "") by-ref {0,1,4} 读入,editprop=M,VoucherSave2(头, 体, (short)1, "<ID>") by-ref {3} 返回 null 为成功,@@TRANCOUNT 仍为 1;U8 按数量差调整订单行 iArrQTY,不重算金额(桥按订单修改的算法算)。sBillType 传空串时报「类型不匹配」
到货单 ← 采购订单 VoucherCO_PU.Init(2, login, conn, info, true, "0", "普通采购", 0, "", "CG")。空白 DOM 取 GetVoucherDataById(头, 体, "", 0, "") 的 schema 再挂行。GetVoucherNO(头, "26", err, no)。行上写 corufts(不在 schema 中也照写,U8 回写订单时用它做并发核对)。VoucherSave2(…, 2, "") 第 6 个参数 sBillType 必须为 "0"。传空串时保存也成功,但 U8 不写临时明细表,订单累计到货 iArrQTY、fPoArrQuantity 不回写,之后按 U8 规则也删不掉
无来源到货单 同到货单 ← 采购订单的 Init、空白 DOM、取号、VoucherSave2(…, 2, "");表头不写 cpocode,表体不写 iposid / cordercode / corufts,价税由桥按采购公式算 采购选项 PU.bPTHavePO 为 True 时桥 409(与无来源采购入库同一选项)。U8 要求部门(「部门不能为空」),桥取请求或供应商分管部门
采购发票 ← 采购入库单 Info_PU.ClsS_Infor.Init(login, "普通采购", "CG");VoucherCO_PU.Init(4, login, conn, info, true, 单据键, "普通采购", 0, "", "CG"),bOutTrans=true。单据键 purbill(专用)或 ppurbill(普通),不是 01 / 02。空白 DOM 同到货单。VoucherSave2(…, 2, "") 新行写 U8 加载出的发票的整套属性,布尔写 True / False。普通发票:税率 0、idiscounttaxtype=1、btaxcost=False。U8 回写入库行 iSumBillQuantity 和订单行 iInvQTY
采购发票删除 同一 Init,GetVoucherDataById(头, 体, "", PBVID, "") 读入,Delete(头, 体) 累计开票数回退
采购发票复核、取消复核 同一 Init,SetVerifyMode(true),GetVoucherDataById(头, 体, "", PBVID, "") by-ref {0,1,4} 读入;复核 ConfirmBill(头, false) by-ref {1}(第二个参数为 out bTask),取消复核 CancelconfirmBill(头) 空串为成功,否则为 U8 原文。复核写 cVerifier、cAuditDate、cAuditTime、iverifystateex=2(条件带 isnull(cVerifier,'')=''),取消复核清掉。两者在桥的事务里可回滚。这是采购管理的复核,不是应付审核(见「发票的应付审核、应收审核」)
红字采购发票 ← 红字采购入库单 同蓝字,只把 VoucherCO_PU.Init 第 5 个参数 bPositive 换为 false(sBillType 仍为 purbill / ppurbill,VT 4);表头 bnegative=True,行 ipbvquantity 和金额为负,rdsid 为红字入库行 AutoID,无订单行时不写 iposid / cordercode。bNegative=1 的发票删除、复核、取消复核同样用 bPositive=false 红字入库行开票后 iSumBillQuantity 为负,等于发票行数量(桥按此核对)。组件无 GetNegaVouchData

采购结算#

采购结算单为卡片 99(PurSettleVouch / PurSettleVouchs),把发票行(iBsID → PurBillVouchs.ID)与入库行(iRdsID → rdrecords01.AutoID)配对,没有审核。组件同发票:VoucherCO_PU.clsVoucherCO_PU。以下调用都在调用方事务里,可回滚。

采购手工结算#

销售订单 / 采购订单锁定#

锁定人在表头 cLocker(SO_SOMain、PO_Pomain,nvarchar(20)),写操作员姓名,空为未锁定。视图 SaleOrderQ、zpurpoheader 中为 clocker。没有 bLocked 之类的列;DispatchList / PurBillVouch 的 iNetLock 是网络并发锁,与此无关。

8. 库存#

登录 ST,USERPCO.VoucherCO,IniLogin 之后调用。单据类型短码:采购入库 01、其他入库 08、其他出库 09、产成品入库 10、材料出库 11、调拨 12、销售出库 32;形态转换 15、调拨申请 62、盘点 18、货位调整 19、期初结存 34。

操作 调用 要点
读取、审核、弃审、删除 Load、Verify / UnVerify / Delete,都只传到 bList,共 9 个参数 仓库启用货位时 U8 拒绝没有货位的单据;库存不足有专门提示
新增 Insert,13 个参数;新主键在下标 6 空白 DOM 用各单据的表头表体视图 where 1=2
修改 Load(type, "id=<id>", 头, 体, pos, err, false, "")(pos 可能为 null,换成空 DOM),Update(type, 头, 体, pos, err, conn, msg, true, true, false, "", true) 返回布尔,错误文本在第 5 个参数。U8 自行写修改人、修改日期
销售出库修改 IniLogin(login, "") by-ref {1};Load("32", "id=<id>", …) by-ref {2,3,4,5},表头和行 editprop=M;Update("32", …) 同上 返回 true,留在请求连接的事务里;U8 按数量差调整发货行 fOutQuantity
调拨单审核 Verify("12", id, err, conn, ufts, msg, true, true, false, new DispatchWrapper(null), "", dict),dict 为 Scripting.Dictionary dict.Keys 是生成的其他出库和其他入库单主键。弃审用 9 参数版本,会删除这两张未审核的生成单
采购入库 ← 采购订单 视图 zpurrkdhead、zpurrkdtail,Insert("01", …)。表体 iposid 为订单行主键 CO 回写订单行累计入库 iReceivedQTY
采购入库 ← 来料检验单 同上,行上带 icheckidbaks、iarrsid、ccheckcode;货位仓库另带 cPosition,货位 DOM 传空(参照采购订单、采购退货单同样) Insert 不检查货位:货位仓的行不带 cPosition 也照存,cPosition 为 NULL、不写 InvPosition,所以桥生单前自行核对。U8 回写检验单累计入库、到货行和订单行累计数,并写 InvPosition;删除时先清货位再删,全部回退
红字采购入库 ← 采购退货单 同上,Insert("01", …);表头 bredvouch=1、csource=采购到货单、carvcode / darvdate / ipurarriveid 指向退货单,行 iarrsid = 退货单行 Autoid、cbarvcode、有订单行时 iposid / cpoid,corufts 取退货单表头 ufts,iquantity / inquantity 和金额为负 U8 待入库视图 pu_v_preparestockbyarrforst 对退货行按 iQuantity - fValidInQuan - fInValidInQuan(负数)算可入库,U8 回写退货行 fValidInQuan(负);订单行 iReceivedQTY 是否减少未验证(桥只记审计)。ST.VouchSource 无「采购退货单」,来源写「采购到货单」
采购入库 ← 蓝字到货单 同上,Insert("01", …);表头 csource=采购到货单、carvcode / darvdate / ipurarriveid 指向到货单、bredvouch=0,cordercode 只在各行属同一张订单时写;行 iarrsid = 到货行 Autoid、cbarvcode / dbarvdate、有订单行时 iposid / cpoid,单价、税率取到货行(iOriCost / iOriTaxCost / iTaxRate / bTaxCost),批号缺省为到货行批号,保质期与自由项沿用到货行;不写 corufts 与 U8 界面生成的入库一致(iarriveid 为空)。U8 回写到货行 fValidInQuan(累计入库),订单行写 freceivedqty。剩余可入库 = U8 视图 pu_arrbody.fininquantity(iQuantity - fRefuseQuantity - fValidInQuan - fInValidInQuan)。「普通业务必有订单」打开且订单已有到货单时,参照订单生成入库 U8 报「该操作会造成订单到货和入库同时存在」。未经完整实测
采购入库(无来源) 同上,表头 csource=库存、cbustype=普通采购、vt_id=27,表体不带来源行 id;Update("01", …) 同其他入库 无回写;价税由桥按采购公式算(见「金额」)。表头 itaxrate 可为空;采购选项 bPTHavePO 为 True 时 U8 拒绝(「普通采购必有订单,存货[x]不能手工录入」);货位仓库审核时要求行上 cPosition,行上带 cPosition、货位 DOM 传空时 U8 在保存时写 InvPosition。带生产日期和失效日期时 U8 原样保存,并从档案带出 iMassDate、cMassUnit;不补「有效期至」dExpirationdate / cExpirationdate
采购入库(无来源红字) 同上,表头 bredvouch=1,表体 iQuantity、iNum、iOriMoney、iOriTaxPrice、ioriSum、iPrice、iTaxPrice、iSum 为负、单价为正(与 U8 客户端手工红字退库一致:cSource=库存、bRdFlag=1、VT_ID=27),Insert 第 11 个参数 bIsRedVouch=true 无回写。「普通业务必有订单」(PU.bPTHavePO)只拦蓝字,U8 对红字退库不执行该选项;桥同样放行红字,蓝字 409

销售出库按行部分出库#

先把一张出库单改小再 MakeOutVouch,U8 会把原剩余数量整份再出一次(选项 bOverDispOut=True 时超发),所以不能靠多次 MakeOutVouch 做部分出库,部分出库之后的整单生成也会超发。另外 USERPCO 的 Load 走 U8 自己的连接:放进桥未提交的事务里,读不到刚生成的单据,或与本事务互相锁住。因此桥分两步,不是一个事务:

  1. 读发货行 iQuantity、fOutQuantity、cSCloser 作基线(已关闭的行不算应出,lines 列出它时 409「明细行已关闭」);MakeOutVouch 整单生成,在桥的事务里(同一连接传给 cnnFrom)调用并由桥提交(调用后 @@TRANCOUNT 仍为 1,U8 不自行提交)。这一步出的是整张发货单的剩余,与 lines 无关:任一未出完的行不能整行出库(库存不足、货位、批次),MakeOutVouch 就整张失败,请求 409(如 stock_shortage 报另一行的存货),即使那一行未被请求。
  2. 按发货行比较 U8 生成的数量(rdrecords32 按 iDLsID 汇总)与应出数量:带 lines 为请求数量,不带为 iQuantity − 基线 fOutQuantity。每行都不多则结束(带 lines 时生成少于请求数量算失败)。有多出时:事务外对每张新单 Load("32", "id=<id>", …),标好改动后开新事务 Update("32", …)(同上表「修改」,by-ref {4..8},不自行提交,U8 按差额调整 DispatchLists.fOutQuantity):要少出的行改为应出数量(iNum 按换算率重算,有 iUnitCost 时 iPrice = round(数量 × iUnitCost, 2)),不要的行 editprop=D;一行都不要的整张走普通删除(9 参数 Delete,有货位记录时先 ClearPosition 再 bList=true,否则 bList=false,见「货位」)。要改数量的单据有货位记录时不改,按失败处理。提交前核对每个发货行 fOutQuantity = 基线 + 应出。

第 2 步任何失败都补偿:新开事务删除第 1 步生成且仍存在的出库单,核对发货行 fOutQuantity 回到基线,返回 409 u8_rejected「已撤回生成的销售出库单:<原因>」。补偿也失败时返回 504 outcome_unknown,消息和审计中列出生成的出库单 id,需人工核对。完整的两步流程、editprop=D 删行、补偿及跨仓库多张出库单的情形未验证,只验证了单步操作。

销售出库指定批号、货位#

MakeOutVouch 没有表头表体覆盖参数(第二个参数是输出的单号集合),U8 按发货行带出仓库、批号、货位。因此桥在第 2 步(按数量改回)之后再加一步,走同一改单路径(事务外 Load("32"),新事务里 Update("32")):

货位#

形态转换单、调拨申请单、盘点单#

与调拨单同一个 USERPCO.VoucherCO、同一组参数:

调拨单参照调拨申请单:不用 MakeTransVouchFromTR(三个 out 参数,依赖界面先把选中行写进临时表),改用调拨单新增的 Insert("12"),表体写 itrids(TransVouchs.iTRIds = ST_AppTransVouchs.autoID,同采购入库参照订单的 iposid)。U8 保存时按 itrids 给申请单行累加 iTvSumQuantity / iTVSumNum,删除调拨单时退回;桥在同一事务里核对。可参照数量 = 核准数量 iTvChkQuantity − 累计调拨(U8 参照列表只列核准数量非 0 的行)。超核准由选项 ST.bOverTransRequestTransfer 控制。表头 ctranrequestcode 由桥写申请单号。U8 是否在行全部调完后自动写 cBCloser 未验证。

货位调整单#

与形态转换单同一组参数:Insert("19") 13 参、Verify / UnVerify / Delete 9 参,都在请求连接的事务里。属第一级写入(受写入策略约束),新增、审核、弃审、删除已实测往返:

单号#

GetNewVouchCode 和 PublicSub.SetBillSerial 都不套用用户设置的编号规则。正确做法:USCOMMON.PublicSub.GetBillRuleXml(login, 卡片号) 取规则,按表头值填各前缀的种子(日期按年月日 / 年月 / 年),再 UFBillComponent.clsBillComponent.InitBill(UfDbName, 卡片号) + GetNumber(规则, true)。取号会改写传入的 DOM,需另建一份表头取号。取号推进 VoucherHistory,事务回滚后号不退回,只留断号。

9. 生产#

操作 调用 要点
材料出库 ← 生产订单 视图 RecordOutQ / recordoutsq,Insert("11")。表体子件分配 AllocateId、订单行序号 U8 回写子件已领量 IssQty,允许超领
产成品入库 ← 产品检验单 视图 recordinq / recordinsq,Insert("10"),非合并检验只能 1 行,表体 imergecheckautoid=-1 U8 回写订单行合格入库量和检验单累计入库量
产成品入库 ← 合并检验的产品检验单(BMERGECHECKFLAG=1) 同上,一个合并来源一行:表体 imergecheckautoid=QMMergeCheckDetail.AUTOID、impoids / cmocode / imoseq 取该来源的订单行(SOURCEAUTOID=MoDId)、inquantity=该来源 FREGQUANTITY,icheckidbaks 仍是检验单 ID;表头不写 cmpocode。imergecheckautoid 不在卡片 0411 的 voucheritems 里,但在 RecordInSQ 视图里 合并来源在 QMMergeCheckDetail(按来源存 FREGQUANTITY / FCONQUANTIY / FSUMQUANTITY / BPROINFLAG / SOURCEAUTOID);表头 SOURCEAUTOID 只是其中一个来源(可能为 0)。入库时 U8 回写来源 FSUMQUANTITY、表头 FsumQuantity(= 来源合计)、订单行 QualifiedInQty,满额时来源 BPROINFLAG=1;删除时 Delete 自己回退。桥提交前在同一事务里核对三处都加了本次数量,删除时核对减回,不符回滚 409。修改时的回写未验证,桥拒绝修改这类入库单
产成品入库 ← 产品不良品处理单(QM06) 同上,表头 csource=产品不良品处理单;表体 irejectids(处理单表体 AUTOID)、crejectcode、icheckidbaks / ccheckcode(处理单的检验单)、impoids(订单行 MoDId,取法同 U8 视图 QM_RefProReject);存货、数量用处理后存货 CDIMINVCODE、处理后数量 FDIMQUANTITY 处理单没有累计入库数列,已入库数按 rdrecords10.iRejectIds 汇总;U8 入库后置 QMREJECTVOUCHERS.BFLAG=1,订单行 QualifiedInQty 含不良品入库量。U8 只接受一次入库全部处理后数量(部分入库报「入库数量合计必须等于降级后数量」)。合格入库量达订单数量时 U8 自动关闭订单行(Status=4、CloseUser、CloseDate),之后删除入库单要先打开订单行
产成品入库 ← 生产订单 同上,表头 csource=生产订单,表体 impoids=订单行 MoDId、inquantity=订单数量,不写 icheckidbaks / ccheckcode QualifiedInQty 等于 rdrecords10 按 iMPoIds 的合计;ST.bOverMPIn=True 时允许超订单数量入库(上限看存货 fInExcess)。订单行 QcFlag=1 且 ST.bQuality=True 时须经产品检验单入库,桥 409。桥在同一事务里核对 QualifiedInQty 加 / 减本次数量。表头只写 cmpocode、不写 iproorderid / cpspcode 可以保存
材料出库(无来源) Insert("11"),表头 csource=库存、cbustype=领料、vt_id=65,表体不写 impoids 不回写子件。库存选项 ST.ballowAddnewVouch(U8 中「领料必有订单」)为 True 时 U8 拒绝无来源领料(USERPBO 资源 00690),桥先 409;为 False 时可无来源新增
两者删除 库存通用 9 参数 Delete U8 回退上述累计数
生产订单审核、弃审 登录子系统 MO。每次新建 UFIDA.U8.U8APIFramework.U8EnvContext(设 U8Login)和 U8ApiComBroker:Connect("U8API/MOrder/MOrderAuditing"或"MOrderUnauditing", env)、AssignNormalValue("mocode", 订单号)、InvokeApi()、GetLastError()、Disconnect() API 自己开事务,不要再包一层。GetLastError 取第一行、去掉 .NET 异常类型名。订单行状态看 mom_orderdetail.Status(3 审核,1/2 未审,4 关闭);重复审核 U8 只回「没有资料需要处理」,调用前自己判断
生产订单关闭、打开 没有 U8API(IB_AppTag 只有新增、审核、弃审、读取、修改、删除),与 U8 界面执行的 SQL 一致(实测核对):在请求连接的 CoTrans 里 CREATE TABLE #tmp_procmodid (MoDId int, Ufts varchar(30), ProcDate datetime, ErrFlag int, Errno int),每行插入 MoDId、convert(char, convert(money, Ufts), 2)(不去前导空格)、登录日期、1、1,再 EXEC Usp_MO_Close @ModifyDate, @ModifyUser, '<ALL>', '', '<ALL>', '<ALL>', '<ALL>', @v_closeflag 或 Usp_MO_UnClose(少最后一个参数),然后读每行 ErrFlag / Errno 存储过程见到 @@TRANCOUNT>0 不自己开事务,回滚有效。ErrFlag=0 成功,行 Status 3→4、写 CloseUser / CloseDate;打开 4→3、清空。Errno:101 登录日期所在月已做成本计算(CaF_IsCalculate,直接返回),102 有在制品(只在 @v_closeflag=0 时),0 不在权限串内(打开时恢复子件预留后预留量超过现存量也记 0),1 未处理(Ufts 不符)。@v_closeflag 缺省 1(允许关在制订单),桥传 0。@ModifyUser 是 varchar(20),被拼进动态 SQL 的字符串常量,含单引号的操作员桥直接拒绝。CloseUser 记操作员编码(与 RelsUser 一样),不是姓名。权限串传 <ALL>,权限由桥判断。建临时表那句不带参数(直接批处理),否则建在 sp_executesql 作用域里

生产订单审核需要 U8 的生产制造服务(U8MPool)在运行,否则 U8 报「连接到 IPC 端口失败」。U8MPool、U8SCMPool、U8TaskService 在某些管理操作(例如重设数据库 sa 口令)之后可能停下,要检查。

生产订单新增与删除#

U8 API 的行为(已在测试账套实测):

桥的做法(MoCreate、MoDelete):

生产订单修改#

MOrderUpdate / MOrderLoad 的行为(已在测试账套实测):

桥的做法(MoUpdate*):

已审核、已领料的订单#

MOrderUpdate 不查审核状态:已审核订单改数量后行 Status 仍为 3,子件全部重插(新 AllocateId)、按新行数量重算,已领量带到同一(行号、存货)的新子件;但材料出库行的 iMPoIds 仍指向旧 AllocateId,成了孤儿(之后删出库单时 U8 回退不了已领量)。U8 客户端的「变更」则直接修改已审核订单并同时改写出库行的子件关联。所以桥要自己改写出库行(MoUpdateRemap):

物料清单#

U8 API 的行为(已在测试账套实测)。桥的实现在 BomCom、BomCreate、BomEdit、BomState、BomRead。

桥的做法:

10. 总账#

记账#

取消记账(只限测试账套)#

红字冲销#

期间损益结转、自定义转账(只限测试账套)#

损益结转凭证的识别#

期初与附件#

11. 基础档案#

货位、计量单位、自定义项、客户存货对照#

计量单位组、结算方式、收发类别等#

币种、凭证类别、会计科目、汇率#

银行账户、联系人#

原因码#

固定资产卡片与设备台账#

12. 审批流#

审批数据全部在账套库里:

表 内容
Table_WorkFlowRelease 已发布的流程。Status = 0 为启用
Table_Task 待办。cTK_State 0 待办、2 已办、3 同节点他人已办、9 作废;cTaskType 1 审批、4 弃审后重审、5 退回后待重新提交
WF_ActiveFlow 流程实例。FlagCode 0 进行中、2 结束
WFAudit 历史。Action 0 提交、1 同意、2 不同意、5 撤销、6 退回、7 弃审、8 重新提交
单据表头 iVerifyStateNew(0 未提交、1 审批中、2 通过、-1 不通过)、IVERIFYSTATE、CVERIFIER、cCurrentAuditor
UserHrPersonContro 操作员与人员的对应。审批人配置的是人员,不是操作员
动作 调用
提交 UFLTMService.clsService 开事务,QMWorkFlowSrv.clsQMFinalVerifyPI.DoSubmit(biz, biz+".Submit", id, login, ufts, ref err),成功才提交
撤销提交 同一事务里 UndoSubmit(id, biz, login, ufts, ref err, code)
同意 AuditServiceProxy.Audit2(keySet, 1, 0, 意见, token, ref result)
不同意并继续 Audit2(keySet, 2, 2, …)
退回提交人 Audit2(keySet, 2, 0, …)
弃审 Abandon2(keySet, 意见, 0, token, ref result)
退回后重新提交 SubmitResubmitMessage2(keySet + ReSubmit, token, ref err)

keySet 是 <KeySet><Key name="VoucherId" value="…"/><Key name="VoucherType" value="QM04"/><Key name="VoucherCode" value="…"/></KeySet>,token 是登录对象的 userToken。审批代理和事务服务自己开连接和事务,不要再包桥的 ADO 事务。

审批引擎在 .NET 线程池里给 U8 移动端推送消息,所需程序集(YonYou.U8.MA.* 等,在 U8AuditWebSite\bin 和 U8AuditWebSite\bin\Query)在非 U8 进程里解析不到会结束进程。桥缺省拦下不推送;config.json 的 mobilePush 为 true 时从这两个目录加载、照常推送(见 architecture.md)。组件日志在 U8 安装目录的 Workflow\Logs。

判断单据是否走审批流:AuditBizObjects 里要有该表的行;Table_WorkFlowRelease 有该业务对象 Status = 0、事件为 <对象ID>.Submit 的发布,或单据的 iswfcontrolled 为真,就是启用。查不到按失败处理,不当成「没有启用」。

审批流终审留下的孤儿任务#

经桥做终审(最后一个节点的 Audit2 同意)或弃审时,U8 的 QM 终审插件在桥的进程里另开 QM 登录且从不注销:每次在 UFSYSTEM..UA_TaskLog 留下一到两行(cSub_Id='QM'、cStation 为本机),ua_Task_Common 可能也有对应行,在系统管理的任务列表里越积越多。点数按「工作站 × 模块」计,这些行不额外占加密点数,只是列表残留。

config.json 的 cleanOrphanTasks 为 true 时,桥在每次调到 U8 的审批流操作(提交、撤销提交、同意、不同意、退回、弃审、重新提交,含 U8 拒绝)后自动清理(TaskOrphans.cs):

  1. 串行:审批流操作在任何 U8 调用(含 IsFlowEnabled2)之前先取进程内的一把锁,清理结束才放。排队最多 60 秒,且不超过请求期限(入队起 75 秒)减 30 秒;看门狗已判定进程不健康时不再等。等不到在调 U8 之前返回 503 busy_timeout(「审批流操作排队中,请稍后重试」),什么都没写,不占幂等键,可以重发。
  2. 窗口:调 U8 前后各取一次 SQL Server 的 GETDATE() 作窗口起止,调用前快照 UA_TaskLog 里 cStation 为本机名(Environment.MachineName,等于桥的 SQL 会话的 HOST_NAME())的全部 cTaskId。快照失败(orphan_tasks_failed)、超过 2000 行(orphan_tasks_skipped,snapshot_too_large)或请求没有操作员、账套(no_operator)时本次不清理,锁照常持有。
  3. 候选:状态回读之后,cStation 为本机名、cSub_Id 在固定名单里(只有 QM)、cTaskId 不在快照里、dInTime 在窗口内的行。UA_TaskLog 没有操作员和账套列,所以再看 ua_Task_Common。
  4. 删除:在新连接上(SET DEADLOCK_PRIORITY LOW、SET LOCK_TIMEOUT 2000)开短事务,UPDLOCK, ROWLOCK(不加 HOLDLOCK)锁住候选在 UA_TaskLog 的全部行。同一任务号还有非 QM 的行时回滚(orphan_tasks_skipped,shared_task);锁到的行数不等于候选数时回滚(count_mismatch);ua_Task_Common 里有行而 cUser_Id / cAcc_Id 不是本次请求的操作员、账套的候选去掉(foreign_owner,count 为去掉的个数),没有行的保留;这些行超过 500 行时回滚(owner_rows_too_many);候选多于 10 个时不删(too_many)。通过后先删 ua_Task_Common(cStation + cTaskId),再删 UA_TaskLog(cStation + cSub_Id + cTaskId),删后仍有残留就回滚。
  5. 结果:清理从不影响请求结果。失败审计 orphan_tasks_failed(带 SQL 错误原文),清掉了审计 orphan_tasks_cleaned(数量和子系统)并在成功响应里加 orphan_tasks_cleaned。meta 的 features.clean_orphan_tasks 显示开关的实际值。

缺省关闭:清理要写 UFSystem,而同一个 cStation 也被服务器上的管理员和 U8 服务使用,窗口内有人在服务器上以同一操作员、同一账套登录 QM 时他的任务行会被误删,所以只在确认没人这样用时打开。打开后审批流操作串行执行。配置了 sql.json 时,专用登录要能读写 UFSYSTEM..UA_TaskLog 和 UFSYSTEM..ua_Task_Common。UA_TaskLog 只列登记过的任务,UFNet 仍持有的点数租约可能不在里面,按它统计站点数会偏少。

质量单据新增#

来料报检单(QM01)、产品报检单(QM02)、来料检验单(QM03)、产品检验单(QM04)的新增、删除(含删除前的报检单弃审)走 U8 质量管理的卡片组件,登录子系统 QM。

不良品处理单#

来料不良品处理单(QM05,VT 355)、产品不良品处理单(QM06,VT 356)的新增、审核、弃审、删除,登录子系统 QM。组件是基于 VO(UFQMVOCom.clsVoucherVO)的接口:QM05 UFQMCo.clsArrRejectCO、QM06 UFQMCo.clsProRejectCO。

其他报检单、其他检验单#

其他报检单(QM11,VT 361,卡片 QM_QOthInspect)、其他检验单(QM15,VT 365,卡片 QM_QOthCheck)与来料 / 产品报检单、检验单同表(按 CVOUCHTYPE 区分),CCHECKTYPECODE=OTH,没有来源单据(来源列为 NULL)、审批流和出入库单。检验单 INSPECTID / INSPECTAUTOID 指向报检单和表体行。

质检单修改#

来料检验单(QM03)、产品检验单(QM04)、其他检验单(QM15)、其他报检单(QM11)的修改(vouchers/update,桥 QmEdit / QmOthEdit),登录子系统 QM。产品报检单(QM02)和来料报检单不开放修改。

13. SQL#

经营管理报表的取数#

14. 事务行为汇总#

组件 在请求连接的事务里能回滚吗
销售、采购、库存 CO 的保存、审核、删除、关闭 能。但采购 VoucherSave2 有时会自行提交
UFAPBO SaveVouch、Sign、CancelSign、DeleteVouch 能;单号记录不退
UFAPBO clsPub_AP.Sign_PurBill、CancelSign_PurBill(Init 传请求连接) 能(采购、销售发票均已实测)
EAI 凭证导入、档案导入 不能,自己提交
U8 API 框架(生产订单审核、新增、修改、删除;物料清单新增、修改、删除、审核、弃审) 不能,自己开事务
质量管理卡片组件 VoucherOperate(报检单新增) 能。失败的新增不回滚会留下来源累计报检数
质量管理卡片组件 VoucherOperate(检验单新增、修改、删除;报检单删除、弃审) 不能,自己提交(修改时调用后 @@TRANCOUNT 为 1,但回滚撤不掉)
不良品处理单组件 AddVoucher、AuditVoucher、UnAuditVoucher、DelVoucher 不能,在 U8 自己的连接上提交
其他报检单、其他检验单组件(同族 VO 接口)AddVoucher、AuditVoucher、UnAuditVoucher、DelVoucher、UpdateVoucher 不能,按同族组件不包事务;UpdateVoucher 实测自己提交
采购结算 VoucherCO_PU.CheckSettle、bRdBVAutoSettle、结算单 Delete 能(bOutTrans=true)
采购手工结算(与 U8 界面执行的 SQL 一致:INSERT + PU_GetID + PU_SettleWriteBKRDS) 能(都在请求连接的事务里)
销售 CO 退货申请单(VT 34)的 Save、Delete、VerifyVouch 不能:转给 .NET 的 SaVoucherService 自己开、提交事务,不看 bManualTrans
销售 CO 红冲蓝字发票 GetNegaVouchData + Save 能(同其他销售发票)
总账记账(BalanceRule.VouchPostAll,桥外包 TransactionScope) 能(见「记账」)
总账取消记账(桥按记账公式反做的 SQL) 能(一个事务,锁同记账)
审批代理、UFLTMService 自己开连接和事务

UFSystem 里的登录记录、主键计数(UA_Identity)和开放接口令牌行不在账套库里,恢复账套库备份还原不了它们,只会留下主键断号。

15. 与 U8 客户端行为的对照#

以下行为已用 U8 自己的组件或接口在测试账套上对照: