This commit is contained in:
wuweihua
2026-11-11 11:30:22 +08:00
7 changed files with 229 additions and 47 deletions
@@ -36,6 +36,10 @@ public interface ArBillMapper {
int insertArBill(ArBill bill);
int updateProfitSnapshot(@Param("arBillId") Long arBillId,
@Param("shenzhenArea") String shenzhenArea,
@Param("guangzhouArea") String guangzhouArea);
int updateArBillStatus(@Param("arBillId") Long arBillId, @Param("status") String status,
@Param("remark") String remark, @Param("operator") String operator,
@Param("fromStatus") String fromStatus);
@@ -935,6 +935,12 @@ public class ArBillServiceImpl implements IArBillService {
throw new ServiceException("账单生成结果为空或订单数量发生变化,请重新生成");
}
ArBill finalBill = billMapper.selectArBillById(task.getArBillId());
// 明细与费用已全部落库;快照和 READY 状态必须在同一事务中提交。
if (billMapper.updateProfitSnapshot(task.getArBillId(),
com.bms.price.enums.ProfitAreaRule.SHENZHEN.getCustomerArea(),
com.bms.price.enums.ProfitAreaRule.GUANGZHOU.getCustomerArea()) != 1) {
throw new ServiceException("账单毛利快照更新失败,请重新生成");
}
billMapper.updateArBillGenerationStatus(task.getArBillId(),
ArBillGenerationStatus.READY, task.getCreateBy(), null);
if (generateTaskMapper.markSuccess(taskId, new Date()) != 1) {
@@ -193,6 +193,49 @@
#{confirmedBy}, #{confirmedTime}, #{createBy}, #{createUserId}, sysdate(), #{updateBy}, sysdate())
</insert>
<!-- 只在生成完成时汇总当前账单;历史回填使用相同的校验口径。 -->
<update id="updateProfitSnapshot">
update ar_bill b
left join (
select d.ar_bill_id, count(*) as detail_count, sum(d.total_amount) as detail_total,
count(distinct d.currency) as detail_currency_count, min(d.currency) as detail_currency,
sum(case when d.currency is not null and f.currency_count = 1 and f.fee_currency = d.currency
then 0 else 1 end) as currency_errors,
sum(case when d.total_amount is not null and f.sales_amount is not null
and f.sales_amount = d.total_amount then 0 else 1 end) as amount_errors,
case when count(f.product_amount) = count(*) then sum(f.product_amount) else null end as product_total,
case when count(f.redline_amount) = count(*) then sum(f.redline_amount) else null end as redline_total
from ar_bill_detail d
left join (
select fee.detail_id,
case when count(fee.amount) = count(*) then sum(fee.amount) else null end as sales_amount,
case when count(fee.currency) = count(*) then count(distinct fee.currency) else 0 end as currency_count,
min(fee.currency) as fee_currency,
case when count(fee.product_amount) = count(*) then sum(fee.product_amount) else null end as product_amount,
case when count(fee.redline_amount) = count(*) then sum(fee.redline_amount) else null end as redline_amount
from ar_bill_detail_fee fee
join ar_bill_detail fd on fd.detail_id = fee.detail_id
where fd.ar_bill_id = #{arBillId}
group by fee.detail_id
) f on f.detail_id = d.detail_id
where d.ar_bill_id = #{arBillId}
group by d.ar_bill_id
) a on a.ar_bill_id = b.ar_bill_id
set b.product_amount = case when abs(a.product_total) &lt;= 99999999999999.9999 then a.product_total else null end,
b.redline_amount = case when abs(a.redline_total) &lt;= 99999999999999.9999 then a.redline_total else null end,
b.profit_calc_status = case
when a.detail_count is null or a.detail_count = 0 then 'NO_DETAIL'
when b.profit_customer_area is null or b.profit_customer_area not in (#{shenzhenArea}, #{guangzhouArea}) then 'MISSING_AREA'
when b.currency is null or a.currency_errors > 0 or a.detail_currency_count != 1 or a.detail_currency != b.currency then 'CURRENCY_MISMATCH'
when b.total_amount is null or a.amount_errors > 0 or a.detail_total != b.total_amount then 'AMOUNT_MISMATCH'
when (b.profit_customer_area = #{shenzhenArea} and a.product_total is null)
or (b.profit_customer_area = #{guangzhouArea} and a.redline_total is null) then 'MISSING_PRICE'
when (b.profit_customer_area = #{shenzhenArea} and abs(a.product_total) > 99999999999999.9999)
or (b.profit_customer_area = #{guangzhouArea} and abs(a.redline_total) > 99999999999999.9999) then 'PRICE_OVERFLOW'
else 'READY' end
where b.ar_bill_id = #{arBillId} and b.generation_status = 'GENERATING'
</update>
<update id="updateArBillStatus">
update ar_bill
set status = #{status},
@@ -60,7 +60,16 @@
<if test="query.endBillDate != null">and b.bill_date &lt;= #{query.endBillDate}</if>
</sql>
<!-- 按费用快照先聚合到订单,再聚合到完整账单,避免账单金额及费用重复。 -->
<!-- 报表直接读取账单快照。 -->
<sql id="validProfitSnapshot">
b.profit_calc_status = 'READY' and b.total_amount is not null
and ((b.profit_customer_area = #{shenzhenArea} and b.product_amount is not null)
or (b.profit_customer_area = #{guangzhouArea} and b.redline_amount is not null))
</sql>
<sql id="areaProductAmount">
case when b.profit_customer_area = #{shenzhenArea} then b.product_amount
when b.profit_customer_area = #{guangzhouArea} then b.redline_amount else null end
</sql>
<sql id="billRows">
<bind name="shenzhenArea" value="@com.bms.price.enums.ProfitAreaRule@SHENZHEN.getCustomerArea()"/>
<bind name="guangzhouArea" value="@com.bms.price.enums.ProfitAreaRule@GUANGZHOU.getCustomerArea()"/>
@@ -68,45 +77,12 @@
b.profit_customer_area as customer_area,
b.profit_sales_user_name as sales_user_name, b.currency, b.status,
b.total_amount as sales_amount, b.written_off_amount, b.unwritten_amount,
case when a.detail_count > 0 and a.invalid_count = 0 and b.total_amount is not null
and a.detail_total = b.total_amount then a.product_total else null end as product_amount,
case when a.detail_count > 0 and a.invalid_count = 0 and b.total_amount is not null
and a.detail_total = b.total_amount then b.total_amount - a.product_total else null end as sales_gross_profit,
case when a.detail_count > 0 and a.invalid_count = 0 and b.total_amount is not null
and a.detail_total = b.total_amount and b.total_amount != 0
then round((b.total_amount - a.product_total) * 100 / b.total_amount, 4) else null end as sales_gross_margin_rate,
case when a.detail_count > 0 and a.invalid_count = 0 and b.total_amount is not null
and a.detail_total = b.total_amount then 0 else 1 end as profit_incomplete
case when <include refid="validProfitSnapshot"/> then <include refid="areaProductAmount"/> else null end as product_amount,
case when <include refid="validProfitSnapshot"/> then b.total_amount - (<include refid="areaProductAmount"/>) else null end as sales_gross_profit,
case when <include refid="validProfitSnapshot"/> and b.total_amount != 0
then round((b.total_amount - (<include refid="areaProductAmount"/>)) * 100 / b.total_amount, 4) else null end as sales_gross_margin_rate,
case when <include refid="validProfitSnapshot"/> then 0 else 1 end as profit_incomplete
from ar_bill b
left join (
select d.ar_bill_id, count(*) as detail_count, sum(d.total_amount) as detail_total,
sum(case when d.total_amount is not null and d.currency is not null and b.currency is not null
and d.currency = b.currency and f.currency_count = 1 and f.fee_currency = d.currency
and f.sales_amount is not null and f.sales_amount = d.total_amount
and ((b.profit_customer_area = #{shenzhenArea} and f.product_complete = 1)
or (b.profit_customer_area = #{guangzhouArea} and f.redline_complete = 1))
then 0 else 1 end) as invalid_count,
sum(case when b.profit_customer_area = #{shenzhenArea} then f.product_amount
when b.profit_customer_area = #{guangzhouArea} then f.redline_amount else null end) as product_total
from ar_bill_detail d join ar_bill b on b.ar_bill_id = d.ar_bill_id
left join (
select fee.detail_id,
case when count(fee.amount) = count(*) then sum(fee.amount) else null end as sales_amount,
case when count(fee.currency) = count(*) then count(distinct fee.currency) else 0 end as currency_count,
min(fee.currency) as fee_currency,
case when count(fee.product_amount) = count(*) then sum(fee.product_amount) else null end as product_amount,
case when count(fee.redline_amount) = count(*) then sum(fee.redline_amount) else null end as redline_amount,
case when count(fee.product_amount) = count(*) then 1 else 0 end as product_complete,
case when count(fee.redline_amount) = count(*) then 1 else 0 end as redline_complete
from ar_bill_detail_fee fee
join ar_bill_detail d on d.detail_id = fee.detail_id
join ar_bill b on b.ar_bill_id = d.ar_bill_id
<include refid="billConditions"/>
group by fee.detail_id
) f on f.detail_id = d.detail_id
<include refid="billConditions"/>
group by d.ar_bill_id
) a on a.ar_bill_id = b.ar_bill_id
<include refid="billConditions"/>
</sql>
<select id="selectBills" resultMap="row">