Browse Source

Merge branch 'dev' of ssh://10.10.100.21/source/saas-platform into dev

jinsy 7 years ago
parent
commit
e94751f774
100 changed files with 3556 additions and 627 deletions
  1. 2 0
      applications/commons/commons-dto/src/main/java/com/usoftchina/saas/commons/exception/BizExceptionCode.java
  2. 5 0
      applications/document/document-dto/src/main/java/com/usoftchina/saas/document/entities/CustomerList.java
  3. 6 2
      applications/document/document-dto/src/main/java/com/usoftchina/saas/document/entities/VendorList.java
  4. 3 0
      applications/document/document-server/src/main/resources/config/application-dev.yml
  5. 5 1
      applications/document/document-server/src/main/resources/mapper/CustomerListMapper.xml
  6. 4 1
      applications/document/document-server/src/main/resources/mapper/VendorMapper.xml
  7. 12 110
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/AccountDetailsView.java
  8. 8 110
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Custmonth.java
  9. 9 110
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Vendmonth.java
  10. 3 0
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/service/impl/FundtransferServiceImpl.java
  11. 0 2
      applications/money/money-server/src/main/java/com/usoftchina/saas/money/service/impl/OthreceiptsServiceImpl.java
  12. 2 0
      applications/money/money-server/src/main/resources/mapper/AccountDetailsViewMapper.xml
  13. 2 1
      applications/money/money-server/src/main/resources/mapper/CustmonthMapper.xml
  14. 1 1
      applications/money/money-server/src/main/resources/mapper/PayablesdetailMapper.xml
  15. 10 8
      applications/money/money-server/src/main/resources/mapper/ProfitdetailMapper.xml
  16. 1 1
      applications/money/money-server/src/main/resources/mapper/RecbalanceMapper.xml
  17. 1 1
      applications/money/money-server/src/main/resources/mapper/ReceivablesdetailMapper.xml
  18. 2 1
      applications/money/money-server/src/main/resources/mapper/VendmonthMapper.xml
  19. 14 5
      applications/operation/operation-server/src/main/java/com/usoftchina/saas/operation/dto/AccountDTO.java
  20. 3 1
      applications/operation/operation-server/src/main/resources/mapper/AccoutMapper.xml
  21. 2 0
      applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/mapper/ProdInOutMapper.java
  22. 2 0
      applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/mapper/PurchaseMapper.java
  23. 4 0
      applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/po/report/PurchaseReportDetail.java
  24. 10 0
      applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/service/impl/ProdInOutServiceImpl.java
  25. 5 0
      applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/service/impl/PurchaseServiceImpl.java
  26. 3 0
      applications/purchase/purchase-server/src/main/resources/mapper/ProdInOutMapper.xml
  27. 4 0
      applications/purchase/purchase-server/src/main/resources/mapper/PurchaseMapper.xml
  28. 2 0
      applications/purchase/purchase-server/src/main/resources/mapper/PurchaseReportMapper.xml
  29. 4 0
      applications/sale/sale-dto/src/main/java/com/usoftchina/saas/sale/dto/PurchaseDTO.java
  30. 2 0
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/mapper/ProdInOutMapper.java
  31. 4 2
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/po/SaleList.java
  32. 4 0
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/po/report/SaleProfitView.java
  33. 7 1
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/service/impl/ProdInOutServiceImpl.java
  34. 5 1
      applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/service/impl/SaleServiceImpl.java
  35. 5 0
      applications/sale/sale-server/src/main/resources/mapper/ProdInOutMapper.xml
  36. 3 1
      applications/sale/sale-server/src/main/resources/mapper/SaleListMapper.xml
  37. 13 0
      applications/sale/sale-server/src/main/resources/mapper/SaleMapper.xml
  38. 2 0
      applications/sale/sale-server/src/main/resources/mapper/SaleProfitViewMapper.xml
  39. 1 0
      applications/transfers/pom.xml
  40. 39 0
      applications/transfers/transfers-auth/pom.xml
  41. 15 0
      applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/EnableOpenApiAuthClient.java
  42. 15 0
      applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/annotation/IgnoreOpenApiAuth.java
  43. 39 0
      applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/configuration/OpenApiConfig.java
  44. 29 0
      applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/configuration/OpenApiConfiguration.java
  45. 90 0
      applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/interceptor/OpenApiAuthInterceptor.java
  46. 4 0
      applications/transfers/transfers-server/pom.xml
  47. 2 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/TransfersApplication.java
  48. 2 0
      applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/controller/TransfersController.java
  49. 4 1
      framework/core/pom.xml
  50. 222 0
      framework/core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextScheduler.java
  51. 11 9
      frontend/operation-web/app/model/statistical/CompanyInfo.js
  52. 26 0
      frontend/operation-web/app/util/BaseUtil.js
  53. 344 0
      frontend/operation-web/app/util/FormUtil.js
  54. 103 0
      frontend/operation-web/app/view/auth/ReLogin.js
  55. 15 0
      frontend/operation-web/app/view/auth/ReLogin.scss
  56. 103 0
      frontend/operation-web/app/view/auth/ReLoginController.js
  57. 443 0
      frontend/operation-web/app/view/core/form/FormPanel.js
  58. 92 0
      frontend/operation-web/app/view/core/form/FormPanel.scss
  59. 556 0
      frontend/operation-web/app/view/core/form/FormPanelController.js
  60. 15 0
      frontend/operation-web/app/view/core/form/FormPanelModel.js
  61. 554 0
      frontend/operation-web/app/view/core/form/field/DetailGridField.js
  62. 49 0
      frontend/operation-web/app/view/core/form/field/DetailGridField.scss
  63. 15 0
      frontend/operation-web/app/view/core/form/field/SeparateField.js
  64. 3 0
      frontend/operation-web/app/view/core/tab/Panel.js
  65. 5 0
      frontend/operation-web/app/view/core/tab/Panel.scss
  66. 12 1
      frontend/operation-web/app/view/statistical/CompanyAnalysis.js
  67. 110 0
      frontend/operation-web/app/view/statistical/CompanyInfo.js
  68. 14 0
      frontend/operation-web/app/view/statistical/CompanyInfoModel.js
  69. 36 1
      frontend/operation-web/app/view/statistical/PersonRegInfo.js
  70. 1 1
      frontend/operation-web/overrides/data/Connection.js
  71. 16 0
      frontend/operation-web/overrides/form/field/Date.js
  72. 36 1
      frontend/saas-web/app/Application.scss
  73. 21 0
      frontend/saas-web/app/model/report/PayDetail.js
  74. 4 1
      frontend/saas-web/app/model/report/Purchase.js
  75. 7 1
      frontend/saas-web/app/model/report/PurchasePay.js
  76. 21 0
      frontend/saas-web/app/model/report/RecDetail.js
  77. 4 1
      frontend/saas-web/app/model/report/Sale.js
  78. 3 1
      frontend/saas-web/app/model/report/SaleProfit.js
  79. 7 1
      frontend/saas-web/app/model/report/SaleRec.js
  80. 1 0
      frontend/saas-web/app/model/report/TotalPayDetail.js
  81. 1 0
      frontend/saas-web/app/model/report/TotalRecDetail.js
  82. 80 116
      frontend/saas-web/app/view/auth/Login.js
  83. 1 0
      frontend/saas-web/app/view/core/chart/EChartsBase.scss
  84. 31 28
      frontend/saas-web/app/view/core/dbfind/ConDbfindTrigger.js
  85. 9 4
      frontend/saas-web/app/view/core/dbfind/types/CurrencyDbfindTrigger.js
  86. 10 0
      frontend/saas-web/app/view/core/dbfind/types/CustomerDbfindTrigger.js
  87. 10 0
      frontend/saas-web/app/view/core/dbfind/types/VendorDbfindTrigger.js
  88. 2 1
      frontend/saas-web/app/view/document/bankinformation/DataList.js
  89. 10 0
      frontend/saas-web/app/view/document/currencys/DatalistController.js
  90. 1 1
      frontend/saas-web/app/view/document/customer/FormPanel.js
  91. 2 1
      frontend/saas-web/app/view/main/MainContainerWrap.js
  92. 83 74
      frontend/saas-web/app/view/main/Navigation.js
  93. 7 3
      frontend/saas-web/app/view/money/othreceipts/FormPanel.js
  94. 7 3
      frontend/saas-web/app/view/money/othspendings/FormPanel.js
  95. 4 4
      frontend/saas-web/app/view/money/payBalance/FormPanel.js
  96. 4 4
      frontend/saas-web/app/view/money/recBalance/FormPanel.js
  97. 6 1
      frontend/saas-web/app/view/money/recBalance/FormPanelController.js
  98. 10 0
      frontend/saas-web/app/view/money/report/AccountDetails.js
  99. 6 3
      frontend/saas-web/app/view/money/report/PayDetail.js
  100. 9 5
      frontend/saas-web/app/view/money/report/ProfitDetail.js

+ 2 - 0
applications/commons/commons-dto/src/main/java/com/usoftchina/saas/commons/exception/BizExceptionCode.java

@@ -72,6 +72,7 @@ public enum BizExceptionCode implements BaseExceptionCode {
     CUSTOMER_ISCLOSE(79507, "客户资料已关闭"),
     REQUIREDFIELD_NULL(79508,"存在必填字段为空"),
     USER_EXIST(79509, ""),
+    CURRENCY_VALID(79510, "币别与源单据不一致不允许修改"),
 
     //采购 70000-71999
     PURCCHECKIN_POST_ERROR(70000,""),
@@ -115,6 +116,7 @@ public enum BizExceptionCode implements BaseExceptionCode {
     DOCUMENTS_AUDITED(74009,"存在已审核单据:%S"),
     DOCUMENTS_UNAUDITED(74010,"存在未审核单据:%S"),
     BANK_AMOUNT_NOTENOUGHS(74011, "资金账户:%S 余额不足"),
+    CURRENCY_NOT_EQUALS(74012, "币别不一致"),
 
     //反结账
     EARLY_USERING(74012, "期初余额被使用,无法反结账"),

+ 5 - 0
applications/document/document-dto/src/main/java/com/usoftchina/saas/document/entities/CustomerList.java

@@ -62,6 +62,11 @@ public class CustomerList extends CommonBaseEntity{
     private Double cu_preamount;
 
     private String cu_remark;
+
+    private String cu_currency;
+
+    //currencys
+    private Double cr_rate;
 //customeraddress
 
     private Long ca_cuid;

+ 6 - 2
applications/document/document-dto/src/main/java/com/usoftchina/saas/document/entities/VendorList.java

@@ -106,6 +106,12 @@ public class VendorList implements Serializable {
 
     private String ve_address;
 
+    private String ve_currency;
+
+    //currencys
+
+    private Double cr_rate;
+
     /* 从表字段 */
 
     private Integer vc_veid;
@@ -132,6 +138,4 @@ public class VendorList implements Serializable {
 
     private String vc_default;
 
-    private String ve_currency;
-
 }

+ 3 - 0
applications/document/document-server/src/main/resources/config/application-dev.yml

@@ -11,3 +11,6 @@ eureka:
     registryFetchIntervalSeconds: 5
     serviceUrl:
       defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@127.0.0.1:8500/eureka/
+logging:
+  level:
+    com.usoftchina.saas.auth.client: DEBUG

+ 5 - 1
applications/document/document-server/src/main/resources/mapper/CustomerListMapper.xml

@@ -22,6 +22,7 @@
     <result column="cu_leftamount" jdbcType="DOUBLE" property="cu_leftamount" />
     <result column="cu_recamount" jdbcType="DOUBLE" property="cu_recamount" />
     <result column="cu_preamount" jdbcType="DOUBLE" property="cu_preamount" />
+    <result column="cu_currency" jdbcType="VARCHAR" property="cu_currency" />
     <result column="ca_cuid" jdbcType="INTEGER" property="ca_cuid" />
     <result column="ca_detno" jdbcType="INTEGER" property="ca_detno" />
     <result column="ca_person" jdbcType="VARCHAR" property="ca_person" />
@@ -38,6 +39,7 @@
     <result column="updaterName" property="updaterName" jdbcType="VARCHAR" />
     <result column="createTime" property="createTime" jdbcType="TIMESTAMP"/>
     <result column="cu_remark" property="cu_remark" jdbcType="VARCHAR"/>
+    <result column="cr_rate" property="cr_rate" jdbcType="DOUBLE"/>
   </resultMap>
 
 
@@ -55,7 +57,9 @@
   </select>
 
   <select id="selectDbFind" resultMap="BaseResultMap">
-    SELECT * FROM CUSTOMER LEFT JOIN CUSTOMERADDRESS ON CU_ID=CA_CUID AND CUSTOMERADDRESS.CA_DEFAULT=1
+    SELECT * FROM CUSTOMER
+    LEFT JOIN CUSTOMERADDRESS ON CU_ID=CA_CUID AND CUSTOMERADDRESS.CA_DEFAULT=1
+    left join currencys on cr_name = cu_currency and currencys.companyid = CUSTOMER.companyid
     <where>
       <if test="condition != null">
         ${condition}

+ 4 - 1
applications/document/document-server/src/main/resources/mapper/VendorMapper.xml

@@ -99,10 +99,13 @@
         <result column="vc_text5" property="vc_text5" jdbcType="VARCHAR" />
         <result column="vc_default" property="vc_default" jdbcType="VARCHAR" />
         <result column="ve_currency" property="ve_currency" jdbcType="VARCHAR" />
+        <result column="cr_rate" property="cr_rate" jdbcType="DOUBLE" />
     </resultMap>
 
     <select id="getListDataByCondition" resultMap="VendorListResultMapper">
-        SELECT * FROM VENDOR LEFT JOIN VENDORCONTACT ON VE_ID = VC_VEID and vc_default = 1
+        SELECT * FROM VENDOR
+        LEFT JOIN VENDORCONTACT ON VE_ID = VC_VEID and vc_default = 1
+        left join currencys on cr_name = ve_currency and currencys.companyid = VENDOR.companyid
         <where>
             <if test="condition!=null">
                 ${condition}

+ 12 - 110
applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/AccountDetailsView.java

@@ -1,14 +1,18 @@
 package com.usoftchina.saas.money.po;
 
-import java.util.Date;
+import lombok.Data;
 
+import java.util.Date;
+@Data
 public class AccountDetailsView {
     private String sl_code;
 
     private String sl_kind;
 
     private String ve_name;
+
     private String cu_name;
+
     private Double sl_amount;
 
     private Integer pb_id;
@@ -18,121 +22,19 @@ public class AccountDetailsView {
     private String pb_code;
 
     private Double total;
-    private Date date;
-    private Long companyId;
-    private Double sl_orderamount;
-    private Double pbd_nowbalance;
-    private Date pb_date;
-
-    public Double getPbd_nowbalance() {
-        return pbd_nowbalance;
-    }
-
-    public void setPbd_nowbalance(Double pbd_nowbalance) {
-        this.pbd_nowbalance = pbd_nowbalance;
-    }
-
-    public Date getPb_date() {
-        return pb_date;
-    }
-
-    public void setPb_date(Date pb_date) {
-        this.pb_date = pb_date;
-    }
-
-    public Double getSl_orderamount() {
-        return sl_orderamount;
-    }
-
-    public void setSl_orderamount(Double sl_orderamount) {
-        this.sl_orderamount = sl_orderamount;
-    }
-
-    public String getCu_name() {
-        return cu_name;
-    }
-
-    public void setCu_name(String cu_name) {
-        this.cu_name = cu_name;
-    }
 
-    public Long getCompanyId() {
-        return companyId;
-    }
-
-    public void setCompanyId(Long companyId) {
-        this.companyId = companyId;
-    }
-
-    public Date getDate() {
-        return date;
-    }
-
-    public void setDate(Date date) {
-        this.date = date;
-    }
-
-    public String getSl_code() {
-        return sl_code;
-    }
-
-    public void setSl_code(String sl_code) {
-        this.sl_code = sl_code;
-    }
-
-    public String getSl_kind() {
-        return sl_kind;
-    }
-
-    public void setSl_kind(String sl_kind) {
-        this.sl_kind = sl_kind;
-    }
-
-    public String getVe_name() {
-        return ve_name;
-    }
-
-    public void setVe_name(String ve_name) {
-        this.ve_name = ve_name;
-    }
-
-    public Double getSl_amount() {
-        return sl_amount;
-    }
-
-    public void setSl_amount(Double sl_amount) {
-        this.sl_amount = sl_amount;
-    }
-
-    public Integer getPb_id() {
-        return pb_id;
-    }
+    private Date date;
 
-    public void setPb_id(Integer pb_id) {
-        this.pb_id = pb_id;
-    }
+    private Long companyId;
 
-    public String getPb_kind() {
-        return pb_kind;
-    }
+    private Double sl_orderamount;
 
-    public void setPb_kind(String pb_kind) {
-        this.pb_kind = pb_kind;
-    }
+    private Double pbd_nowbalance;
 
-    public String getPb_code() {
-        return pb_code;
-    }
+    private Date pb_date;
 
-    public void setPb_code(String pb_code) {
-        this.pb_code = pb_code;
-    }
+    private String currency;
 
-    public Double getTotal() {
-        return total;
-    }
+    private Double rate;
 
-    public void setTotal(Double total) {
-        this.total = total;
-    }
 }

+ 8 - 110
applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Custmonth.java

@@ -1,6 +1,10 @@
 package com.usoftchina.saas.money.po;
 
+import lombok.Data;
+
+@Data
 public class Custmonth {
+
     private Integer cm_id;
 
     private Integer cm_yearmonth;
@@ -22,119 +26,13 @@ public class Custmonth {
     private Integer companyid;
 
     private Double cm_beginpreamount;
-    private Double cm_nowpayamount;
-    private Double cm_nowprepayamount;
-    private Double cm_endpreamount;
-
-    public Double getCm_beginpreamount() {
-        return cm_beginpreamount;
-    }
-
-    public void setCm_beginpreamount(Double cm_beginpreamount) {
-        this.cm_beginpreamount = cm_beginpreamount;
-    }
-
-    public Double getCm_nowpayamount() {
-        return cm_nowpayamount;
-    }
-
-    public void setCm_nowpayamount(Double cm_nowpayamount) {
-        this.cm_nowpayamount = cm_nowpayamount;
-    }
-
-    public Double getCm_nowprepayamount() {
-        return cm_nowprepayamount;
-    }
-
-    public void setCm_nowprepayamount(Double cm_nowprepayamount) {
-        this.cm_nowprepayamount = cm_nowprepayamount;
-    }
-
-    public Double getCm_endpreamount() {
-        return cm_endpreamount;
-    }
-
-    public void setCm_endpreamount(Double cm_endpreamount) {
-        this.cm_endpreamount = cm_endpreamount;
-    }
-
-    public Integer getCm_id() {
-        return cm_id;
-    }
-
-    public void setCm_id(Integer cm_id) {
-        this.cm_id = cm_id;
-    }
-
-    public Integer getCm_yearmonth() {
-        return cm_yearmonth;
-    }
 
-    public void setCm_yearmonth(Integer cm_yearmonth) {
-        this.cm_yearmonth = cm_yearmonth;
-    }
-
-    public Integer getCm_custid() {
-        return cm_custid;
-    }
-
-    public void setCm_custid(Integer cm_custid) {
-        this.cm_custid = cm_custid;
-    }
-
-    public String getCm_custcode() {
-        return cm_custcode;
-    }
-
-    public void setCm_custcode(String cm_custcode) {
-        this.cm_custcode = cm_custcode == null ? null : cm_custcode.trim();
-    }
-
-    public String getCm_custname() {
-        return cm_custname;
-    }
-
-    public void setCm_custname(String cm_custname) {
-        this.cm_custname = cm_custname == null ? null : cm_custname.trim();
-    }
-
-    public Double getCm_beginamount() {
-        return cm_beginamount;
-    }
-
-    public void setCm_beginamount(Double cm_beginamount) {
-        this.cm_beginamount = cm_beginamount;
-    }
-
-    public Double getCm_nowamount() {
-        return cm_nowamount;
-    }
-
-    public void setCm_nowamount(Double cm_nowamount) {
-        this.cm_nowamount = cm_nowamount;
-    }
-
-    public Double getCm_nowpreamount() {
-        return cm_nowpreamount;
-    }
-
-    public void setCm_nowpreamount(Double cm_nowpreamount) {
-        this.cm_nowpreamount = cm_nowpreamount;
-    }
+    private Double cm_nowpayamount;
 
-    public Double getCm_endamount() {
-        return cm_endamount;
-    }
+    private Double cm_nowprepayamount;
 
-    public void setCm_endamount(Double cm_endamount) {
-        this.cm_endamount = cm_endamount;
-    }
+    private Double cm_endpreamount;
 
-    public Integer getCompanyid() {
-        return companyid;
-    }
+    private String cm_currency;
 
-    public void setCompanyid(Integer companyid) {
-        this.companyid = companyid;
-    }
 }

+ 9 - 110
applications/money/money-server/src/main/java/com/usoftchina/saas/money/po/Vendmonth.java

@@ -1,6 +1,10 @@
 package com.usoftchina.saas.money.po;
 
+import lombok.Data;
+
+@Data
 public class Vendmonth {
+
     private Integer vm_id;
 
     private Integer vm_yearmonth;
@@ -20,120 +24,15 @@ public class Vendmonth {
     private Double vm_endamount;
 
     private Integer companyid;
-    private Double vm_beginpreamount;
-    private Double vm_nowpayamount;
-    private Double vm_nowprepayamount;
-    private Double vm_endpreamount;
-
-    public Double getVm_beginpreamount() {
-        return vm_beginpreamount;
-    }
-
-    public void setVm_beginpreamount(Double vm_beginpreamount) {
-        this.vm_beginpreamount = vm_beginpreamount;
-    }
-
-    public Double getVm_nowpayamount() {
-        return vm_nowpayamount;
-    }
-
-    public void setVm_nowpayamount(Double vm_nowpayamount) {
-        this.vm_nowpayamount = vm_nowpayamount;
-    }
-
-    public Double getVm_nowprepayamount() {
-        return vm_nowprepayamount;
-    }
-
-    public void setVm_nowprepayamount(Double vm_nowprepayamount) {
-        this.vm_nowprepayamount = vm_nowprepayamount;
-    }
-
-    public Double getVm_endpreamount() {
-        return vm_endpreamount;
-    }
-
-    public void setVm_endpreamount(Double vm_endpreamount) {
-        this.vm_endpreamount = vm_endpreamount;
-    }
-
-    public Integer getVm_id() {
-        return vm_id;
-    }
-
-    public void setVm_id(Integer vm_id) {
-        this.vm_id = vm_id;
-    }
 
-    public Integer getVm_yearmonth() {
-        return vm_yearmonth;
-    }
-
-    public void setVm_yearmonth(Integer vm_yearmonth) {
-        this.vm_yearmonth = vm_yearmonth;
-    }
-
-    public Integer getVm_vendid() {
-        return vm_vendid;
-    }
-
-    public void setVm_vendid(Integer vm_vendid) {
-        this.vm_vendid = vm_vendid;
-    }
-
-    public String getVm_vendcode() {
-        return vm_vendcode;
-    }
-
-    public void setVm_vendcode(String vm_vendcode) {
-        this.vm_vendcode = vm_vendcode;
-    }
-
-    public String getVm_vendname() {
-        return vm_vendname;
-    }
-
-    public void setVm_vendname(String vm_vendname) {
-        this.vm_vendname = vm_vendname;
-    }
-
-    public Double getVm_beginamount() {
-        return vm_beginamount;
-    }
-
-    public void setVm_beginamount(Double vm_beginamount) {
-        this.vm_beginamount = vm_beginamount;
-    }
-
-    public Double getVm_nowamount() {
-        return vm_nowamount;
-    }
-
-    public void setVm_nowamount(Double vm_nowamount) {
-        this.vm_nowamount = vm_nowamount;
-    }
-
-    public Double getVm_nowpreamount() {
-        return vm_nowpreamount;
-    }
+    private Double vm_beginpreamount;
 
-    public void setVm_nowpreamount(Double vm_nowpreamount) {
-        this.vm_nowpreamount = vm_nowpreamount;
-    }
+    private Double vm_nowpayamount;
 
-    public Double getVm_endamount() {
-        return vm_endamount;
-    }
+    private Double vm_nowprepayamount;
 
-    public void setVm_endamount(Double vm_endamount) {
-        this.vm_endamount = vm_endamount;
-    }
+    private Double vm_endpreamount;
 
-    public Integer getCompanyid() {
-        return companyid;
-    }
+    private String vm_currency;
 
-    public void setCompanyid(Integer companyid) {
-        this.companyid = companyid;
-    }
 }

+ 3 - 0
applications/money/money-server/src/main/java/com/usoftchina/saas/money/service/impl/FundtransferServiceImpl.java

@@ -93,6 +93,9 @@ public class FundtransferServiceImpl extends CommonBaseServiceImpl<FundtransferM
             fundtransferdetail.setCompanyId(BaseContextHolder.getCompanyId());
             fundtransferdetail.setFtd_ftid(Math.toIntExact(id));
             fundtransferdetail.setFt_date(fundtransfer.getFt_date());
+            if (!(fundtransferdetail.getFtd_currency().equals(fundtransferdetail.getFtd_incurrency()))){
+                throw new BizException(500, BizExceptionCode.CURRENCY_NOT_EQUALS.getMessage());
+            }
             if (fundtransferdetail.getId() > 0 ){
                 fundtransferdetailMapper.updateByPrimaryKeySelective(fundtransferdetail);
             }else {

+ 0 - 2
applications/money/money-server/src/main/java/com/usoftchina/saas/money/service/impl/OthreceiptsServiceImpl.java

@@ -104,8 +104,6 @@ public class OthreceiptsServiceImpl extends CommonBaseServiceImpl<OthreceiptsMap
             othreceiptsdetail1.setOr_date(othreceipts.getOr_date());
             othreceiptsdetail1.setOrd_currency(othreceipts.getOr_currency());
             othreceiptsdetail1.setOrd_rate(othreceipts.getOr_rate());
-            othreceiptsdetail1.setOrd_currency(othreceipts.getOr_currency());
-            othreceiptsdetail1.setOrd_rate(othreceipts.getOr_rate());
             if (othreceiptsdetail1.getId() > 0 ){
                 othreceiptsdetailMapper.updateByPrimaryKeySelective(othreceiptsdetail1);
             }else {

+ 2 - 0
applications/money/money-server/src/main/resources/mapper/AccountDetailsViewMapper.xml

@@ -16,6 +16,8 @@
     <result column="pb_date" property="pb_date" jdbcType="TIMESTAMP" />
     <result column="companyId" property="companyId" jdbcType="INTEGER" />
     <result column="pbd_nowbalance" property="pbd_nowbalance" jdbcType="DOUBLE" />
+    <result column="currency" property="currency" jdbcType="VARCHAR" />
+    <result column="rate" property="rate" jdbcType="DOUBLE" />
   </resultMap>
   <select id="selectByCondition" resultMap="BaseResultMap">
     select  *  from account_details_view

+ 2 - 1
applications/money/money-server/src/main/resources/mapper/CustmonthMapper.xml

@@ -16,10 +16,11 @@
     <result column="cm_nowpayamount" property="cm_nowpayamount" jdbcType="DOUBLE" />
     <result column="cm_nowprepayamount" property="cm_nowprepayamount" jdbcType="DOUBLE" />
     <result column="cm_endpreamount" property="cm_endpreamount" jdbcType="DOUBLE" />
+    <result column="cm_currency" property="cm_currency" jdbcType="VARCHAR" />
   </resultMap>
   <sql id="Base_Column_List" >
     cm_id, cm_yearmonth, cm_custid, cm_custcode, cm_custname, CM_BEGINAMOUNT, CM_NOWAMOUNT, 
-    CM_NOWPREAMOUNT, CM_ENDAMOUNT, companyid,cm_endpreamount,cm_nowprepayamount,cm_nowpayamount,cm_beginpreamount
+    CM_NOWPREAMOUNT, CM_ENDAMOUNT, companyid,cm_endpreamount,cm_nowprepayamount,cm_nowpayamount,cm_beginpreamount,cm_currency
   </sql>
 
   <select id="selectByCondition" resultMap="BaseResultMap">

+ 1 - 1
applications/money/money-server/src/main/resources/mapper/PayablesdetailMapper.xml

@@ -34,7 +34,7 @@
         ${con}
       </if>
       <if test="companyId != null">
-        and  companyId = #{companyId} and pd_query =1 and (pd_addpay + pd_addpre + pd_remain) != 0
+        and  companyId = #{companyId} and pd_query =1
       </if>
     </where>
     order by pd_vendid DESC, pd_detno asc, pd_date desc

+ 10 - 8
applications/money/money-server/src/main/resources/mapper/ProfitdetailMapper.xml

@@ -91,7 +91,6 @@ prodinout.companyid= prodiodetail.companyid
 		IFNULL(a.costamount,0) as costamount,
 		IFNULL(SUM(a.netamount - a.costamount),0) as profit,
 		IFNULL((SUM(a.netamount - a.costamount)/IFNULL(a.netamount,SUM(a.netamount - a.costamount))),0) as profitpresent
-
 		FROM
 		(
 		SELECT
@@ -99,9 +98,7 @@ prodinout.companyid= prodiodetail.companyid
 		pi_custname,
 		cu_type,
 		cu_sellername,
-		SUM(
-		IFNULL(pd_sendprice,0) * (IFNULL(pd_outqty,0) - IFNULL(pd_inqty,0))
-		) AS saamount,
+		SUM( (IFNULL( pd_sendprice, 0 ) * ( IFNULL( pd_outqty, 0 ) - IFNULL( pd_inqty, 0 ) )) * ifnull(cr_rate,1) ) AS saamount,,
 		SUM(
 		IFNULL(pd_netprice,0) * (IFNULL(pd_outqty,0) - IFNULL(pd_inqty,0))
 		) AS netamount,
@@ -111,7 +108,8 @@ prodinout.companyid= prodiodetail.companyid
 		FROM
 		prodinout,
 		prodiodetail,
-		customer
+		customer,
+		currencys
 		<where>
 			<if test="con != null">
 				${con}
@@ -120,9 +118,13 @@ prodinout.companyid= prodiodetail.companyid
 				and  prodinout.companyid = #{companyId}
 			</if>
 		</where>
-		and	pi_id = pd_piid
-		AND pi_custid = cu_id and pi_status = '已审核' and
-		prodinout.companyid= prodiodetail.companyid and prodiodetail.companyid = prodinout.companyid
+		AND	pi_id = pd_piid
+		AND pi_custid = cu_id
+		AND pi_status = '已审核'
+		AND prodinout.companyid= prodiodetail.companyid
+		AND prodiodetail.companyid = prodinout.companyid
+		AND currencys.cr_name = prodinout.pi_currency
+		AND currencys.companyid = prodinout.companyid
 		GROUP BY
 		pi_custcode,
 		pi_custname,

+ 1 - 1
applications/money/money-server/src/main/resources/mapper/RecbalanceMapper.xml

@@ -110,7 +110,7 @@
     rb_rdamount, rb_rbdamount, rb_preamount, rb_discounts, rb_havebalance, rb_status, rb_statuscode, rb_remark,
     recbalance.companyId,
     recbalance.updaterId,recbalance.updatedate, rb_text1, rb_text2, rb_text3, rb_text4, rb_text5,
-    recbalance.creatorName, recbalance.createTime, rb_auditman, rb_auditdate, rb_amount
+    recbalance.creatorName, recbalance.createTime, rb_auditman, rb_auditdate, rb_amount,rb_rate,rb_currency
   </sql>
   <sql id="left_Column_List">
     cu_leftamount

+ 1 - 1
applications/money/money-server/src/main/resources/mapper/ReceivablesdetailMapper.xml

@@ -34,7 +34,7 @@
         ${con}
       </if>
       <if test="companyId != null">
-        and  companyId = #{companyId} and rd_query = 1 and (rd_addrec + rd_addpre + rd_remain) != 0
+        and  companyId = #{companyId} and rd_query = 1
       </if>
     </where>
     order by rd_custid DESC, rd_detno asc, rd_date desc

+ 2 - 1
applications/money/money-server/src/main/resources/mapper/VendmonthMapper.xml

@@ -16,10 +16,11 @@
     <result column="vm_nowpayamount" property="vm_nowpayamount" jdbcType="DOUBLE" />
     <result column="vm_nowprepayamount" property="vm_nowprepayamount" jdbcType="DOUBLE" />
     <result column="vm_endpreamount" property="vm_endpreamount" jdbcType="DOUBLE" />
+    <result column="vm_currency" property="vm_currency" jdbcType="VARCHAR" />
   </resultMap>
   <sql id="Base_Column_List" >
     vm_id, vm_yearmonth, vm_vendid, VM_VENDCODE, VM_VENDNAME, VM_BEGINAMOUNT, VM_NOWAMOUNT, 
-    VM_NOWPREAMOUNT, VM_ENDAMOUNT, companyid,vm_endpreamount,vm_nowprepayamount,vm_nowpayamount,vm_beginpreamount
+    VM_NOWPREAMOUNT, VM_ENDAMOUNT, companyid,vm_endpreamount,vm_nowprepayamount,vm_nowpayamount,vm_beginpreamount,vm_currency
   </sql>
 
   <select id="selectByCondition" resultMap="BaseResultMap">

+ 14 - 5
applications/operation/operation-server/src/main/java/com/usoftchina/saas/operation/dto/AccountDTO.java

@@ -25,9 +25,22 @@ public class AccountDTO implements Serializable{
     private Date createTime;
     private long creatorId;
     private Date updateTime;
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+    public int getBind() {
+        return bind;
+    }
+
+    public void setBind(int bind) {
+        this.bind = bind;
+    }
+
     private long updaterId;
     private Long uu;
-
+    private int bind;
     public String getUsername() {
         return username;
     }
@@ -77,10 +90,6 @@ public class AccountDTO implements Serializable{
         this.type = type;
     }
 
-    public Boolean isEnabled() {
-        return enabled;
-    }
-
     public void setEnabled(Boolean enabled) {
         this.enabled = enabled;
     }

+ 3 - 1
applications/operation/operation-server/src/main/resources/mapper/AccoutMapper.xml

@@ -14,6 +14,7 @@
         <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
         <result column="updater_id" jdbcType="BIGINT" property="updaterId"/>
         <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
+        <result column="bind" jdbcType="INTEGER" property="bind"/>
     </resultMap>
 
     <resultMap id="loginMap" type="com.usoftchina.saas.operation.dto.LoginDTO">
@@ -38,7 +39,8 @@
     </resultMap>
 
     <select id="findAccountByCondition" parameterType="string" resultMap="BaseResultMap">
-        select * from saas_account.ac_account  <where>
+        select * from (select   count(account_id) bind  ,ac_account.*
+        from saas_account.ac_account left join saas_account.ac_account_company on ac_account.id=account_id GROUP BY id)account  <where>
         <if test="con != null">
             ${con}
         </if>

+ 2 - 0
applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/mapper/ProdInOutMapper.java

@@ -26,4 +26,6 @@ public interface ProdInOutMapper extends CommonBaseMapper<ProdInOut> {
     int checkQtyFromProdIn(Long id);
 
     void updateCreator(@Param("userId") Long userId,@Param("userName") String userName,@Param("id") Long pi_id);
+
+    String validateCurrency(@Param("piId") Long piId, @Param("piCurrency") String piCurrency);
 }

+ 2 - 0
applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/mapper/PurchaseMapper.java

@@ -42,4 +42,6 @@ public interface PurchaseMapper extends CommonBaseMapper<Purchase>{
     Integer checkPurchaseRequiredField(Long id);
 
     Integer checkClose(Long id);
+
+    String validateCurrency(@Param("puId") Long piId, @Param("puCurrency") String piCurrency);
 }

+ 4 - 0
applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/po/report/PurchaseReportDetail.java

@@ -68,6 +68,10 @@ public class PurchaseReportDetail extends CommonBaseEntity implements Serializab
 
     private String pu_shipaddresscode;
 
+    private String pu_currency;
+
+    private Double pu_rate;
+
     //从表字段
     private Long pd_id;
 

+ 10 - 0
applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/service/impl/ProdInOutServiceImpl.java

@@ -190,6 +190,11 @@ public class ProdInOutServiceImpl extends CommonBaseServiceImpl<ProdInOutMapper,
             messageLogService.save(baseDTO);
             return baseDTO;
         }
+        //校验有来源的验收验退币别是否与源单据一致
+        String piInoutno = getMapper().validateCurrency(pi_id,prodInOut.getPi_currency());
+        if(piInoutno!=null){
+            throw new BizException(BizExceptionCode.CURRENCY_VALID);
+        }
         //更新操作
         getMapper().updateByPrimaryKeySelective(prodInOut);
         //添加从表传输对象
@@ -460,11 +465,16 @@ public class ProdInOutServiceImpl extends CommonBaseServiceImpl<ProdInOutMapper,
         targetPi.setPi_vendid(sourcePi.getPi_vendid());
         targetPi.setPi_vendcode(sourcePi.getPi_vendcode());
         targetPi.setPi_vendname(sourcePi.getPi_vendname());
+        //来源采购
         targetPi.setPi_puid(sourcePi.getPi_puid());
         targetPi.setPi_pucode(sourcePi.getPi_pucode());
+        //记录来源
         targetPi.setPi_ioid(sourcePi.getId());
         targetPi.setPi_iocode(sourcePi.getPi_inoutno());
+        //币别、汇率
         targetPi.setPi_currency(sourcePi.getPi_currency());
+        targetPi.setPi_rate(sourcePi.getPi_rate());
+        //采购员
         targetPi.setPi_buyerid(sourcePi.getPi_buyerid());
         targetPi.setPi_buyercode(sourcePi.getPi_buyercode());
         targetPi.setPi_buyername(sourcePi.getPi_buyername());

+ 5 - 0
applications/purchase/purchase-server/src/main/java/com/usoftchina/saas/purchase/service/impl/PurchaseServiceImpl.java

@@ -186,6 +186,11 @@ public class PurchaseServiceImpl extends CommonBaseServiceImpl<PurchaseMapper, P
             messageLogService.save(baseDTO);
             return baseDTO;
         }
+        //校验有来源的验收验退币别是否与源单据一致
+        String puCode = purchaseMapper.validateCurrency(pu_id,purchase.getPu_currency());
+        if(puCode!=null){
+            throw new BizException(BizExceptionCode.CURRENCY_VALID);
+        }
         //更新操作
         purchaseMapper.updateByPrimaryKeySelective(purchase);
         //添加从表传输对象

+ 3 - 0
applications/purchase/purchase-server/src/main/resources/mapper/ProdInOutMapper.xml

@@ -492,5 +492,8 @@
     update prodinout set creatorId = #{userId} , creatorName=#{userName} where pi_id=#{id}
   </update>
 
+  <select id="validateCurrency" resultType="java.lang.String">
+    select pi_inoutno from prodinout where ( ifnull(pi_pucode,'') != '' or ifnull(pi_iocode,'') != '' ) and  pi_id = #{piId} and pi_currency != #{piCurrency}
+  </select>
 
 </mapper>

+ 4 - 0
applications/purchase/purchase-server/src/main/resources/mapper/PurchaseMapper.xml

@@ -477,4 +477,8 @@
     select count(1) from purchase where pu_id = #{id} and PU_ACCEPTSTATUSCODE='CLOSE'
   </select>
 
+  <select id="validateCurrency" resultType="java.lang.String">
+    select pu_code from purchase where ifnull(pu_sacode,'') != '' and  pu_id = #{puId} and pu_currency != #{puCurrency}
+  </select>
+
 </mapper>

+ 2 - 0
applications/purchase/purchase-server/src/main/resources/mapper/PurchaseReportMapper.xml

@@ -51,6 +51,8 @@
     <result column="pu_text3" property="pu_text3" jdbcType="VARCHAR" />
     <result column="pu_text4" property="pu_text4" jdbcType="VARCHAR" />
     <result column="pu_text5" property="pu_text5" jdbcType="VARCHAR" />
+    <result column="pu_currency" property="pu_currency" jdbcType="VARCHAR" />
+    <result column="pu_rate" property="pu_rate" jdbcType="DOUBLE" />
     <result column="PD_ID" property="pd_id" jdbcType="INTEGER" />
     <result column="PD_PUID" property="pd_puid" jdbcType="INTEGER" />
     <result column="PD_CODE" property="pd_code" jdbcType="VARCHAR" />

+ 4 - 0
applications/sale/sale-dto/src/main/java/com/usoftchina/saas/sale/dto/PurchaseDTO.java

@@ -66,4 +66,8 @@ public class PurchaseDTO extends CommonBaseEntity implements Serializable {
     private Long pu_said;
 
     private String pu_sacode;
+
+    private String pu_currency;
+
+    private Double pu_rate;
 }

+ 2 - 0
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/mapper/ProdInOutMapper.java

@@ -49,4 +49,6 @@ public interface ProdInOutMapper extends CommonBaseMapper<ProdInOut> {
     void updateCreator(@Param("userId") Long userId, @Param("userName") String userName, @Param("id") Long id);
 
     Integer checkSaleInQty(Long pi_id);
+
+    String validateCurrency(@Param("piId") Long piId, @Param("piCurrency") String piCurrency);
 }

+ 4 - 2
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/po/SaleList.java

@@ -71,6 +71,10 @@ public class SaleList implements Serializable {
 
     private Double sa_nettotal;
 
+    private String sa_currency;
+
+    private Double sa_rate;
+
     private Integer sd_id;
 
     private Integer sd_said;
@@ -117,8 +121,6 @@ public class SaleList implements Serializable {
     //已转数
     private Double sd_yqty;
 
-    private String sa_currency;
-
     //private ProductDTO productDTO;级联属性会导致分页查询出现BUG,设置数与查询数量不一致
     private Long pr_id;
     private String pr_code;

+ 4 - 0
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/po/report/SaleProfitView.java

@@ -66,4 +66,8 @@ public class SaleProfitView {
     private String pr_brand;
 
     private String pr_orispeccode;
+
+    private String pi_currency;
+
+    private Double pi_rate;
 }

+ 7 - 1
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/service/impl/ProdInOutServiceImpl.java

@@ -148,6 +148,11 @@ public class ProdInOutServiceImpl extends CommonBaseServiceImpl<ProdInOutMapper,
             messageLogService.save(baseDTO);
             return baseDTO;
         }
+        //校验来源销售订单或出货单 币别,必须与来源保持一致
+        String piInoutno = prodInOutMapper.validateCurrency(pi_id,prodInOut.getPi_currency());
+        if(piInoutno!=null){
+            throw new BizException(BizExceptionCode.CURRENCY_VALID);
+        }
         //更新操作
         prodInOutMapper.updateByPrimaryKeySelective(prodInOut);
         //添加从表传输对象
@@ -497,8 +502,9 @@ public class ProdInOutServiceImpl extends CommonBaseServiceImpl<ProdInOutMapper,
         //来源的单号
         targetPi.setPi_ioid(sourcePi.getId());
         targetPi.setPi_iocode(sourcePi.getPi_inoutno());
-        //币别、业务员
+        //币别、业务员、汇率
         targetPi.setPi_currency(sourcePi.getPi_currency());
+        targetPi.setPi_rate(sourcePi.getPi_rate());
         targetPi.setPi_sellerid(sourcePi.getPi_sellerid());
         targetPi.setPi_sellercode(sourcePi.getPi_sellercode());
         targetPi.setPi_seller(sourcePi.getPi_seller());

+ 5 - 1
applications/sale/sale-server/src/main/java/com/usoftchina/saas/sale/service/impl/SaleServiceImpl.java

@@ -509,8 +509,9 @@ public class SaleServiceImpl implements SaleService{
 
         prodInOut.setPi_prstatus(Status.RECNONE.getDisplay());
         prodInOut.setPi_prstatuscode(Status.RECNONE.name());
-        //币别 业务员
+        //币别 业务员 汇率
         prodInOut.setPi_currency(sale.getSa_currency());
+        prodInOut.setPi_rate(sale.getSa_rate());
         prodInOut.setPi_sellerid(sale.getSa_sellerid());
         prodInOut.setPi_sellercode(sale.getSa_sellercode());
         prodInOut.setPi_seller(sale.getSa_seller());
@@ -630,6 +631,9 @@ public class SaleServiceImpl implements SaleService{
         purchase.setCreatorId(BaseContextHolder.getUserId());
         purchase.setCreateTime(new Date());
         purchase.setCreatorName(BaseContextHolder.getUserName());
+        //币别、汇率
+        purchase.setPu_currency(sale.getSa_currency());
+        purchase.setPu_rate(sale.getSa_rate());
         saleMapper.turnPurchase(purchase);
         Long purchaseId = purchase.getId();
         for (SaleList detail : dateilList){

+ 5 - 0
applications/sale/sale-server/src/main/resources/mapper/ProdInOutMapper.xml

@@ -621,4 +621,9 @@ update ProdInOut SET
     select count(1) from prodinout left join prodiodetail a on pi_id=pd_piid where
     pi_class='销售退货单' and ifnull(pd_inqty,0) > (select ifnull(pd_outqty,0) from prodiodetail where pd_id=a.pd_ioid) and pi_id=#{pi_id};
   </select>
+
+  <select id="validateCurrency" resultType="java.lang.String">
+    select pi_inoutno from prodinout where ( ifnull(pi_sacode,'') != '' or ifnull(pi_iocode,'') != '' ) and  pi_id = #{piId} and pi_currency != #{piCurrency}
+  </select>
+
 </mapper>

+ 3 - 1
applications/sale/sale-server/src/main/resources/mapper/SaleListMapper.xml

@@ -31,6 +31,8 @@
         <result column="sa_text5" property="sa_text5" jdbcType="VARCHAR" />
         <result column="sa_seller" property="sa_seller" jdbcType="VARCHAR" />
         <result column="sa_sellerid" property="sa_sellerid" jdbcType="INTEGER" />
+        <result column="sa_currency" property="sa_currency" jdbcType="VARCHAR"/>
+        <result column="sa_rate" property="sa_rate" jdbcType="DOUBLE" />
         <result column="sd_id" property="sd_id" jdbcType="INTEGER" />
         <result column="sd_said" property="sd_said" jdbcType="INTEGER" />
         <result column="sd_detno" property="sd_detno" jdbcType="INTEGER" />
@@ -54,7 +56,7 @@
         <result column="sd_text3" property="sd_text3" jdbcType="VARCHAR" />
         <result column="sd_text4" property="sd_text4" jdbcType="VARCHAR" />
         <result column="sd_text5" property="sd_text5" jdbcType="VARCHAR" />
-        <result column="sa_currency" property="sa_currency" jdbcType="VARCHAR"/>
+
         <result column="pr_id" property="pr_id"/>
         <result column="pr_code" property="pr_code"/>
         <result column="pr_detail" property="pr_detail"/>

+ 13 - 0
applications/sale/sale-server/src/main/resources/mapper/SaleMapper.xml

@@ -583,6 +583,12 @@
       <if test="pu_sacode != null" >
         pu_sacode,
       </if>
+      <if test="pu_currency != null" >
+        pu_currency,
+      </if>
+      <if test="pu_rate != null" >
+        pu_rate,
+      </if>
     </trim>
     <trim prefix="values (" suffix=")" suffixOverrides="," >
       <if test="pu_code != null" >
@@ -687,6 +693,12 @@
       <if test="pu_sacode != null" >
         #{pu_sacode,jdbcType=VARCHAR},
       </if>
+      <if test="pu_currency != null" >
+        #{pu_currency,jdbcType=VARCHAR},
+      </if>
+      <if test="pu_rate != null" >
+        #{pu_rate,jdbcType=DOUBLE},
+      </if>
     </trim>
   </insert>
 
@@ -730,4 +742,5 @@
     update saledetail set sd_delivery = (select sa_delivery from sale where sa_id =#{id}  and sa_delivery is not null)
      where  sd_said = #{id}  and sd_delivery is null and exists (select 1 from sale where sa_id =#{id}  and sa_delivery is not null)
   </update>
+
 </mapper>

+ 2 - 0
applications/sale/sale-server/src/main/resources/mapper/SaleProfitViewMapper.xml

@@ -7,6 +7,8 @@
     <result column="sa_sellercode" property="sa_sellercode" jdbcType="VARCHAR" />
     <result column="sa_seller" property="sa_seller" jdbcType="VARCHAR" />
     <result column="sa_date" property="sa_date" jdbcType="TIMESTAMP" />
+    <result column="pi_currency" property="pi_currency" jdbcType="VARCHAR" />
+    <result column="pi_rate" property="pi_rate" jdbcType="DOUBLE" />
     <result column="pi_date" property="pi_date" jdbcType="TIMESTAMP" />
     <result column="pd_inoutno" property="pd_inoutno" jdbcType="VARCHAR" />
     <result column="pd_piclass" property="pd_piclass" jdbcType="VARCHAR" />

+ 1 - 0
applications/transfers/pom.xml

@@ -17,6 +17,7 @@
         <module>transfers-server</module>
         <module>mall-api</module>
         <module>transfers-dto</module>
+        <module>transfers-auth</module>
     </modules>
 
 </project>

+ 39 - 0
applications/transfers/transfers-auth/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>transfers</artifactId>
+        <groupId>com.usoftchina.saas</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>transfers-auth</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-netflix-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>auth-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>io.github.openfeign</groupId>
+            <artifactId>feign-core</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 15 - 0
applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/EnableOpenApiAuthClient.java

@@ -0,0 +1,15 @@
+package com.usoftchina.saas.transfers.auth;
+
+
+import com.usoftchina.saas.transfers.auth.configuration.OpenApiConfiguration;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Import(OpenApiConfiguration.class)
+@Documented
+@Inherited
+public @interface EnableOpenApiAuthClient {
+}

+ 15 - 0
applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/annotation/IgnoreOpenApiAuth.java

@@ -0,0 +1,15 @@
+package com.usoftchina.saas.transfers.auth.annotation;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 对外接口忽略鉴权
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value={ElementType.METHOD,ElementType.TYPE})
+public @interface IgnoreOpenApiAuth {
+}

+ 39 - 0
applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/configuration/OpenApiConfig.java

@@ -0,0 +1,39 @@
+package com.usoftchina.saas.transfers.auth.configuration;
+import org.springframework.beans.factory.annotation.Value;
+/**
+ * @author: guq
+ * @create: 2019-01-10 15:58
+ **/
+public class OpenApiConfig {
+
+    @Value("${openapi.signatureParam:_signature}")
+    private String signatureParam;
+    @Value("${openapi.timestampParam:_timestamp}")
+    private String timestampParam;
+    @Value("${openapi.timeout:60000}")
+    private int timeout;
+
+    public String getSignatureParam() {
+        return signatureParam;
+    }
+
+    public void setSignatureParam(String signatureParam) {
+        this.signatureParam = signatureParam;
+    }
+
+    public String getTimestampParam() {
+        return timestampParam;
+    }
+
+    public void setTimestampParam(String timestampParam) {
+        this.timestampParam = timestampParam;
+    }
+
+    public int getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(int timeout) {
+        this.timeout = timeout;
+    }
+}

+ 29 - 0
applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/configuration/OpenApiConfiguration.java

@@ -0,0 +1,29 @@
+package com.usoftchina.saas.transfers.auth.configuration;
+
+import com.usoftchina.saas.transfers.auth.interceptor.OpenApiAuthInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @author: guq
+ * @create: 2019-01-10 16:04
+ **/
+@Configuration
+public class OpenApiConfiguration implements WebMvcConfigurer {
+    @Bean
+    public OpenApiConfig openApiConfig() {
+        return new OpenApiConfig();
+    }
+
+    @Bean
+    public OpenApiAuthInterceptor openApiAuthInterceptor() {
+        return new OpenApiAuthInterceptor();
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(openApiAuthInterceptor());
+    }
+}

+ 90 - 0
applications/transfers/transfers-auth/src/main/java/com/usoftchina/saas/transfers/auth/interceptor/OpenApiAuthInterceptor.java

@@ -0,0 +1,90 @@
+package com.usoftchina.saas.transfers.auth.interceptor;
+
+import com.usoftchina.saas.transfers.auth.annotation.IgnoreOpenApiAuth;
+import com.usoftchina.saas.transfers.auth.configuration.OpenApiConfig;
+import com.usoftchina.saas.utils.StringUtils;
+import com.usoftchina.saas.utils.http.HmacUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+/**
+ * @author: guq
+ * @create: 2019-01-10 16:05
+ **/
+public class OpenApiAuthInterceptor extends HandlerInterceptorAdapter{
+
+    @Autowired
+    private OpenApiConfig openApiConfig;
+
+    // 已使用签名
+    private Map<String, Long> signatureCache = new ConcurrentHashMap<>();
+
+    private static Logger logger = LoggerFactory.getLogger(OpenApiAuthInterceptor.class);
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        if (handler instanceof HandlerMethod) {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            // 配置该注解,说明不进行用户拦截
+            IgnoreOpenApiAuth annotation = handlerMethod.getBeanType().getAnnotation(IgnoreOpenApiAuth.class);
+            if (annotation == null) {
+                annotation = handlerMethod.getMethodAnnotation(IgnoreOpenApiAuth.class);
+            }
+            if (annotation != null) {
+                return super.preHandle(request, response, handler);
+            }
+
+            String sign = request.getParameter(openApiConfig.getSignatureParam());
+            if (!StringUtils.isEmpty(sign)) {
+                String urlMessage = request.getRequestURL() + "?"
+                        + request.getQueryString().substring(0, request.getQueryString().indexOf(openApiConfig.getSignatureParam()) - 1);
+
+                logger.info("urlMessage:{}", urlMessage);
+                String servletPath = request.getServletPath();
+                logger.info("servletPath:{}", servletPath);
+                boolean check = false;
+
+                if (servletPath.indexOf("openapi") > -1) {
+                    check = sign.equals(HmacUtils.encode(urlMessage));
+                }
+                if (check) {
+                    String timestamp = request.getParameter(openApiConfig.getTimestampParam());
+                    long now = System.currentTimeMillis();
+                    if (!StringUtils.isEmpty(timestamp) && Math.abs(now - Long.parseLong(timestamp)) <= openApiConfig.getTimeout()
+                            && !signatureCache.containsKey(sign)) {
+                        // 加入历史记录
+                        signatureCache.put(sign, now);
+                        return true;
+                    }
+                }
+            }
+            response.setStatus(HttpStatus.FORBIDDEN.value());
+            return false;
+        } else {
+        }
+        return super.preHandle(request, response, handler);
+    }
+
+    /**
+     * 清除签名池历史记录
+     */
+    @Scheduled(cron = "0 0/3 * * * ?")
+    public void clearCache() {
+        long now = System.currentTimeMillis();
+        for (String key : signatureCache.keySet()) {
+            long time = signatureCache.get(key);
+            if (now - time > openApiConfig.getTimeout()) {
+                signatureCache.remove(key);
+            }
+        }
+    }
+}

+ 4 - 0
applications/transfers/transfers-server/pom.xml

@@ -65,6 +65,10 @@
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.usoftchina.saas</groupId>
+            <artifactId>transfers-auth</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 2 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/TransfersApplication.java

@@ -1,5 +1,6 @@
 package com.usoftchina.saas.transfers;
 
+import com.usoftchina.saas.transfers.auth.EnableOpenApiAuthClient;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -10,6 +11,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
 @EnableEurekaClient
 @EnableFeignClients("com.usoftchina.saas")
 @MapperScan("com.usoftchina.saas.transfers.mapper")
+@EnableOpenApiAuthClient
 public class TransfersApplication {
     public static void main(String[] args) {
         SpringApplication.run(TransfersApplication.class, args);

+ 2 - 0
applications/transfers/transfers-server/src/main/java/com/usoftchina/saas/transfers/controller/TransfersController.java

@@ -1,6 +1,7 @@
 package com.usoftchina.saas.transfers.controller;
 
 import com.usoftchina.saas.base.Result;
+import com.usoftchina.saas.transfers.auth.annotation.IgnoreOpenApiAuth;
 import com.usoftchina.saas.transfers.service.SendService;
 import com.usoftchina.saas.utils.StringUtils;
 import com.usoftchina.saas.transfers.dto.MessageInfo;
@@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
  **/
 @RestController
 @RequestMapping("/send")
+@IgnoreOpenApiAuth
 public class TransfersController {
 
     @Autowired

+ 4 - 1
framework/core/pom.xml

@@ -65,7 +65,10 @@
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>transmittable-thread-local</artifactId>
-            <version>2.10.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>

+ 222 - 0
framework/core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextScheduler.java

@@ -0,0 +1,222 @@
+/**
+ * Copyright 2013 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.netflix.hystrix.strategy.concurrency;
+
+import java.util.concurrent.*;
+
+import com.alibaba.ttl.TtlRunnable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import rx.*;
+import rx.functions.Action0;
+import rx.functions.Func0;
+import rx.internal.schedulers.ScheduledAction;
+import rx.subscriptions.*;
+
+import com.netflix.hystrix.HystrixThreadPool;
+import com.netflix.hystrix.strategy.HystrixPlugins;
+
+/**
+ * 处理Hystrix线程隔离导致ThreadLocal数据丢失问题
+ * @author: guq
+ * @create: 2019-01-10 11:05
+ */
+public class HystrixContextScheduler extends Scheduler {
+
+    private final HystrixConcurrencyStrategy concurrencyStrategy;
+    private final Scheduler actualScheduler;
+    private final HystrixThreadPool threadPool;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HystrixContextScheduler.class);
+
+    public HystrixContextScheduler(Scheduler scheduler) {
+        this.actualScheduler = scheduler;
+        this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
+        this.threadPool = null;
+    }
+
+    public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, Scheduler scheduler) {
+        this.actualScheduler = scheduler;
+        this.concurrencyStrategy = concurrencyStrategy;
+        this.threadPool = null;
+    }
+
+    public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, HystrixThreadPool threadPool) {
+        this(concurrencyStrategy, threadPool, new Func0<Boolean>() {
+            @Override
+            public Boolean call() {
+                return true;
+            }
+        });
+    }
+
+    public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, HystrixThreadPool threadPool, Func0<Boolean> shouldInterruptThread) {
+        this.concurrencyStrategy = concurrencyStrategy;
+        this.threadPool = threadPool;
+        this.actualScheduler = new ThreadPoolScheduler(threadPool, shouldInterruptThread);
+    }
+
+    @Override
+    public Worker createWorker() {
+        return new HystrixContextSchedulerWorker(actualScheduler.createWorker());
+    }
+
+    private class HystrixContextSchedulerWorker extends Worker {
+
+        private final Worker worker;
+
+        private HystrixContextSchedulerWorker(Worker actualWorker) {
+            this.worker = actualWorker;
+        }
+
+        @Override
+        public void unsubscribe() {
+            worker.unsubscribe();
+        }
+
+        @Override
+        public boolean isUnsubscribed() {
+            return worker.isUnsubscribed();
+        }
+
+        @Override
+        public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
+            if (threadPool != null) {
+                if (!threadPool.isQueueSpaceAvailable()) {
+                    throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.");
+                }
+            }
+            return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action), delayTime, unit);
+        }
+
+        @Override
+        public Subscription schedule(Action0 action) {
+            if (threadPool != null) {
+                if (!threadPool.isQueueSpaceAvailable()) {
+                    throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.");
+                }
+            }
+            return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action));
+        }
+
+    }
+
+    private static class ThreadPoolScheduler extends Scheduler {
+
+        private final HystrixThreadPool threadPool;
+        private final Func0<Boolean> shouldInterruptThread;
+
+        public ThreadPoolScheduler(HystrixThreadPool threadPool, Func0<Boolean> shouldInterruptThread) {
+            this.threadPool = threadPool;
+            this.shouldInterruptThread = shouldInterruptThread;
+        }
+
+        @Override
+        public Worker createWorker() {
+            return new ThreadPoolWorker(threadPool, shouldInterruptThread);
+        }
+
+    }
+
+    /**
+     * Purely for scheduling work on a thread-pool.
+     * <p>
+     * This is not natively supported by RxJava as of 0.18.0 because thread-pools
+     * are contrary to sequential execution.
+     * <p>
+     * For the Hystrix case, each Command invocation has a single action so the concurrency
+     * issue is not a problem.
+     */
+    private static class ThreadPoolWorker extends Worker {
+
+        private final HystrixThreadPool threadPool;
+        private final CompositeSubscription subscription = new CompositeSubscription();
+        private final Func0<Boolean> shouldInterruptThread;
+
+        public ThreadPoolWorker(HystrixThreadPool threadPool, Func0<Boolean> shouldInterruptThread) {
+            this.threadPool = threadPool;
+            this.shouldInterruptThread = shouldInterruptThread;
+        }
+
+        @Override
+        public void unsubscribe() {
+            subscription.unsubscribe();
+        }
+
+        @Override
+        public boolean isUnsubscribed() {
+            return subscription.isUnsubscribed();
+        }
+
+        @Override
+        public Subscription schedule(final Action0 action) {
+            if (subscription.isUnsubscribed()) {
+                // don't schedule, we are unsubscribed
+                return Subscriptions.unsubscribed();
+            }
+
+            // This is internal RxJava API but it is too useful.
+            ScheduledAction sa = new ScheduledAction(action);
+
+            subscription.add(sa);
+            sa.addParent(subscription);
+
+            ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool.getExecutor();
+            LOGGER.info("创建新线程...............");
+            Runnable runnable = TtlRunnable.get(sa);
+            FutureTask<?> f = (FutureTask<?>) executor.submit(runnable);
+            sa.add(new FutureCompleterWithConfigurableInterrupt(f, shouldInterruptThread, executor));
+
+            return sa;
+        }
+
+        @Override
+        public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
+            throw new IllegalStateException("Hystrix does not support delayed scheduling");
+        }
+    }
+
+    /**
+     * Very similar to rx.internal.schedulers.ScheduledAction.FutureCompleter, but with configurable interrupt behavior
+     */
+    private static class FutureCompleterWithConfigurableInterrupt implements Subscription {
+        private final FutureTask<?> f;
+        private final Func0<Boolean> shouldInterruptThread;
+        private final ThreadPoolExecutor executor;
+
+        private FutureCompleterWithConfigurableInterrupt(FutureTask<?> f, Func0<Boolean> shouldInterruptThread, ThreadPoolExecutor executor) {
+            this.f = f;
+            this.shouldInterruptThread = shouldInterruptThread;
+            this.executor = executor;
+        }
+
+        @Override
+        public void unsubscribe() {
+            executor.remove(f);
+            if (shouldInterruptThread.call()) {
+                f.cancel(true);
+            } else {
+                f.cancel(false);
+            }
+        }
+
+        @Override
+        public boolean isUnsubscribed() {
+            return f.isCancelled();
+        }
+    }
+
+}

+ 11 - 9
frontend/operation-web/app/model/statistical/CompanyInfo.js

@@ -1,14 +1,16 @@
 Ext.define('saas.model.statistical.CompanyInfo', {
     extend: 'saas.model.Base',
     fields: [
-        { name: 'name', type: 'string' },
-        { name: 'business_code', type: 'string' },
-        { name: 'address', type: 'string' },
-        { name: 'tel', type: 'string' },
-        { name: 'fax', type: 'string' }, // 传真
-        { name: 'realname', type: 'string' },
-        { name: 'mobile', type: 'string' },
-        { name: 'uu', type: 'int' },
-        { name: 'create_time', type: 'date' },
+        { name: 'cd_id', type: 'int' },
+        { name: 'cd_companyid', type: 'int' },
+        { name: 'cd_accountnum', type: 'int' },
+        { name: 'cd_productnum', type: 'int' },
+        { name: 'cd_customernum', type: 'int' },
+        { name: 'cd_vendornum', type: 'int' },
+        { name: 'cd_salenum', type: 'int' },
+        { name: 'cd_purchasenum', type: 'int' },
+        { name: 'cd_prodionum', type: 'int' },
+        { name: 'cd_paynum', type: 'int' },
+        { name: 'cd_receivenum', type: 'int' },
     ]
 });

+ 26 - 0
frontend/operation-web/app/util/BaseUtil.js

@@ -215,6 +215,32 @@ Ext.define('saas.util.BaseUtil', {
             var xr = (new Array(arr[1].length > decimalPrecision ? decimalPrecision : arr[1].length)).fill('0');
             var format = f1 + '.' + xr.join('');
             return Ext.util.Format.number(value, format);
+        },
+
+        showLoginWin: function() {
+            var main = Ext.getCmp('rootView'),
+            win = Ext.getCmp('relogin');
+
+            if(!win) {
+                win = main.add({
+                    xtype: 'relogin'
+                });
+            }
+
+            win.show();
+        },
+
+        hashcode: function(obj) {
+            let str = JSON.stringify(obj);
+            let hash = 0,
+                i, chr, len;
+            if (str.length === 0) return hash;
+            for (i = 0, len = str.length; i < len; i++) {
+                chr = str.charCodeAt(i);
+                hash = ((hash << 5) - hash) + chr;
+                hash |= 0;
+            }
+            return hash;
         }
     }
 });

+ 344 - 0
frontend/operation-web/app/util/FormUtil.js

@@ -0,0 +1,344 @@
+Ext.define('saas.util.FormUtil', {
+
+    statics: {
+
+        setItems: function(form) {
+            let me = this,
+            defaultItems = form.defaultItems;
+
+            let items = [];
+
+            items = me.applyItemsGroup(defaultItems || []);
+            items = me.initItems(items);
+
+            form.configItems = items;
+
+            items = me.applyDefaultItems(form, items);
+
+            form.removeAll();
+            form.addItems(items);
+
+            form.fireEvent('afterSetItems', form, items);
+
+            me.loadData(form);
+        },
+
+        applyItemsGroup: function(items) {
+            let groups = [],
+            groupCount = 0,
+            newItems = [];
+
+            Ext.Array.each(items, function(it, index) {
+                let item = Object.assign({}, it),
+                groupName = item.group;
+                if(!!groupName) {
+                    let idx = groups.findIndex(function(g) {
+                        return g.title == groupName;
+                    }),group;
+
+                    if(idx == -1) {
+                        group = {
+                            title: groupName,
+                            count: 1
+                        };
+                        groups.push(group);
+                    }else {
+                        group = groups[idx];
+                        group.count++;
+                    }
+                }
+                newItems.push(item);
+            });
+
+            Ext.Array.sort(newItems, function(a, b) {
+                let gs = groups.concat([{
+                    title: '_nogroup'
+                }]);
+                a.group = a.group || '_nogroup';
+                let v1 = gs.findIndex(function(g) {
+                    return g.title == a.group;
+                })
+                let v2 = gs.findIndex(function(g) {
+                    return g.title == b.group;
+                })
+                return v1 - v2;
+            });
+
+            Ext.Array.each(groups, function(g) {
+                let idx = newItems.findIndex(function(i) {
+                    return i.group == g.title;
+                });
+                g.index = idx;
+            });
+
+            Ext.Array.each(groups, function(group, index) {
+                let formIndex = group.index;
+                delete group.index;
+                Ext.Array.insert(newItems, formIndex + index, [{
+                    xtype: 'separatefield',
+                    name: 'group' + (++groupCount),
+                    html: group.title,
+                    fieldLabel: group.title || '分组' + groupCount
+                }]);
+            });
+
+            return newItems;
+        },
+
+        initItems: function(items) {
+            let itemCount = detailCount = 1, newItems = [];
+            Ext.Array.each(items, function(it, i) {
+                let item = Object.assign({}, it);
+                if(item.xtype == 'detailGridField') {
+                    let columns = item.columns,
+                    colCount = 1;
+                    Ext.Array.each(columns, function(col, j) {
+                        if((col.hidden || col.width == 0 || !col.dataIndex) && (!col.hasOwnProperty('initHidden') || col.initHidden)) {
+                            Ext.applyIf(col, {
+                                index: -1,
+                                initHidden: true
+                            });
+                        }else {
+                            Ext.applyIf(col, {
+                                text: '',
+                                hidden: false,
+                                index: colCount++,
+                                allowBlank: true,
+                                width: 100,
+                                initHidden: false
+                            });
+                        }
+                    });
+                    if(!columns[columns.length - 1].flex) {
+                        columns.push({
+                            dataIndex: '',
+                            initHidden: true,
+                            flex: 1,
+                            allowBlank: true
+                        });
+                    }
+                    Ext.applyIf(item, {
+                        allowBlank: false,
+                        columnWidth: 1,
+                        gname: 'detail' + detailCount,
+                        fieldLabel: '从表' + (detailCount++),
+                    });
+                }else if(item.xtype == 'hidden') {
+                    Ext.applyIf(item, {
+                        fieldLabel: '',
+                        hidden: true,
+                        initHidden: true,
+                    });
+                }else if(item.xtype == 'separatefield') {
+                    Ext.applyIf(item, {
+                        fieldLabel: item.html,
+                        columnWidth: 1,
+                    });
+                }else {
+                    Ext.applyIf(item, {
+                        fieldLabel: '',
+                        columnWidth: 0.25,
+                    });
+                }
+
+                if(item.hidden) {
+                    if(item.initHidden || !item.hasOwnProperty('initHidden')) {
+                        Ext.applyIf(item, {
+                            index: -1,
+                            initHidden: true
+                        });
+                    }else {
+                        Ext.applyIf(item, {
+                            index: itemCount++,
+                            initHidden: false
+                        }); 
+                    }
+                }else {
+                    Ext.applyIf(item, {
+                        index: itemCount++,
+                        initHidden: false
+                    });
+                }
+
+                Ext.applyIf(item, {
+                    name: 'item' + i,
+                    hidden: false,
+                    allowBlank: true,
+                    group: undefined,
+                });
+
+                newItems.push(item);
+            });
+
+            Ext.Array.sort(newItems, function(a, b) {
+                return a.index - b.index;
+            });
+
+            return newItems;
+        },
+
+        /**
+         * 处理formitems的一些默认配置
+         */
+        applyDefaultItems: function(form, items) {
+            let me = this,
+            formModel = form.getViewModel();
+
+            Ext.Array.each(items, function(item) {
+
+                // 设置必填
+                if(item.allowBlank==false){
+                    // TODO 需要判断类型
+                    item.beforeLabelTextTpl = "<font color=\"red\" style=\"position:relative; top:2px;right:2px; font-weight: bolder;\">*</font>";
+                }
+
+                if(item.xtype == 'textfield') {
+                    Ext.applyIf(item, {
+                        maxLength: 50
+                    });
+                }else if(item.xtype == 'datefield') {
+                    Ext.applyIf(item, {
+                        editable: false,
+                        format: 'Y-m-d'
+                    });
+                }else if(item.xtype == 'numberfield') {
+                    Ext.applyIf(item, {
+                        hideTrigger: true, // 隐藏trigger
+                        mouseWheelEnabled: false // 取消滚轮事件
+                    });
+                    // 设置默认值为0
+                    formModel.set(item.name, 0);
+                }else if(item.xtype == 'condbfindtrigger') {
+                    item.isConField = true;
+                }else if(item.xtype == 'detailGridField') {
+                    let index = form.detailCount;
+                    let columns = item.columns,
+                    cnames = columns.filter(function(c) {
+                        return c.dataIndex && !c.ignore;
+                    }).map(function(c) {
+                        return c.dataIndex
+                    }),
+                    defaultValueColumns = {};
+
+                    Ext.Array.each(columns, function(c) {
+
+                        if(c.dataIndex && c.defaultValue) {
+                            defaultValueColumns[c.dataIndex] = c.defaultValue;
+                        }
+
+                        // 不可锁定
+                        Ext.applyIf(c, {
+                            lockable: false,
+                            width: 120
+                        });
+
+                        //必填
+                        Ext.applyIf(c, {
+                            allowBlank: true
+                        });
+                        if(!c.allowBlank){
+                            c.cls = 'x-grid-necessary';
+                        }
+
+                        if(c.xtype == 'textfield') {
+                            Ext.applyIf(c, {
+                                maxLength: 50
+                            });
+                        }else if(c.xtype == 'datecolumn') {
+                            Ext.applyIf(c, {
+                                format: 'Y-m-d'
+                            });
+                        }else if(c.xtype == 'numbercolumn') {
+                            Ext.applyIf(c, {
+                                align: 'end'
+                            });
+                        }
+                        
+                        let editor = c.editor;
+                        if(editor) {
+                            Ext.applyIf(editor, {
+                                selectOnFocus: true
+                            });
+                            if(editor.xtype == 'numberfield') {
+                                Ext.applyIf(editor, {
+                                    hideTrigger: true, // 隐藏trigger
+                                    mouseWheelEnabled: false // 取消滚轮事件
+                                });
+                            }else if(editor.xtype == 'datefield') {
+                                Ext.apply(editor, {
+                                    format: 'Y-m-d'
+                                });
+                                Ext.applyIf(editor, {
+                                    editable: false
+                                });
+                            }
+                        }
+                    });
+
+                    cnames.push(item.detnoColumn);
+
+                    formModel.set('detail' + index + '.detailBindFields', cnames);
+                    item.bind = {
+                        store: '{detail' + index + '.detailStore}'
+                    };     
+                    formModel.set('detail' + index + '.detailStore', Ext.create('Ext.data.Store', {
+                        model:item.storeModel,
+                        data: [],
+                        listeners: {
+                            datachanged: function(s, eOpts) {
+                                let g = form.query('detailGridField')[index];
+                                g.fireEvent('datachanged', g, s, eOpts);
+                            },
+                            // 为新增行设置默认值
+                            add: function(store, records, index, eOpts) {
+                                Ext.Array.each(records, function(r) {
+                                    for(k in defaultValueColumns) {
+                                        r.set(k, defaultValueColumns[k]);
+                                    }
+                                    r.commit();
+                                });
+                            }
+                        }
+                    }));
+
+                    form.detailCount++;
+                }
+            });
+
+            return items;
+        },
+
+        loadData: function(form) {
+            let me = this;
+            form.setLoading(true);
+            if(form.initId && form.initId!=0) {
+                let url = form._readUrl + '/' + form.initId;
+                saas.util.BaseUtil.request({url })
+                .then(function(res) {
+                    form.setLoading(false);
+                    if(res.success) {
+                        let d = res.data;
+                        let o = {
+                            main: d.main
+                        };
+                        if(d.hasOwnProperty('items')) {
+                            o.detail0 = d.items;
+                        }else {
+                            let idx = 1;
+                            while(d.hasOwnProperty('items' + idx)) {
+                                o['detail' + (idx - 1)] = d['items' + idx];
+                                idx++;
+                            }
+                        }
+                        form.initFormData(o);
+                        form.fireEvent('load', form, o);
+                    }
+                })
+                .catch(function(e) {
+                    form.setLoading(false);
+                    saas.util.BaseUtil.showErrorToast('读取单据数据错误: ' + e.message);
+                });
+            }
+        }
+    }
+});

+ 103 - 0
frontend/operation-web/app/view/auth/ReLogin.js

@@ -0,0 +1,103 @@
+/**
+ * 重新登录弹窗
+ */
+Ext.define('saas.view.auth.ReLogin', {
+    extend: 'Ext.window.Window',
+    xtype: 'relogin',
+    id: 'relogin',
+
+    controller: 'relogin',
+
+    title: '重新登录',
+    cls: 'x-window-dbfind x-relogin-win',
+    layout: 'fit',
+    modal: true,
+    width: 350,
+    bodyPadding: 20,
+
+    items: [{
+        xtype: 'form',
+        reference: 'reloginform',
+        defaults: {
+            anchor: '100%',
+            labelWidth: 120
+        },
+        items: [{
+            xtype: 'displayfield',
+            height: 32,
+            cls: 'infoLabel',
+            value: '会话已过期,请重新登录',
+        }, {
+            xtype: 'textfield',
+            name: 'username',
+            emptyText: '请输入账号/邮箱/手机号',
+            fieldLabel: '账号',
+            bind: '{account.username}',
+            readOnly: true,
+            hideLabel: true,
+            allowBlank: false
+        }, {
+            xtype: 'textfield',
+            hideLabel: true,
+            fieldLabel: '密码',
+            emptyText: '密码',
+            inputType: 'password',
+            name: 'password',
+            // bind: '{password}',
+            allowBlank: false,
+            enableKeyEvents: true,
+            listeners: {
+                keydown: {
+                    fn: function(th, e, eOpts) {
+                        if(e.keyCode == 13) {
+                            this.up('relogin').getController().onLogin();
+                        }
+                    }
+                }
+            }
+        }, {
+            xtype: 'container',
+            layout: 'hbox',
+            items: [{
+                xtype: 'checkboxfield',
+                flex: 1,
+                cls: 'form-panel-font-color rememberMeCheckbox',
+                height: 30,
+                name: 'remember',
+                bind: '{remember}',
+                boxLabel: '记住密码'
+            }, {
+                xtype: 'box',
+                // html: '<a href="#passwordreset" class="link-forgot-password"> 忘记密码 ?</a>'
+            }]
+        }, {
+            xtype: 'container',
+            layout: {
+                type: 'hbox',
+                pack: 'center'
+            },
+            items: [{
+                xtype: 'button',
+                text: '登录',
+                formBind: true,
+                width: 68,
+                margin: '0 12 0 0',
+                listeners: {
+                    click: 'onLogin'
+                }
+            }, {
+                xtype: 'button',
+                text: '取消',
+                width: 68,
+                margin: '0 0 0 12',
+                listeners: {
+                    click: 'onCancel'
+                }
+            }]
+        }]
+    }],
+
+    listeners: {
+        afterrender: 'onAfterRender',
+    }
+});

+ 15 - 0
frontend/operation-web/app/view/auth/ReLogin.scss

@@ -0,0 +1,15 @@
+.x-relogin-win {
+    .infoLabel {
+        .x-form-display-field {
+            font-size: 14px;
+            vertical-align: middle;
+            line-height: 32px;
+            color: red;
+        }
+    }
+    .rememberMeCheckbox {
+        .x-form-checkbox-default,.x-form-cb-label-default.x-form-cb-label-after {
+            margin: 0;
+        }
+    }
+}

+ 103 - 0
frontend/operation-web/app/view/auth/ReLoginController.js

@@ -0,0 +1,103 @@
+Ext.define('saas.view.auth.ReLoginController', {
+    extend: 'Ext.app.ViewController',
+    alias: 'controller.relogin',
+
+    init: function () {
+        this.callParent(arguments);
+    },
+
+    onAfterRender: function() {
+        var me = this,
+        view = me.getView(),
+        model = me.getViewModel(),
+        remember = model.get('remember'),
+        password = model.get('repassword'),
+        lastSuccess = model.get('lastSuccess'),
+        form = me.lookup('reloginform'),
+        passwordField = form.getForm().findField('password');
+
+        if(remember && lastSuccess) {
+            passwordField.setValue(password);
+        }
+    },
+
+    onLogin: function () {
+        var me = this,
+            view = me.getView(),
+            viewModel = me.getViewModel(),
+            companyId = saas.util.BaseUtil.getCurrentUser().companyId,
+            form = me.lookup('reloginform'),
+            values = form.getValues();
+
+        if(!form.isValid()) {
+            return;
+        }
+
+        view.mask('请稍等...');
+
+        saas.model.Session.login(values.username, values.password, companyId)
+        .then(function (session) {
+            view.isMasked() && view.unmask();
+            viewModel.set('session', session);
+
+            session.get('account').companyId = companyId;
+            
+            me.successReLogin();
+            me.fireEvent('login', session);
+            return session;
+        })
+        .then(function(session) {
+            let headers = Ext.Ajax.getDefaultHeaders() || {};
+            
+            headers['Authorization'] = session.data.token;
+            Ext.Ajax.setDefaultHeaders(headers);
+
+            saas.util.State.set('session', session.data);
+
+            let sessionStr = session.data ? JSON.stringify(session.data) : '';
+
+            if (typeof require === 'function') {
+                let ipc = require('electron').ipcRenderer;
+                ipc.send('session.change', sessionStr);
+            }else {
+                //解析session 把data作为sessionStr
+                sessionStr = session ? JSON.stringify(session.data) : '';
+                const frame = window.frames[window.frames.length - 1];
+                frame.postMessage(sessionStr, '*');
+            }
+            return session;
+        })
+        .catch(function (error) {
+            view.isMasked() && view.unmask();
+            console.error(error);
+            saas.util.BaseUtil.showErrorToast('登录失败: ' + error.message);
+            me.failureReLogin();
+        });
+    },
+
+    successReLogin: function() {
+        var me = this,
+            view = me.getView(),
+            form = me.lookup('reloginform'),
+            values = form.getValues();
+            viewModel = me.getViewModel();
+
+        viewModel.set('repassword', values.password);
+        viewModel.set('lastSuccess', true);
+        view.close();
+    },
+
+    failureReLogin: function() {
+        var me = this,
+        viewModel = me.getViewModel();
+
+        viewModel.set('lastSuccess', false);
+    },
+
+    onCancel: function() {
+        var me = this,
+        view = me.getView();
+
+        view.close();
+    },
+});

+ 443 - 0
frontend/operation-web/app/view/core/form/FormPanel.js

@@ -0,0 +1,443 @@
+Ext.define('saas.view.core.form.FormPanel', {
+    extend: 'Ext.form.Panel',
+    xtype: 'core-form-formpanel',
+
+    controller: 'core-form-formpanel',
+    viewModel: 'core-form-formpanel',
+
+    cls: 'x-core-form',
+
+    detailCount: 0,
+
+    //基础属性
+    initId: 0,
+    layout: 'column',
+    autoScroll: true,
+    bodyPadding: '8 12 8 12',
+
+    fieldDefaults: {
+        margin: '0 0 10 0',
+        labelAlign: 'right',
+        labelWidth: 90,
+        columnWidth: 0.25,
+    },
+
+    items: [],
+
+    initComponent: function () {
+        let me = this;
+        me.callParent(arguments);
+        me.initFormItems();
+
+    },
+
+    initFormItems: function() {
+        let me = this;
+        saas.util.FormUtil.setItems(me);
+    },
+
+    addItems: function (items) {
+        let me = this;
+        me.setBindFields(items);
+        let formItems = me.add(items);
+        me.formItems = formItems;
+        return formItems;
+    },
+
+    /**
+     * 获取form数据
+     */
+    getFormData: function () {
+        let me = this,
+            viewModel = me.getViewModel(),
+            allData = viewModel.getData(),
+            bindFields = allData.base.bindFields,
+            detailCount = me.detailCount,
+            formData = {},
+            detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(bindFields, function(field) {
+            let v = allData[field];
+            if(Ext.isDate(v)) {
+                v = Ext.Date.format(v, 'Y-m-d H:i:s');
+            }
+            formData[field] = v;
+        });
+
+        let data = {
+            main: formData,
+        };
+
+        for (let i = 0; i < detailCount; i++) {
+            let g = detailGrids[i];
+            let gridTrueData = g.getTrueData();
+            let modelDetail = allData['detail' + i];
+            let detailBindFields = modelDetail.detailBindFields;
+
+            Ext.Array.each(gridTrueData, function(d) {
+                for(k in d) {
+                    if(!Ext.Array.contains(detailBindFields, k)) {
+                        delete d[k];
+                    }
+                }
+            });
+            
+
+            data['detail' + i] = gridTrueData;
+        }
+
+        return data;
+    },
+
+    setFormData: function (formData) {
+        let me = this,
+        main = formData.main,
+        detailCount = me.detailCount,
+        viewModel = me.getViewModel(),
+        viewData = viewModel.getData();
+
+        me.setMainData(main);
+
+        let detailGrids = me.query('detailGridField');
+
+        for (let i = 0; i < detailGrids.length; i++) {
+            let detailData = formData['detail' + i] || [];
+            me.setDetailData(detailData, i);
+        }
+    },
+
+    setMainData: function(mainData) {
+        let me = this,
+        viewModel = me.getViewModel();
+
+        viewModel.setData(mainData);
+        viewModel.notify();
+
+        me.isValid();
+    },
+
+    setDetailData: function(detailData, index) {
+        index = index || 0;
+
+        let me = this,
+        viewModel = me.getViewModel(),
+        viewData = viewModel.getData(),
+        detailGrids = me.query('detailGridField'),
+        detail = viewData['detail' + index],
+        grid = detailGrids[index],
+        detnoColumn = grid.detnoColumn,
+        store = detail.detailStore;
+
+        store.removeAll();
+        if (detailData.length > 0) {
+
+            for(let j = 0; j < detailData.length; j++) {
+                let d = detailData[j];
+                let o = {};
+                o[detnoColumn] = j + 1;
+                let r = store.add(o)[0];
+                for(let k in d) {
+                    r.set(k, d[k]);
+                }
+            }
+        }
+
+        me.isValid();
+    },
+
+    initFormData: function(data) {
+        let me = this;
+        me.setFormData(data);
+        me.clearDirty();
+    },
+
+    /**
+     * 设置需要绑定的字段
+     */
+    setBindFields: function (items) {
+        let me = this,
+            viewModel = me.getViewModel(),
+            bindFields = [];
+
+        Ext.Array.each(items, function (item) {
+            let xtype = item.xtype,
+            bind = item.bind,
+            name = item.name,
+            ignore = item.ignore,
+            defaultValue = item.defaultValue,
+            isConField = item.isConField;
+
+            item.listeners = item.listeners || {};
+            item.listeners.validChange = function() {
+                me.isValid();
+            }
+            item.listeners.validitychange = function() {
+                me.isValid();
+            }
+
+
+            if (xtype == 'detailGridField') {
+                item.defaultColumns = item.columns;
+                return;
+            }
+
+            if(item.readOnly){
+                item.defaultReadOnly = item.readOnly;
+            }
+
+            // 如果是组合放大镜
+            if(isConField) {
+                let names = item.dbfinds.map(function(d) {
+                    return d.to;
+                });
+
+                // 设置默认值
+                if (defaultValue != undefined) {
+                    
+
+                    for(let x = 0; x < names.length; x++) {
+                        viewModel.set(names[x], defaultValue[names[x]]);
+                    }
+                }
+
+                // 设置model绑定
+                if (!ignore) {
+                    for(let x = 0; x < names.length; x++) {
+                        if(!Ext.Array.contains(bindFields, names[x])) {
+                            bindFields.push(names[x]);
+                        }
+                    }
+                }
+            }else {
+                if (bind) {
+                    if (!Ext.isString(bind)) {
+                        Ext.apply(bind, {
+                            value: '{' + name + '}'
+                        });
+                    } else {
+                        item.bind = '{' + name + '}';
+                    }
+                } else {
+                    item.bind = '{' + name + '}';
+                }
+                // 设置默认值
+                if (defaultValue != undefined) {
+                    viewModel.set(name, defaultValue);
+                }
+    
+                // 设置model绑定
+                if (!ignore) {
+                    if(!Ext.Array.contains(bindFields, name)) {
+                        bindFields.push(name);
+                    }
+                }
+            }
+        });
+        viewModel.set('base.bindFields', bindFields);
+    },
+
+    clearDirty: function() {
+        let me = this;
+        let detailGrids = me.query('detailGridField');
+        let fields = me.getForm().getFields().items;
+        
+        Ext.Array.each(fields, function(f) {
+            f.resetOriginalValue ? f.resetOriginalValue() : '';
+        });
+        Ext.Array.each(detailGrids, function(g) {
+            g.clearDirty();
+        });
+    },
+
+    setEditable: function(able) {
+        let me = this,
+        viewModel = me.getViewModel(),
+        items = me.getForm().getFields().items;
+
+        let detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(detailGrids, function(g) {
+
+            g.setGridDisabled(able);
+        });
+
+        Ext.Array.each(items, function(item) {
+            if(typeof item.setReadOnly  == 'function') {
+               item.setReadOnly (item.defaultReadOnly || !able);
+            }
+        });
+    },
+
+    //overriders
+    isValid: function() {
+        let me = this;
+        let viewModel = me.getViewModel();
+        let formItems = me.formItems || [];
+        let valid = !Ext.Array.findBy(formItems, function(f) {
+            return !f.isValid();
+        });
+        let detailGrids = me.query('detailGridField');
+
+        for(let i = 0; i < detailGrids.length; i++) {
+            let g = detailGrids[i];
+            if(!g.isValid()) {
+                valid = false;
+                break;
+            }
+        }
+        viewModel.set('base.valid', valid);
+        return valid;
+    },
+
+    isDirty: function () {
+        let me = this,
+        formItems = me.formItems || [],
+        detailGrids = me.query('detailGridField'),
+        dirty = false;
+
+        for(let i = 0; i < detailGrids.length; i++) {
+            let grid = detailGrids[i];
+            if(grid.isDirty()) {
+                dirty = true;
+                break;
+            }
+        }
+
+        if(!dirty) {
+            dirty = !!Ext.Array.findBy(formItems, function(f) {
+                return f.isDirty();
+            });
+        }
+
+        return dirty;
+    },
+
+    getSaveData: function() {
+        let me = this,
+        viewModel = me.getViewModel(),
+        allData = viewModel.getData(),
+        bindFields = allData.base.bindFields,
+        detailCount = me.detailCount,
+        formData = {},
+        detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(bindFields, function(field) {
+            let v = allData[field];
+            if(Ext.isDate(v)) {
+                v = Ext.Date.format(v, 'Y-m-d H:i:s');
+            }
+            formData[field] = v;
+        });
+
+        let dirtyData = {
+            main: formData,
+        };
+
+        for (let i = 0; i < detailCount; i++) {
+            let g = detailGrids[i];
+            let gridDirtyData = g.getSaveData();
+            let modelDetail = allData['detail' + i];
+            let detailBindFields = modelDetail.detailBindFields;
+
+            Ext.Array.each(gridDirtyData, function(d) {
+                for(k in d) {
+                    if(!Ext.Array.contains(detailBindFields, k)) {
+                        delete d[k];
+                    }
+                }
+            });
+            
+
+            dirtyData['detail' + i] = gridDirtyData;
+        }
+
+        return dirtyData;
+    },
+
+    getDirtyData: function() {
+        let me = this,
+        formF = me.getForm(),
+        viewModel = me.getViewModel(),
+        allData = viewModel.getData(),
+        bindFields = allData.base.bindFields,
+        detailCount = me.detailCount,
+        formData = {},
+        detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(bindFields, function(field) {
+            let v = allData[field];
+            let f = formF.findField(field);
+            if(Ext.isDate(v)) {
+                v = Ext.Date.format(v, 'Y-m-d H:i:s');
+            }
+            if(f && f.isDirty()) {
+                formData[field] = f.originalValue;
+            }
+        });
+
+        let dirtyData = {
+            main: formData,
+        };
+
+        for (let i = 0; i < detailCount; i++) {
+            let g = detailGrids[i];
+            let gridDirtyData = g.getDirtyData();
+            let modelDetail = allData['detail' + i];
+            let detailBindFields = modelDetail.detailBindFields;
+
+            Ext.Array.each(gridDirtyData, function(d) {
+                for(k in d) {
+                    if(!Ext.Array.contains(detailBindFields, k)) {
+                        delete d[k];
+                    }
+                }
+            });
+            
+
+            dirtyData['detail' + i] = gridDirtyData;
+        }
+
+        return dirtyData;
+    },
+
+    beforeSave: function() {
+        return true;
+    },
+
+    beforeDelete: function() {
+        return true;
+    },
+
+    beforeAudit: function() {
+        return true;
+    },
+
+    beforeUnAudit: function() {
+        return true;
+    },
+
+    promiseCloseTab: function() {
+        let me = this,
+        controller = me.getController();
+
+        if(me.isDirty()) {
+            return saas.util.BaseUtil.showConfirm('提示', me.dirtyCloseText, {
+                buttons: Ext.Msg.YESNO
+            }).then(function(yes) {
+                if(yes === 'yes') {
+                    return true;
+                }else if(yes === 'no') {
+                    return false
+                }
+            })
+        }else {
+            return new Promise(function (resolve, reject) {
+                return resolve(true);
+            });
+        }
+    },
+
+    refreshViewConfig: function() {
+        this.getController().refresh();
+    }
+});

+ 92 - 0
frontend/operation-web/app/view/core/form/FormPanel.scss

@@ -0,0 +1,92 @@
+.x-core-form {
+    padding: 8px 12px 8px 12px;
+
+    .x-toolbar-default-docked-top{
+        padding: 12px 5px 12px 8px;
+    }
+    .x-tb {
+        color: #A2A2A2;
+        letter-spacing: 0.72px;
+        text-align: left;
+        font-size: 18px;
+        line-height: 16px;
+        font-weight: 400;
+        // font: 400 18px/16px 'PingFangSC-Regular';
+    }
+    .x-codeeditor {
+        top: -2px !important;
+        left: 44.5px !important;
+
+        .x-form-trigger-wrap {
+            border-top: none;
+            border-right: none;
+            border-bottom: 1px solid #aeb1b5;
+            border-left: none;
+
+            input {
+                padding-left: 0;
+                letter-spacing: 0.72px;
+                font-size: 18px;
+                min-height: inherit;
+                font-size: 18px;
+                line-height: 16px;
+                font-weight: 400;
+                // font: 400 18px/16px 'PingFangSC-Regular';
+            }
+        }
+        .x-form-trigger-wrap-invalid {
+            border-bottom: 1px solid #cf4c35;
+        }
+    }
+    .x-codeeditor-btn {
+        background-color: transparent !important;
+        border: none;
+        font-size: 18px;
+        box-shadow: none !important;
+
+        .fa-check-circle {
+            color: #52C41A;
+        }
+
+        .fa-edit {
+            color: #A2A2A2;
+        }
+
+        &:hover {
+            .fa-edit {
+                color: black;
+            }
+            .fa-check-circle {
+                color: #A0D911;
+            }
+        }
+    }
+    .x-audited {
+        border: 1px solid #FF002B;
+        color: #FF002B;
+    }
+}
+
+.x-field-separator {
+    &>div {
+        &>div {
+            padding-left: 20px;
+            font-weight: bold;
+            height: 100%;
+            line-height: 100%;
+            font-size: 14px;
+            vertical-align: middle;
+
+            &:before {
+                content: ' ';
+                position: absolute;
+                width: 5px;
+                height: 16px;
+                border-radius: 4px;
+                background: #33b4ee;
+                left: 4px;
+                top: 10px;
+            }
+        }
+    }
+}

+ 556 - 0
frontend/operation-web/app/view/core/form/FormPanelController.js

@@ -0,0 +1,556 @@
+Ext.define('saas.view.core.form.FormPanelController', {
+    extend: 'Ext.app.ViewController',
+    alias: 'controller.core-form-formpanel',
+
+    auditBtnClick: function() {
+        var me = this,
+        form = me.getView(),
+        statusCodeField = form._statusCodeField,
+        viewModel = me.getViewModel(),
+        status = viewModel.get(statusCodeField);
+
+        status == 'AUDITED' ? me.onUnAudit() : me.onAudit();
+    },
+
+    refresh: function() {
+        var me = this,
+        form = me.getView(),
+        xtype = form.xtype,
+        _config = {
+            initId: form.initId,
+        },
+        currentTab = saas.util.BaseUtil.getCurrentTab();
+        
+        var view = {
+            _config: _config,
+            xtype: xtype
+        };
+        Ext.apply(view, _config);
+        
+        Ext.suspendLayouts();
+        currentTab.removeAll();
+        currentTab.add(view);
+        Ext.resumeLayouts(true);
+    },
+
+    add: function(){
+        var form = this.getView();
+        var id = form.xtype + '-add';
+        saas.util.BaseUtil.openTab(form.xtype,'新增' + form._title, id);
+    },
+
+    onCopy: function() {
+        var me = this;
+        var form = this.getView();
+        var id = form.xtype + '-add';
+
+        var formData = me.initCopyData(form.getFormData());
+
+        saas.util.BaseUtil.openTab(form.xtype,'新增' + form._title, id, {initData: formData});
+    },
+
+    initCopyData: function(formData) {
+        var me = this;
+        var form = this.getView();
+        var detailCount = form.detailCount;
+        var main = formData.main;
+        var auditTexts = form.auditTexts;
+
+        // 单号、id清空
+        main[form._idField] = 0;
+        main[form._codeField] = '';
+        // 单据状态为录入状态(未审核)
+        main[form._statusCodeField] = auditTexts.unAuditCode;
+        main[form._statusField] = auditTexts.unAuditText;
+        //重设录入人,录入日期,审核人,审核日期
+        main['creatorId'] = saas.util.BaseUtil.getCurrentUser().id;
+        main['creatorName'] = saas.util.BaseUtil.getCurrentUser().realname;
+        main['createTime'] = Ext.Date.format(new Date(), 'Y-m-d H:i:s');
+        main['updaterId'] = saas.util.BaseUtil.getCurrentUser().id;
+        main['updaterName'] = saas.util.BaseUtil.getCurrentUser().realname;
+        main['updateTime'] = Ext.Date.format(new Date(), 'Y-m-d H:i:s');
+        main[form._auditmanField] = undefined;
+        main[form._auditdateField] = undefined;
+
+
+
+        for(var k in main) {
+            // 主表日期改为当前日期
+            if(saas.util.BaseUtil.isDateString(main[k])) {
+                main[k] = Ext.Date.format(new Date, 'Y-m-d H:i:s');
+            }
+        }
+
+        for(var j = 0; j < detailCount; j++) {
+            var detail = formData['detail' + j];
+            for(var x = 0; x < detail.length; x ++) {
+                var d = detail[x];
+
+                for(var k in d) {
+                    // 从表id清空
+                    delete d['id'];
+                    // 从表日期清空
+                    if(saas.util.BaseUtil.isDateString(d[k])) {
+                        d[k] = '';
+                    }
+                }
+            }
+        }
+
+        return me.myInitCopyData(formData);;
+    },
+
+    myInitCopyData: function(formData) {
+        return formData;
+    },
+    
+    delete: function(){
+        var me = this;
+        var form = this.getView();
+        var viewModel = me.getViewModel();
+        var id = viewModel.get(form._idField);
+        var code = viewModel.get(form._codeField);
+        if(id&&id.value!=0){
+
+            if(!form.beforeDelete()) {
+                return false;
+            }
+
+            saas.util.BaseUtil.deleteWarn(form._deleteMsg,function(btn){
+                if(btn == 'yes'){
+                    saas.util.BaseUtil.request({
+                        url: form._deleteUrl+'/'+id,
+                        method: 'POST',
+                    })
+                    .then(function(localJson) {
+                        if(localJson.success){
+                            var mainTab = Ext.getCmp('main-tab-panel');
+                            mainTab.getActiveTab().close();
+                            //解析参数
+                            saas.util.BaseUtil.showSuccessToast('删除成功');
+                        }
+                    })
+                    .catch(function(e) {
+                        saas.util.BaseUtil.showErrorToast('删除失败: ' + e.message);
+                    });
+                }
+            });
+        }
+    },
+
+    onSave: function() {
+        var me = this,
+        form = this.getView();
+
+        var valid = form.isValid();
+        if(!valid) {
+            saas.util.BaseUtil.showErrorToast(form.invalidText);
+            return false;
+        }
+        var dirty = form.isDirty();
+        if(!dirty) {
+            saas.util.BaseUtil.showErrorToast(form.noDirtySaveText);
+            return false;
+        }
+
+        if(!form.beforeSave()) {
+            return false;
+        }
+
+        me.save();
+    },
+
+    save:function(){
+        var me = this,
+        form = this.getView(),
+        codeField = form.getForm().findField(form._codeField),
+        detailCount = form.detailCount,
+        viewModel = me.getViewModel(),
+        codeModified = !form.initId || (codeField && codeField.isDirty());
+
+        //form里面数据
+        var formData = form.getSaveData();
+
+        var params = {
+            main:formData.main
+        };
+
+        for(var i = 0; i < detailCount; i++) {
+            params['items' + ( i + 1)] = formData['detail' + i];
+        }
+
+        // 只有一个从表时从表字段改为items
+        if(detailCount == 1) {
+            params.items = params.items1;
+            delete params.items1;
+        }
+
+        params.codeModified = codeModified;
+
+        form.setLoading(true);
+        saas.util.BaseUtil.request({
+            url: form._saveUrl,
+            params: JSON.stringify(params),
+            method: 'POST',
+        })
+        .then(function(localJson) {
+            form.setLoading(false);
+            if(localJson.success){
+                var id = localJson.data.id;
+                var code = localJson.data.code;
+                form.initId = id;
+                saas.util.FormUtil.loadData(form);
+
+                var newId = form.xtype + '-' + id;
+                var newTitle = form._title + '(' + code + ')';
+
+                saas.util.BaseUtil.refreshTabTitle(newId, newTitle);
+
+                saas.util.BaseUtil.showSuccessToast('保存成功');
+                form.fireEvent('aftersave', true, form, localJson);
+            }
+        })
+        .catch(function(e) {
+            form.setLoading(false);
+            saas.util.BaseUtil.showErrorToast('保存失败: ' + e.message);
+            form.fireEvent('aftersave', false, form);
+        });
+    },
+
+    onAudit: function(){
+        var me = this,
+        form = this.getView(),
+        viewModel = me.getViewModel(),
+        id = viewModel.get(form._idField);
+
+        var dirty = form.isDirty();
+
+        if(id && dirty) {
+            saas.util.BaseUtil.showConfirm('提示', form.dirtyAuditText)
+            .then(function(yes) {
+                if(yes == 'yes') {
+                    me.onSave();
+                }
+            });
+            return;
+        }
+
+        var valid = form.isValid();
+
+        if(!valid) {
+            saas.util.BaseUtil.showErrorToast(form.invalidText);
+            return false;
+        }
+
+        if(!form.beforeAudit()) {
+            return false;
+        }
+
+        me.audit();
+    },
+
+    audit: function() {
+        var me = this,
+        form = me.getView(),
+        viewModel = me.getViewModel(),
+        detailCount = form.detailCount,
+        codeField = form.getForm().findField(form._codeField),
+        codeModified = !form.initId || (codeField && codeField.isDirty());
+
+        //form里面数据
+        var formData = form.getFormData();
+        var params = {
+            main: formData.main
+        };
+
+        for(var i = 0; i < detailCount; i++) {
+            params['items' + ( i + 1)] = formData['detail' + i];
+        }
+
+        // 只有一个从表时从表字段改为items
+        if(detailCount == 1) {
+            params.items = params.items1;
+            delete params.items1;
+        }
+
+        params.codeModified = codeModified;
+
+        form.setLoading(true);
+        saas.util.BaseUtil.request({
+            url: form._auditUrl,
+            params: JSON.stringify(params),
+            method: 'POST',
+        })
+        .then(function(localJson) {
+            form.setLoading(false);
+            if(localJson.success){
+                // 未保存直接审核会返回id
+                if(localJson.data) {
+                    var id = localJson.data.id;
+                    var code = localJson.data.code;
+                    
+                    form.initId = id;
+
+                    var newId = form.xtype + '-' + id;
+                    var newTitle = form._title + '(' + code + ')';
+
+                    saas.util.BaseUtil.refreshTabTitle(newId, newTitle);
+                }
+                saas.util.FormUtil.loadData(form);
+                form.setEditable(false);
+                saas.util.BaseUtil.showSuccessToast('审核成功' + (localJson.message ? ': ' + localJson.message : ''));
+                form.fireEvent('afteraudit', true, form, localJson);
+            }
+        })
+        .catch(function(e) {
+            form.setLoading(false);
+            // console.error(e);
+            // if(res.data) {
+            //     var id = localJson.data.id;
+            //     var code = localJson.data.code;
+                
+            //     form.initId = id;
+
+            //     var newId = form.xtype + '-' + id;
+            //     var newTitle = form._title + '(' + code + ')';
+
+            //     saas.util.BaseUtil.refreshTabTitle(newId, newTitle);
+            //     saas.util.FormUtil.loadData(form);
+            // }
+            saas.util.BaseUtil.showErrorToast('审核失败: ' + e.message);
+            form.fireEvent('afteraudit', false, form);
+        });
+    },
+
+    onUnAudit: function() {
+        var me = this;
+        var form = this.getView();
+        var viewModel = me.getViewModel();
+        var id = viewModel.get(form._idField);
+        var code = viewModel.get(form._codeField);
+        if(id&&id.value!=0){
+
+            if(!form.beforeUnAudit()) {
+                return false;
+            }
+
+            me.unAudit();
+        }
+    },
+
+    unAudit: function() {
+        var me = this;
+        var form = this.getView();
+        var viewModel = me.getViewModel();
+        var id = viewModel.get(form._idField);
+        var code = viewModel.get(form._codeField);
+
+        form.setLoading(true);
+        saas.util.BaseUtil.request({
+            url: form._unAuditUrl + '/' + id,
+            method: 'POST',
+        })
+        .then(function(localJson) {
+            form.setLoading(false);
+            if(localJson.success){
+                //解析参数
+                saas.util.BaseUtil.showSuccessToast('反审核成功');
+                saas.util.FormUtil.loadData(form);
+            }
+        })
+        .catch(function(res) {
+            form.setLoading(false);
+            console.error(res);
+            saas.util.BaseUtil.showErrorToast('反审核失败: ' + res.message);
+        });
+    },
+
+    codeEditorBlur: function(e) {
+        var me = this,
+        viewModel = me.getViewModel(),
+        targetEl = event.target,
+        faEl = targetEl.getElementsByClassName('fa')[0];
+
+        if(faEl && faEl.classList.contains('fa-check-circle')) {
+            // 处理重复触发事件
+            // viewModel.set('base.codeEditable', false);
+        }else {
+            viewModel.set('base.codeEditable', false);
+        }
+    },
+    codeEditorClick: function() {
+        var me = this,
+        form = me.getView(),
+        codeField = form.getForm().findField(form._codeField),
+        viewModel = me.getViewModel(),
+        codeEditable = viewModel.get('base.codeEditable');
+
+        codeField.setValue(viewModel.get(form._codeField));
+        viewModel.set('base.codeEditable', !codeEditable);
+    },
+    showMessageLog:function(btn){
+        var me = this,
+        form = me.getView(),
+        viewModel = me.getViewModel(),
+        mlKeyvalue = viewModel.get(form._idField),
+        mlCaller = form.caller,
+        win = Ext.getCmp(form.xtype+mlKeyvalue);
+        if (!win&&mlKeyvalue!=0) {
+            var panel = form.up('core-tab-panel'),panelEl;panelEl = panel.getEl()
+            var box = panelEl.getBox();
+            var height = box.height;
+            var width = box.width;
+            var win = form.add(Ext.create('Ext.window.Window', {
+                modal: true,
+                id:me.xtype+mlKeyvalue,
+                cls:'x-window-dbfind',
+                height: height*0.8,
+                width: width*0.8,
+                title: '操作日志('+viewModel.get(form._codeField)+')',
+                scrollable: true,
+                constrain: true,
+                closable: true,
+                layout: 'fit',
+                items: [{
+                    padding:'5 10 5 10',
+                    xtype: 'core-form-mseeageLog',
+                    mlKeyvalue:mlKeyvalue,
+                    mlCaller:mlCaller
+                }],
+                listeners:{
+                    'close':function(){
+                        btn.removeCls('x-btn-focus');
+                    }
+                }
+            }));
+        };
+        win.show();
+    },
+
+    onPrint: function() {
+        var me = this,
+        form = me.getView(),
+        viewModel = me.getViewModel(),
+        caller = form.caller,
+        id = viewModel.get(form._idField),
+        code = viewModel.get(form._codeField);
+
+        me.beforePrint(caller).then(function(flag) {
+            if(!flag) {
+                return false;
+            }
+            saas.util.BaseUtil.request({
+                url: '/api/commons/jasperReport/printByDefault',
+                // url: 'http://192.168.253.58:8920/jasperReport/printByDefault',
+                method: 'POST',
+                headers: {
+                    "Content-Type": 'application/x-www-form-urlencoded;charset=UTF-8'
+                },
+                params: {
+                    caller: caller,
+                    id: id,
+                    code: code
+                }
+            }).then(function(res) {
+                var data = res.data,
+                printurl = data.printurl,
+                printtype = data.printtype,
+                reportName = data.reportName,
+                title = data.title,
+                userName = data.userName,
+                whereCondition = data.whereCondition,
+                companyId = saas.util.BaseUtil.getCurrentUser().companyId;
+                var url = printurl + '?' + 'reportName=' + reportName + '&' + 'companyId=' + companyId + '&whereCondition=' + whereCondition + (userName ? '&userName=' + userName : "");
+    
+                window.open(url);
+            }).catch(function(res) {
+                console.error(res.message);
+                saas.util.BaseUtil.showErrorToast('获取打印报表错误:' + res.message);
+            });
+        });
+    },
+
+    /**
+     * 判断权限
+     */
+    beforePrint: function(caller) {
+        return saas.util.BaseUtil.request({
+            url: '/api/commons/' + caller + '/print',
+            method: 'GET',
+            headers: {
+                "Content-Type": 'application/x-www-form-urlencoded;charset=UTF-8'
+            },
+            params: {
+                caller: caller,
+                id: id
+            }
+        }).then(function(res) {
+            return res.success;
+        }).catch(function(res) {
+            console.error(res.message);
+            saas.util.BaseUtil.showErrorToast('未通过权限验证:' + res.message);
+        });
+    },
+
+    onSetting: function() {
+        var me = this,
+        form = me.getView(),
+        viewName = form.viewName,
+        configItems = form.configItems,
+        items = [];
+
+        Ext.Array.sort(configItems, function(a, b) {
+            return a.index - b.index;
+        });
+
+        for(let i = 0; i < configItems.length; i++) {
+            let item = configItems[i];
+            let formItem = form.query('[name=' + item.name + ']')[0];
+            let xtypes = formItem.getXTypes().split('/');
+            item.xtypes = xtypes;
+            if(!item.initHidden) {
+                items.push(Object.assign({}, item));
+            }
+        }
+
+        me.openSettingWindow(viewName, items, 'main');
+    },
+
+    onColSetting: function(ct, col, e, t, eoPts) {
+        let me = this,
+        form = me.getView(),
+        viewName = form.viewName,
+        grid = ct.grid.ownerGrid,
+        columns = grid.defaultColumns,
+        items = [];
+
+        for(let i = 0; i < columns.length; i++) {
+            let col = columns[i];
+            if(!col.initHidden) {
+                items.push(Object.assign({}, col));
+            }
+        }
+
+        me.openSettingWindow(viewName, items, grid.gname);
+    },
+
+    openSettingWindow: function(viewName, items, settype) {
+        var panel = saas.util.BaseUtil.getCurrentTab(),
+        box = panel.getBox(),
+        refs = panel.getReferences() || {},
+        win = refs.settingwin;
+
+        title = settype == 'main' ? '界面设置' : '列设置';
+
+        if(!win) {
+            win = panel.add({
+                title: title,
+                xtype: 'settingwin',
+                viewName: viewName,
+                fieldItems: Ext.Array.clone(items),
+                settype: settype,
+            });
+        }
+        win.show();
+    }
+});

+ 15 - 0
frontend/operation-web/app/view/core/form/FormPanelModel.js

@@ -0,0 +1,15 @@
+Ext.define('saas.view.core.form.FormPanelModel', {
+    extend: 'Ext.app.ViewModel',
+    alias: 'viewmodel.core-form-formpanel',
+
+    data: {
+        id: 0,
+        base: {
+            bindFields: [], // 绑定字段
+            valid: true, // 单据是否合法
+            editable: true, // 单据是否可编辑
+        },
+        detailBindeFields: [], // 从表绑定列
+        detailStore: null, // 从表store
+    },
+});

+ 554 - 0
frontend/operation-web/app/view/core/form/field/DetailGridField.js

@@ -0,0 +1,554 @@
+Ext.define('saas.view.core.form.field.DetailGridField', {
+    extend: 'Ext.grid.Panel',
+    xtype: 'detailGridField',
+
+    cls: 'x-detailgridfield',
+
+    sortableColumns: false,
+    enableColumnHide: false,
+    border: 1,
+    margin: '0 0 10 0', // formpanel的fieldDefaults未生效
+    minHeight: 245,
+    columnWidth : 1.0, 
+
+    requires: [
+        'Ext.grid.plugin.CellEditing',
+        'Ext.selection.CellModel'
+    ],
+
+    selModel: {
+        type: 'cellmodel'
+    },
+    
+    showIndex: true,
+    configUrl: '',
+    editable: true,
+    allowEmpty: false, // 表格为空时校验合法
+    showCount: true, // 显示合计栏
+
+    emptyRows: 5,
+
+    allowDeselect: true,
+
+    initComponent: function() {
+        var me = this;
+        me.initColumns();
+        me.setSummary();
+
+        addRows = function(id) {
+            var grid = Ext.getCmp(id);
+            if(grid.editable) {
+                grid.addDetail(0);
+            }
+        };
+
+        Ext.apply(me, {
+            plugins: [{
+                ptype: 'cellediting',
+                clicksToEdit: 1,
+                listeners: {
+                    edit: function(editor, context, eOpts) {
+                        context.column.fireEvent('edit', context.value, me, context.column, editor, context, eOpts);
+                    }
+                }
+            }, {
+                ptype: 'menuclipboard'
+            }],
+            normalViewConfig: {
+                deferEmptyText: false,
+            },
+            lockedViewConfig: {
+                scrollable: {
+                    x: false,
+                    y: true
+                },
+                deferEmptyText: false,
+                emptyText: '<div style="width: 100%; text-align: center; cursor: pointer; color: green;" class="fa fa-plus" title="新增行" onclick="addRows(\'' + me.id + '\')"></div>',
+            },
+            listeners: {
+                boxready: function(g) {
+                    var f = g.ownerCt,
+                    ih = g.initialConfig.height || 0,
+                    iminh = g.initialConfig.minHeight || 0,
+                    // 最小高度等于设定的最小高度或者设定的高度
+                    minh = ih ? (iminh ? Math.min(ih, iminh) : 0) : iminh,
+                    c = f.detailCount || 1,
+                    fb = f.getBox(),
+                    fh = fb.height,
+                    h = (fh * 0.5) / c;
+
+                    // 当自适应高度比设定的最小高度还要小时取设定的最小高度
+                    if(minh && h < minh) {
+                        g.setMinHeight(minh);
+                        h = minh;
+                    }
+                    g.setHeight(h);
+                },
+                edit: function() {
+                    this.fireEvent('validChange');
+                },
+                itemmouseenter: function(view, record, item, index, e, eOpts) {
+                    if(!view.up('detailGridField').editable) {
+                        return;
+                    }
+                    var lockedItems = view.el.dom.parentElement.parentElement.getElementsByClassName('x-grid-scrollbar-clipper-locked')[0].getElementsByClassName('x-grid-item');
+                    var currentLockedItem = lockedItems[index];
+                    var textItem = currentLockedItem.getElementsByClassName('text')[0];
+                    var iconsItem = currentLockedItem.getElementsByClassName('icons')[0];
+
+                    textItem.style.display = 'none';
+                    iconsItem.style.display = 'flex';
+                },
+                itemmouseleave: function(view, record, item, index, e, eOpts) {
+                    if(!view.up('detailGridField').editable) {
+                        return;
+                    }
+                    var lockedItems = view.el.dom.parentElement.parentElement.getElementsByClassName('x-grid-scrollbar-clipper-locked')[0].getElementsByClassName('x-grid-item');
+                    var currentLockedItem = lockedItems[index];
+                    var textItem = currentLockedItem.getElementsByClassName('text')[0];
+                    var iconsItem = currentLockedItem.getElementsByClassName('icons')[0];
+
+                    textItem.style.display = 'block';
+                    iconsItem.style.display = 'none';
+                },
+                cellclick: function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {
+                    var target = e.target;
+                    var detno = record.get(me.detnoColumn);
+
+                    if(target.classList.contains('fa-minus')) {
+                        me.deleteDetail(detno);
+                    }else if(target.classList.contains('fa-plus')) {
+                        me.addDetail(detno);
+                    }
+                },
+                scope: me
+            }
+        });
+        me.callParent(arguments);
+    },
+
+    initColumns: function() {
+        // 构造序号列
+        var me = this,
+        columns = me.columns,
+        detnoField = me.detnoColumn,
+        showCount = me.showCount,
+
+        indexColumn = {
+            // text : "序号", 
+            // text: '<div class="x-sa sa-setting" style="cursor: pointer;" title="列设置"></div>',
+            bind: {
+                text: "{(configurable && isAdmin) ? ('<div class=\"x-sa sa-setting\" style=\"cursor: pointer;\" title=\"列设置\"></div>') : '序号'}"
+            },
+            dataIndex : detnoField, 
+            width : 60, 
+            xtype : "numbercolumn",
+            align : 'center',
+            format:'0',
+            allowBlank: true,
+            locked:true,
+            lockable: false,
+            renderer: function(value, a, record, index) {
+                return '<div class="text">' + value + '</div>' +
+                '<div class="icons" style="height: 19px; display: none;">' + 
+                    '<div style="line-height: 19px; flex: 1; color: #34BAF6; cursor: pointer; margin-right: 2px;" class="x-row-insert fa fa-plus" title="插入行"></div>'+
+                    '<div style="line-height: 19px; flex: 1; color: #FF002B; cursor: pointer; margin-left: 2px;" class="x-row-delete fa fa-minus" title="删除行"></div>'+
+                '</div>';
+            },
+            listeners: {
+                headerclick: function() {
+                    let form = this.up('core-form-formpanel'),
+                    controller = form.getController(),
+                    text = this.text;
+
+                    if(text != '序号') {
+                        controller.onColSetting(arguments[0]);
+                    }
+                }
+            }
+        };
+
+        if(showCount) {
+            Ext.apply(indexColumn, {
+                summaryType: 'count',
+                summaryRenderer: function(value, summaryData, dataIndex) {
+                    return Ext.String.format('合计', value);
+                },
+            });
+        }
+
+        if (detnoField) {
+            Ext.apply(me, { columns: [indexColumn].concat(columns) });
+        }
+    },
+
+    setSummary: function() {
+        var me = this,
+        columns = me.columns,
+        features = me.features || [];
+
+        var hasSummary = !!columns.find(function(c) {
+            return c.summaryType
+        });
+
+        if(hasSummary) {
+            features.push({
+                ftype: 'summary',
+                dock: 'bottom'
+            });
+        }
+        Ext.applyIf(me, {
+            features: features
+        });
+    },
+
+    add10EmptyRow: function(num) {
+        var me = this,
+        detnoColumn = me.detnoColumn,
+        store = me.getStore(),
+        selectedRecord = me.selModel.lastSelected,
+        datas = [];
+
+        num = num || me.emptyRows;
+
+        //当前行后序号全部加1
+        var detno = selectedRecord ? selectedRecord.data[detnoColumn] : 0;
+        Ext.Array.each(new Array(num), function() {
+            detno += 1;
+            var data = {};
+            data[detnoColumn] = detno;
+            datas.push(data);
+        })
+        store.insert(store.indexOf(selectedRecord) + 1, datas);
+    },
+
+    addDetail: function(v, d) {
+        d = d || {};
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+        var detnoColumn = me.detnoColumn,
+        store = me.getStore(),
+        records = store.getData().items,
+        selectedRecord = records.find(function(r) {
+            return r.get(detnoColumn) == v;
+        });
+
+        store.each(function(item){
+            var t = item.data[detnoColumn];
+            if(t > v) {
+                item.set(detnoColumn, t + 1);
+            }
+        });
+
+        var data = d || {};
+        data[detnoColumn] = v + 1;
+        var r = store.insert(store.indexOf(selectedRecord) + 1, data);
+
+        store.each(function(s) {
+            var itemFields = s.fields,
+            itemData = s.getData(),
+            detno = itemData[detnoColumn],
+            id = itemData.id,
+            dirtyFields = [];
+
+            if(s.isDirty()) {
+                var modified = s.modified;
+                var dirtyFields = Ext.Object.getAllKeys(modified);
+            }
+
+            // 如果有有效数据才算dirty,否则直接commit
+            if(dirtyFields.length == 2 && Ext.Array.contains(dirtyFields, 'id') && Ext.Array.contains(dirtyFields, detnoColumn)) {
+                s.commit();
+            }else if(!Ext.isNumber(id) && dirtyFields.length == 1 && dirtyFields[0] == detnoColumn) {
+                s.commit();
+            }
+        });
+
+        me.fireEvent('validChange');
+        return r;
+    },
+
+    deleteDetail: function(v) {
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+
+        var detnoColumn = me.detnoColumn,
+        store = me.getStore(),
+        records = store.getData().items,
+        selectedRecord = records.find(function(r) {
+            return r.get(detnoColumn) == v;
+        });
+
+        var id = selectedRecord.data.id;
+
+        if(id&&id!=0&&(typeof id) == 'number') {
+            saas.util.BaseUtil.showConfirm('警告', '确定删除该条明细')
+            .then(function(yes) {
+                if(yes == 'yes') {
+                    saas.util.BaseUtil.request({
+                        url: me.deleteDetailUrl + '/' + id,
+                        params: '',
+                        method: 'POST',
+                    })
+                    .then(function() {
+                        store.remove(selectedRecord);
+                        me.fireEvent('validChange');
+                        //解析参数
+                        saas.util.BaseUtil.showSuccessToast('删除成功');
+                    })
+                    .catch(function(e) {
+                        me.fireEvent('validChange');
+                        //失败
+                        saas.util.BaseUtil.showErrorToast('删除失败: ' + e.message);
+                    });
+                }else {
+                    throw new Error();
+                }
+            })
+        }else {
+            store.remove(selectedRecord);
+            me.fireEvent('validChange');
+        }
+    },
+
+    swapUp: function() {
+        var me = this;
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+        var store = me.getStore(),
+        record = me.selModel.lastSelected,
+        selectedIdx = store.indexOf(record);
+
+        me.swap(record, selectedIdx, -1);
+    },
+
+    swapDown: function() {
+        var me = this;
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+        var store = me.getStore(),
+        record = me.selModel.lastSelected,
+        selectedIdx = store.indexOf(record);
+        
+        me.swap(record, selectedIdx, 1);
+    },
+
+    swap: function(from, index, dir) {
+        var me = this,
+        store = me.getStore(),
+        to = store.getAt(index + dir);
+
+        if(from && to) {
+            var keys = me.getColumns().map(function(c) {
+                //剔除序号字段
+                if(c.dataIndex!=me.detnoColumn){
+                    return c.dataIndex 
+                }
+            }),
+            data = from.getData(),
+            toData = to.getData();
+
+            Ext.each(keys, function(key, index) {
+                to.set(key, null);
+                from.set(key, toData[key]);
+                to.set(key, data[key]);
+            });
+            //聚焦目标行
+            me.selModel.select(to);
+        }
+        me.fireEvent('validChange');
+    },
+
+    clearDirty: function() {
+        var me = this,
+        store = me.store,
+        count = store.getCount();
+
+        for(var x = 0; x < count; x++) {
+            store.getAt(x).commit();
+        }
+    },
+
+    setGridDisabled: function(able) {
+        var me = this,
+        columns = me.columns;
+
+        me.editable = able;
+
+        Ext.Array.each(columns, function(c) {
+            if(typeof c.getEditor != 'undefined'){
+                var e = c.getEditor();
+                if(e) {
+                    typeof e.setDisabled == 'function' && e.setDisabled(!able);
+                }
+            }
+        });
+    },
+
+    /**
+     * 判断grid数据是否合法
+     */
+    isValid: function() {
+        var me = this,
+        allowEmpty = me.allowEmpty;
+        columns = me.columns,
+        data = me.getTrueData(),
+        valid = allowEmpty;
+
+        // 判断列必填
+        a:
+        for(var i = 0; i < columns.length; i++) {
+            var c = columns[i];
+            var cname = c.dataIndex;
+            var allowBlank = c.allowBlank;
+            var isValid = c.isValid;
+            
+            b:
+            for(var j = 0; j < data.length; j++) {
+                valid = true;
+                var d = data[j];
+                var value = d[cname];
+
+                if(typeof isValid == 'function') {
+                    if(!isValid(value)) {
+                        valid = false;
+                        break a;
+                    }
+                }
+                if(!allowBlank) {
+                    if(!value) {
+                        valid = false;
+                        break a;
+                    }
+                }
+            }
+        }
+
+        return valid;
+    },
+
+    isDirty: function() {
+        var me = this,
+        store = me.getStore(),
+        dataItems = store.data.items;
+
+        for(var i = 0; i < dataItems.length; i++) {
+            var item = dataItems[i];
+            if(item.dirty) {
+                return true;
+            }
+        }
+
+        return false;
+    },
+
+    /**
+     * 获得所有数据
+     */
+    getAllData: function() {
+        var me = this,
+        store = me.getStore(),
+        storeData = store.getData().items,
+        allData = [];
+
+        Ext.Array.each(storeData, function(item){
+            var d = Object.assign({}, item.data);
+
+            if((typeof d.id) != "number" && d.id.indexOf('-')>-1){
+                d.id = 0;
+            }
+            for(k in d) {
+                if(Ext.isDate(d[k])) {
+                    d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                }
+            }
+            allData.push(d);
+        });
+
+        return allData;
+    },
+
+    getSaveData: function() {
+        var me = this,
+        store = me.getStore(),
+        allData = store.getData().items,
+        dirtyData = [];
+
+        Ext.Array.each(allData, function(item){
+            var d = Object.assign({}, item.data),
+            dirty = item.dirty;
+
+            if(dirty){
+                if((typeof d.id) != "number" && d.id.indexOf('-')>-1){
+                    d.id = 0;
+                }
+                for(k in d) {
+                    if(Ext.isDate(d[k])) {
+                        d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                    }
+                }
+                dirtyData.push(d);
+            }
+        });
+        return dirtyData;
+    },
+
+    /**
+     * 获得有效数据
+     */
+    getTrueData: function() {
+        var me = this,
+        store = me.getStore(),
+        allData = store.getData().items,
+        trueData = [];
+
+        Ext.Array.each(allData, function(item){
+            var d = Object.assign({}, item.data),
+            dirty = item.dirty;
+
+            if(dirty){
+                if((typeof d.id) != "number" && d.id.indexOf('-')>-1){
+                    d.id = 0;
+                }
+                for(k in d) {
+                    if(Ext.isDate(d[k])) {
+                        d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                    }
+                }
+                trueData.push(d);
+            }else {
+                if(typeof d.id == "number") {
+                    for(k in d) {
+                        if(Ext.isDate(d[k])) {
+                            d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                        }
+                    }
+                    trueData.push(d);
+                }
+            }
+        });
+        return trueData;
+    },
+
+    getDirtyData: function() {
+        var me = this,
+        store = me.getStore(),
+        modifiedData = store.getModifiedRecords(),
+        dirtyData = [];
+
+        Ext.Array.each(modifiedData, function(m, index) {
+            var modified = m.modified;
+            dirtyData.push(modified);
+        });
+
+        return dirtyData;
+    },
+});

+ 49 - 0
frontend/operation-web/app/view/core/form/field/DetailGridField.scss

@@ -0,0 +1,49 @@
+.x-detailgridfield {
+
+    .x-grid-view {
+
+        .x-grid-empty {
+            padding: 8px 10px;
+        }
+    }
+}
+
+.x-column-header{
+    text-align: center !important;
+    border-width: 1px;
+}
+.x-column-header .x-column-header-text {
+    font: "microsoft yahei";
+    text-align: center;
+    font-size:14px;
+    font-weight: 500;
+    margin-right: 0;
+}
+.x-grid-necessary .x-column-header-text:before{
+    content: '*';
+    font-size: 130%;
+    color: #FF002B;
+    width: 5px;
+    height: 5px;
+    margin-top: 4px;
+    margin-left: -10px;
+    position: absolute;
+}
+
+.x-grid-locked .x-grid-inner-locked {
+    border: none;
+}
+
+.x-grid-locked .x-grid-inner-locked {
+    
+    .x-grid-header-ct {
+        border-right: 1px solid #ABDAFF !important;
+    }
+}
+
+.x-grid-scrollbar-clipper-locked, .x-grid-scrollbar-locked {
+
+    .x-grid-view {
+        border-right: 1px solid #ABDAFF !important;
+    }
+}

+ 15 - 0
frontend/operation-web/app/view/core/form/field/SeparateField.js

@@ -0,0 +1,15 @@
+Ext.define('saas.view.core.form.field.SeparateField', {
+    extend: 'Ext.container.Container',
+    xtype: 'separatefield',
+    userCls: 'x-field-separator',
+    height: 36,
+    columnWidth: 1,
+    ignore: true,
+    // initHidden: true,
+    isValid: function() {
+        return true;
+    },
+    isDirty: function() {
+        return false;
+    }
+});

+ 3 - 0
frontend/operation-web/app/view/core/tab/Panel.js

@@ -10,6 +10,9 @@ Ext.define('saas.view.core.tab.Panel', {
         padding: '0 16 0 0'
     },
     closable: true,
+    tabConfig: {
+        maxWidth: 128
+    },
     
     listeners: {
         activate: 'onTabActivate',

+ 5 - 0
frontend/operation-web/app/view/core/tab/Panel.scss

@@ -2,4 +2,9 @@
     &>div>.x-panel-body {
         background: $panel-body-background;
     }
+}
+.main-right-tabpanel {
+    .x-tab-inner {
+        max-width: 102px;
+    }
 }

+ 12 - 1
frontend/operation-web/app/view/statistical/CompanyAnalysis.js

@@ -31,7 +31,18 @@ Ext.define('saas.view.statistical.CompanyAnalysis', {
                 }, {
                     text: '企业名称',
                     dataIndex: 'ca_company',
-                    width: 200
+                    width: 200,
+                    renderer: function(v, m, r) {
+                        return '<span style="cursor: pointer; color: #3e80f6;">' + v + '</span>';
+                    },
+                    listeners: {
+                        click: function(tableView, td, rowIdx, colIdx, e, model, tr) {
+                            var data = model.data;
+                            saas.util.BaseUtil.openTab('companyinfo', data.ca_company , data.ca_id, {
+                                initId: data.ca_companyid
+                            });
+                        }
+                    }
                 }, {
                     text: '企业地址',
                     dataIndex: 'ca_address',

+ 110 - 0
frontend/operation-web/app/view/statistical/CompanyInfo.js

@@ -0,0 +1,110 @@
+Ext.define('saas.view.statistical.CompanyInfo', {
+    extend: 'saas.view.core.form.FormPanel',
+    xtype: 'companyinfo',
+
+    viewModel: 'companyinfo',
+
+    _readUrl: '/api/operation/data/companyAnalyzeRead',
+    codeInHeader: false,
+
+    initComponent: function () {
+        var me = this;
+        Ext.apply(this, {
+            defaultItems: [{
+                xtype: 'hidden',
+                name: 'ca_id',
+                fieldLabel: 'id'
+            }, {
+                xtype: 'textfield',
+                name: 'ca_companyid',
+                fieldLabel: '客户编号'
+            }, {
+                xtype: "textfield",
+                name: "ca_company",
+                fieldLabel: "客户名称",
+            }, {
+                xtype: "textfield",
+                name: "ca_address",
+                fieldLabel: "地址",
+            }, {
+                xtype: "textfield",
+                name: "ca_admin",
+                fieldLabel: "管理员",
+            }, {
+                xtype: "datefield",
+                name: "ca_createtime",
+                format: 'Y-m-d H:i:s',
+                fieldLabel: "注册时间",
+            }, {
+                xtype: "datefield",
+                name: "ca_newestlogtime",
+                format: 'Y-m-d H:i:s',
+                fieldLabel: "最后操作时间",
+            }, {
+                xtype: 'textfield',
+                name: 'ca_phase',
+                fieldLabel: '目前阶段',
+            }, {
+                xtype: 'textfield',
+                name: 'ca_status',
+                fieldLabel: '使用状态',
+            }, {
+                name: "detailGridField",
+                xtype: "detailGridField",
+                storeModel: 'saas.model.statistical.CompanyInfo',
+                detnoColumn: 'ord_detno',
+                deleteDetailUrl: '/api/money/othreceipts/deleteDetail',
+                showCount: false,
+                columns: [{
+                    text: "账户统计",
+                    dataIndex: "cd_accountnum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "物料资料统计",
+                    dataIndex: "cd_productnum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "客户资料统计",
+                    dataIndex: "cd_customernum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "供应商资料统计",
+                    dataIndex: "cd_vendornum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "销售订单统计",
+                    dataIndex: "cd_salenum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "采购订单统计",
+                    dataIndex: "cd_purchasenum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "出入库单据统计",
+                    dataIndex: "cd_prodionum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "付款单统计",
+                    dataIndex: "cd_paynum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "收款单统计",
+                    dataIndex: "cd_receivenum",
+                    width: 140,
+                    align: 'end'
+                }]
+            }],
+        });
+        this.callParent();
+        this.setEditable(false);
+    },
+
+});

+ 14 - 0
frontend/operation-web/app/view/statistical/CompanyInfoModel.js

@@ -0,0 +1,14 @@
+Ext.define('saas.view.statistical.CompanyInfoModel', {
+    extend: 'saas.view.core.form.FormPanelModel',
+    alias: 'viewmodel.companyinfo',
+
+    data: {
+        id: 0,
+        base: {
+            bindFields: [], // 绑定字段
+            editable: false, // 单据是否可编辑
+        },
+        detailBindeFields: [], // 从表绑定列
+        detailStore: null, // 从表store
+    },
+});

+ 36 - 1
frontend/operation-web/app/view/statistical/PersonRegInfo.js

@@ -14,6 +14,32 @@ Ext.define('saas.view.statistical.PersonRegInfo', {
                 getCondition: function (v) {
                     return "(upper(CONCAT(username, '#', mobile) like '%" + v.toUpperCase() + "%'))";
                 },
+            }, {
+                xtype: 'combobox',
+                name: 'bind',
+                fieldLabel: '绑定企业',
+                queryMode: 'local',
+                displayField: 'name',
+                valueField: 'value',
+                emptyText :'全部',
+                editable:false,
+                store: Ext.create('Ext.data.ArrayStore', {
+                fields: ['value', 'name'],
+                data: [
+                    ["ALL", "全部"],
+                    ["Y", "是"],
+                    ["N", "否"]
+                ]
+                }),
+                getCondition: function(value) {
+                    if(value == 'ALL') {
+                        return '1=1';
+                    }else if(value == 'Y') {
+                        return 'bind > 0';
+                    }else if(value == 'N') {
+                        return 'bind = 0';
+                    }
+                }
             }],
 
             gridConfig: {
@@ -49,9 +75,18 @@ Ext.define('saas.view.statistical.PersonRegInfo', {
                 }, {
                     text: '状态',
                     dataIndex: 'enabled',
+                    align: 'center',
+                    width: 80,
+                    renderer: function(v, m, r) {
+                        return v == true ? '<span style="color: #81CB31;">启用</span>' : '<span style="color: #DD6550;">未启用</span>';
+                    }
+                }, {
+                    text: '绑定企业',
+                    dataIndex: 'bind',
+                    align: 'center',
                     width: 80,
                     renderer: function(v, m, r) {
-                        return v == true ? '<span style="color: #81CB31;">正常</span>' : '<span style="color: #DD6550;">禁用</span>';
+                        return v > 0 ? '是' : '否';
                     }
                 }, {
                     text: '注册时间',

+ 1 - 1
frontend/operation-web/overrides/data/Connection.js

@@ -60,7 +60,7 @@ Ext.define('saas.override.data.Connection', {
 
         if(res && res.code == 40001) {
             // 如果token超时则显示重新登陆弹窗
-            // saas.util.BaseUtil.showLoginWin();
+            saas.util.BaseUtil.showLoginWin();
             throw new Error('会话已过期');
         }
 

+ 16 - 0
frontend/operation-web/overrides/form/field/Date.js

@@ -0,0 +1,16 @@
+Ext.define('saas.override.form.field.Date', {
+    override: 'Ext.form.field.Date',
+    formatText: '',
+
+    setValue: function (v) {
+        var me = this;
+
+        if(v && me.format) {
+            v = new Date(v);
+            v = Ext.Date.format(v, me.format);
+        }
+
+        me.callParent(arguments);
+    },
+
+});

+ 36 - 1
frontend/saas-web/app/Application.scss

@@ -612,4 +612,39 @@ div::-webkit-scrollbar-track {
 input::-webkit-input-placeholder {
   /* placeholder颜色  */
   color: #C3C3C3;
-}
+}
+
+.x-quotation-detail{
+  width: 1105px !important;
+  .x-grid-row{
+    border-color:#fff !important;
+    background-color: #fff !important;
+  }
+  .x-grid-item-selected{
+    border-color:#fff !important;
+    background-color: #fff !important;
+  }
+}
+
+.x-quotation-main{
+  .x-grid-row{
+    border-color:#fff !important;
+    background-color: #fff !important;
+  }
+  .x-grid-item-selected{
+    border-color:#fff !important;
+    background-color: #fff !important;
+  }
+}
+
+.x-btn-quotation{
+  .x-btn-over {
+    .x-btn-wrap {
+      .x-btn-button {
+        .x-btn-inner {
+          color: #34baf6;
+        }
+      }
+    }
+  }
+}

+ 21 - 0
frontend/saas-web/app/model/report/PayDetail.js

@@ -0,0 +1,21 @@
+Ext.define('saas.model.report.PayDetail', {
+    extend: 'saas.model.Base',
+
+    fields: [
+        { name: 'pd_date', type: 'string' },
+        { name: 'pd_code', type: 'string' },
+        { name: 'pd_kind', type: 'date' },
+        { name: 'pd_buyername', type: 'string' },
+        { name: 'pd_currency', type: 'string' },
+        { name: 'pd_addpay', type: 'float' },
+        { name: 'pd_addpre', type: 'float' },
+        { name: 'pd_remain', type: 'float' },
+        { name: 'pd_vendname', type: 'string' },
+        { name: 'groupfield', type: 'string',
+            convert: function(v, rec) {
+                return rec.get('pd_vendname') + rec.get('pd_currency');
+            },
+            depends: ['pd_vendname', 'pd_currency']
+        }
+    ],
+});

+ 4 - 1
frontend/saas-web/app/model/report/Purchase.js

@@ -21,6 +21,9 @@ Ext.define('saas.model.report.Purchase', {
         },
         { name: 'pd_total', type: 'float' }, // 价税合计
         { name: 'pd_pdacceptqty', type: 'float' }, // 收货数量
-        { name: 'pd_remark', type: 'string' }, // 备注
+        { name: 'pu_currency', type:'string'},//币别
+        { name: 'pu_rate', type:'float'},//汇率
+        { name: 'pd_remark', type: 'string' } // 备注
+        
     ],
 });

+ 7 - 1
frontend/saas-web/app/model/report/PurchasePay.js

@@ -20,6 +20,12 @@ Ext.define('saas.model.report.PurchasePay', {
         { name: 'pb_payrate', type: 'float', }, // 付款比例
         { name: 'pb_manname', type: 'string' }, // 付款人
         { name: 'pb_remark', type: 'string' }, // 备注
-        { name: 'pi_currency', type:'string' } //币别
+        { name: 'pi_currency', type:'string' }, //币别
+        { name: 'groupfield', type: 'string', // 供应商+币别分组
+            convert: function(v, rec) {
+                return rec.get('pu_vendname') + rec.get('pi_currency');
+            },
+            depends: ['pu_vendname', 'pi_currency']
+        }
     ],
 });

+ 21 - 0
frontend/saas-web/app/model/report/RecDetail.js

@@ -0,0 +1,21 @@
+Ext.define('saas.model.report.RecDetail', {
+    extend: 'saas.model.Base',
+
+    fields: [
+        { name: 'rd_date', type: 'string' },
+        { name: 'rd_code', type: 'string' },
+        { name: 'rd_kind', type: 'date' },
+        { name: 'rd_sellername', type: 'string' },
+        { name: 'rd_currency', type: 'string' },
+        { name: 'rd_addrec', type: 'float' },
+        { name: 'rd_addpre', type: 'float' },
+        { name: 'rd_remain', type: 'float' },
+        { name: 'rd_custname', type: 'string' },
+        { name: 'groupfield', type: 'string',
+            convert: function(v, rec) {
+                return rec.get('rd_custname') + rec.get('rd_currency');
+            },
+            depends: ['rd_custname', 'rd_currency']
+        }
+    ],
+});

+ 4 - 1
frontend/saas-web/app/model/report/Sale.js

@@ -29,6 +29,9 @@ Ext.define('saas.model.report.Sale', {
         },
         { name: 'sd_total', type: 'float' },
         { name: 'sd_pdsendqty', type: 'float' },
-        { name: 'sd_remark', type: 'string' },
+        { name: 'sa_currency', type: 'string' },//币别
+        { name: 'sa_rate', type: 'float' },//税率
+        { name: 'sd_remark', type: 'string' },//备注
+
     ],
 });

+ 3 - 1
frontend/saas-web/app/model/report/SaleProfit.js

@@ -30,6 +30,8 @@ Ext.define('saas.model.report.SaleProfit', {
         { name: 'pd_total', type: 'float' },
         { name: 'pd_profit', type: 'float' },
         { name: 'pd_profitpresent', type: 'float' },
-        { name: 'pd_remark', type: 'string' }
+        { name: 'pd_remark', type: 'string' },
+        { name: 'pi_currency', type: 'string' },
+        { name: 'pi_rate', type: 'float' }
     ],
 });

+ 7 - 1
frontend/saas-web/app/model/report/SaleRec.js

@@ -21,6 +21,12 @@ Ext.define('saas.model.report.SaleRec', {
             depends: ['pi_total', 'pi_nettotal']
         },
         { name: 'rb_remark', type: 'string' },
-        { name: 'pi_currency', type: 'string' }
+        { name: 'pi_currency', type: 'string' },
+        { name: 'groupfield', type: 'string', // 客户+币别分组
+            convert: function(v, rec) {
+                return rec.get('rb_custname') + rec.get('pi_currency');
+            },
+            depends: ['rb_custname', 'pi_currency']
+        }
     ],
 });

+ 1 - 0
frontend/saas-web/app/model/report/TotalPayDetail.js

@@ -4,6 +4,7 @@ Ext.define('saas.model.report.TotalPayDetail', {
     fields: [
         { name: 'vm_yearmonth', type: 'string' }, // 期间
         { name: 'vm_vendname', type: 'string' }, // 供应商名称
+        { name: 'vm_currency', type:'string' }, //币别
         { name: 'vm_beginpreamount', type: 'float' }, // 期初预付
         { name: 'vm_beginamount', type: 'float' }, // 期初应付
         { name: 'vm_nowpreamount', type: 'float' }, // 本期预付

+ 1 - 0
frontend/saas-web/app/model/report/TotalRecDetail.js

@@ -4,6 +4,7 @@ Ext.define('saas.model.report.TotalRecDetail', {
     fields: [
         { name: 'cm_yearmonth', type: 'string' }, // 期间
         { name: 'cm_custname', type: 'string' }, // 客户名称
+        { name: 'cm_currency', type:'string' }, //币别
         { name: 'cm_beginpreamount', type: 'float' }, // 期初预收
         { name: 'cm_beginamount', type: 'float' }, // 期初应收
         { name: 'cm_nowpreamount', type: 'float' }, // 本期预收

+ 80 - 116
frontend/saas-web/app/view/auth/Login.js

@@ -21,124 +21,88 @@ Ext.define('saas.view.auth.Login', {
     },
     defaultFocus: 'authdialog',
 
-    items: [
-        {
-            xtype: 'authdialog',
-            reference: 'authdialog',
-            defaultButton : 'loginButton',
-            autoComplete: true,
-            bodyPadding: '20 20',
-            cls: 'auth-dialog-login',
-            header: false,
-            width: 415,
-            layout: {
-                type: 'vbox',
-                align: 'stretch'
-            },
+    items: [{
+        xtype: 'authdialog',
+        reference: 'authdialog',
+        defaultButton: 'loginButton',
+        autoComplete: true,
+        bodyPadding: '20 20',
+        cls: 'auth-dialog-login',
+        header: false,
+        width: 415,
+        layout: {
+            type: 'vbox',
+            align: 'stretch'
+        },
 
-            defaults : {
-                margin : '5 0'
+        defaults: {
+            margin: '5 0'
+        },
+        items: [{
+                xtype: 'label',
+                text: '账 户 登 录'
             },
-            items: [
-                {
-                    xtype: 'label',
-                    text: '账 户 登 录'
-                },
-                {
-                    xtype: 'textfield',
-                    cls: 'auth-textbox',
-                    name: 'username',
-                    bind: '{username}',
-                    height: 55,
-                    hideLabel: true,
-                    allowBlank : false,
-                    emptyText: '账号/邮箱/手机号',
-                    triggers: {
-                        glyphed: {
-                            cls: 'trigger-glyph-noop auth-email-trigger'
-                        }
-                    }
-                },
-                {
-                    xtype: 'textfield',
-                    cls: 'auth-textbox',
-                    height: 55,
-                    hideLabel: true,
-                    emptyText: '密码',
-                    inputType: 'password',
-                    name: 'password',
-                    bind: '{password}',
-                    allowBlank : false,
-                    triggers: {
-                        glyphed: {
-                            cls: 'trigger-glyph-noop auth-password-trigger'
-                        }
-                    }
-                },
-                {
-                    xtype: 'container',
-                    layout: 'hbox',
-                    items: [
-                        {
-                            xtype: 'checkboxfield',
-                            flex : 1,
-                            cls: 'form-panel-font-color rememberMeCheckbox',
-                            height: 30,
-                            bind: '{persist}',
-                            boxLabel: '记住账户'
-                        },
-                        {
-                            xtype: 'box',
-                            html: '<a href="#passwordreset" class="link-forgot-password"> 忘记密码 ?</a>'
-                        }
-                    ]
-                },
-                {
-                    xtype: 'button',
-                    reference: 'loginButton',
-                    scale: 'large',
-                    ui: 'soft-green',
-                    iconAlign: 'right',
-                    iconCls: 'x-fa fa-angle-right',
-                    text: '登录',
-                    formBind: true,
-                    listeners: {
-                        click: 'onLoginButton'
+            {
+                xtype: 'textfield',
+                cls: 'auth-textbox',
+                name: 'username',
+                bind: '{username}',
+                height: 55,
+                hideLabel: true,
+                allowBlank: false,
+                emptyText: '账号/邮箱/手机号',
+                triggers: {
+                    glyphed: {
+                        cls: 'trigger-glyph-noop auth-email-trigger'
                     }
-                },
-                {
-                    xtype: 'box',
-                    html: '<div class="outer-div"><div class="seperator">或</div></div>',
-                    margin: '10 0'
-                },
-                {
-                    xtype: 'button',
-                    scale: 'large',
-                    ui: 'weixin',
-                    iconAlign: 'right',
-                    iconCls: 'x-fa fa-weixin',
-                    text: '微信登录',
-                    listeners: {
-                        click: 'onWeixinLogin'
+                }
+            },
+            {
+                xtype: 'textfield',
+                cls: 'auth-textbox',
+                height: 55,
+                hideLabel: true,
+                emptyText: '密码',
+                inputType: 'password',
+                name: 'password',
+                bind: '{password}',
+                allowBlank: false,
+                triggers: {
+                    glyphed: {
+                        cls: 'trigger-glyph-noop auth-password-trigger'
                     }
-                },
-                {
-                    xtype: 'box',
-                    html: '<div class="outer-div"><div class="seperator">或</div></div>',
-                    margin: '10 0'
-                },
-                {
-                    xtype: 'button',
-                    scale: 'large',
-                    ui: 'gray',
-                    iconAlign: 'right',
-                    iconCls: 'x-fa fa-user-plus',
-                    text: '创建账户',
-                    listeners: {
-                        click: 'onNewAccount'
+                }
+            },
+            {
+                xtype: 'container',
+                layout: 'hbox',
+                items: [{
+                        xtype: 'checkboxfield',
+                        flex: 1,
+                        cls: 'form-panel-font-color rememberMeCheckbox',
+                        height: 30,
+                        bind: '{persist}',
+                        boxLabel: '记住账户'
+                    },
+                    {
+                        xtype: 'box',
+                        html: '<a href="#passwordreset" class="link-forgot-password"> 忘记密码 ?</a>'
                     }
-                }                
-            ]
-        }
-    ]
-});
+                ]
+            },
+            {
+                xtype: 'button',
+                reference: 'loginButton',
+                scale: 'large',
+                ui: 'soft-green',
+                iconAlign: 'right',
+                iconCls: 'x-fa fa-angle-right',
+                text: '登录',
+                formBind: true,
+                listeners: {
+                    click: 'onLoginButton'
+                }
+            }
+        ]
+    }]
+});

+ 1 - 0
frontend/saas-web/app/view/core/chart/EChartsBase.scss

@@ -1,6 +1,7 @@
 .x-echarts-container {
 
     .x-empty-chart {
+        background: #fff;
         position: absolute;
         width: 100%;
         height: 100%;

+ 31 - 28
frontend/saas-web/app/view/core/dbfind/ConDbfindTrigger.js

@@ -13,10 +13,11 @@ Ext.define('saas.view.core.form.field.ConDbfindTrigger', {
     isConField: true,
 
     // 额外字段自定义替换属性
+    mainFieldConfig: {},
     supFieldConfig: {},
 
     initComponent: function() {
-        var me = this,
+        let me = this,
         allowBlank = Ext.isBoolean(me.allowBlank) ? me.allowBlank : true,
         dbType = me.dbType,
         dbfinds = me.dbfinds,
@@ -24,7 +25,7 @@ Ext.define('saas.view.core.form.field.ConDbfindTrigger', {
         name1 = dbfinds[1].to;
 
         Ext.apply(me, {
-            items: [{
+            items: [Ext.Object.merge({
                 xtype: dbType,
                 name: name0,
                 allowBlank: allowBlank,
@@ -34,27 +35,7 @@ Ext.define('saas.view.core.form.field.ConDbfindTrigger', {
                 },
                 dbfinds: dbfinds,
                 flex: 0.5,
-                listeners: {
-                    validChange: function() {
-                        me.fireEvent('validChange');
-                    },
-                    validitychange: function() {
-                        me.items.items[1].isValid();
-                        me.fireEvent('validitychange');
-                    },
-                    change: function(f) {
-                        var field1 = me.items.items[1];
-                        if(f.dbValues) {
-                            var value = f.dbValues[f.dbfinds[1].from];
-                            field1.setValue(value);
-                            field1.publishState('value', value);
-                        }
-                    },
-                    blur: function() {
-                        this.fireEvent('change', this)
-                    }
-                }
-            }, Ext.Object.merge({
+            }, me.mainFieldConfig), Ext.Object.merge({
                 xtype: 'textfield',
                 name: name1,
                 allowBlank: allowBlank,
@@ -71,18 +52,40 @@ Ext.define('saas.view.core.form.field.ConDbfindTrigger', {
 
     listeners: {
         afterrender: function(me) {
-            me.items.items[1].on('validChange', function() {
+            let field1 = me.items.items[0];
+            let field2 = me.items.items[1];
+
+            field1.on('validChange', function() {
+                me.fireEvent('validChange');
+            });
+            field1.on('validitychange', function() {
+                me.items.items[1].isValid();
+                me.fireEvent('validitychange');
+            });
+            field1.on('change', function(f) {
+                let field1 = me.items.items[1];
+                if(f.dbValues) {
+                    let value = f.dbValues[f.dbfinds[1].from];
+                    field1.setValue(value);
+                    field1.publishState('value', value);
+                }
+            });
+            field1.on('blur', function() {
+                me.fireEvent('change', this)
+            });
+
+            field2.on('validChange', function() {
                 me.fireEvent('validChange');
             });
-            me.items.items[1].on('validitychange', function() {
-                me.items.items[0].isValid();
+            field2.on('validitychange', function() {
+                field1.isValid();
                 me.fireEvent('validitychange');
             });
         }
     },
 
     setValue: function(values) {
-        var me = this,
+        let me = this,
         name0 = dbfinds[0].to,
         name1 = dbfinds[1].to;
 
@@ -95,6 +98,6 @@ Ext.define('saas.view.core.form.field.ConDbfindTrigger', {
     },
 
     isDirty: function() {
-        return this.items.items[0].isDirty() && this.items.items[1].isDirty();
+        return this.items.items[0].isDirty() || this.items.items[1].isDirty();
     }
 });

+ 9 - 4
frontend/saas-web/app/view/core/dbfind/types/CurrencyDbfindTrigger.js

@@ -1,5 +1,5 @@
 /**
- * BOM资料放大镜
+ * 币别放大镜
  */
 Ext.define('saas.view.core.dbfind.types.CurrencyDbfindTrigger', {
     extend: 'saas.view.core.dbfind.DbfindTrigger',
@@ -12,10 +12,10 @@ Ext.define('saas.view.core.dbfind.types.CurrencyDbfindTrigger', {
     //联想设置
     dbtpls: [{
         field: 'cr_name',
-        width: 150
+        width: 65
     }, {
         field: 'cr_rate',
-        width: 200
+        width: 110
     }],
     
     //defaultCondition: "bo_statuscode='ENABLE'",
@@ -39,11 +39,16 @@ Ext.define('saas.view.core.dbfind.types.CurrencyDbfindTrigger', {
     }, {
         text: "币别",
         dataIndex: "cr_name",
-        width: 150,
+        align:'center',
+        width: 65,
     }, {
         text: "汇率",
         width: 150,
+        xtype: 'numbercolumn',
         dataIndex: "cr_rate",
+        renderer : function(v, m, r) {
+            return saas.util.BaseUtil.numberFormat(v, 6, false);
+        },
     },{
         dataIndex: '',
         flex: 1

+ 10 - 0
frontend/saas-web/app/view/core/dbfind/types/CustomerDbfindTrigger.js

@@ -57,6 +57,16 @@ Ext.define('saas.view.core.dbfind.types.CustomerDbfindTrigger', {
             return saas.util.BaseUtil.numberFormat(v, 2, false);
         }
     }, {
+        text: "币别",
+        dataIndex: "cu_currency",
+        width:65,
+        align:'center'
+    }, {
+        text: "汇率",
+        xtype:'numbercolumn',
+        dataIndex: "cr_rate",
+        width:80
+    },, {
         text: "业务员编号",
         dataIndex: "cu_sellercode",
         width:150,

+ 10 - 0
frontend/saas-web/app/view/core/dbfind/types/VendorDbfindTrigger.js

@@ -58,6 +58,16 @@ Ext.define('saas.view.core.dbfind.types.VendorDbfindTrigger', {
         renderer : function(v, m, r) {
             return saas.util.BaseUtil.numberFormat(v, 2, false);
         }
+    }, {
+        text: "币别",
+        dataIndex: "ve_currency",
+        width:65,
+        align:'center'
+    }, {
+        text: "汇率",
+        xtype:'numbercolumn',
+        dataIndex: "cr_rate",
+        width:80
     }, {
         text: "采购员编号",
         dataIndex: "ve_buyercode",

+ 2 - 1
frontend/saas-web/app/view/document/bankinformation/DataList.js

@@ -37,7 +37,8 @@ Ext.define('saas.view.document.bankinformation.DataList', {
             },{
                 text: '币别',
                 dataIndex: 'bk_currency',
-                width: 100
+                align:'center',
+                width: 65
             },{
                 text: '期初金额(元)',
                 dataIndex: 'bk_beginamount',

+ 10 - 0
frontend/saas-web/app/view/document/currencys/DatalistController.js

@@ -22,6 +22,16 @@ Ext.define('saas.view.document.currencys.DatalistController', {
         }
         return columns;
     },
+    onEdit:function(grid,row,col){
+        var dk = grid.ownerCt.ownerCt;
+        var dataKind=dk.getViewModel().getData()['dataKind'].value,
+        rec = grid.getStore().getAt(row);
+        if(rec.get('cr_standard')==1){
+            saas.util.BaseUtil.showErrorToast('本位币汇率无法修改')
+            return false;
+        }
+        this.createDialog(dataKind,rec,dk);
+    },
     onAdd:function(b){
         var form = this.view;
         this.dialog = form.add({

+ 1 - 1
frontend/saas-web/app/view/document/customer/FormPanel.js

@@ -56,7 +56,7 @@ Ext.define('saas.view.document.customer.FormPanel', {
     },
 
     initComponent: function () {
-        Ext.applyIf(this, {
+        Ext.apply(this, {
             defaultItems: [{
                 xtype: 'hidden',
                 name: 'id',

+ 2 - 1
frontend/saas-web/app/view/main/MainContainerWrap.js

@@ -34,11 +34,12 @@ Ext.define('saas.view.main.MainContainerWrap', {
                 closeTabText: '关闭当前页',
                 closeAllTabsText: '关闭全部页',
                 closeOthersTabsText: '关闭其他页'
-            })],
+            }), new Ext.ux.TabReorderer()],
             items: [{
                 iconCls: 'x-sa sa-home',
                 title: '首页',
                 closable: false,
+                reorderable: false,
                 // margin: '16 0 16 16',
                 // scrollable: true,
                 // bodyPadding: '0 16 0 0',

+ 83 - 74
frontend/saas-web/app/view/main/Navigation.js

@@ -79,7 +79,8 @@ Ext.define('saas.view.main.Navigation', {
     showNavMenu: function (navView, record, navItem, index, e, eOpts) {
         var me = this,
             recData = record.data,
-            menuItems = recData.items || [],
+            d = recData.items || [],
+            menuItems = JSON.parse( JSON.stringify( d ) ),
             itemBox = navItem.getBoundingClientRect(),
             pos = [itemBox.left + itemBox.width, itemBox.top],
             id = recData.id,
@@ -138,79 +139,8 @@ Ext.define('saas.view.main.Navigation', {
                 singleSelect: true,
                 itemSelector: 'menu',
                 listeners: {
-                    boxready: function (view, width, height, eOpts) {
-                        var menu = view.up('menu'),
-                            menuView = view.el.dom.getElementsByClassName('x-navitem-menu')[0],
-                            menuBox = menuView.getBoundingClientRect(),
-                            menuViewWidth = menuBox.width + me.menuPadding * 2,
-                            menuViewHeight = menuBox.height + me.menuPadding * 2,
-                            menuItem = menuView.getElementsByClassName('menuitem');
-
-                        menu.setWidth(menuViewWidth);
-                        menu.setHeight(menuViewHeight);
-                        menu.updateLayout();
-
-                        view.el.dom.addEventListener('mouseenter', function (e) {
-                            menu.show();
-                            menu.navItem.classList.add(menu.navView.overItemCls);
-                        });
-
-                        view.el.dom.addEventListener('mouseleave', function (e) {
-                            menu.navItem.classList.remove(menu.navView.overItemCls);
-                            menu.hide();
-                            // var ex = e.clientX,
-                            //     ey = e.clientY,
-                            //     box = menuView.getBoundingClientRect(),
-                            //     navItem = menu.navItem,
-                            //     navBox = navItem.getBoundingClientRect();
-
-                            // if ((ex <= box.left && (ey <= (navBox.top - 5) || ey >= (navBox.top + navBox.height))) || ey <= (box.top - 5) || ex >= (box.left + box.width + 5) || ey >= (box.top + box.height + 5)) {
-                            //     menu.navItem.classList.remove(menu.navView.overItemCls);
-                            //     menu.hide();
-                            // }
-                        });
-
-                        Ext.Array.each(menuItem, function (mi) {
-                            var menuItemText = mi.getElementsByClassName('item-text');
-                            var menuItemIcon = mi.getElementsByClassName('item-icon');
-
-                            Ext.Array.each(menuItemText, function (item) {
-                                item.addEventListener('click', function (e) {
-                                    var target = e.target,
-                                    dataset = target.dataset,
-                                    viewType = dataset.viewtype,
-                                    type = dataset.type,
-                                    text = dataset.text,
-                                    config = dataset.config,
-                                    id = dataset.id;
-
-                                    var tabTitle = text,
-                                    tabId = 'maintab-' + type + '-' + id;
-
-                                    menu.navItem.classList.remove(menu.navView.overItemCls);
-                                    saas.util.BaseUtil.openTab(viewType, tabTitle, tabId,config);
-                                    menu.hide();
-                                });
-                            });
-                            Ext.Array.each(menuItemIcon, function (item) {
-                                item.addEventListener('click', function (e) {
-                                    var target = e.target,
-                                    dataset = target.dataset,
-                                    viewType = dataset.viewtype,
-                                    type = dataset.type,
-                                    text = dataset.text,
-                                    id = dataset.id;
-
-                                    var tabTitle ='新增' + text,
-                                    tabId = viewType + '-add';
-
-                                    menu.navItem.classList.remove(menu.navView.overItemCls);
-                                    saas.util.BaseUtil.openTab(viewType, tabTitle, tabId);
-                                    menu.hide();
-                                });
-                            });
-                        });
-                    },
+                    // boxready: function (view, width, height, eOpts) {
+                    // },
                 }
             });
             var menu = Ext.create('Ext.menu.Menu', {
@@ -225,7 +155,86 @@ Ext.define('saas.view.main.Navigation', {
                 items: [view]
             });
         }
+        menu.items.items[0].store.removeAll();
+        menu.items.items[0].store.loadData(menuItems);
         menu.showAt(pos);
+        me.refresh(menu.items.items[0],pos)
+    },
+
+    refresh:function(view,pos){
+        var me = this;
+        var menu = view.up('menu'),
+        menuView = view.el.dom.getElementsByClassName('x-navitem-menu')[0],
+        menuBox = menuView.getBoundingClientRect(),
+        menuViewWidth = menuBox.width + me.menuPadding * 2,
+        menuViewHeight = menuBox.height + me.menuPadding * 2,
+        menuItem = menuView.getElementsByClassName('menuitem');
+
+        menu.setX(pos[0]);
+        menu.setWidth(menuViewWidth);
+        menu.setHeight(menuViewHeight);
+        menu.updateLayout();
+
+        view.el.dom.addEventListener('mouseenter', function (e) {
+            menu.show();
+            menu.navItem.classList.add(menu.navView.overItemCls);
+        });
+
+        view.el.dom.addEventListener('mouseleave', function (e) {
+            menu.navItem.classList.remove(menu.navView.overItemCls);
+            menu.hide();
+            // var ex = e.clientX,
+            //     ey = e.clientY,
+            //     box = menuView.getBoundingClientRect(),
+            //     navItem = menu.navItem,
+            //     navBox = navItem.getBoundingClientRect();
+
+            // if ((ex <= box.left && (ey <= (navBox.top - 5) || ey >= (navBox.top + navBox.height))) || ey <= (box.top - 5) || ex >= (box.left + box.width + 5) || ey >= (box.top + box.height + 5)) {
+            //     menu.navItem.classList.remove(menu.navView.overItemCls);
+            //     menu.hide();
+            // }
+        });
+
+        Ext.Array.each(menuItem, function (mi) {
+            var menuItemText = mi.getElementsByClassName('item-text');
+            var menuItemIcon = mi.getElementsByClassName('item-icon');
+
+            Ext.Array.each(menuItemText, function (item) {
+                item.addEventListener('click', function (e) {
+                    var target = e.target,
+                    dataset = target.dataset,
+                    viewType = dataset.viewtype,
+                    type = dataset.type,
+                    text = dataset.text,
+                    config = dataset.config,
+                    id = dataset.id;
+
+                    var tabTitle = text,
+                    tabId = 'maintab-' + type + '-' + id;
+
+                    menu.navItem.classList.remove(menu.navView.overItemCls);
+                    saas.util.BaseUtil.openTab(viewType, tabTitle, tabId,config);
+                    menu.hide();
+                });
+            });
+            Ext.Array.each(menuItemIcon, function (item) {
+                item.addEventListener('click', function (e) {
+                    var target = e.target,
+                    dataset = target.dataset,
+                    viewType = dataset.viewtype,
+                    type = dataset.type,
+                    text = dataset.text,
+                    id = dataset.id;
+
+                    var tabTitle ='新增' + text,
+                    tabId = viewType + '-add';
+
+                    menu.navItem.classList.remove(menu.navView.overItemCls);
+                    saas.util.BaseUtil.openTab(viewType, tabTitle, tabId);
+                    menu.hide();
+                });
+            });
+        });
     },
 
     hideNavMenu: function (view, record, item, index, e, eOpts) {

+ 7 - 3
frontend/saas-web/app/view/money/othreceipts/FormPanel.js

@@ -72,9 +72,13 @@ Ext.define('saas.view.money.othreceipts.FormPanel', {
                 }, {
                     from: 'cr_rate', to: 'or_rate'
                 }],
-                defaultValue: {
-                    or_currency: 'RMB',
-                    or_rate: 1
+                // defaultValue: {
+                //     or_currency: 'RMB',
+                //     or_rate: 1
+                // },
+                mainFieldConfig: {
+                    readOnly: false,
+                    defaultReadOnly: false,
                 },
                 supFieldConfig: {
                     xtype: 'numberfield',

+ 7 - 3
frontend/saas-web/app/view/money/othspendings/FormPanel.js

@@ -66,9 +66,13 @@ Ext.define('saas.view.money.othspendings.FormPanel', {
                 }, {
                     from: 'cr_rate', to: 'os_rate'
                 }],
-                defaultValue: {
-                    os_currency: 'RMB',
-                    os_rate: 1
+                // defaultValue: {
+                //     os_currency: 'RMB',
+                //     os_rate: 1
+                // },
+                mainFieldConfig: {
+                    readOnly: false,
+                    defaultReadOnly: false,
                 },
                 supFieldConfig: {
                     xtype: 'numberfield',

+ 4 - 4
frontend/saas-web/app/view/money/payBalance/FormPanel.js

@@ -76,10 +76,10 @@ Ext.define('saas.view.money.payBalance.FormPanel', {
                 }, {
                     from: 'cr_rate', to: 'pb_rate'
                 }],
-                defaultValue: {
-                    pb_currency: 'RMB',
-                    pb_rate: 1
-                },
+                // defaultValue: {
+                //     pb_currency: 'RMB',
+                //     pb_rate: 1
+                // },
                 supFieldConfig: {
                     xtype: 'numberfield',
                     readOnly: false,

+ 4 - 4
frontend/saas-web/app/view/money/recBalance/FormPanel.js

@@ -85,10 +85,10 @@ Ext.define('saas.view.money.recBalance.FormPanel', {
                 }, {
                     from: 'cr_rate', to: 'rb_rate'
                 }],
-                defaultValue: {
-                    rb_currency: 'RMB',
-                    rb_rate: 1
-                },
+                // defaultValue: {
+                //     rb_currency: 'RMB',
+                //     rb_rate: 1
+                // },
                 supFieldConfig: {
                     xtype: 'numberfield',
                     readOnly: false,

+ 6 - 1
frontend/saas-web/app/view/money/recBalance/FormPanelController.js

@@ -84,7 +84,12 @@ Ext.define('saas.view.money.recBalance.FormPanelController', {
                             from:'cu_name', to:'rb_custname'
                         },{
                             from:'cu_leftamount', to:'cu_leftamount'
-                        }],
+                        },{
+                            from:'cu_currency',to:'rb_currency'
+                        },{
+                            from:'cr_rate',to:'rb_rate'
+                        }
+                        ],
                     }) ;   
                 }
             },

+ 10 - 0
frontend/saas-web/app/view/money/report/AccountDetails.js

@@ -95,6 +95,16 @@ Ext.define('saas.view.money.report.AccountDetails', {
         renderer: function(v) {
             return saas.util.BaseUtil.numberFormat(v, 2, true);
         }
+    }, {
+        text: '币别',
+        dataIndex: 'currency',
+        align:'center',
+        width: 65
+    }, {
+        text: '汇率',
+        dataIndex: 'rate',
+        xtype: 'numbercolumn',
+        width: 80
     }, {
         dataIndex: '',
         flex: 1

+ 6 - 3
frontend/saas-web/app/view/money/report/PayDetail.js

@@ -6,8 +6,8 @@ Ext.define('saas.view.money.report.PayDetail', {
     viewModel: 'money-report-paydetail',
     viewName: 'money-report-paydetail',
 
-    groupField:'pd_vendname',
-    groupHeaderTpl: '供应商名称: {[values.rows[0].data.pd_vendname]}',
+    groupField: 'groupfield',
+    groupHeaderTpl: '{[values.rows[0].data.pd_vendname]} ({[values.rows[0].data.pd_currency]})',
     listUrl: '/api/money/report/payDetail',
     defaultCondition: null,
     reportTitle: '应付账款明细表',
@@ -27,6 +27,8 @@ Ext.define('saas.view.money.report.PayDetail', {
         columnWidth: 0.5
     }],
 
+    reportModel: 'saas.model.report.PayDetail',
+
     reportColumns: [{
         text: '单据日期',
         xtype: 'datecolumn',
@@ -47,7 +49,8 @@ Ext.define('saas.view.money.report.PayDetail', {
     }, {
         text: '币别',
         dataIndex: 'pd_currency',
-        width: 80
+        align:'center',
+        width: 65
     },{
         text: '增加应付(元)',
         xtype: 'numbercolumn',

+ 9 - 5
frontend/saas-web/app/view/money/report/ProfitDetail.js

@@ -54,7 +54,7 @@ Ext.define('saas.view.money.report.ProfitDetail', {
         dataIndex: 'netamount',
         exportFormat: 'Amount',
         xtype: 'numbercolumn',
-        width: 120,
+        width: 0,
         renderer : function(v, m, r) {
             return saas.util.BaseUtil.numberFormat(v, 2, true);
         },
@@ -63,12 +63,12 @@ Ext.define('saas.view.money.report.ProfitDetail', {
         dataIndex: 'saamount-netamount',
         exportFormat: 'Amount',
         xtype: 'numbercolumn',
-        width: 120,
+        width: 0,
         renderer : function(v, m, r) {
             return saas.util.BaseUtil.numberFormat(v, 2, true);
         },
     }, {
-        text: '价税合计(元)',
+        text: '销售金额(元)',
         dataIndex: 'saamount',
         exportFormat: 'Amount',
         xtype: 'numbercolumn',
@@ -77,7 +77,7 @@ Ext.define('saas.view.money.report.ProfitDetail', {
             return saas.util.BaseUtil.numberFormat(v, 2, true);
         },
         summaryType: 'sum',
-        summaryLabel: '价税合计',
+        summaryLabel: '销售金额',
         summaryRenderer: function(v) {
             return saas.util.BaseUtil.numberFormat(v, 2, true);
         }
@@ -87,10 +87,14 @@ Ext.define('saas.view.money.report.ProfitDetail', {
         exportFormat: 'Amount',
         xtype: 'numbercolumn',
         width: 120,
-        summaryLabel: '成本金额',
         renderer : function(v, m, r) {
             return saas.util.BaseUtil.numberFormat(v, 2, true);
         },
+        summaryType: 'sum',
+        summaryLabel: '成本金额',
+        summaryRenderer: function(v) {
+            return saas.util.BaseUtil.numberFormat(v, 2, true);
+        }
     }, {
         text: '毛利润(元)',
         dataIndex: 'profit',

Some files were not shown because too many files changed in this diff