保誠-保戶業務員媒合平台
Tomas
2022-01-21 a7b45caf5b3784f65ba82793d87f5ffb202fca1e
update#134555: [顧問] 預約單結案/編輯結案功能串接
修改6個檔案
新增3個檔案
371 ■■■■■ 已變更過的檔案
PAMapp/components/Appointment/AppointmentClosedInfo.vue 59 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Client/ClientCard.vue 11 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/close/index.vue 108 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/index.vue 77 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myAppointmentList/closedList.vue 16 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/plugins/filters/appointment-fail-reason.filter.ts 16 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/const/appointment-fail-reason-list.ts 26 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/appointment.model.ts 56 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/appointment.service.ts 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Appointment/AppointmentClosedInfo.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,59 @@
<template>
    <section class="close-appointment-detail">
        <div class="close-appointment-detail-nav">
          <div class="mdTxt">結案方式</div>
          <div class="mdTxt text--primary text--underline cursor--pointer" @click="editAppointmentHasClosed">編輯</div>
        </div>
        <span class="mt-10 mb-30">{{ displayClosedType }}</span>
        <template v-if="appointmentDetail.appointmentClosedInfo.policyholderIdentityId">
          <div class="mdTxt mb-10">保戶身份證字號</div>
          <div class="mb-30">{{ appointmentDetail.appointmentClosedInfo.policyholderIdentityId }}</div>
        </template>
        <template v-if="appointmentDetail.appointmentClosedInfo.planCode">
          <div class="mdTxt mb-10">商品代碼Plan Code</div>
          <div class="mb-30">{{ appointmentDetail.appointmentClosedInfo.planCode }}</div>
        </template>
        <template v-if="appointmentDetail.appointmentClosedInfo.closedReason">
          <div class="mdTxt mb-10">未成交原因</div>
          <div >{{ appointmentDetail.appointmentClosedInfo.closedReason | toFailReasonLabel }}</div>
          <div v-if="appointmentDetail.appointmentClosedInfo.closedOtherReason" class="mt-10">{{ appointmentDetail.appointmentClosedInfo.closedOtherReason }}</div>
          <div class="mb-30"></div>
        </template>
        <template v-if="appointmentDetail.appointmentClosedInfo.policyEntryDate">
          <div class="mdTxt mb-10">進件時間</div>
          <div class="mb-30">{{ appointmentDetail.appointmentClosedInfo.policyEntryDate | formatDate }}</div>
        </template>
        <div class="mdTxt mb-10">備註</div>
        <div>{{ appointmentDetail.appointmentClosedInfo.remark || '無' }}</div>
    </section>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator';
import { Appointment } from '~/shared/models/appointment.model';
@Component
export default class AppointmentRecordList extends Vue {
    @Prop()
    appointmentDetail!: Appointment;
    //////////////////////////////////////////////////////////////////////
    editAppointmentHasClosed(): void{
      this.$router.push(`/appointment/${this.appointmentDetail.id}/close`);
    }
}
</script>
<style lang="scss" scoped>
</style>
PAMapp/components/Client/ClientCard.vue
@@ -257,6 +257,7 @@
    viewAppointmentDetail(): void {
      this.getAppointmentDetail(this.client.id).then((_) => {
        this.readAppointment();
        this.$router.push(`/appointment/${this.client.id}`);
      });
    }
@@ -289,7 +290,13 @@
    }
    closeInformDialog(): void {
      const unread = !this.client.consultantReadTime;
        this.readAppointment();
        this.isEdit = false;
        this.clearAppointmentIdFromMsg();
    }
    private readAppointment(): void {
        const unread = !this.client.consultantReadTime;
        if (unread) {
            appointmentService.recordRead(this.client.id).then((_) => {
                const updatedClient = {...this.client};
@@ -297,8 +304,6 @@
                this.updateMyAppointmentList(updatedClient);
            });
        };
        this.isEdit = false;
        this.clearAppointmentIdFromMsg();
    }
    private clearAppointmentIdFromMsg() {
PAMapp/pages/appointment/_appointmentId/close/index.vue
@@ -11,7 +11,7 @@
        </UiField>
      </el-row>
      <template v-if="appointmentCloseInfo.selectCloseOption === 'done'">
      <template v-if="appointmentCloseInfo.selectCloseOption === contactStatus.DONE">
        <el-row
          type="flex"
          class="pam-paragraph" style="flex-direction: column">
@@ -43,18 +43,20 @@
        <el-row
          type="flex"
          class="pam-paragraph">
          <UiField label="進件時間" :labelSize="20" class="required">
            <DateTimePicker  @changeDateTime="appointmentCloseDate = $event"></DateTimePicker>
          <UiField label="進件時間" :labelSize="20">
            <DateTimePicker
              :defaultValue="appointmentCloseInfo.policyEntryDate"
              @changeDateTime="appointmentCloseInfo.policyEntryDate = $event"></DateTimePicker>
          </UiField>
        </el-row>
      </template>
      <template v-if="appointmentCloseInfo.selectCloseOption === 'close'">
      <template v-if="appointmentCloseInfo.selectCloseOption === contactStatus.CLOSE">
        <el-row
          class="pam-paragraph">
          <UiField label="未成交原因" :labelSize="20" class="required">
          </UiField>
            <div class="appointment-client-detail-close__selectbox">
            <div class="appointment-client-detail-close__select-box">
              <select
                class="appointment-client-detail-close__select"
                name="closedReason" id="closedReason" v-model="appointmentCloseInfo.closedReason">
@@ -64,8 +66,7 @@
              </select>
              <i class="icon-down down-icon"></i>
            </div>
            <div style="display: flex" class="mt-10">
            <div class="mt-10">
              <input
                v-if="appointmentCloseInfo.closedReason === 'other'
                    || appointmentCloseInfo.closedReason === 'no_suitable_commodity'"
@@ -88,16 +89,6 @@
            v-model="appointmentCloseInfo.remark"
            resize="none">
          </el-input>
          <!-- <textarea
            v-model="appointmentCloseInfo.archivedDate"
            class="appointment-close__remark"
            placeholder="請輸入"
            name="remark"
            id="remark"
            wrap="off"
            rows="3">
          </textarea> -->
        </UiField>
      </el-row>
@@ -123,9 +114,10 @@
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component } from 'vue-property-decorator';
import { Appointment, ToCloseAppointment, ToDoneAppointment } from '~/shared/models/appointment.model';
import { ContactStatus } from '~/shared/models/enum/contact-status';
import appointmentService from '~/shared/services/appointment.service';
import { appointmentFailReasonList } from '~/shared/const/appointment-fail-reason-list';
import { ContactStatus } from '~/shared/models/enum/contact-status';
const appointmentStore = namespace('appointment.store');
@@ -133,7 +125,10 @@
export default class AppointmentDetailCloseComponent extends Vue {
  @appointmentStore.Action
  updateAppointmentDetail!: () => Appointment;
  updateAppointmentDetail!: (appointmentId: number) => Appointment;
  @appointmentStore.State('appointmentDetail')
  appointmentDetail!: Appointment;
  contactStatus = ContactStatus;
@@ -147,58 +142,60 @@
    policyEntryDate       : this.appointmentCloseDate,
    policyholderIdentityId: '',
    remark                : '',
    selectCloseOption     : 'done',
    selectCloseOption     : this.contactStatus.DONE,
  };
  closeOptions = [
    {
      title:'成交',
      label: 'done',
      label: this.contactStatus.DONE,
    },
    {
      title:'未成交',
      label: 'close',
      label: this.contactStatus.CLOSE,
    }
  ];
  appointmentFailReason = [
    {
      key: '無法聯繫客戶',
      value: 'cannot_to_contact_customer'
    },
    {
      key: '單純諮詢',
      value: 'only_consultation'
    },
    {
      key: '無合適商品',
      value: 'no_suitable_commodity'
    },
    {
      key: '核保問題- é«”況、財務、職業',
      value: 'prohibited_factors'
    },
    {
      key: '經濟因素',
      value: 'economy'
    },
    {
      key: '其他',
      value: 'other'
    },
  ];
  appointmentFailReason = appointmentFailReasonList;
  //////////////////////////////////////////////////////////////////////
  mounted() {
    const appointmentId = +this.$route.params.appointmentId;
    const closedInfo = this.appointmentDetail.appointmentClosedInfo;
    if (this.appointmentDetail.id === appointmentId
        && (this.appointmentDetail.communicateStatus === this.contactStatus.DONE
        || this.appointmentDetail.communicateStatus === this.contactStatus.CLOSE
        || this.appointmentDetail.communicateStatus === this.contactStatus.CANCEL)
        ) {
        this.appointmentCloseInfo = {
        closedOtherReason     : closedInfo?.closedOtherReason,
        closedReason          : closedInfo?.closedReason,
        planCode              : closedInfo?.planCode,
        policyEntryDate       : closedInfo?.policyEntryDate,
        policyholderIdentityId: closedInfo?.policyholderIdentityId,
        remark                : closedInfo?.remark,
        selectCloseOption     : this.appointmentDetail.communicateStatus === this.contactStatus.DONE
                                ? this.contactStatus.DONE
                                : this.contactStatus.CLOSE
      };
    }
  }
  //////////////////////////////////////////////////////////////////////
  closeAppointment(): void {
    const appointmentId = +this.$route.params.appointmentId;
    if (this.appointmentCloseInfo.selectCloseOption === 'done') {
    if (this.appointmentCloseInfo.selectCloseOption === this.contactStatus.DONE) {
      const toDoneAppointment: ToDoneAppointment = {
        appointmentId         : appointmentId,
        contactStatus         : this.contactStatus.DONE,
        planCode              : this.appointmentCloseInfo.planCode,
        policyEntryDate       : this.appointmentCloseInfo.policyEntryDate,
        policyholderIdentityId: this.appointmentCloseInfo.policyholderIdentityId,
        remark                : this.appointmentCloseInfo.remark,
      }
      appointmentService.closeAppointment(toDoneAppointment).then((res) => res);
      appointmentService.closeAppointment(toDoneAppointment).then((_) => this.updateAppointmentDetail(appointmentId));
      this.isShowSuccessAlert = true;
    } else {
      const toCloseAppointment: ToCloseAppointment = {
@@ -208,7 +205,7 @@
        contactStatus    : this.contactStatus.CLOSE,
        remark           : this.appointmentCloseInfo.remark,
      }
      appointmentService.closeAppointment(toCloseAppointment).then((res) => res);
      appointmentService.closeAppointment(toCloseAppointment).then((_) => this.updateAppointmentDetail(appointmentId));
      this.isShowSuccessAlert = true;
    }
  }
@@ -224,11 +221,12 @@
      policyholderIdentityId,
      planCode,
      closedReason,
      closedOtherReason
      closedOtherReason,
      remark
    } = this.appointmentCloseInfo;
    // this.appointmentCloseInfo.policyEntryDate ä¸¦æ²’有辦法取值到 this.appointmentCloseDate
    if (selectCloseOption === 'done') {
      return !policyholderIdentityId || !this.identityIdValid || !planCode || !this.appointmentCloseDate
    if (selectCloseOption === this.contactStatus.DONE) {
      return !policyholderIdentityId || !this.identityIdValid || !planCode || !this.appointmentCloseInfo.policyEntryDate || !remark
    } else if (closedReason === 'other' || closedReason === 'no_suitable_commodity') {
      return !closedOtherReason
    }
@@ -261,7 +259,7 @@
    border-color: $PRIMARY_RED !important;
  }
}
.appointment-client-detail-close__selectbox {
.appointment-client-detail-close__select-box {
  position: relative;
  & .appointment-client-detail-close__select{
PAMapp/pages/appointment/_appointmentId/index.vue
@@ -31,15 +31,22 @@
      </div>
      <div class="client-detail-demand mt-10">
        <div class="client-detail-demand__demand-list">
        <div class="client-detail-demand__demand-list mb-10">
          <div class="client-detail-demand__demand-list-label">需求</div>
          <div>{{ appointmentDetail.requirement }}</div>
          <div class="client-detail-demand__demand-list-content">{{ appointmentDetail.requirement }}</div>
        </div>
        <div class="client-detail-demand__hope-contact-time">
        <div class="client-detail-demand__demand-list">
          <div class="client-detail-demand__demand-list-label">聯絡<br />時段</div>
          <div>星期一 17:00 ~ 19:00</div>
          <!-- TODO: å¤šç­†è¯çµ¡æ™‚段如何呈現 && è·‘版問題 [Tomas. 2021/1/12] -->
          <!-- <div>{{ appointmentDetail.hopeContactTime }}</div> -->
          <div class="client-detail-demand__demand-list-content">
            <div v-for="(hopeContactTime, index) in hopeContactTimeList" :key="index"
              :class="{'mt-10': index > 0, 'pb-10': true, 'hope-contact-time__line': index + 1 < hopeContactTimeList.length }">
              <div v-for="(item, index) in getHopeContactTimeContent(hopeContactTime)" :key="index" :class="{'mt-10': index < 0 }">
                {{ item }}
              </div>
            </div>
          </div>
        </div>
      </div>
@@ -54,24 +61,9 @@
    </section>
    <section class="close-appointment-detail" v-if="showWhenAppointmentHasClosed">
        <div class="close-appointment-detail-nav">
          <div class="mdTxt">結案方式</div>
          <div class="mdTxt text--primary text--underline cursor--pointer" @click="editAppointmentHasClosed">編輯</div>
        </div>
        <span class="mt-10 mb-30">成交</span>
        <div class="mdTxt mb-10">保戶身份證字號</div>
        <div class="mb-30">A123456789</div>
        <div class="mdTxt mb-10">商品代碼Plan Code</div>
        <div class="mb-30">8888888</div>
        <div class="mdTxt mb-10">進件時間</div>
        <div class="mb-30">2021/12/2</div>
        <div class="mdTxt mb-10">備註</div>
        <div class="">約訪4次,客戶很喜歡聊寶可夢。</div>
    </section>
    <template v-if="showWhenAppointmentHasClosed">
      <AppointmentClosedInfo :appointmentDetail="appointmentDetail" />
    </template>
    <InterviewMsg
      :isVisible.sync="isVisibleDialog"
@@ -131,6 +123,28 @@
    return this.appointmentDetail.communicateStatus === this.contactStatus.DONE
        || this.appointmentDetail.communicateStatus === this.contactStatus.CLOSE
        || this.appointmentDetail.communicateStatus === this.contactStatus.CANCEL;
  }
  get displayClosedType(): string {
    let closedType = '成交';
    switch (this.appointmentDetail.communicateStatus) {
      case this.contactStatus.CLOSE:
        closedType = '未成交';
        break;
      case this.contactStatus.CANCEL:
        closedType = '取消';
        break;
    }
    return closedType;
  }
  get hopeContactTimeList(): any[] {
    return this.appointmentDetail.hopeContactTime.split("','")
  }
  getHopeContactTimeContent(hopeContactTimeString: string): string[] {
    const result = hopeContactTimeString.replace("'", '').split('、');
    return result;
  }
}
</script>
@@ -195,9 +209,17 @@
        display: flex;
      }
      .client-detail-demand__demand-list-label {
        @extend .mr-10;
        @extend .mdTxt;
        @extend .mb-10;
        @extend .mdTxt;
        @extend .mr-10;
        color     : $DARK_BLUE;
        flex-basis: auto;
        min-width : 40px;
      }
      .client-detail-demand__demand-list-content {
        text-align: justify;
        text-justify: auto;
        word-break: break-all;
      }
    }
    .client-detail-action {
@@ -219,5 +241,8 @@
  justify-content: space-between;
  flex: 1;
}
.hope-contact-time__line {
  border-bottom: 1px solid #CCCCCC;
}
</style>
PAMapp/pages/myAppointmentList/closedList.vue
@@ -34,7 +34,7 @@
</template>
<script lang="ts">
import { Vue, Component, Watch, State, namespace } from 'nuxt-property-decorator';
import { Vue, Component, Watch, namespace } from 'nuxt-property-decorator';
import { Appointment } from '~/shared/models/appointment.model';
import { ContactStatus } from '~/shared/models/enum/contact-status';
@@ -52,14 +52,16 @@
    @localStorage.Getter
    currentAppointmentIdFromMsg!: string;
    closedItemSum = 0;
    closedList: Appointment[] = [];
    contactStatus= ContactStatus;
    currentPage  : number = 1;
    doneItemSum = 0;
    closedItemSum = 0;
    currentPage   = 1;
    doneItemSum   = 0;
    itemSum       = 0;
    keyWord       = '';
    closedList: Appointment[] = [];
    filterList   : Appointment[] = [];
    itemSum = 0;
    keyWord      : string       = '';
    pageList     : Appointment[] = [];
    selectedClosedCategory: 'all' | 'done' | 'closed' = 'all';
PAMapp/plugins/filters/appointment-fail-reason.filter.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,16 @@
import Vue from 'vue'
import { appointmentFailReasonList } from '~/shared/const/appointment-fail-reason-list';
Vue.filter('toFailReasonLabel', (value: string): string => {
  if (!value ||  typeof value !== 'string') {
    return '--';
  };
  let failReasonLabel = {};
  appointmentFailReasonList.forEach((failReason) => {
    failReasonLabel[failReason.value] = failReason.key;
  });
  return  failReasonLabel[value];
})
PAMapp/shared/const/appointment-fail-reason-list.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,26 @@
export const appointmentFailReasonList = [
  {
    key: '無法聯繫客戶',
    value: 'cannot_to_contact_customer'
  },
  {
    key: '單純諮詢',
    value: 'only_consultation'
  },
  {
    key: '無合適商品',
    value: 'no_suitable_commodity'
  },
  {
    key: '核保問題- é«”況、財務、職業',
    value: 'prohibited_factors'
  },
  {
    key: '經濟因素',
    value: 'economy'
  },
  {
    key: '其他',
    value: 'other'
  },
];
PAMapp/shared/models/appointment.model.ts
@@ -14,31 +14,43 @@
}
export interface Appointment {
  age                : string;
  agentNo            : string;
  appointmentDate    : string;
  appointmentMemoList: AppointmentMemoInfo[]
  communicateStatus  : ContactStatus;
  consultantReadTime : string;
  consultantViewTime : string;
  contactTime        : string;
  contactType        : string;
  customerId         : number;
  email              : string;
  gender             : string;
  hopeContactTime    : string;
  id                 : number;
  interviewRecordDTOs: InterviewRecord[];
  job                : string;
  lastModifiedDate   : string;
  name               : string;
  otherRequirement   : string;
  phone              : string;
  requirement        : string;
  satisfactionScore  : number;
  age                  : string;
  agentNo              : string;
  appointmentClosedInfo: AppointmentClosedInfo;
  appointmentDate      : string;
  appointmentMemoList  : AppointmentMemoInfo[]
  appointmentNoticeLogs: NoticeLogs[];
  communicateStatus    : ContactStatus;
  consultantReadTime   : string;
  consultantViewTime   : string;
  contactTime          : string;
  contactType          : string;
  customerId           : number;
  email                : string;
  gender               : string;
  hopeContactTime      : string;
  id                   : number;
  interviewRecordDTOs  : InterviewRecord[];
  job                  : string;
  lastModifiedDate     : string;
  name                 : string;
  otherRequirement     : string;
  phone                : string;
  requirement          : string;
  satisfactionScore    : number;
};
export interface AppointmentClosedInfo {
  appointmentId         : number;
  closedOtherReason     : string;
  closedReason          : string;
  id                    : number;
  planCode              : string;
  policyEntryDate       : string;
  policyholderIdentityId: string;
  remark                : string;
}
export interface AppointmentMemoInfo {
  appointmentId: number;
  content      : string;
PAMapp/shared/services/appointment.service.ts
@@ -55,7 +55,7 @@
    return http.delete(`/appointment/memo/${appointmentMemoId}`)
  }
  // é ç´„單結案
  // é ç´„單結案, æ›´æ–°çµæ¡ˆæ˜Žç´°
  async closeAppointment(appointmentInfo: ToDoneAppointment | ToCloseAppointment) {
    return http.post(`/appointment/close`, appointmentInfo).then((res) => res.data);
  }