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 不一致。
备注:内容仅供参考。
