保誠-保戶業務員媒合平台
Jack
2021-11-29 13ae6947a48f4c061b1a6479df889fa2aaf07099
Merge branch 'master' of ssh://192.168.0.10:29418/pcalife/PAM
修改9個檔案
新增5個檔案
717 ■■■■■ 已變更過的檔案
PAMapp/pages/login/index.vue 500 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/plugins/filters/date.filter.ts 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/客戶API/取得個人帳號資訊.txt 8 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/客戶API/更新個人帳號資訊.txt 10 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/domain/Customer.java 30 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java 6 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java 6 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java 21 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/CustomerService.java 43 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/CustomerDTO.java 31 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/mapper/CustomerMapper.java 19 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/CustomerInfoResource.java 33 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/login/index.vue
@@ -24,7 +24,9 @@
                    'is-invalid': !phoneValid
                  }"
                  v-model="phoneNumber"
                  placeholder="請輸入手機號碼">
                  placeholder="請輸入手機號碼"
                  :disabled="showPhoneOtpCodeField"
                >
              <div class="error mt-5 mb-5">
                  <span v-show="!phoneValid">手機號碼格式有誤</span>
              </div>
@@ -38,6 +40,7 @@
                }"
                v-model="email"
                placeholder="請輸入 Email åœ°å€"
                :disabled="showEmailVerifyField"
              >
              <div class="error mt-5 mb-5">
                  <span v-show="!emailValid">Email格式有誤</span>
@@ -51,11 +54,11 @@
              <el-row type="flex" justify="space-between">
                  <div class="mdTxt">輸入驗證碼</div>
                  <div class="otp-count-timer">
                    {{otpCounter}}
                    {{phoneCounter}}
                  </div>
              </el-row>
              <el-row class="mb-30">
              <el-row>
                <input
                  class="pam-input mt-10"
                  :class="{
@@ -65,6 +68,9 @@
                  placeholder="請輸入驗證碼"
                  >
              </el-row>
              <div class="error mt-5 mb-10">
                  <span v-show="otpCounterSec === 0">驗證碼已過期,請重發驗證碼</span>
              </div>
              <el-row>
                <el-button
@@ -72,7 +78,10 @@
                  @click="resentOtp('MOBILE')"
                  icon="icon-arrow"
                >
                  é‡ç™¼é©—證碼<span class="pam-field-title__hint pl-5">({{ otpResendCounter }})</span>
                  é‡ç™¼é©—證碼<span
                    class="pam-field-title__hint pl-5"
                    v-if="otpResendCounter !== 0"
                  >({{ otpResendCounter }})</span>
                </el-button>
              </el-row>
            </div>
@@ -93,14 +102,37 @@
          <!-- email é©—證碼 -->
          <template v-if="connectDevice === 'EMAIL'">
            <el-row v-show="showEmailVerifyField">
              <el-row type="flex" justify="space-between">
                  <div class="mdTxt">輸入驗證碼</div>
                  <div class="otp-count-timer">
                    {{emailOtpCounter}}
                  </div>
              </el-row>
              <el-row>
                <input
                  class="pam-input mt-10"
                  :class="{
                    'is-invalid': !emailOtpCode
                  }"
                  v-model="emailOtpCode"
                  placeholder="請輸入驗證碼"
                  >
              </el-row>
              <div class="error mt-5 mb-10">
                  <span v-show="emailCounterSec === 0">驗證碼已過期,請重發驗證碼</span>
              </div>
              <el-button
                :disabled="!email || emailResendCounter !== 0 || !emailValid"
                icon="icon-arrow"
                @click="resentOtp('EMAIL')"
              >
                é‡ç™¼é©—證碼<span class="pam-field-title__hint pl-5">({{ emailResendCounter }})</span>
                é‡ç™¼é©—證碼<span
                    v-if="emailResendCounter !== 0"
                    class="pam-field-title__hint pl-5"
                  >({{ emailResendCounter }})</span>
              </el-button>
              <div class="mt-10 smTxt_bold text--primary">! è«‹ç¨ç­‰ï¼Œæ–°çš„Email待啟用</div>
            </el-row>
            <el-row v-show="!showEmailVerifyField">
@@ -116,12 +148,12 @@
      </div>
      <el-row type="flex" justify="center" class="pam-login-page__action-bar mt-30">
      <el-row type="flex" justify="center" class="pam-login-page__action-bar mt-30 mb-30">
        <el-button
          type="primary"
          v-if="connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP'"
          :disabled="!otpCode || !phoneNumber || !phoneValid"
          @click="phoneLogin">
          v-if="(connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP') || (connectDevice === 'EMAIL' && onEmailVerifyResendStatus === 'CAN_RESEND')"
          :disabled="isSubmitBtnDisabled"
          @click="login">
          é€å‡º
        </el-button>
      </el-row>
@@ -129,7 +161,7 @@
      <el-dialog
        title="歡迎新使用者"
        :custom-class="'pam-register-dialog'"
        :visible.sync="registerDialogVisable"
        :visible.sync="registerDialogVisible"
        :fullscreen="true"
        :close-on-click-modal="false"
        :show-close="false"
@@ -153,7 +185,8 @@
          <el-row class="pt-10">
            <div
              class="mdTxt pam-register-dialog__contract"
              @scroll="detectContructReadStatus">
              ref="contract"
              @scroll="detectContractReadStatus">
              <h3>蒐集個人資料告知事項</h3>
              <p class="mt-10">
              éµå®ˆå€‹äººè³‡æ–™ä¿è­·æ³•規定,在您提供個人資料予本處前,依法告
@@ -216,15 +249,15 @@
          </el-row>
          <el-row class="pt-30">
            <div class="pam-agree-radio">
              <label for="agreeControct" class="pam-radio"
              <label for="agreeContract" class="pam-radio"
                :class="{disabled: !isReadContract}">
                <input
                  type="radio"
                  id="agreeControct"
                  @click="agreeControct = !agreeControct"
                  id="agreeContract"
                  @click="agreeContract = !agreeContract"
                  :disabled="!isReadContract"
                  value="agreeControct">
                  <i :class="agreeControct ?'icon-checkbox-1': 'icon-checkbox'"></i>我同意並繼續
                  value="agreeContract">
                  <i :class="agreeContract ?'icon-checkbox-1': 'icon-checkbox'"></i>我同意並繼續
              </label>
            </div>
          </el-row>
@@ -232,7 +265,7 @@
        <span slot="footer" class="dialog-footer">
          <el-button
            type="primary"
            :disabled="!name || !agreeControct || !isReadContract"
            :disabled="!name || !agreeContract || !isReadContract"
            @click="applyAccount"
            >建立新帳號
          </el-button>
@@ -288,8 +321,8 @@
<script lang="ts">
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component } from 'vue-property-decorator';
import { LoginRequest, loginVerify, OtpInfo, register, RegisterInfo, sendOtp } from '~/assets/ts/api/consultant';
import { Vue, Component, Ref } from 'vue-property-decorator';
import { LoginRequest, LoginVerify, loginVerify, OtpInfo, register, RegisterInfo, sendOtp } from '~/assets/ts/api/consultant';
import { Role } from '~/assets/ts/models/enum/Role';
const roleStorage = namespace('localStorage');
@@ -298,36 +331,52 @@
export default class Login extends Vue {
  @roleStorage.Mutation storageIdToken!: (token:string) => void;
  @roleStorage.Mutation storageRole!: (role:string) => void;
  @Ref('contract') readonly contract!: any;
  connectDevice: 'MOBILE' | 'EMAIL' = 'MOBILE';
  phoneNumber = '';
  otpCode = '';
  onPhoneVerifyStep: 'APPLY_OTP' | 'INPUT_OTP' | 'SUBMIT_OTP' = 'APPLY_OTP';
  otpCounter = '15:00';
  otpCounterSec = 900;
  otpResendCounter = 30;
  otpInterval: any;
  phoneOtpInfo!: OtpInfo;
  email = '';
  onEmailVerifyResendStatus: 'APPLY_OTP' | 'CAN_RESEND' = 'APPLY_OTP';
  emailCounterSec = 900;
  emailResendCounter = 30;
  emailOtpCode = '';
  emailResendInterval: any;
  emailOtpInfo!: OtpInfo;
  name = '';
  agreeControct = false;
  agreeContract = false;
  isReadContract = false;
  phoneSuccessConfirmVisable = false;
  emailOtpConfirmVisable = false;
  registerDialogVisable = false;
  registerDialogVisible = false;
  registerSuccessConfirmVisable = false;
  applyAccount_onAction = false;
  detectContructReadStatus(event: any): void {
  mounted() {
    const phoneOtpTime = localStorage.getItem('phoneOtpTime');
    const emailOtpTime = localStorage.getItem('emailOtpTime');
    const parsePhoneOtpTime = phoneOtpTime ? JSON.parse(phoneOtpTime) : '';
    const parseEmailOtpTime = emailOtpTime ? JSON.parse(emailOtpTime) : '';
    if (parsePhoneOtpTime && parsePhoneOtpTime.contactType === 'SMS') {
      this.phoneDiffTime(parsePhoneOtpTime);
    }
    if (parseEmailOtpTime && parseEmailOtpTime.contactType === 'EMAIL') {
      this.emailDiffTime(parseEmailOtpTime);
    }
  }
  detectContractReadStatus(event: any): void {
    const scrollTop = Math.round(event.target.scrollTop);
    const height = event.target.scrollHeight - event.target.clientHeight;
    if (Math.floor(scrollTop/10) === (Math.floor(height/10))) {
@@ -335,12 +384,30 @@
    }
  };
  get isSubmitBtnDisabled(): boolean {
    return this.connectDevice === 'MOBILE'
      ? (!this.otpCode || !this.phoneNumber || !this.phoneValid || !this.otpCounterSec)
      : (!this.emailOtpCode || !this.email || !this.emailValid || !this.emailCounterSec)
  }
  get phoneCounter() {
    let min = Math.floor(this.otpCounterSec / 60);
    let sec = Math.floor(this.otpCounterSec % 60);
    return `${min < 10 ? '0' + min : min} : ${sec < 10 ? '0' + sec : sec}`;
  }
  get emailOtpCounter() {
    let min = Math.floor(this.emailCounterSec / 60);
    let sec = Math.floor(this.emailCounterSec % 60);
    return `${min < 10 ? '0' + min : min} : ${sec < 10 ? '0' + sec : sec}`;
  }
  get showPhoneOtpCodeField(): boolean {
    return this.connectDevice === 'MOBILE' && this.onPhoneVerifyStep === 'INPUT_OTP';
  };
  get showEmailVerifyField(): boolean {
    return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus !== 'APPLY_OTP';
    return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus === 'CAN_RESEND';
  };
  get phoneValid() {
@@ -353,6 +420,12 @@
    return this.email ? rule.test(this.email) : true;
  }
  get isLoginBtnDisabled() {
    return this.connectDevice === 'MOBILE'
      ? (!this.otpCode || !this.phoneNumber || !this.phoneValid || !this.otpCounterSec)
      : (!this.emailOtpCode || !this.email || !this.emailValid || !this.emailCounterSec)
  }
  applyOtpVerification(type: string): void {
    const isMobile = this.connectDevice === 'MOBILE';
    const loginInfo: LoginRequest = {
@@ -361,7 +434,9 @@
    }
    sendOtp(loginInfo).then(otpInfo => {
      if (otpInfo.success) {
        this.startOtpCount(type, otpInfo);
        this.storageOtpTime(type, otpInfo);
        this.startOtpSetting(type);
        this.startOtpCount(type);
      }
    });
  };
@@ -370,52 +445,150 @@
    if (type === 'MOBILE') {
      clearInterval(this.otpInterval);
      this.otpResendCounter = 30;
      this.otpCounter = '15:00';
      this.startPhoneCounter();
      this.otpCounterSec = 900;
    } else {
      clearInterval(this.emailResendInterval);
      this.emailResendCounter = 30;
      this.startEmailCounter();
      this.emailCounterSec = 900;
    }
    this.applyOtpVerification(type);
  }
  applyAccount(): void {
    if (this.applyAccount_onAction) {
      return ;
    }
    this.applyAccount_onAction = true;
    const registerInfo = this.setRegisterInfo();
    register(registerInfo).then(res => {
      this.storageIdToken(res.data.id_token);
      this.storageRole(Role.USER);
      this.storagePhoneOrEmail(registerInfo);
      this.registerSuccessConfirmVisable = true;
    }).catch(() => {
      this.applyAccount_onAction = false;
    });
  };
  confirmApplySuccess(): void {
    this.phoneSuccessConfirmVisable = false;
    this.registerSuccessConfirmVisable = false;
    this.$router.go(-1);
  }
  login() {
    const login: LoginVerify = this.setLoginInfo();
    this.removeOtpTime();
    loginVerify(login).then(res => {
      this.storageIdToken(res.data.id_token);
      this.storageRole(Role.USER);
      this.phoneSuccessConfirmVisable = true;
      this.storagePhoneOrEmail(this.setRegisterInfo());
    }).catch(error => {
      if (error.response.status === 401) {
        this.registerDialogVisible = true;
        setTimeout(() => {
          const isScrollBarNeedless = this.contract.scrollHeight <= this.contract.clientHeight;
          if (isScrollBarNeedless) {
            this.isReadContract = true;
          }
        }, 1000);
      }
    })
  }
  destroyed() {
    this.removeOtpTime();
    clearInterval(this.otpInterval);
    clearInterval(this.emailResendInterval);
  }
  private phoneDiffTime(parseOtpTime: any) {
    const diffSecs = this.calcDiffSecs(parseOtpTime.time);
    if (diffSecs < this.otpCounterSec) {
      this.otpResendCounter = diffSecs < 30 ? 30 - diffSecs : 0;
        this.otpCounterSec -= diffSecs;
        this.phoneNumber = parseOtpTime.phone;
        this.onPhoneVerifyStep = 'INPUT_OTP';
        this.phoneOtpInfo = this.setOtpInfo(parseOtpTime);
        this.startOtpCount('MOBILE');
    } else {
      localStorage.removeItem('phoneOtpTime');
    }
  }
  private emailDiffTime(parseOtpTime: any) {
    const diffSecs = this.calcDiffSecs(parseOtpTime.time);
    if (diffSecs < this.emailCounterSec) {
      this.emailResendCounter =  diffSecs < 30 ? 30 - diffSecs : 0;
      this.emailCounterSec -= diffSecs;
      this.email = parseOtpTime.email;
      this.onEmailVerifyResendStatus = 'CAN_RESEND';
      this.emailOtpInfo = this.setOtpInfo(parseOtpTime);
      this.startOtpCount('EMAIL');
    } else {
      localStorage.removeItem('emailOtpTime');
    }
  }
  private calcDiffSecs(parseOtpTime) {
    const currentTime = new Date().getTime();
    const storageTime = new Date(parseOtpTime).getTime();
    return Math.floor((currentTime - storageTime) / 1000);
  }
  private setOtpInfo(parseOtpTime) {
    return {
      indexKey: parseOtpTime.indexKey,
      success: true,
      failCode: '',
      failReason: '',
    }
  }
  private storageOtpTime(type: string, otpInfo: OtpInfo) {
    type === 'MOBILE' ? this.phoneOtpInfo = otpInfo : this.emailOtpInfo = otpInfo;
    const info = {...this.setRegisterInfo(), time: new Date()}
    type === 'MOBILE' ? localStorage.setItem('phoneOtpTime',JSON.stringify(info))
                      : localStorage.setItem('emailOtpTime',JSON.stringify(info));
  }
  private startOtpSetting(type: string) {
    if (type === 'MOBILE') {
      this.onPhoneVerifyStep = 'INPUT_OTP';
    } else {
      this.onEmailVerifyResendStatus = 'CAN_RESEND';
      this.emailOtpConfirmVisable = true;
    }
  }
  private startOtpCount(type: string, otpInfo) {
    type === 'MOBILE' ? this.phoneOtpInfo = otpInfo : this.emailOtpInfo = otpInfo;
    if (type === 'MOBILE') {
        this.onPhoneVerifyStep = 'INPUT_OTP';
        this.startPhoneCounter();
      } else {
        this.onEmailVerifyResendStatus = 'CAN_RESEND';
        this.startEmailCounter();
        this.emailOtpConfirmVisable = true;
      }
  private startOtpCount(type: string) {
    type === 'MOBILE' ? this.startPhoneCounter() : this.startEmailCounter();;
  }
  private startEmailCounter() {
    this.emailResendInterval = setInterval(() => {
      this.emailResendCounter -= 1;
      if (this.emailResendCounter === 0) {
        clearInterval(this.emailResendInterval)
      this.emailCounterSec -= 1;
      if (this.emailResendCounter !== 0) {
        this.emailResendCounter -= 1;
      }
      if (this.emailCounterSec === 0) {
        clearInterval(this.emailResendInterval);
      }
    }, 1000)
  }
  private startPhoneCounter() {
    const minCount = this.otpCounter.split(':');
    let secCount = (+minCount[0] * 60) + (+minCount[1]);
    let min = 0;
    let sec = 0;
    this.otpInterval = setInterval(() => {
      secCount -= 1;
      min = Math.floor(secCount/60);
      sec = Math.floor(secCount%60);
      this.otpCounter = `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`;
      this.otpCounterSec -= 1;
      if (this.otpResendCounter !== 0) {
        this.otpResendCounter -= 1;
      }
      if (secCount === 0) {
      if (this.otpCounterSec === 0) {
        clearInterval(this.otpInterval)
      }
    }, 1000)
@@ -439,86 +612,55 @@
        }
  }
  applyAccount(): void {
    if (this.applyAccount_onAction) {
      return ;
  private storagePhoneOrEmail(registerInfo:RegisterInfo):void{
    const info = {...registerInfo, time: new Date()}
    localStorage.setItem('userInfo',JSON.stringify(info));
  }
  private removeOtpTime() {
    localStorage.removeItem('emailOtpTime');
    localStorage.removeItem('phoneOtpTime');
  }
  private setLoginInfo() {
    const isMobile = this.connectDevice === 'MOBILE'
    return {
      account: isMobile ? this.phoneNumber : this.email,
      indexKey: isMobile ? this.phoneOtpInfo.indexKey : this.emailOtpInfo.indexKey,
      otpCode: isMobile ? this.otpCode : this.emailOtpCode
    }
    this.applyAccount_onAction = true;
    const registerInfo = this.setRegisterInfo();
    this.storagePhoneOrEmail(registerInfo);
    register(registerInfo).then(res => {
      this.storageIdToken(res.data.id_token);
      this.storageRole(Role.USER);
      this.registerSuccessConfirmVisable = true;
    }).catch(() => {
      this.applyAccount_onAction = false;
    });
  };
  storagePhoneOrEmail(registerInfo:RegisterInfo):void{
    localStorage.setItem('userInfo',JSON.stringify(registerInfo));
  }
  confirmApplySuccess(): void {
    this.phoneSuccessConfirmVisable = false;
    this.registerSuccessConfirmVisable = false;
    this.$router.go(-1);
  }
  phoneLogin() {
    const login = {
      account: this.phoneNumber,
      indexKey: this.phoneOtpInfo.indexKey,
      otpCode: this.otpCode
    }
    loginVerify(login).then(res => {
      this.storageIdToken(res.data.id_token);
      this.storageRole(Role.USER);
      this.phoneSuccessConfirmVisable = true;
    }).catch(error => {
      if (error.response.status === 401) {
        this.registerDialogVisable = true;
      }
    })
  }
  destroyed() {
    clearInterval(this.otpInterval);
    clearInterval(this.emailResendInterval);
  }
}
</script>
<style lang="scss">
.pam-login-page {
  font-size: 20px !important;
  display: flex;
  flex-direction: column;
  .pam-login-page__action-bar {
    font-size: 20px !important;
    display: flex;
    flex: 1;
    align-items: flex-end;
    flex-direction: column;
    .pam-login-page__action-bar {
      display: flex;
      flex: 1;
      align-items: flex-end;
    }
  }
}
.pam-input {
  height: 26px;
  width: calc(100% - 36px);
  border-radius: 10px !important;
  padding: 12px 18px !important;
  border:1px solid #CCCCCC;
  outline: 0;
  @extend .text--middle;
  &::placeholder {
    color: $PRUDENTIAL_GREY;
  .pam-input {
    height: 26px;
    width: calc(100% - 36px);
    border-radius: 10px !important;
    padding: 12px 18px !important;
    border:1px solid #CCCCCC;
    outline: 0;
    @extend .text--middle;
    &::placeholder {
      color: $PRUDENTIAL_GREY;
    }
    &.is-invalid {
      border: 1px solid $PRIMARY_RED !important;
      border-radius: 20px;
    }
  }
  &.is-invalid {
    border: 1px solid $PRIMARY_RED !important;
    border-radius: 20px;
  }
}
.pam-register-dialog__contract {
  $DEVICE_EXTRA_HEIGHT: 42px;
@@ -530,63 +672,79 @@
  border-radius: 6px;
  border: 1px solid #707070;
  padding: 20px;
}
.pam-radio {
  color: $PRIMARY_RED;
  align-items: center;
  display: flex;
  font-size: 20px;
  font-weight: bold;
  input {
    display: none;
  }
  i {
    font-size: 27px;
    padding-right: 5px;
  @include desktop {
    height: 335px;
  }
}
.pam-register-dialog {
  padding: 30px 20px;
  display: flex;
  flex-direction: column;
  border-radius: 0;
  &.el-dialog {
    border-radius: 0;
  .pam-register-dialog__contract {
    $DEVICE_EXTRA_HEIGHT: 42px;
    $ALIGN_PADDING: 60px;
    $TOP_CONTENT_HEIGHT: 186px;
    $BOTTOM_CONTENT_HEIGHT: 131px;
    max-height: calc(100vh - $DEVICE_EXTRA_HEIGHT - $ALIGN_PADDING - $TOP_CONTENT_HEIGHT - $BOTTOM_CONTENT_HEIGHT);
    overflow-y: scroll;
    border-radius: 6px;
    border: 1px solid #707070;
    padding: 20px;
  }
  .el-dialog__header {
    padding: 0;
    margin-bottom: 30px;
    .el-dialog__title {
      @extend .subTitle;
  .pam-radio {
    color: $PRIMARY_RED;
    align-items: center;
    display: flex;
    font-size: 20px;
    font-weight: bold;
    input {
      display: none;
    }
    i {
      font-size: 27px;
      padding-right: 5px;
    }
  }
  .el-dialog__body {
    flex: 1;
    padding: 0;
    margin-bottom: 30px;
  .pam-register-dialog {
    padding: 30px 20px;
    display: flex;
    flex-direction: column;
    border-radius: 0;
    &.el-dialog {
      border-radius: 0;
    }
    .el-dialog__header {
      padding: 0;
      margin-bottom: 30px;
      .el-dialog__title {
        @extend .subTitle;
      }
    }
    .el-dialog__body {
      flex: 1;
      padding: 0;
      margin-bottom: 30px;
    }
    .el-dialog__footer {
      padding: 0 !important;
    }
  }
  .el-dialog__footer {
    padding: 0 !important;
  .pam-field-title__hint {
    @extend .smTxt_bold;
    color: #68737A;
  }
}
.pam-field-title__hint {
  @extend .smTxt_bold;
  color: #68737A;
}
  .error {
    @extend .smTxt_bold;
    @extend .text--primary;
    height: 16px;
  }
.error {
  @extend .smTxt_bold;
  @extend .text--primary;
  height: 16px;
}
.pam-popUp-title {
  line-height: 24px;
}
.disabled {
  color: #A7A8AA;
}
  .pam-popUp-title {
      font-size: 20px;
      line-height: 27px;
  }
  .disabled {
    color: #A7A8AA;
  }
</style>
PAMapp/plugins/filters/date.filter.ts
@@ -24,7 +24,7 @@
    return compareDate.getFullYear() === today.getFullYear();
  };
  const minutes = date.getMinutes() > 10 ?  date.getMinutes() : `0${date.getMinutes()}`;
  const minutes = date.getMinutes() > 9 ?  date.getMinutes() : `0${date.getMinutes()}`;
  if (isThisYear(date)) {
    return isToday(date)
pamapi/src/doc/«È¤áAPI/¨ú±o­Ó¤H±b¸¹¸ê°T.txt
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,8 @@
http get: http://localhost:8080/api/customer/info
response body:
{
    "email": null,
    "phone": "0955555555",
    "name": "test999"
}
pamapi/src/doc/«È¤áAPI/§ó·s­Ó¤H±b¸¹¸ê°T.txt
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,10 @@
http put: http://localhost:8080/api/customer/info
request body:
{
    "email": null,
    "phone": "0955555555",
    "name": "test998"
}
response http code: 204 (NO_CONTENT)
pamapi/src/main/java/com/pollex/pam/domain/Customer.java
@@ -4,49 +4,43 @@
import java.time.Instant;
import java.util.Optional;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.pollex.pam.enums.OtpLoginTypeEnum;
@EntityListeners(AuditingEntityListener.class)
@Entity
@Table(name = "customer")
public class Customer implements Serializable {
    /**
     *
     *
     */
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "phone")
    private String phone;
    @Column(name = "email")
    private String email;
    @Enumerated(value = EnumType.STRING)
    @Column(name = "contact_type")
    private OtpLoginTypeEnum contactType;
    @CreatedDate
    @Column(name = "created_date", updatable = false)
    @JsonIgnore
@@ -112,7 +106,7 @@
    public void setContactType(OtpLoginTypeEnum contactType) {
        this.contactType = contactType;
    }
    public String toAccountString() {
        return Optional.ofNullable(getPhone())
                .filter(StringUtils::hasText)
pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java
@@ -1,10 +1,8 @@
package com.pollex.pam.enums;
public enum CustomerDetailEnum {
    ID("CustomerId"),
    NAME("CustomerName"),
    ACCOUNT("CustomerAccount"),
    CONTACT_TYPE("ContactType");
    DB_ID("CustomerDBId"),
    NAME("CustomerName");
    private final String value;
pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java
@@ -107,10 +107,9 @@
        return userDetails.get(ConsultantDetailEnum.AGENT_NO.getValue());
    }
    // todo , should get id from user details
    public static Long getCustomerId() {
    public static Long getCustomerDBId() {
        Map<String, String> userDetails = getCurrentUserDetails();
        return Long.parseLong(userDetails.get(CustomerDetailEnum.ID.getValue()));
        return Long.parseLong(userDetails.get(CustomerDetailEnum.DB_ID.getValue()));
    }
    public static Map<String, String> getCurrentUserDetails() {
pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java
@@ -16,7 +16,6 @@
import com.pollex.pam.service.dto.AppointmentCustomerViewDTO;
import com.pollex.pam.service.mapper.AppointmentCustomerViewMapper;
import com.pollex.pam.service.mapper.AppointmentDTOMapper;
import com.pollex.pam.service.mapper.AppointmentMapper;
import com.pollex.pam.web.rest.errors.AppointmentNotFoundException;
@Service
@@ -40,7 +39,7 @@
    public void customerCreateAppointment(AppointmentCreateDTO appointmentCreateDTO) {
        Appointment appointment = appointmentDTOMapper.toAppointment(appointmentCreateDTO);
        appointment.setCustomerId(SecurityUtils.getCustomerId());
        appointment.setCustomerId(SecurityUtils.getCustomerDBId());
        appointment.setCommunicateStatus(ContactStatusEnum.RESERVED);
        appointmentRepository.save(appointment);
    }
pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
@@ -44,7 +44,7 @@
    AppointmentCustomerViewMapper appointmentCustomerViewMapper;
    public List<ConsultantDTO> getMyConsultantList() {
        Long userId = SecurityUtils.getCustomerId();
        Long userId = SecurityUtils.getCustomerDBId();
        return customerFavoriteConsultantRepository.findAllByCustomerId(userId)
            .stream()
            .map(consultantMapper::toDto)
@@ -81,7 +81,7 @@
    public void addConsultantToCustomList(AddConsultantParam param) {
        List<String> agentNoList = param.getAgentNoList();
        List<Consultant> consultants = consultantRepository.findAllByAgentNoIn(agentNoList);
        Long userId = SecurityUtils.getCustomerId();
        Long userId = SecurityUtils.getCustomerDBId();
        consultants.forEach(consultant -> {
            boolean isConsultantInList = customerFavoriteConsultantRepository.findOneByCustomerIdAndConsultant(userId, consultant).isPresent();
@@ -107,7 +107,7 @@
    }
    public void removeConsultantFromCustomList(String agentNo) {
        Long customId = SecurityUtils.getCustomerId();
        Long customId = SecurityUtils.getCustomerDBId();
        Consultant consultant = consultantRepository.findOneByAgentNo(agentNo).orElseThrow(ConsultantNotFoundException::new);
        CustomerFavoriteConsultant target = customerFavoriteConsultantRepository.findOneByCustomerIdAndConsultant(customId, consultant).orElse(null);
pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java
@@ -25,16 +25,16 @@
@Service
@Transactional
public class CustomerAuthService {
    @Autowired
    AuthenticationManagerBuilder authenticationManagerBuilder;
    @Autowired
    CustomerRepository customerRepository;
    @Autowired
    TokenProvider tokenProvider;
    public String authorize(Customer account, String indexKey, String otpCode) {
        OtpAccount otpAccount = OtpAccount.createOtpAccount(account, indexKey);
        OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(
@@ -43,26 +43,25 @@
        );
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        Authentication authentication = buildCustomerAuthToken(account, otpCode, indexKey);
        String jwt = tokenProvider.createToken(authentication, false);
        return jwt;
    }
    public UsernamePasswordAuthenticationToken buildCustomerAuthToken(Customer customer
            , String otpCode, String indexKey) {
        List<GrantedAuthority> grantedAuths = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
        final String account = customer.toAccountString();
        UsernamePasswordAuthenticationToken authenticationToken =
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(account, otpCode, grantedAuths);
        Map<String, String> details = new HashMap<>();
        details.put(CustomerDetailEnum.ID.getValue(), customer.getId().toString());
        details.put(CustomerDetailEnum.DB_ID.getValue(), customer.getId().toString());
        details.put(CustomerDetailEnum.NAME.getValue(), customer.getName());
        details.put(CustomerDetailEnum.ACCOUNT.getValue(), account);
//        details.put(CustomerDetailEnum.CONTACT_TYPE.getValue(), customer.getContactType());
        authenticationToken.setDetails(details);
        return authenticationToken;
pamapi/src/main/java/com/pollex/pam/service/CustomerService.java
@@ -2,6 +2,9 @@
import java.util.Optional;
import com.pollex.pam.security.SecurityUtils;
import com.pollex.pam.service.dto.CustomerDTO;
import com.pollex.pam.service.mapper.CustomerMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@@ -18,31 +21,34 @@
@Service
@Transactional
public class CustomerService {
    @Autowired
    CustomerRepository customerRepository;
    @Autowired
    CustomerDTOMapper customerDTOMapper;
    @Autowired
    CustomerAuthService customerAuthService;
    @Autowired
    OtpTmpService otpTmpService;
    @Autowired
    CustomerMapper customerMapper;
    public Customer save(Customer customer) {
        return customerRepository.save(customer);
    }
    public Customer registerCustomer(CustomerRegisterDTO registDTO) {
        boolean isCustomerExist = checkCustomerExist(registDTO);
        if(isCustomerExist) {
            throw new UsernameAlreadyUsedException();
        }else {
            String account = getCustomerAccount(registDTO);
            OtpTmp otpTmp = otpTmpService.findByAccountAndIndexKey(account, registDTO.getIndexKey());
            if(otpTmp.getStatus() == OtpTmpStatusEnum.VERRIFIED) {
                Customer customer = customerDTOMapper.toCustomer(registDTO);
@@ -54,9 +60,28 @@
                        + " => status: " + otpTmp.getStatus());
            }
        }
    }
    public void updateLoggedCustomer(CustomerDTO customerDTO) {
        Long customerId = SecurityUtils.getCustomerDBId();
        Customer customer = customerRepository.findById(customerId)
            .orElseThrow(() -> new UsernameNotFoundException("customerId which is from token is not found in customer db table, customer id = " + customerId));
        customer.setEmail(customerDTO.getEmail());
        customer.setPhone(customerDTO.getPhone());
        customer.setName(customerDTO.getName());
        customerRepository.save(customer);
    }
    public CustomerDTO getLoggedCustomerDTO() {
        Long customerId = SecurityUtils.getCustomerDBId();
        Customer customer = customerRepository.findById(customerId)
            .orElseThrow(() -> new UsernameNotFoundException("customerId which is from token is not found in customer db table, customer id = " + customerId));
        return customerMapper.toDto(customer);
    }
    private String getCustomerAccount(CustomerRegisterDTO registDTO) {
        return registDTO.getContactType() == OtpLoginTypeEnum.EMAIL?registDTO.getEmail():registDTO.getPhone();
    }
pamapi/src/main/java/com/pollex/pam/service/dto/CustomerDTO.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,31 @@
package com.pollex.pam.service.dto;
public class CustomerDTO {
    private String email;
    private String phone;
    private String name;
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
pamapi/src/main/java/com/pollex/pam/service/mapper/CustomerMapper.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,19 @@
package com.pollex.pam.service.mapper;
import com.pollex.pam.domain.Customer;
import com.pollex.pam.service.dto.CustomerDTO;
import org.springframework.stereotype.Service;
@Service
public class CustomerMapper {
    public CustomerDTO toDto(Customer customer) {
        CustomerDTO dto = new CustomerDTO();
        dto.setEmail(customer.getEmail());
        dto.setPhone(customer.getPhone());
        dto.setName(customer.getName());
        return dto;
    }
}
pamapi/src/main/java/com/pollex/pam/web/rest/CustomerInfoResource.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,33 @@
package com.pollex.pam.web.rest;
import com.pollex.pam.service.CustomerService;
import com.pollex.pam.service.dto.CustomerDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/customer/info")
public class CustomerInfoResource {
    @Autowired
    CustomerService customerService;
    @GetMapping("")
    public ResponseEntity<CustomerDTO> getLoggedCustomerInfo() {
        return new ResponseEntity<>(customerService.getLoggedCustomerDTO(), HttpStatus.OK);
    }
    @PutMapping("")
    public ResponseEntity<Void> updateLoggedCustomerInfo(@RequestBody CustomerDTO customerDTO) {
        boolean hasEmail = StringUtils.hasText(customerDTO.getEmail());
        boolean hasPhone = StringUtils.hasText(customerDTO.getPhone());
        Assert.isTrue(hasEmail || hasPhone, "the email and the phone both are empty!");
        customerService.updateLoggedCustomer(customerDTO);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}