Преглед на файлове

Merge branch 'dev' into dev-station-activity

skyline преди 2 години
родител
ревизия
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
 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 homePieRef = ref();
-const homeBarRef = ref();
 const storesTagsViewRoutes = useTagsViewRoutes();
 const storesThemeConfig = useThemeConfig();
 const {themeConfig} = storeToRefs(storesThemeConfig);
@@ -425,7 +424,7 @@ const initEchartsResize = () => {
 };
 
 const loadCurrentEquipmentStatus = () => {
-  $get(`equipment/statEquipmentStatus`, {stationId: state.currentStationId}).then((res: any) => {
+  $get(`connector/statConnectorStatus`, {stationId: state.currentStationId}).then((res: any) => {
     console.log(res)
     initPieChart(res || {});
   })

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

@@ -6,7 +6,7 @@
     <el-dialog
         :title="state.dialog.title"
         v-model="state.dialog.isShowDialog"
-        width="820px"
+        width="920px"
         draggable
         destroy-on-close
         :close-on-click-modal="false"
@@ -20,182 +20,213 @@
           size="default"
           label-width="125px"
           class="mt5">
-        <el-form-item label="微信发票申请id" prop="applyId">
+<!--        <el-form-item label="微信发票申请id" prop="applyId">
           <el-input
               v-model.trim="state.ruleForm.applyId"
-              placeholder="微信发票申请id"
-              clearable
+              placeholder="微信发票申请"
+              readonly
               class="wd200">
           </el-input>
-        </el-form-item>
-        <el-form-item label="用户id" prop="userId">
+        </el-form-item>-->
+        <el-form-item label="开票日期" prop="fapiaoTime">
           <el-input
-              v-model.trim="state.ruleForm.userId"
-              placeholder="用户id"
-              clearable
+              v-model.trim="state.ruleForm.fapiaoTime"
+              readonly
               class="wd200">
           </el-input>
         </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
-              v-model.trim="state.ruleForm.openid"
-              placeholder="发票抬头填写人的openid"
-              clearable
+              v-model.trim="state.ruleForm.sellerInformation.name"
+              placeholder="开票人"
+              readonly
               class="wd200">
           </el-input>
         </el-form-item>
-        <el-form-item label="发票关联订单详情" prop="orderDetails">
+<!--        <el-form-item label="用户id" prop="userId">
           <el-input
-              v-model.trim="state.ruleForm.orderDetails"
-              placeholder="发票关联订单详情"
-              clearable
+              v-model.trim="state.ruleForm.userId"
+              placeholder="用户id"
+              readonly
               class="wd200">
           </el-input>
-        </el-form-item>
+        </el-form-item>-->
         <el-form-item label="累积充电量(度)" prop="totalPower">
           <el-input
-              v-model.trim="state.ruleForm.totalPower"
+              v-model.trim="state.detail.totalPower"
               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">
           </el-input>
         </el-form-item>
-        <el-form-item label="银行账户" prop="bankAccount">
+        <el-form-item label="累积总金额(元)" prop="totalMoney">
           <el-input
-              v-model.trim="state.ruleForm.bankAccount"
-              placeholder="银行账户"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.ruleForm.totalMoney)"
+              placeholder="累积总金额(元)"
+              readonly
               class="wd200">
           </el-input>
         </el-form-item>
-        <el-form-item label="发票金额(单位:分)" prop="invoiceAmount">
+        <el-form-item label="累积电费(元)" prop="elecMoney">
           <el-input
-              v-model.trim="state.ruleForm.invoiceAmount"
-              placeholder="发票金额(单位:分)"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.detail.elecMoney)"
+              placeholder="累积电费(元)"
+              readonly
               class="wd200">
           </el-input>
         </el-form-item>
-        <el-form-item label="税额详情信息" prop="taxInfo">
+        <el-form-item label="累积服务费(元)" prop="serviceMoney">
           <el-input
-              v-model.trim="state.ruleForm.taxInfo"
-              placeholder="税额详情信息"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.detail.serviceMoney)"
+              placeholder="累积服务费(元)"
+              readonly
               class="wd200">
           </el-input>
         </el-form-item>
-        <el-form-item label="开票人" prop="biller">
+        <el-form-item label="服务费优惠金额(元)" prop="serviceMoneyDiscount">
           <el-input
-              v-model.trim="state.ruleForm.biller"
-              placeholder="开票人"
-              clearable
+              :model-value="u.fmt.fmtMoney(state.detail.serviceMoneyDiscount)"
+              placeholder="服务费优惠金额(元)"
+              readonly
               class="wd200">
           </el-input>
         </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
               v-model.trim="state.ruleForm.remark"
               placeholder="备注"
-              clearable
+              readonly
               class="wd200">
           </el-input>
-        </el-form-item>
+        </el-form-item>-->
       </el-form>
 
       <template #footer>
@@ -213,6 +244,7 @@ import {defineAsyncComponent, reactive, onMounted, ref} from 'vue';
 import {Msg} from "/@/utils/message";
 import {$body, $get} from "/@/utils/request";
 import u from '/@/utils/u'
+import ExtDLabel from "/@/components/form/ExtDLabel.vue";
 
 
 // 定义子组件向父组件传值/事件
@@ -221,7 +253,15 @@ const formRef = ref();
 //定义初始变量,重置使用
 const initState = () => ({
   ruleForm: {
-    id: 0
+    id: 0,
+    buyerInformation:{},
+    blueFapiao:{},
+    cardInformation:{},
+    extraInformationd:{},
+    sellerInformation:{},
+  },
+  detail:{
+    orderDetails:{}
   },
   btnLoading: false,
   dialog: {
@@ -239,11 +279,12 @@ const state = reactive(initState());
 
 // 打开弹窗
 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.detail = row;
   if (action !== 'add') {
-    loadData(row.id);
+    loadData(row.applyId);
   } else {
     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"/>
           查询
         </el-button>
+
+        <el-button class="ml10" plain size="default" type="warning" @click="handleDownloadExcel">
+          <SvgIcon name="ele-Download"/>
+          下载
+        </el-button>
       </el-form>
 
       <el-table
@@ -169,8 +174,9 @@
             </template>
             <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="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 v-else>
               <div>{{ row[field.prop] }}</div>
@@ -190,7 +196,7 @@
 
 <script setup lang="ts" name="InvoiceList">
 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 u from "/@/utils/u"
 
@@ -256,7 +262,7 @@ const state = reactive({
       {label: '创建时间', prop: 'createTime', 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) => {
   $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">
           <ext-input-number v-model="state.ruleForm.actualElecMoney"  class="wd200"></ext-input-number>
@@ -73,62 +73,62 @@
         </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>
 
       <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.totalPower }}</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.actualElecMoney) }}</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.totalMoney - state.ruleForm.discountAmount) }}</td>

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

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

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

@@ -22,6 +22,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
                 // login接口不鉴权
                 .excludePathPatterns(
                         "/wx/*",
+                        "/finance/export/*",
                         "/**/login",
                         "/**/error",
                         "/**/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
+     */
+    @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;
 
-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.RestController;
 
@@ -18,21 +15,4 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/equipment")
 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;
 
+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.annotation.SysLog;
 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.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.RefundLogService;
 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
@@ -18,16 +45,20 @@ import org.springframework.web.bind.annotation.*;
 @RequestMapping("/finance")
 public class FinanceController {
 
+    private final Logger logger = LoggerFactory.getLogger(FinanceController.class);
+
     private final WxPayService wxPayService;
 
     private final RefundLogService refundLogService;
 
     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.refundLogService = refundLogService;
         this.invoiceService = invoiceService;
+        this.invoiceDetailService = invoiceDetailService;
     }
 
     @SysLog("退款申请列表")
@@ -44,7 +75,7 @@ public class FinanceController {
     }
 
     @GetMapping("/getUserTitle/{applyId}")
-    Object getUserTitle(@PathVariable String applyId){
+    Object getUserTitle(@PathVariable String applyId) {
         return wxPayService.userTitle(applyId);
     }
 
@@ -74,12 +105,56 @@ public class FinanceController {
 
     /**
      * 取消申请发票,发票抬头填写页关闭事件调用
+     *
      * @return
      */
     @GetMapping("/cancelApplyInvoice/{invoiceId}")
-    R<?> cancelApplyInvoice(@PathVariable String invoiceId){
+    R<?> cancelApplyInvoice(@PathVariable String invoiceId) {
         invoiceService.cancelApplyInvoice(invoiceId);
         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 com.kym.common.R;
+import com.kym.common.utils.CommUtil;
 import com.kym.entity.admin.InvestorInfo;
 import com.kym.entity.admin.queryParams.CommonQueryParam;
 import com.kym.service.admin.InvestorInfoService;
@@ -36,6 +37,7 @@ public class InvestorInfoController {
     R<?> create(@RequestBody InvestorInfo investorInfo) {
         investorInfo.setId(null);
         investorInfo.setStationName(KymCache.INSTANCE.getStationNameById(investorInfo.getStationId()));
+        CommUtil.asserts(investorInfo.getAdminUserId() != null, "请选择关联客户");
         return R.success(investorInfoService.save(investorInfo));
     }
 
@@ -47,6 +49,7 @@ public class InvestorInfoController {
     @PostMapping("/update")
     R<?> update(@RequestBody InvestorInfo investorInfo) {
         investorInfo.setStationName(KymCache.INSTANCE.getStationNameById(investorInfo.getStationId()));
+        CommUtil.asserts(investorInfo.getAdminUserId() != null, "请选择关联客户");
         return R.success(investorInfoService.updateById(investorInfo));
     }
 

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

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

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

@@ -53,7 +53,8 @@ public enum ResponseEnum implements BusinessExceptionAssert {
     // EN+
     EN_PLUS_API_EXCEPTION(90000, "接口数据异常"),
     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 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
     @Accessors(chain = true)
-    public static class BuyerInformation{
+    public static class BuyerInformation {
         /**
          * INDIVIDUAL: 个人
          * ORGANIZATION: 单位
@@ -105,7 +105,7 @@ public class FaPiao {
 
     @Data
     @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;
 
     @Data
-    public class FapiaoEntity {
+    public static class FapiaoEntity {
         private String fapiao_id;
         /**
          * ISSUE_ACCEPTED: 开票申请已受理
@@ -75,7 +75,7 @@ public class FapiaoApplications {
     }
 
     @Data
-    public class FapiaoInfo {
+    public static class FapiaoInfo {
         /**
          * 发票代码
          */
@@ -100,7 +100,7 @@ public class FapiaoApplications {
     }
 
     @Data
-    public class CardInfo {
+    public static class CardInfo {
         private String card_appid;
         private String card_openid;
         private String card_id;
@@ -115,7 +115,7 @@ public class FapiaoApplications {
     }
 
     @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
     @Accessors(chain = true)
-    class SellerInfo{
+    public static class SellerInfo{
 
         /**
          * 销售方名称
@@ -55,7 +55,7 @@ public class InvoiceBaseInfo {
 
     @Data
     @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;
 
 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.entity.miniapp.ChargeOrder;
 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.event.ContextRefreshedEvent;
 import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
+import java.util.ArrayList;
 import java.util.concurrent.DelayQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -31,8 +34,6 @@ import java.util.concurrent.Executors;
 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // 设置成单例
 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 ArrayList<String> retryList = new ArrayList<>();
+
     public StartChargeDelayJob(ChargeOrderService chargeOrderService, ChargeService chargeService) {
         this.chargeOrderService = chargeOrderService;
         this.chargeService = chargeService;
@@ -51,8 +57,9 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
 
     @DS("db-miniapp")
     // 这里不能使用@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()
                 .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
@@ -69,7 +76,6 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
                 .toList();
         var delayList = delayChargeOrderList.stream().map(delay -> new DelayedItem<>(delay, delay.getStartTime())).toList();
         START_DELAY_QUEUE.addAll(delayList);
-
         // 开启线程处理队列消息
         processDelayedOrders();
     }
@@ -78,15 +84,13 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
         while (true) {
             executor.execute(() -> {
                 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 {
                     delayedItem = START_DELAY_QUEUE.take();
                     log.info("出队预约充电订单:{},队列剩余:{}", delayedItem.data.getStartChargeSeq(), START_DELAY_QUEUE.size());
                     // 启动充电
                     var order = delayedItem.data;
-                    threadLocal.set(order.getStartChargeSeq());
                     chargeService.queryStartCharge(order.getUserId(), order.getConnectorId(), null, false, null, null);
                     log.info("预约充电启动成功:用户:{},订单号:{},预约启动时间:{}", order.getUserId(), order.getStartChargeSeq(), order.getStartTime());
                     // 线程休眠250ms
@@ -96,14 +100,27 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
                         log.error("预约充电队列take异常", e);
                     } else {
                         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());
                         // 启动失败将订单状态修改为充电状态已结束,订单状态已确认,结束原因:预约启动失败
-                        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 {
                     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
     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.event.ContextRefreshedEvent;
 import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 import java.util.concurrent.DelayQueue;
@@ -50,12 +51,14 @@ public class StopChargeDelayJob implements DelayService<DelayChargeOrder> {
 
     @DS("db-miniapp")
     // 这里不能使用@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()
-                .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)
                 .orderByAsc(ChargeOrder::getEndTime)
                 .list();
@@ -78,7 +81,7 @@ public class StopChargeDelayJob implements DelayService<DelayChargeOrder> {
         while (true) {
             executor.execute(() -> {
                 ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> null); // 初始化为空值,避免使用new ThreadLocal()
-                log.info("预约充电订单处理线程:{}", Thread.currentThread().getName());
+                log.info("预约停止充电处理线程:{}", Thread.currentThread().getName());
                 DelayedItem<DelayChargeOrder> delayedItem;
 
                 try {

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

@@ -94,7 +94,7 @@ spring:
     redis:
       port: 6380
       host: 121.40.98.15
-      password: 123456
+      password: kuaiyuman
       database: 10
       lettuce:
         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.common.PageBean;
 
+import java.util.Map;
+
 /**
  * <p>
  * 充电桩接口(枪)信息 服务类
@@ -17,4 +19,5 @@ import com.kym.entity.common.PageBean;
 public interface ConnectorInfoService extends MPJBaseService<ConnectorInfo> {
     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> {
 
-    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.kym.common.R;
 import com.kym.common.constant.ResponseEnum;
+import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.IDGenerator;
 import com.kym.entity.admin.AdminUser;
@@ -65,6 +66,9 @@ public class AdminUserServiceImpl extends MPJBaseServiceImpl<AdminUserMapper, Ad
     @Override
     public R<?> login(String mobilePhone, String password) {
         var user = lambdaQuery().eq(AdminUser::getMobilePhone, mobilePhone).one();
+        if(user.getStatus() == AdminUser.STATUS_禁用){
+            throw new BusinessException("用户状态异常,无法登录");
+        }
         RSA rsa = new RSA(privateKey, publicKey);
         password = rsa.decryptStr(password, KeyType.PrivateKey, StandardCharsets.UTF_8);
         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 com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
+import com.kym.common.utils.CommUtil;
 import com.kym.entity.admin.ConnectorInfo;
+import com.kym.entity.admin.EquipmentInfo;
 import com.kym.entity.admin.queryParams.EquipmentQueryParam;
 import com.kym.entity.admin.vo.ConnectorInfoVo;
 import com.kym.entity.common.PageBean;
+import com.kym.entity.miniapp.ChargeOrder;
 import com.kym.mapper.admin.ConnectorInfoMapper;
 import com.kym.service.admin.ConnectorInfoService;
 import com.kym.service.cache.KymCache;
+import com.kym.service.miniapp.ChargeOrderService;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
 
@@ -30,6 +37,13 @@ import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
 @Service
 @DS("db-admin")
 public class ConnectorInfoServiceImpl extends MPJBaseServiceImpl<ConnectorInfoMapper, ConnectorInfo> implements ConnectorInfoService {
+
+    private final ChargeOrderService chargeOrderService;
+
+    public ConnectorInfoServiceImpl(ChargeOrderService chargeOrderService) {
+        this.chargeOrderService = chargeOrderService;
+    }
+
     @Override
     public PageBean<ConnectorInfoVo> listConnectors(EquipmentQueryParam params) {
         // 判断数据权限
@@ -63,4 +77,31 @@ public class ConnectorInfoServiceImpl extends MPJBaseServiceImpl<ConnectorInfoMa
         page.setList(res);
         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;
 
-import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.github.yulichang.base.MPJBaseServiceImpl;
-import com.kym.common.utils.CommUtil;
 import com.kym.entity.admin.EquipmentInfo;
 import com.kym.mapper.admin.EquipmentInfoMapper;
-import com.kym.service.admin.ConnectorInfoService;
 import com.kym.service.admin.EquipmentInfoService;
 import com.kym.service.cache.KymCache;
 import jakarta.annotation.PostConstruct;
 import org.springframework.stereotype.Service;
 
-import java.util.Map;
 import java.util.stream.Collectors;
 
-import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
-
 /**
  * <p>
  * 充电桩桩体设备 服务实现类
@@ -30,13 +24,6 @@ import static com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotNull;
 @DS("db-admin")
 public class EquipmentInfoServiceImpl extends MPJBaseServiceImpl<EquipmentInfoMapper, EquipmentInfo> implements EquipmentInfoService {
 
-    private final ConnectorInfoService connectorInfoService;
-
-    public EquipmentInfoServiceImpl(ConnectorInfoService connectorInfoService) {
-        this.connectorInfoService = connectorInfoService;
-    }
-
-
     @PostConstruct
     private void init() {
         // 手动切换数据源
@@ -45,15 +32,4 @@ public class EquipmentInfoServiceImpl extends MPJBaseServiceImpl<EquipmentInfoMa
         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()
                 .like(CommUtil.isNotEmptyAndNull(params.getMobilePhone()), InvestorInfo::getTelephone, params.getMobilePhone())
                 .like(CommUtil.isNotEmptyAndNull(params.getUsername()), InvestorInfo::getAdminUserName, params.getUsername())
+                .orderByDesc(InvestorInfo::getUpdateTime)
                 .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;
 
 import com.baomidou.dynamic.datasource.annotation.DS;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 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.common.PageBean;
 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 org.springframework.beans.BeanUtils;
 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)) // 增值税额(分)
                     .setActualSplittingAmount(splittingAmount - (int) (splittingAmount / (1 + investorInfo.getVatRate()) * investorInfo.getVatRate() * 1.12)); // 实际分成金额(分)
         }).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);
     }
 
@@ -109,7 +118,7 @@ public class StatementsServiceImpl extends MPJBaseServiceImpl<StatementsMapper,
                 .eq(!CommUtil.isEmptyOrNull(params.getStationId()), Statements::getStationId, params.getStationId())
                 .eq(!CommUtil.isEmptyOrNull(params.getStatMonth()), Statements::getStatMonth, params.getStatMonth())
                 .like(!CommUtil.isEmptyOrNull(params.getAdminUserName()), Statements::getAdminUserName, params.getAdminUserName())
-                .orderByDesc(Statements::getStatMonth)
+                .orderByDesc(Statements::getCreateTime)
                 .list();
         return new PageBean<>(res);
     }
@@ -117,15 +126,20 @@ public class StatementsServiceImpl extends MPJBaseServiceImpl<StatementsMapper,
     @Override
     public StatementsVo preview(String 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)
-                .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);
 
-
+        if (CommUtil.isEmptyOrNull(res)) {
+            throw new BusinessException("请先完善站点相关投资者/物业客户账号信息关联之后重新生成账单");
+        }
 
         var statementsVo = new StatementsVo();
         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.EnPushException;
 import com.kym.common.utils.AESUtil;
+import com.kym.common.utils.CommUtil;
 import com.kym.entity.common.RedisKeys;
 import com.kym.entity.enplus.EnRespQueryToken;
 import com.kym.entity.enplus.response.EnResponse;
@@ -19,7 +20,6 @@ import okhttp3.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
@@ -40,7 +40,6 @@ public class EnPlusServiceImpl implements EnPlusService {
     static OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder().build();
     private final RedisTemplate<String, String> redisTemplate;
 
-    private final EnPlusServiceHelper enPlusServiceHelper;
     @Value("${en-plus.operatorId}")
     private String OperatorId;
     @Value("${en-plus.operatorSecret}")
@@ -48,9 +47,8 @@ public class EnPlusServiceImpl implements EnPlusService {
     @Value("${en-plus.sigSecret}")
     private String SigSecret;
 
-    public EnPlusServiceImpl(RedisTemplate<String, String> redisTemplate, @Lazy EnPlusServiceHelper enPlusServiceHelper) {
+    public EnPlusServiceImpl(RedisTemplate<String, String> redisTemplate) {
         this.redisTemplate = redisTemplate;
-        this.enPlusServiceHelper = enPlusServiceHelper;
     }
 
     public static <T> T parse(String json, Class<T> clz) {
@@ -75,7 +73,7 @@ public class EnPlusServiceImpl implements EnPlusService {
     @Override
     public EnResponse enPlusPost(String url, String params) {
         // token获取
-        var token = enPlusServiceHelper.queryToken();
+        var token = queryToken();
         Headers headers = Headers.of("Authorization", "Bearer ".concat(token));
         RequestBody requestBody = RequestBody.create(params, JSON);
         Request request = new Request.Builder()
@@ -88,7 +86,12 @@ public class EnPlusServiceImpl implements EnPlusService {
         if (0 == response.getRet()) {
             return response;
         } 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);
         }
     }
@@ -102,6 +105,11 @@ public class EnPlusServiceImpl implements EnPlusService {
     @Override
     public String queryToken() {
         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 = """
                 {
                     "OperatorID":"%s",
@@ -117,8 +125,8 @@ public class EnPlusServiceImpl implements EnPlusService {
             // 解密Data获取token
             var enRespQueryToken = JSONObject.parseObject(AESUtil.decrypt(enResponse.getData()), EnRespQueryToken.class);
             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();
         } else {
             // 记录错误码,返回错误信息

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

@@ -1,5 +1,6 @@
 package com.kym.service.miniapp;
 
+import cn.hutool.poi.excel.ExcelWriter;
 import com.github.yulichang.base.MPJBaseService;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.common.PageBean;
@@ -27,4 +28,5 @@ public interface InvoiceService extends MPJBaseService<Invoice> {
     List<Invoice> listInvoiceForApp(Integer status);
 
     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;
 
 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.ExcelWriter;
 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.toolkit.JoinWrappers;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.kym.common.IQuery;
 import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.OrderUtils;
 import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.common.PageBean;
 import com.kym.entity.miniapp.ChargeOrder;
+import com.kym.entity.miniapp.DataDict;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.miniapp.User;
 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.mapper.miniapp.InvoiceMapper;
 import com.kym.service.miniapp.ChargeOrderService;
+import com.kym.service.miniapp.DataDictService;
 import com.kym.service.miniapp.InvoiceService;
 import com.kym.service.wechat.WxPayService;
 import lombok.extern.slf4j.Slf4j;
@@ -29,8 +34,11 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -47,8 +55,10 @@ public class InvoiceServiceImpl extends MPJBaseServiceImpl<InvoiceMapper, Invoic
 
     private final ChargeOrderService chargeOrderService;
     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.wxPayService = wxPayService;
     }
@@ -146,38 +156,6 @@ public class InvoiceServiceImpl extends MPJBaseServiceImpl<InvoiceMapper, Invoic
         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
     public List<Invoice> listInvoiceForApp(Integer status) {
         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.LambadaTools;
 import com.kym.common.utils.OrderUtils;
+import com.kym.entity.admin.InvoiceDetail;
 import com.kym.entity.miniapp.Account;
 import com.kym.entity.miniapp.*;
 import com.kym.entity.wechat.*;
 import com.kym.service.admin.ActivityService;
+import com.kym.service.admin.InvoiceDetailService;
 import com.kym.service.enplus.EnPlusService;
 import com.kym.service.miniapp.*;
 import com.kym.service.wechat.WxPayService;
@@ -107,6 +109,8 @@ public class WxPayServiceImpl implements WxPayService {
 
     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,
                             PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService,
                             RefundLogService refundLogService, InvoiceService invoiceService, InvoiceTitleService invoiceTitleService,
-                            EnPlusService enPlusService, ActivityService activityService, UserRechargeRightsService userRechargeRightsService) {
+                            EnPlusService enPlusService, ActivityService activityService, UserRechargeRightsService userRechargeRightsService, InvoiceDetailService invoiceDetailService) {
         this.conf = conf;
         this.fapiaoConfig = fapiaoConfig;
         this.walletDetailService = walletDetailService;
@@ -130,6 +134,7 @@ public class WxPayServiceImpl implements WxPayService {
         this.enPlusService = enPlusService;
         this.activityService = activityService;
         this.userRechargeRightsService = userRechargeRightsService;
+        this.invoiceDetailService = invoiceDetailService;
     }
 
     /**
@@ -883,6 +888,31 @@ public class WxPayServiceImpl implements WxPayService {
                 if (!CommUtil.isEmptyOrNull(chargeOrderSeqs)) {
                     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 {
                 LOGGER.error("微信开具发票失败:{}", invoiceNotification);
             }
@@ -962,8 +992,27 @@ public class WxPayServiceImpl implements WxPayService {
         headers.addHeader("Accept", "application/json");
         headers.addHeader("Content-Type", "application/json");
         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();
     }
 
-
 }