|
|
@@ -5,10 +5,13 @@ import com.actionsoft.bpms.bpmn.engine.model.run.delegate.ProcessInstance;
|
|
|
import com.actionsoft.bpms.commons.database.RowMap;
|
|
|
import com.actionsoft.bpms.server.UserContext;
|
|
|
import com.actionsoft.bpms.util.DBSql;
|
|
|
+import com.actionsoft.bpms.util.TypeUtil;
|
|
|
import com.actionsoft.sdk.local.SDK;
|
|
|
import com.actionsoft.sdk.local.api.Logger;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.joda.time.LocalDate;
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.ArrayList;
|
|
|
@@ -40,14 +43,16 @@ public class InvoiceService {
|
|
|
}
|
|
|
|
|
|
// ====================== 1. 核心入口方法:处理发票生成 ======================
|
|
|
- public List<String> processInvoice(UserContext uc, String accDeduIds, InvoiceDealDto param, boolean isDeal) {
|
|
|
+ public String processInvoice(UserContext uc, String accDeduIds, InvoiceDealDto param, boolean isDeal) {
|
|
|
|
|
|
logger.info("处理发票生成");
|
|
|
logger.info("accDeduIds:" + accDeduIds);
|
|
|
logger.info("日期范围:" + param.getDateRangeFrom() + "--" + param.getDateRangeTo());
|
|
|
|
|
|
// 1. 基础校验:空值过滤
|
|
|
- if (StringUtils.isBlank(accDeduIds)) return new ArrayList<>();
|
|
|
+ if (StringUtils.isBlank(accDeduIds))
|
|
|
+ return "参数错误";
|
|
|
+
|
|
|
String[] dedIdArr = accDeduIds.split(",");
|
|
|
|
|
|
StringBuilder where = new StringBuilder("(");
|
|
|
@@ -57,15 +62,12 @@ public class InvoiceService {
|
|
|
String sqlWhere = where.toString(); // 复用:构建IN条件
|
|
|
|
|
|
// 2. 初始化基础数据(3张核心表查询)
|
|
|
- List<RowMap> postedDataList = DBSql.getMaps(
|
|
|
- "SELECT * FROM VIEW_EU_DNV_POSTED_ALL d WHERE d.ID IN " + sqlWhere + " AND (d.invoice_id is null OR d.invoice_id='' OR NOT EXISTS(SELECT 1 FROM bo_eu_dnctt_invoice i WHERE i.id=d.invoice_id AND IS_VOIDED=0)) AND d.bill_account_id is NOT NULL AND d.ORGID='" + uc.getCompanyModel().getId() + "' ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID",
|
|
|
- dedIdArr
|
|
|
- );
|
|
|
+ List<RowMap> postedDataList = DBSql.getMaps("SELECT * FROM VIEW_EU_DNV_POSTED_ALL d WHERE d.ID IN " + sqlWhere + " AND (d.invoice_id is null OR d.invoice_id='' OR NOT EXISTS(SELECT 1 FROM bo_eu_dnctt_invoice i WHERE i.id=d.invoice_id AND IS_VOIDED=0)) AND d.bill_account_id is NOT NULL AND d.ORGID='" + uc.getCompanyModel().getId() + "' ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID", dedIdArr);
|
|
|
|
|
|
logger.info("账单个数:" + postedDataList.size());
|
|
|
|
|
|
- if (postedDataList.isEmpty())
|
|
|
- return null;
|
|
|
+ if (postedDataList.isEmpty() || postedDataList.size() != dedIdArr.length)
|
|
|
+ return "所选条目有账单状态必须为未生成生成账单";
|
|
|
|
|
|
// 统一Key为大写,避免字段名大小写不一致导致的NPE
|
|
|
postedDataList.forEach(row -> row.forEach((key, value) -> {
|
|
|
@@ -80,20 +82,44 @@ public class InvoiceService {
|
|
|
}
|
|
|
}));
|
|
|
|
|
|
- List<RowMap> accountDedList = DBSql.getMaps(
|
|
|
- "SELECT * FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE ID IN " + sqlWhere + " ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID",
|
|
|
- dedIdArr
|
|
|
- );
|
|
|
- List<RowMap> itemTypeDict = DBSql.getMaps(
|
|
|
- "SELECT OID,NAME FROM BO_EU_DND_GENERAL WHERE GENERAL_TABLE_ID='121' AND ORGID=?", new Object[]{uc.getCompanyModel().getId()}
|
|
|
- );
|
|
|
+ //ITEM_DATE i) 弹出窗口输入账单起止日期,保存时需要校验所选条目是否在账单起止日期内
|
|
|
+ // ii) 批量选择条目:计费客户相同、状态必须为----未生成生成账单
|
|
|
+
|
|
|
+ String BILL_ACCOUNT_ID = null;
|
|
|
+ for (RowMap row : postedDataList) {
|
|
|
+ String ITEM_DATE = row.getString("ITEM_DATE");
|
|
|
+ if (StringUtils.isNotBlank(ITEM_DATE)) {
|
|
|
+ LocalDate itemDate = TypeUtil.convert(ITEM_DATE, LocalDate.class);
|
|
|
+ //i) 弹出窗口输入账单起止日期,保存时需要校验所选条目是否在账单起止日期内
|
|
|
+ if (itemDate.isBefore(param.getDateRangeFrom()) || itemDate.isAfter(param.getDateRangeTo())) {
|
|
|
+ return "所选条目有账单起止日期不符合要求";
|
|
|
+ }
|
|
|
+
|
|
|
+ // ii) 批量选择条目:计费客户相同、状态必须为----未生成生成账单
|
|
|
+ if (StringUtils.isBlank(BILL_ACCOUNT_ID))
|
|
|
+ BILL_ACCOUNT_ID = row.getString("BILL_ACCOUNT_ID");
|
|
|
+ else {
|
|
|
+ if (!BILL_ACCOUNT_ID.equals(row.getString("BILL_ACCOUNT_ID"))) {
|
|
|
+ return "所选条目有账单客户必须相同";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //RECEIVE_STATUS 4661 未生成生成账单
|
|
|
+ String RECEIVE_STATUS = row.getString("RECEIVE_STATUS");
|
|
|
+ if (RECEIVE_STATUS.equals("4661") == false)
|
|
|
+ return "所选条目有账单状态必须为未生成生成账单";
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ List<RowMap> accountDedList = DBSql.getMaps("SELECT * FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE ID IN " + sqlWhere + " ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID", dedIdArr);
|
|
|
+ List<RowMap> itemTypeDict = DBSql.getMaps("SELECT OID,NAME FROM BO_EU_DND_GENERAL WHERE GENERAL_TABLE_ID='121' AND ORGID=?", new Object[]{uc.getCompanyModel().getId()});
|
|
|
|
|
|
// 3. 按「账单客户ID」分组抵扣数据(核心分组逻辑)
|
|
|
- Map<String, List<RowMap>> customerDedMap = accountDedList.stream()
|
|
|
- .filter(ded -> ded.get("BILL_ACCOUNT_ID") != null)
|
|
|
- .collect(Collectors.groupingBy(ded -> ded.get("BILL_ACCOUNT_ID").toString()));
|
|
|
- if (customerDedMap.isEmpty())
|
|
|
- return null;
|
|
|
+ Map<String, List<RowMap>> customerDedMap = accountDedList.stream().filter(ded -> ded.get("BILL_ACCOUNT_ID") != null).collect(Collectors.groupingBy(ded -> ded.get("BILL_ACCOUNT_ID").toString()));
|
|
|
+ if (customerDedMap.isEmpty()) return null;
|
|
|
+
|
|
|
|
|
|
// 4. 循环处理每个客户的发票(拆分「有无采购订单」逻辑)
|
|
|
List<String> invoiceIds = new ArrayList<>();
|
|
|
@@ -108,43 +134,29 @@ public class InvoiceService {
|
|
|
}
|
|
|
|
|
|
// 4.2 处理「无采购订单」的发票
|
|
|
- List<RowMap> noPoDedList = customerDedList.stream()
|
|
|
- .filter(ded -> StringUtils.isBlank(ded.getString("PURCHASE_ORDER_NO")))
|
|
|
- .collect(Collectors.toList());
|
|
|
+ List<RowMap> noPoDedList = customerDedList.stream().filter(ded -> StringUtils.isBlank(ded.getString("PURCHASE_ORDER_NO"))).collect(Collectors.toList());
|
|
|
if (!noPoDedList.isEmpty()) {
|
|
|
- List<RowMap> noPoPostedList = postedDataList.stream()
|
|
|
- .filter(p -> StringUtils.isBlank(p.getString("PURCHASE_ORDER_NO")))
|
|
|
- .filter(p -> ACCOUNT_ID.equals(p.get("ACCOUNT_ID")) || ACCOUNT_ID.equals(p.get("BILL_ACCOUNT_ID")))
|
|
|
- .collect(Collectors.toList());
|
|
|
- String noPoInvoiceId = createInvoiceAndDetails(
|
|
|
- uc, customer, noPoPostedList, noPoDedList, itemTypeDict, param, isDeal, ""
|
|
|
- );
|
|
|
+ List<RowMap> noPoPostedList = postedDataList.stream().filter(p -> StringUtils.isBlank(p.getString("PURCHASE_ORDER_NO"))).filter(p -> ACCOUNT_ID.equals(p.get("ACCOUNT_ID")) || ACCOUNT_ID.equals(p.get("BILL_ACCOUNT_ID"))).collect(Collectors.toList());
|
|
|
+ String noPoInvoiceId = createInvoiceAndDetails(uc, customer, noPoPostedList, noPoDedList, itemTypeDict, param, isDeal, "");
|
|
|
if (StringUtils.isNotBlank(noPoInvoiceId)) {
|
|
|
invoiceIds.add(noPoInvoiceId);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 4.3 处理「有采购订单」的发票(按PO分组)
|
|
|
- Map<String, List<RowMap>> poDedMap = customerDedList.stream()
|
|
|
- .filter(ded -> StringUtils.isNotBlank(ded.getString("PURCHASE_ORDER_NO")))
|
|
|
- .collect(Collectors.groupingBy(ded -> ded.getString("PURCHASE_ORDER_NO")));
|
|
|
+ Map<String, List<RowMap>> poDedMap = customerDedList.stream().filter(ded -> StringUtils.isNotBlank(ded.getString("PURCHASE_ORDER_NO"))).collect(Collectors.groupingBy(ded -> ded.getString("PURCHASE_ORDER_NO")));
|
|
|
for (Map.Entry<String, List<RowMap>> poEntry : poDedMap.entrySet()) {
|
|
|
String poNo = poEntry.getKey();
|
|
|
List<RowMap> poDedList = poEntry.getValue();
|
|
|
- List<RowMap> poPostedList = postedDataList.stream()
|
|
|
- .filter(p -> poNo.equals(p.getString("PURCHASE_ORDER_NO")))
|
|
|
- .filter(p -> ACCOUNT_ID.equals(p.get("ACCOUNT_ID")) || ACCOUNT_ID.equals(p.get("BILL_ACCOUNT_ID")))
|
|
|
- .collect(Collectors.toList());
|
|
|
- String poInvoiceId = createInvoiceAndDetails(
|
|
|
- uc, customer, poPostedList, poDedList, itemTypeDict, param, isDeal, poNo
|
|
|
- );
|
|
|
+ List<RowMap> poPostedList = postedDataList.stream().filter(p -> poNo.equals(p.getString("PURCHASE_ORDER_NO"))).filter(p -> ACCOUNT_ID.equals(p.get("ACCOUNT_ID")) || ACCOUNT_ID.equals(p.get("BILL_ACCOUNT_ID"))).collect(Collectors.toList());
|
|
|
+ String poInvoiceId = createInvoiceAndDetails(uc, customer, poPostedList, poDedList, itemTypeDict, param, isDeal, poNo);
|
|
|
if (StringUtils.isNotBlank(poInvoiceId)) {
|
|
|
invoiceIds.add(poInvoiceId);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
logger.info("发票ID:" + invoiceIds.toString());
|
|
|
- return invoiceIds;
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -156,33 +168,21 @@ public class InvoiceService {
|
|
|
summary.totalTax = postedList.stream().mapToDouble(p -> p.getDouble("TAX_DOLLARS")).sum();
|
|
|
summary.totalPrice = summary.totalMoney - summary.totalTax;
|
|
|
// 工时汇总(不计费、计费、预付)
|
|
|
- summary.noBillHours = postedList.stream()
|
|
|
- .filter(p -> StringUtils.isBlank(p.getString("BILLABLE")))
|
|
|
- .mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
|
|
|
- summary.billHours = postedList.stream()
|
|
|
- .filter(p -> StringUtils.isNotBlank(p.getString("BILLABLE")))
|
|
|
- .mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
|
|
|
- summary.prepaidHours = postedList.stream()
|
|
|
- .filter(p -> StringUtils.isNotBlank(p.getString("CONTRACT_BLOCK_ID")))
|
|
|
- .mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
|
|
|
+ summary.noBillHours = postedList.stream().filter(p -> StringUtils.isBlank(p.getString("BILLABLE"))).mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
|
|
|
+ summary.billHours = postedList.stream().filter(p -> StringUtils.isNotBlank(p.getString("BILLABLE"))).mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
|
|
|
+ summary.prepaidHours = postedList.stream().filter(p -> StringUtils.isNotBlank(p.getString("CONTRACT_BLOCK_ID"))).mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
|
|
|
return summary;
|
|
|
}
|
|
|
|
|
|
// ====================== 5. 核心复用方法4:创建发票头+明细+更新抵扣 ======================
|
|
|
- private String createInvoiceAndDetails(UserContext uc, RowMap customer,
|
|
|
- List<RowMap> postedList, List<RowMap> dedList, List<RowMap> itemTypeDict,
|
|
|
- InvoiceDealDto param, boolean isDeal, String poNo) {
|
|
|
+ private String createInvoiceAndDetails(UserContext uc, RowMap customer, List<RowMap> postedList, List<RowMap> dedList, List<RowMap> itemTypeDict, InvoiceDealDto param, boolean isDeal, String poNo) {
|
|
|
try {
|
|
|
// 1. 计算汇总数据
|
|
|
InvoiceSummary summary = calculateSummary(postedList);
|
|
|
|
|
|
// 2. 生成发票编号+创建流程实例
|
|
|
String invoiceNo = SDK.getRuleAPI().executeAtScript("B@left(@date,4)@mid(@date,6,2).@mid(@sequenceMonth(@companyId_invoice,4,0,1),7)", uc);
|
|
|
- ProcessInstance pi = SDK.getProcessAPI().createProcessInstance(
|
|
|
- "obj_a7d46a9dd4df40b899ddcf02113fe0d2",
|
|
|
- uc.getUID(),
|
|
|
- "新增账单" + invoiceNo
|
|
|
- );
|
|
|
+ ProcessInstance pi = SDK.getProcessAPI().createProcessInstance("obj_a7d46a9dd4df40b899ddcf02113fe0d2", uc.getUID(), "新增账单" + invoiceNo);
|
|
|
|
|
|
// 3. 构建并保存发票头
|
|
|
BO invoice = new BO();
|
|
|
@@ -203,8 +203,7 @@ public class InvoiceService {
|
|
|
invoice.set("PURCHASE_ORDER_NO", poNo); // 有PO则赋值,无PO为空
|
|
|
invoice.set("NOTES", param.getNotes());
|
|
|
// 税区名称查询
|
|
|
- String taxRegionName = StringUtils.isBlank(customer.getString("TAX_REGION_ID")) ? "" :
|
|
|
- DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ?", new Object[]{customer.getString("TAX_REGION_ID")});
|
|
|
+ String taxRegionName = StringUtils.isBlank(customer.getString("TAX_REGION_ID")) ? "" : DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ?", new Object[]{customer.getString("TAX_REGION_ID")});
|
|
|
invoice.set("TAX_REGION_NAME", taxRegionName);
|
|
|
invoice.setBindId(pi.getId());
|
|
|
|
|
|
@@ -215,8 +214,7 @@ public class InvoiceService {
|
|
|
dn.recordFormChanges.record(uc, invoice, "保存发票:" + invoiceNo);
|
|
|
|
|
|
// 4. 构建并保存发票明细(批量处理)
|
|
|
- Map<String, RowMap> dedIdMap = dedList.stream()
|
|
|
- .collect(Collectors.toMap(ded -> ded.getString("ID"), ded -> ded));
|
|
|
+ Map<String, RowMap> dedIdMap = dedList.stream().collect(Collectors.toMap(ded -> ded.getString("ID"), ded -> ded));
|
|
|
int lineNo = 1;
|
|
|
for (RowMap posted : postedList) {
|
|
|
String dedId = posted.getString("ID");
|
|
|
@@ -231,12 +229,16 @@ public class InvoiceService {
|
|
|
detail.set("RESOURCE_NAME", posted.get("RESOURCE_NAME"));
|
|
|
detail.set("QUANTITY", posted.get("QUANTITY"));
|
|
|
detail.set("RATE", posted.get("RATE"));
|
|
|
- detail.set("DOLLARS", posted.get("DOLLARS"));
|
|
|
+ detail.set("DOLLARS", posted.get("DOLLARS"));//含税总价
|
|
|
+ detail.set("TAX_DOLLARS", posted.get("TAX_DOLLARS"));//税额
|
|
|
+
|
|
|
+ BigDecimal DOLLARS_NOTAX =(posted.get("DOLLARS")==null?BigDecimal.ZERO: TypeUtil.convert(posted.get("DOLLARS"), BigDecimal.class)).subtract((posted.get("TAX_DOLLARS")==null?BigDecimal.ZERO: TypeUtil.convert(posted.get("TAX_DOLLARS"), BigDecimal.class)));
|
|
|
+
|
|
|
+ detail.set("DOLLARS_NOTAX", DOLLARS_NOTAX);//不含税总价
|
|
|
+
|
|
|
detail.set("HOURLY_RATE", posted.get("HOURLY_RATE"));
|
|
|
detail.set("ROLE_NAME", posted.get("ROLE_NAME"));
|
|
|
detail.set("WORK_TYPE", posted.get("WORK_TYPE"));
|
|
|
- detail.set("TAX_DOLLARS", posted.get("TAX_DOLLARS"));
|
|
|
-
|
|
|
detail.set("BILL_ACCOUNT_ID", ded.getString("BILL_ACCOUNT_ID"));//计费客户
|
|
|
detail.set("ACCOUNT_ID", ded.getString("ACCOUNT_ID"));//客户
|
|
|
detail.set("ACCOUNT_DEDUCTION_ID", dedId);
|
|
|
@@ -246,19 +248,14 @@ public class InvoiceService {
|
|
|
String billableHours = posted.getString("BILLABLE_HOURS");
|
|
|
if ("1199".equals(contractTypeId) || "1201".equals(contractTypeId)) {
|
|
|
billableHours = "合同已包";
|
|
|
- } else if (("1202".equals(contractTypeId) || "1203".equals(contractTypeId))
|
|
|
- && StringUtils.isNotBlank(posted.getString("BILLABLE"))) {
|
|
|
+ } else if (("1202".equals(contractTypeId) || "1203".equals(contractTypeId)) && StringUtils.isNotBlank(posted.getString("BILLABLE"))) {
|
|
|
billableHours = "预支付";
|
|
|
}
|
|
|
detail.set("BILLABLE_HOURS", billableHours);
|
|
|
|
|
|
// 4.3 特殊处理:条目类型(OID转名称)
|
|
|
String itemTypeOid = posted.getString("ITEM_TYPE");
|
|
|
- String itemTypeName = itemTypeDict.stream()
|
|
|
- .filter(type -> itemTypeOid.equals(type.getString("OID")))
|
|
|
- .findFirst()
|
|
|
- .map(type -> type.getString("NAME"))
|
|
|
- .orElse(null);
|
|
|
+ String itemTypeName = itemTypeDict.stream().filter(type -> itemTypeOid.equals(type.getString("OID"))).findFirst().map(type -> type.getString("NAME")).orElse(null);
|
|
|
if (itemTypeName == null)
|
|
|
itemTypeName = DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{itemTypeOid, uc.getCompanyModel().getId()});
|
|
|
|
|
|
@@ -363,13 +360,10 @@ public class InvoiceService {
|
|
|
invoice.set("IS_VOIDED", 1);//作废
|
|
|
invoice.set("VOIDED_TIME", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
|
|
invoice.set("VOIDED_RESOURCE_ID", uc.getUID());
|
|
|
-
|
|
|
dn.recordFormChanges.record(uc, invoice, "作废发票");
|
|
|
-
|
|
|
SDK.getBOAPI().update("BO_EU_DNCTT_INVOICE", invoice);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
if (isDelete || dealStatus.equals("作废发票")) {
|
|
|
List<BO> deductions = SDK.getBOAPI().query("BO_EU_DNCRM_ACCOUNT_DEDUCTION").addQuery("INVOICE_ID =", invoiceId).list();
|
|
|
for (BO deduction : deductions) {
|