保誠-保戶業務員媒合平台
HelenHuang
2022-01-20 f5831acafe510f9a79f8e2ecf6dee09026d67fc6
Merge branch 'Phase3' of https://dev.pollex.com.tw:8443/r/pcalife/PAM into Phase3
修改32個檔案
新增6個檔案
修改1個檔案名稱
1162 ■■■■■ 已變更過的檔案
PAMapp/components/Appointment/AppointmentInterviewList.vue 133 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Appointment/AppointmentRecordList.vue 103 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/BackActionBar.vue 24 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewAdd.vue 72 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewCard.vue 145 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewMsg.vue 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewRecordCard.vue 96 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiDateFormat.vue 8 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/close/index.vue 40 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/index.vue 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/interviewList/index.vue 38 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/recordList/index.vue 32 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myAppointmentList/contactedList.vue 41 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/appointment.model.ts 15 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/reviews.service.ts 8 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/appointment.store.ts 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/index.ts 31 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/滿意度/客戶填寫滿意度.txt 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/預約單/取得預約單細節API.txt 38 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/預約單/結案API.txt 4 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/appointment/process/AppointmentProcess.java 26 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/appointment/process/AppointmentProcessInterface.java 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/appointment/process/ClosedProcess.java 31 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/appointment/process/DoneProcess.java 35 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/domain/Appointment.java 2 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/repository/SatisfactionRepository.java 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java 21 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java 21 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/SatisfactionService.java 55 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/AppointmentCustomerViewDTO.java 10 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/SatisfactionCustomerScoreDTO.java 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/mapper/AppointmentCustomerViewMapper.java 13 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/mapper/AppointmentMapper.java 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/mapper/AppointmentNoticeSendMapper.java 8 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/mapper/SatisfactionDTOMapper.java 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java 48 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/SatisfactionResource.java 8 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/errors/SatisfactionAlreadyExistException.java 13 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/errors/SatisfactionNotFoundException.java 13 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Appointment/AppointmentInterviewList.vue
@@ -5,78 +5,16 @@
          <div class="pam-link-button--lg"
          @click="addInterview">+新增</div>
      </div>
        <InterviewCard :interviewList="displayList.slice(0, 3)"></InterviewCard>
      <template v-if="!interviewList.length">
          <div class="record-card record-card--empty">
            ç„¡ç´„訪紀錄
          </div>
      </template>
      <template v-if="interviewList.length">
        <div
            v-for="(item, index) in futureList"
            :key="index + 'feature'"
            class="interview--future"
            @click="editInterview(item)"
        >
            <div class="record-card">
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        </div>
        <section
            class="interview--past"
            v-for="(item, index) in pastList"
            :key="index + 'past'"
            @click="editInterview(item)"
        >
            <div class="record-card">
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        <section class="text--right mt-30" v-if="interviewList.length > 3">
                <div class="pam-link-button--lg" @click="readMoreBtn">展開看更多</div>
        </section>
        <section class="more-log-action">
                <div class="pam-link-button--lg">展開看更多</div>
        </section>
      </template>
    </div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch, Mutation } from 'nuxt-property-decorator';
import { Vue, Component, Prop, Watch } from 'nuxt-property-decorator';
import { InterviewRecord } from '~/shared/models/appointment.model';
@Component
@@ -84,13 +22,8 @@
  @Prop()
  interviewList!: InterviewRecord[];
  @Mutation
  updateInterviewRecord!: (data: InterviewRecord) => void;
  appointmentId!: string;
  futureList: InterviewRecord[] = [];
  pastList: InterviewRecord[] = [];
  displayList: InterviewRecord[] = [];
  //////////////////////////////////////////////////////////////////////
@@ -103,10 +36,9 @@
  @Watch('interviewList', {immediate: true})
  updateInterviewList() {
      if (this.interviewList && this.interviewList.length > 0) {
          this.futureList = this.interviewList
            .filter(item => new Date(item.interviewDate).getTime() >= new Date().getTime())
          this.pastList = this.interviewList
            .filter(item =>  new Date(item.interviewDate).getTime() < new Date().getTime());
          this.displayList = this.interviewList
            .map((i) => ({ ...i, sortDate: new Date(i.interviewDate)}))
            .sort((preItem, nextItem) => +nextItem.sortDate - +preItem.sortDate);
      }
  }
@@ -116,9 +48,8 @@
    this.$router.push(`/appointment/${this.appointmentId}/interview/new`);
  }
  editInterview(interviewRecord) {
    this.updateInterviewRecord(interviewRecord);
    this.$router.push(`/appointment/${this.appointmentId}/interview/${interviewRecord.id}`);
  readMoreBtn() {
      this.$router.push(`/appointment/${this.appointmentId}/interviewList`);
  }
}
@@ -129,49 +60,5 @@
  display        : flex;
  justify-content: space-between;
  margin-bottom  : 10px;
}
.interview--future{
    border-bottom: 1px solid #CCCCCC;
    padding-bottom: 17px;
    margin-bottom: 17px;
    .record{
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
    }
}
.record-card {
    height: 62px;
    border: 1px solid #707070;
    border-radius: 5px;
    display: flex;
    border-bottom: 1px solid #000;
    .record-card-date{
        display: flex;
        flex-direction: column;
        margin-left: 10px;
        margin-right: 10px;
        margin-top: 10px;
    }
    .record-card-content{
        height: 42px;
        margin-top: 10px;
        margin-right: 10px;
        line-height: 1.2;
    }
  &.record-card--empty {
    align-items     : center;
    background-color: #fff;
    color           : $MID_GREY;
    justify-content : center;
  }
}
.line-space{
    letter-spacing: 1px;
}
.more-log-action{
    margin-top: 30px;
    display: flex;
    justify-content:flex-end;
}
</style>
PAMapp/components/Appointment/AppointmentRecordList.vue
@@ -2,42 +2,11 @@
    <div class="record-log-component">
        <div class="mdTxt mt-30 mb-10">系統通知紀錄</div>
            <div v-for="(item, index) in displayLogs"
                :key="index">
                <section
                    class="record-log-card"
                >
                    <div class="record-log-card-date-container">
                        <div class="record-log-card-date-container-circle">
                            <div class="xxsTxt bold line-height">{{item.createdDate | formatYear}}</div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt bold line-height"
                                    :date="item.createdDate"
                                    onlyShowSection="DAY" />
                            </div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt mt-4 line-space"
                                    :date="item.createdDate"
                                    onlyShowSection="TIME" />
                            </div>
                        </div>
                    </div>
                        <div class="record-log-msg">
                            <div>發送約訪通知
                                <span v-if="item.email && item.phone">(手機簡訊、Email)</span>
                                <span v-else-if="item.email">(Email)</span>
                                <span v-else>(手機簡訊)</span>
                            </div>
                            <div class="mt-10">預約{{item.interviewDate | formatDate}}</div>
                        </div>
                </section>
                <div class="time-line"></div>
            </div>
            <InterviewRecordCard :noticeLogsList="displayLogs.slice(0, 3)"></InterviewRecordCard>
            <section class="more-log-action">
            <section class="text--right mt-30" v-if="displayLogs.length > 3">
                <div class="pam-link-button--lg"
                @click="readMoreBtn"
                >展開看更多</div>
            </section>
    </div>
@@ -47,15 +16,7 @@
import { Vue, Component, Prop, Watch } from 'nuxt-property-decorator';
import { NoticeLogs } from '~/shared/models/appointment.model';
@Component({
    filters: {
        formatYear(value) {
            if (value) {
                return new Date(value).getFullYear();
            }
        }
    }
})
@Component
export default class AppointmentRecordList extends Vue {
    @Prop()
@@ -63,6 +24,8 @@
    appointmentId: string       = '';
    displayLogs  : NoticeLogs[] = [];
    //////////////////////////////////////////////////////////////////////
    mounted() {
        this.appointmentId = this.$route.params.appointmentId;
@@ -79,53 +42,11 @@
      }
    }
    //////////////////////////////////////////////////////////////////////
    readMoreBtn() {
        this.$router.push(`/appointment/${this.appointmentId}/recordList`);
    }
}
</script>
<style lang="scss" scoped>
.record-log-component{
    display: flex;
    flex-direction: column;
    .record-log-card{
        display: flex;
        .record-log-card-date-container{
            position:relative;
            .record-log-card-date-container-circle{
                display: flex;
                flex-direction: column;
                width: 56px;
                height: 56px;
                border-radius: 50%;
                border:1px solid $PRIMARY_BLACK;
                justify-content: center;
                align-items: center;
                align-content: center;
            }
        }
    }
}
.mt-4{
    margin-top: 4px;
}
.line-space{
    letter-spacing: 1px;
}
.line-height{
    line-height:1.2;
}
.time-line{
    border-left: 1px solid black;
    height: 30px;
    margin-left: 28px;
}
.record-log-msg{
    margin-left: 13px;
    margin-top: 10px;
}
.more-log-action{
    display: flex;
    justify-content:flex-end;
}
</style>
PAMapp/components/BackActionBar.vue
@@ -14,6 +14,7 @@
import * as _ from 'lodash';
import { Role } from '~/shared/models/enum/Role';
const appointmentStore = namespace('appointment.store');
const roleStorage = namespace('localStorage');
@Component
@@ -22,11 +23,16 @@
  @roleStorage.Getter
  currentRole!:string;
  @appointmentStore.Getter
  isCloseAppointment!: boolean;
  //////////////////////////////////////////////////////////////////////
  goBack(): void {
    const pathName = this.$route.name;
    pathName?.includes('myConsultantList') ? this.$router.push('/') : this.$router.go(-1);
    pathName?.includes('myConsultantList')
      ? this.$router.push('/')
      : this.$router.go(-1);
  }
  get label(): string {
@@ -78,11 +84,21 @@
          featureLabel = 'F&Q å¸¸è¦‹å•é¡Œ';
          break;
        case 'appointment':
          const appointmentFeatureLabel = this.$route.name.includes('close') ? '結案' : '預約資訊';
          const appointmentFeatureLabel = this.$route.name.includes('close')
                                                            ? '結案'
                                                            : this.isCloseAppointment ? '結案明細' : '預約資訊';
          const inInterview = this.$route.name.includes('interview');
          const addNewInterview = this.$route.name.includes('new');
          if (inInterview) {
            featureLabel = addNewInterview ? '新增約訪紀錄' : '編輯約訪紀錄';
          const interviewList = this.$route.name.includes('interviewList');
          const recordList = this.$route.name.includes('recordList');
          if (interviewList) {
            featureLabel = '約訪紀錄';
          } else if (recordList) {
            featureLabel = '系統通知紀錄';
          } else if (inInterview) {
            featureLabel = addNewInterview
                  ? '新增約訪紀錄'
                  : '編輯約訪紀錄';
          } else {
            featureLabel = appointmentFeatureLabel;
          }
PAMapp/components/Interview/InterviewAdd.vue
@@ -1,6 +1,6 @@
<template>
  <div class="edit-appointment-record">
      <div class="edit-appointment-record-date" v-if="interviewId">
      <div class="edit-appointment-record-date" v-if="interviewId && interviewRecord">
          <span>{{interviewRecord.createdDate | formatDate}} å»ºç«‹</span>
          <span>{{interviewRecord.lastModifiedDate | formatDate}} æ›´æ–°</span>
      </div>
@@ -85,25 +85,26 @@
      <InterviewMsg
        :isVisible.sync="showInterviewMsgPopup"
        :client="appointmentDetail"
        @closeDialog="closePopup"
      ></InterviewMsg>
  </div>
</template>
<script lang="ts">
import { InterviewRecord, InterviewRecordInfo } from '~/shared/models/appointment.model';
import { Vue, Component, Prop, State, Mutation, Watch, Action } from 'nuxt-property-decorator';
import { Appointment, InterviewRecord, InterviewRecordInfo } from '~/shared/models/appointment.model';
import { Vue, Component, Watch, namespace } from 'nuxt-property-decorator';
import appointmentService from '~/shared/services/appointment.service';
const appointmentStore = namespace('appointment.store');
@Component
export default class InterviewAdd extends Vue {
    @State
    interviewRecord!: InterviewRecord;
    @Mutation
    updateInterviewRecord!: (data: InterviewRecord) => void;
    @appointmentStore.State
    appointmentDetail!: Appointment;
    @Mutation
    clearInterviewRecord!: () => void;
    @appointmentStore.Action
    updateAppointmentDetail!: (id: number) => Appointment;
    interviewTime = '';
    content = '';
@@ -121,38 +122,29 @@
    defaultValue = '';
    interviewRecord!: InterviewRecord;
    ////////////////////////////////////////////////////////////////////
    mounted() {
        this.interviewId = this.$route.params.interviewId;
        this.appointmentId = this.$route.params.appointmentId;
        const isEditPage = this.interviewId && this.interviewRecord;
        if (isEditPage) {
            this.checkInterviewRecord();
        }
    }
    private checkInterviewRecord() {
        if (this.interviewRecord.appointmentId !== +this.appointmentId
                || this.interviewRecord.id !== +this.interviewId) {
            appointmentService.getAppointmentDetail(+this.appointmentId).then((data) => {
                const currentInterviewRecord = data.interviewRecordDTOs.filter(item => item.id === +this.interviewId)[0];
                this.updateInterviewRecord(currentInterviewRecord);
            })
        }
    }
    destroyed() {
        this.clearInterviewRecord();
        this.onAppointmentDetailChange();
    }
    ////////////////////////////////////////////////////////////////////
    @Watch('interviewRecord', {immediate: true})
    watchInterviewRecord() {
        if (this.interviewRecord && this.interviewRecord.content) {
    @Watch('appointmentDetail', {immediate: true})
    onAppointmentDetailChange() {
        if (this.appointmentDetail && this.appointmentDetail.id === +this.appointmentId) {
            this.interviewRecord = this.appointmentDetail.interviewRecordDTOs
                .filter(item => item.id === +this.interviewId)[0];
                if (this.interviewRecord && this.interviewId) {
            this.content = this.interviewRecord.content;
            this.defaultValue = this.interviewRecord.interviewDate;
                }
        }
    }
@@ -177,19 +169,20 @@
    private createdRecord(interviewRecordInfo) {
        appointmentService.createInterviewRecord(interviewRecordInfo).then(res => {
            this.confirmTxt = '新增成功'
            this.showPopUp();
            this.showPopUp('新增成功');
        });
    }
    private updateRecord(updateInterviewRecord) {
        appointmentService.updateInterviewRecord(updateInterviewRecord).then(res => {
            this.confirmTxt = '編輯成功';
            this.showPopUp();
            this.showPopUp('編輯成功');
        });
    }
    private showPopUp() {
    private showPopUp(confirmTxt) {
        this.confirmTxt = confirmTxt;
        this.updateAppointmentDetail(+this.appointmentId);
        if (new Date(this.interviewTime).getTime() >= new Date().getTime()) {
            this.showFutureDateConfirmPopup = true;
        } else {
@@ -197,14 +190,11 @@
        }
    }
    closePopup() {
        this.$router.push(`/appointment/${this.appointmentId}`);
    }
    deleteInterviewRecord() {
        appointmentService.deleteInterviewRecord(this.interviewId).then(res => {
            this.confirmTxt = '刪除成功';
            this.showConfirmPopup = true;
            this.updateAppointmentDetail(+this.appointmentId);
        });
    }
@@ -214,10 +204,14 @@
           this.defaultValue = this.interviewRecord.interviewDate;
           this.isEdit = false;
        } else {
           this.$router.push(`/appointment/${this.appointmentId}`);
           this.$router.go(-1);
        }
    }
    closePopup() {
        this.$router.go(-1);
    }
    ////////////////////////////////////////////////////////////////////
    get formatInterviewDate() {
PAMapp/components/Interview/InterviewCard.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,145 @@
<template>
    <div>
       <template v-if="!interviewList.length">
          <div class="record-card record-card--empty">
            ç„¡ç´„訪紀錄
          </div>
      </template>
      <template v-else>
        <div class="interview--future">
            <div class="record-card mb-10"
                v-for="(item, index) in futureList"
                :key="index + 'feature'"
                @click="editInterview(item)"
            >
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        </div>
        <section class="interview--past" v-if="pastList.length">
            <div class="record-card mb-10"
                v-for="(item, index) in pastList"
                :key="index + 'past'"
                @click="editInterview(item)"
            >
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        </section>
      </template>
    </div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "nuxt-property-decorator";
import { InterviewRecord } from "~/shared/models/appointment.model";
@Component
export default class InterviewCard extends Vue {
    @Prop()
    interviewList!: InterviewRecord[];
    futureList: InterviewRecord[] = [];
    pastList: InterviewRecord[] = [];
    appointmentId!: number;
    mounted() {
        this.appointmentId = +this.$route.params.appointmentId;
    }
    @Watch('interviewList', {immediate: true})
    onInterviewListChange() {
        if (this.interviewList.length > 0) {
            this.futureList = this.interviewList
            .filter(item => new Date(item.interviewDate).getTime() >= new Date().getTime())
            .sort((preItem, nextItem) => +new Date(nextItem.interviewDate) - +new Date(preItem.interviewDate));
          this.pastList = this.interviewList
            .filter(item =>  new Date(item.interviewDate).getTime() < new Date().getTime())
            .sort((preItem, nextItem) => +new Date(nextItem.interviewDate) - +new Date(preItem.interviewDate));
        }
    }
    editInterview(interviewRecord) {
        this.$router.push(`/appointment/${this.appointmentId}/interview/${interviewRecord.id}`);
    }
}
</script>
<style lang="scss" scoped>
.interview--future{
    .record{
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
    }
}
.interview--past {
    border-top: 1px solid #CCCCCC;
    padding-top: 17px;
    margin-top: 17px;
}
.record-card {
    height: 62px;
    border: 1px solid #707070;
    border-radius: 5px;
    display: flex;
    border-bottom: 1px solid #000;
    .record-card-date{
        display: flex;
        flex-direction: column;
        margin-left: 10px;
        margin-right: 10px;
        margin-top: 10px;
    }
    .record-card-content{
        height: 42px;
        margin-top: 10px;
        margin-right: 10px;
        line-height: 1.2;
    }
  &.record-card--empty {
    align-items     : center;
    background-color: #fff;
    color           : $MID_GREY;
    justify-content : center;
  }
}
.line-space{
    letter-spacing: 1px;
}
</style>
PAMapp/components/Interview/InterviewMsg.vue
@@ -50,12 +50,16 @@
import { AgentInfo } from '~/shared/models/agent-info.model';
const loginStore = namespace('login.store');
const appointmentStore = namespace('appointment.store');
@Component
export default class InterviewMsg extends Vue {
    @Action
    storeMyAppointmentList!: () => Promise<number>;
    @appointmentStore.Action
    updateAppointmentDetail!: (id: number) => Appointment;
    @PropSync('isVisible')
    dialogVisible!: boolean;
@@ -94,6 +98,7 @@
      };
      appointmentService.informAppointment(appointmentInformation).then((_) => {
        this.isShowSuccessAlert = true ;
        this.updateAppointmentDetail(this.client.id);
      });
    }
PAMapp/components/Interview/InterviewRecordCard.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,96 @@
<template>
    <div class="record-log-component">
        <div v-for="(item, index) in noticeLogsList"
                :key="index">
                <section
                    class="record-log-card"
                >
                    <div class="record-log-card-date-container">
                        <div class="record-log-card-date-container-circle">
                            <div>
                                <UiDateFormat
                                    class="xxsTxt bold line-height"
                                    :date="item.createdDate"
                                    onlyShowSection="YEAR" />
                            </div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt bold line-height"
                                    :date="item.createdDate"
                                    onlyShowSection="DATE" />
                            </div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt mt-4 line-space"
                                    :date="item.createdDate"
                                    onlyShowSection="TIME" />
                            </div>
                        </div>
                    </div>
                        <div class="record-log-msg">
                            <div>發送約訪通知
                                <span v-if="item.email && item.phone">(手機簡訊、Email)</span>
                                <span v-else-if="item.email">(Email)</span>
                                <span v-else>(手機簡訊)</span>
                            </div>
                            <div class="mt-10">預約{{item.interviewDate | formatDate}}</div>
                        </div>
                </section>
                <div class="time-line"></div>
            </div>
    </div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "nuxt-property-decorator";
import { NoticeLogs } from "~/shared/models/appointment.model";
@Component
export default class RecordCard extends Vue {
    @Prop()
    noticeLogsList!: NoticeLogs[];
}
</script>
<style lang="scss" scoped>
.record-log-component{
    display: flex;
    flex-direction: column;
    .record-log-card{
        display: flex;
        .record-log-card-date-container{
            position:relative;
            .record-log-card-date-container-circle{
                display: flex;
                flex-direction: column;
                width: 56px;
                height: 56px;
                border-radius: 50%;
                border:1px solid $PRIMARY_BLACK;
                justify-content: center;
                align-items: center;
                align-content: center;
            }
        }
    }
}
.mt-4{
    margin-top: 4px;
}
.line-space{
    letter-spacing: 1px;
}
.line-height{
    line-height:1.2;
}
.time-line{
    border-left: 1px solid black;
    height: 30px;
    margin-left: 28px;
}
.record-log-msg{
    margin-left: 13px;
    margin-top: 10px;
}
</style>
PAMapp/components/Ui/UiDateFormat.vue
@@ -13,7 +13,7 @@
    date!: Date | string;
    @Prop()
    onlyShowSection!: 'DAY' | 'TIME';
    onlyShowSection!: 'YEAR' | 'DATE' | 'DAY' | 'TIME';
    compareTarget!: Date;
    displayValue = '';
@@ -46,6 +46,12 @@
                        this.displayValue = isThisYear(compareTarget) ? thisYearDayLabel : `${compareTarget.getFullYear()}/${compareTarget.getMonth() + 1}/${compareTarget.getDate()}`;
                    } else if (this.onlyShowSection === 'TIME') {
                        this.displayValue = `${compareTarget.getHours()}:${minutes}`;
                    } else if (this.onlyShowSection === 'DATE') {
                        this.displayValue = isThisYear(compareTarget)
                            ? thisYearDayLabel
                            : `${compareTarget.getMonth() + 1}/${compareTarget.getDate()}`;
                    } else if (this.onlyShowSection === 'YEAR') {
                        this.displayValue = `${compareTarget.getFullYear()}`;
                    }
                    if (this.onlyShowSection) return;
PAMapp/pages/appointment/_appointmentId/close/index.vue
@@ -14,14 +14,18 @@
      <template v-if="appointmentCloseInfo.selectCloseOption === 'done'">
        <el-row
          type="flex"
          class="pam-paragraph">
          class="pam-paragraph" style="flex-direction: column">
          <UiField label="保戶身分證字號" :labelSize="20">
            <input
              class="appointment-client-detail-close__input"
              :class="{'is-invalid':!identityIdValid}"
              v-model="appointmentCloseInfo.policyholderIdentityId"
              placeholder="請輸入"
              type="text">
          </UiField>
          <div class="error mt-5 mb-5" v-show="!identityIdValid">
            <span>身分證字號格式有誤</span>
          </div>
        </el-row>
        <el-row
@@ -100,7 +104,7 @@
        justify="center"
        class="pam-paragraph">
        <el-button @click="$router.go(-1)">取消</el-button>
        <el-button @click="closeAppointment">確認</el-button>
        <el-button @click="closeAppointment" :disabled="isSubmitBtnDisabled">確認</el-button>
      </el-row>
      <PopUpFrame :isOpen.sync="isShowSuccessAlert">
@@ -212,6 +216,30 @@
    this.$router.push(`/myAppointmentList/contactedList`);
  }
  get isSubmitBtnDisabled() {
    const {
      selectCloseOption,
      policyholderIdentityId,
      planCode,
      remark,
      closedReason,
      closedOtherReason
    } = this.appointmentCloseInfo;
    // this.appointmentCloseInfo.policyEntryDate ä¸¦æ²’有辦法取值到 this.appointmentCloseDate
    if (selectCloseOption === 'done') {
      return !policyholderIdentityId || !this.identityIdValid || !planCode || !this.appointmentCloseDate || !remark
    } else if (closedReason === 'other' || closedReason === 'no_suitable_commodity') {
      return !closedOtherReason || !remark
    }
    return !remark
  }
  get identityIdValid() {
    const rule = /^[A-Z]\d{9}$/;
    const identityId = this.appointmentCloseInfo.policyholderIdentityId;
    return identityId ? rule.test(identityId) : true;
  }
}
</script>
@@ -227,10 +255,18 @@
  &::placeholder {
    color: $MID_GREY;
  }
  &.is-invalid {
    border-color: $PRIMARY_RED !important;
  }
}
.invite-review{
    display: flex;
    flex-direction: column;
    align-items: center;
  }
 .error {
    @extend .smTxt_bold;
    @extend .text--primary;
    height: 16px;
  }
</style>
PAMapp/pages/appointment/_appointmentId/index.vue
@@ -91,12 +91,9 @@
</template>
<script lang="ts">
import { Context } from '@nuxt/types';
import { Vue, Component } from 'vue-property-decorator';
import { namespace } from 'nuxt-property-decorator';
import appointmentService from '~/shared/services/appointment.service';
import { Appointment } from '~/shared/models/appointment.model';
import { ContactStatus } from '~/shared/models/enum/contact-status';
PAMapp/pages/appointment/_appointmentId/interviewList/index.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,38 @@
<template>
    <div>
        <div class="text--right mb-30">
            <div class="pam-link-button--lg"
            @click="addInterview">+新增</div>
        </div>
        <InterviewCard :interviewList="appointmentDetail.interviewRecordDTOs"></InterviewCard>
    </div>
</template>
<script lang="ts">
import { Component, namespace, Vue } from "nuxt-property-decorator";
import { Appointment } from "~/shared/models/appointment.model";
const appointmentStore = namespace('appointment.store');
@Component
export default class InterviewList extends Vue {
    @appointmentStore.State
    appointmentDetail!: Appointment;
    appointmentId!: number;
    ////////////////////////////////////////////////////////
    mounted() {
      this.appointmentId = +this.$route.params.appointmentId;
    }
    ////////////////////////////////////////////////////////
    addInterview(): void {
        this.$router.push(`/appointment/${this.appointmentId}/interview/new`);
    }
}
</script>
PAMapp/pages/appointment/_appointmentId/recordList/index.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,32 @@
<template>
    <InterviewRecordCard :noticeLogsList="displayLogs"></InterviewRecordCard>
</template>
<script lang="ts">
import { Component, namespace, Vue, Watch } from "nuxt-property-decorator";
import { Appointment, NoticeLogs } from "~/shared/models/appointment.model";
const appointmentStore = namespace('appointment.store');
@Component
export default class RecordList extends Vue {
    @appointmentStore.State
    appointmentDetail!: Appointment;
    displayLogs: NoticeLogs[] = [];
    ////////////////////////////////////////////////////////
    @Watch('appointmentDetail', {immediate: true})
    onAppointmentDetailChange() {
      if (this.appointmentDetail?.appointmentNoticeLogs.length) {
        this.displayLogs = this.appointmentDetail?.appointmentNoticeLogs
                            .map((i) => ({ ...i, sortDate: new Date(i.createdDate)}))
                            .sort((preItem, nextItem) => +nextItem.sortDate - +preItem.sortDate);
      }
    }
}
</script>
PAMapp/pages/myAppointmentList/contactedList.vue
@@ -14,12 +14,17 @@
            ></i>
        </el-input>
        <div class="sort-indicator mb-10 text--primary text--underline cursor--pointer" @click="changeSortType">
          {{ this.sortType === 'DESC' ? '新->舊' : '舊->新' }}
        </div>
        <ClientList
            :clients="pageList"
            :title="'contactedList'"
        ></ClientList>
        <UiPagination
            v-if="togglePagination"
            :totalList="filterList"
            :currentPage="currentPage"
            @changePage="changePage"
@@ -28,7 +33,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';
@@ -46,10 +51,12 @@
    currentAppointmentIdFromMsg!: string;
    contactedList: Appointment[] = [];
    currentPage      : number        = 1;
    filterList   : Appointment[] = [];
    keyWord      : string       = '';
    pageList     : Appointment[] = [];
    currentPage  : number = 1;
    sortType        : 'ASC' | 'DESC' = 'DESC';
    togglePagination: boolean        = true;
    //////////////////////////////////////////////////////////////////////
@@ -61,16 +68,32 @@
    @Watch('myAppointmentList')
    onMyAppointmentListChange() {
      this.setContactedList();
    }
    @Watch('sortType')
    onSortTypeChange() {
      this.togglePagination = false;
      setTimeout(() => {
      this.togglePagination = true;
        this.currentPage = 1;
        this.setContactedList();
      }, 0)
    }
    private setContactedList(): void {
        this.contactedList = (this.myAppointmentList || [])
            .filter(item => item.communicateStatus === 'contacted')
            .map((item) => ({...item, sortTime: new Date(item.contactTime)}))
            .sort((prevItem, nextItem) => +nextItem.sortTime - +prevItem.sortTime);
            .map((item) => ({...item, sortTime: new Date(item.lastModifiedDate)}))
            .sort((prevItem, nextItem) => {
              return this.sortType === 'DESC' ? +nextItem.sortTime - +prevItem.sortTime : +prevItem.sortTime - +nextItem.sortTime
            });
        this.filterList = this.contactedList;
        this.getCurrentPage();
    }
    private getCurrentPage() {
    private getCurrentPage(): void {
        const currentIndex = this.filterList.findIndex(item => item.id === +this.currentAppointmentIdFromMsg);
        const pageSize = 5;
        if (currentIndex > -1) {
@@ -90,5 +113,13 @@
        this.pageList = pageList;
    }
    changeSortType(): void {
      if (this.sortType === 'DESC') {
        this.sortType = 'ASC';
      } else {
        this.sortType = 'DESC';
      }
    }
}
</script>
PAMapp/shared/models/appointment.model.ts
@@ -1,15 +1,16 @@
import { ContactStatus } from "./enum/contact-status";
export interface AppointmentLog {
    id              : number;
    createdDate     : string;
    lastModifiedDate: string;
    customerId      : number;
    agentNo         : string;
    status          : 'UNFILLED' | 'FILLED';
    score           : number;
    agentName       : string;
    agentNo         : string;
    appointmentId   : number;
    createdDate     : string;
    customerId      : number;
    customerName    : string;
    id              : number;
    lastModifiedDate: string;
    score           : number;
    status          : 'UNFILLED' | 'FILLED';
}
export interface Appointment {
PAMapp/shared/services/reviews.service.ts
@@ -7,12 +7,18 @@
  //客戶進行滿意度評分
  userReviewsConsultants(data: UserReviewsConsultantsParams) {
    return http.post('/satisfaction/create', data );
    return http.post('/satisfaction/score', data );
  }
  //取得所有評分紀錄
  async getMyReviewLog(): Promise<AppointmentLog[]> {
    return http.get('/satisfaction/getMySatisfaction').then(res => res.data);
  }
  // é¡§å•ä¸»å‹•發送滿意度通知
  sendSatisfactionToClient(appointmentId: number): void {
    http.post('/consultant/sendSatisfactionToClient/').then((res) => res);
  }
}
export default new ReviewsService();
PAMapp/store/appointment.store.ts
@@ -35,6 +35,11 @@
    .filter(item => item.communicateStatus === this.contactStatus.DONE || item.communicateStatus === this.contactStatus.CLOSE ).length;
  }
  get isCloseAppointment(): boolean {
    const closedStatusList = [this.contactStatus.DONE, this.contactStatus.CLOSE, this.contactStatus.CANCEL];
    return closedStatusList.includes(this.appointmentDetail!.communicateStatus);
  }
  //////////////////////////////////////////////////////////////////////
  @Mutation
PAMapp/store/index.ts
@@ -5,7 +5,7 @@
import reviewsService from '~/shared/services/reviews.service';
import { Consultant } from '~/shared/models/consultant.model';
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/shared/storageConsultant';
import { AppointmentLog, InterviewRecord } from '~/shared/models/appointment.model';
import { AppointmentLog } from '~/shared/models/appointment.model';
import { AgentOfStrictQuery, StrictQueryParams } from '~/shared/models/strict-query.model';
@Module
@@ -15,16 +15,6 @@
    myConsultantList: Consultant[] = [];
    myAppointmentReviewLogList: AppointmentLog[] = [];
    interviewRecord: InterviewRecord = {
        appointmentId   : 0,
        content         : '',
        createdBy       : '',
        createdDate     : '',
        id              : 0,
        interviewDate   : '',
        lastModifiedBy  : '',
        lastModifiedDate: ''
    }
    get isUserLogin() {
        return this.context.getters['localStorage/isUserLogin'];
@@ -48,25 +38,6 @@
    @Mutation
    updateMyAppointmentReviewLog(data: AppointmentLog[]) {
        this.myAppointmentReviewLogList = data;
    }
    @Mutation
    updateInterviewRecord(data: InterviewRecord) {
        this.interviewRecord = data;
    }
    @Mutation
    clearInterviewRecord() {
        this.interviewRecord = {
            appointmentId   : 0,
            content         : '',
            createdBy       : '',
            createdDate     : '',
            id              : 0,
            interviewDate   : '',
            lastModifiedBy  : '',
            lastModifiedDate: ''
        }
    }
    @Action
pamapi/src/doc/º¡·N«×/«È¤á¶ñ¼gº¡·N«×.txt
@@ -1,6 +1,6 @@
http post :
http://localhost:8080/api/satisfaction/create
http://localhost:8080/api/satisfaction/score
pamapi/src/doc/¹w¬ù³æ/¨ú±o¹w¬ù³æ²Ó¸`API.txt
@@ -4,10 +4,11 @@
appointmentNoticeLogs: é ç´„單發送通知的歷程
appointmentClosedInfo: é ç´„單結案資料
response body:
[ {
{
      "id" : 385,
      "phone" : "0911223344",
      "email" : "SDD",
@@ -16,11 +17,11 @@
      "age" : "21-30",
      "job" : "內勤",
      "requirement" : "健康與保障",
      "communicateStatus" : "contacted",
    "communicateStatus": "done",
      "hopeContactTime" : "'星期一,星期二,星期三,星期四,星期五,星期六,星期日、9:00~12:00,12:00~14:00,14:00~18:00,18:00~21:00'",
      "otherRequirement" : null,
      "appointmentDate" : "2021-12-16T07:11:05.400Z",
      "lastModifiedDate" : "2021-12-28T07:16:37.004Z",
    "lastModifiedDate": "2022-01-19T10:57:51.380Z",
      "agentNo" : "A568420",
      "customerId" : 139,
      "name" : "Angula-test",
@@ -30,19 +31,34 @@
      "satisfactionScore" : null,
      "appointmentMemoList" : [ ],
      "interviewRecordDTOs" : [ ],
      "appointmentNoticeLogs" : [ {
        "id" : 1,
    "appointmentNoticeLogs": [
        {
            "id": 4,
        "phone" : "0912345678",
        "email" : "pollex@gmail.com",
        "appointmentId" : 385,
        "content" : "notice customer invterview time",
        "createdDate" : "2022-01-11T08:54:35.651Z"
      }, {
        "id" : 2,
            "createdDate": "2022-01-11T09:33:57.754Z",
            "interviewDate": null
        },
        {
            "id": 6,
        "phone" : "0912345678",
        "email" : "pollex@gmail.com",
        "appointmentId" : 385,
        "content" : "notice customer invterview time",
        "createdDate" : "2022-01-11T08:57:23.133Z"
      } ]
} ]
            "createdDate": "2022-01-19T10:38:42.187Z",
            "interviewDate": "2022-11-01T08:00:00.000+00:00"
        }
    ],
    "appointmentClosedInfo": {
        "id": 9,
        "policyholderIdentityId": "A123456789",
        "planCode": "ATMdd",
        "policyEntryDate": "2022-01-12T00:00:00.000+00:00",
        "remark": "test remark",
        "closedReason": "other2",
        "closedOtherReason": "心情不好不想買2",
        "appointmentId": 385
    }
}
pamapi/src/doc/¹w¬ù³æ/µ²®×API.txt
@@ -1,9 +1,7 @@
http post : 
新增結案明細:
新增結案明細, æ›´æ–°çµæ¡ˆæ˜Žç´°:
http://localhost:8080/api/appointment/close
更新結案明細:
http://localhost:8080/api/appointment/close/info/edit
request body :
pamapi/src/main/java/com/pollex/pam/appointment/process/AppointmentProcess.java
@@ -1,5 +1,6 @@
package com.pollex.pam.appointment.process;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
@@ -8,6 +9,7 @@
import com.pollex.pam.domain.Appointment;
import com.pollex.pam.domain.AppointmentClosedInfo;
import com.pollex.pam.enums.ContactStatusEnum;
import com.pollex.pam.repository.AppointmentClosedInfoRepository;
import com.pollex.pam.repository.AppointmentRepository;
import com.pollex.pam.service.AppointmentClosedInfoService;
@@ -32,25 +34,23 @@
    
    public void process(AbstractAppointmentProcessDTO dto) {
        
//        AbstractAppointmentProcessDTO appointmentProcessDTO = dto;
        processList.stream().forEach(process ->{
            if(process.getProcessType() == dto.getContactStatus()) {
                process.createProcess(dto);
                Optional<AppointmentClosedInfo> closedInfoOP = appointmentClosedInfoRepository.findByAppointmentId(dto.getAppointmentId());
                if(closedInfoOP.isPresent()) {
                    process.editClosedInfo(dto, closedInfoOP.get());
                }else {
                    process.create(dto);
                }
            }
        });
        Appointment appointment = appointmentService.findById(dto.getAppointmentId());
        appointment.setCommunicateStatus(dto.getContactStatus());
        appointmentRepository.save(appointment);
        changeAppointmentCommunicateStatus(dto.getAppointmentId(), dto.getContactStatus());
    }
    public void editClosedInfo(AbstractAppointmentProcessDTO dto) {
        processList.stream().forEach(process ->{
            if(process.getProcessType() == dto.getContactStatus()) {
                process.editClosedInfo(dto);
            }
        });
        Appointment appointment = appointmentService.findById(dto.getAppointmentId());
        appointment.setCommunicateStatus(dto.getContactStatus());
    private void changeAppointmentCommunicateStatus(Long appointmentId, ContactStatusEnum contactStatus) {
        Appointment appointment = appointmentService.findById(appointmentId);
        appointment.setCommunicateStatus(contactStatus);
        appointmentRepository.save(appointment);
    }
    
pamapi/src/main/java/com/pollex/pam/appointment/process/AppointmentProcessInterface.java
@@ -6,8 +6,9 @@
public interface AppointmentProcessInterface {
    
    void createProcess(AbstractAppointmentProcessDTO dto);
    AppointmentClosedInfo editClosedInfo(AbstractAppointmentProcessDTO dto);
    AppointmentClosedInfo create(AbstractAppointmentProcessDTO dto);
    AppointmentClosedInfo editClosedInfo(AbstractAppointmentProcessDTO dto
            , AppointmentClosedInfo closedInfo);
    ContactStatusEnum getProcessType();
    
}
pamapi/src/main/java/com/pollex/pam/appointment/process/ClosedProcess.java
@@ -1,10 +1,9 @@
package com.pollex.pam.appointment.process;
import java.util.Optional;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.pollex.pam.domain.Appointment;
import com.pollex.pam.domain.AppointmentClosedInfo;
@@ -12,12 +11,12 @@
import com.pollex.pam.repository.AppointmentClosedInfoRepository;
import com.pollex.pam.service.AppointmentClosedInfoService;
import com.pollex.pam.service.AppointmentService;
import com.pollex.pam.service.SatisfactionService;
import com.pollex.pam.service.dto.AbstractAppointmentProcessDTO;
import com.pollex.pam.service.dto.ClosedProcessDTO;
import com.pollex.pam.service.dto.DoneProcessDTO;
import com.pollex.pam.web.rest.errors.AppointmentClosedInfoNotFoundException;
@Service
@Transactional
public class ClosedProcess implements AppointmentProcessInterface{
    
    @Autowired
@@ -29,14 +28,17 @@
    @Autowired
    AppointmentClosedInfoService appointmentClosedInfoService;
    
    @Autowired
    SatisfactionService satisfactionService;
    @Override
    public void createProcess(AbstractAppointmentProcessDTO processDTO) {
        checkClosedInfo(processDTO.getAppointmentId());
    public AppointmentClosedInfo create(AbstractAppointmentProcessDTO processDTO) {
        ClosedProcessDTO closeProcess = toClosedProcessDTO(processDTO);
        AppointmentClosedInfo closedInfo = new AppointmentClosedInfo();
        BeanUtils.copyProperties(closeProcess, closedInfo);
        appointmentClosedInfoRepository.save(closedInfo);
        Appointment appointment = appointmentService.findById(processDTO.getAppointmentId());
        satisfactionService.createSatisfaction(appointment);
        return appointmentClosedInfoRepository.save(closedInfo);
    }
    
    private ClosedProcessDTO toClosedProcessDTO(AbstractAppointmentProcessDTO processDTO) {
@@ -45,23 +47,16 @@
        return closeProcess;
    }
    private void checkClosedInfo(Long appointmentId) {
        Optional<AppointmentClosedInfo> closedInfo = appointmentClosedInfoRepository.findByAppointmentId(appointmentId);
        if(closedInfo.isPresent()) {
            throw new IllegalArgumentException("appointment closed info exist");
        }
    }
    @Override
    public ContactStatusEnum getProcessType() {
        return ContactStatusEnum.CLOSED;
    }
    @Override
    public AppointmentClosedInfo editClosedInfo(AbstractAppointmentProcessDTO abstractDTO) {
    public AppointmentClosedInfo editClosedInfo(
            AbstractAppointmentProcessDTO abstractDTO
            , AppointmentClosedInfo closedInfo) {
        ClosedProcessDTO closeProcess =  toClosedProcessDTO(abstractDTO);
        AppointmentClosedInfo closedInfo = appointmentClosedInfoService.findByAppointmentId(abstractDTO.getAppointmentId());
        BeanUtils.copyProperties(closeProcess, closedInfo);
        return appointmentClosedInfoRepository.save(closedInfo);
    }
pamapi/src/main/java/com/pollex/pam/appointment/process/DoneProcess.java
@@ -1,21 +1,22 @@
package com.pollex.pam.appointment.process;
import java.util.Optional;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.pollex.pam.domain.Appointment;
import com.pollex.pam.domain.AppointmentClosedInfo;
import com.pollex.pam.enums.ContactStatusEnum;
import com.pollex.pam.repository.AppointmentClosedInfoRepository;
import com.pollex.pam.service.AppointmentClosedInfoService;
import com.pollex.pam.service.AppointmentService;
import com.pollex.pam.service.SatisfactionService;
import com.pollex.pam.service.dto.AbstractAppointmentProcessDTO;
import com.pollex.pam.service.dto.ClosedProcessDTO;
import com.pollex.pam.service.dto.DoneProcessDTO;
import com.pollex.pam.web.rest.errors.AppointmentClosedInfoNotFoundException;
@Service
@Transactional
public class DoneProcess implements AppointmentProcessInterface{
    
    @Autowired
@@ -24,13 +25,20 @@
    @Autowired
    AppointmentClosedInfoService appointmentClosedInfoService;
    
    @Autowired
    SatisfactionService satisfactionService;
    @Autowired
    AppointmentService appointmentService;
    @Override
    public void createProcess(AbstractAppointmentProcessDTO processDTO) {
        checkClosedInfo(processDTO.getAppointmentId());
    public AppointmentClosedInfo create(AbstractAppointmentProcessDTO processDTO) {
        DoneProcessDTO doneProcess = toDoneProcessDTO(processDTO);
        AppointmentClosedInfo closedInfo = new AppointmentClosedInfo();
        BeanUtils.copyProperties(doneProcess, closedInfo);
        appointmentClosedInfoRepository.save(closedInfo);
        Appointment appointment = appointmentService.findById(processDTO.getAppointmentId());
        satisfactionService.createSatisfaction(appointment);
        return appointmentClosedInfoRepository.save(closedInfo);
    }
    @Override
@@ -38,23 +46,16 @@
        return ContactStatusEnum.DONE;
    }
    
    private void checkClosedInfo(Long appointmentId) {
        Optional<AppointmentClosedInfo> closedInfo = appointmentClosedInfoRepository.findByAppointmentId(appointmentId);
        if(closedInfo.isPresent()) {
            throw new IllegalArgumentException("appointment closed info exist");
        }
    }
    @Override
    public AppointmentClosedInfo editClosedInfo(AbstractAppointmentProcessDTO abstractDTO) {
    public AppointmentClosedInfo editClosedInfo(
            AbstractAppointmentProcessDTO abstractDTO
            , AppointmentClosedInfo closedInfo) {
        DoneProcessDTO doneProcess =  toDoneProcessDTO(abstractDTO);
        AppointmentClosedInfo closedInfo = appointmentClosedInfoService.findByAppointmentId(abstractDTO.getAppointmentId());
        BeanUtils.copyProperties(doneProcess, closedInfo);
        return appointmentClosedInfoRepository.save(closedInfo);
    }
    private DoneProcessDTO toDoneProcessDTO(AbstractAppointmentProcessDTO abstractDTO) {
        DoneProcessDTO doneProcess = (DoneProcessDTO)abstractDTO;
        BeanUtils.copyProperties(abstractDTO, doneProcess);
        return doneProcess;
pamapi/src/main/java/com/pollex/pam/domain/Appointment.java
@@ -20,6 +20,7 @@
import javax.persistence.Table;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import com.pollex.pam.enums.AppointmentStatusEnum;
@@ -75,6 +76,7 @@
    private Instant appointmentDate = Instant.now();
    @Column(name = "last_modified_date")
    @LastModifiedDate
    private Instant lastModifiedDate = Instant.now();
    @Column(name = "agent_no")
pamapi/src/main/java/com/pollex/pam/repository/SatisfactionRepository.java
@@ -20,6 +20,7 @@
    Optional<Satisfaction> findOneByAppointmentId(Long appointmentId);
    @Query(value = "SELECT avg(score) FROM satisfaction where agent_no=:agent_no"
            + " and score is not null"
            , nativeQuery = true)
    Float getAgentScoreAvg(@Param("agent_no") String agentNo);
}
pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java
@@ -6,14 +6,18 @@
import java.util.Optional;
import java.util.stream.Collectors;
import com.pollex.pam.appointment.process.AppointmentProcess;
import com.pollex.pam.config.ApplicationProperties;
import com.pollex.pam.service.dto.AppointmentUpdateDTO;
import com.pollex.pam.service.dto.ClosedProcessDTO;
import com.pollex.pam.service.dto.DoneProcessDTO;
import com.pollex.pam.service.dto.InterviewRecordDTO;
import com.pollex.pam.web.rest.errors.SendEmailFailException;
import com.pollex.pam.web.rest.errors.SendSMSFailException;
import io.jsonwebtoken.lang.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -26,6 +30,7 @@
import com.pollex.pam.repository.AppointmentCustomerViewRepository;
import com.pollex.pam.repository.AppointmentRepository;
import com.pollex.pam.security.SecurityUtils;
import com.pollex.pam.service.dto.AppointmentCloseDTO;
import com.pollex.pam.service.dto.AppointmentCreateDTO;
import com.pollex.pam.service.dto.AppointmentCustomerViewDTO;
import com.pollex.pam.service.mapper.AppointmentCustomerViewMapper;
@@ -75,6 +80,9 @@
    
    @Autowired
    InterviewRecordService interviewRecordService;
    @Autowired
    AppointmentProcess abstractAppointmentProcess;
    public Appointment customerCreateAppointment(AppointmentCreateDTO appointmentCreateDTO) {
        Appointment appointment = appointmentDTOMapper.toAppointment(appointmentCreateDTO);
@@ -142,7 +150,6 @@
            .map(appointmentCustomerView -> {
                AppointmentCustomerViewDTO dto = appointmentCustomerViewMapper.toAppointmentCustomerViewDTO(appointmentCustomerView);
                setSatisfactionScore(dto, appointmentCustomerView.getId());
                return dto;
            })
            .collect(Collectors.toList());
@@ -261,4 +268,16 @@
        return appointmentRepository.findById(id)
                .orElseThrow(AppointmentNotFoundException::new);
    }
    public void closeAppointment(AppointmentCloseDTO closeDTO) {
        if(closeDTO.getContactStatus() == ContactStatusEnum.DONE) {
            DoneProcessDTO dto = new DoneProcessDTO();
            BeanUtils.copyProperties(closeDTO, dto);
            abstractAppointmentProcess.process(dto);
        }else if(closeDTO.getContactStatus() == ContactStatusEnum.CLOSED){
            ClosedProcessDTO dto = new ClosedProcessDTO();
            BeanUtils.copyProperties(closeDTO, dto);
            abstractAppointmentProcess.process(dto);
        }
    }
}
pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
@@ -5,10 +5,12 @@
import com.pollex.pam.domain.AppointmentCustomerView;
import com.pollex.pam.domain.Consultant;
import com.pollex.pam.domain.CustomerFavoriteConsultant;
import com.pollex.pam.domain.Satisfaction;
import com.pollex.pam.enums.ContactStatusEnum;
import com.pollex.pam.enums.LoginResult;
import com.pollex.pam.repository.ConsultantRepository;
import com.pollex.pam.repository.CustomerFavoriteConsultantRepository;
import com.pollex.pam.repository.SatisfactionRepository;
import com.pollex.pam.security.SecurityUtils;
import com.pollex.pam.service.dto.*;
import com.pollex.pam.service.mapper.AppointmentCustomerViewMapper;
@@ -30,6 +32,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
@@ -82,6 +85,9 @@
    
    @Autowired
    ConsultantService consultantService;
    @Autowired
    SatisfactionRepository satisfactionRepository;
    public List<CustomerFavoriteConsultantDTO> getMyConsultantList() {
        Long customerId = SecurityUtils.getCustomerDBId();
@@ -288,4 +294,19 @@
    public String getSendSatisfactionToClientUrl(Long appointmentId) {
        return applicationProperties.getFrontEndDomain() + "/?appointmentId=" + appointmentId;
    }
    public void setConsultantAvgScore(Satisfaction satisfaction) {
        float avgScore = getAgentAvgScore(satisfaction.getAgentNo());
        Consultant consultant = consultantRepository.findOneByAgentNo(satisfaction.getAgentNo())
                .get();
        consultant.setAvgScore(avgScore);
        consultantRepository.save(consultant);
    }
    public float getAgentAvgScore(String agentNo) {
        Float avgScore = satisfactionRepository.getAgentScoreAvg(agentNo);
        if(avgScore==null)return 0;
        BigDecimal bigDecimal = new BigDecimal(avgScore);
        return avgScore = bigDecimal.setScale(1,BigDecimal.ROUND_HALF_UP).floatValue();
    }
}
pamapi/src/main/java/com/pollex/pam/service/SatisfactionService.java
@@ -1,6 +1,5 @@
package com.pollex.pam.service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@@ -9,16 +8,18 @@
import org.springframework.transaction.annotation.Transactional;
import com.pollex.pam.domain.Appointment;
import com.pollex.pam.domain.Consultant;
import com.pollex.pam.domain.Satisfaction;
import com.pollex.pam.enums.SatisfactionStatusEnum;
import com.pollex.pam.repository.ConsultantRepository;
import com.pollex.pam.repository.CustomerRepository;
import com.pollex.pam.repository.SatisfactionRepository;
import com.pollex.pam.service.dto.SatisfactionCustomerCreateDTO;
import com.pollex.pam.service.dto.SatisfactionCustomerScoreDTO;
import com.pollex.pam.service.dto.SatisfactionDTO;
import com.pollex.pam.service.mapper.AppointmentMapper;
import com.pollex.pam.service.mapper.SatisfactionDTOMapper;
import com.pollex.pam.service.mapper.SatisfactionMapper;
import com.pollex.pam.web.rest.errors.SatisfactionAlreadyExistException;
import com.pollex.pam.web.rest.errors.SatisfactionNotFoundException;
@Service
@Transactional
@@ -42,43 +43,37 @@
    @Autowired
    ConsultantRepository consultantRepository;
    public Satisfaction createSatisfaction(Satisfaction satisfaction) {
    @Autowired
    ConsultantService consultantService;
    public Satisfaction save(Satisfaction satisfaction) {
        satisfaction = satisfactionRepository.save(satisfaction);
        setConsultantAvgScore(satisfaction);
        consultantService.setConsultantAvgScore(satisfaction);
        return satisfaction;
    }
    private void setConsultantAvgScore(Satisfaction satisfaction) {
        float avgScore = getAgentAvgScore(satisfaction);
        Consultant consultant = consultantRepository.findOneByAgentNo(satisfaction.getAgentNo())
                .get();
        consultant.setAvgScore(avgScore);
        consultantRepository.save(consultant);
    public Satisfaction scorefaction(SatisfactionCustomerScoreDTO scoreDTO) {
        Optional<Satisfaction> satisfactionOP = getByAppointmentId(scoreDTO.getAppointmentId());
        Satisfaction satisfaction = satisfactionOP.orElseThrow(SatisfactionNotFoundException::new);
        satisfaction.setScore(scoreDTO.getScore());
        satisfaction.setStatus(SatisfactionStatusEnum.FILLED);
        return save(satisfaction);
    }
    private float getAgentAvgScore(Satisfaction satisfaction) {
        Float avgScore = satisfactionRepository.getAgentScoreAvg(satisfaction.getAgentNo());
        BigDecimal bigDecimal = new BigDecimal(avgScore);
        return avgScore = bigDecimal.setScale(1,BigDecimal.ROUND_HALF_UP).floatValue();
    }
    
    public Satisfaction createSatisfaction(Appointment appointment) {
        boolean isexist = getByAppointmentId(appointment.getId()).isPresent();
        if(isexist) {
            throw new SatisfactionAlreadyExistException();
        }
        Satisfaction satisfaction = appointmentMapper.toSatisfaction(appointment);
        return createSatisfaction(satisfaction);
        return save(satisfaction);
    }
    public Satisfaction createSatisfaction(SatisfactionCustomerCreateDTO createDTO) {
        // todo : å°šæœªæ¨™è¨˜å·²è¯çµ¡çš„預約單不該可以新增滿意度評分
        // todo : éžè‡ªå·±çš„預約單不該可以進行評分
        Satisfaction satisfaction = satisfactionDTOMapper.toSatisfaction(createDTO);
        return createSatisfaction(satisfaction);
    }
//
//    public Satisfaction createSatisfaction(SatisfactionCustomerScoreDTO createDTO) {
//        Satisfaction satisfaction = satisfactionDTOMapper.toSatisfaction(createDTO);
//        return save(satisfaction);
//    }
    public List<SatisfactionDTO> getByAgentNo(String agentNo) {
        List<Satisfaction> satisfactionList = satisfactionRepository.findByAgentNo(agentNo);
pamapi/src/main/java/com/pollex/pam/service/dto/AppointmentCustomerViewDTO.java
@@ -3,6 +3,7 @@
import java.time.Instant;
import java.util.List;
import com.pollex.pam.domain.AppointmentClosedInfo;
import com.pollex.pam.domain.AppointmentMemo;
import com.pollex.pam.domain.AppointmentNoticeLog;
import com.pollex.pam.domain.InterviewRecord;
@@ -33,6 +34,7 @@
    private List<AppointmentMemo> appointmentMemoList;
    private List<InterviewRecordDTO> interviewRecordDTOs;
    private List<AppointmentNoticeLog> appointmentNoticeLogs;
    private AppointmentClosedInfo appointmentClosedInfo;
    public Long getId() {
        return id;
@@ -172,6 +174,14 @@
    public void setAppointmentNoticeLogs(List<AppointmentNoticeLog> appointmentNoticeLogs) {
        this.appointmentNoticeLogs = appointmentNoticeLogs;
    }
    public AppointmentClosedInfo getAppointmentClosedInfo() {
        return appointmentClosedInfo;
    }
    public void setAppointmentClosedInfo(AppointmentClosedInfo appointmentClosedInfo) {
        this.appointmentClosedInfo = appointmentClosedInfo;
    }
    
    
}
pamapi/src/main/java/com/pollex/pam/service/dto/SatisfactionCustomerScoreDTO.java
File was renamed from pamapi/src/main/java/com/pollex/pam/service/dto/SatisfactionCustomerCreateDTO.java
@@ -1,6 +1,6 @@
package com.pollex.pam.service.dto;
public class SatisfactionCustomerCreateDTO {
public class SatisfactionCustomerScoreDTO {
    
    private Long appointmentId;
    private Float score;
pamapi/src/main/java/com/pollex/pam/service/mapper/AppointmentCustomerViewMapper.java
@@ -3,14 +3,18 @@
import static java.util.stream.Collectors.toList;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.pollex.pam.domain.AppointmentClosedInfo;
import com.pollex.pam.domain.AppointmentCustomerView;
import com.pollex.pam.domain.AppointmentNoticeLog;
import com.pollex.pam.repository.AppointmentClosedInfoRepository;
import com.pollex.pam.service.AppointmentClosedInfoService;
import com.pollex.pam.service.AppointmentNoticeLogService;
import com.pollex.pam.service.AppointmentService;
import com.pollex.pam.service.dto.AppointmentCustomerViewDTO;
@@ -24,6 +28,9 @@
    @Autowired
    AppointmentNoticeLogService appointmentNoticeLogService;
    
    @Autowired
    AppointmentClosedInfoRepository appointmentClosedInfoRepository;
    @Transactional
    public AppointmentCustomerViewDTO toAppointmentCustomerViewDTO(AppointmentCustomerView source) {
        AppointmentCustomerViewDTO target = new AppointmentCustomerViewDTO();
@@ -32,6 +39,12 @@
        appointmentService.setInterviewRecordDTO(target);
        List<AppointmentNoticeLog> noticeLogs = appointmentNoticeLogService.findByAppointmentId(source.getId());
        target.setAppointmentNoticeLogs(noticeLogs);
        Optional<AppointmentClosedInfo> appointmentClosedInfoOP = appointmentClosedInfoRepository
                .findByAppointmentId(source.getId());
        if(appointmentClosedInfoOP.isPresent()) {
            target.setAppointmentClosedInfo(appointmentClosedInfoOP.get());
        }
        return target;
    }
pamapi/src/main/java/com/pollex/pam/service/mapper/AppointmentMapper.java
@@ -13,7 +13,7 @@
import com.pollex.pam.enums.SatisfactionStatusEnum;
import com.pollex.pam.repository.AppointmentRepository;
import com.pollex.pam.service.dto.AppointmentDTO;
import com.pollex.pam.service.dto.SatisfactionCustomerCreateDTO;
import com.pollex.pam.service.dto.SatisfactionCustomerScoreDTO;
@Service
public class AppointmentMapper {
@@ -37,6 +37,7 @@
        target.setAppointmentId(appointment.getId());
        target.setAgentNo(appointment.getAgentNo());
        target.setCustomerId(appointment.getCustomerId());
        target.setStatus(SatisfactionStatusEnum.UNFILLED);
        return target;
    }
pamapi/src/main/java/com/pollex/pam/service/mapper/AppointmentNoticeSendMapper.java
@@ -1,5 +1,6 @@
package com.pollex.pam.service.mapper;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import com.pollex.pam.domain.AppointmentNoticeLog;
@@ -10,10 +11,11 @@
    
    public AppointmentNoticeLog toAppointmentNoticeLog(AppointmentNoticeSendDTO source) {
        AppointmentNoticeLog target = new AppointmentNoticeLog();
        target.setAppointmentId(source.getAppointmentId());
        BeanUtils.copyProperties(source, target);
//        target.setAppointmentId(source.getAppointmentId());
        target.setContent(source.getMessage());
        target.setEmail(source.getEmail());
        target.setPhone(source.getPhone());
//        target.setEmail(source.getEmail());
//        target.setPhone(source.getPhone());
        return target;
    }
}
pamapi/src/main/java/com/pollex/pam/service/mapper/SatisfactionDTOMapper.java
@@ -5,7 +5,7 @@
import com.pollex.pam.domain.Satisfaction;
import com.pollex.pam.enums.SatisfactionStatusEnum;
import com.pollex.pam.service.dto.SatisfactionCustomerCreateDTO;
import com.pollex.pam.service.dto.SatisfactionCustomerScoreDTO;
@Service
public class SatisfactionDTOMapper {
@@ -13,7 +13,7 @@
    @Autowired
    AppointmentMapper appointmentMapper;
    
    public Satisfaction toSatisfaction(SatisfactionCustomerCreateDTO source) {
    public Satisfaction toSatisfaction(SatisfactionCustomerScoreDTO source) {
        Satisfaction satisfaction = appointmentMapper.toSatisfaction(source.getAppointmentId());
        satisfaction.setScore(source.getScore());
        if(satisfaction.getScore()!=null) {
pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java
@@ -73,37 +73,25 @@
    
    @PostMapping("/close")
    public ResponseEntity<Void> closeAppointment(@RequestBody AppointmentCloseDTO closeDTO) {
        if(closeDTO.getContactStatus() == ContactStatusEnum.DONE) {
            DoneProcessDTO dto = new DoneProcessDTO();
            BeanUtils.copyProperties(closeDTO, dto);
            abstractAppointmentProcess.process(dto);
        }else if(closeDTO.getContactStatus() == ContactStatusEnum.CLOSED){
            ClosedProcessDTO dto = new ClosedProcessDTO();
            BeanUtils.copyProperties(closeDTO, dto);
            abstractAppointmentProcess.process(dto);
        }else {
            return ResponseEntity.notFound().build();
        }
        appointmentService.closeAppointment(closeDTO);
        return ResponseEntity.noContent().build();
    }
    
    @PostMapping("/close/info/edit")
    public ResponseEntity<Void> editAppointmentClosedInfo(@RequestBody AppointmentCloseDTO closeDTO) {
        if(closeDTO.getContactStatus() == ContactStatusEnum.DONE) {
            DoneProcessDTO dto = new DoneProcessDTO();
            BeanUtils.copyProperties(closeDTO, dto);
            abstractAppointmentProcess.editClosedInfo(dto);
        }else if(closeDTO.getContactStatus() == ContactStatusEnum.CLOSED){
            ClosedProcessDTO dto = new ClosedProcessDTO();
            BeanUtils.copyProperties(closeDTO, dto);
            abstractAppointmentProcess.editClosedInfo(dto);
        }else {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build();
    }
//    @PostMapping("/close/info/edit")
//    public ResponseEntity<Void> editAppointmentClosedInfo(@RequestBody AppointmentCloseDTO closeDTO) {
//
//        if(closeDTO.getContactStatus() == ContactStatusEnum.DONE) {
//            DoneProcessDTO dto = new DoneProcessDTO();
//            BeanUtils.copyProperties(closeDTO, dto);
//            abstractAppointmentProcess.editClosedInfo(dto);
//        }else if(closeDTO.getContactStatus() == ContactStatusEnum.CLOSED){
//            ClosedProcessDTO dto = new ClosedProcessDTO();
//            BeanUtils.copyProperties(closeDTO, dto);
//            abstractAppointmentProcess.editClosedInfo(dto);
//        }else {
//            return ResponseEntity.notFound().build();
//        }
//
//        return ResponseEntity.noContent().build();
//    }
}
pamapi/src/main/java/com/pollex/pam/web/rest/SatisfactionResource.java
@@ -17,7 +17,7 @@
import com.pollex.pam.domain.Satisfaction;
import com.pollex.pam.security.SecurityUtils;
import com.pollex.pam.service.SatisfactionService;
import com.pollex.pam.service.dto.SatisfactionCustomerCreateDTO;
import com.pollex.pam.service.dto.SatisfactionCustomerScoreDTO;
import com.pollex.pam.service.dto.SatisfactionDTO;
import com.pollex.pam.service.dto.SatisfactionUpdateDTO;
@@ -31,9 +31,9 @@
    @Autowired
    SatisfactionService satisfactionService;
    
    @PostMapping("/create")
    public Satisfaction createSatisfaction(@RequestBody SatisfactionCustomerCreateDTO createDTO) {
        return satisfactionService.createSatisfaction(createDTO);
    @PostMapping("/score")
    public Satisfaction scorefaction(@RequestBody SatisfactionCustomerScoreDTO scoreDTO) {
        return satisfactionService.scorefaction(scoreDTO);
    }
    
    @GetMapping("/getMySatisfaction")
pamapi/src/main/java/com/pollex/pam/web/rest/errors/SatisfactionAlreadyExistException.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,13 @@
package com.pollex.pam.web.rest.errors;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Satisfaction already exist")
public class SatisfactionAlreadyExistException extends RuntimeException{
    /**
     *
     */
    private static final long serialVersionUID = 1L;
}
pamapi/src/main/java/com/pollex/pam/web/rest/errors/SatisfactionNotFoundException.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,13 @@
package com.pollex.pam.web.rest.errors;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Satisfaction not found")
public class SatisfactionNotFoundException extends RuntimeException{
    /**
     *
     */
    private static final long serialVersionUID = 1L;
}