Bladeren bron

电子签自动落印代码调整,采购服务合同会签推送

fyz 3 weken geleden
bovenliggende
commit
5d628f8754

+ 157 - 84
mjava-sdzl/src/main/java/com/malk/sdzl/common/OmFormContentMapper.java

@@ -4,9 +4,11 @@ import com.alibaba.fastjson.JSON;
 import com.malk.sdzl.entity.dto.OmFormContent;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * OM 表单字段映射。字段配置依据《OM表单字段唯一标识清单.xlsx》固化,
@@ -79,63 +81,36 @@ public final class OmFormContentMapper {
         addForm("FORM-C73526894F3647F690DECD5F16EE67AF4FKX", "OM收款单", new String[][] {
                 {"section", "selectField_x1tbicc"},
                 {"accountingEntity", "selectField_tafovni"},
-                {"customer", "associationFormField_mtv6c4pv"},
                 {"dateOfEntry", "dateField_mpcdx37x"},
                 {"collectionBank", "associationFormField_mtv6c4pt"},
                 {"collectionAmount", "numberField_mpceu15v"},
                 {"operator", "employeeField_mpcdx37w"},
                 {"incomeInformation", "tableField_mpcdx39v"},
-                {"revenueCategory", "selectField_mtv6c4px"},
-                {"subjectCode", "textField_mpcdx39x"},
-                {"subAccountCodeVerification", "numberField_mq6eh339"},
-                {"incomeSubject", "selectField_mtv6c4pz"},
-                {"incomeCode", "textField_mpcdx39z"},
-                {"project", "selectField_mtv6c4q1"},
-                {"itemCode", "textField_mpcdx3a1"},
-                {"costCenter", "selectField_mtv6c4q2"},
-                {"costCenterCode", "textField_mpcdx3a3"},
-                {"amountOfMoney", "numberField_mpcdx3a4"},
-                {"incomeBillAmount", "numberField_mpceu15w"},
-                {"explain", "textareaField_mpceu15x"},
-//                {"选择合同", "associationFormField_mtv6c4q3"},
-                {"cricContractNumber", "textField_mpceu160"},
                 {"remarks", "textareaField_mpceu16o"},
                 {"accountCodeVerification", "numberField_mq6eh337"},
-                {"全称", "textField_mtv6c4pu"},
+                {"customer", "textField_mtv6c4pu"},
                 {"accountingSubjectCode", "textField_mq6erof9"},
                 {"eliminationMode", "textField_mpcdx37c"},
-                {"accountingEntity", "textField_mpcdx370"},
-                {"section", "textField_mpcdx36z"},
-                {"transferInformation", "tableField_mpceu163"},
-                {"transferCategory", "textField_mpceu164"},
-                {"transferCategoryCode", "textField_mpceu165"},
-                {"transferAssessment", "textField_mpceu166"},
-                {"transferAssessmentCode", "textField_mpceu167"},
-                {"transferProject", "textField_mpceu169"},
-                {"transferProjectCode", "textField_mpceu168"},
-                {"projectCostCenter", "textField_mpceu16a"},
-                {"projectCostCenterCode", "textField_mpceu16b"},
-                {"projectAmount", "numberField_mpceu16c"},
-                {"projectBillAmount", "numberField_mpceu16d"},
-                {"noteIncomeType", "selectField_mpxozyvs"},
-                {"projectExplain", "textareaField_mpceu16e"},
-                {"totalTransferNotes", "numberField_mpceu16f"},
-                {"totalTransferCategories", "numberField_mpceu16h"},
-                {"billsRegistrationForm", "textField_mpcdx38t"},
-                {"billAmount", "numberField_mpceu16m"},
-                {"billStatus", "textField_mpceu16n"},
-                {"totalRevenueNotes", "numberField_mpcdx3aj"},
-                {"totalRevenueCategory", "numberField_mpcdx3ak"},
-                {"incomeAccounting", "employeeField_mpcdx3b2"},
-                {"cashier", "employeeField_mpcdx3b3"},
-                {"omReturnNewHouse", "textField_mq6e84og"},
-                {"omDifferenceReasons", "textField_mpceu16p"},
-                {"processEndDate", "dateField_mpceu16r"},
-                {"accountant", "textareaField_mq6e84oi"},
-                {"secondRentPerson", "textareaField_mq6e84ok"},
-                {"marketingPartner", "textareaField_mq6e84on"},
-                {"channelGroupSettlement", "textareaField_mq6e84om"},
+                {"accountingCode", "textField_mq6erof9"},
+                {"sectionCode", "textField_mv4tszev"},
         });
+        addDetailFields("FORM-C73526894F3647F690DECD5F16EE67AF4FKX", "tableField_mpcdx39v",
+                new String[][] {
+                        {"revenueCategory", "selectField_mtv6c4px"},
+                        {"subjectCode", "textField_mpcdx39x"},
+                        {"subAccountCodeVerification", "numberField_mq6eh339"},
+                        {"incomeSubject", "selectField_mtv6c4pz"},
+                        {"incomeCode", "textField_mpcdx39z"},
+                        {"project", "selectField_mtv6c4q1"},
+                        {"itemCode", "textField_mpcdx3a1"},
+                        {"costCenter", "selectField_mtv6c4q2"},
+                        {"costCenterCode", "textField_mpcdx3a3"},
+                        {"amountOfMoney", "numberField_mpcdx3a4"},
+                        {"incomeBillAmount", "numberField_mpceu15w"},
+                        {"explain", "textareaField_mpceu15x"},
+//                        {"选择合同", "associationFormField_mtv6c4q3"},
+                        {"cricContractNumber", "textField_mpceu160"},
+                });
         addForm("FORM-A10F4E77BF0D4A1E9216626A4C57AFE6P7GP", "OM开票申请单", new String[][] {
                 {"开票申请流水号", "serialNumberField_mtv3my6j"},
                 {"operator", "employeeField_mpcdx37w"},
@@ -159,36 +134,36 @@ public final class OmFormContentMapper {
                 {"cricBillingContent", "selectField_mtcb9a4h"},
                 {"cric", "tableField_mpcdx394"},
 //                {"关联产品", "associationFormField_mtbeqiyj"},
-                {"cricContractNo", "textField_mpcdx396"},
-                {"cricProductName", "textField_mpcdx397"},
-                {"cricSubprojectName", "textField_mpcdx398"},
-                {"cricExecutionAmount", "numberField_mpcdx399"},
-                {"CRIC开票金额", "numberField_mtbeqiyk"},
-                {"CRIC业绩金额", "numberField_mtbeqiyl"},
-                {"cricInvoicedAmount", "numberField_mpcdx39a"},
-                {"cricUninvoicedAmount", "numberField_mpcdx39c"},
-                {"amountReceivedByCric", "numberField_mpcdx39b"},
-                {"cricThisInvoicingAmount", "numberField_mpcdx39d"},
+//                {"cricContractNo", "textField_mpcdx396"},
+//                {"cricProductName", "textField_mpcdx397"},
+//                {"cricSubprojectName", "textField_mpcdx398"},
+//                {"cricExecutionAmount", "numberField_mpcdx399"},
+//                {"CRIC开票金额", "numberField_mtbeqiyk"},
+//                {"CRIC业绩金额", "numberField_mtbeqiyl"},
+//                {"cricInvoicedAmount", "numberField_mpcdx39a"},
+//                {"cricUninvoicedAmount", "numberField_mpcdx39c"},
+//                {"amountReceivedByCric", "numberField_mpcdx39b"},
+//                {"cricThisInvoicingAmount", "numberField_mpcdx39d"},
                 {"totalAmountInvoicing", "numberField_mpcdx39e"},
                 {"billInformation", "tableField_mpcdx39v"},
-                {"revenueCategory", "selectField_mtv8djh4"},
-                {"revenueCategoryCode", "textField_mpcdx39x"},
-                {"assessmentSubject", "selectField_g9kc21s"},
-                {"billAssessmentSubjectCode", "textField_mpcdx39z"},
-                {"costCenter", "selectField_j69kuio"},
-                {"costCenterCode", "textField_mpcdx3a3"},
-                {"project", "selectField_mrsytbp6"},
-                {"itemCode", "textField_mpcdx3a1"},
-                {"invoicingAmountIncludTax", "numberField_mpcdx3a4"},
-                {"taxRate", "selectField_mtv8djh5"},
-                {"amountexcludingTax", "numberField_mpcdx3a6"},
-                {"taxAmount", "numberField_mpcdx3a7"},
-                {"taxAdjustment", "numberField_mpcdx3a8"},
-                {"internalSettlement", "selectField_mtv8djh7"},
-                {"debitAccount", "textField_mpcdx3ab"},
-                {"debitAccountCode", "textField_mpcdx3ac"},
-                {"transferSubjects", "textField_mpcdx3ad"},
-                {"transferAccountCode", "textField_mpcdx3ae"},
+//                {"revenueCategory", "selectField_mtv8djh4"},
+//                {"revenueCategoryCode", "textField_mpcdx39x"},
+//                {"assessmentSubject", "selectField_g9kc21s"},
+//                {"billAssessmentSubjectCode", "textField_mpcdx39z"},
+//                {"costCenter", "selectField_j69kuio"},
+//                {"costCenterCode", "textField_mpcdx3a3"},
+//                {"project", "selectField_mrsytbp6"},
+//                {"itemCode", "textField_mpcdx3a1"},
+//                {"invoicingAmountIncludTax", "numberField_mpcdx3a4"},
+//                {"taxRate", "selectField_mtv8djh5"},
+//                {"amountexcludingTax", "numberField_mpcdx3a6"},
+//                {"taxAmount", "numberField_mpcdx3a7"},
+//                {"taxAdjustment", "numberField_mpcdx3a8"},
+//                {"internalSettlement", "selectField_mtv8djh7"},
+//                {"debitAccount", "textField_mpcdx3ab"},
+//                {"debitAccountCode", "textField_mpcdx3ac"},
+//                {"transferSubjects", "textField_mpcdx3ad"},
+//                {"transferAccountCode", "textField_mpcdx3ae"},
                 {"totalAmountIncludTax", "numberField_mpcdx3aj"},
                 {"totalAmountExcludingTax", "numberField_mpcdx3ak"},
                 {"totalTaxAmount", "numberField_mpcdx3al"},
@@ -198,15 +173,49 @@ public final class OmFormContentMapper {
                 {"allpowerReceiveMailbox", "textField_mpcdx37t"},
                 {"remarks", "textareaField_mpcdx3ba"},
                 {"附件", "attachmentField_mtv8djha"},
-                {"incomeAccounting", "employeeField_mpcdx3b2"},
-                {"cashier", "employeeField_mpcdx3b3"},
-                {"channelGroupSettlement", "employeeField_mpcdx3b5"},
-                {"accountant", "employeeField_mpcdx3b7"},
-                {"eInvoiceReceiptMobile", "textField_mpcdx3ar"},
-                {"eInvoiceReceiptMailbox", "textField_mpcdx3at"},
-                {"oaCoding", "textField_mpxp3omf"},
-                {"taxType", "selectField_mtbeqiyg"},
+//                {"incomeAccounting", "employeeField_mpcdx3b2"},
+//                {"cashier", "employeeField_mpcdx3b3"},
+//                {"channelGroupSettlement", "employeeField_mpcdx3b5"},
+//                {"accountant", "employeeField_mpcdx3b7"},
+//                {"eInvoiceReceiptMobile", "textField_mpcdx3ar"},
+//                {"eInvoiceReceiptMailbox", "textField_mpcdx3at"},
+//                {"oaCoding", "textField_mpxp3omf"},
+//                {"taxType", "selectField_mtbeqiyg"},
         });
+        addDetailFields("FORM-A10F4E77BF0D4A1E9216626A4C57AFE6P7GP", "tableField_mpcdx394",
+                new String[][] {
+                        {"cricContractNo", "textField_mpcdx396"},
+                        {"cricProductName", "textField_mpcdx397"},
+                        {"cricSubprojectName", "textField_mpcdx398"},
+                        {"cricExecutionAmount", "numberField_mpcdx399"},
+                        {"CRIC开票金额", "numberField_mtbeqiyk"},
+                        {"CRIC业绩金额", "numberField_mtbeqiyl"},
+                        {"cricInvoicedAmount", "numberField_mpcdx39a"},
+                        {"cricUninvoicedAmount", "numberField_mpcdx39c"},
+                        {"amountReceivedByCric", "numberField_mpcdx39b"},
+                        {"cricThisInvoicingAmount", "numberField_mpcdx39d"},
+                });
+        addDetailFields("FORM-A10F4E77BF0D4A1E9216626A4C57AFE6P7GP", "tableField_mpcdx39v",
+                new String[][] {
+                        {"revenueCategory", "selectField_mtv8djh4"},
+                        {"revenueCategoryCode", "textField_mpcdx39x"},
+                        {"assessmentSubject", "selectField_g9kc21s"},
+                        {"billAssessmentSubjectCode", "textField_mpcdx39z"},
+                        {"costCenter", "selectField_j69kuio"},
+                        {"costCenterCode", "textField_mpcdx3a3"},
+                        {"project", "selectField_mrsytbp6"},
+                        {"itemCode", "textField_mpcdx3a1"},
+                        {"invoicingAmountIncludTax", "numberField_mpcdx3a4"},
+                        {"taxRate", "selectField_mtv8djh5"},
+                        {"amountexcludingTax", "numberField_mpcdx3a6"},
+                        {"taxAmount", "numberField_mpcdx3a7"},
+                        {"taxAdjustment", "numberField_mpcdx3a8"},
+                        {"internalSettlement", "selectField_mtv8djh7"},
+                        {"debitAccount", "textField_mpcdx3ab"},
+                        {"debitAccountCode", "textField_mpcdx3ac"},
+                        {"transferSubjects", "textField_mpcdx3ad"},
+                        {"transferAccountCode", "textField_mpcdx3ae"},
+                });
     }
 
     private OmFormContentMapper() {
@@ -233,7 +242,14 @@ public final class OmFormContentMapper {
         content.setFormUuid(formUuid.trim());
         content.setFormName(definition.formName);
         for (FieldDefinition field : definition.fields) {
-            content.putField(field.fieldName, field.fieldId, findValue(formData, field.fieldId));
+            if (definition.detailFieldIds.contains(field.fieldId)) {
+                continue;
+            }
+            List<FieldDefinition> detailFields = definition.detailFields.get(field.fieldId);
+            Object value = detailFields == null
+                    ? findValue(formData, field.fieldId)
+                    : mapDetailRows(findRawValue(formData, field.fieldId), detailFields);
+            content.putField(field.fieldName, field.fieldId, value);
         }
         return content;
     }
@@ -255,6 +271,38 @@ public final class OmFormContentMapper {
         FORM_DEFINITIONS.put(formUuid, definition);
     }
 
+    private static void addDetailFields(String formUuid, String tableFieldId, String[][] fields) {
+        FormDefinition definition = FORM_DEFINITIONS.get(formUuid);
+        if (definition == null) {
+            throw new IllegalArgumentException("未找到 OM 表单定义:" + formUuid);
+        }
+        List<FieldDefinition> detailFields = new ArrayList<>();
+        for (String[] field : fields) {
+            FieldDefinition detailField = new FieldDefinition(field[0], field[1]);
+            detailFields.add(detailField);
+            definition.detailFieldIds.add(detailField.fieldId);
+        }
+        definition.detailFields.put(tableFieldId, detailFields);
+    }
+
+    private static List<Object> mapDetailRows(Object source, List<FieldDefinition> fields) {
+        List<Object> details = new ArrayList<>();
+        if (!(source instanceof Iterable)) {
+            return details;
+        }
+        for (Object sourceRow : (Iterable) source) {
+            if (!(sourceRow instanceof Map)) {
+                continue;
+            }
+            Map<String, Object> detail = new LinkedHashMap<>();
+            for (FieldDefinition field : fields) {
+                detail.put(field.fieldName, findValue(sourceRow, field.fieldId));
+            }
+            details.add(detail);
+        }
+        return details;
+    }
+
     private static Object findValue(Object source, String fieldId) {
         if (source instanceof Map) {
             Map map = (Map) source;
@@ -283,9 +331,34 @@ public final class OmFormContentMapper {
         return null;
     }
 
+    private static Object findRawValue(Object source, String fieldId) {
+        if (source instanceof Map) {
+            Map map = (Map) source;
+            if (map.containsKey(fieldId)) {
+                return map.get(fieldId);
+            }
+            for (Object value : map.values()) {
+                Object found = findRawValue(value, fieldId);
+                if (found != null) {
+                    return found;
+                }
+            }
+        } else if (source instanceof Iterable) {
+            for (Object value : (Iterable) source) {
+                Object found = findRawValue(value, fieldId);
+                if (found != null) {
+                    return found;
+                }
+            }
+        }
+        return null;
+    }
+
     private static class FormDefinition {
         private final String formName;
         private final List<FieldDefinition> fields = new ArrayList<>();
+        private final Map<String, List<FieldDefinition>> detailFields = new LinkedHashMap<>();
+        private final Set<String> detailFieldIds = new HashSet<>();
 
         private FormDefinition(String formName) {
             this.formName = formName;

+ 18 - 0
mjava-sdzl/src/main/java/com/malk/sdzl/controller/YqbOptionPushController.java

@@ -8,12 +8,15 @@ import com.malk.sdzl.entity.dto.YqbInvoiceTitleSyncResult;
 import com.malk.sdzl.entity.dto.YqbOptionPushRequest;
 import com.malk.sdzl.entity.dto.YqbOptionPushResult;
 import com.malk.sdzl.entity.dto.YqbSupplierDedupResult;
+import com.malk.sdzl.entity.dto.YqbSupplierFormPushRequest;
+import com.malk.sdzl.entity.dto.YqbSupplierFormPushResult;
 import com.malk.sdzl.entity.dto.YqbReceiptAccountImportResult;
 import com.malk.sdzl.entity.dto.YqbReceiptAccountRequest;
 import com.malk.sdzl.entity.dto.YqbReceiptAccountSyncResult;
 import com.malk.sdzl.service.YqbInvoiceTitleService;
 import com.malk.sdzl.service.YqbOptionPushService;
 import com.malk.sdzl.service.YqbReceiptAccountService;
+import com.malk.sdzl.service.YqbSupplierFormPushService;
 import com.malk.server.common.McR;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +42,21 @@ public class YqbOptionPushController {
     @Autowired
     private YqbReceiptAccountService yqbReceiptAccountService;
 
+    @Autowired
+    private YqbSupplierFormPushService yqbSupplierFormPushService;
+
+    /**
+     * 根据宜搭供应商表单实例依次新增收款信息、发票抬头和供应商。
+     * POST /api/sdzl/yqb/option/supplier/form/push
+     */
+    @PostMapping("/supplier/form/push")
+    public McR pushSupplierByForm(@RequestBody YqbSupplierFormPushRequest request) {
+        log.info("开始根据宜搭供应商表单串联推送易企报,formInstanceId={}",
+                request == null ? null : request.getFormInstanceId());
+        YqbSupplierFormPushResult result = yqbSupplierFormPushService.pushByFormInstance(request);
+        return McR.success(result);
+    }
+
     /**
      * 新增辅助类别。
      * POST /api/sdzl/yqb/option/category

+ 15 - 0
mjava-sdzl/src/main/java/com/malk/sdzl/entity/dto/YqbSupplierFormPushRequest.java

@@ -0,0 +1,15 @@
+package com.malk.sdzl.entity.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/** 根据宜搭供应商表单实例创建易企报收款信息、发票抬头和供应商辅助选项的请求。 */
+@Data
+public class YqbSupplierFormPushRequest implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 宜搭供应商表单实例 ID。 */
+    private String formInstanceId;
+}

+ 19 - 0
mjava-sdzl/src/main/java/com/malk/sdzl/entity/dto/YqbSupplierFormPushResult.java

@@ -0,0 +1,19 @@
+package com.malk.sdzl.entity.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/** 宜搭供应商表单串联推送易企报的结果。 */
+@Data
+public class YqbSupplierFormPushResult implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String formInstanceId;
+    private String payAccountId;
+    private String invoiceTitleId;
+    private YqbApiResponse<Object> receiptAccountResponse;
+    private YqbApiResponse<Object> invoiceTitleResponse;
+    private YqbApiResponse<Object> supplierResponse;
+}

+ 8 - 6
mjava-sdzl/src/main/java/com/malk/sdzl/service/EqbService.java

@@ -17,20 +17,22 @@ public interface EqbService {
      * @param params 请求参数,约定如下:
      *               表单模式需传formUuid和formInstanceId,使用请求中的formUuid匹配json/party-fields.json;
      *               formInstanceId用于查询宜搭实例,formData中无需包含表单UUID;
-     *               甲乙丙丁方编号有值时,分别按serialNumberField_mrlipwdo查询相对方登记表并生成signers;
+     *               甲乙方编号有值时,分别按serialNumberField_mrlipwdo查询相对方登记表并生成signers;
      *               空编号跳过,登记信息缺失或不唯一时停止;此模式忽略调用方传入的signers;
      *               读取两个约定的附件组件,先下载到eqb.downloadFilePath,再逐文件上传;
      *               全部附件确认就绪后合并发起一次签署,每次调用只为该表单创建一个流程;
      *               不传formUuid和formInstanceId时,fileName必填,为该目录内的文件名(包含真实扩展名);
      *               本地文件模式的signers必填,为1至10个签署方,个人传psnSignerInfo,企业手动签署传orgSignerInfo及经办人;
-     *               表单模式当前统一设置autoSign=true、freeMode=true,不传企业经办人;自然人因e签宝不支持自动签署而在上传前报错;
-     *               跨企业自动签署仍需具备e签宝印章授权,自由签章与自动签署组合的实际行为需联调确认;
+     *               表单模式只生成甲方、乙方签署人;textField_kcmzci1必须填写“甲方”或“乙方”,用于标识我方;
+     *               我方设置autoSign=true、freeMode=false并指定我方印章,企业自动签署不传经办人;
+     *               另一方使用手动签署,企业签署时使用宜搭中的对应联系人作为经办人;
+     *               若我方登记为自然人,会因e签宝不支持个人自动签署而在上传前报错;
      *               signFlowConfig可选,主题默认使用文件名(表单模式取第一个附件名),自动开启流程,autoFinish默认true;
      *               convertToPDF、convertToHTML、convertToOFD可选且互斥,默认false,非PDF合同需指定转换格式;
      *               未传签署区时默认自由签章,每位签署方的签署区配置会应用到每个附件,fileId由服务端绑定;
-     *               当任一签署区normalSignFieldConfig.autoSign=true时,必须传顶层autoSignKeywords数组;
-     *               关键字数组按自动签署区在signers中的出现顺序一一匹配。服务端在文件上传完成后调用
-     *               e签宝关键字定位接口,并将首个命中坐标写入signFieldPosition(positionPage、positionX、positionY);
+     *               文件上传完成后,服务端先通过e签宝关键字定位接口一次检索“甲方/甲方授权代表电子签名”和
+     *               “乙方电子签名”,再将首个命中坐标写入甲乙双方签署区的signFieldPosition
+     *               (positionPage、positionX、positionY),最后发起签署;
      *               自动定位签署区不能设置freeMode=true,signFieldStyle仅支持1(单页签章);
      *               同一流程最多50个文件、总大小500MB,展开后的签署区总数不能超过300个。
      * @return 成功时data包含fileId、fileName、fileSize(原文件字节数)、fileStatus及signFlowId;

+ 93 - 97
mjava-sdzl/src/main/java/com/malk/sdzl/service/Impl/EqbServiceImpl.java

@@ -55,6 +55,13 @@ public class EqbServiceImpl implements EqbService {
     private static final long FILE_STATUS_POLL_TIMEOUT_MILLIS = 90000;
     private static final String TEMPORARY_URL_ATTACHMENT_FIELD = "attachmentField_mrkcwgh7";
     private static final String DIRECT_URL_ATTACHMENT_FIELD = "attachmentField_msv7rz7m";
+    private static final List<String> FORM_SIGNER_PARTIES =
+            Collections.unmodifiableList(Arrays.asList("甲方", "乙方"));
+    private static final String OUR_PARTY = "乙方";
+    private static final String PARTY_A_SIGN_KEYWORD = "甲方/甲方授权代表电子签名";
+    private static final String PARTY_B_SIGN_KEYWORD = "乙方电子签名";
+    private static final String OUR_PARTY_SEAL_ID = "25b69e0c-4d74-4c8d-9164-eb10891a284d";
+    private static final String INTERNAL_SIGN_KEYWORD = "_signKeyword";
     private static final Set<String> UPLOAD_FILE_EXTENSIONS = new HashSet<>(Arrays.asList(
             "pdf", "docx", "doc", "rtf", "xlsx", "xls", "pptx", "ppt", "wps", "et", "dps",
             "jpeg", "jpg", "png", "bmp", "tiff", "tif", "gif", "html", "htm"));
@@ -131,24 +138,7 @@ public class EqbServiceImpl implements EqbService {
         if (params != null && (params.containsKey("formInstanceId") || params.containsKey("formUuid"))) {
             return uploadFormAttachments(params);
         }
-        try {
-            Object fileName = params == null ? null : params.get("fileName");
-            validateContractFileName(fileName);
-            JSONObject signRequest = prepareSignRequest(params, (String) fileName, 1);
-            McR result = uploadLocalFile(params, null);
-            if (!result.isSuccess()) {
-                return result;
-            }
-            Map<String, Object> fileResult = (Map<String, Object>) result.getData();
-            String signFlowId = createUploadedFilesSignFlow(signRequest, Collections.singletonList(fileResult));
-            fileResult.put("signFlowId", signFlowId);
-            return result;
-        } catch (IllegalArgumentException e) {
-            return McR.error("400", e.getMessage());
-        } catch (Exception e) {
-            LOG.error("e签宝上传合同并发起签署失败", e);
-            return McR.error("500", "e签宝上传合同并发起签署失败:" + e.getMessage());
-        }
+        return McR.error("400","未传入formInstanceId或formUuid");
     }
 
     /**
@@ -188,14 +178,15 @@ public class EqbServiceImpl implements EqbService {
                 throw new IllegalStateException("查询宜搭表单失败或未返回表单数据");
             }
             List<FormAttachment> attachments = new ArrayList<>();
-            collectAttachments(form.getFormData(), TEMPORARY_URL_ATTACHMENT_FIELD, attachments);
+//            collectAttachments(form.getFormData(), TEMPORARY_URL_ATTACHMENT_FIELD, attachments);
             collectAttachments(form.getFormData(), DIRECT_URL_ATTACHMENT_FIELD, attachments);
             if (attachments.isEmpty()) {
-                throw new IllegalArgumentException("两个附件组件中均没有可上传的文件");
+                throw new IllegalArgumentException("附件组件中均没有可上传的文件");
             }
             stage = "查询相对方登记信息";
             // 表单模式不采用请求中的signers,所有参与方均由表单编号及相对方登记信息生成。
             Map<String, Object> signParams = new HashMap<>(params);
+            signParams.remove("autoSignKeywords");
             signParams.put("signers", buildFormSigners(formUuid, form.getFormData()));
             // 整个表单共用签署配置;未指定主题时使用第一个附件名,并校验展开后的签署区总数。
             stage = "校验签署配置";
@@ -291,7 +282,7 @@ public class EqbServiceImpl implements EqbService {
     }
 
     /**
-     * 使用请求中的formUuid匹配party-fields.json,再按甲、乙、丙、丁顺序提取编号。
+     * 使用请求中的formUuid匹配party-fields.json,再按甲、乙顺序提取编号。
      * 未配置或未填写的参与方跳过;formData仅提供字段值,不读取其中的表单UUID。
      * 编号保持文本格式,保留前导零;业务编号仅用于查询登记表,不直接作为e签宝账号ID。
      */
@@ -330,7 +321,7 @@ public class EqbServiceImpl implements EqbService {
             throw new IllegalStateException("当前表单的parties配置必须为对象:" + formUuid);
         }
         List<Map<String, String>> partyNumbers = new ArrayList<>();
-        for (String party : Arrays.asList("甲方", "乙方", "丙方", "丁方")) {
+        for (String party : FORM_SIGNER_PARTIES) {
             Object fields = ((Map) rawParties).get(party);
             if (fields == null) {
                 continue;
@@ -366,7 +357,7 @@ public class EqbServiceImpl implements EqbService {
             }
         }
         if (partyNumbers.isEmpty()) {
-            throw new IllegalArgumentException("当前表单没有已配置且已填写的甲乙丙丁方编号:" + formUuid);
+            throw new IllegalArgumentException("当前表单没有已配置且已填写的甲乙方编号:" + formUuid);
         }
         return partyNumbers;
     }
@@ -379,12 +370,11 @@ public class EqbServiceImpl implements EqbService {
         String jPhone = UtilMap.getString(formData,"textField_mtwp487g");
         String yName = UtilMap.getString(formData,"textField_mtwp487i");
         String yPhone = UtilMap.getString(formData,"textField_mtwp487h");
-        String bName = UtilMap.getString(formData,"textField_mtwp487j");
-        String bPhone = UtilMap.getString(formData,"textField_mtwp487k");
-        String dName = UtilMap.getString(formData,"textField_mtwp487l");
-        String dPhone = UtilMap.getString(formData,"textField_mtwp487m");
         for (Map<String, String> party : extractFormPartyNumbers(formUuid, formData)) {
             String partyName = party.get("party");
+            boolean isOurParty = OUR_PARTY.equals(partyName);
+            String signerName = "甲方".equals(partyName) ? jName : yName;
+            String signerPhone = "甲方".equals(partyName) ? jPhone : yPhone;
             List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
                     .appType(appType)
                     .systemToken(systemToken)
@@ -416,63 +406,58 @@ public class EqbServiceImpl implements EqbService {
 //                        ? "CRED_PSN_PASSPORT" : "CRED_PSN_CH_IDCARD");
                 JSONObject psnSignerInfo = new JSONObject();
                 psnSignerInfo.put("psnInfo", psnInfo);
-                if ("甲方".equals(partyName)){
-                    psnInfo.put("psnName",jName);
-                    psnSignerInfo.put("psnAccount",jPhone);
-                }else if ("乙方".equals(partyName)){
-                    psnInfo.put("psnName",yName);
-                    psnSignerInfo.put("psnAccount",yPhone);
-                }else if ("丙方".equals(partyName)){
-                    psnInfo.put("psnName",bName);
-                    psnSignerInfo.put("psnAccount",bPhone);
-                }else if ("丁方".equals(partyName)){
-                    psnInfo.put("psnName",dName);
-                    psnSignerInfo.put("psnAccount",dPhone);
-                }
+                psnInfo.put("psnName", signerName);
+                psnSignerInfo.put("psnAccount", signerPhone);
                 signer.put("psnSignerInfo", psnSignerInfo);
             } else {
                 signer.put("signerType", 1);
-                JSONObject psnInfo = new JSONObject();
-                JSONObject transactorInfo = new JSONObject();
-
-                if ("甲方".equals(partyName)){
-                    psnInfo.put("psnName",jName);
-                    transactorInfo.put("psnAccount",jPhone);
-                }else if ("乙方".equals(partyName)){
-                    psnInfo.put("psnName",yName);
-                    transactorInfo.put("psnAccount",yPhone);
-                }else if ("丙方".equals(partyName)){
-                    psnInfo.put("psnName",bName);
-                    transactorInfo.put("psnAccount",bPhone);
-                }else if ("丁方".equals(partyName)){
-                    psnInfo.put("psnName",dName);
-                    transactorInfo.put("psnAccount",dPhone);
-                }
-                transactorInfo.put("psnInfo",psnInfo);
-
                 JSONObject orgInfo = new JSONObject();
                 orgInfo.put("orgIDCardType", "CRED_ORG_USCC");
                 orgInfo.put("orgIDCardNum", idCardNum);
                 JSONObject orgSignerInfo = new JSONObject();
                 orgSignerInfo.put("orgName", name);
                 orgSignerInfo.put("orgInfo", orgInfo);
-                orgSignerInfo.put("transactorInfo", transactorInfo);
-                // 自动签署不传企业经办人信息;跨企业自动落章需由e签宝验证印章授权。
-                signer.put("orgSignerInfo", orgSignerInfo);
-            }
-            JSONObject fieldConfig = new JSONObject();
-//            fieldConfig.put("autoSign", true);
-            fieldConfig.put("autoSign", false);
-            fieldConfig.put("freeMode", true);
-            JSONObject field = new JSONObject();
-            field.put("normalSignFieldConfig", fieldConfig);
-            signer.put("signFields", Collections.singletonList(field));
+                if (!isOurParty) {
+                    JSONObject psnInfo = new JSONObject();
+                    psnInfo.put("psnName", signerName);
+                    JSONObject transactorInfo = new JSONObject();
+                    transactorInfo.put("psnAccount", signerPhone);
+                    transactorInfo.put("psnInfo", psnInfo);
+                    orgSignerInfo.put("transactorInfo", transactorInfo);
+                    signer.put("orgSignerInfo", orgSignerInfo);
+                }
+            }
+            signer.put("signFields", Collections.singletonList(createSignField(partyName)));
             signers.add(signer);
         }
-        // 当前统一采用自动签署,个人分支会由prepareSignRequest明确拒绝,不能绕过接口限制。
         return signers;
     }
 
+    private JSONObject createSignField(String partyName) {
+        boolean isOurParty = OUR_PARTY.equals(partyName);
+        JSONObject fieldConfig = new JSONObject();
+        fieldConfig.put("autoSign", isOurParty);
+        fieldConfig.put("freeMode", false);
+        fieldConfig.put("signFieldStyle", 1);
+        if (isOurParty) {
+            fieldConfig.put("assignedSealId", OUR_PARTY_SEAL_ID);
+        }
+        JSONObject field = new JSONObject();
+        field.put("normalSignFieldConfig", fieldConfig);
+        field.put(INTERNAL_SIGN_KEYWORD, getSignKeyword(partyName));
+        return field;
+    }
+
+    private String getSignKeyword(String partyName) {
+        if ("甲方".equals(partyName)) {
+            return PARTY_A_SIGN_KEYWORD;
+        }
+        if ("乙方".equals(partyName)) {
+            return PARTY_B_SIGN_KEYWORD;
+        }
+        throw new IllegalArgumentException("不支持的签署方:" + partyName);
+    }
+
     private String requirePartyText(Map data, String fieldId, String partyName, String label) {
         Object value = data.get(fieldId);
         if (!(value instanceof String) || !hasText((String) value)) {
@@ -715,7 +700,7 @@ public class EqbServiceImpl implements EqbService {
             signer.put("signFields", fields);
         }
         // fileId 只有在所有附件上传完成后才可确定,因此自动落印的关键字定位必须在此处执行。
-        applyAutoSignKeywordPositions(signRequest);
+        applySignKeywordPositions(signRequest);
         Map signResult = createSignFlow(signRequest);
         Object signFlowId = signResult == null ? null : signResult.get("signFlowId");
         if (!(signFlowId instanceof String) || !hasText((String) signFlowId)) {
@@ -737,7 +722,7 @@ public class EqbServiceImpl implements EqbService {
             }
             return;
         }
-        if (keywords.size() != autoSignFieldCount) {
+        if (!keywords.isEmpty() && keywords.size() != autoSignFieldCount) {
             throw new IllegalArgumentException("autoSignKeywords数量必须与autoSign=true的签署区数量一致,当前自动签署区数="
                     + autoSignFieldCount + ",关键字数=" + keywords.size());
         }
@@ -748,52 +733,63 @@ public class EqbServiceImpl implements EqbService {
                 JSONObject signField = (JSONObject) fieldValue;
                 JSONObject fieldConfig = signField.getJSONObject("normalSignFieldConfig");
                 if (Boolean.TRUE.equals(fieldConfig.getBoolean("autoSign"))) {
-                    signField.put("_autoSignKeyword", keywords.get(keywordIndex++));
+                    if (!keywords.isEmpty()) {
+                        signField.put(INTERNAL_SIGN_KEYWORD, keywords.get(keywordIndex++));
+                    } else if (!hasText(signField.getString(INTERNAL_SIGN_KEYWORD))) {
+                        throw new IllegalArgumentException("autoSign=true的签署区必须配置定位关键字");
+                    }
                 }
             }
         }
     }
 
-    /** 根据已绑定的关键字定位每个展开后的自动落印签署区,并将坐标写回signFieldPosition。 */
-    private void applyAutoSignKeywordPositions(JSONObject signRequest) {
-        Map<String, Map<String, List<KeywordPosition>>> positionsByFile = new HashMap<>();
+    /** 根据已绑定的关键字定位每个展开后的签署区,并将坐标写回signFieldPosition。 */
+    private void applySignKeywordPositions(JSONObject signRequest) {
+        Map<String, Set<String>> keywordsByFile = new LinkedHashMap<>();
         for (Object signerValue : signRequest.getJSONArray("signers")) {
             JSONObject signer = (JSONObject) signerValue;
             for (Object fieldValue : signer.getJSONArray("signFields")) {
                 JSONObject signField = (JSONObject) fieldValue;
                 JSONObject fieldConfig = signField.getJSONObject("normalSignFieldConfig");
-                if (!Boolean.TRUE.equals(fieldConfig.getBoolean("autoSign"))) {
-                    continue;
-                }
-                String keyword = signField.getString("_autoSignKeyword");
-                signField.remove("_autoSignKeyword");
+                String keyword = signField.getString(INTERNAL_SIGN_KEYWORD);
                 if (!hasText(keyword)) {
-                    throw new IllegalStateException("自动落印签署区缺少已绑定的定位关键字");
+                    continue;
                 }
                 String fileId = signField.getString("fileId");
                 if (!hasText(fileId)) {
-                    throw new IllegalStateException("自动落印签署区缺少fileId");
-                }
-                Map<String, List<KeywordPosition>> filePositions = positionsByFile.get(fileId);
-                if (filePositions == null) {
-                    filePositions = new HashMap<>();
-                    positionsByFile.put(fileId, filePositions);
+                    throw new IllegalStateException("定位签署区缺少fileId");
                 }
-                List<KeywordPosition> keywordPositions = filePositions.get(keyword);
-                if (keywordPositions == null) {
-                    keywordPositions = queryKeywordPositions(fileId, Collections.singletonList(keyword));
-                    filePositions.put(keyword, keywordPositions);
+                keywordsByFile.computeIfAbsent(fileId, key -> new LinkedHashSet<>()).add(keyword);
+            }
+        }
+
+        Map<String, List<KeywordPosition>> positionsByFile = new HashMap<>();
+        for (Map.Entry<String, Set<String>> entry : keywordsByFile.entrySet()) {
+            positionsByFile.put(entry.getKey(),
+                    queryKeywordPositions(entry.getKey(), new ArrayList<>(entry.getValue())));
+        }
+
+        for (Object signerValue : signRequest.getJSONArray("signers")) {
+            JSONObject signer = (JSONObject) signerValue;
+            for (Object fieldValue : signer.getJSONArray("signFields")) {
+                JSONObject signField = (JSONObject) fieldValue;
+                JSONObject fieldConfig = signField.getJSONObject("normalSignFieldConfig");
+                String keyword = signField.getString(INTERNAL_SIGN_KEYWORD);
+                if (!hasText(keyword)) {
+                    continue;
                 }
-                KeywordPosition position = findFirstKeywordPosition(keywordPositions, keyword);
+                signField.remove(INTERNAL_SIGN_KEYWORD);
+                String fileId = signField.getString("fileId");
+                KeywordPosition position = findFirstKeywordPosition(positionsByFile.get(fileId), keyword);
                 if (position == null) {
-                    throw new IllegalArgumentException("自动落印关键字未定位到坐标,fileId=" + fileId + ",keyword=" + keyword);
+                    throw new IllegalArgumentException("签署关键字未定位到坐标,fileId=" + fileId + ",keyword=" + keyword);
                 }
                 if (Boolean.TRUE.equals(fieldConfig.getBoolean("freeMode"))) {
-                    throw new IllegalArgumentException("autoSign=true且使用关键字定位时,normalSignFieldConfig.freeMode不能为true");
+                    throw new IllegalArgumentException("使用关键字定位时,normalSignFieldConfig.freeMode不能为true");
                 }
                 Integer signFieldStyle = fieldConfig.getInteger("signFieldStyle");
                 if (signFieldStyle != null && signFieldStyle != 1) {
-                    throw new IllegalArgumentException("autoSign=true且使用关键字定位时,signFieldStyle必须为1(单页签章)");
+                    throw new IllegalArgumentException("使用关键字定位时,signFieldStyle必须为1(单页签章)");
                 }
                 fieldConfig.put("signFieldStyle", 1);
                 JSONObject signFieldPosition = fieldConfig.getJSONObject("signFieldPosition");
@@ -804,7 +800,7 @@ public class EqbServiceImpl implements EqbService {
                 signFieldPosition.put("positionPage", String.valueOf(position.pageNum));
                 signFieldPosition.put("positionX", position.positionX);
                 signFieldPosition.put("positionY", position.positionY);
-                LOG.info("e签宝自动落印定位成功,fileId={},keyword={},page={},x={},y={}",
+                LOG.info("e签宝签署区定位成功,fileId={},keyword={},page={},x={},y={}",
                         fileId, keyword, position.pageNum, position.positionX, position.positionY);
             }
         }

+ 2 - 2
mjava-sdzl/src/main/java/com/malk/sdzl/service/Impl/PayServiceImpl.java

@@ -1817,7 +1817,7 @@ public class PayServiceImpl implements PayService {
         if (isBlank(request.getOaCode())) {
             request.setOaCode(firstString(formData,
                     "serialNumberField_mudxcsdb",
-                    "textField_mpxojd0l", "textField_mpxnklyj"));
+                    "serialNumberField_mtv3my6j", "textField_mpxnklyj"));
             if (isBlank(request.getOaCode())) {
                 request.setOaCode(firstString(responseData, "oaCode", "oaCoding", "code"));
             }
@@ -1843,7 +1843,7 @@ public class PayServiceImpl implements PayService {
         }
         if (isBlank(request.getOmId())) {
             request.setOmId(firstString(formData,
-                    "omId", "omID", "om_id", "textField_mt804b4o"));
+                    "omId", "omID", "textField_mpcdx37o", "textField_mt804b4o"));
             if (isBlank(request.getOmId())) {
                 request.setOmId(firstString(responseData, "omId", "omID", "om_id"));
             }

+ 1 - 0
mjava-sdzl/src/main/java/com/malk/sdzl/service/Impl/YqbOrderPushServiceImpl.java

@@ -84,6 +84,7 @@ public class YqbOrderPushServiceImpl implements YqbOrderPushService {
             mapping("selectField_mpgp9h6x", "1302720250164277250"),
             mapping("textField_mpgp9h74", "1302720250164277249"),
             mapping("textField_mpgp9h76", "1302722528027865107"),
+            mapping("serialNumberField_mrsztw4p", "1306967724076822547"),//流水号
             mapping("attachmentField_mpgp9h6z", "1302722528027865106")
     ));
 

+ 223 - 0
mjava-sdzl/src/main/java/com/malk/sdzl/service/Impl/YqbSupplierFormPushServiceImpl.java

@@ -0,0 +1,223 @@
+package com.malk.sdzl.service.Impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.sdzl.entity.dto.YqbApiResponse;
+import com.malk.sdzl.entity.dto.YqbInvoiceTitleRequest;
+import com.malk.sdzl.entity.dto.YqbOptionPushRequest;
+import com.malk.sdzl.entity.dto.YqbReceiptAccountRequest;
+import com.malk.sdzl.entity.dto.YqbSupplierFormPushRequest;
+import com.malk.sdzl.entity.dto.YqbSupplierFormPushResult;
+import com.malk.sdzl.service.YqbInvoiceTitleService;
+import com.malk.sdzl.service.YqbOptionPushService;
+import com.malk.sdzl.service.YqbReceiptAccountService;
+import com.malk.sdzl.service.YqbSupplierFormPushService;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.dingtalk.DDR_New;
+import com.malk.service.aliwork.YDClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.Map;
+
+/** 根据宜搭供应商表单创建易企报收款信息、发票抬头和供应商辅助选项。 */
+@Slf4j
+@Service
+public class YqbSupplierFormPushServiceImpl implements YqbSupplierFormPushService {
+
+    private static final String SUPPLIER_FORM_UUID = "FORM-3648E003C1614C87AD5EC2709874F975TPYM";
+    private static final String SUPPLIER_CATEGORY_CODE = "02";
+
+    /** 宜搭供应商表单字段编码。 */
+    private static final String FIELD_SUPPLIER_NAME = "textField_mpfb5ond";
+    private static final String FIELD_TAX_PAYER_NO = "textField_mpfb5oo5";
+    private static final String FIELD_BANK_FULL_NAME = "textField_mpfb5onx";
+    private static final String FIELD_BANK_ACCOUNT = "textField_mpfb5ony";
+    private static final String FIELD_ADDRESS = "textField_mpdskkg3";
+    private static final String FIELD_BANK_NAME = "selectField_mpfb5oo1";
+    private static final String FIELD_CITY = "textField_mrlpiibu";
+    private static final String FIELD_PHONE = "textField_mpfb5oo3";
+    private static final String FIELD_SUPPLIER_CODE = "textField_mrleu14h";
+
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private YqbReceiptAccountService yqbReceiptAccountService;
+
+    @Autowired
+    private YqbInvoiceTitleService yqbInvoiceTitleService;
+
+    @Autowired
+    private YqbOptionPushService yqbOptionPushService;
+
+    @Value("${aliwork.appType}")
+    private String appType;
+
+    @Value("${aliwork.systemToken}")
+    private String systemToken;
+
+    @Override
+    public YqbSupplierFormPushResult pushByFormInstance(YqbSupplierFormPushRequest request) {
+        String formInstanceId = validateAndGetFormInstanceId(request);
+        validateYidaConfig();
+
+        DDR_New<?> form = ydClient.queryData(YDParam.builder()
+                .appType(appType)
+                .systemToken(systemToken)
+                .formUuid(SUPPLIER_FORM_UUID)
+                .formInstanceId(formInstanceId)
+                .build(), YDConf.FORM_QUERY.retrieve_id);
+        if (form == null || form.getFormData() == null) {
+            throw new IllegalStateException("查询宜搭供应商表单失败或未返回表单数据,formInstanceId=" + formInstanceId);
+        }
+
+        Map<String, Object> formData = form.getFormData();
+        YqbReceiptAccountRequest receiptRequest = buildReceiptAccountRequest(formData);
+        YqbInvoiceTitleRequest invoiceRequest = buildInvoiceTitleRequest(formData);
+        YqbOptionPushRequest supplierRequest = buildSupplierRequest(formData);
+
+        YqbApiResponse<Object> receiptResponse = yqbReceiptAccountService.addReceiptAccount(receiptRequest);
+        ensureSuccess(receiptResponse, "新增收款信息");
+        String payAccountId = extractResponseId(receiptResponse.getData(), "新增收款信息");
+
+        YqbApiResponse<Object> invoiceResponse = yqbInvoiceTitleService.addOrUpdateInvoiceTitle(invoiceRequest);
+        ensureSuccess(invoiceResponse, "新增发票抬头");
+        String invoiceTitleId = extractResponseId(invoiceResponse.getData(), "新增发票抬头");
+
+        supplierRequest.setPayAccountIds(payAccountId);
+        supplierRequest.setInvoiceTitleIds(invoiceTitleId);
+        YqbApiResponse<Object> supplierResponse = yqbOptionPushService.pushOne(supplierRequest);
+        ensureSuccess(supplierResponse, "新增供应商");
+
+        YqbSupplierFormPushResult result = new YqbSupplierFormPushResult();
+        result.setFormInstanceId(formInstanceId);
+        result.setPayAccountId(payAccountId);
+        result.setInvoiceTitleId(invoiceTitleId);
+        result.setReceiptAccountResponse(receiptResponse);
+        result.setInvoiceTitleResponse(invoiceResponse);
+        result.setSupplierResponse(supplierResponse);
+        log.info("宜搭供应商表单串联推送成功,formInstanceId={},payAccountId={},invoiceTitleId={}",
+                formInstanceId, payAccountId, invoiceTitleId);
+        return result;
+    }
+
+    private YqbReceiptAccountRequest buildReceiptAccountRequest(Map<String, Object> formData) {
+        YqbReceiptAccountRequest request = new YqbReceiptAccountRequest();
+        request.setHoldName(requiredField(formData, FIELD_SUPPLIER_NAME, "供应商全称"));
+        request.setBankCardNum(requiredField(formData, FIELD_BANK_ACCOUNT, "银行账号"));
+        request.setBankName(requiredField(formData, FIELD_BANK_NAME, "法人收款银行"));
+        request.setBankFullName(valueOf(formData.get(FIELD_BANK_FULL_NAME)));
+        request.setCity(valueOf(formData.get(FIELD_CITY)));
+        request.setType(YqbReceiptAccountRequest.TYPE_CORPORATE);
+        request.setVisible(YqbReceiptAccountRequest.VISIBLE_ALL);
+        request.setSourceType(YqbReceiptAccountRequest.SOURCE_TYPE_IMPORT);
+        return request;
+    }
+
+    private YqbInvoiceTitleRequest buildInvoiceTitleRequest(Map<String, Object> formData) {
+        YqbInvoiceTitleRequest request = new YqbInvoiceTitleRequest();
+        request.setInvoiceTitle(requiredField(formData, FIELD_SUPPLIER_NAME, "供应商全称"));
+        request.setTaxPayerNo(requiredField(formData, FIELD_TAX_PAYER_NO, "统一社会信用代码/身份证"));
+        request.setBankName(valueOf(formData.get(FIELD_BANK_FULL_NAME)));
+        request.setBankCardNum(valueOf(formData.get(FIELD_BANK_ACCOUNT)));
+        request.setAddress(valueOf(formData.get(FIELD_ADDRESS)));
+        request.setPhone(valueOf(formData.get(FIELD_PHONE)));
+        request.setVisible(YqbInvoiceTitleRequest.VISIBLE_ALL);
+        return request;
+    }
+
+    private YqbOptionPushRequest buildSupplierRequest(Map<String, Object> formData) {
+        YqbOptionPushRequest request = new YqbOptionPushRequest();
+        request.setCategoryCode(SUPPLIER_CATEGORY_CODE);
+        request.setItemCode(requiredField(formData, FIELD_SUPPLIER_CODE, "供应商流水号"));
+        request.setItemName(requiredField(formData, FIELD_SUPPLIER_NAME, "供应商全称"));
+        return request;
+    }
+
+    private void ensureSuccess(YqbApiResponse<Object> response, String operation) {
+        if (response == null || !response.isSuccess()) {
+            throw new IllegalStateException(operation + "失败:result=" + (response == null ? null : response.getResult())
+                    + ",message=" + (response == null ? null : response.getMessage()));
+        }
+    }
+
+    private String extractResponseId(Object data, String operation) {
+        String id = extractId(data);
+        if (isBlank(id)) {
+            throw new IllegalStateException(operation + "成功响应 data 中缺少 id");
+        }
+        return id;
+    }
+
+    @SuppressWarnings("unchecked")
+    private String extractId(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof Map) {
+            Object id = ((Map<?, ?>) value).get("id");
+            return id == null ? null : valueOf(id);
+        }
+        if (value instanceof JSONObject) {
+            return ((JSONObject) value).getString("id");
+        }
+        if (value instanceof Collection) {
+            for (Object item : (Collection<?>) value) {
+                String id = extractId(item);
+                if (!isBlank(id)) {
+                    return id;
+                }
+            }
+            return null;
+        }
+        if (value instanceof String) {
+            String text = ((String) value).trim();
+            if ((text.startsWith("{") && text.endsWith("}")) || (text.startsWith("[") && text.endsWith("]"))) {
+                try {
+                    return extractId(JSON.parse(text));
+                } catch (Exception ignored) {
+                    return null;
+                }
+            }
+        }
+        return null;
+    }
+
+    private String requiredField(Map<String, Object> formData, String fieldCode, String fieldName) {
+        String value = valueOf(formData.get(fieldCode));
+        if (isBlank(value)) {
+            throw new IllegalArgumentException("宜搭供应商表单字段“" + fieldName + "”为空,fieldCode=" + fieldCode);
+        }
+        return value;
+    }
+
+    private String valueOf(Object value) {
+        if (value == null) {
+            return null;
+        }
+        String text = String.valueOf(value).trim();
+        return text.isEmpty() ? null : text;
+    }
+
+    private String validateAndGetFormInstanceId(YqbSupplierFormPushRequest request) {
+        if (request == null || isBlank(request.getFormInstanceId())) {
+            throw new IllegalArgumentException("formInstanceId不能为空");
+        }
+        return request.getFormInstanceId().trim();
+    }
+
+    private void validateYidaConfig() {
+        if (isBlank(appType) || isBlank(systemToken)) {
+            throw new IllegalStateException("未配置aliwork.appType或aliwork.systemToken");
+        }
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+}

+ 13 - 0
mjava-sdzl/src/main/java/com/malk/sdzl/service/YqbSupplierFormPushService.java

@@ -0,0 +1,13 @@
+package com.malk.sdzl.service;
+
+import com.malk.sdzl.entity.dto.YqbSupplierFormPushRequest;
+import com.malk.sdzl.entity.dto.YqbSupplierFormPushResult;
+
+/** 宜搭供应商表单串联推送易企报服务。 */
+public interface YqbSupplierFormPushService {
+
+    /**
+     * 根据宜搭供应商表单实例依次创建收款信息、发票抬头和供应商辅助选项。
+     */
+    YqbSupplierFormPushResult pushByFormInstance(YqbSupplierFormPushRequest request);
+}

+ 1 - 1
mjava-sdzl/src/main/resources/application-prod.yml

@@ -70,7 +70,7 @@ aliwork:
 
 eqb:
   #  downloadFilePath: C:\\Users\\EDY\\Desktop\\项目\\华高\\电子签\\files\\
-  downloadFilePath: /home/server/mankalong/file/sign/
+  downloadFilePath: /home/server/sdzl/file/sign/
   #  signatoryPsnId: c3fb35cb7e574baf97ed9f8917c6327b #签署人 陈伟东
   signatoryPsnId: 95945af8f0864d15bf186e7808693176 #签署人 俞静妙
   apiHost: https://openapi.esign.cn