Просмотр исходного кода

Merge branch 'dev' into dev-station-activity

skyline 2 лет назад
Родитель
Сommit
facc24e727
41 измененных файлов с 1010 добавлено и 377 удалено
  1. 22 0
      README.md
  2. 1 2
      admin-web/src/views/admin/index.vue
  3. 187 142
      admin-web/src/views/admin/invoice/dialog.vue
  4. 25 4
      admin-web/src/views/admin/invoice/index.vue
  5. 54 54
      admin-web/src/views/admin/station/stat/dialog.vue
  6. 1 1
      admin-web/src/views/admin/station/statment/dialog.vue
  7. 13 5
      admin-web/src/views/admin/station/statment/index.vue
  8. 1 0
      admin/src/main/java/com/kym/admin/config/SaTokenConfigure.java
  9. 10 0
      admin/src/main/java/com/kym/admin/controller/ConnectorInfoController.java
  10. 0 20
      admin/src/main/java/com/kym/admin/controller/EquipmentInfoController.java
  11. 79 4
      admin/src/main/java/com/kym/admin/controller/FinanceController.java
  12. 3 0
      admin/src/main/java/com/kym/admin/controller/InvestorInfoController.java
  13. 1 1
      admin/src/main/resources/application-dev.yml
  14. 2 1
      common/src/main/java/com/kym/common/constant/ResponseEnum.java
  15. 113 0
      entity/src/main/java/com/kym/entity/admin/InvoiceDetail.java
  16. 18 0
      entity/src/main/java/com/kym/entity/admin/queryParams/InvoiceDetailQueryParam.java
  17. 28 0
      entity/src/main/java/com/kym/entity/typehandle/InvoiceDetailBuyerInformationTypeHandle.java
  18. 30 0
      entity/src/main/java/com/kym/entity/typehandle/InvoiceDetailItemsTypeHandle.java
  19. 28 0
      entity/src/main/java/com/kym/entity/typehandle/InvoiceDetailSellerInformationTypeHandle.java
  20. 2 2
      entity/src/main/java/com/kym/entity/wechat/FaPiao.java
  21. 4 4
      entity/src/main/java/com/kym/entity/wechat/FapiaoApplications.java
  22. 2 2
      entity/src/main/java/com/kym/entity/wechat/InvoiceBaseInfo.java
  23. 17 0
      mapper/src/main/java/com/kym/mapper/admin/InvoiceDetailMapper.java
  24. 31 0
      mapper/src/main/resources/mappers/admin/InvoiceDetailMapper.xml
  25. 41 16
      miniapp/src/main/java/com/kym/miniapp/jobs/StartChargeDelayJob.java
  26. 9 6
      miniapp/src/main/java/com/kym/miniapp/jobs/StopChargeDelayJob.java
  27. 1 1
      miniapp/src/main/resources/application-dev.yml
  28. 3 0
      service/src/main/java/com/kym/service/admin/ConnectorInfoService.java
  29. 0 1
      service/src/main/java/com/kym/service/admin/EquipmentInfoService.java
  30. 23 0
      service/src/main/java/com/kym/service/admin/InvoiceDetailService.java
  31. 4 0
      service/src/main/java/com/kym/service/admin/impl/AdminUserServiceImpl.java
  32. 41 0
      service/src/main/java/com/kym/service/admin/impl/ConnectorInfoServiceImpl.java
  33. 0 24
      service/src/main/java/com/kym/service/admin/impl/EquipmentInfoServiceImpl.java
  34. 1 0
      service/src/main/java/com/kym/service/admin/impl/InvestorInfoServiceImpl.java
  35. 113 0
      service/src/main/java/com/kym/service/admin/impl/InvoiceDetailServiceImpl.java
  36. 22 8
      service/src/main/java/com/kym/service/admin/impl/StatementsServiceImpl.java
  37. 0 36
      service/src/main/java/com/kym/service/enplus/impl/EnPlusServiceHelper.java
  38. 16 8
      service/src/main/java/com/kym/service/enplus/impl/EnPlusServiceImpl.java
  39. 2 0
      service/src/main/java/com/kym/service/miniapp/InvoiceService.java
  40. 11 33
      service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java
  41. 51 2
      service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

+ 22 - 0
README.md

@@ -21,4 +21,26 @@ mvn clean package -DskipTests
 ```
 ```
 cd  ./charge-java/miniapp
 cd  ./charge-java/miniapp
 mvn clean package -DskipTests
 mvn clean package -DskipTests
+```
+
+
+### docker启动脚本
+```
+##jenkins
+docker run  -u 1001 --name jenkins -d -p 8099:8080 -p 50099:50000 -v /home/kym/jenkins1:/var/jenkins_home  jenkins/jenkins:jdk17-preview
+
+##mysql
+ docker run -u 1000  -p 3306:3306 --name mymysql  \
+-v /usr/local/docker/mysql/mysql-files:/var/lib/mysql-files \
+-v  /usr/local/docker/mysql/conf/my.cnf:/etc/mysql/my.cnf \
+ -v  /usr/local/docker/mysql/logs:/var/log/mysql \
+ -v /usr/local/docker/mysql/data:/var/lib/mysql  \
+-d mysql
+
+##redis
+docker run -u 1000 -p 6379:6379 --name myredis \
+-v /home/redis/myredis/redis.conf:/etc/redis/redis.conf \
+-v /home/redis/myredis/data:/data \
+-d redis redis-server /etc/redis/redis.conf \
+--appendonly yes 
 ```
 ```

+ 1 - 2
admin-web/src/views/admin/index.vue

@@ -81,7 +81,6 @@ import {Session} from "/@/utils/storage";
 // 定义变量内容
 // 定义变量内容
 const homeLineRef = ref();
 const homeLineRef = ref();
 const homePieRef = ref();
 const homePieRef = ref();
-const homeBarRef = ref();
 const storesTagsViewRoutes = useTagsViewRoutes();
 const storesTagsViewRoutes = useTagsViewRoutes();
 const storesThemeConfig = useThemeConfig();
 const storesThemeConfig = useThemeConfig();
 const {themeConfig} = storeToRefs(storesThemeConfig);
 const {themeConfig} = storeToRefs(storesThemeConfig);
@@ -425,7 +424,7 @@ const initEchartsResize = () => {
 };
 };
 
 
 const loadCurrentEquipmentStatus = () => {
 const loadCurrentEquipmentStatus = () => {
-  $get(`equipment/statEquipmentStatus`, {stationId: state.currentStationId}).then((res: any) => {
+  $get(`connector/statConnectorStatus`, {stationId: state.currentStationId}).then((res: any) => {
     console.log(res)
     console.log(res)
     initPieChart(res || {});
     initPieChart(res || {});
   })
   })

+ 187 - 142
admin-web/src/views/admin/invoice/dialog.vue

@@ -6,7 +6,7 @@
     <el-dialog
     <el-dialog
         :title="state.dialog.title"
         :title="state.dialog.title"
         v-model="state.dialog.isShowDialog"
         v-model="state.dialog.isShowDialog"
-        width="820px"
+        width="920px"
         draggable
         draggable
         destroy-on-close
         destroy-on-close
         :close-on-click-modal="false"
         :close-on-click-modal="false"
@@ -20,182 +20,213 @@
           size="default"
           size="default"
           label-width="125px"
           label-width="125px"
           class="mt5">
           class="mt5">
-        <el-form-item label="微信发票申请id" prop="applyId">
+<!--        <el-form-item label="微信发票申请id" prop="applyId">
           <el-input
           <el-input
               v-model.trim="state.ruleForm.applyId"
               v-model.trim="state.ruleForm.applyId"
-              placeholder="微信发票申请id"
-              clearable
+              placeholder="微信发票申请"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
-        </el-form-item>
-        <el-form-item label="用户id" prop="userId">
+        </el-form-item>-->
+        <el-form-item label="开票日期" prop="fapiaoTime">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.userId"
-              placeholder="用户id"
-              clearable
+              v-model.trim="state.ruleForm.fapiaoTime"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="发票抬头填写人的openid" prop="openid">
+        <el-form-item label="发票状态" prop="status">
+          <ext-d-label    class="wd200" type="Invoice.status" :model-value="state.ruleForm.status"> </ext-d-label>
+        </el-form-item>
+        <el-form-item label="开票人" prop="biller">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.openid"
-              placeholder="发票抬头填写人的openid"
-              clearable
+              v-model.trim="state.ruleForm.sellerInformation.name"
+              placeholder="开票人"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="发票关联订单详情" prop="orderDetails">
+<!--        <el-form-item label="用户id" prop="userId">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.orderDetails"
-              placeholder="发票关联订单详情"
-              clearable
+              v-model.trim="state.ruleForm.userId"
+              placeholder="用户id"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
-        </el-form-item>
+        </el-form-item>-->
         <el-form-item label="累积充电量(度)" prop="totalPower">
         <el-form-item label="累积充电量(度)" prop="totalPower">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.totalPower"
+              v-model.trim="state.detail.totalPower"
               placeholder="累积充电量(度)"
               placeholder="累积充电量(度)"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="累积总金额(分)" prop="totalMoney">
-          <el-input
-              v-model.trim="state.ruleForm.totalMoney"
-              placeholder="累积总金额(分)"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="累积电费(分)" prop="elecMoney">
-          <el-input
-              v-model.trim="state.ruleForm.elecMoney"
-              placeholder="累积电费(分)"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="累积服务费(分)" prop="serviceMoney">
-          <el-input
-              v-model.trim="state.ruleForm.serviceMoney"
-              placeholder="累积服务费(分)"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="服务费优惠金额(分)" prop="serviceMoneyDiscount">
-          <el-input
-              v-model.trim="state.ruleForm.serviceMoneyDiscount"
-              placeholder="服务费优惠金额(分)"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="接收发票邮箱" prop="email">
-          <el-input
-              v-model.trim="state.ruleForm.email"
-              placeholder="接收发票邮箱"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="电话" prop="phone">
-          <el-input
-              v-model.trim="state.ruleForm.phone"
-              placeholder="电话"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="发票类型:INDIVIDUAL-个人 ORGANIZATION-企业" prop="invoiceType">
-          <el-input
-              v-model.trim="state.ruleForm.invoiceType"
-              placeholder="发票类型:INDIVIDUAL-个人 ORGANIZATION-企业"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="发票抬头名称" prop="invoiceTitle">
-          <el-input
-              v-model.trim="state.ruleForm.invoiceTitle"
-              placeholder="发票抬头名称"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="公司税号" prop="taxId">
-          <el-input
-              v-model.trim="state.ruleForm.taxId"
-              placeholder="公司税号"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="公司地址" prop="address">
-          <el-input
-              v-model.trim="state.ruleForm.address"
-              placeholder="公司地址"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="开户银行" prop="bankName">
-          <el-input
-              v-model.trim="state.ruleForm.bankName"
-              placeholder="开户银行"
-              clearable
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="银行账户" prop="bankAccount">
+        <el-form-item label="累积总金额(元)" prop="totalMoney">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.bankAccount"
-              placeholder="银行账户"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.ruleForm.totalMoney)"
+              placeholder="累积总金额(元)"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="发票金额(单位:分)" prop="invoiceAmount">
+        <el-form-item label="累积电费(元)" prop="elecMoney">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.invoiceAmount"
-              placeholder="发票金额(单位:分)"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.detail.elecMoney)"
+              placeholder="累积电费(元)"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="税额详情信息" prop="taxInfo">
+        <el-form-item label="累积服务费(元)" prop="serviceMoney">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.taxInfo"
-              placeholder="税额详情信息"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.detail.serviceMoney)"
+              placeholder="累积服务费(元)"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="开票人" prop="biller">
+        <el-form-item label="服务费优惠金额(元)" prop="serviceMoneyDiscount">
           <el-input
           <el-input
-              v-model.trim="state.ruleForm.biller"
-              placeholder="开票人"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.detail.serviceMoneyDiscount)"
+              placeholder="服务费优惠金额(元)"
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="发票状态:0-待开票 1-已开票 2-已作废" prop="status">
-          <el-input
-              v-model.trim="state.ruleForm.status"
-              placeholder="发票状态:0-待开票 1-已开票 2-已作废"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
-        <el-form-item label="备注" prop="remark">
+
+        <el-card header="开票人" shadow="hover" class="mt20">
+          <el-form-item label="开票企业名称" prop="invoiceTitle">
+            <el-input
+                v-model.trim="state.ruleForm.sellerInformation.name"
+                placeholder="开票企业名称"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="公司税号" prop="taxId">
+            <el-input
+                v-model.trim="state.ruleForm.sellerInformation.taxpayer_id"
+                placeholder="公司税号"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="公司地址" prop="address">
+            <el-input
+                v-model.trim="state.ruleForm.sellerInformation.address"
+                placeholder="公司地址"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="开户银行" prop="bankName">
+            <el-input
+                v-model.trim="state.ruleForm.sellerInformation.bank_name"
+                placeholder="开户银行"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="联系电话" prop="bankAccount">
+            <el-input
+                v-model.trim="state.ruleForm.sellerInformation.telephone"
+                placeholder="联系电话"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+        </el-card>
+
+        <el-card header="发票信息" shadow="hover" class="mt20">
+          <el-form-item label="发票类型" prop="invoiceType">
+            <ext-d-label   class="wd200"  type="Invoice.type" :model-value="state.ruleForm.buyerInformation.type"></ext-d-label>
+          </el-form-item>
+          <el-form-item label="发票抬头名称" prop="invoiceTitle">
+            <el-input
+                v-model.trim="state.ruleForm.buyerInformation.name"
+                placeholder="发票抬头名称"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="公司税号" prop="taxId">
+            <el-input
+                v-model.trim="state.ruleForm.buyerInformation.taxpayer_id"
+                placeholder="公司税号"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="公司地址" prop="address">
+            <el-input
+                v-model.trim="state.ruleForm.buyerInformation.address"
+                placeholder="公司地址"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="开户银行" prop="bankName">
+            <el-input
+                v-model.trim="state.ruleForm.buyerInformation.bank_name"
+                placeholder="开户银行"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="银行账户" prop="bankAccount">
+            <el-input
+                v-model.trim="state.ruleForm.buyerInformation.bank_account"
+                placeholder="银行账户"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="发票金额(单位:分)" prop="invoiceAmount">
+            <el-input
+                :model-value="u.fmt.fmtMoney(state.ruleForm.totalAmount)"
+                placeholder="发票金额(单位:分)"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="税额" prop="taxInfo">
+            <el-input
+                :model-value="u.fmt.fmtMoney(state.ruleForm.taxAmount)"
+                placeholder="税额"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+
+          <el-form-item label="接收发票邮箱" prop="email">
+            <el-input
+                v-model.trim="state.ruleForm.email"
+                placeholder="接收发票邮箱"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+          <el-form-item label="电话" prop="phone">
+            <el-input
+                v-model.trim="state.ruleForm.phone"
+                placeholder="电话"
+                readonly
+                class="wd200">
+            </el-input>
+          </el-form-item>
+        </el-card>
+
+
+
+<!--        <el-form-item label="备注" prop="remark">
           <el-input
           <el-input
               v-model.trim="state.ruleForm.remark"
               v-model.trim="state.ruleForm.remark"
               placeholder="备注"
               placeholder="备注"
-              clearable
+              readonly
               class="wd200">
               class="wd200">
           </el-input>
           </el-input>
-        </el-form-item>
+        </el-form-item>-->
       </el-form>
       </el-form>
 
 
       <template #footer>
       <template #footer>
@@ -213,6 +244,7 @@ import {defineAsyncComponent, reactive, onMounted, ref} from 'vue';
 import {Msg} from "/@/utils/message";
 import {Msg} from "/@/utils/message";
 import {$body, $get} from "/@/utils/request";
 import {$body, $get} from "/@/utils/request";
 import u from '/@/utils/u'
 import u from '/@/utils/u'
+import ExtDLabel from "/@/components/form/ExtDLabel.vue";
 
 
 
 
 // 定义子组件向父组件传值/事件
 // 定义子组件向父组件传值/事件
@@ -221,7 +253,15 @@ const formRef = ref();
 //定义初始变量,重置使用
 //定义初始变量,重置使用
 const initState = () => ({
 const initState = () => ({
   ruleForm: {
   ruleForm: {
-    id: 0
+    id: 0,
+    buyerInformation:{},
+    blueFapiao:{},
+    cardInformation:{},
+    extraInformationd:{},
+    sellerInformation:{},
+  },
+  detail:{
+    orderDetails:{}
   },
   },
   btnLoading: false,
   btnLoading: false,
   dialog: {
   dialog: {
@@ -239,11 +279,12 @@ const state = reactive(initState());
 
 
 // 打开弹窗
 // 打开弹窗
 const open = (action: string = 'add', row: any) => {
 const open = (action: string = 'add', row: any) => {
-  state.dialog.title = u.dialog.actions[action].title + "『发票记录表』"
-  state.dialog.submitTxt = u.dialog.actions[action].btn + "『发票记录表』"
+  state.dialog.title = u.dialog.actions[action].title + "『发票』"
+  state.dialog.submitTxt = u.dialog.actions[action].btn
   state.dialog.isShowDialog = true;
   state.dialog.isShowDialog = true;
+  state.detail = row;
   if (action !== 'add') {
   if (action !== 'add') {
-    loadData(row.id);
+    loadData(row.applyId);
   } else {
   } else {
     state.ruleForm = Object.assign(state.ruleForm, row);
     state.ruleForm = Object.assign(state.ruleForm, row);
   }
   }
@@ -282,9 +323,13 @@ const handleFormChange = (formData: any) => {
 }
 }
 
 
 // 初始化表格数据
 // 初始化表格数据
-const loadData = (id: number) => {
-  $get(`invoice/detail/${id}`).then((res: any) => {
-    state.ruleForm = res;
+const loadData = (applyId: String) => {
+  $get(`finance/listInvoiceDetail`, {applyId}).then((res: any) => {
+    if (res.list && res.list.length > 0) {
+      state.ruleForm = res.list[0];
+      console.log(state.ruleForm)
+      console.log(state.detail)
+    }
   })
   })
 }
 }
 
 

+ 25 - 4
admin-web/src/views/admin/invoice/index.vue

@@ -91,6 +91,11 @@
           <SvgIcon name="ele-Search"/>
           <SvgIcon name="ele-Search"/>
           查询
           查询
         </el-button>
         </el-button>
+
+        <el-button class="ml10" plain size="default" type="warning" @click="handleDownloadExcel">
+          <SvgIcon name="ele-Download"/>
+          下载
+        </el-button>
       </el-form>
       </el-form>
 
 
       <el-table
       <el-table
@@ -169,8 +174,9 @@
             </template>
             </template>
             <template v-else-if="field.prop==='action'">
             <template v-else-if="field.prop==='action'">
               <el-button v-if="row.status===0" v-auth="'invoice.modify'" size="small" plain type="warning" @click="handleInvice(row)">开票</el-button>
               <el-button v-if="row.status===0" v-auth="'invoice.modify'" size="small" plain type="warning" @click="handleInvice(row)">开票</el-button>
-              <el-button v-if="row.status===0" v-auth="'invoice.modify'" size="small" plain type="danger" @click="handleCancelInvoice(row)">取消</el-button>
-              <el-button v-if="row.status===1" v-auth="'invoice.modify'" size="small" plain type="success" @click="previewInvoice(row)">查看</el-button>
+              <el-button v-if="row.status===0" v-auth="'invoice.modify'" size="small" plain type="danger" @click="handleCancelInvoice(row)">取消开票</el-button>
+              <el-button v-if="row.status===1" v-auth="'invoice.modify'" size="small" plain type="success" @click="previewInvoice(row)">下载</el-button>
+              <el-button   size="small" plain type="success" @click="handleInfo(row)">详情</el-button>
             </template>
             </template>
             <template v-else>
             <template v-else>
               <div>{{ row[field.prop] }}</div>
               <div>{{ row[field.prop] }}</div>
@@ -190,7 +196,7 @@
 
 
 <script setup lang="ts" name="InvoiceList">
 <script setup lang="ts" name="InvoiceList">
 import {defineAsyncComponent, reactive, onMounted, onBeforeMount, ref, getCurrentInstance, nextTick, onBeforeUnmount} from 'vue';
 import {defineAsyncComponent, reactive, onMounted, onBeforeMount, ref, getCurrentInstance, nextTick, onBeforeUnmount} from 'vue';
-import {$body, $get} from "/@/utils/request";
+import {$body, $get, $post} from "/@/utils/request";
 import {Msg} from "/@/utils/message";
 import {Msg} from "/@/utils/message";
 import u from "/@/utils/u"
 import u from "/@/utils/u"
 
 
@@ -256,7 +262,7 @@ const state = reactive({
       {label: '创建时间', prop: 'createTime', sortable: 'custom', resizable: true, width: 170},
       {label: '创建时间', prop: 'createTime', sortable: 'custom', resizable: true, width: 170},
       {label: '更新时间', prop: 'updateTime', sortable: 'custom', resizable: true, width: 170},
       {label: '更新时间', prop: 'updateTime', sortable: 'custom', resizable: true, width: 170},
       {
       {
-        label: '操作', prop: 'action', width: 180, align: 'center', fixed: 'right',
+        label: '操作', prop: 'action', width: 240, align: 'center', fixed: 'right',
       }
       }
     ],
     ],
   },
   },
@@ -324,6 +330,21 @@ const handleInvice = (row: any) => {
 
 
 }
 }
 
 
+const handleDownloadExcel = () => {
+  let url = import.meta.env.VITE_API_URL;
+  if (!url) {
+    url = `${location.origin}/admin/`;
+  }
+  // $get(`/finance/export/invoiceList`,{...state.formQuery, ...state.pageQuery})
+  let params =   Object.keys(state.formQuery).map(key=>`${key}=${state.formQuery[key]}`).join("&")
+
+  window.open(`${url}/finance/export/invoiceList?t=${new Date().getTime()}&${params}`, "_blank")
+}
+
+const handleInfo = (row:any) => {
+  invoiceDialogRef.value.open('view', row);
+}
+
 
 
 const previewInvoice =   (row: any) => {
 const previewInvoice =   (row: any) => {
   $get(`/finance/downloadInvoice/${row.id}`).then((res: any) => {
   $get(`/finance/downloadInvoice/${row.id}`).then((res: any) => {

+ 54 - 54
admin-web/src/views/admin/station/stat/dialog.vue

@@ -53,17 +53,17 @@
 
 
 
 
 
 
-        <el-form-item label="总电量" prop="totalPower">
-          <ext-input-number v-model="state.ruleForm.totalPower"  class="wd200"></ext-input-number>
-        </el-form-item>
+<!--        <el-form-item label="总电量" prop="totalPower">-->
+<!--          <ext-input-number v-model="state.ruleForm.totalPower"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
 
 
-        <el-form-item label="总充电费用" prop="totalMoney">
-          <ext-input-number v-model="state.ruleForm.totalMoney"  class="wd200"></ext-input-number>
-        </el-form-item>
+<!--        <el-form-item label="总充电费用" prop="totalMoney">-->
+<!--          <ext-input-number v-model="state.ruleForm.totalMoney"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
 
 
-        <el-form-item label="总电费" prop="elecMoney">
-          <ext-input-number v-model="state.ruleForm.elecMoney"  class="wd200"></ext-input-number>
-        </el-form-item>
+<!--        <el-form-item label="总电费" prop="elecMoney">-->
+<!--          <ext-input-number v-model="state.ruleForm.elecMoney"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
 
 
         <el-form-item label="实际抄表电费" prop="actualElecMoney">
         <el-form-item label="实际抄表电费" prop="actualElecMoney">
           <ext-input-number v-model="state.ruleForm.actualElecMoney"  class="wd200"></ext-input-number>
           <ext-input-number v-model="state.ruleForm.actualElecMoney"  class="wd200"></ext-input-number>
@@ -73,62 +73,62 @@
         </el-form-item>
         </el-form-item>
 
 
 
 
-        <el-form-item label="总服务费" prop="serviceMoney">
-          <ext-input-number v-model="state.ruleForm.serviceMoney"  class="wd200"></ext-input-number>
-        </el-form-item>
+<!--        <el-form-item label="总服务费" prop="serviceMoney">-->
+<!--          <ext-input-number v-model="state.ruleForm.serviceMoney"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
 
 
 
 
-        <el-form-item label="服务费优惠金额" prop="serviceMoneyDiscount">
-          <ext-input-number v-model="state.ruleForm.serviceMoneyDiscount"  class="wd200"></ext-input-number>
-        </el-form-item>
+<!--        <el-form-item label="服务费优惠金额" prop="serviceMoneyDiscount">-->
+<!--          <ext-input-number v-model="state.ruleForm.serviceMoneyDiscount"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
 
 
 
 
-        <el-form-item label="总优惠金额" prop="discountAmount">
-          <ext-input-number v-model="state.ruleForm.discountAmount"  class="wd200"></ext-input-number>
-        </el-form-item>
+<!--        <el-form-item label="总优惠金额" prop="discountAmount">-->
+<!--          <ext-input-number v-model="state.ruleForm.discountAmount"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
 
 
-        <el-form-item label="单枪平均日充电量" prop="avgConnectorElec">
-          <ext-input-number v-model="state.ruleForm.avgConnectorElec"  class="wd200"></ext-input-number>
-        </el-form-item>
-        <el-form-item label="订单平均充电量" prop="avgOrderElec">
-          <ext-input-number v-model="state.ruleForm.avgOrderElec"  class="wd200"></ext-input-number>
-        </el-form-item>
-        <el-form-item label="订单平均充电费用" prop="avgOrderMoney">
-          <ext-input-number v-model="state.ruleForm.avgOrderMoney"  class="wd200"></ext-input-number>
-        </el-form-item>
+<!--        <el-form-item label="单枪平均日充电量" prop="avgConnectorElec">-->
+<!--          <ext-input-number v-model="state.ruleForm.avgConnectorElec"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="订单平均充电量" prop="avgOrderElec">-->
+<!--          <ext-input-number v-model="state.ruleForm.avgOrderElec"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
+<!--        <el-form-item label="订单平均充电费用" prop="avgOrderMoney">-->
+<!--          <ext-input-number v-model="state.ruleForm.avgOrderMoney"  class="wd200"></ext-input-number>-->
+<!--        </el-form-item>-->
 
 
 
 
-        <el-form-item label="充电人数" prop="chargeUsers">
-          <el-input
-              type="number"
-              v-model="state.ruleForm.chargeUsers"
-              placeholder="充电人数"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
+<!--        <el-form-item label="充电人数" prop="chargeUsers">-->
+<!--          <el-input-->
+<!--              type="number"-->
+<!--              v-model="state.ruleForm.chargeUsers"-->
+<!--              placeholder="充电人数"-->
+<!--              clearable-->
+<!--              class="wd200">-->
+<!--          </el-input>-->
+<!--        </el-form-item>-->
 
 
 
 
 
 
-        <el-form-item label="充电有效订单数" prop="validOrders">
-          <el-input
-              type="number"
-              v-model="state.ruleForm.validOrders"
-              placeholder="充电有效订单数"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
+<!--        <el-form-item label="充电有效订单数" prop="validOrders">-->
+<!--          <el-input-->
+<!--              type="number"-->
+<!--              v-model="state.ruleForm.validOrders"-->
+<!--              placeholder="充电有效订单数"-->
+<!--              clearable-->
+<!--              class="wd200">-->
+<!--          </el-input>-->
+<!--        </el-form-item>-->
 
 
-        <el-form-item label="设备使用率" prop="connectorUsageRate">
-          <el-input
-              type="number"
-              v-model="state.ruleForm.connectorUsageRate"
-              placeholder="设备使用率"
-              clearable
-              class="wd200">
-          </el-input>
-        </el-form-item>
+<!--        <el-form-item label="设备使用率" prop="connectorUsageRate">-->
+<!--          <el-input-->
+<!--              type="number"-->
+<!--              v-model="state.ruleForm.connectorUsageRate"-->
+<!--              placeholder="设备使用率"-->
+<!--              clearable-->
+<!--              class="wd200">-->
+<!--          </el-input>-->
+<!--        </el-form-item>-->
       </el-form>
       </el-form>
 
 
       <template #footer>
       <template #footer>

+ 1 - 1
admin-web/src/views/admin/station/statment/dialog.vue

@@ -69,8 +69,8 @@ tbody tr td {
             <td>{{ state.ruleForm.stationName }}</td>
             <td>{{ state.ruleForm.stationName }}</td>
             <td>{{ state.ruleForm.totalPower }}</td>
             <td>{{ state.ruleForm.totalPower }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.elecMoney) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.elecMoney) }}</td>
-            <td>{{ state.ruleForm.actualPower }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.actualPower) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.actualPower) }}</td>
+            <td>{{ u.fmt.fmtMoney(state.ruleForm.actualElecMoney) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.elecLossMoney) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.elecLossMoney) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.serviceMoney - state.ruleForm.discountAmount) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.serviceMoney - state.ruleForm.discountAmount) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.totalMoney - state.ruleForm.discountAmount) }}</td>
             <td>{{ u.fmt.fmtMoney(state.ruleForm.totalMoney - state.ruleForm.discountAmount) }}</td>

+ 13 - 5
admin-web/src/views/admin/station/statment/index.vue

@@ -48,13 +48,20 @@
             @blur="loadData(true)"
             @blur="loadData(true)"
             class="wd150 mr10">
             class="wd150 mr10">
         </el-input>
         </el-input>
-        <el-input
-            v-model="state.formQuery.stationName"
-            placeholder="站点名称"
+
+        <ext-select
+            v-model="state.formQuery.stationId"
+            placeholder="站点"
             clearable
             clearable
-            @blur="loadData(true)"
+            url="station/listStation"
+            urlMethod="get"
+            data-key=""
+            label-key="stationName"
+            value-key="stationId"
+            @on-change="loadData(true)"
             class="wd150 mr10">
             class="wd150 mr10">
-        </el-input>
+        </ext-select>
+
         <ext-d-select
         <ext-d-select
             type="Statement.status"
             type="Statement.status"
             v-model="state.formQuery.status"
             v-model="state.formQuery.status"
@@ -138,6 +145,7 @@ import ExtPage from '/@/components/form/ExtPage.vue'
 import mittBus from '/@/utils/mitt';
 import mittBus from '/@/utils/mitt';
 import ExtDLabel from "/@/components/form/ExtDLabel.vue";
 import ExtDLabel from "/@/components/form/ExtDLabel.vue";
 import ExtDSelect from "/@/components/form/ExtDSelect.vue";
 import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtSelect from "/@/components/form/ExtSelect.vue";
 
 
 const StatementsDialog = defineAsyncComponent(() => import("/@/views/admin/station/statment/dialog.vue"));
 const StatementsDialog = defineAsyncComponent(() => import("/@/views/admin/station/statment/dialog.vue"));
 
 

+ 1 - 0
admin/src/main/java/com/kym/admin/config/SaTokenConfigure.java

@@ -22,6 +22,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
                 // login接口不鉴权
                 // login接口不鉴权
                 .excludePathPatterns(
                 .excludePathPatterns(
                         "/wx/*",
                         "/wx/*",
+                        "/finance/export/*",
                         "/**/login",
                         "/**/login",
                         "/**/error",
                         "/**/error",
                         "/**/pullEnStations",
                         "/**/pullEnStations",

+ 10 - 0
admin/src/main/java/com/kym/admin/controller/ConnectorInfoController.java

@@ -41,5 +41,15 @@ public class ConnectorInfoController {
         return R.success(res);
         return R.success(res);
     }
     }
 
 
+    /**
+     * 统计充电桩状态
+     *
+     * @return
+     */
+    @GetMapping("/statConnectorStatus")
+    R<?> statEquipment(String stationId) {
+        return R.success(connectorInfoService.statConnectorStatus(stationId));
+    }
+
 
 
 }
 }

+ 0 - 20
admin/src/main/java/com/kym/admin/controller/EquipmentInfoController.java

@@ -1,8 +1,5 @@
 package com.kym.admin.controller;
 package com.kym.admin.controller;
 
 
-import com.kym.common.R;
-import com.kym.service.admin.EquipmentInfoService;
-import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.bind.annotation.RestController;
 
 
@@ -18,21 +15,4 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/equipment")
 @RequestMapping("/equipment")
 public class EquipmentInfoController {
 public class EquipmentInfoController {
 
 
-    private final EquipmentInfoService equipmentInfoService;
-
-    public EquipmentInfoController(EquipmentInfoService equipmentInfoService) {
-        this.equipmentInfoService = equipmentInfoService;
-    }
-
-
-    /**
-     * 统计充电桩状态
-     *
-     * @return
-     */
-    @GetMapping("/statEquipmentStatus")
-    R<?> statEquipment(String stationId) {
-        return R.success(equipmentInfoService.statEquipmentStatus(stationId));
-    }
-
 }
 }

+ 79 - 4
admin/src/main/java/com/kym/admin/controller/FinanceController.java

@@ -1,13 +1,40 @@
 package com.kym.admin.controller;
 package com.kym.admin.controller;
 
 
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.ExcelWriter;
 import com.kym.common.R;
 import com.kym.common.R;
 import com.kym.common.annotation.SysLog;
 import com.kym.common.annotation.SysLog;
 import com.kym.entity.admin.queryParams.CommonQueryParam;
 import com.kym.entity.admin.queryParams.CommonQueryParam;
+import com.kym.entity.admin.queryParams.InvoiceDetailQueryParam;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
+import com.kym.entity.common.PageBean;
+import com.kym.entity.miniapp.vo.InvoiceVo;
+import com.kym.service.admin.InvoiceDetailService;
 import com.kym.service.miniapp.InvoiceService;
 import com.kym.service.miniapp.InvoiceService;
 import com.kym.service.miniapp.RefundLogService;
 import com.kym.service.miniapp.RefundLogService;
 import com.kym.service.wechat.WxPayService;
 import com.kym.service.wechat.WxPayService;
-import org.springframework.web.bind.annotation.*;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 
 
 /**
 /**
  * @author skyline
  * @author skyline
@@ -18,16 +45,20 @@ import org.springframework.web.bind.annotation.*;
 @RequestMapping("/finance")
 @RequestMapping("/finance")
 public class FinanceController {
 public class FinanceController {
 
 
+    private final Logger logger = LoggerFactory.getLogger(FinanceController.class);
+
     private final WxPayService wxPayService;
     private final WxPayService wxPayService;
 
 
     private final RefundLogService refundLogService;
     private final RefundLogService refundLogService;
 
 
     private final InvoiceService invoiceService;
     private final InvoiceService invoiceService;
+    private final InvoiceDetailService invoiceDetailService;
 
 
-    public FinanceController(WxPayService wxPayService, RefundLogService refundLogService, InvoiceService invoiceService) {
+    public FinanceController(WxPayService wxPayService, RefundLogService refundLogService, InvoiceService invoiceService, InvoiceDetailService invoiceDetailService) {
         this.wxPayService = wxPayService;
         this.wxPayService = wxPayService;
         this.refundLogService = refundLogService;
         this.refundLogService = refundLogService;
         this.invoiceService = invoiceService;
         this.invoiceService = invoiceService;
+        this.invoiceDetailService = invoiceDetailService;
     }
     }
 
 
     @SysLog("退款申请列表")
     @SysLog("退款申请列表")
@@ -44,7 +75,7 @@ public class FinanceController {
     }
     }
 
 
     @GetMapping("/getUserTitle/{applyId}")
     @GetMapping("/getUserTitle/{applyId}")
-    Object getUserTitle(@PathVariable String applyId){
+    Object getUserTitle(@PathVariable String applyId) {
         return wxPayService.userTitle(applyId);
         return wxPayService.userTitle(applyId);
     }
     }
 
 
@@ -74,12 +105,56 @@ public class FinanceController {
 
 
     /**
     /**
      * 取消申请发票,发票抬头填写页关闭事件调用
      * 取消申请发票,发票抬头填写页关闭事件调用
+     *
      * @return
      * @return
      */
      */
     @GetMapping("/cancelApplyInvoice/{invoiceId}")
     @GetMapping("/cancelApplyInvoice/{invoiceId}")
-    R<?> cancelApplyInvoice(@PathVariable String invoiceId){
+    R<?> cancelApplyInvoice(@PathVariable String invoiceId) {
         invoiceService.cancelApplyInvoice(invoiceId);
         invoiceService.cancelApplyInvoice(invoiceId);
         return R.success();
         return R.success();
     }
     }
 
 
+    /**
+     * 查询发票
+     */
+    @GetMapping("/getInvoice/{applyId}")
+    R<?> getInvoice(@PathVariable String applyId) {
+        return R.success(wxPayService.queryFapiao(applyId));
+    }
+
+
+    /**
+     * 发票详情
+     */
+    @GetMapping("/listInvoiceDetail")
+    R<?> listInvoiceDetail(@ModelAttribute InvoiceDetailQueryParam params) {
+        return R.success(invoiceDetailService.listInvoiceDetail(params));
+    }
+
+
+    /**
+     * 导出发票列表
+     *
+     * @param params
+     * @return 导出发票列表
+     */
+    @GetMapping("/export/invoiceList")
+    void exportInvoiceList(@ModelAttribute InvoiceDetailQueryParam params, HttpServletRequest request, HttpServletResponse response) {
+        ExcelWriter writer = invoiceDetailService.exportInvoiceDetail(params);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
+        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode("发票详情列表", StandardCharsets.UTF_8) + ".xlsx");
+        ServletOutputStream excelOut = null;
+        try {
+            excelOut = response.getOutputStream();
+            writer.flush(excelOut, true);
+        } catch (Exception e) {
+            logger.error("exportInvoiceList error", e);
+        } finally {
+            writer.close();
+        }
+        IoUtil.close(excelOut);
+    }
+
 }
 }
+
+

+ 3 - 0
admin/src/main/java/com/kym/admin/controller/InvestorInfoController.java

@@ -2,6 +2,7 @@ package com.kym.admin.controller;
 
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import com.kym.common.R;
 import com.kym.common.R;
+import com.kym.common.utils.CommUtil;
 import com.kym.entity.admin.InvestorInfo;
 import com.kym.entity.admin.InvestorInfo;
 import com.kym.entity.admin.queryParams.CommonQueryParam;
 import com.kym.entity.admin.queryParams.CommonQueryParam;
 import com.kym.service.admin.InvestorInfoService;
 import com.kym.service.admin.InvestorInfoService;
@@ -36,6 +37,7 @@ public class InvestorInfoController {
     R<?> create(@RequestBody InvestorInfo investorInfo) {
     R<?> create(@RequestBody InvestorInfo investorInfo) {
         investorInfo.setId(null);
         investorInfo.setId(null);
         investorInfo.setStationName(KymCache.INSTANCE.getStationNameById(investorInfo.getStationId()));
         investorInfo.setStationName(KymCache.INSTANCE.getStationNameById(investorInfo.getStationId()));
+        CommUtil.asserts(investorInfo.getAdminUserId() != null, "请选择关联客户");
         return R.success(investorInfoService.save(investorInfo));
         return R.success(investorInfoService.save(investorInfo));
     }
     }
 
 
@@ -47,6 +49,7 @@ public class InvestorInfoController {
     @PostMapping("/update")
     @PostMapping("/update")
     R<?> update(@RequestBody InvestorInfo investorInfo) {
     R<?> update(@RequestBody InvestorInfo investorInfo) {
         investorInfo.setStationName(KymCache.INSTANCE.getStationNameById(investorInfo.getStationId()));
         investorInfo.setStationName(KymCache.INSTANCE.getStationNameById(investorInfo.getStationId()));
+        CommUtil.asserts(investorInfo.getAdminUserId() != null, "请选择关联客户");
         return R.success(investorInfoService.updateById(investorInfo));
         return R.success(investorInfoService.updateById(investorInfo));
     }
     }
 
 

+ 1 - 1
admin/src/main/resources/application-dev.yml

@@ -94,7 +94,7 @@ spring:
     redis:
     redis:
       port: 6380
       port: 6380
       host: 121.40.98.15
       host: 121.40.98.15
-      password: 123456
+      password: kuaiyuman@3rt
       database: 0
       database: 0
       lettuce:
       lettuce:
         pool:
         pool:

+ 2 - 1
common/src/main/java/com/kym/common/constant/ResponseEnum.java

@@ -53,7 +53,8 @@ public enum ResponseEnum implements BusinessExceptionAssert {
     // EN+
     // EN+
     EN_PLUS_API_EXCEPTION(90000, "接口数据异常"),
     EN_PLUS_API_EXCEPTION(90000, "接口数据异常"),
     EN_PLUS_QUERY_TOKEN_ERROR(90001, "TOKEN获取异常"),
     EN_PLUS_QUERY_TOKEN_ERROR(90001, "TOKEN获取异常"),
-    EN_PLUS_PUSH_SIGN_FAIL(90002, "EN+推送数据验签失败");
+    EN_PLUS_PUSH_SIGN_FAIL(90002, "EN+推送数据验签失败"),
+    EN_PLUS_TOKEN_EXCEPTION(90003,"EN+TOKEN过期");
 
 
     private final Integer code;
     private final Integer code;
     private final String message;
     private final String message;

+ 113 - 0
entity/src/main/java/com/kym/entity/admin/InvoiceDetail.java

@@ -0,0 +1,113 @@
+package com.kym.entity.admin;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.kym.entity.BaseEntity;
+import com.kym.entity.typehandle.InvoiceDetailBuyerInformationTypeHandle;
+import com.kym.entity.typehandle.InvoiceDetailSellerInformationTypeHandle;
+import com.kym.entity.wechat.FaPiao;
+import com.kym.entity.wechat.FapiaoApplications;
+import com.kym.entity.wechat.InvoiceBaseInfo;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 发票详情表
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-03-19
+ */
+@Getter
+@Setter
+@TableName(value = "t_invoice_detail",autoResultMap = true)
+@Accessors(chain = true)
+public class InvoiceDetail extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 微信发票申请id
+     */
+    private String applyId;
+
+    /**
+     * 开票时间
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime fapiaoTime;
+
+    /**
+     * 发票状态:ISSUE_ACCEPTED-开票申请已受理,ISSUED-发票已开具,REVERSE_ACCEPTED-冲红申请已受理,REVERSED-发票已冲红
+     */
+    private String status;
+
+    /**
+     * 蓝票
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private FapiaoApplications.FapiaoInfo blueFapiao;
+
+    /**
+     * 红票
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Object redFapiao;
+
+    /**
+     * 卡包信息
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Object cardInformation;
+
+    /**
+     * 总开票金额(分)
+     */
+    private Integer totalAmount;
+
+    /**
+     * 税额(分)
+     */
+    private Integer taxAmount;
+
+    /**
+     * 不含税金额(分)
+     */
+    private Integer amount;
+
+    /**
+     * 售卖方信息
+     */
+    @TableField(typeHandler = InvoiceDetailSellerInformationTypeHandle.class)
+    private InvoiceBaseInfo.SellerInfo sellerInformation;
+
+    /**
+     * 购买方信息
+     */
+    @TableField(typeHandler = InvoiceDetailBuyerInformationTypeHandle.class)
+    private FaPiao.BuyerInformation buyerInformation;
+
+    /**
+     * 扩展信息
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Object extraInformation;
+
+    /**
+     * 发票明细
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<FapiaoApplications.FapiaoItem> items;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 18 - 0
entity/src/main/java/com/kym/entity/admin/queryParams/InvoiceDetailQueryParam.java

@@ -0,0 +1,18 @@
+package com.kym.entity.admin.queryParams;
+
+import com.kym.entity.common.PageParams;
+import lombok.Data;
+
+/**
+ * @author skyline
+ * @description 发票详情查询参数
+ * @date 2023-08-22 18:56
+ */
+@Data
+public class InvoiceDetailQueryParam extends PageParams {
+    /**
+     * 发票申请id
+     */
+    private String applyId;
+
+}

+ 28 - 0
entity/src/main/java/com/kym/entity/typehandle/InvoiceDetailBuyerInformationTypeHandle.java

@@ -0,0 +1,28 @@
+package com.kym.entity.typehandle;
+
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.kym.entity.wechat.FaPiao;
+
+import java.io.IOException;
+
+/**
+ * @author skyline
+ * @description 发票中购买方信息序列化处理
+ * https://github.com/baomidou/mybatis-plus-samples/blob/master/mybatis-plus-sample-typehandler/src/main/java/com/baomidou/mybatisplus/samples/typehandler/WalletListTypeHandler.java
+ */
+public class InvoiceDetailBuyerInformationTypeHandle extends JacksonTypeHandler {
+    public InvoiceDetailBuyerInformationTypeHandle(Class<?> type) {
+        super(type);
+    }
+
+    @Override
+    protected Object parse(String json) {
+        try {
+            return getObjectMapper().readValue(json, new TypeReference<FaPiao.BuyerInformation>() {
+            });
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 30 - 0
entity/src/main/java/com/kym/entity/typehandle/InvoiceDetailItemsTypeHandle.java

@@ -0,0 +1,30 @@
+package com.kym.entity.typehandle;
+
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.kym.entity.wechat.FaPiao;
+import com.kym.entity.wechat.FapiaoApplications;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author skyline
+ * @description 发票中发票行
+ * https://github.com/baomidou/mybatis-plus-samples/blob/master/mybatis-plus-sample-typehandler/src/main/java/com/baomidou/mybatisplus/samples/typehandler/WalletListTypeHandler.java
+ */
+public class InvoiceDetailItemsTypeHandle extends JacksonTypeHandler {
+    public InvoiceDetailItemsTypeHandle(Class<?> type) {
+        super(type);
+    }
+
+    @Override
+    protected Object parse(String json) {
+        try {
+            return getObjectMapper().readValue(json, new TypeReference<List<FapiaoApplications.FapiaoItem>>() {
+            });
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 28 - 0
entity/src/main/java/com/kym/entity/typehandle/InvoiceDetailSellerInformationTypeHandle.java

@@ -0,0 +1,28 @@
+package com.kym.entity.typehandle;
+
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.kym.entity.wechat.InvoiceBaseInfo;
+
+import java.io.IOException;
+
+/**
+ * @author skyline
+ * @description 发票中购买方信息序列化处理
+ * https://github.com/baomidou/mybatis-plus-samples/blob/master/mybatis-plus-sample-typehandler/src/main/java/com/baomidou/mybatisplus/samples/typehandler/WalletListTypeHandler.java
+ */
+public class InvoiceDetailSellerInformationTypeHandle extends JacksonTypeHandler {
+    public InvoiceDetailSellerInformationTypeHandle(Class<?> type) {
+        super(type);
+    }
+
+    @Override
+    protected Object parse(String json) {
+        try {
+            return getObjectMapper().readValue(json, new TypeReference<InvoiceBaseInfo.SellerInfo>() {
+            });
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 2 - 2
entity/src/main/java/com/kym/entity/wechat/FaPiao.java

@@ -52,7 +52,7 @@ public class FaPiao {
      */
      */
     @Data
     @Data
     @Accessors(chain = true)
     @Accessors(chain = true)
-    public static class BuyerInformation{
+    public static class BuyerInformation {
         /**
         /**
          * INDIVIDUAL: 个人
          * INDIVIDUAL: 个人
          * ORGANIZATION: 单位
          * ORGANIZATION: 单位
@@ -105,7 +105,7 @@ public class FaPiao {
 
 
     @Data
     @Data
     @Accessors(chain = true)
     @Accessors(chain = true)
-    public static class FaPiaoInfomation{
+    public static class FaPiaoInfomation {
 
 
         /**
         /**
          * 商户发票单号,唯一标识一张要开具的发票。只能是字母、数字、中划线-、下划线_、竖线|、星号*
          * 商户发票单号,唯一标识一张要开具的发票。只能是字母、数字、中划线-、下划线_、竖线|、星号*

+ 4 - 4
entity/src/main/java/com/kym/entity/wechat/FapiaoApplications.java

@@ -15,7 +15,7 @@ public class FapiaoApplications {
     private List<FapiaoEntity> fapiao_information;
     private List<FapiaoEntity> fapiao_information;
 
 
     @Data
     @Data
-    public class FapiaoEntity {
+    public static class FapiaoEntity {
         private String fapiao_id;
         private String fapiao_id;
         /**
         /**
          * ISSUE_ACCEPTED: 开票申请已受理
          * ISSUE_ACCEPTED: 开票申请已受理
@@ -75,7 +75,7 @@ public class FapiaoApplications {
     }
     }
 
 
     @Data
     @Data
-    public class FapiaoInfo {
+    public static class FapiaoInfo {
         /**
         /**
          * 发票代码
          * 发票代码
          */
          */
@@ -100,7 +100,7 @@ public class FapiaoApplications {
     }
     }
 
 
     @Data
     @Data
-    public class CardInfo {
+    public static class CardInfo {
         private String card_appid;
         private String card_appid;
         private String card_openid;
         private String card_openid;
         private String card_id;
         private String card_id;
@@ -115,7 +115,7 @@ public class FapiaoApplications {
     }
     }
 
 
     @Data
     @Data
-    public class FapiaoItem {
+    public static class FapiaoItem {
         /**
         /**
          * 税局侧规定的货物或应税劳务、服务税收分类编码
          * 税局侧规定的货物或应税劳务、服务税收分类编码
          */
          */

+ 2 - 2
entity/src/main/java/com/kym/entity/wechat/InvoiceBaseInfo.java

@@ -19,7 +19,7 @@ public class InvoiceBaseInfo {
 
 
     @Data
     @Data
     @Accessors(chain = true)
     @Accessors(chain = true)
-    class SellerInfo{
+    public static class SellerInfo{
 
 
         /**
         /**
          * 销售方名称
          * 销售方名称
@@ -55,7 +55,7 @@ public class InvoiceBaseInfo {
 
 
     @Data
     @Data
     @Accessors(chain = true)
     @Accessors(chain = true)
-    class ExtraInfo{
+    public static class ExtraInfo{
 
 
         /**
         /**
          * 收款人
          * 收款人

+ 17 - 0
mapper/src/main/java/com/kym/mapper/admin/InvoiceDetailMapper.java

@@ -0,0 +1,17 @@
+package com.kym.mapper.admin;
+
+import com.github.yulichang.base.MPJBaseMapper;
+import com.kym.entity.admin.InvoiceDetail;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 发票详情表 Mapper 接口
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-03-19
+ */
+public interface InvoiceDetailMapper extends MPJBaseMapper<InvoiceDetail> {
+
+}

+ 31 - 0
mapper/src/main/resources/mappers/admin/InvoiceDetailMapper.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.kym.mapper.admin.InvoiceDetailMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.kym.entity.admin.InvoiceDetail">
+        <result column="id" property="id" />
+        <result column="apply_id" property="applyId" />
+        <result column="fapiao_time" property="fapiaoTime" />
+        <result column="status" property="status" />
+        <result column="blue_fapiao" property="blueFapiao" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
+        <result column="red_fapiao" property="redFapiao" />
+        <result column="card_information" property="cardInformation" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
+        <result column="total_amount" property="totalAmount" />
+        <result column="tax_amount" property="taxAmount" />
+        <result column="amount" property="amount" />
+        <result column="seller_information" property="sellerInformation" typeHandler="com.kym.entity.typehandle.InvoiceDetailSellerInformationTypeHandle" />
+        <result column="buyer_information" property="buyerInformation" typeHandler="com.kym.entity.typehandle.InvoiceDetailBuyerInformationTypeHandle" />
+        <result column="extra_information" property="extraInformation" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
+        <result column="items" property="items" typeHandler="com.kym.entity.typehandle.InvoiceDetailItemsTypeHandle" />
+        <result column="remark" property="remark" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id,apply_id, fapiao_time, status, blue_fapiao, red_fapiao, card_information, total_amount, tax_amount, amount, seller_information, buyer_information, extra_information, items, remark,create_time,update_time
+    </sql>
+
+</mapper>

+ 41 - 16
miniapp/src/main/java/com/kym/miniapp/jobs/StartChargeDelayJob.java

@@ -1,7 +1,8 @@
 package com.kym.miniapp.jobs;
 package com.kym.miniapp.jobs;
 
 
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.annotation.DS;
-import com.google.common.util.concurrent.RateLimiter;
+import com.kym.common.constant.ResponseEnum;
+import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.CommUtil;
 import com.kym.entity.miniapp.ChargeOrder;
 import com.kym.entity.miniapp.ChargeOrder;
 import com.kym.entity.miniapp.delay.DelayChargeOrder;
 import com.kym.entity.miniapp.delay.DelayChargeOrder;
@@ -14,8 +15,10 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.context.annotation.Scope;
 import org.springframework.context.annotation.Scope;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.context.event.EventListener;
 import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Component;
 
 
+import java.util.ArrayList;
 import java.util.concurrent.DelayQueue;
 import java.util.concurrent.DelayQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Executors;
@@ -31,8 +34,6 @@ import java.util.concurrent.Executors;
 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // 设置成单例
 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // 设置成单例
 public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
 public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
 
 
-    private final static RateLimiter rateLimiter = RateLimiter.create(4);
-
     /**
     /**
      * 预约订单队列
      * 预约订单队列
      */
      */
@@ -44,6 +45,11 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
      */
      */
     private final ExecutorService executor = Executors.newFixedThreadPool(1);
     private final ExecutorService executor = Executors.newFixedThreadPool(1);
 
 
+    /**
+     * 重试列表
+     */
+    private ArrayList<String> retryList = new ArrayList<>();
+
     public StartChargeDelayJob(ChargeOrderService chargeOrderService, ChargeService chargeService) {
     public StartChargeDelayJob(ChargeOrderService chargeOrderService, ChargeService chargeService) {
         this.chargeOrderService = chargeOrderService;
         this.chargeOrderService = chargeOrderService;
         this.chargeService = chargeService;
         this.chargeService = chargeService;
@@ -51,8 +57,9 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
 
 
     @DS("db-miniapp")
     @DS("db-miniapp")
     // 这里不能使用@PostConstruct,在初始化完成后, bean 进入增强阶段, 所以这个阶段的任何AOP都是无效的,https://www.cnblogs.com/eternityz/p/15330069.html
     // 这里不能使用@PostConstruct,在初始化完成后, bean 进入增强阶段, 所以这个阶段的任何AOP都是无效的,https://www.cnblogs.com/eternityz/p/15330069.html
-    @EventListener
-    public void init(ContextRefreshedEvent event) {
+    @EventListener(classes = {ContextRefreshedEvent.class}, id = "StartChargeDelayJob")
+    @Async
+    public void init() {
         // 队列加载所有充电状态为预约中的订单,按照开始时间排序
         // 队列加载所有充电状态为预约中的订单,按照开始时间排序
         var orderList = chargeOrderService.lambdaQuery()
         var orderList = chargeOrderService.lambdaQuery()
                 .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
                 .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
@@ -69,7 +76,6 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
                 .toList();
                 .toList();
         var delayList = delayChargeOrderList.stream().map(delay -> new DelayedItem<>(delay, delay.getStartTime())).toList();
         var delayList = delayChargeOrderList.stream().map(delay -> new DelayedItem<>(delay, delay.getStartTime())).toList();
         START_DELAY_QUEUE.addAll(delayList);
         START_DELAY_QUEUE.addAll(delayList);
-
         // 开启线程处理队列消息
         // 开启线程处理队列消息
         processDelayedOrders();
         processDelayedOrders();
     }
     }
@@ -78,15 +84,13 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
         while (true) {
         while (true) {
             executor.execute(() -> {
             executor.execute(() -> {
                 ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> null); // 初始化为空值,避免使用new ThreadLocal()
                 ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> null); // 初始化为空值,避免使用new ThreadLocal()
-                log.info("预约充电订单处理线程:{}", Thread.currentThread().getName());
-                DelayedItem<DelayChargeOrder> delayedItem;
-
+                log.info("预约启动充电处理线程:{}", Thread.currentThread().getName());
+                DelayedItem<DelayChargeOrder> delayedItem = null;
                 try {
                 try {
                     delayedItem = START_DELAY_QUEUE.take();
                     delayedItem = START_DELAY_QUEUE.take();
                     log.info("出队预约充电订单:{},队列剩余:{}", delayedItem.data.getStartChargeSeq(), START_DELAY_QUEUE.size());
                     log.info("出队预约充电订单:{},队列剩余:{}", delayedItem.data.getStartChargeSeq(), START_DELAY_QUEUE.size());
                     // 启动充电
                     // 启动充电
                     var order = delayedItem.data;
                     var order = delayedItem.data;
-                    threadLocal.set(order.getStartChargeSeq());
                     chargeService.queryStartCharge(order.getUserId(), order.getConnectorId(), null, false, null, null);
                     chargeService.queryStartCharge(order.getUserId(), order.getConnectorId(), null, false, null, null);
                     log.info("预约充电启动成功:用户:{},订单号:{},预约启动时间:{}", order.getUserId(), order.getStartChargeSeq(), order.getStartTime());
                     log.info("预约充电启动成功:用户:{},订单号:{},预约启动时间:{}", order.getUserId(), order.getStartChargeSeq(), order.getStartTime());
                     // 线程休眠250ms
                     // 线程休眠250ms
@@ -96,14 +100,27 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
                         log.error("预约充电队列take异常", e);
                         log.error("预约充电队列take异常", e);
                     } else {
                     } else {
                         log.info("预约启动充电失败,订单号:{}", threadLocal.get());
                         log.info("预约启动充电失败,订单号:{}", threadLocal.get());
+                        if (e instanceof BusinessException && (ResponseEnum.EN_PLUS_TOKEN_EXCEPTION.getCode().equals(((BusinessException) e).getCode()))) {
+                            if (retryList.contains(threadLocal.get())) {
+                                log.info("EN+ token异常,预约订单:{}已重试忽略", threadLocal.get());
+                                log.error(e.getMessage());
+                                // 启动失败将订单状态修改为充电状态已结束,订单状态已确认,结束原因:预约启动失败
+                                updateOrderStatus(threadLocal.get(), ChargeOrder.CHARGE_STATUS_已结束, ChargeOrder.ORDER_STATUS_失败, ChargeOrder.STOP_REASON_预约启动充电失败);
+                                retryList.remove(threadLocal.get());
+                                return;
+                            }
+                            log.info("EN+ token异常,预约订单:{},重试", threadLocal.get());
+                            // token异常就重新放入队列重试
+                            var success = addToDelayQueue(delayedItem);
+                            if (success) {
+                                retryList.add(threadLocal.get());
+                            }
+                            // 跳出本次循环
+                            return;
+                        }
                         log.error(e.getMessage());
                         log.error(e.getMessage());
                         // 启动失败将订单状态修改为充电状态已结束,订单状态已确认,结束原因:预约启动失败
                         // 启动失败将订单状态修改为充电状态已结束,订单状态已确认,结束原因:预约启动失败
-                        chargeOrderService.lambdaUpdate()
-                                .eq(ChargeOrder::getStartChargeSeq, threadLocal.get())
-                                .set(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_已结束)
-                                .set(ChargeOrder::getOrderStatus, ChargeOrder.ORDER_STATUS_失败)
-                                .set(ChargeOrder::getStopReason, ChargeOrder.STOP_REASON_预约启动充电失败)
-                                .update();
+                        updateOrderStatus(threadLocal.get(), ChargeOrder.CHARGE_STATUS_已结束, ChargeOrder.ORDER_STATUS_失败, ChargeOrder.STOP_REASON_预约启动充电失败);
                     }
                     }
                 } finally {
                 } finally {
                     threadLocal.remove();
                     threadLocal.remove();
@@ -120,6 +137,14 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
         }
         }
     }
     }
 
 
+    private void updateOrderStatus(String startChargeSeq, int chargeStatus, int orderStatus, int stopReason) {
+        chargeOrderService.lambdaUpdate()
+                .eq(ChargeOrder::getStartChargeSeq, startChargeSeq)
+                .set(ChargeOrder::getChargeStatus, chargeStatus)
+                .set(ChargeOrder::getOrderStatus, orderStatus)
+                .set(ChargeOrder::getStopReason, stopReason)
+                .update();
+    }
 
 
     @Override
     @Override
     public boolean addToDelayQueue(DelayedItem<DelayChargeOrder> delayedItem) {
     public boolean addToDelayQueue(DelayedItem<DelayChargeOrder> delayedItem) {

+ 9 - 6
miniapp/src/main/java/com/kym/miniapp/jobs/StopChargeDelayJob.java

@@ -14,6 +14,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.context.annotation.Scope;
 import org.springframework.context.annotation.Scope;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.context.event.EventListener;
 import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Component;
 
 
 import java.util.concurrent.DelayQueue;
 import java.util.concurrent.DelayQueue;
@@ -50,12 +51,14 @@ public class StopChargeDelayJob implements DelayService<DelayChargeOrder> {
 
 
     @DS("db-miniapp")
     @DS("db-miniapp")
     // 这里不能使用@PostConstruct,在初始化完成后, bean 进入增强阶段, 所以这个阶段的任何AOP都是无效的,https://www.cnblogs.com/eternityz/p/15330069.html
     // 这里不能使用@PostConstruct,在初始化完成后, bean 进入增强阶段, 所以这个阶段的任何AOP都是无效的,https://www.cnblogs.com/eternityz/p/15330069.html
-    @EventListener
-    public void init(ContextRefreshedEvent event) {
-        // 队列加载所有充电状态为预约中且有结束时间的订单,按照开始时间排序
+    @EventListener(classes = {ContextRefreshedEvent.class}, id = "StopChargeDelayJob")
+    @Async
+    public void init() {
+
+        // 队列加载所有充电状态为预约中且有结束时间的订单,按照结束时间排序
         var orderList = chargeOrderService.lambdaQuery()
         var orderList = chargeOrderService.lambdaQuery()
-                .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
-                .eq(ChargeOrder::getIsBooking, ChargeOrder.IS_BOOKING_是)
+                .eq(ChargeOrder::getOrderStatus, ChargeOrder.ORDER_STATUS_未知)
+                .in(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中, ChargeOrder.CHARGE_STATUS_启动中, ChargeOrder.CHARGE_STATUS_充电中)
                 .isNotNull(ChargeOrder::getEndTime)
                 .isNotNull(ChargeOrder::getEndTime)
                 .orderByAsc(ChargeOrder::getEndTime)
                 .orderByAsc(ChargeOrder::getEndTime)
                 .list();
                 .list();
@@ -78,7 +81,7 @@ public class StopChargeDelayJob implements DelayService<DelayChargeOrder> {
         while (true) {
         while (true) {
             executor.execute(() -> {
             executor.execute(() -> {
                 ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> null); // 初始化为空值,避免使用new ThreadLocal()
                 ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> null); // 初始化为空值,避免使用new ThreadLocal()
-                log.info("预约充电订单处理线程:{}", Thread.currentThread().getName());
+                log.info("预约停止充电处理线程:{}", Thread.currentThread().getName());
                 DelayedItem<DelayChargeOrder> delayedItem;
                 DelayedItem<DelayChargeOrder> delayedItem;
 
 
                 try {
                 try {

+ 1 - 1
miniapp/src/main/resources/application-dev.yml

@@ -94,7 +94,7 @@ spring:
     redis:
     redis:
       port: 6380
       port: 6380
       host: 121.40.98.15
       host: 121.40.98.15
-      password: 123456
+      password: kuaiyuman
       database: 10
       database: 10
       lettuce:
       lettuce:
         pool:
         pool:

+ 3 - 0
service/src/main/java/com/kym/service/admin/ConnectorInfoService.java

@@ -6,6 +6,8 @@ import com.kym.entity.admin.queryParams.EquipmentQueryParam;
 import com.kym.entity.admin.vo.ConnectorInfoVo;
 import com.kym.entity.admin.vo.ConnectorInfoVo;
 import com.kym.entity.common.PageBean;
 import com.kym.entity.common.PageBean;
 
 
+import java.util.Map;
+
 /**
 /**
  * <p>
  * <p>
  * 充电桩接口(枪)信息 服务类
  * 充电桩接口(枪)信息 服务类
@@ -17,4 +19,5 @@ import com.kym.entity.common.PageBean;
 public interface ConnectorInfoService extends MPJBaseService<ConnectorInfo> {
 public interface ConnectorInfoService extends MPJBaseService<ConnectorInfo> {
     PageBean<ConnectorInfoVo> listConnectors(EquipmentQueryParam params);
     PageBean<ConnectorInfoVo> listConnectors(EquipmentQueryParam params);
 
 
+    Map<Integer, Long> statConnectorStatus(String stationId);
 }
 }

+ 0 - 1
service/src/main/java/com/kym/service/admin/EquipmentInfoService.java

@@ -15,5 +15,4 @@ import java.util.Map;
  */
  */
 public interface EquipmentInfoService extends MPJBaseService<EquipmentInfo> {
 public interface EquipmentInfoService extends MPJBaseService<EquipmentInfo> {
 
 
-    Map<Integer, Long> statEquipmentStatus(String stationId);
 }
 }

+ 23 - 0
service/src/main/java/com/kym/service/admin/InvoiceDetailService.java

@@ -0,0 +1,23 @@
+package com.kym.service.admin;
+
+import cn.hutool.poi.excel.ExcelWriter;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.kym.entity.admin.InvoiceDetail;
+import com.kym.entity.admin.queryParams.InvoiceDetailQueryParam;
+import com.kym.entity.common.PageBean;
+
+/**
+ * <p>
+ * 发票详情表 服务类
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-03-19
+ */
+public interface InvoiceDetailService extends IService<InvoiceDetail> {
+
+    PageBean<InvoiceDetail> listInvoiceDetail(InvoiceDetailQueryParam params);
+
+
+    ExcelWriter exportInvoiceDetail(InvoiceDetailQueryParam params);
+}

+ 4 - 0
service/src/main/java/com/kym/service/admin/impl/AdminUserServiceImpl.java

@@ -12,6 +12,7 @@ import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.kym.common.R;
 import com.kym.common.R;
 import com.kym.common.constant.ResponseEnum;
 import com.kym.common.constant.ResponseEnum;
+import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.IDGenerator;
 import com.kym.common.utils.IDGenerator;
 import com.kym.entity.admin.AdminUser;
 import com.kym.entity.admin.AdminUser;
@@ -65,6 +66,9 @@ public class AdminUserServiceImpl extends MPJBaseServiceImpl<AdminUserMapper, Ad
     @Override
     @Override
     public R<?> login(String mobilePhone, String password) {
     public R<?> login(String mobilePhone, String password) {
         var user = lambdaQuery().eq(AdminUser::getMobilePhone, mobilePhone).one();
         var user = lambdaQuery().eq(AdminUser::getMobilePhone, mobilePhone).one();
+        if(user.getStatus() == AdminUser.STATUS_禁用){
+            throw new BusinessException("用户状态异常,无法登录");
+        }
         RSA rsa = new RSA(privateKey, publicKey);
         RSA rsa = new RSA(privateKey, publicKey);
         password = rsa.decryptStr(password, KeyType.PrivateKey, StandardCharsets.UTF_8);
         password = rsa.decryptStr(password, KeyType.PrivateKey, StandardCharsets.UTF_8);
         var pwd = MD5.digestHex(password.concat(MD5.digestHex(mobilePhone).substring(0, 5)));
         var pwd = MD5.digestHex(password.concat(MD5.digestHex(mobilePhone).substring(0, 5)));

+ 41 - 0
service/src/main/java/com/kym/service/admin/impl/ConnectorInfoServiceImpl.java

@@ -2,20 +2,27 @@ package com.kym.service.admin.impl;
 
 
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.github.yulichang.base.MPJBaseServiceImpl;
+import com.kym.common.utils.CommUtil;
 import com.kym.entity.admin.ConnectorInfo;
 import com.kym.entity.admin.ConnectorInfo;
+import com.kym.entity.admin.EquipmentInfo;
 import com.kym.entity.admin.queryParams.EquipmentQueryParam;
 import com.kym.entity.admin.queryParams.EquipmentQueryParam;
 import com.kym.entity.admin.vo.ConnectorInfoVo;
 import com.kym.entity.admin.vo.ConnectorInfoVo;
 import com.kym.entity.common.PageBean;
 import com.kym.entity.common.PageBean;
+import com.kym.entity.miniapp.ChargeOrder;
 import com.kym.mapper.admin.ConnectorInfoMapper;
 import com.kym.mapper.admin.ConnectorInfoMapper;
 import com.kym.service.admin.ConnectorInfoService;
 import com.kym.service.admin.ConnectorInfoService;
 import com.kym.service.cache.KymCache;
 import com.kym.service.cache.KymCache;
+import com.kym.service.miniapp.ChargeOrderService;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Comparator;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 
 import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
 import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
 
 
@@ -30,6 +37,13 @@ import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
 @Service
 @Service
 @DS("db-admin")
 @DS("db-admin")
 public class ConnectorInfoServiceImpl extends MPJBaseServiceImpl<ConnectorInfoMapper, ConnectorInfo> implements ConnectorInfoService {
 public class ConnectorInfoServiceImpl extends MPJBaseServiceImpl<ConnectorInfoMapper, ConnectorInfo> implements ConnectorInfoService {
+
+    private final ChargeOrderService chargeOrderService;
+
+    public ConnectorInfoServiceImpl(ChargeOrderService chargeOrderService) {
+        this.chargeOrderService = chargeOrderService;
+    }
+
     @Override
     @Override
     public PageBean<ConnectorInfoVo> listConnectors(EquipmentQueryParam params) {
     public PageBean<ConnectorInfoVo> listConnectors(EquipmentQueryParam params) {
         // 判断数据权限
         // 判断数据权限
@@ -63,4 +77,31 @@ public class ConnectorInfoServiceImpl extends MPJBaseServiceImpl<ConnectorInfoMa
         page.setList(res);
         page.setList(res);
         return page;
         return page;
     }
     }
+
+    @Override
+    public Map<Integer, Long> statConnectorStatus(String stationId) {
+        if (CommUtil.null2Long(stationId) <= 0) {
+            // 判断数据权限
+            stationId = KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong()) == null ? null : KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong()).get(0);
+        }
+        // 获取指定站点下的充电桩
+        var connectorInfos = lambdaQuery().eq(isNotNull(stationId), ConnectorInfo::getStationId, stationId).list();
+        // 根据状态进行分组
+
+        // 预约中的设备
+        // 手动切换数据源
+        DynamicDataSourceContextHolder.push("db-miniapp");
+        // 查询预约中的订单,按照站点id分组并对connectorId去重计数
+        var orderInfos = chargeOrderService.lambdaQuery()
+                .select(ChargeOrder::getConnectorId)
+                .eq(ChargeOrder::getStationId, stationId)
+                .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
+                .list().stream().filter(CommUtil.distinctByKey(ChargeOrder::getConnectorId)).count();
+
+        DynamicDataSourceContextHolder.poll();
+        var res = connectorInfos.stream().collect(Collectors.groupingBy(ConnectorInfo::getStatus, Collectors.counting()));
+        res.put(EquipmentInfo.SERVICE_STATUS_已连接, res.getOrDefault(EquipmentInfo.SERVICE_STATUS_已连接, 0L) - orderInfos);
+        res.put(EquipmentInfo.SERVICE_STATUS_预约中, orderInfos);
+        return res;
+    }
 }
 }

+ 0 - 24
service/src/main/java/com/kym/service/admin/impl/EquipmentInfoServiceImpl.java

@@ -1,23 +1,17 @@
 package com.kym.service.admin.impl;
 package com.kym.service.admin.impl;
 
 
-import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.github.yulichang.base.MPJBaseServiceImpl;
-import com.kym.common.utils.CommUtil;
 import com.kym.entity.admin.EquipmentInfo;
 import com.kym.entity.admin.EquipmentInfo;
 import com.kym.mapper.admin.EquipmentInfoMapper;
 import com.kym.mapper.admin.EquipmentInfoMapper;
-import com.kym.service.admin.ConnectorInfoService;
 import com.kym.service.admin.EquipmentInfoService;
 import com.kym.service.admin.EquipmentInfoService;
 import com.kym.service.cache.KymCache;
 import com.kym.service.cache.KymCache;
 import jakarta.annotation.PostConstruct;
 import jakarta.annotation.PostConstruct;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
-import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
-import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
-
 /**
 /**
  * <p>
  * <p>
  * 充电桩桩体设备 服务实现类
  * 充电桩桩体设备 服务实现类
@@ -30,13 +24,6 @@ import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
 @DS("db-admin")
 @DS("db-admin")
 public class EquipmentInfoServiceImpl extends MPJBaseServiceImpl<EquipmentInfoMapper, EquipmentInfo> implements EquipmentInfoService {
 public class EquipmentInfoServiceImpl extends MPJBaseServiceImpl<EquipmentInfoMapper, EquipmentInfo> implements EquipmentInfoService {
 
 
-    private final ConnectorInfoService connectorInfoService;
-
-    public EquipmentInfoServiceImpl(ConnectorInfoService connectorInfoService) {
-        this.connectorInfoService = connectorInfoService;
-    }
-
-
     @PostConstruct
     @PostConstruct
     private void init() {
     private void init() {
         // 手动切换数据源
         // 手动切换数据源
@@ -45,15 +32,4 @@ public class EquipmentInfoServiceImpl extends MPJBaseServiceImpl<EquipmentInfoMa
         DynamicDataSourceContextHolder.poll();
         DynamicDataSourceContextHolder.poll();
     }
     }
 
 
-    @Override
-    public Map<Integer, Long> statEquipmentStatus(String stationId) {
-        if (CommUtil.null2Long(stationId) <= 0) {
-            // 判断数据权限
-            stationId = KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong()) == null ? null : KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong()).get(0);
-        }
-        // 获取指定站点下的充电桩
-        var equipmentInfos = lambdaQuery().eq(isNotNull(stationId), EquipmentInfo::getStationId, stationId).list();
-        // 根据状态进行分组
-        return equipmentInfos.stream().collect(Collectors.groupingBy(EquipmentInfo::getServiceStatus, Collectors.counting()));
-    }
 }
 }

+ 1 - 0
service/src/main/java/com/kym/service/admin/impl/InvestorInfoServiceImpl.java

@@ -30,6 +30,7 @@ public class InvestorInfoServiceImpl extends ServiceImpl<InvestorInfoMapper, Inv
         var list = lambdaQuery()
         var list = lambdaQuery()
                 .like(CommUtil.isNotEmptyAndNull(params.getMobilePhone()), InvestorInfo::getTelephone, params.getMobilePhone())
                 .like(CommUtil.isNotEmptyAndNull(params.getMobilePhone()), InvestorInfo::getTelephone, params.getMobilePhone())
                 .like(CommUtil.isNotEmptyAndNull(params.getUsername()), InvestorInfo::getAdminUserName, params.getUsername())
                 .like(CommUtil.isNotEmptyAndNull(params.getUsername()), InvestorInfo::getAdminUserName, params.getUsername())
+                .orderByDesc(InvestorInfo::getUpdateTime)
                 .list();
                 .list();
         return new PageBean<>(list);
         return new PageBean<>(list);
     }
     }

+ 113 - 0
service/src/main/java/com/kym/service/admin/impl/InvoiceDetailServiceImpl.java

@@ -0,0 +1,113 @@
+package com.kym.service.admin.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.ExcelWriter;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.github.pagehelper.PageHelper;
+import com.github.yulichang.base.MPJBaseServiceImpl;
+import com.kym.common.utils.CommUtil;
+import com.kym.entity.admin.InvoiceDetail;
+import com.kym.entity.admin.queryParams.InvoiceDetailQueryParam;
+import com.kym.entity.common.PageBean;
+import com.kym.entity.miniapp.DataDict;
+import com.kym.entity.miniapp.Invoice;
+import com.kym.entity.wechat.FapiaoApplications;
+import com.kym.mapper.admin.InvoiceDetailMapper;
+import com.kym.service.admin.InvoiceDetailService;
+import com.kym.service.miniapp.DataDictService;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 发票详情表 服务实现类
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-03-19
+ */
+@Service
+public class InvoiceDetailServiceImpl extends MPJBaseServiceImpl<InvoiceDetailMapper, InvoiceDetail> implements InvoiceDetailService {
+
+    private final DataDictService dataDictService;
+
+    public InvoiceDetailServiceImpl(DataDictService dataDictService) {
+        this.dataDictService = dataDictService;
+    }
+
+
+    @Override
+    public PageBean<InvoiceDetail> listInvoiceDetail(InvoiceDetailQueryParam params) {
+        PageHelper.startPage(params.getPageNum(), params.getPageSize());
+        var res = lambdaQuery()
+                .eq(CommUtil.isNotEmptyAndNull(params.getApplyId()), InvoiceDetail::getApplyId, params.getApplyId())
+                .orderByDesc(InvoiceDetail::getFapiaoTime)
+                .list();
+        return new PageBean<>(res);
+    }
+
+    @Override
+    public ExcelWriter exportInvoiceDetail(InvoiceDetailQueryParam params) {
+        var invoiceDetailList = lambdaQuery()
+                .eq(CommUtil.isNotEmptyAndNull(params.getApplyId()), InvoiceDetail::getApplyId, params.getApplyId())
+                .orderByDesc(InvoiceDetail::getFapiaoTime)
+                .list();
+        ExcelWriter writer = ExcelUtil.getWriter();
+
+        // excel 行信息
+        List<Map<String, Object>> rows = new ArrayList<>();
+
+        // 单张发票若发票行有多行,则每项单独一行
+        invoiceDetailList.forEach(invoiceDetail -> {
+            var items = JSON.parseArray(JSON.toJSONString(invoiceDetail.getItems()), FapiaoApplications.FapiaoItem.class);
+            items.forEach(item -> {
+                var row = new HashMap<String, Object>();
+                // 发票基本信息
+                row.put("blue_fapiao.fapiao_time", invoiceDetail.getFapiaoTime());
+                row.put("blue_fapiao.fapiao_code", invoiceDetail.getBlueFapiao().getFapiao_code());
+                row.put("blue_fapiao.fapiao_number", invoiceDetail.getBlueFapiao().getFapiao_number());
+                row.put("buyer_information.type", Invoice.TYPE_企业.equals(invoiceDetail.getBuyerInformation().getType()) ? "企业" : "个人");
+                row.put("buyer_information.name", invoiceDetail.getBuyerInformation().getName());
+                row.put("buyer_information.taxpayer_id", invoiceDetail.getBuyerInformation().getTaxpayer_id());
+                row.put("fapiao_information.status", "否"); // invoiceDetail.getStatus()
+
+                row.putAll(Map.of(
+                        "goods_name", item.getGoods_name(),
+//             "specification",item.getSpecification(),
+//             "unit",item.getUnit(),
+                        "quantity", item.getQuantity() / 100000000,
+                        "amount", NumberUtil.decimalFormat("#.##", item.getAmount() / 100f),
+                        "tax_amount", NumberUtil.decimalFormat("#.##", item.getTax_amount() / 100f),
+                        "tax_rate", NumberUtil.decimalFormat("#.##", item.getTax_rate() / 10000f)
+                ));
+
+                rows.add(row);
+            });
+        });
+
+        writer.addHeaderAlias("blue_fapiao.fapiao_code", "发票代码");
+        writer.addHeaderAlias("blue_fapiao.fapiao_number", "发票号码");
+        writer.addHeaderAlias("blue_fapiao.fapiao_time", "开票日期");
+        writer.addHeaderAlias("goods_name", "货物、应税劳务及服务");
+        writer.addHeaderAlias("specification", "规格型号");
+        writer.addHeaderAlias("quantity", "数量");
+        writer.addHeaderAlias("unit", "单位");
+        writer.addHeaderAlias("amount", "金额");
+        writer.addHeaderAlias("tax_rate", "税率");
+        writer.addHeaderAlias("tax_amount", "税额");
+        writer.addHeaderAlias("fapiao_information.status", "是否作废");
+        writer.addHeaderAlias("buyer_information.type", "购买方类型");
+        writer.addHeaderAlias("buyer_information.name", "购方单位名称");
+        writer.addHeaderAlias("buyer_information.taxpayer_id", "购方识别号");
+
+        writer.write(rows, true);
+
+        return writer;
+
+    }
+}

+ 22 - 8
service/src/main/java/com/kym/service/admin/impl/StatementsServiceImpl.java

@@ -1,7 +1,6 @@
 package com.kym.service.admin.impl;
 package com.kym.service.admin.impl;
 
 
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.annotation.DS;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.github.yulichang.toolkit.JoinWrappers;
 import com.github.yulichang.toolkit.JoinWrappers;
@@ -13,7 +12,10 @@ import com.kym.entity.admin.queryParams.StatementsQueryParam;
 import com.kym.entity.admin.vo.StatementsVo;
 import com.kym.entity.admin.vo.StatementsVo;
 import com.kym.entity.common.PageBean;
 import com.kym.entity.common.PageBean;
 import com.kym.mapper.admin.StatementsMapper;
 import com.kym.mapper.admin.StatementsMapper;
-import com.kym.service.admin.*;
+import com.kym.service.admin.AdminUserRoleService;
+import com.kym.service.admin.InvestorInfoService;
+import com.kym.service.admin.StatementsService;
+import com.kym.service.admin.StationStatMonthService;
 import com.kym.service.cache.KymCache;
 import com.kym.service.cache.KymCache;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -99,6 +101,13 @@ public class StatementsServiceImpl extends MPJBaseServiceImpl<StatementsMapper,
                     .setVatAmount((int) (splittingAmount / (1 + investorInfo.getVatRate()) * investorInfo.getVatRate() * 1.12)) // 增值税额(分)
                     .setVatAmount((int) (splittingAmount / (1 + investorInfo.getVatRate()) * investorInfo.getVatRate() * 1.12)) // 增值税额(分)
                     .setActualSplittingAmount(splittingAmount - (int) (splittingAmount / (1 + investorInfo.getVatRate()) * investorInfo.getVatRate() * 1.12)); // 实际分成金额(分)
                     .setActualSplittingAmount(splittingAmount - (int) (splittingAmount / (1 + investorInfo.getVatRate()) * investorInfo.getVatRate() * 1.12)); // 实际分成金额(分)
         }).toList();
         }).toList();
+        // 删除之前生成的对账单(投资者/物业 - 站点 - 月份)
+        var oldData = lambdaQuery()
+                .eq(Statements::getStatMonth, statMonthInfo.getStatMonth())
+                .eq(Statements::getStationId, statMonthInfo.getStationId())
+                .in(Statements::getAdminUserId, investorInfoList.stream().map(InvestorInfo::getAdminUserId).toList())
+                .list().stream().map(Statements::getId).toList();
+        removeBatchByIds(oldData);
         saveBatch(res);
         saveBatch(res);
     }
     }
 
 
@@ -109,7 +118,7 @@ public class StatementsServiceImpl extends MPJBaseServiceImpl<StatementsMapper,
                 .eq(!CommUtil.isEmptyOrNull(params.getStationId()), Statements::getStationId, params.getStationId())
                 .eq(!CommUtil.isEmptyOrNull(params.getStationId()), Statements::getStationId, params.getStationId())
                 .eq(!CommUtil.isEmptyOrNull(params.getStatMonth()), Statements::getStatMonth, params.getStatMonth())
                 .eq(!CommUtil.isEmptyOrNull(params.getStatMonth()), Statements::getStatMonth, params.getStatMonth())
                 .like(!CommUtil.isEmptyOrNull(params.getAdminUserName()), Statements::getAdminUserName, params.getAdminUserName())
                 .like(!CommUtil.isEmptyOrNull(params.getAdminUserName()), Statements::getAdminUserName, params.getAdminUserName())
-                .orderByDesc(Statements::getStatMonth)
+                .orderByDesc(Statements::getCreateTime)
                 .list();
                 .list();
         return new PageBean<>(res);
         return new PageBean<>(res);
     }
     }
@@ -117,15 +126,20 @@ public class StatementsServiceImpl extends MPJBaseServiceImpl<StatementsMapper,
     @Override
     @Override
     public StatementsVo preview(String statId) {
     public StatementsVo preview(String statId) {
         var statements = getById(statId);
         var statements = getById(statId);
-        var investorInfo = investorInfoService.lambdaQuery().eq(InvestorInfo::getAdminUserId, statements.getAdminUserId()).one();
+        var investorInfo = investorInfoService.lambdaQuery()
+                .eq(InvestorInfo::getAdminUserId, statements.getAdminUserId())
+                .eq(InvestorInfo::getStationId, statements.getStationId())
+                .one();
         // 查询角色
         // 查询角色
         MPJLambdaWrapper<AdminUserRole> wrapper = JoinWrappers.lambda(AdminUserRole.class)
         MPJLambdaWrapper<AdminUserRole> wrapper = JoinWrappers.lambda(AdminUserRole.class)
-                .select(Role::getRoleName,Role::getRoleDesc)
-                .leftJoin(Role.class,Role::getId,AdminUserRole::getRoleId)
-                .eq(AdminUserRole::getAdminUserId,statements.getAdminUserId());
+                .select(Role::getRoleName, Role::getRoleDesc)
+                .leftJoin(Role.class, Role::getId, AdminUserRole::getRoleId)
+                .eq(AdminUserRole::getAdminUserId, statements.getAdminUserId());
         var res = adminUserRoleService.selectJoinMap(wrapper);
         var res = adminUserRoleService.selectJoinMap(wrapper);
 
 
-
+        if (CommUtil.isEmptyOrNull(res)) {
+            throw new BusinessException("请先完善站点相关投资者/物业客户账号信息关联之后重新生成账单");
+        }
 
 
         var statementsVo = new StatementsVo();
         var statementsVo = new StatementsVo();
         statementsVo.setRoleName(res.get("role_name").toString());
         statementsVo.setRoleName(res.get("role_name").toString());

+ 0 - 36
service/src/main/java/com/kym/service/enplus/impl/EnPlusServiceHelper.java

@@ -1,36 +0,0 @@
-package com.kym.service.enplus.impl;
-
-import com.kym.service.enplus.EnPlusService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CacheConfig;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.stereotype.Service;
-
-/**
- * @author skyline
- * @description 这里处理很粗糙,后面再优化吧
- * @date 2023-08-02 15:38
- */
-@Service
-@CacheConfig
-public class EnPlusServiceHelper {
-    /*
-     * 原因:
-     * Spring 缓存注解是基于Spring AOP切面,必须走代理才能生效,同类调用或者子类调用父类带有缓存注解的方法时属于内部调用,没有走代理,所以注解不生效。
-     * 解决方法: 将方法抽离到一个独立类中
-     */
-
-    private EnPlusService enPlusService;
-
-    @Autowired
-    public void setEnPlusService(EnPlusService enPlusService) {
-        this.enPlusService = enPlusService;
-    }
-
-    @Cacheable(cacheNames = "EN_PLUS", key = "#root.methodName")
-    //有个坑,@Cacheable 注解在对象内部调用不会生效,参考:https://blog.csdn.net/zh452647457/article/details/86487423
-    public String queryToken() {
-        return enPlusService.queryToken();
-    }
-
-}

+ 16 - 8
service/src/main/java/com/kym/service/enplus/impl/EnPlusServiceImpl.java

@@ -11,6 +11,7 @@ import com.kym.common.enums.EnPlusApi;
 import com.kym.common.exception.BusinessException;
 import com.kym.common.exception.BusinessException;
 import com.kym.common.exception.EnPushException;
 import com.kym.common.exception.EnPushException;
 import com.kym.common.utils.AESUtil;
 import com.kym.common.utils.AESUtil;
+import com.kym.common.utils.CommUtil;
 import com.kym.entity.common.RedisKeys;
 import com.kym.entity.common.RedisKeys;
 import com.kym.entity.enplus.EnRespQueryToken;
 import com.kym.entity.enplus.EnRespQueryToken;
 import com.kym.entity.enplus.response.EnResponse;
 import com.kym.entity.enplus.response.EnResponse;
@@ -19,7 +20,6 @@ import okhttp3.*;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
@@ -40,7 +40,6 @@ public class EnPlusServiceImpl implements EnPlusService {
     static OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder().build();
     static OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder().build();
     private final RedisTemplate<String, String> redisTemplate;
     private final RedisTemplate<String, String> redisTemplate;
 
 
-    private final EnPlusServiceHelper enPlusServiceHelper;
     @Value("${en-plus.operatorId}")
     @Value("${en-plus.operatorId}")
     private String OperatorId;
     private String OperatorId;
     @Value("${en-plus.operatorSecret}")
     @Value("${en-plus.operatorSecret}")
@@ -48,9 +47,8 @@ public class EnPlusServiceImpl implements EnPlusService {
     @Value("${en-plus.sigSecret}")
     @Value("${en-plus.sigSecret}")
     private String SigSecret;
     private String SigSecret;
 
 
-    public EnPlusServiceImpl(RedisTemplate<String, String> redisTemplate, @Lazy EnPlusServiceHelper enPlusServiceHelper) {
+    public EnPlusServiceImpl(RedisTemplate<String, String> redisTemplate) {
         this.redisTemplate = redisTemplate;
         this.redisTemplate = redisTemplate;
-        this.enPlusServiceHelper = enPlusServiceHelper;
     }
     }
 
 
     public static <T> T parse(String json, Class<T> clz) {
     public static <T> T parse(String json, Class<T> clz) {
@@ -75,7 +73,7 @@ public class EnPlusServiceImpl implements EnPlusService {
     @Override
     @Override
     public EnResponse enPlusPost(String url, String params) {
     public EnResponse enPlusPost(String url, String params) {
         // token获取
         // token获取
-        var token = enPlusServiceHelper.queryToken();
+        var token = queryToken();
         Headers headers = Headers.of("Authorization", "Bearer ".concat(token));
         Headers headers = Headers.of("Authorization", "Bearer ".concat(token));
         RequestBody requestBody = RequestBody.create(params, JSON);
         RequestBody requestBody = RequestBody.create(params, JSON);
         Request request = new Request.Builder()
         Request request = new Request.Builder()
@@ -88,7 +86,12 @@ public class EnPlusServiceImpl implements EnPlusService {
         if (0 == response.getRet()) {
         if (0 == response.getRet()) {
             return response;
             return response;
         } else {
         } else {
-            LOGGER.error("EN+接口数据异常:url:{}\n params:{}\ntoken:{}\n返回信息:{}", url, params, token, response);
+            LOGGER.error(":url:{}\n params:{}\ntoken:{}\n返回信息:{}", url, params, token, response);
+            if(4002 == response.getRet() ){
+                // 如果返回Ret=4002,token错误的情况下,删除redis中的token,如果是预约订单则将此订单设置为延迟启动
+                redisTemplate.delete(RedisKeys.EN_PLUS_TOKEN);
+                throw new BusinessException(ResponseEnum.EN_PLUS_TOKEN_EXCEPTION);
+            }
             throw new BusinessException(ResponseEnum.EN_PLUS_API_EXCEPTION);
             throw new BusinessException(ResponseEnum.EN_PLUS_API_EXCEPTION);
         }
         }
     }
     }
@@ -102,6 +105,11 @@ public class EnPlusServiceImpl implements EnPlusService {
     @Override
     @Override
     public String queryToken() {
     public String queryToken() {
         LOGGER.debug("查询token");
         LOGGER.debug("查询token");
+        var token = redisTemplate.opsForValue().get(RedisKeys.EN_PLUS_TOKEN);
+        if (CommUtil.isNotEmptyAndNull(token)) {
+            LOGGER.debug("从缓存中查询到token:{},ttl:{}", token, redisTemplate.getExpire(RedisKeys.EN_PLUS_TOKEN));
+            return token;
+        }
         var data = """
         var data = """
                 {
                 {
                     "OperatorID":"%s",
                     "OperatorID":"%s",
@@ -117,8 +125,8 @@ public class EnPlusServiceImpl implements EnPlusService {
             // 解密Data获取token
             // 解密Data获取token
             var enRespQueryToken = JSONObject.parseObject(AESUtil.decrypt(enResponse.getData()), EnRespQueryToken.class);
             var enRespQueryToken = JSONObject.parseObject(AESUtil.decrypt(enResponse.getData()), EnRespQueryToken.class);
             LOGGER.debug("EN+接口AccessToken:{}", enRespQueryToken.toString());
             LOGGER.debug("EN+接口AccessToken:{}", enRespQueryToken.toString());
-            // 缓存token,有效期7天,这里有效期减1天,防止临界请求token失效
-            redisTemplate.opsForValue().set(RedisKeys.EN_PLUS_TOKEN, enRespQueryToken.getAccessToken(), enRespQueryToken.getTokenAvailableTime() - 3600 * 24, TimeUnit.SECONDS);
+            // 缓存token,有效期7天(我们这里每次请求en+获取token的有效期并不是从7天开始,有效期是在en+的剩余有效时间)
+            redisTemplate.opsForValue().set(RedisKeys.EN_PLUS_TOKEN, enRespQueryToken.getAccessToken(), enRespQueryToken.getTokenAvailableTime(), TimeUnit.SECONDS);
             return enRespQueryToken.getAccessToken();
             return enRespQueryToken.getAccessToken();
         } else {
         } else {
             // 记录错误码,返回错误信息
             // 记录错误码,返回错误信息

+ 2 - 0
service/src/main/java/com/kym/service/miniapp/InvoiceService.java

@@ -1,5 +1,6 @@
 package com.kym.service.miniapp;
 package com.kym.service.miniapp;
 
 
+import cn.hutool.poi.excel.ExcelWriter;
 import com.github.yulichang.base.MPJBaseService;
 import com.github.yulichang.base.MPJBaseService;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.common.PageBean;
 import com.kym.entity.common.PageBean;
@@ -27,4 +28,5 @@ public interface InvoiceService extends MPJBaseService<Invoice> {
     List<Invoice> listInvoiceForApp(Integer status);
     List<Invoice> listInvoiceForApp(Integer status);
 
 
     void cancelApplyInvoice(String invoiceId);
     void cancelApplyInvoice(String invoiceId);
+
 }
 }

+ 11 - 33
service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

@@ -1,6 +1,8 @@
 package com.kym.service.miniapp.impl;
 package com.kym.service.miniapp.impl;
 
 
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.NumberUtil;
 import cn.hutool.poi.excel.ExcelUtil;
 import cn.hutool.poi.excel.ExcelUtil;
 import cn.hutool.poi.excel.ExcelWriter;
 import cn.hutool.poi.excel.ExcelWriter;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.annotation.DS;
@@ -8,12 +10,14 @@ import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.github.yulichang.toolkit.JoinWrappers;
 import com.github.yulichang.toolkit.JoinWrappers;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.kym.common.IQuery;
 import com.kym.common.exception.BusinessException;
 import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.OrderUtils;
 import com.kym.common.utils.OrderUtils;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.common.PageBean;
 import com.kym.entity.common.PageBean;
 import com.kym.entity.miniapp.ChargeOrder;
 import com.kym.entity.miniapp.ChargeOrder;
+import com.kym.entity.miniapp.DataDict;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.miniapp.User;
 import com.kym.entity.miniapp.User;
 import com.kym.entity.miniapp.queryParams.ApplyInvoiceParams;
 import com.kym.entity.miniapp.queryParams.ApplyInvoiceParams;
@@ -22,6 +26,7 @@ import com.kym.entity.wechat.InvoiceOrderDetail;
 import com.kym.entity.wechat.TitleUrl;
 import com.kym.entity.wechat.TitleUrl;
 import com.kym.mapper.miniapp.InvoiceMapper;
 import com.kym.mapper.miniapp.InvoiceMapper;
 import com.kym.service.miniapp.ChargeOrderService;
 import com.kym.service.miniapp.ChargeOrderService;
+import com.kym.service.miniapp.DataDictService;
 import com.kym.service.miniapp.InvoiceService;
 import com.kym.service.miniapp.InvoiceService;
 import com.kym.service.wechat.WxPayService;
 import com.kym.service.wechat.WxPayService;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -29,8 +34,11 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * <p>
  * <p>
@@ -47,8 +55,10 @@ public class InvoiceServiceImpl extends MPJBaseServiceImpl<InvoiceMapper, Invoic
 
 
     private final ChargeOrderService chargeOrderService;
     private final ChargeOrderService chargeOrderService;
     private final WxPayService wxPayService;
     private final WxPayService wxPayService;
+    private final DataDictService dataDictService;
 
 
-    public InvoiceServiceImpl(ChargeOrderService chargeOrderService, @Lazy WxPayService wxPayService) {
+    public InvoiceServiceImpl(ChargeOrderService chargeOrderService, @Lazy WxPayService wxPayService, @Lazy DataDictService dataDictService) {
+        this.dataDictService = dataDictService;
         this.chargeOrderService = chargeOrderService;
         this.chargeOrderService = chargeOrderService;
         this.wxPayService = wxPayService;
         this.wxPayService = wxPayService;
     }
     }
@@ -146,38 +156,6 @@ public class InvoiceServiceImpl extends MPJBaseServiceImpl<InvoiceMapper, Invoic
         return new PageBean<>(list);
         return new PageBean<>(list);
     }
     }
 
 
-    void invoiceExport(InvoiceQueryParam params){
-        MPJLambdaWrapper<Invoice> wrapper = JoinWrappers.lambda(Invoice.class)
-                .selectAsClass(Invoice.class, InvoiceVo.class)
-                .selectAll(Invoice.class)
-                .select(User::getMobilePhone)
-                .leftJoin(User.class, User::getId, Invoice::getUserId)
-                .like(CommUtil.isNotEmptyAndNull(params.getPhone()), User::getMobilePhone, params.getPhone())
-                .like(CommUtil.isNotEmptyAndNull(params.getInvoiceTitle()), Invoice::getInvoiceTitle, params.getInvoiceTitle())
-                .like(CommUtil.isNotEmptyAndNull(params.getBiller()), Invoice::getBiller, params.getBiller())
-                .like(CommUtil.isNotEmptyAndNull(params.getTaxId()), Invoice::getTaxId, params.getTaxId())
-                .like(CommUtil.isNotEmptyAndNull(params.getInvoiceType()), Invoice::getInvoiceType, params.getInvoiceType())
-                .like(CommUtil.isNotEmptyAndNull(params.getEmail()), Invoice::getEmail, params.getEmail())
-                .eq(params.getStatus() != null, Invoice::getStatus, params.getStatus())
-                .orderByDesc(Invoice::getId);
-        var list = selectJoinList(InvoiceVo.class, wrapper);
-
-        // 通过工具类创建writer,默认创建xls格式
-        ExcelWriter writer = ExcelUtil.getWriter();
-
-        //自定义标题别名
-        writer.addHeaderAlias("", "发票号码");
-        writer.addHeaderAlias("", "开票日期");
-        writer.addHeaderAlias("", "货物、应税劳务及服务");
-        writer.addHeaderAlias("", "金额");
-        writer.addHeaderAlias("", "税率");
-        writer.addHeaderAlias("", "税额");
-        writer.addHeaderAlias("", "购方识别号");
-        writer.addHeaderAlias("", "购方单位名称");
-
-
-    }
-
     @Override
     @Override
     public List<Invoice> listInvoiceForApp(Integer status) {
     public List<Invoice> listInvoiceForApp(Integer status) {
         return lambdaQuery().eq(Invoice::getUserId, StpUtil.getLoginIdAsLong()).eq(status != null, Invoice::getStatus, status).list();
         return lambdaQuery().eq(Invoice::getUserId, StpUtil.getLoginIdAsLong()).eq(status != null, Invoice::getStatus, status).list();

+ 51 - 2
service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -16,10 +16,12 @@ import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.LambadaTools;
 import com.kym.common.utils.LambadaTools;
 import com.kym.common.utils.OrderUtils;
 import com.kym.common.utils.OrderUtils;
+import com.kym.entity.admin.InvoiceDetail;
 import com.kym.entity.miniapp.Account;
 import com.kym.entity.miniapp.Account;
 import com.kym.entity.miniapp.*;
 import com.kym.entity.miniapp.*;
 import com.kym.entity.wechat.*;
 import com.kym.entity.wechat.*;
 import com.kym.service.admin.ActivityService;
 import com.kym.service.admin.ActivityService;
+import com.kym.service.admin.InvoiceDetailService;
 import com.kym.service.enplus.EnPlusService;
 import com.kym.service.enplus.EnPlusService;
 import com.kym.service.miniapp.*;
 import com.kym.service.miniapp.*;
 import com.kym.service.wechat.WxPayService;
 import com.kym.service.wechat.WxPayService;
@@ -107,6 +109,8 @@ public class WxPayServiceImpl implements WxPayService {
 
 
     private final UserRechargeRightsService userRechargeRightsService;
     private final UserRechargeRightsService userRechargeRightsService;
 
 
+    private final InvoiceDetailService invoiceDetailService;
+
 
 
     /**
     /**
      * 微信支付专用,支持自动签名验签解密等
      * 微信支付专用,支持自动签名验签解密等
@@ -117,7 +121,7 @@ public class WxPayServiceImpl implements WxPayService {
     public WxPayServiceImpl(WxPayConfig conf, WxFapiaoConfig fapiaoConfig, WalletDetailService walletDetailService,
     public WxPayServiceImpl(WxPayConfig conf, WxFapiaoConfig fapiaoConfig, WalletDetailService walletDetailService,
                             PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService,
                             PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService,
                             RefundLogService refundLogService, InvoiceService invoiceService, InvoiceTitleService invoiceTitleService,
                             RefundLogService refundLogService, InvoiceService invoiceService, InvoiceTitleService invoiceTitleService,
-                            EnPlusService enPlusService, ActivityService activityService, UserRechargeRightsService userRechargeRightsService) {
+                            EnPlusService enPlusService, ActivityService activityService, UserRechargeRightsService userRechargeRightsService, InvoiceDetailService invoiceDetailService) {
         this.conf = conf;
         this.conf = conf;
         this.fapiaoConfig = fapiaoConfig;
         this.fapiaoConfig = fapiaoConfig;
         this.walletDetailService = walletDetailService;
         this.walletDetailService = walletDetailService;
@@ -130,6 +134,7 @@ public class WxPayServiceImpl implements WxPayService {
         this.enPlusService = enPlusService;
         this.enPlusService = enPlusService;
         this.activityService = activityService;
         this.activityService = activityService;
         this.userRechargeRightsService = userRechargeRightsService;
         this.userRechargeRightsService = userRechargeRightsService;
+        this.invoiceDetailService = invoiceDetailService;
     }
     }
 
 
     /**
     /**
@@ -883,6 +888,31 @@ public class WxPayServiceImpl implements WxPayService {
                 if (!CommUtil.isEmptyOrNull(chargeOrderSeqs)) {
                 if (!CommUtil.isEmptyOrNull(chargeOrderSeqs)) {
                     chargeOrderService.lambdaUpdate().set(ChargeOrder::getInvoiceStatus, ChargeOrder.INVOICE_STATUS_已开票).in(ChargeOrder::getStartChargeSeq, chargeOrderSeqs).update();
                     chargeOrderService.lambdaUpdate().set(ChargeOrder::getInvoiceStatus, ChargeOrder.INVOICE_STATUS_已开票).in(ChargeOrder::getStartChargeSeq, chargeOrderSeqs).update();
                 }
                 }
+                /*
+                 * 销项发票excel数据中需包含:
+                 * 1.开票日期
+                 * 2.购买方名称及税号
+                 * 3.开票明细、金额、税额
+                 * 4.开票优惠明细、金额、税额
+                 * 5.发票状态是否作废或者红冲
+                 */
+                var fapiaoApplications = queryFapiao(invoice.getApplyId());
+                var invoiceDetail = new InvoiceDetail()
+                        .setApplyId(invoice.getApplyId())
+                        .setStatus(InvoiceNotification.FapiaoStatus.ISSUED.name())
+                        .setFapiaoTime(DateUtil.parse(fapiaoApplications.getFapiao_information().get(0).getBlue_fapiao().getFapiao_time(), "yyyy-MM-dd'T'HH:mm:ssXXX").toLocalDateTime())
+                        .setBlueFapiao(fapiaoApplications.getFapiao_information().get(0).getBlue_fapiao())
+                        .setRedFapiao(fapiaoApplications.getFapiao_information().get(0).getRed_fapiao())
+                        .setCardInformation(fapiaoApplications.getFapiao_information().get(0).getCard_information())
+                        .setTotalAmount(fapiaoApplications.getFapiao_information().get(0).getTotal_amount())
+                        .setTaxAmount(fapiaoApplications.getFapiao_information().get(0).getTax_amount())
+                        .setAmount(fapiaoApplications.getFapiao_information().get(0).getAmount())
+                        .setSellerInformation(fapiaoApplications.getFapiao_information().get(0).getSeller_information())
+                        .setBuyerInformation(fapiaoApplications.getFapiao_information().get(0).getBuyer_information())
+                        .setExtraInformation(fapiaoApplications.getFapiao_information().get(0).getExtra_information())
+                        .setItems(fapiaoApplications.getFapiao_information().get(0).getItems());
+                invoiceDetailService.save(invoiceDetail);
+
             } else {
             } else {
                 LOGGER.error("微信开具发票失败:{}", invoiceNotification);
                 LOGGER.error("微信开具发票失败:{}", invoiceNotification);
             }
             }
@@ -962,8 +992,27 @@ public class WxPayServiceImpl implements WxPayService {
         headers.addHeader("Accept", "application/json");
         headers.addHeader("Accept", "application/json");
         headers.addHeader("Content-Type", "application/json");
         headers.addHeader("Content-Type", "application/json");
         var res = wxHttpClient.get(headers, fapiaoConfig.getQueryFapiao().formatted(applyId), FapiaoApplications.class);
         var res = wxHttpClient.get(headers, fapiaoConfig.getQueryFapiao().formatted(applyId), FapiaoApplications.class);
+
+
+        var fapiaoApplications = res.getServiceResponse();
+        var invoiceDetail = new InvoiceDetail()
+                .setApplyId(applyId)
+                .setStatus(InvoiceNotification.FapiaoStatus.ISSUED.name())
+                .setFapiaoTime(DateUtil.parse(fapiaoApplications.getFapiao_information().get(0).getBlue_fapiao().getFapiao_time(), "yyyy-MM-dd'T'HH:mm:ssXXX").toLocalDateTime())
+                .setBlueFapiao(fapiaoApplications.getFapiao_information().get(0).getBlue_fapiao())
+                .setRedFapiao(fapiaoApplications.getFapiao_information().get(0).getRed_fapiao())
+                .setCardInformation(fapiaoApplications.getFapiao_information().get(0).getCard_information())
+                .setTotalAmount(fapiaoApplications.getFapiao_information().get(0).getTotal_amount())
+                .setTaxAmount(fapiaoApplications.getFapiao_information().get(0).getTax_amount())
+                .setAmount(fapiaoApplications.getFapiao_information().get(0).getAmount())
+                .setSellerInformation(fapiaoApplications.getFapiao_information().get(0).getSeller_information())
+                .setBuyerInformation(fapiaoApplications.getFapiao_information().get(0).getBuyer_information())
+                .setExtraInformation(fapiaoApplications.getFapiao_information().get(0).getExtra_information())
+                .setItems(fapiaoApplications.getFapiao_information().get(0).getItems());
+        invoiceDetailService.save(invoiceDetail);
+
+
         return res.getServiceResponse();
         return res.getServiceResponse();
     }
     }
 
 
-
 }
 }