zhangyao 3 miesięcy temu
rodzic
commit
723ca14424

+ 42 - 21
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/controller/contractCreateController.java

@@ -1,5 +1,8 @@
 package com.awspaas.user.apps.donenow_ctt.controller;
 
+import com.actionsoft.bpms.bo.design.cache.BOCache;
+import com.actionsoft.bpms.bo.design.model.BOItemModel;
+import com.actionsoft.bpms.bo.design.model.BOModel;
 import com.actionsoft.bpms.bo.engine.BO;
 import com.actionsoft.bpms.bpmn.engine.model.run.delegate.ProcessInstance;
 import com.actionsoft.bpms.commons.database.RowMap;
@@ -84,7 +87,7 @@ public class contractCreateController {
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同关联了" + cnt + "个项目,无法删除!");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNSDK_TASK WHERE CONTRACT_ID=? AND CLOSED=0", new Object[]{id});
+        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNSDK_TASK WHERE CONTRACT_ID=? ", new Object[]{id});
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同关联了" + cnt + "个任务,无法删除!");
         }
@@ -96,11 +99,11 @@ public class contractCreateController {
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同关联了" + cnt + "个合同成本,无法删除!");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE CONTRACT_ID=? AND CLOSED=0", new Object[]{id});
+        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE CONTRACT_ID=? ", new Object[]{id});
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同关联了" + cnt + "个已计费条目,无法删除!");
         }
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNSDK_WORK_ENTRY WHERE CONTRACT_ID=? AND CLOSED=0", new Object[]{id});
+        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNSDK_WORK_ENTRY WHERE CONTRACT_ID=? ", new Object[]{id});
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同关联了" + cnt + "个工时,无法删除!");
         }
@@ -112,12 +115,12 @@ public class contractCreateController {
             return ResponseObject.newErrResponse("该合同合同初始费用已审批并提交,无法删除!");
         }
 
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST WHERE (CONTRACT_ID=? OR BINDID=?) AND APPROVE_AND_POST_DATE IS NOT NULL AND CLOSED=0", new Object[]{id, bindid});
+        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST WHERE (CONTRACT_ID=? OR BINDID=?) AND APPROVE_AND_POST_DATE IS NOT NULL ", new Object[]{id, bindid});
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同服务已审批并提交,无法删除!");
         }
 
-        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE (CONTRACT_ID=? OR BINDID=?) AND APPROVE_AND_POST_DATE IS NOT NULL AND CLOSED=0", new Object[]{id, bindid});
+        cnt = DBSql.getInt("SELECT COUNT(0) FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE (CONTRACT_ID=? OR BINDID=?) AND APPROVE_AND_POST_DATE IS NOT NULL ", new Object[]{id, bindid});
         if (cnt > 0) {
             return ResponseObject.newErrResponse("该合同服务已审批并提交,无法删除!");
         }
@@ -140,23 +143,15 @@ public class contractCreateController {
             updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST", params, conn);
             updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", params, conn);
 
-            List<BO> SBSboList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_BUNDLE_SERVICE").addQuerySqlWhere(" CONTRACT_SERVICE_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID))  AND CLOSED=0", params).list();
-            for (BO bo : SBSboList) {
-                bo.set("CLOSED", 1);
-                SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_BUNDLE_SERVICE", bo, conn);
-            }
+            updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_BUNDLE_SERVICE", " CONTRACT_SERVICE_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID)) ", params, conn);
 
-            List<BO> SABSboList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST_BUNDLE_SERVICE").addQuerySqlWhere(" CONTRACT_SERVICE_ADJUST_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID))  AND CLOSED=0", params).list();
-            for (BO bo : SABSboList) {
-                bo.set("CLOSED", 1);
-                SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST_BUNDLE_SERVICE", bo, conn);
-            }
+            updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST_BUNDLE_SERVICE", " CONTRACT_SERVICE_ADJUST_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID))  ", params, conn);
+
+
+            updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST_BUNDLE_SERVICE", " CONTRACT_SERVICE_ADJUST_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID)) ", params, conn);
+
+            updateContractCLOSED(uc, "BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", " CONTRACT_SERVICE_PERIOD_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID))  ", params, conn);
 
-            List<BO> SPBSboList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE").addQuerySqlWhere(" CONTRACT_SERVICE_PERIOD_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID))  AND CLOSED=0", params).list();
-            for (BO bo : SPBSboList) {
-                bo.set("CLOSED", 1);
-                SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_BUNDLE_SERVICE", bo, conn);
-            }
 
             conn.commit();
         } catch (SQLException e) {
@@ -176,9 +171,35 @@ public class contractCreateController {
      * @param params
      * @param conn
      */
+    private void updateContractCLOSED(UserContext uc, String boName, String SqlWhere, Map<String, Object> params, Connection conn) {
+        //获取需要删除的数据
+
+        BOModel boModel = BOCache.getInstance().getModelByEntityName(boName);
+        BOItemModel CLOSED = boModel.getItem("CLOSED");
+
+        if (CLOSED != null && CLOSED.getName().equals("CLOSED")) {
+            SqlWhere += " AND CLOSED=0";
+        }
+
+        List<BO> boList = SDK.getBOAPI().query(boName).addQuerySqlWhere(SqlWhere, params).list();
+        for (BO bo : boList) {
+            //记录删除日志
+            dn.recordFormChanges.deleteBeforeRecord(uc, boName, bo.getId(), "删除", conn);
+            SDK.getBOAPI().remove(boName, bo.getId(), conn);
+        }
+    }
+
     private void updateContractCLOSED(UserContext uc, String boName, Map<String, Object> params, Connection conn) {
         //获取需要删除的数据
-        List<BO> boList = SDK.getBOAPI().query(boName).addQuerySqlWhere(" (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID) AND CLOSED=0", params).list();
+
+        BOModel boModel = BOCache.getInstance().getModelByEntityName(boName);
+        BOItemModel CLOSED = boModel.getItem("CLOSED");
+        String whereSql = "";
+        if (CLOSED != null && CLOSED.getName().equals("CLOSED")) {
+            whereSql = " AND CLOSED=0";
+        }
+
+        List<BO> boList = SDK.getBOAPI().query(boName).addQuerySqlWhere(" (CONTRACT_ID=:CONTRACT_ID OR BINDID=:BINDID) " + whereSql, params).list();
         for (BO bo : boList) {
             //记录删除日志
             dn.recordFormChanges.deleteBeforeRecord(uc, boName, bo.getId(), "删除", conn);

+ 66 - 0
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/controller/invoiceController.java

@@ -0,0 +1,66 @@
+package com.awspaas.user.apps.donenow_ctt.controller;
+
+import com.actionsoft.bpms.commons.mvc.view.ResponseObject;
+import com.actionsoft.bpms.server.UserContext;
+import com.actionsoft.bpms.server.bind.annotation.Controller;
+import com.actionsoft.bpms.server.bind.annotation.Mapping;
+import com.awspaas.user.apps.donenow_ctt.service.InvoiceDealDto;
+import com.awspaas.user.apps.donenow_ctt.service.InvoiceService;
+
+import java.util.List;
+
+/**
+ * 账单管理
+ */
+@Controller
+public class invoiceController {
+    /**
+     * 预览发票
+     * @param uc
+     * @param accDeduIds
+     * @return
+     */
+    @Mapping("com.awspaas.user.apps.donenow_ctt.preview_invoice")
+    public ResponseObject previewInvoice(UserContext uc, String accDeduIds) {
+        InvoiceDealDto invoiceDealDto = new InvoiceDealDto();
+        invoiceDealDto.setIds(accDeduIds);
+        List<String> invoiceIds = InvoiceService.getInstance().processInvoice(uc, accDeduIds, invoiceDealDto, false);
+
+
+        return ResponseObject.newOkResponse();
+    }
+
+
+    /**
+     * 处理发票
+     * @param uc
+     * @param accDeduIds
+     * @return
+     */
+    @Mapping("com.awspaas.user.apps.donenow_ctt.deal_invoice")
+    public ResponseObject dealInvoice(UserContext uc, String accDeduIds) {
+        InvoiceDealDto invoiceDealDto = new InvoiceDealDto();
+        invoiceDealDto.setIds(accDeduIds);
+        List<String> invoiceIds = InvoiceService.getInstance().processInvoice(uc, accDeduIds, invoiceDealDto, true);
+
+        return ResponseObject.newOkResponse();
+    }
+
+
+    /**
+     * 发票更新
+     * @param uc
+     * @param invoiceIds
+     * @return
+     */
+    @Mapping("com.awspaas.user.apps.donenow_ctt.invoiceUpdate")
+    public ResponseObject invoiceUpdate(UserContext uc, String invoiceIds, String dealStatus) {
+
+        InvoiceService.getInstance().invoiceUpdate(uc, invoiceIds, dealStatus);
+
+
+
+        return ResponseObject.newOkResponse();
+    }
+
+}

+ 296 - 0
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/service/InvoiceDealDto.java

@@ -0,0 +1,296 @@
+package com.awspaas.user.apps.donenow_ctt.service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 发票处理和生成发票向导共用DTO
+ */
+public class InvoiceDealDto {
+
+    // 页面参数:1-必填参数,2-可空参数
+    /**
+     * 处理活动(原C#:int)
+     */
+    private int processAction;
+
+    /**
+     * 发票模板(必填,原C#:int)
+     */
+    private int invoiceTemplateId;
+
+    /**
+     * 是否启用发票邮件服务(默认false,原C#:bool)
+     */
+    private boolean isInvoiceEmail = false;
+
+    /**
+     * 邮件信息模板ID(原C#:int)
+     */
+    private int emailTempId;
+
+    /**
+     * 发票日期(必填,原C#:DateTime)
+     * 注:Java中用LocalDateTime对应日期时间类型,若仅需日期可改用LocalDate
+     */
+    private LocalDateTime invoiceDate;
+
+    /**
+     * 发票开始日期(可空,原C#:DateTime?)
+     */
+    private LocalDateTime dateRangeFrom;
+
+    /**
+     * 发票结束日期(可空,原C#:DateTime?)
+     */
+    private LocalDateTime dateRangeTo;
+
+    /**
+     * 订单号(可空,原C#:string)
+     */
+    private String purchaseOrderNo;
+
+    /**
+     * 发票备注(可空,原C#:string)
+     */
+    private String notes;
+
+    /**
+     * 支付条款ID(可空,原C#:int?)
+     */
+    private Integer paymentTermId;
+
+    // 需要用到的参数
+    /**
+     * 所有待处理发票的ID集合(必填,原C#:string)
+     * 注:原类中与thisIds字段语义重复,建议后续业务中统一字段名
+     */
+    private String ids;
+
+    /**
+     * 向导专用:是否显示打印按钮(默认false,原C#:bool)
+     */
+    private boolean isShowPrint = false;
+
+    /**
+     * 向导专用:是否显示邮件按钮(默认false,原C#:bool)
+     */
+    private boolean isShowEmail = false;
+
+    /**
+     * 向导专用:是否关联QuickBooks(默认false,原C#:bool)
+     */
+    private boolean isQuickBooks = false;
+
+    /**
+     * 跳转预览界面使用的客户ID(待定,原C#:long)
+     */
+    private long accountId;
+
+    /**
+     * 本批次生成发票的批次ID(原C#:long)
+     */
+    private long invoiceBatch;
+
+    /**
+     * 存储发票编号信息(原C#:string)
+     */
+    private String invoiceNo;
+
+    /**
+     * 待处理发票的ID集合(与ids语义重复,原C#:string)
+     */
+    private String thisIds;
+
+    /**
+     * 支付条款名称(原C#:string)
+     */
+    private String paymentTermName;
+
+    /**
+     * 支付截止日期(原C#:string,建议后续改为LocalDate类型更规范)
+     */
+    private String paymentEndDate;
+
+    private List<Map<String, Object>> UserDefinedFieldValue;
+
+    // ==================== Getter 和 Setter 方法 ====================
+    public int getProcessAction() {
+        return processAction;
+    }
+
+    public void setProcessAction(int processAction) {
+        this.processAction = processAction;
+    }
+
+    public int getInvoiceTemplateId() {
+        return invoiceTemplateId;
+    }
+
+    public void setInvoiceTemplateId(int invoiceTemplateId) {
+        this.invoiceTemplateId = invoiceTemplateId;
+    }
+
+    public boolean isInvoiceEmail() {
+        return isInvoiceEmail;
+    }
+
+    public void setInvoiceEmail(boolean invoiceEmail) {
+        isInvoiceEmail = invoiceEmail;
+    }
+
+    public int getEmailTempId() {
+        return emailTempId;
+    }
+
+    public void setEmailTempId(int emailTempId) {
+        this.emailTempId = emailTempId;
+    }
+
+    public LocalDateTime getInvoiceDate() {
+        if (invoiceDate == null)
+            invoiceDate = LocalDateTime.now();
+        return invoiceDate;
+    }
+
+    public void setInvoiceDate(LocalDateTime invoiceDate) {
+        this.invoiceDate = invoiceDate;
+    }
+
+    public LocalDateTime getDateRangeFrom() {
+        return dateRangeFrom;
+    }
+
+    public void setDateRangeFrom(LocalDateTime dateRangeFrom) {
+        this.dateRangeFrom = dateRangeFrom;
+    }
+
+    public LocalDateTime getDateRangeTo() {
+        return dateRangeTo;
+    }
+
+    public void setDateRangeTo(LocalDateTime dateRangeTo) {
+        this.dateRangeTo = dateRangeTo;
+    }
+
+    public String getPurchaseOrderNo() {
+        return purchaseOrderNo;
+    }
+
+    public void setPurchaseOrderNo(String purchaseOrderNo) {
+        this.purchaseOrderNo = purchaseOrderNo;
+    }
+
+    public String getNotes() {
+        return notes;
+    }
+
+    public void setNotes(String notes) {
+        this.notes = notes;
+    }
+
+    public Integer getPaymentTermId() {
+        return paymentTermId;
+    }
+
+    public void setPaymentTermId(Integer paymentTermId) {
+        this.paymentTermId = paymentTermId;
+    }
+
+    public String getIds() {
+        return ids;
+    }
+
+    public void setIds(String ids) {
+        this.ids = ids;
+    }
+
+    public boolean isShowPrint() {
+        return isShowPrint;
+    }
+
+    public void setShowPrint(boolean showPrint) {
+        isShowPrint = showPrint;
+    }
+
+    public boolean isShowEmail() {
+        return isShowEmail;
+    }
+
+    public void setShowEmail(boolean showEmail) {
+        isShowEmail = showEmail;
+    }
+
+    public boolean isQuickBooks() {
+        return isQuickBooks;
+    }
+
+    public void setQuickBooks(boolean quickBooks) {
+        isQuickBooks = quickBooks;
+    }
+
+    public long getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(long accountId) {
+        this.accountId = accountId;
+    }
+
+    public long getInvoiceBatch() {
+        return invoiceBatch;
+    }
+
+    public void setInvoiceBatch(long invoiceBatch) {
+        this.invoiceBatch = invoiceBatch;
+    }
+
+    public String getInvoiceNo() {
+        return invoiceNo;
+    }
+
+    public void setInvoiceNo(String invoiceNo) {
+        this.invoiceNo = invoiceNo;
+    }
+
+    public String getThisIds() {
+        return thisIds;
+    }
+
+    public void setThisIds(String thisIds) {
+        this.thisIds = thisIds;
+    }
+
+    public String getPaymentTermName() {
+        return paymentTermName;
+    }
+
+    public void setPaymentTermName(String paymentTermName) {
+        this.paymentTermName = paymentTermName;
+    }
+
+    public String getPaymentEndDate() {
+        return paymentEndDate;
+    }
+
+    public void setPaymentEndDate(String paymentEndDate) {
+        this.paymentEndDate = paymentEndDate;
+    }
+
+    /**
+     * 获取用户自定义字段的值
+     * @return
+     */
+    public List<Map<String, Object>> getUserDefinedFieldValue() {
+        return UserDefinedFieldValue;
+    }
+
+    /**
+     * 设置用户自定义字段的值
+     * @param userDefinedFieldValue
+     */
+    public void setUserDefinedFieldValue(List<Map<String, Object>> userDefinedFieldValue) {
+        UserDefinedFieldValue = userDefinedFieldValue;
+    }
+}

+ 353 - 0
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/service/InvoiceService.java

@@ -0,0 +1,353 @@
+package com.awspaas.user.apps.donenow_ctt.service;
+
+import com.actionsoft.bpms.bo.engine.BO;
+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.sdk.local.SDK;
+import org.apache.commons.lang3.StringUtils;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 发票服务(单例模式,方法体≤5个)
+ */
+public class InvoiceService {
+    // 单例模式:双重校验锁保证线程安全
+    private static volatile InvoiceService instance;
+
+    private InvoiceService() {
+    }
+
+    public static InvoiceService getInstance() {
+        if (instance == null) {
+            synchronized (InvoiceService.class) {
+                if (instance == null) {
+                    instance = new InvoiceService();
+                }
+            }
+        }
+        return instance;
+    }
+
+    // ====================== 1. 核心入口方法:处理发票生成 ======================
+    public List<String> processInvoice(UserContext uc, String accDeduIds, InvoiceDealDto param, boolean isDeal) {
+
+        System.out.println("处理发票生成");
+        System.out.println("accDeduIds:" + accDeduIds);
+
+        // 1. 基础校验:空值过滤
+        if (StringUtils.isBlank(accDeduIds)) return new ArrayList<>();
+        String[] dedIdArr = accDeduIds.split(",");
+
+        StringBuilder where = new StringBuilder("(");
+        for (int i = 0; i < dedIdArr.length; i++) {
+            where.append("?").append(i < dedIdArr.length - 1 ? "," : ")");
+        }
+        String sqlWhere = where.toString(); // 复用:构建IN条件
+
+        // 2. 初始化基础数据(3张核心表查询)
+        List<RowMap> postedDataList = DBSql.getMaps(
+                "SELECT * FROM VIEW_EU_DNV_POSTED_ALL WHERE ID IN " + sqlWhere + " ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID",
+                dedIdArr
+        );
+        // 统一Key为大写,避免字段名大小写不一致导致的NPE
+        postedDataList.forEach(row -> row.forEach((key, value) -> row.put(key.toUpperCase(), value)));
+
+        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 new ArrayList<>();
+
+        // 4. 循环处理每个客户的发票(拆分「有无采购订单」逻辑)
+        List<String> invoiceIds = new ArrayList<>();
+        for (Map.Entry<String, List<RowMap>> entry : customerDedMap.entrySet()) {
+            String ACCOUNT_ID = entry.getKey();
+            List<RowMap> customerDedList = entry.getValue();
+
+            // 4.1 客户信息校验:必须有客户顾问(否则跳过)
+            RowMap customer = DBSql.getMap("SELECT * FROM BO_EU_DNCRM_ACCOUNT WHERE ID = ?", ACCOUNT_ID);
+            if (customer == null || StringUtils.isBlank(customer.getString("RESOURCE_ID"))) {
+                continue;
+            }
+
+            // 4.2 处理「无采购订单」的发票
+            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, ""
+                );
+                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")));
+            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
+                );
+                if (StringUtils.isNotBlank(poInvoiceId)) {
+                    invoiceIds.add(poInvoiceId);
+                }
+            }
+        }
+
+        return invoiceIds;
+    }
+
+
+    // ====================== 4. 计算发票汇总数据(金额/工时) ======================
+    private InvoiceSummary calculateSummary(List<RowMap> postedList) {
+        InvoiceSummary summary = new InvoiceSummary();
+        // 金额汇总(含税总额、税额、不含税总价)
+        summary.totalMoney = postedList.stream().mapToDouble(p -> p.getDouble("DOLLARS")).sum();
+        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();
+        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) {
+        try {
+            // 1. 计算汇总数据
+            InvoiceSummary summary = calculateSummary(postedList);
+
+            // 2. 生成发票编号+创建流程实例
+            String invoiceNo = SDK.getRuleAPI().executeAtScript(
+                    "@year()-@sequence(@concat(@companyId(),@year(),INVOICE),6)"
+            );
+            ProcessInstance pi = SDK.getProcessAPI().createProcessInstance(
+                    "obj_a7d46a9dd4df40b899ddcf02113fe0d2",
+                    uc.getUID(),
+                    "新增账单" + invoiceNo
+            );
+
+            // 3. 构建并保存发票头
+            BO invoice = new BO();
+            invoice.set("INVOICE_NO", invoiceNo);
+            invoice.set("ACCOUNT_ID", customer.getString("ID"));
+            invoice.set("ACCOUNT_NAME", customer.getString("NAME"));
+            invoice.set("OWNER_RESOURCE_NAME", DBSql.getString("SELECT USERNAME FROM orguser WHERE USERID=?", new Object[]{customer.getString("RESOURCE_ID")}));
+            invoice.set("INVOICE_DATE", param.getInvoiceDate());
+            invoice.set("TOTAL", summary.totalMoney);
+            invoice.set("TAX_VALUE", summary.totalTax);
+            invoice.set("TOTAL_PRICE", summary.totalPrice);
+            invoice.set("NOBILL_HOURS", summary.noBillHours);
+            invoice.set("BILL_HOURS", summary.billHours);
+            invoice.set("PREPAID_HOURS", summary.prepaidHours);
+            invoice.set("DATE_RANGE_FROM", param.getDateRangeFrom());
+            invoice.set("DATE_RANGE_TO", param.getDateRangeTo());
+            invoice.set("PAYMENT_TERM_ID", param.getPaymentTermId());
+            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")});
+            invoice.set("TAX_REGION_NAME", taxRegionName);
+            invoice.setBindId(pi.getId());
+
+            // 记录变更+保存发票BO
+            dn.recordFormChanges.record(uc, invoice, "保存发票:" + invoiceNo);
+            SDK.getBOAPI().create("BO_EU_DNCTT_INVOICE", invoice, pi, uc);
+
+            // 4. 构建并保存发票明细(批量处理)
+            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");
+                RowMap ded = dedIdMap.get(dedId);
+
+                // 4.1 构建明细BO
+                BO detail = new BO();
+                detail.setBindId(invoice.getBindId());
+                detail.set("INVOICE_ID", invoice.getId());
+                detail.set("ITEM_DATE", posted.get("ITEM_DATE"));
+                detail.set("ITEM_DESC", posted.get("ITEM_DESC"));
+                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("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_INCLUSIVE", posted.get("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);
+
+                // 4.2 特殊处理:工时显示(按合同类型)
+                String contractTypeId = posted.getString("CONTRACT_TYPE_ID");
+                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"))) {
+                    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);
+                if (itemTypeName == null)
+                    itemTypeName = DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{itemTypeOid, uc.getCompanyModel().getId()});
+
+                detail.set("ITEM_TYPE", itemTypeName);
+
+                // 4.4 税务相关字段
+                detail.set("EFFECTIVE_TAX_RATE", ded.getDouble("EFFECTIVE_TAX_RATE") * 100);
+                detail.set("TAX_CATEGORY_NAME", ded.get("TAX_CATEGORY_NAME"));
+
+                // 4.5 保存明细
+                SDK.getBOAPI().createDataBO("BO_EU_DNCTT_INVOICE_DETAIL", detail, uc);
+
+                // 5. 实际处理时:更新账户抵扣(关联发票ID+行号)
+                if (isDeal) {
+                    BO dedBo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", dedId);
+                    dedBo.set("INVOICE_ID", invoice.getId());
+                    dedBo.set("INVOICE_LINE_ITEM_NO", lineNo);
+                    SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", dedBo);
+                }
+
+                lineNo++;
+            }
+
+            return invoice.getId(); // 返回生成的发票ID
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    // ====================== 内部辅助类:发票汇总数据载体 ======================
+    private static class InvoiceSummary {
+        double totalMoney;    // 含税总额
+        double totalTax;      // 税额
+        double totalPrice;    // 不含税总价
+        double noBillHours;   // 不计费工时
+        double billHours;     // 计费工时
+        double prepaidHours;  // 预付工时
+    }
+
+
+    /**
+     * 发票处理 1、生成发票 2、作废发票
+     * @param uc
+     * @param invoiceIds
+     * @param dealStatus
+     * @return
+     */
+    public boolean invoiceUpdate(UserContext uc, String invoiceIds, String dealStatus) {
+        for (String invoiceId : invoiceIds.split(",")) {
+            BO invoice = SDK.getBOAPI().get("BO_EU_DNCTT_INVOICE", invoiceId);
+            List<BO> invoiceDetails = SDK.getBOAPI().query("BO_EU_DNCTT_INVOICE_DETAIL").bindId(invoice.getBindId()).addQuery("INVOICE_ID=", invoiceId).orderBy("ACCOUNT_ID").orderBy("BILL_ACCOUNT_ID").list();
+
+            boolean isDelete = true;
+            int lineNo = 1;
+            for (BO invoiceDetail : invoiceDetails) {
+                String accountDeductionId = invoiceDetail.getString("ACCOUNT_DEDUCTION_ID");
+                BO accountDeduction = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accountDeductionId);
+
+                if (dealStatus.equals("生成发票")) {
+                    isDelete = false;
+                    if (StringUtils.isBlank(accountDeduction.getString("INVOICE_ID"))) {
+                        accountDeduction.set("INVOICE_ID", invoice.getId());
+                        accountDeduction.set("INVOICE_LINE_ITEM_NO", lineNo);
+
+                        dn.recordFormChanges.record(uc, accountDeduction, "生成发票");
+
+                        SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accountDeduction);
+                        lineNo++;
+                    }
+
+                } else if (dealStatus.equals("作废发票")) {
+                    if (StringUtils.isNotBlank(accountDeduction.getString("INVOICE_ID"))) {
+                        isDelete = false;
+                        accountDeduction.set("INVOICE_ID", null);
+                        accountDeduction.set("INVOICE_LINE_ITEM_NO", 0);
+
+                        dn.recordFormChanges.record(uc, accountDeduction, "作废发票");
+
+                        SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accountDeduction);
+                        lineNo++;
+                    }
+                }
+            }
+
+            if (isDelete) {
+                SDK.getProcessAPI().deleteById(invoice.getBindId(), uc.getUID());
+                SDK.getBOAPI().remove("BO_EU_DNCTT_INVOICE", invoiceId);
+                SDK.getBOAPI().removeByBindId("BO_EU_DNCTT_INVOICE_DETAIL", invoice.getBindId());
+                DBSql.update("DELETE FROM BO_EU_DNCTT_INVOICE_DETAIL WHERE BINDID=? OR INVOICE_ID=?", new Object[]{invoice.getBindId(), invoiceId});
+            } else if (dealStatus.equals("生成发票")) {
+                invoice.set("IS_VOIDED", 0);
+                invoice.set("VOIDED_TIME", null);
+                invoice.set("VOIDED_RESOURCE_ID", null);
+
+                dn.recordFormChanges.record(uc, invoice, "生成发票");
+
+                SDK.getBOAPI().update("BO_EU_DNCTT_INVOICE", invoice);
+            } else if (dealStatus.equals("作废发票")) {
+                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);
+            }
+        }
+        return true;
+
+    }
+}

+ 7 - 3
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/service/contractService.java

@@ -1148,7 +1148,10 @@ public class contractService {
                             costCodeId = DBSql.getString("SELECT COST_CODE_ID FROM BO_EU_DNIVT_SERVICE_BUNDLE WHERE ID = ?", new Object[]{bo.getString("OBJECT_ID")});
                         }
 
-                        TAX_CATEGORY_ID = SDK.getBOAPI().get("BO_EU_DND_COST_CODE", costCodeId, "TAX_CATEGORY_ID").toString();
+                        Object taxCategoryId = SDK.getBOAPI().get("BO_EU_DND_COST_CODE", costCodeId, "TAX_CATEGORY_ID");
+                        if (taxCategoryId != null) {
+                            TAX_CATEGORY_ID = taxCategoryId.toString();
+                        }
 
                     } else {
                         //BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD
@@ -1178,7 +1181,9 @@ public class contractService {
                             } else {
                                 costCodeId = DBSql.getString("SELECT COST_CODE_ID FROM BO_EU_DNIVT_SERVICE_BUNDLE WHERE ID = ?", new Object[]{bo.getString("OBJECT_ID")});
                             }
-                            TAX_CATEGORY_ID = SDK.getBOAPI().get("BO_EU_DND_COST_CODE", costCodeId, "TAX_CATEGORY_ID").toString();
+                            Object taxCategoryId = SDK.getBOAPI().get("BO_EU_DND_COST_CODE", costCodeId, "TAX_CATEGORY_ID");
+                            if (taxCategoryId != null)
+                                TAX_CATEGORY_ID = taxCategoryId.toString();
                         }
                     }
                 }
@@ -1214,7 +1219,6 @@ public class contractService {
                 // cad.set("EXTENDED_COST", tax_rate);
                 // cad.extended_cost = GetCost(id);
 
-
                 if (ca.getString("IS_TAX_EXEMPT").equals("1")) {
                     double tax_dollars = cad.get("EXTENDED_PRICE", Double.class) - cad.get("EXTENDED_PRICE", Double.class) / (1 + tax_rate);//税额
                     cad.set("TAX_DOLLARS", tax_dollars);

+ 408 - 0
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/service/invoiceService.java1

@@ -0,0 +1,408 @@
+package com.awspaas.user.apps.donenow_ctt.service;
+
+import com.actionsoft.bpms.bo.engine.BO;
+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.sdk.local.SDK;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 发票服务
+ */
+public class invoiceService {
+    // 单例模式
+    private static volatile invoiceService instance;
+
+    private invoiceService() {
+    }
+
+    public static invoiceService getInstance() {
+        if (instance == null) {
+            synchronized (invoiceService.class) {
+                if (instance == null) {
+                    instance = new invoiceService();
+                }
+            }
+        }
+        return instance;
+    }
+
+    /*
+     *处理发票
+     * @param uc
+     * @param accDeduIds
+     */
+    public List<String> ProcessInvoice(UserContext uc, String accDeduIds, InvoiceDealDto param, boolean isDeal) {
+        if (StringUtils.isBlank(accDeduIds))
+            return null;
+
+        String sqlWhere = " (";
+        String[] arr = accDeduIds.split(",");
+        for (int i = 0; i < arr.length; i++) {
+            sqlWhere += "?,";
+        }
+        sqlWhere = sqlWhere.substring(0, sqlWhere.length() - 1) + ")";
+
+        List<RowMap> thisAccDtoList = DBSql.getMaps("SELECT * FROM VIEW_EU_DNV_POSTED_ALL WHERE ID IN " + sqlWhere, Arrays.stream(arr).toArray());
+        if (thisAccDtoList.isEmpty())
+            return null;
+
+        //把里面的key转成大写
+        for (RowMap rowMap : thisAccDtoList) {
+            rowMap.forEach((key, value) -> rowMap.put(key.toUpperCase(), value));
+            //rowMap.forEach((key, value) -> rowMap.put(key.toLowerCase(), value));
+        }
+
+        List<RowMap> thisAccList = DBSql.getMaps("SELECT * FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE ID IN " + sqlWhere, Arrays.stream(arr).toArray());
+
+        Map<String, List<RowMap>> dicList = thisAccList.stream().filter(rowMap -> rowMap.get("BILL_ACCOUNT_ID") != null).collect(
+                Collectors.groupingBy(rowMap -> rowMap.get("BILL_ACCOUNT_ID").toString())
+        );
+        if (dicList.isEmpty())
+            return null;
+
+        List<RowMap> itemTypeList = DBSql.getMaps("SELECT OID,NAME FROM BO_EU_DND_GENERAL WHERE GENERAL_TABLE_ID='121'");
+
+        List<String> invoiceIds = new ArrayList<>();
+        for (String key : dicList.keySet()) {
+
+            RowMap account = DBSql.getMap("SELECT * FROM BO_EU_DNCRM_ACCOUNT WHERE ID = ?", key);
+            // 客户的客户顾问在发票中是必填项,没有客户顾问暂时不创建发票
+            if (StringUtils.isBlank(account.getString("RESOURCE_ID"))) {
+                continue;
+            }
+
+            List<RowMap> noPurOrderList = dicList.get(key).stream().filter(rowMap -> StringUtils.isBlank(rowMap.getString("PURCHASE_ORDER_NO"))).collect(Collectors.toList());
+            if (noPurOrderList.size() > 0) {
+                //没有销售订单的条目,计费到这个客户的和这个客户的条目
+                List<RowMap> thisNoPurOrdList = thisAccDtoList.stream().filter(rowMap -> StringUtils.isBlank(rowMap.getString("PURCHASE_ORDER_NO")) && rowMap.get("ACCOUNT_ID").equals(key)).collect(Collectors.toList());
+                List<RowMap> billToThisNoPurOrdList = thisAccDtoList.stream().filter(rowMap -> StringUtils.isBlank(rowMap.getString("PURCHASE_ORDER_NO")) && !rowMap.get("ACCOUNT_ID").equals(key) && rowMap.get("BILL_ACCOUNT_ID").equals(key)).collect(Collectors.toList());
+
+                BO invoice = new BO();
+                //汇总
+                Double totalMoney = thisNoPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("DOLLARS")).sum() + billToThisNoPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("DOLLARS")).sum();
+                Double totalTax = thisNoPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("TAX_DOLLARS")).sum() + billToThisNoPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("TAX_DOLLARS")).sum();
+                invoice.set("TOTAL_MONEY", totalMoney);//总额
+                invoice.set("TOTAL_TAX", totalTax);//税
+                Double totalPrice = totalMoney - totalTax;//总价
+                invoice.set("TOTAL_PRICE", totalPrice);//总价(不含税)
+                Double noBillHours = thisNoPurOrdList.stream().filter(rowMap -> StringUtils.isBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum() + billToThisNoPurOrdList.stream().filter(rowMap -> StringUtils.isBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum();
+
+                invoice.set("NOBILL_HOURS", noBillHours);//不计费工时
+                Double billHours = thisNoPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum() + billToThisNoPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum();
+                invoice.set("BILL_HOURS", billHours);//计费工时
+                Double prepaidHours = thisNoPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("CONTRACT_BLOCK_ID"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum() + billToThisNoPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("CONTRACT_BLOCK_ID"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum();
+                invoice.set("PREPAID_HOURS", prepaidHours);//预付工时
+
+
+                StringBuilder thisIds = new StringBuilder();
+                if (thisNoPurOrdList.size() > 0) {
+                    thisNoPurOrdList.forEach(rowMap -> thisIds.append(rowMap.get("ID").toString() + ","));
+                }
+                if (billToThisNoPurOrdList.size() > 0) {
+                    billToThisNoPurOrdList.forEach(rowMap -> thisIds.append(rowMap.get("ID").toString() + ","));
+                }
+                String stringIds = thisIds.toString();
+                if (StringUtils.isNotBlank(stringIds)) {
+                    stringIds = stringIds.substring(0, stringIds.length() - 1);
+                }
+                String invoiceNo = SDK.getRuleAPI().executeAtScript("@year()-@sequence(@concat(@companyId(),@year(),INVOICE),6)");//发票编号
+                ProcessInstance processInstance = SDK.getProcessAPI().createProcessInstance("obj_a7d46a9dd4df40b899ddcf02113fe0d2", uc.getUID(), "新增账单" + invoiceNo);
+
+
+                invoice.set("INVOICE_NO", invoiceNo);//发票编号
+                invoice.set("INVOICE_DATE", param.getInvoiceDate());
+                invoice.set("TOTAL", dicList.get(key).stream().mapToDouble(rowMap -> rowMap.getDouble("EXTENDED_PRICE")).sum());//总价
+                invoice.set("TAX_VALUE", dicList.get(key).stream().mapToDouble(rowMap -> rowMap.getDouble("TAX_DOLLARS")).sum());//税
+                invoice.set("DATE_RANGE_FROM", param.getDateRangeFrom());//开始时间
+                invoice.set("DATE_RANGE_TO", param.getDateRangeTo());//结束时间
+                invoice.set("PAYMENT_TERM_ID", param.getPaymentTermId());//付款条款
+                invoice.set("PURCHASE_ORDER_NO", "");
+                invoice.set("NOTES", param.getNotes());
+                invoice.set("TAX_REGION_NAME", account.getString("TAX_REGION_ID") == null ? "" : DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ?", new Object[]{account.getString("TAX_REGION_ID")}));
+                invoice.setBindId(processInstance.getId());
+
+                dn.recordFormChanges.record(uc, invoice, "保存发票");
+
+                SDK.getBOAPI().create("BO_EU_DNCTT_INVOICE", invoice, processInstance, uc);
+
+                int invoice_line = 1;
+                for (RowMap param_item : thisNoPurOrdList) {
+
+                    RowMap accDedItem = dicList.get(key).stream().filter(rowMap -> rowMap.get("ID").equals(param_item.get("ID"))).findFirst().get();
+
+                    String billable_hours = param_item.getString("BILLABLE_HOURS");
+
+                    if (param_item.getString("CONTRACT_TYPE_ID").equals("1199") || param_item.getString("CONTRACT_TYPE_ID").equals("1201")) {
+                        billable_hours = "合同已包";
+                    } else if (param_item.getString("CONTRACT_TYPE_ID").equals("1202") || param_item.getString("CONTRACT_TYPE_ID").equals("1203")) {
+                        if (StringUtils.isNotBlank(param_item.getString("BILLABLE")))
+                            billable_hours = "预支付";
+                    }
+
+                    BO invoiceItem = new BO();
+                    invoiceItem.setBindId(invoice.getBindId());
+                    invoiceItem.set("INVOICE_ID", invoice.getId());
+                    invoiceItem.set("ITEM_DATE", param_item.get("ITEM_DATE"));
+                    invoiceItem.set("ITEM_DESC", param_item.get("ITEM_DESC"));
+                    invoiceItem.set("ITEM_TYPE", param_item.get("ITEM_TYPE"));
+                    invoiceItem.set("RESOURCE_NAME", param_item.get("RESOURCE_NAME"));
+                    invoiceItem.set("billable_hours", billable_hours);
+                    invoiceItem.set("QUANTITY", param_item.get("QUANTITY"));
+                    invoiceItem.set("RATE", param_item.get("RATE"));
+                    invoiceItem.set("EFFECTIVE_TAX_RATE", accDedItem.getDouble("EFFECTIVE_TAX_RATE") * 100);
+                    invoiceItem.set("TAX_CATEGORY_NAME", accDedItem.get("TAX_CATEGORY_NAME"));
+                    String itemType = itemTypeList.stream().filter(rowMap -> rowMap.get("OID").equals(param_item.get("ITEM_TYPE"))).findFirst().get().getString("NAME");
+                    invoiceItem.set("ITEM_TYPE", itemType);
+                    invoiceItem.set("DOLLARS", param_item.get("DOLLARS"));
+                    invoiceItem.set("HOURLY_RATE", param_item.get("HOURLY_RATE"));
+                    invoiceItem.set("ROLE_NAME", param_item.get("ROLE_NAME"));
+                    invoiceItem.set("WORK_TYPE", param_item.get("WORK_TYPE"));
+                    invoiceItem.set("TAX_INCLUSIVE", param_item.get("DOLLARS"));
+                    invoiceItem.set("ACCOUNT_DEDUCTION_ID", param_item.get("ID"));
+                    SDK.getBOAPI().createDataBO("BO_EU_DNCTT_INVOICE_DETAIL", invoiceItem, uc);
+
+                    //更新账户抵扣条目
+                    if (isDeal)// 处理,区分预览和实际生成
+                    {
+                        BO thisacc_ded = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accDedItem.getString("ID"));
+                        thisacc_ded.set("INVOICE_ID", invoice.getId());
+                        thisacc_ded.set("INVOICE_LINE_ITEM_NO", invoice_line);
+                        SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", thisacc_ded);
+                    }
+                    invoice_line++;
+                }
+                //付费到这个客户的条目
+                if (billToThisNoPurOrdList.size() > 0) {
+                    Map<String, List<RowMap>> dicBillToThis = billToThisNoPurOrdList.stream().collect(Collectors.groupingBy(rowMap -> rowMap.getString("account_id")));
+                    for (String bkey : dicBillToThis.keySet()) {
+
+                        for (RowMap param_item : dicBillToThis.get(bkey)) {
+                            RowMap accDedItem = dicList.get(key).stream().filter(rowMap -> rowMap.get("ID").equals(param_item.get("ID"))).findFirst().get();
+
+                            String billable_hours = param_item.getString("BILLABLE_HOURS");
+
+                            if (param_item.getString("CONTRACT_TYPE_ID").equals("1199") || param_item.getString("CONTRACT_TYPE_ID").equals("1201")) {
+                                billable_hours = "合同已包";
+                            } else if (param_item.getString("CONTRACT_TYPE_ID").equals("1202") || param_item.getString("CONTRACT_TYPE_ID").equals("1203")) {
+                                if (StringUtils.isNotBlank(param_item.getString("BILLABLE")))
+                                    billable_hours = "预支付";
+                            }
+
+                            BO invoiceItem = new BO();
+                            invoiceItem.setBindId(invoice.getBindId());
+                            invoiceItem.set("INVOICE_ID", invoice.getId());
+                            invoiceItem.set("ITEM_DATE", param_item.get("ITEM_DATE"));
+                            invoiceItem.set("ITEM_DESC", param_item.get("ITEM_DESC"));
+                            invoiceItem.set("ITEM_TYPE", param_item.get("ITEM_TYPE"));
+                            invoiceItem.set("RESOURCE_NAME", param_item.get("RESOURCE_NAME"));
+                            invoiceItem.set("billable_hours", billable_hours);
+                            invoiceItem.set("QUANTITY", param_item.get("QUANTITY"));
+                            invoiceItem.set("RATE", param_item.get("RATE"));
+                            invoiceItem.set("EFFECTIVE_TAX_RATE", accDedItem.getDouble("EFFECTIVE_TAX_RATE") * 100);
+                            invoiceItem.set("TAX_CATEGORY_NAME", accDedItem.get("TAX_CATEGORY_NAME"));
+                            String itemType = itemTypeList.stream().filter(rowMap -> rowMap.get("OID").equals(param_item.get("ITEM_TYPE"))).findFirst().get().getString("NAME");
+                            invoiceItem.set("ITEM_TYPE", itemType);
+                            invoiceItem.set("DOLLARS", param_item.get("DOLLARS"));
+                            invoiceItem.set("HOURLY_RATE", param_item.get("HOURLY_RATE"));
+                            invoiceItem.set("ROLE_NAME", param_item.get("ROLE_NAME"));
+                            invoiceItem.set("WORK_TYPE", param_item.get("WORK_TYPE"));
+                            invoiceItem.set("TAX_INCLUSIVE", param_item.get("DOLLARS"));
+                            invoiceItem.set("ACCOUNT_DEDUCTION_ID", param_item.get("ID"));
+                            SDK.getBOAPI().createDataBO("BO_EU_DNCTT_INVOICE_DETAIL", invoiceItem, uc);
+
+
+                            //更新账户抵扣条目
+                            if (isDeal)// 处理,区分预览和实际生成
+                            {
+                                BO thisacc_ded = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accDedItem.getString("ID"));
+                                thisacc_ded.set("INVOICE_ID", invoice.getId());
+                                thisacc_ded.set("INVOICE_LINE_ITEM_NO", invoice_line);
+                                SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", thisacc_ded);
+                            }
+                            invoice_line++;
+                        }
+
+                    }
+
+                }
+
+            }
+
+
+            //有采购订单的条目
+            List<RowMap> purchOrderList = dicList.get(key).stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("purchase_order_no"))).collect(Collectors.toList());
+
+            if (purchOrderList.size() > 0) {
+                Map<String, List<RowMap>> poDic = purchOrderList.stream().collect(Collectors.groupingBy(rowMap -> rowMap.getString("purchase_order_no")));
+                for (String poKey : poDic.keySet()) {
+
+                    //采购订单的条目
+                    List<RowMap> thisPurOrdList = poDic.get(poKey).stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("purchase_order_no")) && rowMap.getString("account_id").equals(key) && rowMap.getString("bill_account_id").equals(key)).collect(Collectors.toList());
+
+                    //采购订单的条目
+                    List<RowMap> billToThisPurOrdList = poDic.get(poKey).stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("purchase_order_no")) && !rowMap.getString("account_id").equals(key) && rowMap.getString("bill_account_id").equals(key)).collect(Collectors.toList());
+
+
+                    BO invoice = new BO();
+                    //汇总
+                    Double totalMoney = thisPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("DOLLARS")).sum() + billToThisPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("DOLLARS")).sum();
+                    Double totalTax = thisPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("TAX_DOLLARS")).sum() + billToThisPurOrdList.stream().mapToDouble(rowMap -> rowMap.getDouble("TAX_DOLLARS")).sum();
+                    invoice.set("TOTAL_MONEY", totalMoney);//总额
+                    invoice.set("TOTAL_TAX", totalTax);//税
+                    Double totalPrice = totalMoney - totalTax;//总价
+                    invoice.set("TOTAL_PRICE", totalPrice);//总价(不含税)
+                    Double noBillHours = thisPurOrdList.stream().filter(rowMap -> StringUtils.isBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum() + billToThisPurOrdList.stream().filter(rowMap -> StringUtils.isBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum();
+
+                    invoice.set("NOBILL_HOURS", noBillHours);//不计费工时
+                    Double billHours = thisPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum() + billToThisPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("BILLABLE"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum();
+                    invoice.set("BILL_HOURS", billHours);//计费工时
+                    Double prepaidHours = thisPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("CONTRACT_BLOCK_ID"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum() + billToThisPurOrdList.stream().filter(rowMap -> StringUtils.isNotBlank(rowMap.getString("CONTRACT_BLOCK_ID"))).mapToDouble(rowMap -> rowMap.getDouble("BILLABLE_HOURS")).sum();
+                    invoice.set("PREPAID_HOURS", prepaidHours);//预付工时
+
+
+                    String invoiceNo = SDK.getRuleAPI().executeAtScript("@year()-@sequence(@concat(@companyId(),@year(),INVOICE),6)");//发票编号
+                    ProcessInstance processInstance = SDK.getProcessAPI().createProcessInstance("obj_a7d46a9dd4df40b899ddcf02113fe0d2", uc.getUID(), "新增账单" + invoiceNo);
+
+
+                    invoice.set("INVOICE_NO", invoiceNo);//发票编号
+                    invoice.set("INVOICE_DATE", param.getInvoiceDate());
+                    invoice.set("TOTAL", poDic.get(key).stream().mapToDouble(rowMap -> rowMap.getDouble("EXTENDED_PRICE")).sum());//总价
+                    invoice.set("TAX_VALUE", poDic.get(key).stream().mapToDouble(rowMap -> rowMap.getDouble("TAX_DOLLARS")).sum());//税
+                    invoice.set("DATE_RANGE_FROM", param.getDateRangeFrom());//开始时间
+                    invoice.set("DATE_RANGE_TO", param.getDateRangeTo());//结束时间
+                    invoice.set("PAYMENT_TERM_ID", param.getPaymentTermId());//付款条款
+                    invoice.set("PURCHASE_ORDER_NO", "");
+                    invoice.set("NOTES", param.getNotes());
+                    invoice.set("TAX_REGION_NAME", account.getString("TAX_REGION_ID") == null ? "" : DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ?", new Object[]{account.getString("TAX_REGION_ID")}));
+                    invoice.setBindId(processInstance.getId());
+
+                    dn.recordFormChanges.record(uc, invoice, "保存发票");
+
+                    SDK.getBOAPI().create("BO_EU_DNCTT_INVOICE", invoice, processInstance, uc);
+
+
+                    int invoice_line = 1;
+                    for (RowMap param_item : thisPurOrdList) {
+
+                        RowMap accDedItem = dicList.get(key).stream().filter(rowMap -> rowMap.get("ID").equals(param_item.get("ID"))).findFirst().get();
+
+                        String billable_hours = param_item.getString("BILLABLE_HOURS");
+
+                        if (param_item.getString("CONTRACT_TYPE_ID").equals("1199") || param_item.getString("CONTRACT_TYPE_ID").equals("1201")) {
+                            billable_hours = "合同已包";
+                        } else if (param_item.getString("CONTRACT_TYPE_ID").equals("1202") || param_item.getString("CONTRACT_TYPE_ID").equals("1203")) {
+                            if (StringUtils.isNotBlank(param_item.getString("BILLABLE")))
+                                billable_hours = "预支付";
+                        }
+
+                        BO invoiceItem = new BO();
+                        invoiceItem.setBindId(invoice.getBindId());
+                        invoiceItem.set("INVOICE_ID", invoice.getId());
+                        invoiceItem.set("ITEM_DATE", param_item.get("ITEM_DATE"));
+                        invoiceItem.set("ITEM_DESC", param_item.get("ITEM_DESC"));
+                        invoiceItem.set("ITEM_TYPE", param_item.get("ITEM_TYPE"));
+                        invoiceItem.set("RESOURCE_NAME", param_item.get("RESOURCE_NAME"));
+                        invoiceItem.set("billable_hours", billable_hours);
+                        invoiceItem.set("QUANTITY", param_item.get("QUANTITY"));
+                        invoiceItem.set("RATE", param_item.get("RATE"));
+                        invoiceItem.set("EFFECTIVE_TAX_RATE", accDedItem.getDouble("EFFECTIVE_TAX_RATE") * 100);
+                        invoiceItem.set("TAX_CATEGORY_NAME", accDedItem.get("TAX_CATEGORY_NAME"));
+                        String itemType = itemTypeList.stream().filter(rowMap -> rowMap.get("OID").equals(param_item.get("ITEM_TYPE"))).findFirst().get().getString("NAME");
+                        invoiceItem.set("ITEM_TYPE", itemType);
+                        invoiceItem.set("DOLLARS", param_item.get("DOLLARS"));
+                        invoiceItem.set("HOURLY_RATE", param_item.get("HOURLY_RATE"));
+                        invoiceItem.set("ROLE_NAME", param_item.get("ROLE_NAME"));
+                        invoiceItem.set("WORK_TYPE", param_item.get("WORK_TYPE"));
+                        invoiceItem.set("TAX_INCLUSIVE", param_item.get("DOLLARS"));
+                        invoiceItem.set("ACCOUNT_DEDUCTION_ID", param_item.get("ID"));
+                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_INVOICE_DETAIL", invoiceItem, uc);
+
+                        //更新账户抵扣条目
+                        if (isDeal)// 处理,区分预览和实际生成
+                        {
+                            BO thisacc_ded = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accDedItem.getString("ID"));
+                            thisacc_ded.set("INVOICE_ID", invoice.getId());
+                            thisacc_ded.set("INVOICE_LINE_ITEM_NO", invoice_line);
+                            SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", thisacc_ded);
+                        }
+                        invoice_line++;
+                    }
+                    //付费到这个客户的条目
+                    if (billToThisPurOrdList.size() > 0) {
+                        Map<String, List<RowMap>> dicBillToThis = billToThisPurOrdList.stream().collect(Collectors.groupingBy(rowMap -> rowMap.getString("account_id")));
+                        for (String bkey : dicBillToThis.keySet()) {
+
+                            for (RowMap param_item : dicBillToThis.get(bkey)) {
+                                RowMap accDedItem = dicList.get(key).stream().filter(rowMap -> rowMap.get("ID").equals(param_item.get("ID"))).findFirst().get();
+
+                                String billable_hours = param_item.getString("BILLABLE_HOURS");
+
+                                if (param_item.getString("CONTRACT_TYPE_ID").equals("1199") || param_item.getString("CONTRACT_TYPE_ID").equals("1201")) {
+                                    billable_hours = "合同已包";
+                                } else if (param_item.getString("CONTRACT_TYPE_ID").equals("1202") || param_item.getString("CONTRACT_TYPE_ID").equals("1203")) {
+                                    if (StringUtils.isNotBlank(param_item.getString("BILLABLE")))
+                                        billable_hours = "预支付";
+                                }
+
+                                BO invoiceItem = new BO();
+                                invoiceItem.setBindId(invoice.getBindId());
+                                invoiceItem.set("INVOICE_ID", invoice.getId());
+                                invoiceItem.set("ITEM_DATE", param_item.get("ITEM_DATE"));
+                                invoiceItem.set("ITEM_DESC", param_item.get("ITEM_DESC"));
+                                invoiceItem.set("ITEM_TYPE", param_item.get("ITEM_TYPE"));
+                                invoiceItem.set("RESOURCE_NAME", param_item.get("RESOURCE_NAME"));
+                                invoiceItem.set("billable_hours", billable_hours);
+                                invoiceItem.set("QUANTITY", param_item.get("QUANTITY"));
+                                invoiceItem.set("RATE", param_item.get("RATE"));
+                                invoiceItem.set("EFFECTIVE_TAX_RATE", accDedItem.getDouble("EFFECTIVE_TAX_RATE") * 100);
+                                invoiceItem.set("TAX_CATEGORY_NAME", accDedItem.get("TAX_CATEGORY_NAME"));
+                                String itemType = itemTypeList.stream().filter(rowMap -> rowMap.get("OID").equals(param_item.get("ITEM_TYPE"))).findFirst().get().getString("NAME");
+                                invoiceItem.set("ITEM_TYPE", itemType);
+                                invoiceItem.set("DOLLARS", param_item.get("DOLLARS"));
+                                invoiceItem.set("HOURLY_RATE", param_item.get("HOURLY_RATE"));
+                                invoiceItem.set("ROLE_NAME", param_item.get("ROLE_NAME"));
+                                invoiceItem.set("WORK_TYPE", param_item.get("WORK_TYPE"));
+                                invoiceItem.set("TAX_INCLUSIVE", param_item.get("DOLLARS"));
+                                invoiceItem.set("ACCOUNT_DEDUCTION_ID", param_item.get("ID"));
+                                SDK.getBOAPI().createDataBO("BO_EU_DNCTT_INVOICE_DETAIL", invoiceItem, uc);
+
+
+                                //更新账户抵扣条目
+                                if (isDeal)// 处理,区分预览和实际生成
+                                {
+                                    BO thisacc_ded = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accDedItem.getString("ID"));
+                                    thisacc_ded.set("INVOICE_ID", invoice.getId());
+                                    thisacc_ded.set("INVOICE_LINE_ITEM_NO", invoice_line);
+                                    SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", thisacc_ded);
+                                }
+                                invoice_line++;
+                            }
+                        }
+                    }
+
+                }
+
+            }
+
+        }
+
+
+        return invoiceIds;
+    }
+
+
+    private boolean addInvoiceItem(UserContext uc, List<RowMap> invoiceItemList) {
+
+        return true;
+    }
+
+}

+ 4 - 3
com.awspaas.user.apps.donenow_sys/src/com/awspaas/user/apps/donenow_sys/controller/udfController.java

@@ -303,9 +303,10 @@ public class udfController {
                     }
                 }
 
-                List<RowMap> list = DBSql.getMaps(sql);
-
-                field.put("list", list);
+                if (StringUtils.isNotBlank(sql) && sql.toLowerCase().startsWith("select")) {
+                    List<RowMap> list = DBSql.getMaps(sql);
+                    field.put("list", list);
+                }
             }
 
             field.put("comment", udf.getString("COL_COMMENT"));//字段中文名