SAP DI API:如何为多张发票添加一笔付款?
结论
Payments.Add() 只能调用一次,并且要放在 foreach 循环之后。
在循环中,应逐行填写 _pay.Invoices 集合,并调用 _pay.Invoices.Add() 添加发票行。当前代码每次循环都会调用 _pay.Add(),SAP Business One 因此会把每张发票保存成一笔单独的付款。
原因
DI API 中的两个 Add() 用途不同:
_pay.Invoices.Add():在当前付款对象中添加一条发票明细。_pay.Add():将整个付款对象提交到 SAP Business One。
调用顺序应为:
- 创建一个
Payments对象。 - 设置付款抬头和付款方式。
- 循环添加所有发票行。
- 循环结束后,只调用一次
_pay.Add()。
代码示例
以下示例假设:
pay.CardCode是付款对应的客户代码。i.AmountApplied是当前发票的核销金额,请替换为实际字段。inv.DocEntry是 SAP 发票的内部键。如果现有实体只有其他字段,需要先查询真正的DocEntry。- 所有发票都属于同一个业务伙伴。
Payments payment = (Payments)company.GetBusinessObject(
BoObjectTypes.oIncomingPayments);
payment.CardCode = pay.CardCode;
payment.DocDate = pay.DocDate;
payment.TaxDate = pay.TaxDate;
payment.DueDate = pay.DocDueDate;
// 建议设置 Series,让 SAP Business One 自动分配 DocNum。
payment.Series = 98;
payment.CashAccount = pay.CashAcct;
payment.CashSum = pay.CashSum;
int lineIndex = 0;
foreach (var detail in pay.Details)
{
InvoiceHeader inv = context.InvoiceHeaders
.FirstOrDefault(x => x.Id == detail.TransactionDocumentNo);
if (inv == null)
{
throw new InvalidOperationException(
$"未找到付款明细对应的发票:{detail.TransactionDocumentNo}");
}
if (lineIndex > 0)
{
payment.Invoices.Add();
}
payment.Invoices.SetCurrentLine(lineIndex);
payment.Invoices.DocEntry = Convert.ToInt32(inv.DocEntry);
payment.Invoices.InvoiceType = BoRcptInvTypes.it_Invoice;
payment.Invoices.SumApplied = detail.AmountApplied;
lineIndex++;
}
if (lineIndex == 0)
{
throw new InvalidOperationException("付款至少需要关联一张发票。");
}
// 整笔付款只提交一次。
int errCode = payment.Add();
if (errCode != 0)
{
company.GetLastError(out int sapErrorCode, out string sapErrorMessage);
throw new InvalidOperationException(
$"创建收款失败。SAP 错误 {sapErrorCode}:{sapErrorMessage}");
}
也可以先填写当前行,再调用 Invoices.Add(),但要注意不能在最后留下未填写的空白行。上面的写法只在添加第二张及后续发票前创建新行,处理起来更清楚。
每张发票应设置自己的核销金额
原代码在循环外执行:
_pay.Invoices.SumApplied = pay.TotalPaid;
_pay.Invoices.AppliedFC = pay.TotalPaid;
这样会把整笔付款金额写入第一条发票行。通常也不应同时填写本币和外币金额。应按每条明细分别赋值:
payment.Invoices.SumApplied = detail.AmountApplied;
如果付款和发票使用外币,而且当前场景确实需要填写外币核销金额,再使用:
payment.Invoices.AppliedFC = detail.AmountAppliedFC;
应根据公司本币、付款币种和发票币种,决定使用 SumApplied 还是 AppliedFC,不能无条件把同一金额同时写入两个字段。
注意事项
DocEntry 不能用 DocNum 代替
Payments.Invoices.DocEntry 需要的是发票内部键 DocEntry,不是用户界面中显示的单据编号 DocNum。
因此,下面的代码是否正确,要看 inv.DocumentNumber 实际保存的是什么:
payment.Invoices.DocEntry = Convert.ToInt32(inv.DocumentNumber);
如果 DocumentNumber 保存的是 OINV.DocNum,必须先查询对应的 OINV.DocEntry。例如:
SELECT DocEntry
FROM OINV
WHERE DocNum = 12345
AND Series = 10
在使用多个编号系列的环境中,只按 DocNum 查询可能无法唯一确定发票。
不要直接读取 NNM1.NextNumber
不建议先查询 NNM1.NextNumber,再手动设置 DocNum:
select NextNumber from NNM1 where Series = 98
并发提交时,多个进程可能读到相同的号码。通常只需设置:
payment.Series = 98;
单据编号会由 SAP Business One 在执行 payment.Add() 时分配。
所有关联发票必须属于同一业务伙伴
一笔收款通常只能对应一个客户,因此所有发票的 CardCode 都应与下面的值一致:
payment.CardCode
如果明细中包含不同客户的发票,需要先按客户分组,再为每个客户分别创建一笔付款。
付款金额必须与付款方式匹配
所有发票行的核销金额合计,应与现金、转账、支票或信用卡等付款方式的金额保持合理关系。例如,只使用现金付款时,通常应满足:
CashSum = 所有发票行的 SumApplied 合计
如果涉及折扣、预付款、差额、外币汇率或多种付款方式,还需要根据实际业务设置相应字段。
需要修改的核心部分是:在循环中调用 payment.Invoices.Add() 管理发票行,循环结束后只调用一次 payment.Add(),提交整笔付款。
备注:内容仅供参考。
