PAYATHON 2026

Power BI 中按 Invoice Date 计算 Amount to be paid

社区小冯

结论

将 Amount to be paid 建为计算列,按以下区间汇总同一 Contract 的 value:

上一个 Invoice Date(含)<= Date < 当前 Invoice Date

如果不存在上一个开票日期,就汇总当前开票日期之前的所有月份。本次金额不包含当前开票月份。

Amount to be paid =
VAR CurrentContract = 'Data'[Contract]
VAR CurrentInvoiceDate = 'Data'[Invoice Date]
VAR PreviousInvoiceDate =
    MAXX(
        FILTER(
            ALL('Data'),
            'Data'[Contract] = CurrentContract
                && NOT ISBLANK('Data'[Invoice Date])
                && 'Data'[Invoice Date] < CurrentInvoiceDate
        ),
        'Data'[Invoice Date]
    )
RETURN
IF(
    ISBLANK(CurrentInvoiceDate),
    BLANK(),
    CALCULATE(
        SUM('Data'[value]),
        FILTER(
            ALL('Data'),
            'Data'[Contract] = CurrentContract
                && 'Data'[Date] < CurrentInvoiceDate
                && (
                    ISBLANK(PreviousInvoiceDate)
                    || 'Data'[Date] >= PreviousInvoiceDate
                )
        )
    )
)

请将 'Data' 替换为实际表名。

计算逻辑

移除当前行的筛选后,PreviousInvoiceDate 会查找满足以下条件的记录:

  • Contract 与当前行相同;
  • Invoice Date 不为空;
  • Invoice Date 早于当前开票日期。

然后从这些记录中取最大的 Invoice Date,也就是该合同上一次开票的日期。

金额计算使用两个边界:

'Data'[Date] < CurrentInvoiceDate

这个条件用于排除当前开票月份。

'Data'[Date] >= PreviousInvoiceDate

这个条件表示从上一次开票所在月份开始累计。如果没有上一次开票日期,则不设置下限,从最早的月份开始计算。

根据题目中的数据,结果如下:

  • Contract 1 在 01/03/2023 开票:380 + 1000 = 1380
  • Contract 1 在 01/05/2023 开票:550 + 950 = 1500
  • Contract 2 在 01/02/2023 开票:150
  • Contract 2 在 01/03/2023 开票:350

如果第一次开票日期之前没有任何月份,SUM 会返回空值。因此,Contract 1 在 01/01/2023 的结果为空。

为什么直接汇总 value 更合适

同一个月有多条明细时,Monthly total 会重复出现。例如,Contract 1 在 01/01/2023 有两行,两行的 Monthly total 都是 380。如果直接执行:

SUM('Data'[Monthly total])

结果会变成 380 + 380 = 760,导致重复计算。

直接汇总明细列可以避开这个问题:

SUM('Data'[value])

只要每条明细只出现一次,就能正确算出每个月及各开票区间的总金额。

如果业务上必须使用 Monthly total,需要先按合同和月份去重:

Amount to be paid by Monthly Total =
VAR CurrentContract = 'Data'[Contract]
VAR CurrentInvoiceDate = 'Data'[Invoice Date]
VAR PreviousInvoiceDate =
    MAXX(
        FILTER(
            ALL('Data'),
            'Data'[Contract] = CurrentContract
                && NOT ISBLANK('Data'[Invoice Date])
                && 'Data'[Invoice Date] < CurrentInvoiceDate
        ),
        'Data'[Invoice Date]
    )
VAR MonthsToInvoice =
    FILTER(
        ALL('Data'),
        'Data'[Contract] = CurrentContract
            && 'Data'[Date] < CurrentInvoiceDate
            && (
                ISBLANK(PreviousInvoiceDate)
                || 'Data'[Date] >= PreviousInvoiceDate
            )
    )
RETURN
IF(
    ISBLANK(CurrentInvoiceDate),
    BLANK(),
    SUMX(
        SUMMARIZE(
            MonthsToInvoice,
            'Data'[Contract],
            'Data'[Date],
            "MonthAmount", MAX('Data'[Monthly total])
        ),
        [MonthAmount]
    )
)

注意事项

日期列必须是日期类型

Date 和 Invoice Date 都应设为 Date 或 Date/Time 类型,不能使用文本类型。否则,<、>= 等比较可能按字符串顺序执行,结果并不可靠。

如果列中保存的是月初日期,例如用 01/03/2023 表示 2023 年 3 月,上面的公式可以直接使用。如果列中包含具体日期,但业务规则按整月结算,则需要先建立统一的月份字段,例如:

MonthStart =
DATE(
    YEAR('Data'[Date]),
    MONTH('Data'[Date]),
    1
)

开票日期也要转换到相同的月份粒度,再进行比较。

结果会在开票月份的多行中重复

如果同一个开票月份包含多条明细,计算列会在这些行中显示相同的金额。例如,1380 会同时出现在 Contract 1 的两条 01/03/2023 记录中。这与示例结果一致,但在可视化中不能对该列使用 Sum,否则金额会被重复累计。

可以将汇总方式设为:

Don't summarize

也可以在可视化中使用 MAX:

Displayed Amount to be paid =
MAX('Data'[Amount to be paid])

更规范的建模方式是单独建立一张开票表,确保每个 Contract + Invoice Date 只有一行,再把应付金额放到这张表中。

区间边界取决于业务规则

这里使用的区间是:

PreviousInvoiceDate <= Date < CurrentInvoiceDate

这个规则来自示例:3 月开票包含 1 月,5 月开票包含 3 月。

如果实际业务规定上一次开票月份不能再次计入,应将下限改为严格大于:

'Data'[Date] > PreviousInvoiceDate

不过,这样计算后,Contract 1 在 3 月开票时只会得到 2 月的 1000,与示例中的 1380 不一致。

备注:内容仅供参考。