Parcourir la source

Merge branch 'master' of https://ac.tastecn.com/github/wormwood-evcharge/strides

vbea il y a 5 mois
Parent
commit
c767a99c53

+ 6 - 6
Strides-APP/app/pages/chargeV2/SummaryV2.js

@@ -40,7 +40,7 @@ export default class SummaryV2 extends Component {
   componentDidMount() {
     const params = this.props.route.params;
     if (params.chargingPk) {
-      //Dialog.showProgressDialog();
+      Dialog.showProgressDialog();
       this.setState({
         chargingPk: params.chargingPk
       })
@@ -75,7 +75,7 @@ export default class SummaryV2 extends Component {
     apiCharge.getChargeSummaryV2({
       chargingPk: chargingPk
     }).then(res => {
-      //Dialog.dismissLoading();
+      Dialog.dismissLoading();
       if (res.data) {
         this.setState({
           loading: false,
@@ -85,7 +85,7 @@ export default class SummaryV2 extends Component {
         });
       }
     }).catch((err) => {
-      //Dialog.dismissLoading();
+      Dialog.dismissLoading();
       toastShort(err);
       this.setState({
         isPendding: true,
@@ -134,7 +134,7 @@ export default class SummaryV2 extends Component {
     if (this.state.summaryInfo?.taxRate) {
       return title.replace("$s", this.state.summaryInfo.taxRate)
     } else {
-      return title;
+      return title.replace(" ($s)", "");
     }
   }
 
@@ -388,7 +388,7 @@ export default class SummaryV2 extends Component {
                 <TextView style={styles.text}>{this.state.summaryInfo.idleFee.duration}</TextView>
               </View>
               <View style={styles.formRow}>
-                <TextView style={styles.label}>{this.getTaxTitle($t('receipt.labelIdleFeeSubtotal2'))}</TextView>
+                <TextView style={styles.label}>{$t('receipt.labelIdleFeeSubtotal3')}</TextView>
                 <TextView style={styles.text}>{this.state.summaryInfo.idleFee.subtotal}</TextView>
               </View>
             </View>
@@ -427,7 +427,7 @@ export default class SummaryV2 extends Component {
               }
               { utils.isNotEmpty(this.state.summaryInfo.payment.idleFeeSubtotal) &&
                 <View style={styles.formRow}>
-                  <TextView style={styles.label}>{this.getTaxTitle($t('receipt.labelIdleFeeSubtotal2'))}</TextView>
+                  <TextView style={styles.label}>{$t('receipt.labelIdleFeeSubtotal3')}</TextView>
                   <TextView style={styles.text}>{this.state.summaryInfo.payment.idleFeeSubtotal}</TextView>
                 </View>
               }

+ 6 - 2
Strides-Admin/src/api/apiEmails.js

@@ -5,9 +5,10 @@ const prefix = "dawn/api/v1/"
 const apiEmails = {
   /**
    * 获取邮件配置
+   * @param {Object} emailType
    */
-  getEmailConfiguration() {
-    return get(prefix + "email/config")
+  getEmailConfiguration(params) {
+    return get(prefix + "email/config", params)
   },
   /**
    * 保存邮件配置
@@ -70,6 +71,9 @@ const apiEmails = {
    */
   sendTestMail(data) {
     return post(prefix + "email/send-test", data)
+  },
+  getEmailTypeOptions() {
+    return get(prefix + "email/type-select")
   }
 }
 

+ 77 - 0
Strides-Admin/src/api/apiMaintain.js

@@ -0,0 +1,77 @@
+import {get, post, put, del, download} from '../http/http'
+
+const prefix = "dawn/api/v1/"
+
+
+const apiMaintain = {
+  /**
+   * 获取维护类型选项
+   */
+  getMaintainTypeOptions() {
+    return get(prefix + "maintenance-type-select")
+  },
+  /**
+   * 获取维护计划表格(分页)
+   * @param {Object} params
+   */
+  getMaintainPages(params) {
+    return post(prefix + "downtime-pages", params)
+  },
+  /**
+   * 创建维护计划
+   * @param {Object} data
+   */
+  creatMaintainPlan(data) {
+    return post(prefix + "downtime", data)
+  },
+  /**
+   * 更新维护计划
+   * @param {Object} data
+   */
+  updateMaintainPlan(data) {
+    return put(prefix + "downtime/" + data.downtimeId, data)
+  },
+  /**
+   * 查询指定的维护计划
+   * @param {Object} downtimeId
+   */
+  getMaintainById(downtimeId) {
+    return get(prefix + "downtime/" + downtimeId)
+  },
+  /**
+   * 删除指定的维护计划
+   * @param {Object} downtimeId
+   */
+  deleteMaintainById(downtimeId) {
+    return del(prefix + "downtime/" + downtimeId)
+  },
+  /**
+   * 获取维护计划的连接器分配表格(分页)
+   * @param {Object} params
+   */
+  getMaintainAssignPages(params) {
+    return post(prefix + "downtime/connector-pages", params)
+  },
+  /**
+   * 分配指定维护计划的连接器
+   * @param {Object} data
+   */
+  assignMaintainConnector(data) {
+    return post(prefix + "downtime/assign-connectors", data)
+  },
+  /**
+   * 取消分配指定维护计划的连接器
+   * @param {Object} data
+   */
+  unassignMaintainConnector(data) {
+    return post(prefix + "downtime/unassign-connectors", data)
+  },
+  /**
+   * 下载批量创建维护计划的模板
+   */
+  downloadTemplate() {
+    return download(prefix + 'downtime-template')
+  }
+}
+
+export default apiMaintain;

+ 3 - 0
Strides-Admin/src/api/apiReport.js

@@ -9,6 +9,9 @@ const apiReport = {
   getReportTypeOptions() {
     return get(prefix + 'report-types')
   },
+  getSiteTagOptions() {
+    return get(prefix + "site-labels")
+  },
   generateReport(params) {
     return post(prefix + 'generate', params)
   },

+ 6 - 0
Strides-Admin/src/http/api/site.js

@@ -66,9 +66,15 @@ const site = {
   getHistoryRates(params) {
     return get(MODULE_NAME + '/site-rates', params)
   },
+  /**
+   * 站点标签部分
+   */
   getLabelPages(params) {
     return post("label/label-pages", params)
   },
+  getLabelTypeOption() {
+    return get("label/label-type-select")
+  },
   addSiteLabel(params) {
     return post("label/labels", params)
   },

+ 38 - 1
Strides-Admin/src/router/IncidentRouter.js

@@ -44,6 +44,43 @@ export default {
         activeIcon: 'sidebar-submenu-item-active',
         breadcrumb: true
       }
-    }
+    },
+    {
+      path: '/incident-management/maintenance-settings',
+      component: () => import('@/views/maintain/index'),
+      name: 'system-maintenance',
+      meta: {
+        title: 'Maintenance Management',
+        icon: 'sidebar-submenu-item',
+        activeIcon: 'sidebar-submenu-item-active'
+      }
+    },
+    {
+      path: '/incident-management/maintenance-settings/add',
+      component: () => import('@/views/maintain/detail'),
+      name: 'system-maintenance-add',
+      meta: {
+        title: 'Create',
+        activeMenu: "/incident-management/maintenance-settings",
+        parent: {
+          title: 'Maintenance Management',
+          path: "/incident-management/maintenance-settings"
+        }
+      }
+    },
+    {
+      path: '/incident-management/maintenance-settings/:id',
+      component: () => import('@/views/maintain/detail'),
+      name: 'system-maintenance-update',
+      meta: {
+        title: 'Update',
+        icon: 'sidebar-submenu-item',
+        activeMenu: "/incident-management/maintenance-settings",
+        parent: {
+          title: 'Maintenance Management',
+          path: "/incident-management/maintenance-settings"
+        }
+      }
+    },
   ]
 }

+ 3 - 3
Strides-Admin/src/views/company/index.vue

@@ -117,7 +117,7 @@
           Eligible Sites<br/><span style="white-space: nowrap;">(w Basic Discount)</span>
         </template>
         <template slot-scope="{row}">
-          {{getEligibleCount(row.eligibleSiteCount)}}
+          <span class="link-type" @click="assignSites(row)">{{getEligibleCount(row.eligibleSiteCount)}}</span>
         </template>
       </el-table-column>
       <el-table-column
@@ -284,7 +284,7 @@ export default {
       } else {
         this.assign.item = {};
         this.assign.visible = false;
-        this.getGroupPages();
+        this.getTableData();
       }
     },
     onClickEditButton(row) {
@@ -301,7 +301,7 @@ export default {
             type: 'success',
             message: 'Delete success!'
           })
-          this.getGroupPages()
+          this.getTableData()
         }
       }).catch(error => {
         this.$message({

+ 308 - 0
Strides-Admin/src/views/maintain/AssignmentDialog.vue

@@ -0,0 +1,308 @@
+<template>
+  <el-dialog
+    :title="title"
+    :visible="visible"
+    :before-close="onHide"
+    custom-class="points-assign-dialog">
+    <div class="filter-container filter-view">
+      <el-select
+        style="min-width: 70px; max-width: 120px;"
+        clearable
+        v-model="filter.pageCriteria.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.pageCriteria.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-table-actions">
+      <el-button
+        type="danger"
+        :disabled="selectRow.length == 0"
+        :loading="loading.unassign"
+        @click="unassignSites">
+        Batch Un-assign
+      </el-button>
+      <el-button
+        type="accent"
+        :disabled="selectRow.length == 0"
+        :loading="loading.assign"
+        @click="assignSites">
+        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"
+        v-if="visible">
+        <el-table-column
+          align="center"
+          label="Site Name"
+          prop="siteName"
+          min-width="130"/>
+        <el-table-column
+          align="center"
+          label="Charger Id"
+          prop="chargeBoxId"
+          min-width="150"/>
+        <el-table-column
+          align="center"
+          label="Connector Id"
+          prop="connectorId"
+          min-width="150"/>
+        <el-table-column
+          align="center"
+          label="Assignment Status"
+          prop="assignmentStatus"
+          min-width="150"/>
+        <el-table-column
+          align="center"
+          label="Select"
+          type="selection"/>
+      </el-table>
+    </div>
+    <div class="center" style="margin-bottom: -20px;">
+      <Pagination
+        v-show="table.total"
+        :total="table.total"
+        :page.sync="filter.pageNum"
+        :limit.sync="filter.pageSize"
+        @pagination="getTableData"/>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import apiBase from '@/api/apiBase';
+import api from '@/api/apiMaintain.js';
+import Pagination from '@/components/Pagination'
+export default {
+  name: "MaintainAssignmentDialog",
+  props: {
+    title: {
+      type: String,
+      default: ""
+    },
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  components: {Pagination},
+  data() {
+    return {
+      filter: {
+        pageNum: 1,
+        pageSize: 10,
+        pageCriteria: {
+          criteria: "",
+          downtimeId: "",
+          assignmentStatus: ""
+        }
+      },
+      table: {
+        data: [],
+        total: 0,
+        loading: false
+      },
+      loading: {
+        assign: false,
+        unassign: false
+      },
+      selectRow: [],
+      statusOptions:[]
+    };
+  },
+  mounted() {
+    this.getStatusOptions();
+  },
+  watch: {
+    visible: {
+      handler(n, o) {
+        if (n) {
+          this.filter.pageCriteria.downtimeId = this.item.downtimeId;
+          this.onSearch();
+        }
+      }
+    }
+  },
+  methods: {
+    onHide() {
+      this.$emit("hide");
+    },
+    onSearch() {
+      this.filter.pageNo = 1;
+      this.getTableData();
+    },
+    getStatusOptions() {
+      apiBase.getAssignStatusOptions().then(res => {
+        if (res.data) {
+          this.statusOptions = res.data
+        }
+      }).catch(error => {
+        this.$message({
+          type: 'error',
+          message: error
+        })
+      })
+    },
+    getTableData() {
+      this.selectRow = []
+      this.table.loading = true;
+      api.getMaintainAssignPages(this.filter).then(res => {
+        if (res.data.totalRow && res.data.records) {
+          this.table.total = res.data.totalRow;
+          this.table.data = res.data.records;
+        } 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 => {
+        ids.push(item.connectorPk)
+      })
+      return ids;
+    },
+    assignSites() {
+      const params = {
+        downtimeId: this.item.downtimeId,
+        connectorPks: this.getSelectIds()
+      }
+      this.loading.assign = true;
+      api.assignMaintainConnector(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;
+      })
+    },
+    unassignSites() {
+      const params = {
+        downtimeId: this.item.downtimeId,
+        connectorPks: this.getSelectIds()
+      }
+      this.loading.unassign = true;
+      api.unassignMaintainConnector(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 scoped>
+  >>> .points-assign-dialog {
+    width: 65vw;
+    height: 90vh;
+    display: flex;
+    max-width: 1200px;
+    flex-direction: column;
+    margin-top: 5vh !important;
+  }
+  >>> .points-assign-dialog .el-dialog__header {
+    padding: 20px 20px 0;
+    font-weight: bold;
+  }
+  >>> .points-assign-dialog .el-dialog__body {
+    flex: 1;
+    padding: 20px;
+    display: flex;
+    overflow: hidden;
+    flex-direction: column;
+  }
+  .assign-table-actions {
+    display: flex;
+    padding-top: 5px;
+    flex-wrap: wrap-reverse;
+    align-items: center;
+    justify-content: flex-end;
+  }
+  .points-assign-dialog .table-view {
+    flex: 1;
+    overflow-y: auto;
+    padding-top: 10px;
+    margin-bottom: -10px;
+  }
+  @media screen and (max-width: 1200px) {
+    .points-assign-dialog {
+      width: 70vw;
+    }
+  }
+  @media screen and (max-width: 1000px) {
+    .points-assign-dialog {
+      width: 80vw;
+    }
+  }
+  @media screen and (max-width: 800px) {
+    .points-assign-dialog {
+      width: 90vw;
+    }
+  }
+  @media screen and (max-width: 700px) {
+    .points-assign-dialog {
+      width: 99vw;
+    }
+  }
+  @media screen and (max-width: 320px) {
+    .points-assign-dialog {
+      width: 100%;
+      min-width: 300px;
+    }
+  }
+</style>

+ 472 - 0
Strides-Admin/src/views/maintain/detail.vue

@@ -0,0 +1,472 @@
+<template>
+  <div class="card-container">
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-position="right"
+      label-width="150px"
+      style="width: 100%;"
+      v-loading="loading.page">
+      <div class="flexr">
+        <div class="flex1 card-content">
+          <div class="section-title">Maintenance Info</div>
+          <el-form-item
+            label="Maintenance Type:"
+            prop="maintenanceType">
+            <el-select
+              class="add-text"
+              placeholder="Maintenance Type"
+              v-model="form.maintenanceType">
+              <el-option
+                v-for="(item, index) in options.type"
+                :key="index"
+                :label="item"
+                :value="item"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Type Category:"
+            prop="maintenanceTypeCategory">
+            <el-input
+              class="add-text"
+              v-model="form.maintenanceTypeCategory"
+              placeholder="Maintenance Type Category"
+              maxlength="30"/>
+          </el-form-item>
+          <el-form-item
+            label="Carpark Code:"
+            prop="carparkCode">
+            <el-input
+              class="add-text"
+              v-model="form.carparkCode"
+              placeholder="Carpark Code"
+              maxlength="10"/>
+          </el-form-item>
+          <el-form-item
+            label="Chargers:"
+            prop="chargeBoxIds">
+            <el-select
+              clearable
+              class="add-text"
+              v-model="form.chargeBoxIds"
+              placeholder="Filter by Station ID"
+              multiple
+              filterable>
+              <el-option
+                v-for="(item, index) in options.station"
+                :key="index"
+                :label="item.chargeBoxId"
+                :value="item.chargeBoxId"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            prop="startTime"
+            label="Start Date Time:">
+            <el-date-picker
+              v-model="form.startTime"
+              class="add-text"
+              type="datetime"
+              format="yyyy-MM-dd HH:mm"
+              value-format="yyyy-MM-dd HH:mm"/>
+          </el-form-item>
+          <el-form-item
+            prop="endTime"
+            label="End Date Time:">
+            <el-date-picker
+              v-model="form.endTime"
+              class="add-text"
+              type="datetime"
+              format="yyyy-MM-dd HH:mm"
+              value-format="yyyy-MM-dd HH:mm"
+              :default-value="form.startTime"/>
+          </el-form-item>
+          <el-form-item
+            label="Send Notification:">
+            <el-switch
+              v-model="form.notification"/>
+          </el-form-item>
+          <el-form-item
+            label="Remarks:">
+            <el-input
+              class="add-text"
+              :autosize="autosize"
+              v-model="form.remark"
+              type="textarea"
+              maxlength="5000"/>
+          </el-form-item>
+        </div>
+        <div class="flex1 card-content">
+          <div class="section-title">Evidences</div>
+          <el-form-item
+            label="Reported By:"
+            label-width="120px">
+            <el-input
+              class="add-text"
+              v-model="form.reportedBy"
+              maxlength="50"/>
+          </el-form-item>
+          <el-form-item
+            label="Approved By:"
+            label-width="120px">
+            <el-input
+              class="add-text"
+              v-model="form.approvedBy"
+              maxlength="50"/>
+          </el-form-item>
+          <el-form-item
+            label="Evidence:"
+            class="is-required"
+            label-width="120px">
+            <el-upload
+              class="logo-upload"
+              action
+              :limit="1"
+              :show-file-list="false"
+              :file-list="[]"
+              :on-remove="file => removeEvidence(file)"
+              :http-request="file => uploadEvidence(file)"
+              accept=".jpg,.jpeg,.png,.gif,.JPG,.JPEG"
+              v-loading="loading.upload">
+              <el-image
+                v-if="evidences.length > 0"
+                :src="evidences[0].url"
+                title="Click to update logo"/>
+              <i v-else
+                class="el-icon-plus avatar-uploader-icon"
+                title="Click to select file"/>
+            </el-upload>
+          </el-form-item>
+        </div>
+      </div>
+      <div class="card-content flexcr">
+        <div class="buttons">
+          <el-button
+            type="primary"
+            class="cancel-button"
+            @click="onBack">
+            Back
+          </el-button>
+          <el-button
+            @click="onClickSave"
+            type="primary"
+            :loading="loading.save">
+            &nbsp;Save&nbsp;
+          </el-button>
+        </div>
+        <audit-view
+          v-if="form.downtimeId"
+          url="dawn/api/v1/downtime/audit-pages"
+          :params="{downtimeId: form.downtimeId}"
+          :audit="form.audit"/>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import api from '../../api/apiMaintain.js';
+import charge from '../../http/api/charge'
+import apiBase from '../../api/apiBase.js';
+import AuditView from "@/components/AuditView"
+export default {
+  data() {
+    return {
+      loading: {
+        page: false,
+        save: false,
+        upload: false,
+        option: false
+      },
+      isEdit: false,
+      form: {
+        downtimeId: "",
+        carparkCode: "",
+        chargerId: "",
+        chargeBoxIds: [],
+        maintenanceType: "",
+        maintenanceTypeCategory: "",
+        startTime: "",
+        endTime: "",
+        notification: false,
+        notificationSent: "",
+        remark: "",
+        reportedBy: "",
+        approvedBy: "",
+        evidenceUrl: "",
+        audit: {}
+      },
+      autosize: {
+        minRows: 3,
+        maxRows: 10,
+      },
+      rules: {
+        maintenanceType: {
+          required: true,
+          trigger: "change",
+          message: "Please select maintenance type"
+        },
+        startTime: [{
+          message: "Please select start date time",
+          trigger: "change",
+          required: true,
+        }],
+        endTime: [{
+          message: "Please select end date time",
+          trigger: "change",
+          required: true,
+        }],
+        chargeBoxIds: {
+          required: true,
+          trigger: "change",
+          message: "Please select at least one charger"
+        }
+      },
+      options: {
+        type: [],
+        station: []
+      },
+      evidences: [],
+      feedbackInfo: {}
+    }
+  },
+  components: {AuditView},
+  created() {
+    this.getTypeOptions();
+    this.getStationOptions();
+    if (this.$route.params.id) {
+      this.isEdit = true;
+      this.getMaintainInfo()
+    }
+  },
+  methods: {
+    getTypeOptions() {
+      api.getMaintainTypeOptions().then(res => {
+        if (res.data && res.data.length > 0) {
+          this.options.type = res.data;
+        }
+      }).catch(err => {
+        
+      })
+    },
+    getStationOptions() {
+      this.loading.option = true;
+      charge.getStationPages({
+        pageNo: 1,
+        pageSize: 500,
+        pageVo: {
+          criteria: ""
+        }
+      }).then(res => {
+        if (res.data) {
+          this.options.station = res.data;
+        }
+      }).finally(() => {
+        this.loading.option = false;
+      })
+    },
+    getMaintainInfo() {
+      this.loading.page = true;
+      api.getMaintainById(this.$route.params.id).then(res => {
+        if (res.data) {
+          if (res.data.notificationSent) {
+            res.data.notification = true;
+          } else {
+            res.data.notification = false;
+          }
+          if (res.data.chargerId) {
+            res.data.chargeBoxIds = res.data.chargerId.split(",")
+          } else {
+            res.data.chargeBoxIds = [];
+          }
+          this.form = res.data;
+          if (this.form.evidenceUrl) {
+            this.evidences.push({
+              path: this.form.evidenceUrl,
+              url: this.$imageSrc(this.form.evidenceUrl)
+            });
+          }
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      }).finally(() => {
+        this.loading.page = false;
+      })
+    },
+    uploadEvidence(file) {
+      this.loading.upload = true;
+      const formData = new FormData()
+      formData.append('file', file.file)
+      apiBase.uploadImage(formData).then(res => {
+        if (this.evidences.length == 0) {
+          this.evidences.push({
+            url: ""
+          })
+        }
+        this.evidences[0] = ({
+          path: res.data.picturePath,
+          url: this.$imageSrc(res.data.picturePath)
+        })
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      }).finally(() => {
+        this.loading.upload = false;
+      })
+    },
+    removeEvidence(file) {
+      this.evidences = []
+      this.form.providerLogo = ""
+    },
+    onBack() {
+      this.$nextTick(() => {
+        this.$router.replace({
+          path: "/incident-management/maintenance-settings"
+        })
+      })
+    },
+    onClickSave() {
+      this.$refs.form.validate(result => {
+        if (result) {
+          if (this.evidences.length > 0) {
+            this.form.evidenceUrl = this.evidences[0].path
+          } else {
+            this.form.evidenceUrl = "";
+          }
+          /*if (!this.form.evidenceUrl) {
+            this.$message({
+              message: "Please upload evidence",
+              type: 'error'
+            });
+            return
+          }*/
+          this.form.notificationSent = this.form.notification ? "Y" : "";
+          this.form.chargerId = this.form.chargeBoxIds.join(",");
+          const params = {...this.form};
+          delete params.notification;
+          delete params.chargeBoxIds;
+          this.loading.save = true;
+          this.isEdit ? this.updateMaintain(params) : this.addMaintain(params);
+        }
+      });
+    },
+    addMaintain(params) {
+      api.creatMaintainPlan(params).then(res => {
+        this.$message({
+          message: 'Add maintenance successfully',
+          type: 'success'
+        })
+        this.onBack();
+      }).catch(err => {
+        this.loading.save = false;
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      });
+    },
+    updateMaintain(params) {
+      api.updateMaintainPlan(params).then(res => {
+        this.$message({
+          message: 'Update maintenance successfully',
+          type: 'success'
+        })
+        this.onBack();
+      }).catch(err => {
+        this.loading.save = false;
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      });
+    }
+  }
+}
+</script>
+
+<style scoped="scoped" lang='scss'>
+@import '../../styles/variables.scss';
+
+.card-container {
+  width: 100%;
+  padding: 20px 60px;
+  min-height: $mainAppMinHeight;
+  background-color: #F0F5FC;
+}
+.card-content {
+  margin: 0 8px 16px;
+  padding: 15px 80px;
+  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: 100px;
+  max-width: 300px;
+}
+.add-text ::v-deep .el-textarea__inner {
+  font-family: sans-serif;
+}
+
+.list-item-icon {
+  width: 30px;
+  height: 30px;
+  cursor: pointer;
+  margin: 0 10px 22px;
+}
+.buttons {
+  padding-top: 15px;
+  padding-bottom: 15px;
+}
+
+@media screen and (max-width: 500px) {
+  .card-container {
+    padding: 0px;
+  }
+  .card-content {
+    padding: 15px 40px;
+  }
+}
+.logo-upload {
+  width: 150px;
+  height: 150px;
+  position: relative;
+  border-radius: 6px;
+  background-color: #F8F8F8;
+  ::v-deep .el-image {
+    width: 150px;
+    height: 150px;
+    border-radius: 6px;
+    img {
+      object-fit: contain;
+    }
+  }
+}
+.avatar-uploader-icon {
+  width: 150px;
+  height: 150px;
+  color: #8c939d;
+  cursor: pointer;
+  font-size: 28px;
+  text-align: center;
+  line-height: 150px;
+  border-radius: 6px;
+  border: 1px dashed #d9d9d9;
+}
+</style>

+ 347 - 0
Strides-Admin/src/views/maintain/index.vue

@@ -0,0 +1,347 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container filter-view">
+      <el-select
+        class="filter-view-item"
+        placeholder="Maintenance Type"
+        v-model="params.pageCriteria.maintenanceType"
+        @change="toSearch">
+        <el-option
+          v-for="(item, index) in options.type"
+          :key="index"
+          :label="item"
+          :value="item"/>
+      </el-select>
+      <div style="flex: 1; max-width: 300px;">
+        <el-input
+          class="filter-view-item"
+          v-model="params.pageCriteria.criteria"
+          placeholder="Search by maintenance"
+          prefix-icon="el-icon-search"
+          @keyup.enter.native="toSearch"
+          @change="toSearch"
+          clearable/>
+      </div>
+      <div class="filter-flex-button"></div>
+      <my-upload
+        accept=".xls,.xlsx,.csv"
+        :limit="1"
+        :is-blob="true"
+        :action="action"
+        :headers="headers"
+        :file-list="fileList"
+        :show-file-list="false"
+        :before-upload="onImportStart"
+        :on-success="onImportExcel"
+        :on-error="onImportExcelErr"
+        v-if="!$route.meta.onlyView">
+        <el-button
+          icon="el-icon-upload2"
+          type="primary"
+          :loading="loading.upload">
+          Batch Upload
+        </el-button>
+      </my-upload>
+      <div v-if="!$route.meta.onlyView">
+        <el-button
+          icon="el-icon-download"
+          type="primary"
+          :loading="loading.download"
+          @click="onDownloadTmp">
+          Template
+        </el-button>
+      </div>
+      <el-button
+        type="primary"
+        icon="el-icon-plus"
+        @click="onClickAdd">
+        Create Maintenance
+      </el-button>
+    </div>
+    <el-table
+      v-loading="loading.table"
+      :data="table.data">
+      <el-table-column
+        align="center"
+        label="Maintenance ID"
+        min-width="130">
+        <template slot-scope="{row}" >
+          <span
+            class="link-type"
+            @click="onClickEdit(row)"
+            v-if="!$route.meta.onlyView && row.dataStatus != 'Inactive'">
+            {{ row.downtimeId }}
+          </span>
+          <span v-else>{{ row.downtimeId }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="Maintenance Type"
+        align="center"
+        prop="maintenanceType"
+        min-width="150"/>
+      <el-table-column
+        label="Maintenance Type Category"
+        align="center"
+        prop="maintenanceTypeCategory"
+        min-width="210"/>
+      <el-table-column
+        label="Start Time"
+        align="center"
+        prop="startTime"
+        min-width="120"/>
+      <el-table-column
+        label="End Time"
+        align="center"
+        prop="endTime"
+        min-width="120"/>
+      <el-table-column
+        label="Peak Downtime Min"
+        align="center"
+        prop="peakDowntimeMin"
+        min-width="160"/>
+      <el-table-column
+        label="Off Peak Downtime Min"
+        align="center"
+        prop="offPeakDowntimeMin"
+        min-width="180"/>
+      <el-table-column
+        label="Sent Notification"
+        align="center"
+        prop="notificationSent"
+        min-width="140"/>
+      <el-table-column
+        label="Remarks"
+        align="center"
+        prop="remark"
+        min-width="120"/>
+      <el-table-column
+        label="Reported By"
+        align="center"
+        prop="reportedBy"
+        min-width="120"/>
+      <el-table-column
+        label="Approved By"
+        align="center"
+        prop="approvedBy"
+        min-width="120"/>
+      <el-table-column
+        align="center"
+        label="Action"
+        min-width="70"
+        v-if="!$route.meta.onlyView">
+        <template v-slot="{ row }">
+          <el-dropdown
+            class="action-dropdown"
+            @command="(v) => handleCommand(v, row)"
+            v-if="row.dataStatus != 'Inactive'">
+            <i class="el-icon-more icon-action"></i>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item
+                command="onClickAssign">
+                Assign Connectors
+              </el-dropdown-item>
+              <el-dropdown-item
+                command="onClickEdit">
+                Edit
+              </el-dropdown-item>
+              <el-dropdown-item
+                command="onClickDelete">
+                Delete
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="right">
+      <Pagination
+        v-show="table.total > 0"
+        :total="table.total"
+        :page.sync="params.pageNum"
+        :limit.sync="params.pageSize"
+        @pagination="getTableData" />
+    </div>
+    <AssignmentDialog
+      :visible="assign.visible"
+      title="ASSIGN CONNECTORS"
+      :item="assign.item"
+      @hide="onClickAssign"/>
+  </div>
+</template>
+
+<script>
+import api from '../../api/apiMaintain.js';
+import { baseURL } from '@/http/http'
+import Pagination from '@/components/Pagination';
+import AssignmentDialog from './AssignmentDialog';
+import MyUpload from '@/components/MyUpload'
+import { getToken } from '@/utils/auth'
+export default {
+  data() {
+    return {
+      params: {
+        pageNum: 1,
+        pageSize: 10,
+        pageCriteria: {
+          criteria: "",
+          maintenanceType: ""
+        }
+      },
+      table: {
+        data: [],
+        total: 0
+      },
+      loading: {
+        table: false,
+        upload: false,
+        download: false
+      },
+      options: {
+        type: []
+      },
+      fileList: [],
+      assign: {
+        item: {},
+        visible: false
+      },
+      dialogAction: {
+        id: "",
+        isEdit: false,
+        visible: false
+      }
+    };
+  },
+  components: {Pagination, MyUpload, AssignmentDialog},
+  computed: {
+    action() {
+      return baseURL + process.env.VUE_APP_API_PREFIX + '/dawn/api/v1/batch-create-downtime'
+    },
+    headers() {
+      return {
+        accessToken: getToken()
+      }
+    }
+  },
+  created() {
+    this.getTypeOptions();
+    this.toSearch();
+  },
+  methods: {
+    toSearch() {
+      this.params.pageNum = 1;
+      this.getTableData();
+    },
+    getTypeOptions() {
+      api.getMaintainTypeOptions().then(res => {
+        if (res.data && res.data.length > 0) {
+          this.options.type = res.data;
+        }
+      }).catch(err => {
+        
+      })
+    },
+    getTableData() {
+      this.loading.table = true;
+      api.getMaintainPages(this.params).then(res => {
+        if (res.data && res.data.totalRow) {
+          this.table.data = res.data.records
+          this.table.total = res.data.totalRow
+        } else {
+          this.table.data = []
+          this.table.total = 0
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+        this.table.data = []
+        this.table.total = 0
+      }).finally(() => {
+        this.loading.table = false
+      })
+    },
+    handleCommand(cb, item) {
+      this[cb](item)
+    },
+    onClickAssign(row) {
+      if (row) {
+        this.assign.item = row;
+        this.assign.visible = true;
+      } else {
+        this.assign.item = {};
+        this.assign.visible = false;
+        this.getTableList();
+      }
+    },
+    onClickAdd() {
+      this.$router.push({
+        path: "/incident-management/maintenance-settings/add"
+      })
+    },
+    onClickEdit(row) {
+      this.$router.push({
+        path: "/incident-management/maintenance-settings/" + row.downtimeId
+      })
+    },
+    onImportStart() {
+      this.loading.upload = true;
+    },
+    onImportExcel(res, file, fileList) {
+      fileList = [];
+      this.fileList = [];
+      if (res.success == undefined) {
+        this.downloadExcel(res, "batch-maintenance-result.xls");
+        this.loading.upload = false;
+      } else {
+        this.onImportExcelErr(res.msg, file, fileList);
+      }
+    },
+    onImportExcelErr(err, file, fileList) {
+      this.$message({
+        type: 'error',
+        message: err
+      })
+      this.loading.upload = false;
+    },
+    onDownloadTmp() {
+      this.loading.download = true;
+      api.downloadTemplate().then(res => {
+        this.downloadExcel(res, "batch-maintenance-template.xlsx")
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      }).finally(() => {
+        this.loading.download = false;
+      })
+    },
+    downloadExcel(res, fileName) {
+      const blob = new Blob([res], {
+        type: 'application/vnd.ms-excel;charset=utf-8'
+      })
+      // let href = window.URL.createObjectURL(blob)
+      if ('download' in document.createElement('a')) {
+        // 非IE下载
+        const elink = document.createElement('a')
+        elink.download = fileName
+        elink.style.display = 'none'
+        elink.href = URL.createObjectURL(blob)
+        document.body.appendChild(elink)
+        elink.click()
+        URL.revokeObjectURL(elink.href) // 释放URL 对象
+        document.body.removeChild(elink)
+      } else {
+        // IE10+下载
+        navigator.msSaveBlob(blob, fileName)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 23 - 0
Strides-Admin/src/views/report/ReportV3.vue

@@ -44,6 +44,18 @@
             :value="item.providerPk"
             :key="index"/>
         </el-select>
+        <el-select
+          clearable
+          v-if="hasSiteType.indexOf(params.reportType) >= 0"
+          v-model="params.siteLabelId"
+          class="filter-input"
+          placeholder="Site Label">
+          <el-option
+            v-for="(item, index) in options.siteTag"
+            :key="index"
+            :label="item.siteLabelName"
+            :value="item.siteLabelId"/>
+        </el-select>
         <el-button
           class="filter-input"
           v-if="hasSiteType.indexOf(params.reportType) >= 0"
@@ -180,12 +192,14 @@ export default {
       },
       params: {
         reportType: "",
+        siteLabelId: "",
         sitePks: [],
         groupPks: [],
         providerPks: [],
         yearMonthRange: []
       },
       options: {
+        siteTag: [],
         reportType: [],
         siteOptions: [],
         groupOptions: [],
@@ -221,6 +235,7 @@ export default {
       this.loading.filter = true
       Promise.all([
         this.getReportTypeOptions(),
+        this.getSiteTagOptions(),
         //this.getSiteOptions(),
         this.getServiceProviderList()
       ]).then(() => {
@@ -242,6 +257,13 @@ export default {
         }
       })
     },
+    getSiteTagOptions() {
+      return api.getSiteTagOptions().then(res => {
+        if (res.data) {
+          this.options.siteTag = res.data
+        }
+      })
+    },
     getServiceProviderList() {
       getServiceProviderOptions(list => {
         this.options.serviceProvider = list;
@@ -275,6 +297,7 @@ export default {
       if (this.hasGroupType.indexOf(this.params.reportType) >= 0) {
         this.getGroupOptions();
       }
+      this.params.siteLabelId = "";
       this.params.sitePks = [];
       this.params.groupPks = [];
       this.params.providerPks = [];

+ 29 - 1
Strides-Admin/src/views/site-label/detail.vue

@@ -21,6 +21,24 @@
             maxlength="30"/>
         </el-form-item>
       </div>
+      <div class="form-row">
+        <el-form-item
+          prop="siteLabelType"
+          class="form-item"
+          label="Label Type:">
+          <el-select
+            v-model="form.siteLabelType"
+            placeholder="Label Type"
+            class="flex-item"
+            clearable>
+            <el-option
+              v-for="(item,index) in options"
+              :key="index"
+              :label="item"
+              :value="item"/>
+          </el-select>
+        </el-form-item>
+      </div>
       <div class="form-row">
         <el-form-item
           prop="siteLabelBackground"
@@ -70,6 +88,10 @@ export default {
     id: {
       type: String|Number,
       default: ''
+    },
+    options: {
+      type: Array,
+      default: () => ([])
     }
   },
   data() {
@@ -79,6 +101,7 @@ export default {
       form: {
         siteLabelId: "",
         siteLabelName: "",
+        siteLabelType: "",
         siteLabelBackground: ""
       },
       rules: {
@@ -86,7 +109,12 @@ export default {
           required: true,
           message: "Label name is required",
           trigger: "blur"
-        }, 
+        },
+        siteLabelType: {
+          required: true,
+          message: "Label type is required",
+          trigger: "change"
+        },
         siteLabelBackground: {
           required: true,
           trigger: "change",

+ 31 - 1
Strides-Admin/src/views/site-label/index.vue

@@ -2,6 +2,18 @@
   <div class="app-container">
     <div class="filter-container">
       <div class="filter-view">
+        <el-select
+          v-model="filters.pageVo.siteLabelType"
+          placeholder="Label Type"
+          @change="onClickSearch"
+          class="filter-view-item"
+          clearable>
+          <el-option
+            v-for="(item,index) in typeOptions"
+            :key="index"
+            :label="item"
+            :value="item"/>
+        </el-select>
         <el-input
           class="filter-view-item"
           v-model="filters.pageVo.criteria"
@@ -55,6 +67,11 @@
           <div class="site-label-name" :style='"background-color:" + row.siteLabelBackground + ";"'>{{row.siteLabelName}}</div>
         </template>
       </el-table-column>
+      <el-table-column
+        align="center"
+        label="Label Type"
+        prop="siteLabelType"
+        min-width="100"/>
       <el-table-column
         align="center"
         label="No. of Sites"
@@ -109,6 +126,7 @@
     </div>
     <DialogDetail
       v-bind="dialogAction"
+      :options="typeOptions"
       @hide="hideActionDialog"/>
     <DialogAssignment
       v-bind="dialogAssign"
@@ -130,13 +148,15 @@ export default {
         pageNo: 1,
         pageSize: 10,
         pageVo: {
-          criteria: ""
+          criteria: "",
+          siteLabelType: ""
         }
       },
       table: {
         list: [],
         total: 0
       },
+      typeOptions: [],
       dialogAssign: {
         item: {},
         visible: false
@@ -150,6 +170,7 @@ export default {
   },
   components: { Pagination, TableAction,DialogDetail,DialogAssignment },
   created() {
+    this.getLabelTypeOption();
     this.onClickSearch()
   },
   methods: {
@@ -157,6 +178,15 @@ export default {
       this.filters.pageNo = 1
       this.getTableData()
     },
+    getLabelTypeOption() {
+      api.getLabelTypeOption().then(res => {
+        if (res.data) {
+          this.typeOptions = res.data
+        }
+      }).catch(err => {
+        
+      })
+    },
     getTableData() {
       this.loading = true;
       api.getLabelPages(this.filters).then(res => {

+ 1 - 1
Strides-Admin/src/views/tender/detailPage.vue

@@ -43,7 +43,7 @@
         <audit-view
           v-if="form.tenderPackageId"
           url="dawn/api/v1/tender-package/audit-pages"
-          :params="form"
+          :params="{tenderPackageId: form.tenderPackageId}"
           :audit="form.audit"/>
       </div>
     </el-form>

+ 42 - 7
Strides-Admin/src/views/zetting/MailNotification.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="card-container" v-loading="loading.page">
+  <div class="card-container">
     <div class="flexr">
       <el-form
         :model="form"
@@ -7,7 +7,23 @@
         ref="mailForm"
         label-position="right"
         label-width="110px"
-        class="card-content flex1">
+        class="card-content flex1"
+        v-loading="loading.page">
+        <div class="section-title" style="text-transform: uppercase;">Email Type</div>
+        <el-form-item
+          label="Email Type:"
+          prop="emailType">
+          <el-select
+            class="add-text"
+            v-model="form.emailType"
+            @change="getMailSettings">
+            <el-option
+              v-for="(item, index) in configTypes"
+              :key="index"
+              :label="item"
+              :value="item"/>
+          </el-select>
+        </el-form-item>
         <div class="section-title">
           <span style="padding-right: 30px;text-transform: uppercase;">Mail Notification Settings</span>
           <el-checkbox v-model="form.enable">Enable notifications</el-checkbox>
@@ -95,7 +111,8 @@
         ref="recipForm"
         label-width="90px"
         label-position="right"
-        class="card-content flex1">
+        class="card-content flex1"
+        v-if="form.emailType == 'Internal'">
         <el-form-item
           label="Recipients:"
           prop="recipient">
@@ -120,7 +137,7 @@
             </el-button>
           </div>
         </el-form-item>
-        <div v-loading="loading.table">
+        <div v-loading="loading.table || loading.page">
           <el-table :data="table.list">
             <el-table-column
               align="center"
@@ -211,6 +228,8 @@ export default {
         save: false
       },
       form: {
+        tenantId: "",
+        emailType: "Internal",
         emailId: "",
         enable: false,
         protocol: "smtp",
@@ -218,7 +237,9 @@ export default {
         port: "",
         emailFrom: "",
         username: "",
-        password: ""
+        password: "",
+        enableSsl: true,
+        enableStarttls: true
       },
       filter: {
         pageNum: 1,
@@ -270,6 +291,7 @@ export default {
           message: 'Please type a correct recipient'
         }
       },
+      configTypes: [],
       protocolTypes: [],
       dialogAssign: {
         item: {},
@@ -284,6 +306,13 @@ export default {
   },
   methods: {
     getProtocolTypes() {
+      api.getEmailTypeOptions().then(res => {
+        if (res.data) {
+          this.configTypes = res.data
+        }
+      }).catch(err => {
+        
+      })
       api.getProtocolOptions().then(res => {
         if (res.data) {
           this.protocolTypes = res.data
@@ -293,11 +322,17 @@ export default {
       })
     },
     getMailSettings() {
-      api.getEmailConfiguration().then(res => {
+      this.loading.page = true;
+      api.getEmailConfiguration({
+        emailType: this.form.emailType
+      }).then(res => {
         this.loading.page = false;
         if (res.data) {
+          if (!res.data.emailType) {
+            res.data.emailType = this.form.emailType;
+          }
           this.form = res.data;
-          if (res.data.emailId) {
+          if (res.data.emailId && this.form.emailType == "Internal") {
             this.filter.pageCriteria.emailId = res.data.emailId;
             this.getReceiptTable();
           }

+ 1 - 1
Strides-SPAPP/android/app/src/main/AndroidManifest.xml

@@ -11,7 +11,7 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
+    <!-- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/> -->
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>

+ 2 - 2
Strides-SPAPP/android/app/version.properties

@@ -1,2 +1,2 @@
-#Tue Dec 23 14:41:22 CST 2025
-VERSION_CODE=786
+#Tue Jan 06 14:06:21 CST 2026
+VERSION_CODE=787

+ 3 - 3
Strides-SPAPP/app/pages/chargeV2/SummaryV2.js

@@ -134,7 +134,7 @@ export default class SummaryV2 extends Component {
     if (this.state.summaryInfo?.taxRate) {
       return title.replace("$s", this.state.summaryInfo.taxRate)
     } else {
-      return title;
+      return title.replace(" ($s)", "");
     }
   }
 
@@ -388,7 +388,7 @@ export default class SummaryV2 extends Component {
                 <TextView style={styles.text}>{this.state.summaryInfo.idleFee.duration}</TextView>
               </View>
               <View style={styles.formRow}>
-                <TextView style={styles.label}>{this.getTaxTitle($t('receipt.labelIdleFeeSubtotal2'))}</TextView>
+                <TextView style={styles.label}>{$t('receipt.labelIdleFeeSubtotal3')}</TextView>
                 <TextView style={styles.text}>{this.state.summaryInfo.idleFee.subtotal}</TextView>
               </View>
             </View>
@@ -427,7 +427,7 @@ export default class SummaryV2 extends Component {
               }
               { utils.isNotEmpty(this.state.summaryInfo.payment.idleFeeSubtotal) &&
                 <View style={styles.formRow}>
-                  <TextView style={styles.label}>{this.getTaxTitle($t('receipt.labelIdleFeeSubtotal2'))}</TextView>
+                  <TextView style={styles.label}>{$t('receipt.labelIdleFeeSubtotal3')}</TextView>
                   <TextView style={styles.text}>{this.state.summaryInfo.payment.idleFeeSubtotal}</TextView>
                 </View>
               }

+ 1 - 1
Strides-SPAPP/package.json

@@ -52,7 +52,7 @@
     "react": "18.3.1",
     "react-native": "0.77.0",
     "react-native-device-info": "14.0.4",
-    "react-native-gesture-handler": "2.22.0",
+    "react-native-gesture-handler": "2.25.0",
     "react-native-i18n": "https://gitee.com/vbes/react-native-i18n.git",
     "react-native-image-crop-picker": "0.51.1",
     "react-native-linear-gradient": "2.8.3",

+ 1 - 1
WebApp-Lite/pages/receipt/index.vue

@@ -201,7 +201,7 @@
               </view>
             </template>
             <view class="receipt-section-row" v-if="receiptInfo.operator.operatorUen">
-              <text class="receipt-label2">UEN/BRN:</text>
+              <text class="receipt-label2">Co./GST Reg. No.:</text>
               <text class="receipt-value2">{{receiptInfo.operator.operatorUen}}</text>
             </view>
             <view class="receipt-section-row" v-if="receiptInfo.operator.operatorAddress">

+ 1 - 1
WebApp-Lite/settings.js

@@ -1,5 +1,5 @@
 //是否访问生产环境
-const RELEASE_PROD = true;
+const RELEASE_PROD = false;
 const APP_NAME = "chargEco"; //lumi, chargEco, fpnc, juice
 
 const apiAddress = {