vbea 2 лет назад
Родитель
Сommit
550eff39f4

+ 4 - 0
Strides-Admin/src/http/api/charge.js

@@ -42,7 +42,11 @@ const charge = {
   },
   getErrorCodeOption: () => {
     return get('station/getErrorCodeList')
+  },
+  getWebPosQrCode: (connectorPk) => {
+    return get("station/webpos-qr/" + connectorPk)
   }
+  
 }
 
 export default charge;

+ 52 - 1
Strides-Admin/src/http/api/financial.js

@@ -1,4 +1,4 @@
-import {get, post} from '../http'
+import {get, post, put, del} from '../http'
 
 const financial = {
   getTransactionsPages(data) {
@@ -12,6 +12,57 @@ const financial = {
   },
   getPaymentOptions() {
     return get("financial/getPaymentStatus")
+  },
+  getSystemTransactionsPages(data) {
+    return post("system-transaction/credits", data)
+  },
+  getCreditTypeOptions() {
+    return get("system-transaction/credit-types")
+  },
+  getPaymentStatusOptions() {
+    return get("system-transaction/payment-statuses")
+  },
+  getBillingAccountTypes() {
+    return get("financial/billing-account/account-types")
+  },
+  getAdditionalItemTypes() {
+    return get("financial/billing-account/billing-additional-item-types")
+  },
+  getBillingAccountPages(data) {
+    return post("financial/billing-account/billing-account-pages", data)
+  },
+  addBillingAccount(data) {
+    return post("financial/billing-account/billing-accounts", data)
+  },
+  viewBillingAccount(entityId) {
+    return get("financial/billing-account/billing-accounts/" + entityId)
+  },
+  updateBillingAccount(data) {
+    return put("financial/billing-account/billing-accounts", data)
+  },
+  deleteBillingAccount(entityId) {
+    return del("financial/billing-account/billing-accounts/" + entityId)
+  },
+  deleteAdditionalItem(itemId) {
+    return del("financial/billing-account/additional-items/" + itemId)
+  },
+  getAssignStatusOptions() {
+    return get('financial/billing-account/assignment-statuses')
+  },
+  getAssignSitePages(data) {
+    return post('financial/billing-account/assign-site-pages', data)
+  },
+  getAssignGroupPages(data) {
+    return post('financial/billing-account/assign-group-pages', data)
+  },
+  assignEligible(data) {
+    return post('financial/billing-account/assign-eligible', data)
+  },
+  unassignEligible(data) {
+    return post('financial/billing-account/unassign-eligible', data)
+  },
+  getAccountSettlementPages(data) {
+    return post("financial/account-settlement/account-settlement-pages", data)
   }
 }
 

+ 55 - 16
Strides-Admin/src/router/FinancialRouter.js

@@ -11,6 +11,39 @@ export default {
     icon: 'financial'
   },
   children: [
+    {
+      path: '/financial-management/system-transactions',
+      component: () => import('@/views/financial/transactions'),
+      name: 'SystemTransactions',
+      meta: {
+        title: 'System Transactions',
+        icon: 'sidebar-submenu-item',
+        activeIcon: 'sidebar-submenu-item-active',
+        affix: false
+      }
+    },
+    {
+      path: '/financial-management/billing-account-mgmt',
+      component: () => import('@/views/settlement/BillingAccount'),
+      name: 'BillingAccountMgmt',
+      meta: {
+        title: 'Billing Account Management',
+        icon: 'sidebar-submenu-item',
+        activeIcon: 'sidebar-submenu-item-active',
+        affix: false
+      }
+    },
+    {
+      path: '/financial-management/account-settlement',
+      component: () => import('@/views/settlement/AccountSetle'),
+      name: 'AccountSettlement',
+      meta: {
+        title: 'Account Settlement',
+        icon: 'sidebar-submenu-item',
+        activeIcon: 'sidebar-submenu-item-active',
+        affix: false
+      }
+    },
     {
       path: '/financial-management/charging-transactions',
       component: () => import('@/views/financial/index'),
@@ -45,26 +78,32 @@ export default {
       }
     },
     {
-      path: '/financial-management/billing-account-mgmt',
-      component: () => import('@/views/settlement/billingAccount'),
-      name: 'FinancialTopup',
+      path: '/financial-management/billing-account-add',
+      component: () => import('@/views/settlement/DetailAccount'),
+      name: 'BillingAccountAdd',
       meta: {
-        title: 'Billing Account Management',
-        icon: 'sidebar-submenu-item',
-        activeIcon: 'sidebar-submenu-item-active',
-        affix: false
-      }
+        title: 'Create',
+        activeMenu: "/financial-management/billing-account-mgmt",
+        parent: {
+          title: 'Billing Account Management',
+          path: "/financial-management/billing-account-mgmt"
+        }
+      },
+      hidden: true
     },
     {
-      path: '/financial-management/account-settlement',
-      component: () => import('@/views/settlement/index'),
-      name: 'FinancialTopup',
+      path: '/financial-management/billing-account-edit/:id',
+      component: () => import('@/views/settlement/DetailAccount'),
+      name: 'BillingAccountEdit',
       meta: {
-        title: 'Account Settlement',
-        icon: 'sidebar-submenu-item',
-        activeIcon: 'sidebar-submenu-item-active',
-        affix: false
-      }
+        title: 'Details',
+        activeMenu: "/financial-management/billing-account-mgmt",
+        parent: {
+          title: 'Billing Account Management',
+          path: "/financial-management/billing-account-mgmt"
+        }
+      },
+      hidden: true
     }
   ]
 }

+ 1 - 0
Strides-Admin/src/settings.js

@@ -14,6 +14,7 @@ module.exports = {
   defaultCountry: 'SG',
   defaultCalling: '65',
   dashboardVersion: 2,
+  enableWebPos: false,
   /**
    * @type {string}
    * @description API url base service

+ 212 - 192
Strides-Admin/src/views/charge/Connectors.vue

@@ -148,6 +148,11 @@
                 command="showConnectorQR">
                 QRCode
               </el-dropdown-item>
+              <el-dropdown-item
+                command="showWebPosQR"
+                v-if="enableWebPos">
+                WebPosQR
+              </el-dropdown-item>
               <el-dropdown-item
                 command="changeToAvailable"
                 v-if='row.status == "Unavailable"'>
@@ -192,210 +197,225 @@
     <DialogRemoteOcpp
       v-bind="actionDialog"
       @hide="hideActionDialog"/>
+    <WebPosQR 
+      v-bind="webPosDialog"
+      @hide="hideConnectorQR"/>
   </div>
 </template>
 
 <script>
-  import Pagination from '@/components/Pagination' 
-  import TableAction from '@/components/TableAction.vue'
-  import ConnectorTags from './components/ConnectorTags.vue'
-  import DialogRemoteOcpp from './components/DialogRemoteOcpp.vue'
-  import api from '../../http/api/charge'
-  import ocpp from '../../http/api/ocpp'
-  export default {
-    components: { Pagination, TableAction, ConnectorTags, DialogRemoteOcpp },
-    data() {
-      return {
-        filter: {
-          pageNo: 1,
-          pageSize: 10,
-          pageVo: {
-            criteria: '',
-            status: '',
-            errorCode: ''
-          }
-        },
-        listLoading: true,
-        connectorList: [],
-        total: 0,
-        statusOptions: [],
-        errCodeOptions: [],
-        printConnector: {
-          qrCode: "",
-          visible: false
-        },
-        sitePk: "",
-        actionDialog: {
-          visible: false,
-          isStart: true,
-          item: {}
+import Pagination from '@/components/Pagination' 
+import TableAction from '@/components/TableAction.vue'
+import ConnectorTags from './components/ConnectorTags.vue'
+import WebPosQR from './components/WebPosQR.vue'
+import DialogRemoteOcpp from './components/DialogRemoteOcpp.vue'
+import api from '../../http/api/charge'
+import ocpp from '../../http/api/ocpp'
+import settings from '../../settings.js'
+export default {
+  components: { Pagination, TableAction, ConnectorTags, DialogRemoteOcpp,WebPosQR },
+  data() {
+    return {
+      filter: {
+        pageNo: 1,
+        pageSize: 10,
+        pageVo: {
+          criteria: '',
+          status: '',
+          errorCode: ''
         }
-      }
-    },
-    created() {
-      if (this.$route.params.id) {
-        this.filter.sitePk = this.sitePk = this.$route.params.id;
-      }
-      this.getStatusOption();
-      this.getErrorCodeOptions();
-    },
-    methods: {
-      goBack() {
-        this.$router.push({
-          path: "/site-management/edit/" + this.sitePk
-        })
-      },
-      handleFilter() {
-        this.filter.pageNo = 1;
-        this.getConnectorList();
-      },
-      getStatusOption() {
-        api.getStatusList().then(res => {
-          if (res.data.length > 0) {
-            res.data.forEach(item => {
-              this.statusOptions.push({
-                name: item,
-                value: item
-              })
-            })
-          }
-          this.getConnectorList();
-        }).catch(err => {
-          this.$message.error(err)
-        })
-      },
-      getErrorCodeOptions() {
-        api.getErrorCodeOption().then(res => {
-          if (res.data.length > 0) {
-            res.data.forEach(item => {
-              this.errCodeOptions.push({
-                name: item,
-                value: item
-              })
-            })
-          }
-          this.getConnectorList();
-        }).catch(err => {
-          this.$message.error(err)
-        })
-      },
-      getConnectorList() {
-        this.listLoading = true;
-        api.getConnectorList(this.filter).then(res => {
-          this.listLoading = false;
-          this.total = res.total;
-          this.connectorList = res.data;
-        }).catch(err => {
-          this.$message.error(err)
-          this.listLoading = false;
-        })
-      },
-      handleCommand(cb, item) {
-        this[cb](item)
       },
-      showConnectorQR(row) {
-        this.printConnector.visible = true;
-        this.printConnector.qrCode = row.chargeBoxId + "::" + row.connectorId
+      listLoading: true,
+      connectorList: [],
+      total: 0,
+      statusOptions: [],
+      errCodeOptions: [],
+      printConnector: {
+        qrCode: "",
+        visible: false
       },
-      unlockConnector(row) {
-        this.$confirm("Confirm unlock this connector?", "Unlock Connector",  {
-          confirmButtonText: 'Confirm',
-          cancelButtonText: 'Cancel',
-          type: 'warning'
-        }).then(res => {
-          const params = {
-            connectorId: row.connectorId,
-            stationIds: [row.chargeBoxId]
-          }
-          this.sendPerform("ocppOperations/unlockConnector", params);
-        })
-      },
-      changeToAvailable(row) {
-        this.$confirm("Confirm change to available?", "Change Availability",  {
-          confirmButtonText: 'Confirm',
-          cancelButtonText: 'Cancel',
-          type: 'warning'
-        }).then(res => {
-          const params = {
-            availType: "OPERATIVE",
-            connectorId: row.connectorId,
-            stationIds: [row.chargeBoxId]
-          }
-          this.sendPerform("ocppOperations/changeAvailability", params);
-        })
+      sitePk: "",
+      actionDialog: {
+        visible: false,
+        isStart: true,
+        item: {}
       },
-      changeToUnavailable(row) {
-        this.$confirm("Confirm change to unavailable?", "Change Availability",  {
-          confirmButtonText: 'Confirm',
-          cancelButtonText: 'Cancel',
-          type: 'warning'
-        }).then(res => {
-          const params = {
-            availType: "INOPERATIVE",
-            connectorId: row.connectorId,
-            stationIds: [row.chargeBoxId]
-          }
-          this.sendPerform("ocppOperations/changeAvailability", params);
-        })
+      webPosDialog: {
+        visible: false,
+        item: {}
       },
-      sendPerform(api, params) {
-        this.listLoading = true;
-        ocpp.sendPerform(api, params).then(res => {
-          this.listLoading = false;
-          if (res.data.taskId) {
-            this.$openRoute("/ocpp-operations/result/" + res.data.taskId);
-            //this.$router.push({path: '/ocpp-operations/result/' + res.data.taskId});
-          }
-        }).catch(err => {
-          this.listLoading = false;
-          this.$message({
-            type: 'error',
-            message: err
+      enableWebPos: settings.enableWebPos
+    }
+  },
+  created() {
+    if (this.$route.params.id) {
+      this.filter.sitePk = this.sitePk = this.$route.params.id;
+    }
+    this.getStatusOption();
+    this.getErrorCodeOptions();
+  },
+  methods: {
+    goBack() {
+      this.$router.push({
+        path: "/site-management/edit/" + this.sitePk
+      })
+    },
+    handleFilter() {
+      this.filter.pageNo = 1;
+      this.getConnectorList();
+    },
+    getStatusOption() {
+      api.getStatusList().then(res => {
+        if (res.data.length > 0) {
+          res.data.forEach(item => {
+            this.statusOptions.push({
+              name: item,
+              value: item
+            })
           })
-        });
-      },
-      remoteStart(row) {
-        this.actionDialog.visible = true;
-        this.actionDialog.item = row;
-        this.actionDialog.isStart = true;
-        /*this.$confirm("Confirm remote start?", "Remote",  {
-          confirmButtonText: 'Confirm',
-          cancelButtonText: 'Cancel',
-          type: 'warning'
-        }).then(res => {
-          const params = {
-            connectorId: row.connectorId,
-            stationIds: [row.chargeBoxId],
-            //mobileNumber: ""
-          }
-          //this.sendPerform("ocppOperations/remoteStartTransaction", params);
-        })*/
-      },
-      remoteStop(row) {
-        this.actionDialog.visible = true;
-        this.actionDialog.item = row;
-        this.actionDialog.isStart = false;
-        /*this.$confirm("Confirm remote stop?", "Remote",  {
-          confirmButtonText: 'Confirm',
-          cancelButtonText: 'Cancel',
-          type: 'warning'
-        }).then(res => {
-          const params = {
-            availType: "INOPERATIVE",
-            connectorId: row.connectorId,
-            stationIds: [row.chargeBoxId]
-          }
-          //this.sendPerform("ocppOperations/remoteStopTransaction", params);
-        })*/
-      },
-      hideConnectorQR() {
-        this.printConnector.visible = false;
-      },
-      hideActionDialog() {
-        this.actionDialog.visible = false;
-      }
+        }
+        this.getConnectorList();
+      }).catch(err => {
+        this.$message.error(err)
+      })
+    },
+    getErrorCodeOptions() {
+      api.getErrorCodeOption().then(res => {
+        if (res.data.length > 0) {
+          res.data.forEach(item => {
+            this.errCodeOptions.push({
+              name: item,
+              value: item
+            })
+          })
+        }
+        this.getConnectorList();
+      }).catch(err => {
+        this.$message.error(err)
+      })
+    },
+    getConnectorList() {
+      this.listLoading = true;
+      api.getConnectorList(this.filter).then(res => {
+        this.listLoading = false;
+        this.total = res.total;
+        this.connectorList = res.data;
+      }).catch(err => {
+        this.$message.error(err)
+        this.listLoading = false;
+      })
+    },
+    handleCommand(cb, item) {
+      this[cb](item)
+    },
+    showConnectorQR(row) {
+      this.printConnector.visible = true;
+      this.printConnector.qrCode = row.chargeBoxId + "::" + row.connectorId
+    },
+    showWebPosQR(row) {
+      this.webPosDialog.item = row;
+      this.webPosDialog.visible = true;
+    },
+    unlockConnector(row) {
+      this.$confirm("Confirm unlock this connector?", "Unlock Connector",  {
+        confirmButtonText: 'Confirm',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      }).then(res => {
+        const params = {
+          connectorId: row.connectorId,
+          stationIds: [row.chargeBoxId]
+        }
+        this.sendPerform("ocppOperations/unlockConnector", params);
+      })
+    },
+    changeToAvailable(row) {
+      this.$confirm("Confirm change to available?", "Change Availability",  {
+        confirmButtonText: 'Confirm',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      }).then(res => {
+        const params = {
+          availType: "OPERATIVE",
+          connectorId: row.connectorId,
+          stationIds: [row.chargeBoxId]
+        }
+        this.sendPerform("ocppOperations/changeAvailability", params);
+      })
+    },
+    changeToUnavailable(row) {
+      this.$confirm("Confirm change to unavailable?", "Change Availability",  {
+        confirmButtonText: 'Confirm',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      }).then(res => {
+        const params = {
+          availType: "INOPERATIVE",
+          connectorId: row.connectorId,
+          stationIds: [row.chargeBoxId]
+        }
+        this.sendPerform("ocppOperations/changeAvailability", params);
+      })
+    },
+    sendPerform(api, params) {
+      this.listLoading = true;
+      ocpp.sendPerform(api, params).then(res => {
+        this.listLoading = false;
+        if (res.data.taskId) {
+          this.$openRoute("/ocpp-operations/result/" + res.data.taskId);
+          //this.$router.push({path: '/ocpp-operations/result/' + res.data.taskId});
+        }
+      }).catch(err => {
+        this.listLoading = false;
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      });
+    },
+    remoteStart(row) {
+      this.actionDialog.visible = true;
+      this.actionDialog.item = row;
+      this.actionDialog.isStart = true;
+      /*this.$confirm("Confirm remote start?", "Remote",  {
+        confirmButtonText: 'Confirm',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      }).then(res => {
+        const params = {
+          connectorId: row.connectorId,
+          stationIds: [row.chargeBoxId],
+          //mobileNumber: ""
+        }
+        //this.sendPerform("ocppOperations/remoteStartTransaction", params);
+      })*/
+    },
+    remoteStop(row) {
+      this.actionDialog.visible = true;
+      this.actionDialog.item = row;
+      this.actionDialog.isStart = false;
+      /*this.$confirm("Confirm remote stop?", "Remote",  {
+        confirmButtonText: 'Confirm',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      }).then(res => {
+        const params = {
+          availType: "INOPERATIVE",
+          connectorId: row.connectorId,
+          stationIds: [row.chargeBoxId]
+        }
+        //this.sendPerform("ocppOperations/remoteStopTransaction", params);
+      })*/
+    },
+    hideConnectorQR() {
+      this.printConnector.visible = false;
+      this.webPosDialog.visible = false;
+    },
+    hideActionDialog() {
+      this.actionDialog.visible = false;
     }
   }
+}
 </script>
 
 <style lang="scss">

+ 112 - 0
Strides-Admin/src/views/charge/components/WebPosQR.vue

@@ -0,0 +1,112 @@
+<template>
+  <el-dialog
+    title="WebPOS QR"
+    :visible="visible"
+    width="320px"
+    top="100px"
+    :before-close="dialogBeforeClose">
+    <div class="qrcode" v-loading="loading" id="webPosQr">
+      <el-image class="qr-image" :src="qrcode"/>
+    </div>
+    <div class="footer">
+      <el-button @click="onPrint" type="primary">Print</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import api from '@/http/api/charge'
+export default {
+  name: "WebPosQR",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    item: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      id: "",
+      qrcode: "",
+      loading: false
+    };
+  },
+  mounted() {
+    
+  },
+  watch: {
+    visible: {
+      handler(value) {
+        if (value) {
+          this.$nextTick(() => {
+            if (this.id !== this.item.connectorPk) {
+              this.loading = true;
+              this.getQRCode()
+            }
+          })
+        }
+      },
+    }
+  },
+  methods: {
+    dialogBeforeClose() {
+      this.$emit("hide")
+    },
+    getQRCode() {
+      if (this.item.connectorPk) {
+        this.id = this.item.connectorPk;
+        api.getWebPosQrCode(this.item.connectorPk).then(res => {
+          if (res.data && res.data.base64WebposQr) {
+            this.qrcode = res.data.base64WebposQr;
+          }
+        }).catch(err => {
+          this.$message.error(err)
+        }).finally(() => {
+          this.loading = false;
+        })
+      }
+    },
+    onPrint() {
+      const contentHtmlString = document.querySelector('#webPosQr').outerHTML;
+      let iframe = document.getElementById('print-iframe');
+      if (!iframe) {
+        iframe = document.createElement('IFRAME')
+        iframe.setAttribute('id', 'print-iframe')
+        iframe.setAttribute(
+          'style',
+          'position:absolute;width:0px;height:0px;left:-500px;top:-500px;'
+        )
+        document.body.appendChild(iframe)
+      }
+      const doc = iframe.contentWindow.document
+      doc.write(contentHtmlString)
+      doc.close()
+      iframe.contentWindow.focus()
+      iframe.contentWindow.print()
+      if (navigator.userAgent.indexOf('MSIE') > 0) {
+        document.body.removeChild(iframe)
+      }
+      this.dialogBeforeClose()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.qrcode {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.footer {
+  text-align: center;
+  padding: 30px 0 0px;
+}
+.qr-image {
+  
+}
+</style>

+ 92 - 107
Strides-Admin/src/views/feedback/Detail.vue

@@ -1,100 +1,89 @@
 <template>
   <div class="card-container">
-    <div class="card-content" v-loading="loading">
-      <el-form
-        label-position="left"
-        label-width="140px"
-        style="width: 100%;">
-        <el-row :gutter="20">
-          <el-col :xs="24" :md="12">
-            <div class="section-title">Feedback</div>
-            <el-row :gutter="20">
-              <el-col :xs="24">
-                <el-form-item
-                  label="Type of Feedback:">
-                  <el-input
-                    class="add-text"
-                    v-model="feedbackInfo.typeOfFeedback"
-                    readonly/>
-                </el-form-item>
-              </el-col>
-              <el-col :xs="24">
-                <el-form-item
-                  label="Feedback:">
-                  <el-input
-                    class="add-text"
-                    :autosize="autosize"
-                    v-model="feedbackInfo.feedback"
-                    type="textarea"
-                    readonly/>
-                </el-form-item>
-              </el-col>
-              <el-col :xs="24">
-                <el-form-item
-                  label="Feedback Status:">
-                  <el-input
-                    class="add-text"
-                    :value='feedbackInfo.readStatus ? "Read" : "Unread"'
-                    readonly/>
-                </el-form-item>
-              </el-col>
-              <el-col :xs="24">
-                <el-form-item
-                  label="Feedback By:">
-                  <el-input
-                    class="add-text"
-                    v-model="feedbackInfo.nickName"
-                    readonly/>
-                </el-form-item>
-              </el-col>
-              <el-col :xs="24">
-                <el-form-item
-                  label="Feedback Time:">
-                  <el-input
-                    class="add-text"
-                    v-model="feedbackInfo.feedbackTime"
-                    readonly/>
-                </el-form-item>
-              </el-col>
-              <el-col :xs="24" v-if="feedbackInfo.chargeBoxId">
-                <el-form-item
-                  label="Charging Station:">
-                  <el-input
-                    class="add-text"
-                    v-model="feedbackInfo.chargeBoxId"
-                    readonly/>
-                </el-form-item>
-              </el-col>
-              <el-col :xs="24" v-if="feedbackInfo.connectorId">
-                <el-form-item
-                  label="Connector:">
-                  <el-input
-                    class="add-text"
-                    v-model="feedbackInfo.connectorId"
-                    readonly/>
-                </el-form-item>
-              </el-col>
-            </el-row>
-          </el-col>
-          <el-col :xs="24" :md="12">
-            <div class="section-title">Photos</div>
-            <div class="feedback-photo">
-              <img
-                class="feedback-img"
-                :src="baseURL + feedbackInfo.feedbackImgOne"
-                v-if="feedbackInfo.feedbackImgOne"/>
-              <img
-                class="feedback-img"
-                :src="baseURL + feedbackInfo.feedbackImgTwo"
-                v-if="feedbackInfo.feedbackImgTwo"/>
-              <img
-                class="feedback-img"
-                :src="baseURL + feedbackInfo.feedbackImgThree"
-                v-if="feedbackInfo.feedbackImgThree"/>
-            </div>
-          </el-col>
-        </el-row>
-        <div class="hr-full"/>
+    <el-form
+      label-position="right"
+      label-width="140px"
+      style="width: 100%;"
+      v-loading="loading">
+      <div class="flexr">
+        <div class="flex1 card-content">
+          <div class="section-title">Feedback</div>
+          <el-form-item
+            label="Type of Feedback:">
+            <el-input
+              class="add-text"
+              v-model="feedbackInfo.typeOfFeedback"
+              readonly/>
+          </el-form-item>
+          <el-form-item
+            label="Feedback:">
+            <el-input
+              class="add-text"
+              :autosize="autosize"
+              v-model="feedbackInfo.feedback"
+              type="textarea"
+              readonly/>
+          </el-form-item>
+          <el-form-item
+            label="Feedback Status:">
+            <el-input
+              class="add-text"
+              :value='feedbackInfo.readStatus ? "Read" : "Unread"'
+              readonly/>
+          </el-form-item>
+          <el-form-item
+            label="Feedback By:">
+            <el-input
+              class="add-text"
+              v-model="feedbackInfo.nickName"
+              readonly/>
+          </el-form-item>
+          <el-form-item
+            label="Feedback Time:">
+            <el-input
+              class="add-text"
+              v-model="feedbackInfo.feedbackTime"
+              readonly/>
+          </el-form-item>
+          <el-form-item
+            label="Charging Station:"
+            v-if="feedbackInfo.chargeBoxId">
+            <el-input
+              class="add-text"
+              v-model="feedbackInfo.chargeBoxId"
+              readonly/>
+          </el-form-item>
+          <el-form-item
+            label="Connector:"
+            v-if="feedbackInfo.connectorId">
+            <el-input
+              class="add-text"
+              v-model="feedbackInfo.connectorId"
+              readonly/>
+          </el-form-item>
+        </div>
+        <div class="flex1 card-content">
+          <div class="section-title">Photos</div>
+          <div class="feedback-photo">
+            <el-image
+              class="feedback-img"
+              :src="baseURL + feedbackInfo.feedbackImgOne"
+              :previewSrcList="[baseURL + feedbackInfo.feedbackImgOne]"
+              v-if="feedbackInfo.feedbackImgOne"/>
+            <el-image
+              class="feedback-img"
+              :src="baseURL + feedbackInfo.feedbackImgTwo"
+              :previewSrcList="[baseURL + feedbackInfo.feedbackImgTwo]"
+              v-if="feedbackInfo.feedbackImgTwo"/>
+            <el-image
+              class="feedback-img"
+              :src="baseURL + feedbackInfo.feedbackImgThree"
+              :previewSrcList="[baseURL + feedbackInfo.feedbackImgThree]"
+              v-if="feedbackInfo.feedbackImgThree"/>
+          </div>
+        </div>
+      </div>
+      <div class="card-content flexcr">
         <div class="buttons">
           <el-button
             type="primary"
@@ -103,8 +92,8 @@
             Back
           </el-button>
         </div>
-      </el-form>
-    </div>
+      </div>
+    </el-form>
   </div>
 </template>
 
@@ -159,12 +148,6 @@
 <style scoped="scoped" lang='scss'>
   @import '../../styles/variables.scss';
 
-  .card-container {
-    width: 100%;
-    padding: 20px 60px;
-    min-height: $mainAppMinHeight;
-    background-color: #F0F5FC;
-  }
   .card-container {
     width: 100%;
     padding: 20px 60px;
@@ -172,6 +155,7 @@
     background-color: #F0F5FC;
   }
   .card-content {
+    margin: 0 8px 16px;
     padding: 15px 80px;
     border-radius: 6px;
     background-color: white;
@@ -180,16 +164,17 @@
     color: #333;
     margin-top: 20px;
     margin-bottom: 30px;
-    font-size: 16px;
+    font-size: 15px;
     user-select: none;
     line-height: 24px;
-    font-weight: 500;
+    font-weight: bold;
     font-family: sans-serif;
     text-transform: uppercase;
   }
   .add-text {
     width: 100%;
-    max-width: 400px;
+    min-width: 100px;
+    max-width: 300px;
   }
   .add-text ::v-deep .el-textarea__inner {
     font-family: sans-serif;
@@ -221,7 +206,7 @@
   }
   .buttons {
     padding-top: 15px;
-    padding-bottom: 30px;
+    padding-bottom: 15px;
   }
   .feedback-photo {
     display: flex;

+ 187 - 0
Strides-Admin/src/views/financial/transactions.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container filter-view">
+      <el-date-picker
+        v-model="filter.pageVo.dateRange"
+        type="daterange"
+        value-format="yyyy-MM-dd"
+        start-placeholder="Start Date"
+        end-placeholder="End Date"
+        clearable
+        class="filter-view-item"
+        @change="toSearch"/>
+      <el-select
+        class="filter-view-item"
+        v-model="filter.pageVo.creditType"
+        placeholder="Credit Type"
+        @change="toSearch"
+        clearable>
+        <el-option
+          v-for="item in options.creditType"
+          :key="item"
+          :label="item"
+          :value="item" />
+      </el-select>
+      <el-select
+        class="filter-view-item"
+        v-model="filter.pageVo.paymentStatus"
+        placeholder="Status"
+        @change="toSearch"
+        clearable>
+        <el-option
+          v-for="item in options.paymentStatus"
+          :key="item"
+          :label="item"
+          :value="item" />
+      </el-select>
+    </div>
+    <el-table
+      :data="table.list"
+      v-loading="loading">
+      <el-table-column
+        label="Credit ID"
+        prop="creditId"
+        align="center"
+        min-width="120"/>
+      <el-table-column
+        label="Credit Type"
+        prop="creditType"
+        align="center"
+        min-width="150"/>
+      <el-table-column
+        label="Amount"
+        align="center"
+        min-width="120">
+        <template slot-scope="{row}">
+          <div>{{row.preAmount}}</div>
+          <div class="text-post" v-if="row.creditType == 'PAY-PER-USE'">{{row.postAmount}}</div>
+          <div class="text-refund" v-if="row.creditType == 'PAY-PER-USE'">{{row.refundAmount}}</div>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="DateTime"
+        prop="dateTime"
+        align="center"
+        min-width="120"/>
+      <el-table-column
+        label="E-mail"
+        prop="email"
+        align="center"
+        min-width="120"/>
+      <el-table-column
+        label="Module"
+        prop="module"
+        align="center"
+        min-width="120"/>
+      <el-table-column
+        label="Reference"
+        prop="reference"
+        align="center"
+        min-width="120"/>
+      <el-table-column
+        label="Status"
+        prop="status"
+        align="center"
+        min-width="120"/>
+    </el-table>
+    <div class="right">
+      <Pagination
+        v-show="table.total > 0"
+        :total="table.total"
+        :page.sync="filter.pageNo"
+        :limit.sync="filter.pageSize"
+        @pagination="getTableData" />
+    </div>
+  </div>
+</template>
+
+<script>
+import financial from '@/http/api/financial';
+import Pagination from '@/components/Pagination';
+export default {
+  data() {
+    return {
+      loading: false,
+      filter: {
+        pageNo: 1,
+        pageSize: 10,
+        pageVo: {
+          criteria: "",
+          dateRange: [],
+          creditType: "",
+          paymentStatus: ""
+        }
+      },
+      options: {
+        creditType: [],
+        paymentStatus: []
+      },
+      table: {
+        list: [],
+        total: 0
+      }
+    };
+  },
+  components: { Pagination },
+  created() {
+    this.getCreditTypeOption();
+    this.getPaymentStatusOption();
+    this.toSearch();
+  },
+  methods: {
+    toSearch() {
+      this.filter.pageNo = 1;
+      this.getTableData();
+    },
+    getPaymentStatusOption() {
+      financial.getPaymentStatusOptions().then(res => {
+        if (res.data) {
+          this.options.paymentStatus = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: 'error'
+        })
+      })
+    },
+    getCreditTypeOption() {
+      financial.getCreditTypeOptions().then(res => {
+        if (res.data) {
+          this.options.creditType = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: 'error'
+        })
+      })
+    },
+    getTableData() {
+      this.loading = true;
+      financial.getSystemTransactionsPages(this.filter).then(res => {
+        this.loading = false;
+        if (res.data) {
+          this.table.total = res.total
+          this.table.list = res.data
+        }
+      }).catch(err => {
+        this.loading = false;
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      })
+    },
+  }
+}
+</script>
+
+<style scoped>
+.text-post {
+  color: #009E81;
+}
+.text-refund {
+  color: #F8A300;
+}
+</style>

+ 4 - 4
Strides-Admin/src/views/rate-dynamic/detail.vue

@@ -487,13 +487,13 @@ export default {
     align-items: center;
     padding: 0 10px 10px;
     .link-type + .link-type {
-      margin-left: 5px;
-      &::before {
+      margin-left: 10px;
+      &::after {
+        left: -7px;
         color: #333;
         content: "|";
-        font-size: 15px;
         font-weight: normal;
-        padding-right: 5px;
+        position: absolute;
       }
     }
   }

+ 47 - 24
Strides-Admin/src/views/settlement/index.vue → Strides-Admin/src/views/settlement/AccountSetle.vue

@@ -7,12 +7,12 @@
           placeholder="Account Type"
           clearable
           @change="onClickSearch"
-          v-model="filters.pageVo.accountTypeId">
+          v-model="filters.pageVo.accountType">
           <el-option
             v-for="(item,index) in options.types"
             :key="index"
-            :label="item.name"
-            :value="item.value"/>
+            :label="item"
+            :value="item"/>
         </el-select>
         <el-input
           v-model="filters.pageVo.criteria"
@@ -30,53 +30,64 @@
       <el-table-column
         align="center"
         label="Entity Name"
-        prop="articleTitle"
-        min-width="150"/>
+        prop="entityName"
+        min-width="160"/>
       <el-table-column
         align="center"
         label="Account Type"
-        prop="articleTitle"
-        min-width="150"/>
+        prop="accountType"
+        min-width="120"/>
       <el-table-column
         align="center"
         label="POC Name"
-        prop="articleTitle"
+        prop="pocName"
         min-width="150"/>
       <el-table-column
         align="center"
         label="POC Email"
-        prop="articleTitle"
+        prop="pocEmail"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Generated Date"
-        prop="articleTitle"
+        prop="generatedDate"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Update Date Time"
-        prop="articleTitle"
+        prop="updateDateTime"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Amount"
-        prop="articleTitle"
-        min-width="150"/>
+        prop="amount"
+        min-width="140"/>
       <el-table-column
         align="center"
         label="Status"
-        prop="articleTitle"
-        min-width="120"/>
+        prop="status"
+        min-width="100"/>
       <el-table-column
         align="center"
         label="Action"
         min-width="120"
         v-if="!$route.meta.onlyView">
         <template v-slot="{ row }">
-          <TableAction
-            @edit="onClickEdit(row)"
+          <a
+            class="link-detail"
+            :href="row.fileUri"
+            target="_blank"
+            v-if="row.fileUri">
+            <i class="el-icon-download"></i>
+            Download
+          </a>
+          <!-- <TableAction
+            :showDel="false"
+            :showIcon="false"
+            editText="Download"
+            @edit="onClickDownload(row)"
             @delete="onClickDelete(row)"
-            v-if="row.dataStatus != 'Inactive'"/>
+            v-if="row.status != 'Inactive'"/> -->
         </template>
       </el-table-column>
     </el-table>
@@ -92,6 +103,7 @@
 </template>
 
 <script>
+import financial from '@/http/api/financial';
 import TableAction from '@/components/TableAction.vue'
 import Pagination from '@/components/Pagination'
 export default {
@@ -102,8 +114,7 @@ export default {
         pageSize: 10,
         pageVo: {
           criteria: "",
-          dataStatus: "",
-          accountTypeId: ""
+          accountType: ""
         }
       },
       table: {
@@ -112,23 +123,35 @@ export default {
         loading: false
       },
       options: {
-        status: [],
         types: []
       }
     };
   },
   components: { Pagination, TableAction },
   created() {
-    //this.onClickSearch();
+    this.getAccountTypeOptions();
+    this.onClickSearch();
   },
   methods: {
     onClickSearch() {
       this.filters.pageNo = 1
       this.getTableData()
     },
+    getAccountTypeOptions() {
+      financial.getBillingAccountTypes().then(res => {
+        if (res.data) {
+          this.options.types = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: 'error'
+        })
+      })
+    },
     getTableData() {
       this.table.loading = true;
-      api.getSettlementPages(this.filters).then(res => {
+      financial.getAccountSettlementPages(this.filters).then(res => {
         if (res.data && res.total) {
           this.table.total = res.total;
           this.table.list = res.data;
@@ -146,7 +169,7 @@ export default {
       }).finally(() => {
         this.table.loading = false;
       })
-    },
+    }
   }
 }
 </script>

+ 582 - 0
Strides-Admin/src/views/settlement/DetailAccount.vue

@@ -0,0 +1,582 @@
+<template>
+  <div class="card-container">
+    <el-form
+      ref="detailForm"
+      :model="form"
+      label-position="right"
+      label-width="180px"
+      style="width: 100%;"
+      v-loading="loading"
+      :rules="rules">
+      <div class="flexr">
+        <div class="flex1 card-content">
+          <div class="section-title">Account details</div>
+          <el-form-item
+            label="Entity Name:">
+            <el-input
+              class="add-text"
+              v-model="form.entityName"
+              maxlength="100"/>
+          </el-form-item>
+          <el-form-item
+            label="Entity Business Reg No.:">
+            <el-input
+              class="add-text"
+              v-model="form.entityBusinessRegNo"
+              maxlength="50"/>
+          </el-form-item>
+          <el-form-item
+            label="Country:">
+            <el-select
+              v-model="form.entityCountryCode"
+              class="add-text">
+              <el-option
+                v-for="item in options.country"
+                :key="item.name"
+                :label="item.name"
+                :value="item.value" />
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Account Type:">
+            <el-select
+              v-model="form.entityType"
+              class="add-text">
+              <el-option
+                v-for="item in options.accountType"
+                :key="item"
+                :label="item"
+                :value="item" />
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Entity POC Name:">
+            <el-input
+              class="add-text"
+              v-model="form.entityContactName"
+              maxlength="50"/>
+          </el-form-item>
+          <el-form-item
+            label="POC Email:">
+            <el-input
+              class="add-text"
+              v-model="form.entityContactEmail"
+              maxlength="80"/>
+          </el-form-item>
+          <el-form-item
+            label="POC Contact No.:">
+            <div class="add-text flexc">
+              <el-select
+                style="min-width: 75px; max-width: 80px;"
+                v-model="form.entityCallingCode">
+                <el-option
+                  v-for="item in options.callingCode"
+                  :key="item.callingCode"
+                  :label="'+' + item.callingCode"
+                  :value="item.callingCode"
+                />
+              </el-select>
+              <el-input
+                v-model="form.entityContactNumber"
+                style="flex: 1; margin-left: 10px;"
+                placeholder=""
+                maxlength="20"/>
+            </div>
+          </el-form-item>
+          <el-form-item
+            label="Effective Date:">
+            <el-date-picker
+              v-model="form.entitySigningDate"
+              class="add-text"
+              format="yyyy-MM-dd"
+              value-format="yyyy-MM-dd"/>
+          </el-form-item>
+          <el-form-item
+            label="Billing Date:">
+            <div class="add-text flex">
+              <el-select
+                style="width: 65px;"
+                v-model="form.entitySettlementDate"
+                placeholder="D/M">
+                <el-option
+                  v-for="item in options.dates"
+                  :key="item"
+                  :label="item"
+                  :value="item"/>
+              </el-select>
+              <div class="flex1 flexc" style="padding-left: 10px;">
+                <span style="font-size: 14px; font-weight: bold;">Of every subsequent month</span>
+              </div>
+            </div>
+          </el-form-item>
+        </div>
+        <div class="flex1 card-content">
+          <div class="section-title">Billing calculation details ({{currencyData[form.entityCountryCode]}})</div>
+          <el-table
+            :data="form.tabulations"
+            class="tabu-table no-border"
+            header-row-class-name="customer-row"
+            row-class-name="customer-row">
+            <el-table-column
+              label="Tabulation Model"
+              min-width="180">
+              <template slot-scope="{row}">
+                <div class="row-text">{{row.tabulationModel}} Fee Model</div>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="Rates"
+              min-width="260">
+              <template slot-scope="{row}">
+                <div class="flexc" v-if="row.tabulationModel == 'Revenue'">
+                  <el-input
+                    v-model="row.tabulationPercent"
+                    class="input-text"
+                    placeholder=""
+                    maxlength="5"/>
+                  <span class="row-text">% Per Total Charges</span>
+                </div>
+                <div class="flexc" v-if="row.tabulationModel == 'Concession'">
+                  <el-input
+                    v-model="row.tabulationFixed"
+                    class="input-text"
+                    placeholder=""
+                    maxlength="10"/>
+                  <span class="row-text">{{currencyData[form.entityCountryCode]}} Per kWh</span>
+                </div>
+                <div class="flexc" v-if="row.tabulationModel == 'Commission'">
+                  <el-input
+                    v-model="row.tabulationPercent"
+                    class="input-text"
+                    placeholder=""
+                    maxlength="5"/>
+                  <span class="row-text">% Per Total Charges</span>
+                </div>
+                <div class="flexc" v-if="row.tabulationModel == 'Commission'" style="margin-top: 5px;">
+                  <el-input
+                    v-model="row.tabulationFixed"
+                    class="input-text"
+                    placeholder=""
+                    maxlength="10"/>
+                  <span class="row-text">{{currencyData[form.entityCountryCode]}} Per Transaction</span>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="Enable"
+              width="80">
+              <template slot-scope="{row, $index}">
+                <el-switch
+                  v-model="row.enable"
+                  :disabled="isModelEnable($index)"/>
+              </template>
+            </el-table-column>
+          </el-table>
+          <el-table
+            :data="form.items"
+            class="items-table no-border"
+            header-row-class-name="customer-row"
+            row-class-name="customer-row">
+            <el-table-column
+              label="Item Name:"
+              min-width="160">
+              <template slot-scope="{row}">
+                <el-input
+                  v-model="row.itemName"
+                  style="width: 100%;"
+                  maxlength="50"/>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="Price:"
+              min-width="100">
+              <template slot-scope="{row}">
+                <el-input
+                  v-model="row.itemPrice"
+                  style="width: 100%;"
+                  maxlength="10"
+                  type="number"/>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="Type:"
+              min-width="120">
+              <template slot-scope="{row}">
+                <el-select
+                  style="width: 100%;"
+                  v-model="row.itemType">
+                  <el-option
+                    v-for="item in options.itemType"
+                    :key="item"
+                    :label="item"
+                    :value="item"/>
+                </el-select>
+              </template>
+            </el-table-column>
+            <el-table-column
+              label=""
+              min-width="90">
+              <template slot-scope="{row, $index}">
+                <div class="flexc">
+                  <img
+                    class="list-item-icon"
+                    @click="subtAdditionalItem($index)"
+                    src="../../assets/form-list-sub.png"/>
+                  <img
+                    v-if="$index === form.items.length - 1"
+                    class="list-item-icon"
+                    @click="addAdditionalItem"
+                    src="../../assets/form-list-add.png"/>
+                </div>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+      <div class="card-content flexcr">
+        <div class="buttons">
+          <el-button
+            type="primary"
+            class="cancel-button"
+            @click="onClickCancel">
+            CANCEL
+          </el-button>
+          <el-button
+            class="confirm-button"
+            type="primary"
+            @click="onClickSave">
+            SAVE
+          </el-button>
+        </div>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import site from '@/http/api/site'
+import settings from '../../settings.js'
+import financial from '@/http/api/financial';
+import {getCountryList} from '../../utils/index.js'
+export default {
+  data() {
+    return {
+      loading: false,
+      form: {
+        entityId: "",
+        entityName: "",
+        entityBusinessRegNo: "",
+        entityCountryCode: settings.defaultCountry,
+        entityCallingCode: settings.defaultCalling,
+        entityType: "",
+        entityContactName: "",
+        entityContactEmail: "",
+        entityContactNumber: "",
+        entitySigningDate: "",
+        entitySettlementDate: "",
+        items: [],
+        tabulations: []
+      },
+      rules: {},
+      options: {
+        dates: [],
+        country: [],
+        itemType: [],
+        accountType: [],
+        callingCode: []
+      },
+      currencyData: {
+        SG: "S$"
+      },
+      isEdit: false
+    }
+  },
+  created() {
+    this.loading = true;
+    this.getCountryList();
+    this.getItemTypeOptions();
+    this.getAccountTypeOptions();
+    this.getBillingDateOptions();
+    if (this.$route.params.id) {
+      this.isEdit = true;
+      this.getBillingEntityInfo()
+    } else {
+      this.addAdditionalItem();
+      setTimeout(() => {
+        this.loading = false;
+      }, 1000);
+    }
+  },
+  methods: {
+    getBillingDateOptions() {
+      this.options.dates = [];
+      for (let i = 1; i < 31; i++) {
+        this.options.dates.push(i);
+      }
+      this.form.tabulations = [{
+        enable: false,
+        tabulationFixed: "",
+        tabulationModel: "Revenue",
+        tabulationPercent: ""
+      }, {
+        enable: false,
+        tabulationFixed: "",
+        tabulationModel: "Concession",
+        tabulationPercent: ""
+      }, {
+        enable: false,
+        tabulationFixed: "",
+        tabulationModel: "Commission",
+        tabulationPercent: ""
+      }]
+    },
+    getCountryList() {
+      getCountryList(list => {
+        this.options.callingCode = list
+      })
+      site.getCountryList().then(res => {
+        if (res.data) {
+          this.options.country = res.data
+          const sign = {}
+          res.data.forEach(item => {
+            sign[item.value] = item.currencySymbol
+          })
+          this.currencyData = sign;
+        }
+      })
+    },
+    getAccountTypeOptions() {
+      financial.getBillingAccountTypes().then(res => {
+        if (res.data) {
+          this.options.accountType = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: 'error'
+        })
+      })
+    },
+    getItemTypeOptions() {
+      financial.getAdditionalItemTypes().then(res => {
+        if (res.data) {
+          this.options.itemType = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: 'error'
+        })
+      })
+    },
+    getBillingEntityInfo() {
+      financial.viewBillingAccount(this.$route.params.id).then(res => {
+        if (res.data) {
+          this.form = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: 'error'
+        })
+      }).finally(() => {
+        this.loading = false;
+      })
+    },
+    isModelEnable(index) {
+      for (let i = 0; i < this.form.tabulations.length; i++) {
+        let type = this.form.tabulations[i];
+        if (type.enable) {
+          if (index == i) {
+            return false;
+          } else {
+            return true;
+          }
+        } else {
+          continue;
+        }
+      }
+      return false;
+    },
+    addAdditionalItem() {
+      this.form.items.push({
+        itemName: "",
+        itemPrice: "",
+        itemType: ""
+      })
+    },
+    subtAdditionalItem(index) {
+      const item = this.form.items[index];
+      if (item.itemId) {
+        this.$confirm('Are you sure you want to delete this item?', 'Delete', {
+          confirmButtonText: 'Confirm',
+          cancelButtonText: 'Cancel',
+          type: 'warning',
+        }).then(() => {
+          this.deleteAddtionalItem(item, index);
+        })
+      } else {
+        this.form.items.splice(index, 1)
+        if (this.form.items.length == 0) {
+          this.addAdditionalItem()
+        }
+      }
+    },
+    deleteAddtionalItem(item, index) {
+      financial.deleteAdditionalItem(item.itemId).then(res => {
+        this.$message({
+          type: 'success',
+          message: "Delete success",
+        })
+        this.form.items.splice(index, 1)
+        if (this.form.items.length == 0) {
+          this.addAdditionalItem()
+        }
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      })
+    },
+    onClickCancel() {
+      this.$nextTick(() => {
+        this.$router.replace({
+          path: "/financial-management/billing-account-mgmt"
+        })
+      })
+    },
+    onClickSave() {
+      this.$refs['detailForm'].validate(result => {
+        if (result) {
+          this.loading = true;
+          if (this.isEdit) {
+            this.onUpdateBiling();
+          } else {
+            this.onCreateBiling();
+          }
+        }
+      })
+    },
+    onCreateBiling() {
+      financial.addBillingAccount(this.form).then(res => {
+        this.$message({
+          message: "Add successfully",
+          type: 'success',
+          duration: 3000,
+        })
+        this.onClickCancel();
+      }).catch((error) => {
+        this.$message({
+          message: error,
+          type: "error",
+          duration: 5000,
+        })
+      }).finally(() => {
+        this.loading = false;
+      })
+    },
+    onUpdateBiling() {
+      financial.updateBillingAccount(this.form).then(res => {
+        this.$message({
+          message: "Update successfully",
+          type: 'success',
+          duration: 3000,
+        })
+        this.onClickCancel();
+      }).catch((error) => {
+        this.$message({
+          message: error,
+          type: "error",
+          duration: 5000,
+        })
+      }).finally(() => {
+        this.loading = false;
+      })
+    }
+  }
+}
+</script>
+
+<style scoped="scoped" lang='scss'>
+  @import '../../styles/variables.scss';
+  
+  .card-container {
+    width: 100%;
+    padding: 20px 40px;
+    min-height: $mainAppMinHeight;
+    background-color: #F0F5FC;
+  }
+  .card-content {
+    margin: 0 8px 16px;
+    padding: 15px 50px;
+    border-radius: 6px;
+    background-color: white;
+  }
+  .section-title {
+    color: #333;
+    margin-top: 20px;
+    margin-bottom: 30px;
+    font-size: 15px;
+    user-select: none;
+    line-height: 24px;
+    font-weight: bold;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+  .add-text {
+    width: 100%;
+    min-width: 150px;
+    max-width: 300px;
+  }
+  .add-text ::v-deep .el-textarea__inner {
+    font-family: sans-serif;
+  }
+  .hr {
+    height: 2px;
+    margin: 10px -40px;
+    background-color: #F0F5FC;
+  }
+  .buttons {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+  @media screen and (max-width: 500px) {
+    .card-container {
+      padding: 0px;
+    }
+    .card-content {
+      padding: 15px 40px;
+    }
+  }
+  .tabu-table {
+    width: 100%;
+    max-width: 520px;
+    padding-bottom: 10px;
+  }
+  .items-table {
+    width: 100%;
+    padding: 10px 0;
+    max-width: 520px;
+  }
+  ::v-deep .customer-row {
+    th,td {
+      background: #fff;
+      border-bottom: none;
+    }
+    .row-text {
+      color: #333;
+      font-size: 14px;
+    }
+    .input-text {
+      width: 70px;
+      margin-right: 5px;
+    }
+    .list-item-icon {
+      width: 28px;
+      height: 28px;
+      cursor: pointer;
+      margin: 0 8px;
+    }
+  }
+</style>

+ 355 - 0
Strides-Admin/src/views/settlement/assignment.vue

@@ -0,0 +1,355 @@
+<template>
+  <el-dialog
+    :title="title"
+    :visible="visible"
+    :before-close="onHide"
+    class="assign-label-dialog">
+    <div class="filter-container filter-view">
+      <el-select
+        style="min-width: 70px; max-width: 120px;"
+        clearable
+        v-model="filter.pageVo.assignmentStatus"
+        placeholder="Status"
+        @change="onSearch">
+        <el-option
+          v-for="(item, index) in statusOptions"
+          :key="index"
+          :label="item"
+          :value="item"/>
+      </el-select>
+      <div style="flex: 1; min-width: 150px; max-width: 300px;">
+        <el-input
+          clearable
+          v-model="filter.pageVo.criteria"
+          placeholder="Search by Site Name or Service Provider"
+          @keyup.enter.native="onSearch"/>
+      </div>
+      <el-button
+        type="primary"
+        @click="onSearch">
+        Search
+      </el-button>
+    </div>
+    <div class="assign-label-actions">
+      <el-button
+        type="danger"
+        :disabled="selectRow.length == 0"
+        :loading="loading.unassign"
+        @click="onClickUnassign">
+        Batch Un-assign
+      </el-button>
+      <el-button
+        type="accent"
+        :disabled="selectRow.length == 0"
+        :loading="loading.assign"
+        @click="onClickAssign">
+        Batch Assign
+      </el-button>
+    </div>
+    <div class="table-view" v-loading="table.loading">
+      <el-table
+        :data="table.data"
+        height="100%"
+        class="no-border"
+        @selection-change="changeSelection">
+        <template v-if="isGroup">
+          <el-table-column
+            align="center"
+            label="Name"
+            prop="name"
+            min-width="140"/>
+          <el-table-column
+            align="center"
+            label="Type"
+            prop="type"
+            min-width="120"/>
+          <el-table-column
+            align="center"
+            label="Country"
+            prop="country"
+            min-width="120"/>
+          <el-table-column
+            align="center"
+            label="Approved"
+            prop="approved"
+            min-width="100"/>
+          <el-table-column
+            align="center"
+            label="Pending"
+            prop="pending"
+            min-width="100"/>
+        </template>
+        <template v-else>
+          <el-table-column
+            align="center"
+            label="Site Name"
+            prop="siteName"
+            min-width="120"/>
+          <el-table-column
+            align="center"
+            label="Address"
+            prop="address"
+            min-width="120"/>
+          <el-table-column
+            align="center"
+            label="Service Provider"
+            min-width="140">
+            <template slot-scope="{row}">
+              <div v-for="item in row.serviceProviders" :key="item">{{item}}</div>
+            </template>
+          </el-table-column>
+          <el-table-column
+            align="center"
+            label="Assignment Status"
+            prop="assignmentStatus"
+            min-width="130"/>
+        </template>
+        <el-table-column
+          align="center"
+          label="Select"
+          type="selection"
+          width="50"
+          v-if="visible"/>
+      </el-table>
+    </div>
+    <div class="center" style="margin-bottom: -20px;">
+      <Pagination
+        v-show="table.total"
+        :total="table.total"
+        :page.sync="filter.pageNo"
+        :limit.sync="filter.pageSize"
+        @pagination="getTableData"/>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import api from '../../http/api/financial.js'
+import Pagination from '@/components/Pagination'
+export default {
+  name: "AssignmentSiteLabel",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    isGroup: {
+      type: Boolean,
+      default: false
+    },
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      filter: {
+        pageSize: 10,
+        pageNo: 1,
+        pageVo: {
+          criteria: "",
+          entityId: "",
+          countryCode: "",
+          assignmentStatus: ""
+        }
+      },
+      table: {
+        data: [],
+        total: 0,
+        loading: false
+      },
+      loading: {
+        assign: false,
+        unassign: false
+      },
+      statusOptions: [],
+      selectRow: []
+    };
+  },
+  components: {Pagination},
+  computed: {
+    title() {
+      return this.isGroup ? "ASSIGN GROUP (SITE WHITELIST)" : "ASSIGN SITES" 
+    }
+  },
+  watch: {
+    visible: {
+      handler(n, o) {
+        console.log("watch.visible", n, this.item);
+        if (n) {
+          this.filter.pageVo.entityId = this.item.entityId;
+          this.filter.pageVo.countryCode = this.item.entityCountryCode;
+          this.filter.pageVo.assignmentStatus = "";
+          this.onSearch()
+        }
+      }
+    }
+  },
+  mounted() {
+    this.getStatusOptions();
+  },
+  methods: {
+    onHide() {
+      this.$emit("hide", true);
+    },
+    onSearch() {
+      this.filter.pageNo = 1;
+      this.getTableData();
+    },
+    getStatusOptions() {
+      api.getAssignStatusOptions().then(res => {
+        if (res.data) {
+          this.statusOptions = res.data
+        }
+      }).catch(error => {
+        this.$message({
+          type: 'error',
+          message: error
+        })
+      })
+    },
+    getTableData() {
+      const promise = this.isGroup
+        ? api.getAssignGroupPages(this.filter)
+        : api.getAssignSitePages(this.filter)
+      promise.then(res => {
+        if (res.total && res.data) {
+          this.table.total = res.total;
+          this.table.data = res.data;
+        } else {
+          this.table.total = 0;
+          this.table.data = [];
+        }
+        this.table.loading = false;
+      }).catch(error => {
+        this.$message({
+          type: 'error',
+          message: error
+        })
+        this.table.total = 0;
+        this.table.data = [];
+        this.table.loading = false;
+      })
+    },
+    changeSelection(val) {
+      this.selectRow = val;
+    },
+    getSelectIds() {
+      const ids = [];
+      this.selectRow.forEach(item => {
+        if (item.sitePk) {
+          ids.push(item.sitePk)
+        } else {
+          ids.push(item.id)
+        }
+      })
+      return ids;
+    },
+    onClickAssign() {
+      const params = {
+        entityId: this.item.entityId,
+        eligibleRefIds: this.getSelectIds()
+      }
+      this.loading.assign = true;
+      api.assignEligible(params).then(res => {
+        this.$message({
+          type: 'success',
+          message: res.msg || "Success"
+        })
+        this.getTableData()
+      }).catch(error => {
+        this.$message({
+          type: 'error',
+          message: error
+        })
+      }).finally(() => {
+        this.loading.assign = false;
+      })
+    },
+    onClickUnassign() {
+      const params = {
+        entityId: this.item.entityId,
+        eligibleRefIds: this.getSelectIds()
+      }
+      this.loading.unassign = true;
+      api.unassignEligible(params).then(res => {
+        this.$message({
+          type: 'success',
+          message: res.msg || "Success"
+        })
+        this.getTableData()
+      }).catch(error => {
+        this.$message({
+          type: 'error',
+          message: error
+        })
+      }).finally(() => {
+        this.loading.unassign = false;
+      })
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.assign-label-dialog
+  ::v-deep .el-dialog {
+    width: 65vw;
+    height: 90vh;
+    display: flex;
+    max-width: 1200px;
+    flex-direction: column;
+    margin-top: 5vh !important;
+  .el-dialog__header {
+    padding: 20px 20px 0;
+    font-weight: bold;
+  }
+  .el-dialog__body {
+    flex: 1;
+    padding: 20px;
+    display: flex;
+    overflow: hidden;
+    flex-direction: column;
+  }
+  @media screen and (max-width: 1200px) {
+    & {
+      width: 70vw;
+    }
+  }
+  @media screen and (max-width: 1000px) {
+    & {
+      width: 80vw;
+    }
+  }
+  @media screen and (max-width: 800px) {
+    & {
+      width: 90vw;
+    }
+  }
+  @media screen and (max-width: 700px) {
+    & {
+      width: 99vw;
+    }
+  }
+  @media screen and (max-width: 320px) {
+    & {
+      width: 100%;
+      min-width: 300px;
+    }
+  }
+}
+.assign-label-dialog .table-view {
+  flex: 1;
+  overflow-y: auto;
+  padding-top: 10px;
+  margin-bottom: -10px;
+}
+.assign-label-actions {
+  display: flex;
+  padding-top: 5px;
+  flex-wrap: wrap-reverse;
+  align-items: center;
+  justify-content: flex-end;
+}
+</style>

+ 121 - 23
Strides-Admin/src/views/settlement/billingAccount.vue

@@ -7,12 +7,12 @@
           placeholder="Account Type"
           clearable
           @change="onClickSearch"
-          v-model="filters.pageVo.accountTypeId">
+          v-model="filters.pageVo.accountType">
           <el-option
             v-for="(item,index) in options.types"
             :key="index"
-            :label="item.name"
-            :value="item.value"/>
+            :label="item"
+            :value="item"/>
         </el-select>
         <el-input
           v-model="filters.pageVo.criteria"
@@ -30,53 +30,75 @@
       <el-table-column
         align="center"
         label="Entity Name"
-        prop="articleTitle"
+        prop="entityName"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Account Type"
-        prop="articleTitle"
+        prop="accountType"
         min-width="150"/>
       <el-table-column
         align="center"
         label="POC Name"
-        prop="articleTitle"
+        prop="pocName"
         min-width="150"/>
       <el-table-column
         align="center"
         label="POC Email"
-        prop="articleTitle"
+        prop="pocEmail"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Effective Date"
-        prop="articleTitle"
+        prop="effectiveDate"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Billing Date"
-        prop="articleTitle"
+        prop="billingDate"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Update Date Time"
-        prop="articleTitle"
+        prop="updateDateTime"
         min-width="150"/>
       <el-table-column
         align="center"
         label="Status"
-        prop="articleTitle"
+        prop="status"
         min-width="120"/>
       <el-table-column
         align="center"
         label="Action"
-        min-width="120"
+        min-width="80"
         v-if="!$route.meta.onlyView">
         <template v-slot="{ row }">
-          <TableAction
-            @edit="onClickEdit(row)"
-            @delete="onClickDelete(row)"
-            v-if="row.dataStatus != 'Inactive'"/>
+          <el-dropdown
+            class="action-dropdown"
+            @command="(v) => handleCommand(v, row)"
+            v-if="row.status != 'Inactive'">
+            <i class="el-icon-more icon-action"></i>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item
+                command="onClickEdit">
+                Edit
+              </el-dropdown-item>
+              <el-dropdown-item
+                command="assignGroups"
+                v-if="row.accountType == 'Corporate'">
+                Assign Groups
+              </el-dropdown-item>
+              <el-dropdown-item
+                command="assignSites"
+                v-else>
+                Assign Sites
+              </el-dropdown-item>
+              <el-dropdown-item
+                command="onClickDelete">
+                Delete
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
         </template>
       </el-table-column>
     </el-table>
@@ -88,11 +110,15 @@
         :limit.sync="filters.pageSize"
         @pagination="getTableData" />
     </div>
+    <Assignment
+      v-bind="assign"
+      @hide="hideAssiment"/>
   </div>
 </template>
 
 <script>
-import TableAction from '@/components/TableAction.vue'
+import financial from '@/http/api/financial';
+import Assignment from './assignment.vue'
 import Pagination from '@/components/Pagination'
 export default {
   data() {
@@ -102,8 +128,7 @@ export default {
         pageSize: 10,
         pageVo: {
           criteria: "",
-          dataStatus: "",
-          accountTypeId: ""
+          accountType: ""
         }
       },
       table: {
@@ -112,23 +137,40 @@ export default {
         loading: false
       },
       options: {
-        status: [],
         types: []
+      },
+      assign: {
+        item: {},
+        visible: false,
+        isGroup: false
       }
     };
   },
-  components: { Pagination, TableAction },
+  components: { Pagination, Assignment },
   created() {
-    //this.onClickSearch();
+    this.getAccountTypeOptions();
+    this.onClickSearch();
   },
   methods: {
     onClickSearch() {
       this.filters.pageNo = 1
       this.getTableData()
     },
+    getAccountTypeOptions() {
+      financial.getBillingAccountTypes().then(res => {
+        if (res.data) {
+          this.options.types = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: 'error'
+        })
+      })
+    },
     getTableData() {
       this.table.loading = true;
-      api.getSettlementPages(this.filters).then(res => {
+      financial.getBillingAccountPages(this.filters).then(res => {
         if (res.data && res.total) {
           this.table.total = res.total;
           this.table.list = res.data;
@@ -147,6 +189,62 @@ export default {
         this.table.loading = false;
       })
     },
+    handleCommand(cb, item) {
+      this[cb](item)
+    },
+    onClickAdd() {
+      this.$router.push({
+        path: "/financial-management/billing-account-add"
+      })
+    },
+    onClickEdit(row) {
+      this.$router.push({
+        path: "/financial-management/billing-account-edit/" + row.entityId
+      })
+    },
+    onClickDelete(row) {
+      this.$confirm('Are you sure you want to delete this item?', 'Set Inactive', {
+        confirmButtonText: 'OK',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      }).then(res => {
+        this.deleteEntity(row.entityId);
+      })
+    },
+    assignSites(row) {
+      this.assign.item = row;
+      this.assign.isGroup = false;
+      this.assign.visible = true;
+    },
+    assignGroups(row) {
+      this.assign.item = row;
+      this.assign.isGroup = true;
+      this.assign.visible = true;
+    },
+    hideAssiment(e) {
+      this.assign.item = {};
+      this.assign.visible = false;
+      this.assign.isGroup = false;
+      if (e) {
+        this.getTableData();
+      }
+    },
+    deleteEntity(id) {
+      this.loading = true;
+      financial.deleteBillingAccount(id).then(res => {
+        this.$message({
+          type: 'success',
+          message: "Set inactive success."
+        })
+        this.getTableData()
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        })
+        this.loading = false;
+      })
+    }
   }
 }
 </script>

+ 1 - 1
Strides-Admin/src/views/zetting/Administrator.vue

@@ -129,7 +129,7 @@
         </div>
         <div class="card-content">
           <div class="section-title">
-            <span style="padding-right: 10px;"></span>
+            <span style="padding-right: 10px;">Notification Types</span>
           </div>
           <el-checkbox-group
             class="features-group"