From f8ab133a7dc20562c25a092a402266f5e7b0b296 Mon Sep 17 00:00:00 2001 From: Jack <jack.su@pollex.com.tw> Date: 星期一, 24 一月 2022 10:29:38 +0800 Subject: [PATCH] Merge branch 'Phase3' of ssh://dev.pollex.com.tw:29418/pcalife/PAM into Phase3 --- pamapi/src/main/resources/i18n/messages.properties | 8 pamapi/src/main/java/com/pollex/pam/config/Constants.java | 19 pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java | 40 + PAMapp/components/Appointment/AppointmentProgress.vue | 4 PAMapp/components/DateTimePicker.vue | 16 PAMapp/pages/userReviewsRecord/index.vue | 13 pamapi/src/main/java/com/pollex/pam/service/PersonalNotificationService.java | 24 PAMapp/assets/scss/_common.scss | 46 + pamapi/src/main/java/com/pollex/pam/service/SatisfactionService.java | 14 PAMapp/assets/scss/utilities/_heading.scss | 2 pamapi/src/main/java/com/pollex/pam/repository/AppointmentRepository.java | 4 pamapi/src/main/resources/templates/mail/appointmentPendingNotifyEmail.html | 11 PAMapp/components/Interview/interviewNotification.vue | 46 - PAMapp/shared/models/reviews.model.ts | 17 pamapi/src/main/resources/config/application-dev.yml | 2 PAMapp/pages/questionnaire/_agentNo.vue | 29 PAMapp/pages/appointment/_appointmentId/interview/new/index.vue | 9 pamapi/src/main/java/com/pollex/pam/repository/AppointmentExpiringNotifyRecordRepository.java | 12 PAMapp/pages/satisfactionList.vue | 53 + PAMapp/assets/icon/style.css | 4 pamapi/src/doc/預約單/顧問取得所有自己的預約單API.txt | 89 ++- PAMapp/components/NavBar.vue | 48 + PAMapp/pages/agentInfo/_agentNo.vue | 11 pamapi/src/main/resources/i18n/messages_zh_TW.properties | 9 PAMapp/components/Interview/InterviewCard.vue | 101 ++- PAMapp/pages/appointmentAgenda/index.vue | 92 +- pamapi/src/main/resources/templates/mail/appointmentExpiringNotifyEmail.html | 11 PAMapp/pages/notification/index.vue | 43 + PAMapp/store/index.ts | 33 + PAMapp/pages/myAppointmentList/closedList.vue | 12 PAMapp/pages/record/index.vue | 15 pamapi/src/doc/預約單/標記為已聯絡API.txt | 67 + pamapi/src/main/java/com/pollex/pam/repository/AppointmentCustomerViewRepository.java | 4 PAMapp/components/Interview/InterviewRecordCard.vue | 6 pamapi/src/doc/預約單/顧問取得未處理預約單數量通知.txt | 5 pamapi/src/main/java/com/pollex/pam/web/rest/errors/NotFoundExpiringAppointmentException.java | 8 pamapi/src/doc/預約單/客戶取得最新預約的未處理預約單.txt | 61 ++ PAMapp/components/ReviewRecords/ReviewRecords.vue | 10 PAMapp/pages/myAppointmentList/contactedList.vue | 23 pamapi/src/doc/sql/20220122_w.sql | 6 PAMapp/pages/appointment/_appointmentId/index.vue | 37 + PAMapp/assets/scss/_variable.scss | 1 PAMapp/components/Interview/InterviewAdd.vue | 39 PAMapp/components/Ui/UiTimePicker.vue | 69 ++ pamapi/src/main/java/com/pollex/pam/repository/SatisfactionRepository.java | 4 PAMapp/components/Ui/UiDatePicker.vue | 20 PAMapp/pages/myAppointmentList.vue | 3 pamapi/src/main/java/com/pollex/pam/service/ScheduleTaskService.java | 158 ++++++ PAMapp/pages/appointment/_appointmentId/close/index.vue | 7 pamapi/src/main/java/com/pollex/pam/domain/AppointmentExpiringNotifyRecord.java | 46 + pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java | 58 + PAMapp/components/Appointment/AppointmentInterviewList.vue | 13 PAMapp/components/Interview/InterviewMsg.vue | 31 PAMapp/shared/services/reviews.service.ts | 7 pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java | 29 PAMapp/assets/images/appointment/avatar_bg.svg | 9 56 files changed, 1,190 insertions(+), 368 deletions(-) diff --git a/PAMapp/assets/icon/style.css b/PAMapp/assets/icon/style.css index bbb23b2..1d5c85e 100644 --- a/PAMapp/assets/icon/style.css +++ b/PAMapp/assets/icon/style.css @@ -76,8 +76,8 @@ .icon-circle-right:before { content: "\e90b"; } -.icon-down:before, .el-icon-arrow-up:before { - content: "\e910"; +.icon-close:before { + content: "\e90c"; } .icon-comment:before { content: "\e90d"; diff --git a/PAMapp/assets/images/appointment/avatar_bg.svg b/PAMapp/assets/images/appointment/avatar_bg.svg new file mode 100644 index 0000000..88abc04 --- /dev/null +++ b/PAMapp/assets/images/appointment/avatar_bg.svg @@ -0,0 +1,9 @@ +<svg id="Component_29" data-name="Component 29" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"> + <circle id="Ellipse_726" data-name="Ellipse 726" cx="50" cy="50" r="50" fill="#feecdc"/> + <g id="Group_3685" data-name="Group 3685" transform="translate(-4269 -1812)"> + <circle id="Ellipse_723" data-name="Ellipse 723" cx="50" cy="50" r="50" transform="translate(4269 1812)" fill="#feecdc"/> + <path id="Intersection_4" data-name="Intersection 4" d="M8376.456,8861.851v-2.594c0-14.428,24.235-18.738,38.724-18.738s38.367,4.313,38.367,18.738v2.59A49.9,49.9,0,0,1,8415.013,8880H8415A49.892,49.892,0,0,1,8376.456,8861.851Zm17.021-48.454a21.7,21.7,0,0,1,21.7-21.686h.014a21.693,21.693,0,1,1-21.716,21.686Z" transform="translate(-4095.456 -6968)" fill="#d0d0ce" opacity="0.789"/> + <circle id="Ellipse_724" data-name="Ellipse 724" cx="15" cy="15" r="15" transform="translate(4334 1882)" fill="none"/> + <path id="female" d="M14.817,13.279l1.116,1.11c.457.457.92.914,1.377,1.383a.856.856,0,0,1-1.235,1.187c-.665-.653-1.324-1.318-1.983-1.977l-.516-.522a.938.938,0,0,1-.107.16l-2.339,2.339a.837.837,0,0,1-.867.231.813.813,0,0,1-.594-.641.843.843,0,0,1,.261-.819l2.321-2.315a.771.771,0,0,1,.125-.095l-1.395-1.4a5.9,5.9,0,0,1-5.028.968,5.734,5.734,0,0,1-3.383-2.333,5.936,5.936,0,1,1,9.669.107l1.431,1.4c.071-.083.142-.184.231-.273.736-.736,1.466-1.472,2.208-2.2A.859.859,0,1,1,17.3,10.822l-1.6,1.6C15.4,12.7,15.114,12.965,14.817,13.279ZM11.683,7.124a4.244,4.244,0,1,0-4.256,4.232A4.244,4.244,0,0,0,11.683,7.124Z" transform="matrix(0.719, 0.695, -0.695, 0.719, 4348.543, 1882.728)" fill="none"/> + </g> +</svg> diff --git a/PAMapp/assets/scss/_common.scss b/PAMapp/assets/scss/_common.scss index 768bb07..1898ed7 100644 --- a/PAMapp/assets/scss/_common.scss +++ b/PAMapp/assets/scss/_common.scss @@ -91,4 +91,48 @@ line-height: 40px; padding-right: 10px; } -} \ No newline at end of file +} + +.remind-container{ + margin-top: 13px; + margin-bottom: 20px; + display: flex; + .remind-date{ + display: flex; + flex-direction: column; + align-items: center; + font-weight: bold; + width: 70px; + border-radius: 6px; + border-bottom: 1px solid #CCCCCC; + border-right: 1px solid #CCCCCC; + border-left: 1px solid #CCCCCC; + } + .remind-content-txt{ + display: flex; + flex-direction: column; + border: 1px solid #CCCCCC; + flex:1; + border-radius: 5px; + padding: 10px; + } + .mb-3{ + margin-bottom: 3px; + } + .mt-2{ + margin-top:2px; + } + .date-year{ + color: #fff; + align-items: center; + display: flex; + justify-content: center; + } + .bgc-primary-red{ + background-color:$PRIMARY_RED; + width: 70.5px; + border-top-left-radius:6px; + border-top-right-radius:6px; + border: 1px solid #CCCCCC; + } +} diff --git a/PAMapp/assets/scss/_variable.scss b/PAMapp/assets/scss/_variable.scss index 0fd2878..3eb388f 100644 --- a/PAMapp/assets/scss/_variable.scss +++ b/PAMapp/assets/scss/_variable.scss @@ -10,6 +10,7 @@ $SKY_BLUE: #009CBD; $LIGHT_BLUE: #8DB9CA; $DARK_BLUE: #1B365D; +$LIGHT_RED: #DA3738; $BEIGE: #A89968; $PRUDENTIAL_GREY: #68737A; $LIGHT_GREY: #D0D0CE; diff --git a/PAMapp/assets/scss/utilities/_heading.scss b/PAMapp/assets/scss/utilities/_heading.scss index 250bfa7..9ec9f55 100644 --- a/PAMapp/assets/scss/utilities/_heading.scss +++ b/PAMapp/assets/scss/utilities/_heading.scss @@ -125,7 +125,6 @@ @extend .text--bold; @extend .text--primary; @extend .cursor--pointer; - @extend .text--underline; } .pam-link-button--lg { @@ -134,5 +133,4 @@ @extend .text--bold; @extend .text--primary; @extend .cursor--pointer; - @extend .text--underline; } diff --git a/PAMapp/components/Appointment/AppointmentInterviewList.vue b/PAMapp/components/Appointment/AppointmentInterviewList.vue index dc06f5b..8366632 100644 --- a/PAMapp/components/Appointment/AppointmentInterviewList.vue +++ b/PAMapp/components/Appointment/AppointmentInterviewList.vue @@ -2,13 +2,16 @@ <div> <div class="interview__header"> <div class="mdTxt">蝝赤蝝����</div> - <div class="pam-link-button--lg" + <div class="pam-link-button" @click="addInterview">+�憓�</div> </div> <InterviewCard :interviewList="displayList.slice(0, 3)"></InterviewCard> - <section class="text--right mt-30" v-if="interviewList.length > 3"> - <div class="pam-link-button--lg" @click="readMoreBtn">撅��憭�</div> + <section class="text--right mt-30 interview-check-more" v-if="interviewList.length > 3"> + <div class="pam-link-button" @click="readMoreBtn"> + 撅��憭� + <i class="icon-expand"></i> + </div> </section> </div> </template> @@ -61,4 +64,8 @@ justify-content: space-between; margin-bottom : 10px; } +.interview-check-more{ + display: flex; + justify-content: center; +} </style> diff --git a/PAMapp/components/Appointment/AppointmentProgress.vue b/PAMapp/components/Appointment/AppointmentProgress.vue index 3debf04..5d31d83 100644 --- a/PAMapp/components/Appointment/AppointmentProgress.vue +++ b/PAMapp/components/Appointment/AppointmentProgress.vue @@ -96,14 +96,14 @@ position : relative; .circle { background-color: white; - border : 1px solid #707070; + border : 1px solid #CCCCCC; border-radius : 50%; height : 8px; margin : 0; width : 8px; z-index : 5; &.activate { - background-color: $PRIMARY_BLACK; + background-color: $BEIGE; } } .line { diff --git a/PAMapp/components/DateTimePicker.vue b/PAMapp/components/DateTimePicker.vue index 7916c77..f2a8763 100644 --- a/PAMapp/components/DateTimePicker.vue +++ b/PAMapp/components/DateTimePicker.vue @@ -4,25 +4,31 @@ <div class="dateTime"> <UiDatePicker @changeDate="changeDateTime($event, 'date')" + :isPastDateDisabled="isPastDateDisabled" :defaultValue="defaultValue" ></UiDatePicker> <UiTimePicker @changeTime="changeDateTime($event, 'time')" :defaultValue="defaultValue" + :isPastDateDisabled="isPastDateDisabled" + :changeDate="changeDate" ></UiTimePicker> </div> </template> <script lang="ts"> -import { Component, Emit, Prop, Vue } from "nuxt-property-decorator"; +import { Component, Emit, Prop, Vue, Watch } from "nuxt-property-decorator"; @Component export default class DateTimePicker extends Vue { - changeDate!: Date; + changeDate: Date | string = ''; changeTime!: string; @Prop() defaultValue!: string; + + @Prop() + isPastDateDisabled!: boolean; @Emit('changeDateTime') changeDateTime(event, type) { @@ -33,12 +39,14 @@ this.changeTime = event; } if (this.changeDate && this.changeTime) { - const timeArray = this.changeTime.split(':'); - const interViewTime = this.changeDate.setHours(+timeArray[0], +timeArray[1]); + const hour = this.changeTime.split(':')[0]; + const minute = this.changeTime.split(':')[1]; + const interViewTime = new Date(this.changeDate).setHours(+hour, +minute); return new Date(interViewTime); } return ''; } + } </script> diff --git a/PAMapp/components/Interview/InterviewAdd.vue b/PAMapp/components/Interview/InterviewAdd.vue index d86d7a6..511920e 100644 --- a/PAMapp/components/Interview/InterviewAdd.vue +++ b/PAMapp/components/Interview/InterviewAdd.vue @@ -5,18 +5,20 @@ <span>{{interviewRecord.lastModifiedDate | formatDate}} ��</span> </div> <el-row class="mdTxt mb-10"> - <el-col :xs="16" :sm="20" class="required">蝝赤����</el-col> + <el-col :xs="16" :sm="20"> + <span :class="{'required': !interviewId || isEdit}">蝝赤����</span> + </el-col> <el-col :xs="8" :sm="4" class="text--right" v-if="interviewId"> <span v-if="!isEdit" - class="mr-10 text--primary text--underline cursor--pointer" + class="mr-10 text--primary cursor--pointer" @click="showCancelPopUp = true" - >��</span> + ><i class="icon-delet"></i></span> <span v-if="!isEdit" - class="text--primary text--underline cursor--pointer" + class="text--primary cursor--pointer" @click="isEdit = !isEdit" - >蝺刻摩</span> + ><i class="icon-edit"></i></span> </el-col> </el-row> <template v-if="!interviewId || isEdit"> @@ -26,12 +28,12 @@ ></DateTimePicker> </template> <template v-else> - <div class="mdTxt lighter mt-20"> + <div class="fs-20 mt-20"> {{formatInterviewDate}} </div> </template> - <div class="mdTxt mb-10 mt-30">蝝赤蝝����</div> + <div class="mdTxt mb-10 mt-30" :class="{'required': !interviewId || isEdit}">蝝赤蝝����</div> <template v-if="!interviewId || isEdit"> <el-input type="textarea" @@ -43,7 +45,7 @@ </el-input> </template> <template v-else> - <div class="mdTxt lighter mt-20"> + <div class="fs-20 mt-20"> {{content}} </div> </template> @@ -86,6 +88,7 @@ <InterviewMsg :isVisible.sync="showInterviewMsgPopup" :client="appointmentDetail" + :defaultValue="interviewTime" @closeDialog="closePopup" ></InterviewMsg> </div> @@ -249,17 +252,15 @@ } } .required { - position: relative; - transform: translateX(10px); + position: relative; - &::before { - content: '*'; - font-size: 15px; - font-weight: bold; - position: absolute; - color: #FF0000; - transform: translateX(-10px); - z-index: 5; + &::before { + content: '*'; + position: absolute; + color: #FF0000; + transform: translate(-12px, 0); + z-index: 5; + } } -} + </style> diff --git a/PAMapp/components/Interview/InterviewCard.vue b/PAMapp/components/Interview/InterviewCard.vue index e3f4f54..c0e9a0f 100644 --- a/PAMapp/components/Interview/InterviewCard.vue +++ b/PAMapp/components/Interview/InterviewCard.vue @@ -1,35 +1,46 @@ <template> <div> - <template v-if="!interviewList.length"> - <div class="record-card record-card--empty"> + <template v-if="!interviewList.length"> + <span class="record-card record-card--empty" style="display:flex"> �蝝赤蝝���� - </div> + </span> </template> <template v-else> <div class="interview--future"> - <div class="record-card mb-10" + <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 + > + <div class="remind-container"> + <div class="remind-date mr-10"> + <div class="mb-3 smTxt bgc-primary-red date-year"> + <div class="mb-3 mt-2"> + <UiDateFormat class="date bold" :date="item.interviewDate" - onlyShowSection="DAY" /> - </div> - <div> - <UiDateFormat - class="time mt-5 line-space" + onlyShowSection="YEAR" /> + </div> + </div> + <div class="p mt-5"> + <UiDateFormat + class="date bold" + :date="item.interviewDate" + onlyShowSection="DATE" /> + </div> + </div> + <div class="remind-content-txt"> + <div style="display:flex"> + <UiDateFormat + class="date bold " :date="item.interviewDate" onlyShowSection="TIME" /> - </div> - </div> - <div class="record-card-content"> - <span>{{item.content}}</span> - </div> + </div> + <div class="interview-card-content">{{item.content}}</div> + </div> + + </div> </div> </div> @@ -39,23 +50,34 @@ :key="index + 'past'" @click="editInterview(item)" > - <div class="record-card-date"> - <div> - <UiDateFormat - class="date bold" + + <div class="remind-container"> + <div class="remind-date mr-10"> + <div class="mb-3 smTxt bgc-primary-red date-year"> + <div class="mb-3 mt-2"> + <UiDateFormat + class="bold date" :date="item.interviewDate" - onlyShowSection="DAY" /> + onlyShowSection="YEAR" /> </div> - <div> - <UiDateFormat - class="time mt-5 line-space" + </div> + <div class="p mt-5"> + <UiDateFormat + class="mt-5 line-space time" + :date="item.interviewDate" + onlyShowSection="DATE" /> + </div> + </div> + <div class="remind-content-txt"> + <div style="display:flex"> + <UiDateFormat + class="mt-5 line-space mb-3 time" :date="item.interviewDate" onlyShowSection="TIME" /> - </div> + </div> + <div class="interview-card-content">{{item.content}}</div> </div> - <div class="record-card-content"> - <span>{{item.content}}</span> - </div> + </div> </div> </section> </template> @@ -109,16 +131,14 @@ } } .interview--past { + margin-top: 10px; 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; + height: 64px; + margin-bottom: 20px; .record-card-date{ display: flex; flex-direction: column; @@ -126,11 +146,13 @@ 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; @@ -139,6 +161,15 @@ justify-content : center; } } +.interview-card-content{ + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + word-break: break-all; + word-wrap: break-word; + -webkit-line-clamp: 2; +} .line-space{ letter-spacing: 1px; } diff --git a/PAMapp/components/Interview/InterviewMsg.vue b/PAMapp/components/Interview/InterviewMsg.vue index 9695183..12dbf80 100644 --- a/PAMapp/components/Interview/InterviewMsg.vue +++ b/PAMapp/components/Interview/InterviewMsg.vue @@ -20,9 +20,11 @@ </el-input> <div v-if="client.phone"> - <div class="mdTxt mt-30 mb-10">����赤��挾</div> + <div class="mdTxt mt-30 mb-10 required">����赤��挾</div> <DateTimePicker @changeDateTime="interviewTime = $event" + :isPastDateDisabled="true" + :defaultValue="defaultValue" ></DateTimePicker> </div> @@ -33,7 +35,8 @@ </el-dialog> <PopUpFrame - :isOpen.sync="isShowSuccessAlert"> + :isOpen.sync="isShowSuccessAlert" + @closePopUp="closeAllDialog"> <div class="text--middle invite-review"> <div class="mb-30 mt-10">撌脩��赤�</div> <div class="text--primary text--middle cursor--pointer text--underline" @click="closeAllDialog " :size="'250px'">������</div> @@ -42,7 +45,7 @@ </div> </template> <script lang="ts"> -import { Vue, Component, Prop, PropSync, Emit, Action, namespace } from 'nuxt-property-decorator'; +import { Vue, Component, Prop, PropSync, Emit, namespace } from 'nuxt-property-decorator'; import appointmentService from '~/shared/services/appointment.service'; import { Appointment, ToInformAppointment } from '~/shared/models/appointment.model'; @@ -54,11 +57,11 @@ @Component export default class InterviewMsg extends Vue { - @Action - storeMyAppointmentList!: () => Promise<number>; - @appointmentStore.Action updateAppointmentDetail!: (id: number) => Appointment; + + @appointmentStore.Action + getMyAppointmentList!: () => Promise<Appointment[]>; @PropSync('isVisible') dialogVisible!: boolean; @@ -68,6 +71,9 @@ @Prop() client!: Appointment; + + @Prop() + defaultValue!: string; @Emit('closeDialog') closeDialog() { @@ -97,14 +103,14 @@ }; appointmentService.informAppointment(appointmentInformation).then((_) => { this.isShowSuccessAlert = true ; - this.updateAppointmentDetail(this.client.id); }); } closeAllDialog() { this.isShowSuccessAlert = false ; this.dialogVisible = false; - this.storeMyAppointmentList(); + this.updateAppointmentDetail(this.client.id); + this.getMyAppointmentList(); } get isBtnDisabled() :Boolean { @@ -118,6 +124,15 @@ <style lang="scss" > .interview-msg-component{ + .required { + position: relative; + &::before { + content: '*'; + position: absolute; + color: #FF0000; + transform: translate(-12px, 0); + } + } .msg-dialog-title{ display: flex; justify-content: center; diff --git a/PAMapp/components/Interview/InterviewRecordCard.vue b/PAMapp/components/Interview/InterviewRecordCard.vue index a02c7a6..2a8bb4c 100644 --- a/PAMapp/components/Interview/InterviewRecordCard.vue +++ b/PAMapp/components/Interview/InterviewRecordCard.vue @@ -33,7 +33,7 @@ <span v-else-if="item.email">(Email)</span> <span v-else>(���陛閮�)</span> </div> - <div class="mt-10">���{item.interviewDate | formatDate}}</div> + <div v-if="item.phone" class="mt-10">���{item.interviewDate | formatDate}}</div> </div> </section> <div class="time-line"></div> @@ -70,6 +70,8 @@ justify-content: center; align-items: center; align-content: center; + background-color:#1B365D; + color: #fff; } } } @@ -93,4 +95,4 @@ margin-left: 13px; margin-top: 10px; } -</style> \ No newline at end of file +</style> diff --git a/PAMapp/components/Interview/interviewNotification.vue b/PAMapp/components/Interview/interviewNotification.vue index 8b4f687..9bb6253 100644 --- a/PAMapp/components/Interview/interviewNotification.vue +++ b/PAMapp/components/Interview/interviewNotification.vue @@ -4,17 +4,22 @@ <div class="mdTxt text--black">�撠�赤����</div> <div class="smTxt_bold text--primary sub-title cursor--pointer" @click="toAgendaPage">����</div> </div> + <div class="remind-container"> - <div class="remind-content"> - <div class="remind-first-line mr-10"> - <div class="txt-margin">2021</div> - <div>12/25</div> + <div class="remind-date mr-10"> + <div class="mb-3 smTxt bgc-primary-red date-year"> + <div class="mb-3 mt-2">2021</div> </div> - <div class="remind-content-txt"> - <div class="txt-margin">09:00</div> - <div>蝝赤���</div> - </div> + <div class="p mt-5"> + <div>12/25</div> + </div> </div> + + <div class="remind-content-txt"> + <div class="mb-3">09:00</div> + <div>蝝赤���</div> + </div> + </div> </div> </template> @@ -48,29 +53,6 @@ border-bottom: 1px solid $PRIMARY_RED; } } - .remind-container{ - margin-top: 13px; - border: 1px solid #CCCCCC; - height: 61px; - border-radius: 5px; - margin-bottom: 20px; - .remind-content{ - display: flex; - padding: 13px 10px; - .remind-first-line{ - display: flex; - flex-direction: column; - align-items: center; - font-weight: bold; - } - } - } } -.remind-content-txt{ - display: flex; - flex-direction: column; -} -.txt-margin{ - margin-bottom: 3px; -} + </style> diff --git a/PAMapp/components/NavBar.vue b/PAMapp/components/NavBar.vue index 3754bfb..13c53a4 100644 --- a/PAMapp/components/NavBar.vue +++ b/PAMapp/components/NavBar.vue @@ -8,7 +8,7 @@ </div> <div class="pam-header__action-bar"> <i - v-if="currentRole" + v-if="isShowNotification" class="icon-bell text--dark-blue cursor--pointer fix-chrome-click--issue" @click="$router.push('/notification')" ></i> @@ -49,9 +49,11 @@ <script lang="ts"> import { Vue, Component } from 'vue-property-decorator'; - import { namespace } from 'nuxt-property-decorator'; + import { Action, namespace, State, Watch } from 'nuxt-property-decorator'; import { Role } from '~/shared/models/enum/Role'; import * as _ from 'lodash'; + import { NotificationList } from '~/shared/models/reviews.model'; + import { AppointmentLog } from '~/shared/models/appointment.model'; const roleStorage = namespace('localStorage'); @Component @@ -71,6 +73,21 @@ @roleStorage.Getter isAdminLogin!: boolean; + + @roleStorage.Getter + isUserLogin!: boolean; + + @Action + storeMyPersonalNotification!: () => void; + + @State + notificationList!: NotificationList[]; + + @Action + storeMyAppointmentReviewLog!: () => void; + + @State + unReviewLogList!: AppointmentLog[]; isOpenDropdown = false; @@ -123,6 +140,24 @@ ////////////////////////////////////////////////////////////////////// + @Watch('$route', {immediate: true}) + onRouterChange() { + this.getNotificationAndReviewLog(); + } + + private getNotificationAndReviewLog() { + if (this.isUserLogin) { + this.storeMyPersonalNotification(); + this.storeMyAppointmentReviewLog(); + } + + if (this.isAdminLogin) { + this.storeMyPersonalNotification(); + } + } + + ////////////////////////////////////////////////////////////////////// + routerNavigateTo(url: string): void { (this.$refs.dropdown as any).hide(); _.isEqual(url,'') @@ -144,6 +179,15 @@ return this.idToken && this.currentRole ? (this.currentRole as Role): Role.NOT_LOGIN; } + get isShowNotification() { + if (this.isUserLogin) { + return this.notificationList.length || this.unReviewLogList.length; + } + if (this.isAdminLogin) { + return this.notificationList.length + } + } + } </script> diff --git a/PAMapp/components/ReviewRecords/ReviewRecords.vue b/PAMapp/components/ReviewRecords/ReviewRecords.vue index 422c3a1..5258e5e 100644 --- a/PAMapp/components/ReviewRecords/ReviewRecords.vue +++ b/PAMapp/components/ReviewRecords/ReviewRecords.vue @@ -11,11 +11,11 @@ </section> <div class="user-reviews-page"> - <template v-if="myAppointmentReviewLogList.length"> + <template v-if="reviewLogList.length"> <section class="user-reviews-content"> <div class="user-reviews-card" - v-for="(appointmentLog, index) in myAppointmentReviewLogList" + v-for="(appointmentLog, index) in reviewLogList" :key="index"> <div class="user-reviews-card-content" v-if="isUserLogin"> �撠�<span class="mdTxt">{{ ` ${appointmentLog.agentName} ` }}</span>���� <UiReviewScore :score="appointmentLog.score" /> 閰嚗� @@ -55,17 +55,19 @@ </template> <script lang="ts"> import { AppointmentLog } from '~/shared/models/appointment.model'; -import { Vue, Component, Prop } from 'nuxt-property-decorator'; +import { Vue, Component, Prop, Watch } from 'nuxt-property-decorator'; import authService from '~/shared/services/auth.service'; @Component export default class ReviewRecords extends Vue{ + @Prop() - myAppointmentReviewLogList!: AppointmentLog[]; + reviewLogList!: AppointmentLog[]; isUserLogin = false; ////////////////////////////////////////////////////////////////////// + mounted() { this.isUserLogin = authService.isUserLogin(); } diff --git a/PAMapp/components/Ui/UiDatePicker.vue b/PAMapp/components/Ui/UiDatePicker.vue index ef89297..20132bc 100644 --- a/PAMapp/components/Ui/UiDatePicker.vue +++ b/PAMapp/components/Ui/UiDatePicker.vue @@ -8,13 +8,14 @@ format="yyyy/MM/dd" placeholder="������" prefix-icon="icon-down down-icon" + :picker-options="pickerOptions" @change="changeDate" > </el-date-picker> </template> <script lang="ts"> -import { Component, Emit, Prop, Vue, Watch } from "nuxt-property-decorator"; +import { Component, Emit, Prop, PropSync, Vue, Watch } from "nuxt-property-decorator"; @Component export default class UiDatePicker extends Vue { @@ -22,6 +23,9 @@ @Prop() defaultValue!: string; + + @Prop({default: false}) + isPastDateDisabled!: boolean; @Emit('changeDate') changeDate() { @@ -35,5 +39,19 @@ this.changeDate(); } } + + get pickerOptions() { + if (this.isPastDateDisabled) { + return { + disabledDate(time: Date) { + const date = new Date(); + const currentDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`; + const pickedDate = `${time.getFullYear()}/${time.getMonth() + 1}/${time.getDate()}` + return new Date(pickedDate).getTime() < new Date(currentDate).getTime(); + } + } + } + } + } </script> \ No newline at end of file diff --git a/PAMapp/components/Ui/UiTimePicker.vue b/PAMapp/components/Ui/UiTimePicker.vue index 6803eae..b661e00 100644 --- a/PAMapp/components/Ui/UiTimePicker.vue +++ b/PAMapp/components/Ui/UiTimePicker.vue @@ -19,14 +19,17 @@ @Component export default class UiTimePicker extends Vue { timeValue = ''; - pickerOptions = { - start: '09:00', - step: '00:15', - end: '21:00' - } @Prop() defaultValue!: string; + + @Prop({default: ''}) + changeDate!: Date | string; + + @Prop() + isPastDateDisabled!: boolean; + + /////////////////////////////////////////////////////////////////////// @Emit('changeTime') changeTime() { @@ -36,11 +39,61 @@ @Watch('defaultValue', {immediate: true}) updateDefault() { if (this.defaultValue) { - const hours = new Date(this.defaultValue).getHours(); - const minutes = new Date(this.defaultValue).getMinutes(); - this.timeValue = `${hours < 10 ? '0' + hours : hours}:${minutes < 10 ? '0' + minutes : minutes}`; + const defaultDate = new Date(this.defaultValue); + this.timeValue = this.formatTimeString(defaultDate); this.changeTime(); } } + + /////////////////////////////////////////////////////////////////////// + + get pickerOptions() { + let minTime = ''; + const currentDate = new Date(); + + if (this.isPastDateDisabled && this.changeDate && this.isPickedToday(currentDate)) { + minTime = this.formatTimeString(currentDate); + this.isPickedDisableTime(currentDate, minTime); + } + + return { + start: '09:00', + step: '00:15', + end: '21:00', + minTime: minTime + } + } + + private isPickedDisableTime(currentDate: Date, minTime: string) { + const currentTime = this.getTimeValue(currentDate, minTime); + const pickedTime = this.getTimeValue(currentDate, this.timeValue); + if (pickedTime < currentTime) { + this.timeValue = ''; + this.changeTime(); + } + } + + private isPickedToday(currentDate: Date) { + const pickedDate = new Date(this.changeDate); + const today = this.formatDateString(currentDate); + const picked = this.formatDateString(pickedDate); + return today === picked; + } + + private getTimeValue(date: Date, time: string) { + const hour = time.split(':')[0]; + const minute = time.split(':')[1]; + return date.setHours(+hour, +minute); + } + + private formatDateString(date: Date) { + return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`; + } + + private formatTimeString(date: Date) { + const hours = date.getHours(); + const minutes = date.getMinutes(); + return `${hours < 10 ? '0' + hours : hours}:${minutes < 10 ? '0' + minutes : minutes}`; + } } </script> \ No newline at end of file diff --git a/PAMapp/pages/agentInfo/_agentNo.vue b/PAMapp/pages/agentInfo/_agentNo.vue index 17a3b3d..5b948c9 100644 --- a/PAMapp/pages/agentInfo/_agentNo.vue +++ b/PAMapp/pages/agentInfo/_agentNo.vue @@ -146,7 +146,7 @@ <el-row type="flex" class="pam-paragraph"> - <UiField icon="comment" label="�犖��艙"> + <UiField icon="comment" label="�犖��艙" class="agent-info-textarea"> {{ agentInfo.concept }} </UiField> </el-row> @@ -154,7 +154,7 @@ <el-row type="flex" class="pam-paragraph"> - <UiField icon="school" label="�犖��"> + <UiField icon="school" label="�犖��" class="agent-info-textarea"> <span> {{ agentInfo.experiences }} </span> @@ -164,7 +164,7 @@ <el-row type="flex" class="pam-paragraph"> - <UiField icon="trophy" label="敺��風"> + <UiField icon="trophy" label="敺��風" class="agent-info-textarea"> {{ agentInfo.awards }} </UiField> </el-row> @@ -332,5 +332,8 @@ .pam-field{ display: flex; } - +.agent-info-textarea{ + word-break: break-all; + word-wrap: break-word; +} </style> diff --git a/PAMapp/pages/appointment/_appointmentId/close/index.vue b/PAMapp/pages/appointment/_appointmentId/close/index.vue index 94e02fd..d10476c 100644 --- a/PAMapp/pages/appointment/_appointmentId/close/index.vue +++ b/PAMapp/pages/appointment/_appointmentId/close/index.vue @@ -196,8 +196,11 @@ contactStatus : this.contactStatus.CLOSE, remark : this.appointmentCloseInfo.remark, } - appointmentService.closeAppointment(toCloseAppointment).then((_) => this.updateAppointmentDetail(appointmentId)); - this.isShowSuccessAlert = true; + appointmentService.closeAppointment(toCloseAppointment).then((_) => { + this.updateAppointmentDetail(appointmentId); + this.isShowSuccessAlert = true; + }); + } } diff --git a/PAMapp/pages/appointment/_appointmentId/index.vue b/PAMapp/pages/appointment/_appointmentId/index.vue index d96baa5..fa822d6 100644 --- a/PAMapp/pages/appointment/_appointmentId/index.vue +++ b/PAMapp/pages/appointment/_appointmentId/index.vue @@ -14,12 +14,15 @@ <section class="client-detail"> - <div class="client-detail-info"> + <div class="client-detail-info mb-30"> <div class="client-detail-info__avatar"> <div class="circle"> {{ appointmentDetail.name || 'NO NAME' }} - <div class="sm-circle"> - {{ appointmentDetail.gender === 'male' ? '�' : '憟�'}} + <div class="sm-circle sm-circle-male" v-if="appointmentDetail.gender === 'male'"> + <i class="icon-sex-male sex-icon"></i> + </div> + <div class="sm-circle sm-circle-female" v-if="appointmentDetail.gender === 'female'"> + <i class="icon-sex-female sex-icon"></i> </div> </div> </div> @@ -156,13 +159,14 @@ } getHopeContactTimeContent(hopeContactTimeString: string): string[] { - const result = hopeContactTimeString.replace("'", '').split('��'); + const result = hopeContactTimeString.replaceAll("'", '').split('��'); return result; } inviteReview(): void { - reviewsService.sendSatisfactionToClient(this.appointmentDetail.id).then(res => res); - this.isShowInviteReviewDialog = true ; + reviewsService.sendSatisfactionToClient(this.appointmentDetail.id).then(res => { + this.isShowInviteReviewDialog = true; + }); } } </script> @@ -189,8 +193,7 @@ height: 100px; width: 100px; border-radius: 50%; - background-color: #fff; - border: 1px solid $PRIMARY_BLACK; + background-image: url('~/assets/images/appointment/avatar_bg.svg'); position: relative; display: flex; justify-content: center; @@ -201,12 +204,26 @@ width: 30px; border-radius: 50%; background-color: #fff; - border: 1px solid $PRIMARY_BLACK; bottom: 0; right: 0; display: flex; justify-content: center; align-items: center; + .sex-icon { + font-size: 20px; + &.icon-sex-male{ + color: $SKY_BLUE; + } + &.icon-sex-female{ + color: $CORAL; + } + } + &-male { + border: 1px solid $SKY_BLUE; + } + &-female { + border: 1px solid $LIGHT_RED; + } } } } @@ -230,12 +247,14 @@ @extend .mb-10; @extend .mdTxt; @extend .mr-10; + line-height: 1.3; color : $DARK_BLUE; flex-basis: auto; min-width : 40px; } .client-detail-demand__demand-list-content { text-align: justify; + line-height: 1.3; text-justify: auto; word-break: break-all; } diff --git a/PAMapp/pages/appointment/_appointmentId/interview/new/index.vue b/PAMapp/pages/appointment/_appointmentId/interview/new/index.vue index debc00f..e1f9420 100644 --- a/PAMapp/pages/appointment/_appointmentId/interview/new/index.vue +++ b/PAMapp/pages/appointment/_appointmentId/interview/new/index.vue @@ -43,4 +43,13 @@ display: flex; justify-content: center; } +.required { + position: relative; + &::before { + content: '*'; + position: absolute; + color: #FF0000; + transform: translate(-12px, 0); + } +} </style> diff --git a/PAMapp/pages/appointmentAgenda/index.vue b/PAMapp/pages/appointmentAgenda/index.vue index a1b5568..4e3db90 100644 --- a/PAMapp/pages/appointmentAgenda/index.vue +++ b/PAMapp/pages/appointmentAgenda/index.vue @@ -1,41 +1,56 @@ <template> <div> <div class="mdTxt">�撠�赤����(3)</div> + <div class="remind-container"> - <div class="remind-content"> - <div class="remind-first-line mr-10"> - <div class="txt-margin">2021</div> - <div>12/25</div> + <div class="remind-date mr-10"> + <div class="mb-3 smTxt bgc-primary-red date-year"> + <div class="mb-3 mt-2">2021</div> </div> - <div class="remind-content-txt"> - <div class="txt-margin">09:00</div> - <div>蝝赤���</div> - </div> + <div class="p mt-5"> + <div>12/25</div> + </div> + </div> + + <div class="remind-content-txt"> + <div class="mb-3">09:00</div> + <div>蝝赤���</div> </div> </div> - <div class="remind-container"> - <div class="remind-content"> - <div class="remind-first-line mr-10"> - <div class="txt-margin">2021</div> - <div>12/22</div> + + <div class="remind-container"> + + <div class="remind-date mr-10"> + <div class="mb-3 smTxt bgc-primary-red date-year"> + <div class="mb-3 mt-2">2021</div> </div> - <div class="remind-content-txt"> - <div class="txt-margin">13:00</div> - <div>蝝赤�����</div> - </div> + <div class="p mt-5"> + <div>12/25</div> + </div> </div> + + <div class="remind-content-txt"> + <div class="mb-3">09:00</div> + <div>蝝赤���</div> + </div> + </div> - <div class="remind-container"> - <div class="remind-content"> - <div class="remind-first-line mr-10"> - <div class="txt-margin">2021</div> - <div>12/18</div> + <div class="remind-container"> + + <div class="remind-date mr-10"> + <div class="mb-3 smTxt bgc-primary-red date-year"> + <div class="mb-3 mt-2">2021</div> </div> - <div class="remind-content-txt"> - <div class="txt-margin">09:00</div> - <div>蝝赤��摩隡�</div> - </div> + <div class="p mt-5"> + <div>12/25</div> + </div> </div> + + <div class="remind-content-txt"> + <div class="mb-3">09:00</div> + <div>蝝赤���</div> + </div> + </div> </div> </template> @@ -50,29 +65,4 @@ } </script> <style lang="scss" scoped> - .remind-container{ - margin-top: 13px; - border: 1px solid #CCCCCC; - height: 61px; - border-radius: 5px; - margin-bottom: 20px; - background-color: #fff; - .remind-content{ - display: flex; - padding: 13px 10px; - .remind-first-line{ - display: flex; - flex-direction: column; - align-items: center; - font-weight: bold; - } - } - } -.remind-content-txt{ - display: flex; - flex-direction: column; -} -.txt-margin{ - margin-bottom: 3px; -} </style> diff --git a/PAMapp/pages/myAppointmentList.vue b/PAMapp/pages/myAppointmentList.vue index d1a6847..5045e5f 100644 --- a/PAMapp/pages/myAppointmentList.vue +++ b/PAMapp/pages/myAppointmentList.vue @@ -1,8 +1,9 @@ <template> <div> + <div class="pam-myAppointment-banner"></div> <InterviewNotification></InterviewNotification> <div class="pam-container"> - <div class="pam-cus-tabs mb-30"> + <div class="pam-cus-tabs mb-10"> <div class="cus-tab-item" :class="{'is-active': activeTabName === 'appointmentList'}" diff --git a/PAMapp/pages/myAppointmentList/closedList.vue b/PAMapp/pages/myAppointmentList/closedList.vue index b050cab..4e6d18f 100644 --- a/PAMapp/pages/myAppointmentList/closedList.vue +++ b/PAMapp/pages/myAppointmentList/closedList.vue @@ -14,7 +14,7 @@ ></i> </el-input> - <div class="closed-appointment__tag-filter"> + <div class="closed-appointment__tag-filter mb-10"> <el-radio v-model="selectedClosedCategory" :label="'all'" border>��({{ itemSum }})</el-radio> <el-radio v-model="selectedClosedCategory" :label="'done'" border>��漱({{ doneItemSum }})</el-radio> <el-radio v-model="selectedClosedCategory" :label="'closed'" border>���漱({{ closedItemSum }})</el-radio> @@ -126,15 +126,19 @@ .closed-appointment__tag-filter { display: flex; .el-radio { - border-color: $PRIMARY_BLACK; - border-width: 2px; + border-color: $LIGHT_GREY; + border-radius: 30px; + border-width: 1px; font-size : 16px; margin-left : 0 !important; margin-right: 10px; padding : 10px; @extend .fix-chrome-click--issue; &.is-checked { - background-color: #D0D0CE; + background-color: $CORAL; + .el-radio__label { + color : $PRIMARY_WHITE !important; + } } .el-radio__input { display: none; diff --git a/PAMapp/pages/myAppointmentList/contactedList.vue b/PAMapp/pages/myAppointmentList/contactedList.vue index d842de8..805b000 100644 --- a/PAMapp/pages/myAppointmentList/contactedList.vue +++ b/PAMapp/pages/myAppointmentList/contactedList.vue @@ -14,15 +14,18 @@ ></i> </el-input> - <div class="mb-10"> - <a class="sort-indicator text--primary text--underline cursor--pointer" @click="changeSortType"> + <div class="mb-10 sort-indicator-container"> + <a class="sort-indicator cursor--pointer" @click="changeSortType"> {{ this.sortType === 'DESC' ? '�->���' : '���->�' }} + <i v-if="isSortType" class="icon-sort-add"></i> + <i v-else class="icon-sort-decrease"></i> </a> </div> <ClientList :clients="pageList" :title="'contactedList'" + class="mt-10" ></ClientList> <UiPagination @@ -123,5 +126,21 @@ } } + get isSortType () :boolean { + return this.sortType === 'DESC'; + } + } </script> +<style lang="scss" scoped> +.sort-indicator-container{ + margin-bottom: 20px; +} +.sort-indicator{ + border-radius:30px; + border: 1px solid #D0D0CE; + background-color:#fff; + padding: 10px 20px; + color: #68737A; +} +</style> diff --git a/PAMapp/pages/notification/index.vue b/PAMapp/pages/notification/index.vue index d9f0140..1dcdba1 100644 --- a/PAMapp/pages/notification/index.vue +++ b/PAMapp/pages/notification/index.vue @@ -3,7 +3,11 @@ <div class="text--right mb-10" @click="showNotificationHint = true"> <i class="satisfaction-icon icon-edit"></i> </div> - <div class="satisfaction-banner my-10 cursor--pointer" @click="$router.push('/satisfactionList')"> + <div + v-if="isUserLogin && unReviewLogList.length" + class="satisfaction-banner my-10 cursor--pointer" + @click="$router.push('/satisfactionList')" + > <p class="satisfaction-text text--center">隢‵撖急遛��漲隤踵</p> </div> <el-row @@ -14,7 +18,7 @@ align="middle" class="notification-card" > - <el-col class="unRead" :span="3"></el-col> + <el-col class="unRead" :span="3" v-if="!item.readDate"></el-col> <el-col :span="18"> <p class="text">{{item.content}}</p> </el-col> @@ -22,13 +26,13 @@ <div> <UiDateFormat class="date" - :date="item.date" + :date="item.createdDate" onlyShowSection="DAY" /> </div> <div> <UiDateFormat class="time" - :date="item.date" + :date="item.createdDate" onlyShowSection="TIME" /> </div> @@ -54,22 +58,29 @@ </template> <script lang="ts"> -import { Component, Vue } from "nuxt-property-decorator"; +import { Component, State, Vue } from "nuxt-property-decorator"; +import { AppointmentLog } from "~/shared/models/appointment.model"; +import { NotificationList } from "~/shared/models/reviews.model"; +import authService from "~/shared/services/auth.service"; @Component export default class Notification extends Vue { - showNotificationHint = false; - notificationList = [ - { - content: '蝟餌絞�������10/19(鈭�)22:30�10/21(�)20:00�脰�頂蝯望�', - date: '2022-01-05T04:18:05.249Z' - }, - { - content: '蝟餌絞�������10/19(鈭�)22:30�10/21(�)20:00�脰�頂蝯望�', - date: '2022-01-05T04:18:05.249Z' - } - ] + @State + unReviewLogList!: AppointmentLog[]; + + @State + notificationList!: NotificationList[]; + + showNotificationHint = false; + isUserLogin = false; + + //////////////////////////////////////////////////////////// + + mounted() { + this.isUserLogin = authService.isUserLogin(); + } + } </script> diff --git a/PAMapp/pages/questionnaire/_agentNo.vue b/PAMapp/pages/questionnaire/_agentNo.vue index 1c2d4a2..cc1ccc5 100644 --- a/PAMapp/pages/questionnaire/_agentNo.vue +++ b/PAMapp/pages/questionnaire/_agentNo.vue @@ -115,12 +115,21 @@ </PopUpFrame> <PopUpFrame :isOpen.sync="sendReserve" @update:isOpen="closeReservePopUp"> - <div class="text--middle mt-30 sendReserve-txt">�������</div> - <div class="text--middle sendReserve-txt">�����“�������蝯∴��</div> + <div class="mdTxt mt-30 sendReserve-txt">�������</div> + <div class="mdTxt sendReserve-txt mb-30">�����“�������蝯∴��</div> + <div class="pam-app-review mb-10"> + <div class="mdTxt mb-10">撠 + <span class="mdTxt text--primary text--bold ">������</span> + 撟喳��擃���� + </div> + <div class="mdTxt">�蝯虫�嗾憿��嚗�</div> + </div> + <el-rate v-model="score" class="pam-satisfaction-rate fix-chrome-click--issue"></el-rate> <div class="text--center mdTxt"> + <el-button @click="closeReservePopUp">����</el-button> <el-button type="primary" @click="closeReservePopUp"> - ������ + � </el-button> </div> </PopUpFrame> @@ -166,6 +175,8 @@ @roleStorage.State recommendConsultantItem!:string; + + score ="" ; genderOptions=[ { @@ -478,7 +489,6 @@ display: flex; justify-content: center; margin-top: 10px; - margin-bottom: 26px; } //drawer��摨���見撘� @@ -603,6 +613,16 @@ margin: 0px 20px; } +.pam-app-review{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.pam-satisfaction-rate{ + margin-bottom: 45px; +} @include desktop{ .ques-header{ @@ -630,5 +650,6 @@ } } + </style> diff --git a/PAMapp/pages/record/index.vue b/PAMapp/pages/record/index.vue index de16eb0..be49f11 100644 --- a/PAMapp/pages/record/index.vue +++ b/PAMapp/pages/record/index.vue @@ -1,7 +1,7 @@ <template> <div> <ReviewRecords - :myAppointmentReviewLogList="myAppointmentReviewLogList" + :reviewLogList="reviewLogList" ></ReviewRecords> </div> </template> @@ -13,17 +13,8 @@ @Component export default class Reviews extends Vue{ - @State('myAppointmentReviewLogList') - myAppointmentReviewLogList!: AppointmentLog[]; - - @Action - storeMyAppointmentReviewLog!: any; - - ////////////////////////////////////////////////////////////////////// - - mounted() { - this.storeMyAppointmentReviewLog(); - } + @State('reviewLogList') + reviewLogList!: AppointmentLog[]; } </script> diff --git a/PAMapp/pages/satisfactionList.vue b/PAMapp/pages/satisfactionList.vue index e54717c..a6bae69 100644 --- a/PAMapp/pages/satisfactionList.vue +++ b/PAMapp/pages/satisfactionList.vue @@ -4,17 +4,17 @@ <div class="pam-container"> <div class="satisfaction-title"> <span class="mdTxt">皛踵�漲隤踵</span> - <span class="ml-10 text--prudential_grey smTxt_bold">� {{satisfactionList.length}} 蝑�</span> + <span class="ml-10 text--prudential_grey smTxt_bold">� {{mapUnReviewLogList.length}} 蝑�</span> </div> - <div class="satisfaction-card" v-for="(item, index) in satisfactionList" :key="index"> + <div class="satisfaction-card" v-for="(item, index) in mapUnReviewLogList" :key="index"> <div class="satisfaction-card-content"> - <UiAvatar :size="80" :agentNo="''"></UiAvatar> + <UiAvatar :size="80" :agentNo="item.agentNo"></UiAvatar> <div class="satisfaction-card-text">撠憿批�� <span class="text--primary text--bold">{{item.agentName}}</span> ��擃���蝯虫�嗾憿��嚗� </div> </div> - <el-rate v-model="item.score" class="pam-satisfaction-rate mt-10"></el-rate> + <el-rate v-model="item.score" class="pam-satisfaction-rate mt-10 fix-chrome-click--issue"></el-rate> </div> <div class="text--center mt-30"> <el-button type="primary" :disabled="isBtnDisabled">�</el-button> @@ -23,27 +23,46 @@ </div> </template> -<script> -import { Vue, Component } from 'nuxt-property-decorator'; +<script lang="ts"> +import { Vue, Component, Action, State, Watch } from 'nuxt-property-decorator'; +import { AppointmentLog } from '~/shared/models/appointment.model'; @Component({ layout: 'home' }) export default class MySatisfactionList extends Vue { - satisfactionList = [ - { - agentName: '�蝢��', - score: 0 - }, - { - agentName: '�蝢��', - score: 0 + + @State + unReviewLogList!: AppointmentLog[]; + + mapUnReviewLogList: AppointmentReviewLog[] = []; + + /////////////////////////////////////////////////////// + + @Watch('unReviewLogList') + onUnReviewLogListChange() { + if (this.unReviewLogList.length) { + this.mapUnReviewLogList = this.unReviewLogList.map(item => { + return { + ...item, + satisfaction: 0 + } + }) } - ] + } + + /////////////////////////////////////////////////////// get isBtnDisabled() { - return this.satisfactionList.findIndex(item => item.score > 0) === -1; + if (this.mapUnReviewLogList.length) { + return this.mapUnReviewLogList.findIndex(item => item.satisfaction > 0) === -1; + } + return false; } +} + +interface AppointmentReviewLog extends AppointmentLog { + satisfaction: number; } </script> @@ -99,4 +118,4 @@ } } -</style> \ No newline at end of file +</style> diff --git a/PAMapp/pages/userReviewsRecord/index.vue b/PAMapp/pages/userReviewsRecord/index.vue index 9ef2fe3..a161107 100644 --- a/PAMapp/pages/userReviewsRecord/index.vue +++ b/PAMapp/pages/userReviewsRecord/index.vue @@ -1,7 +1,7 @@ <template> <div> <ReviewRecords - :myAppointmentReviewLogList="myAppointmentReviewLogList" + :reviewLogList="reviewLogList" ></ReviewRecords> </div> </template> @@ -11,16 +11,9 @@ @Component export default class UserReviewsRecord extends Vue{ - @State('myAppointmentReviewLogList') - myAppointmentReviewLogList!: AppointmentLog[]; - @Action - storeMyAppointmentReviewLog!: any; - - ////////////////////////////////////////////////////////////////////// - mounted() { - this.storeMyAppointmentReviewLog(); - } + @State('reviewLogList') + reviewLogList!: AppointmentLog[]; } </script> diff --git a/PAMapp/shared/models/reviews.model.ts b/PAMapp/shared/models/reviews.model.ts index 438cdd3..7e5941e 100644 --- a/PAMapp/shared/models/reviews.model.ts +++ b/PAMapp/shared/models/reviews.model.ts @@ -2,3 +2,20 @@ appointmentId: number, score : number, } + +export interface NotificationList { + id: number; + /** 璅�� */ + title: string; + /** ��摰� */ + content: string; + /** ACTIVITY(�犖瘣餃��)�YSTEM(蝟餌絞�) */ + notificationType: string; + /** CUSTOMER(摰X)�ONSULTANT(憿批��) */ + ownerRole: string; + /** ���d */ + ownerId: number + createdDate: string; + /** 撌脰����� */ + readDate: string; +} \ No newline at end of file diff --git a/PAMapp/shared/services/reviews.service.ts b/PAMapp/shared/services/reviews.service.ts index be34328..44c51d9 100644 --- a/PAMapp/shared/services/reviews.service.ts +++ b/PAMapp/shared/services/reviews.service.ts @@ -1,6 +1,6 @@ import { http } from "./httpClient"; -import { UserReviewsConsultantsParams } from "../models/reviews.model"; +import { NotificationList, UserReviewsConsultantsParams } from "../models/reviews.model"; import { AppointmentLog } from "../models/appointment.model"; class ReviewsService { @@ -19,6 +19,11 @@ sendSatisfactionToClient(appointmentId: number) { return http.post(`/consultant/sendSatisfactionToClient/${appointmentId}`).then((res) => res); } + + // �撠� + getMyPersonalNotification(): Promise<NotificationList[]> { + return http.get('/personal_notification/getMyPersonalNotification').then(res => res.data); + } } export default new ReviewsService(); diff --git a/PAMapp/store/index.ts b/PAMapp/store/index.ts index f322519..3fe527f 100644 --- a/PAMapp/store/index.ts +++ b/PAMapp/store/index.ts @@ -7,6 +7,7 @@ import { getFavoriteFromStorage, setFavoriteToStorage } from '~/shared/storageConsultant'; import { AppointmentLog } from '~/shared/models/appointment.model'; import { AgentOfStrictQuery, StrictQueryParams } from '~/shared/models/strict-query.model'; +import { NotificationList } from '~/shared/models/reviews.model'; @Module export default class Store extends VuexModule { @@ -14,7 +15,9 @@ strictQueryList: AgentOfStrictQuery[] = []; myConsultantList: Consultant[] = []; - myAppointmentReviewLogList: AppointmentLog[] = []; + reviewLogList: AppointmentLog[] = []; + unReviewLogList: AppointmentLog[] = []; + notificationList: NotificationList[] = []; get isUserLogin() { return this.context.getters['localStorage/isUserLogin']; @@ -36,8 +39,18 @@ } @Mutation - updateMyAppointmentReviewLog(data: AppointmentLog[]) { - this.myAppointmentReviewLogList = data; + updateReviewLog(data: AppointmentLog[]) { + this.reviewLogList = data; + } + + @Mutation + updateUnReviewLog(data: AppointmentLog[]) { + this.unReviewLogList = data; + } + + @Mutation + updateNotification(data: NotificationList[]) { + this.notificationList = data; } @Action @@ -118,7 +131,10 @@ } }); const sortedData = dataWithLatestDate.sort((a, b) => +b.compareDate - +a.compareDate); - this.context.commit('updateMyAppointmentReviewLog', sortedData); + const reviewLog = sortedData.filter(item => item.score); + const unReviewLog = sortedData.filter(item => !item.score); + this.context.commit('updateReviewLog', reviewLog); + this.context.commit('updateUnReviewLog', unReviewLog); }); } @@ -131,4 +147,13 @@ }); } + @Action + storeMyPersonalNotification() { + reviewsService.getMyPersonalNotification().then(data => { + const sortData = data + .sort((preItem, nextItem) => +new Date(nextItem.createdDate) - +new Date(preItem.createdDate)) + this.context.commit('updateNotification', sortData); + }) + } + } diff --git a/pamapi/src/doc/sql/20220122_w.sql b/pamapi/src/doc/sql/20220122_w.sql new file mode 100644 index 0000000..1acd577 --- /dev/null +++ b/pamapi/src/doc/sql/20220122_w.sql @@ -0,0 +1,6 @@ +CREATE TABLE public.appointment_expiring_notify_record ( + id bigserial NOT NULL, + appointment_id bigserial NOT NULL, + send_time timestamp NULL, + CONSTRAINT appointment_pending_notify_record_pk PRIMARY KEY (id) +); diff --git "a/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\345\256\242\346\210\266\345\217\226\345\276\227\346\234\200\346\226\260\351\240\220\347\264\204\347\232\204\346\234\252\350\231\225\347\220\206\351\240\220\347\264\204\345\226\256.txt" "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\345\256\242\346\210\266\345\217\226\345\276\227\346\234\200\346\226\260\351\240\220\347\264\204\347\232\204\346\234\252\350\231\225\347\220\206\351\240\220\347\264\204\345\226\256.txt" new file mode 100644 index 0000000..d31a4ea --- /dev/null +++ "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\345\256\242\346\210\266\345\217\226\345\276\227\346\234\200\346\226\260\351\240\220\347\264\204\347\232\204\346\234\252\350\231\225\347\220\206\351\240\220\347\264\204\345\226\256.txt" @@ -0,0 +1,61 @@ +http get : + +http://localhost:8080/api/appointment/customer/expiring/newest + +蝪∟��mail��誑閰脩雯���脣擐�� -> http://localhost:3000?notContactAppointmentId={���銝�蝑�����} + +response body: ����200銝衣策隞乩�����(���遙雿�暹������嚗����404) +{ + "id": 385, + "phone": "0911223344", + "email": "SDD", + "contactType": "phone", + "gender": "female", + "age": "21-30", + "job": "��", + "requirement": "�摨瑁����", + "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": "2022-01-19T10:57:51.380Z", + "agentNo": "A568420", + "customerId": 139, + "name": "Angula-test", + "consultantViewTime": "2021-12-27T02:02:18.711Z", + "consultantReadTime": "2021-12-28T07:16:01.295Z", + "contactTime": "2021-12-28T07:16:37.004Z", + "satisfactionScore": null, + "appointmentMemoList": [], + "interviewRecordDTOs": [], + "appointmentNoticeLogs": [ + { + "id": 4, + "phone": "0912345678", + "email": "pollex@gmail.com", + "appointmentId": 385, + "content": "notice customer invterview time", + "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-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 + } +} diff --git "a/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\346\250\231\350\250\230\347\202\272\345\267\262\350\201\257\347\265\241API.txt" "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\346\250\231\350\250\230\347\202\272\345\267\262\350\201\257\347\265\241API.txt" index d4f900c..ad86aac 100644 --- "a/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\346\250\231\350\250\230\347\202\272\345\267\262\350\201\257\347\265\241API.txt" +++ "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\346\250\231\350\250\230\347\202\272\345\267\262\350\201\257\347\265\241API.txt" @@ -3,25 +3,56 @@ response body: { - "id": 401, - "phone": "0912345678", - "email": "wayne@pollex.com.tw", - "contactType": "EMAIL", - "gender": "male", - "age": "under_20", - "job": "123", - "requirement": "�摨瑁����,靽�瑼�/閬��", - "communicateStatus": "contacted", + "id": 385, + "phone": "0911223344", + "email": "SDD", + "contactType": "phone", + "gender": "female", + "age": "21-30", + "job": "��", + "requirement": "�摨瑁����", + "communicateStatus": "done", "hopeContactTime": "'����,�����,�����,�����,�����,���,�����9:00~12:00,12:00~14:00,14:00~18:00,18:00~21:00'", "otherRequirement": null, - "appointmentDate": "2021-12-21T08:13:50.154Z", - "lastModifiedDate": "2022-01-04T09:40:13.715Z", - "agentNo": "J149388015", - "customerId": 155, - "name": "123", - "consultantViewTime": "2021-12-24T07:27:48.681Z", - "consultantReadTime": null, - "contactTime": "2022-01-04T09:40:13.715Z", + "appointmentDate": "2021-12-16T07:11:05.400Z", + "lastModifiedDate": "2022-01-19T10:57:51.380Z", + "agentNo": "A568420", + "customerId": 139, + "name": "Angula-test", + "consultantViewTime": "2021-12-27T02:02:18.711Z", + "consultantReadTime": "2021-12-28T07:16:01.295Z", + "contactTime": "2021-12-28T07:16:37.004Z", "satisfactionScore": null, - "appointmentMemoList": [] + "appointmentMemoList": [], + "interviewRecordDTOs": [], + "appointmentNoticeLogs": [ + { + "id": 4, + "phone": "0912345678", + "email": "pollex@gmail.com", + "appointmentId": 385, + "content": "notice customer invterview time", + "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-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 + } } diff --git "a/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\351\241\247\345\225\217\345\217\226\345\276\227\346\211\200\346\234\211\350\207\252\345\267\261\347\232\204\351\240\220\347\264\204\345\226\256API.txt" "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\351\241\247\345\225\217\345\217\226\345\276\227\346\211\200\346\234\211\350\207\252\345\267\261\347\232\204\351\240\220\347\264\204\345\226\256API.txt" index c863f36..9072a0f 100644 --- "a/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\351\241\247\345\225\217\345\217\226\345\276\227\346\211\200\346\234\211\350\207\252\345\267\261\347\232\204\351\240\220\347\264\204\345\226\256API.txt" +++ "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\351\241\247\345\225\217\345\217\226\345\276\227\346\211\200\346\234\211\350\207\252\345\267\261\347\232\204\351\240\220\347\264\204\345\226\256API.txt" @@ -5,36 +5,59 @@ appointmentMemoList : ��酉鞈�� interviewRecordDTOs : 蝝赤蝝���� -[ { - "id" : 385, - "phone" : "0911223344", - "email" : "SDD", - "contactType" : "phone", - "gender" : "female", - "age" : "21-30", - "job" : "��", - "requirement" : "�摨瑁����", - "communicateStatus" : "contacted", - "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", - "agentNo" : "A568420", - "customerId" : 139, - "name" : "Angula-test", - "consultantViewTime" : "2021-12-27T02:02:18.711Z", - "consultantReadTime" : "2021-12-28T07:16:01.295Z", - "contactTime" : "2021-12-28T07:16:37.004Z", - "satisfactionScore" : null, - "appointmentMemoList" : [ ], - "interviewRecordDTOs" : [ { - "id" : 6, - "content" : "test record content", - "createdDate" : "2022-01-07T07:38:05.976Z", - "lastModifiedDate" : "2022-01-07T07:38:05.976Z", - "createdBy" : "A568420", - "lastModifiedBy" : "A568420", - "interviewDate" : "2021-01-01T08:00:00.000+00:00", - "appointmentId" : 385 - } ] -} ] \ No newline at end of file +[ + { + "id": 385, + "phone": "0911223344", + "email": "SDD", + "contactType": "phone", + "gender": "female", + "age": "21-30", + "job": "��", + "requirement": "�摨瑁����", + "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": "2022-01-19T10:57:51.380Z", + "agentNo": "A568420", + "customerId": 139, + "name": "Angula-test", + "consultantViewTime": "2021-12-27T02:02:18.711Z", + "consultantReadTime": "2021-12-28T07:16:01.295Z", + "contactTime": "2021-12-28T07:16:37.004Z", + "satisfactionScore": null, + "appointmentMemoList": [], + "interviewRecordDTOs": [], + "appointmentNoticeLogs": [ + { + "id": 4, + "phone": "0912345678", + "email": "pollex@gmail.com", + "appointmentId": 385, + "content": "notice customer invterview time", + "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-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 + } + } +] diff --git "a/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\351\241\247\345\225\217\345\217\226\345\276\227\346\234\252\350\231\225\347\220\206\351\240\220\347\264\204\345\226\256\346\225\270\351\207\217\351\200\232\347\237\245.txt" "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\351\241\247\345\225\217\345\217\226\345\276\227\346\234\252\350\231\225\347\220\206\351\240\220\347\264\204\345\226\256\346\225\270\351\207\217\351\200\232\347\237\245.txt" new file mode 100644 index 0000000..4582f1e --- /dev/null +++ "b/pamapi/src/doc/\351\240\220\347\264\204\345\226\256/\351\241\247\345\225\217\345\217\226\345\276\227\346\234\252\350\231\225\347\220\206\351\240\220\347\264\204\345\226\256\346\225\270\351\207\217\351\200\232\347\237\245.txt" @@ -0,0 +1,5 @@ +http get : +http://localhost:8080/api/appointment/consultant/pending/sum + +response body: +2 diff --git a/pamapi/src/main/java/com/pollex/pam/config/Constants.java b/pamapi/src/main/java/com/pollex/pam/config/Constants.java index ead5fc8..aae9b67 100644 --- a/pamapi/src/main/java/com/pollex/pam/config/Constants.java +++ b/pamapi/src/main/java/com/pollex/pam/config/Constants.java @@ -11,5 +11,24 @@ public static final String SYSTEM = "system"; public static final String DEFAULT_LANGUAGE = "zh-tw"; + /** + * �閰勗�mail�撱箇���(T)敺�憭抵������� + * �����摰2 + */ + public static final int APPOINTMENT_PENDING_PHONE_INTERVAL = 2; + public static final int APPOINTMENT_PENDING_EMAIL_INTERVAL = 2; + + /** + * �閰勗�mail�撱箇���(T)敺�憭拇�◤閬������嚗憭拇甈⊥����策憿批�� + * �敺�憭�(T+N+1)嚗停���甈∠策摰X�� 閰脤“���敹�瘜������閬��� + */ + public static final int APPOINTMENT_EXPIRING_PHONE_INTERVAL = APPOINTMENT_PENDING_PHONE_INTERVAL + 1; + public static final int APPOINTMENT_EXPIRING_EMAIL_INTERVAL = APPOINTMENT_PENDING_EMAIL_INTERVAL + 1; + + /** + * �摰X��活��� + */ + public static final int SEND_EXPIRING_NOTIFY_LIMIT = 1; + private Constants() {} } diff --git a/pamapi/src/main/java/com/pollex/pam/domain/AppointmentExpiringNotifyRecord.java b/pamapi/src/main/java/com/pollex/pam/domain/AppointmentExpiringNotifyRecord.java new file mode 100644 index 0000000..5f2f8f6 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/domain/AppointmentExpiringNotifyRecord.java @@ -0,0 +1,46 @@ +package com.pollex.pam.domain; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.Instant; + +@Entity +@Table(name = "appointment_expiring_notify_record") +public class AppointmentExpiringNotifyRecord implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "appointment_id") + private Long appointmentId; + + @Column(name = "send_time") + private Instant sendTime; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getAppointmentId() { + return appointmentId; + } + + public void setAppointmentId(Long appointmentId) { + this.appointmentId = appointmentId; + } + + public Instant getSendTime() { + return sendTime; + } + + public void setSendTime(Instant sendTime) { + this.sendTime = sendTime; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/repository/AppointmentCustomerViewRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/AppointmentCustomerViewRepository.java index b3499c6..61b5a04 100644 --- a/pamapi/src/main/java/com/pollex/pam/repository/AppointmentCustomerViewRepository.java +++ b/pamapi/src/main/java/com/pollex/pam/repository/AppointmentCustomerViewRepository.java @@ -2,6 +2,9 @@ import java.util.List; +import com.pollex.pam.domain.Appointment; +import com.pollex.pam.enums.AppointmentStatusEnum; +import com.pollex.pam.enums.ContactStatusEnum; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -11,4 +14,5 @@ public interface AppointmentCustomerViewRepository extends JpaRepository<AppointmentCustomerView, Long>{ List<AppointmentCustomerView> findByAgentNo(String agentNo); List<AppointmentCustomerView> findByAgentNoAndCustomerId(String agentNo, Long customerId); + List<AppointmentCustomerView> findAllByCommunicateStatusAndStatus(ContactStatusEnum contactStatus, AppointmentStatusEnum status); } diff --git a/pamapi/src/main/java/com/pollex/pam/repository/AppointmentExpiringNotifyRecordRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/AppointmentExpiringNotifyRecordRepository.java new file mode 100644 index 0000000..61a559a --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/repository/AppointmentExpiringNotifyRecordRepository.java @@ -0,0 +1,12 @@ +package com.pollex.pam.repository; + +import com.pollex.pam.domain.AppointmentExpiringNotifyRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface AppointmentExpiringNotifyRecordRepository extends JpaRepository<AppointmentExpiringNotifyRecord, Long> { + List<AppointmentExpiringNotifyRecord> findAllByAppointmentId(Long appointmentId); +} diff --git a/pamapi/src/main/java/com/pollex/pam/repository/AppointmentRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/AppointmentRepository.java index a59dbb6..fe01955 100644 --- a/pamapi/src/main/java/com/pollex/pam/repository/AppointmentRepository.java +++ b/pamapi/src/main/java/com/pollex/pam/repository/AppointmentRepository.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.Optional; +import com.pollex.pam.enums.AppointmentStatusEnum; +import com.pollex.pam.enums.ContactStatusEnum; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -16,4 +18,6 @@ List<Appointment> findByAgentNoAndCustomerId(String agentNo, Long customerId); Optional<Appointment> findTopByAgentNoAndCustomerIdOrderByAppointmentDateDesc(String agentNo, Long customerId); + + List<Appointment> findAllByCommunicateStatusAndStatus(ContactStatusEnum contactStatus, AppointmentStatusEnum status); } diff --git a/pamapi/src/main/java/com/pollex/pam/repository/SatisfactionRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/SatisfactionRepository.java index 98476e7..5b0a284 100644 --- a/pamapi/src/main/java/com/pollex/pam/repository/SatisfactionRepository.java +++ b/pamapi/src/main/java/com/pollex/pam/repository/SatisfactionRepository.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Optional; +import com.pollex.pam.enums.SatisfactionStatusEnum; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -19,9 +20,10 @@ Optional<Satisfaction> findOneByAppointmentId(Long appointmentId); + List<Satisfaction> findAllByStatus(SatisfactionStatusEnum status); + @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); } - \ No newline at end of file diff --git a/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java b/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java index dcac303..6d5ded6 100644 --- a/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java +++ b/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java @@ -1,6 +1,9 @@ package com.pollex.pam.service; import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -8,10 +11,9 @@ 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.config.Constants; +import com.pollex.pam.service.dto.*; +import com.pollex.pam.web.rest.errors.NotFoundExpiringAppointmentException; import com.pollex.pam.web.rest.errors.SendEmailFailException; import com.pollex.pam.web.rest.errors.SendSMSFailException; import io.jsonwebtoken.lang.Assert; @@ -24,15 +26,11 @@ import com.pollex.pam.domain.Appointment; import com.pollex.pam.domain.AppointmentCustomerView; -import com.pollex.pam.domain.InterviewRecord; import com.pollex.pam.enums.ContactStatusEnum; import com.pollex.pam.enums.InterviewRecordStatusEnum; 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; import com.pollex.pam.service.mapper.AppointmentDTOMapper; import com.pollex.pam.web.rest.errors.AppointmentNotFoundException; @@ -77,13 +75,13 @@ @Autowired SpringTemplateEngine springTemplateEngine; - + @Autowired InterviewRecordService interviewRecordService; - + @Autowired AppointmentProcess abstractAppointmentProcess; - + @Autowired PersonalNotificationService personalNotificationService; @@ -119,7 +117,7 @@ appointment.setCommunicateStatus(ContactStatusEnum.CANCEL); appointmentRepository.save(appointment); personalNotificationService.createMarkAppointmentDeletedToConsultant(appointment); - + } public List<Appointment> findByAgentNo(String agentNo) { @@ -203,7 +201,7 @@ Assert.notNull(appointment, "appointment entity cannot be null"); log.debug("is need send appointment notify msg? = {}", applicationProperties.isSendNotifyMsg()); - + log.debug("sending appointment notify, appointmentId = {}", appointment.getId()); sendAppointmentNotifyBySMS(appointment); sendAppointmentNotifyByHtmlEmail(appointment); @@ -268,7 +266,7 @@ public String getAppointmentDetailUrl(Long appointmentId) { return applicationProperties.getFrontEndDomain() + "/myAppointmentList/contactedList?appointmentId=" + appointmentId; } - + public Appointment findById(Long id) { return appointmentRepository.findById(id) .orElseThrow(AppointmentNotFoundException::new); @@ -285,4 +283,36 @@ abstractAppointmentProcess.process(dto); } } + + public Long getConsultantPendingAppointmentSum(String agentNo) { + return appointmentCustomerViewRepository.findAllByCommunicateStatusAndStatus(ContactStatusEnum.RESERVED, AVAILABLE) + .stream() + .filter(appointment -> agentNo.equals(appointment.getAgentNo())) + .filter(appointment -> isAppointmentDateNotInIntervalFromNow(appointment, Constants.APPOINTMENT_PENDING_PHONE_INTERVAL, Constants.APPOINTMENT_PENDING_EMAIL_INTERVAL)) + .count(); + } + + public AppointmentCustomerViewDTO getCustomerNewestExpiringAppointment(Long customerId) { + return appointmentCustomerViewRepository.findAllByCommunicateStatusAndStatus(ContactStatusEnum.RESERVED, AVAILABLE) + .stream() + .filter(appointment -> customerId.equals(appointment.getCustomerId())) + .filter(appointment -> isAppointmentDateNotInIntervalFromNow(appointment, Constants.APPOINTMENT_EXPIRING_PHONE_INTERVAL, Constants.APPOINTMENT_EXPIRING_EMAIL_INTERVAL)) + .max(Comparator.comparing(AppointmentCustomerView::getAppointmentDate)) + .map(appointmentCustomerView -> appointmentCustomerViewMapper.toAppointmentCustomerViewDTO(appointmentCustomerView)) + .orElse(null); + } + + public boolean isAppointmentDateNotInIntervalFromNow(AppointmentCustomerView appointment, int phoneInterval, int emailInterval) { + final boolean isHavePhone = StringUtils.hasText(appointment.getPhone()); + final boolean isHaveEmail = StringUtils.hasText(appointment.getEmail()); + + LocalDate appointmentDate = appointment.getAppointmentDate().atZone(ZoneId.systemDefault()).toLocalDate(); + LocalDate nowDate = Instant.now().atZone(ZoneId.systemDefault()).toLocalDate(); + long intervalDays = nowDate.toEpochDay() - appointmentDate.toEpochDay(); + + final boolean isAppointmentExpiringByPhone = isHavePhone && intervalDays >= phoneInterval; + final boolean isAppointmentExpiringByEmail = isHaveEmail && intervalDays >= emailInterval; + + return isAppointmentExpiringByPhone || isAppointmentExpiringByEmail; + } } diff --git a/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java b/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java index d78a39f..0179b54 100644 --- a/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java +++ b/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java @@ -73,22 +73,22 @@ @Autowired ApplicationProperties applicationProperty; - + @Autowired SendMsgService sendMsgService; - + @Autowired SpringTemplateEngine springTemplateEngine; - + @Autowired ApplicationProperties applicationProperties; - + @Autowired ConsultantService consultantService; - + @Autowired SatisfactionRepository satisfactionRepository; - + @Autowired PersonalNotificationService personalNotificationService; @@ -108,7 +108,7 @@ dto, appointmentService.findAvailableByAgentNoAndCustomerId(consultant.getAgentNo(), customerId) ); - + setFavoriteConsultantUpdatedTime(relation, dto); return dto; @@ -140,8 +140,7 @@ if (!appointments.isEmpty()) { AppointmentCustomerView latestAvailableAppointment = appointments.get(0); ContactStatusEnum latestStatus = latestAvailableAppointment.getCommunicateStatus(); - if( latestStatus != ContactStatusEnum.DONE - || latestStatus != ContactStatusEnum.CLOSED) + if(latestStatus != ContactStatusEnum.DONE && latestStatus != ContactStatusEnum.CLOSED) customerFavoriteConsultantDTO.setContactStatus(latestStatus); else customerFavoriteConsultantDTO.setContactStatus(PICKED); @@ -271,19 +270,19 @@ public void sendSatisfactionToClient(Appointment appointment) { String subject = "皛踵�漲憛怠神�"; - + if(StringUtils.hasText(appointment.getEmail())) { String content = genSendSatisfactionEmailContent(appointment); sendMsgService.sendMsgByEmail(appointment.getEmail(), subject, content, true); - + }if(StringUtils.hasText(appointment.getPhone())) { String content = genSendSatisfactionSMSContent(appointment); sendMsgService.sendMsgBySMS(appointment.getPhone(), content); } - + personalNotificationService.createSendSatisfactionToClientToCustomer(appointment); } - + private String genSendSatisfactionSMSContent(Appointment appointment) { String agentNo = appointment.getAgentNo(); Consultant consultant = consultantService.findByAgentNo(agentNo); @@ -305,7 +304,7 @@ 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()) @@ -317,7 +316,7 @@ public float getAgentAvgScore(String agentNo) { Float avgScore = satisfactionRepository.getAgentScoreAvg(agentNo); if(avgScore==null)return 0; - BigDecimal bigDecimal = new BigDecimal(avgScore); + BigDecimal bigDecimal = new BigDecimal(avgScore); return avgScore = bigDecimal.setScale(1,BigDecimal.ROUND_HALF_UP).floatValue(); } } diff --git a/pamapi/src/main/java/com/pollex/pam/service/PersonalNotificationService.java b/pamapi/src/main/java/com/pollex/pam/service/PersonalNotificationService.java index b6426cd..cad80e9 100644 --- a/pamapi/src/main/java/com/pollex/pam/service/PersonalNotificationService.java +++ b/pamapi/src/main/java/com/pollex/pam/service/PersonalNotificationService.java @@ -20,22 +20,22 @@ @Service @Transactional public class PersonalNotificationService { - + @Autowired PersonalNotificationRepository personalNotificationRepository; - + @Autowired ConsultantService consultantService; - + @Autowired AppointmentService appointmentService; - + @Autowired CustomerService customerService; - + @Autowired CustomerRepository customerRepository; - + @Autowired SatisfactionService satisfactionService; @@ -68,6 +68,18 @@ personalNotificationRepository.save(entity); } + public void createNotFillSatisfactionSumToCustomer(Long customerId, int notFillSatisfactionSum) { + PersonalNotification entity = new PersonalNotification(); + + String content = "���� "+notFillSatisfactionSum+" 蝑“���遛��漲��閬‵撖�"; + entity.setContent(content); + entity.setNotificationType(NotificationTypeEnum.ACTIVITY); + entity.setOwnerId(customerId); + entity.setOwnerRole(PersonalNotificationRoleEnum.CUSTOMER); + entity.setTitle("摰X皛踵�漲"); + personalNotificationRepository.save(entity); + } + public void createEditConsultantToConsultant(Consultant consultant) { PersonalNotification entity = new PersonalNotification(); String content = "����犖撣唾�身摰歇�脰��"; diff --git a/pamapi/src/main/java/com/pollex/pam/service/SatisfactionService.java b/pamapi/src/main/java/com/pollex/pam/service/SatisfactionService.java index 569844e..9911f04 100644 --- a/pamapi/src/main/java/com/pollex/pam/service/SatisfactionService.java +++ b/pamapi/src/main/java/com/pollex/pam/service/SatisfactionService.java @@ -40,13 +40,13 @@ @Autowired CustomerRepository customerRepository; - + @Autowired ConsultantRepository consultantRepository; - + @Autowired ConsultantService consultantService; - + @Autowired PersonalNotificationService personalNotificationService; @@ -55,7 +55,7 @@ consultantService.setConsultantAvgScore(satisfaction); return satisfaction; } - + public Satisfaction scorefaction(SatisfactionCustomerScoreDTO scoreDTO) { Optional<Satisfaction> satisfactionOP = getByAppointmentId(scoreDTO.getAppointmentId()); Satisfaction satisfaction = satisfactionOP.orElseThrow(SatisfactionNotFoundException::new); @@ -65,7 +65,7 @@ personalNotificationService.createScorefactionToConsultant(satisfaction); return satisfaction; } - + public Satisfaction createSatisfaction(Appointment appointment) { boolean isexist = getByAppointmentId(appointment.getId()).isPresent(); if(isexist) { @@ -94,6 +94,10 @@ return satisfactionRepository.findOneByAppointmentId(appointmentId); } + public List<Satisfaction> getByStatus(SatisfactionStatusEnum status) { + return satisfactionRepository.findAllByStatus(status); + } + public List<Satisfaction> scoreAllfaction(List<SatisfactionCustomerScoreDTO> scoreDTO) { List<Satisfaction> satisfactionList = new ArrayList<>(); scoreDTO.stream().forEach(dto ->{ diff --git a/pamapi/src/main/java/com/pollex/pam/service/ScheduleTaskService.java b/pamapi/src/main/java/com/pollex/pam/service/ScheduleTaskService.java new file mode 100644 index 0000000..604479e --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/ScheduleTaskService.java @@ -0,0 +1,158 @@ +package com.pollex.pam.service; + +import com.pollex.pam.config.ApplicationProperties; +import com.pollex.pam.config.Constants; +import com.pollex.pam.domain.*; +import com.pollex.pam.enums.AppointmentStatusEnum; +import com.pollex.pam.enums.ContactStatusEnum; +import com.pollex.pam.enums.SatisfactionStatusEnum; +import com.pollex.pam.repository.AppointmentCustomerViewRepository; +import com.pollex.pam.repository.AppointmentExpiringNotifyRecordRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@Transactional +public class ScheduleTaskService { + + private static final String NOT_CONTACTED_NOTIFY_SUBJECT = "�����脰�蝜恍�"; + private static final Logger log = LoggerFactory.getLogger(ScheduleTaskService.class); + + @Autowired + ConsultantService consultantService; + + @Autowired + AppointmentService appointmentService; + + @Autowired + AppointmentCustomerViewRepository appointmentCustomerViewRepository; + + @Autowired + SendMsgService sendMsgService; + + @Autowired + SpringTemplateEngine springTemplateEngine; + + @Autowired + ApplicationProperties applicationProperties; + + @Autowired + AppointmentExpiringNotifyRecordRepository appointmentExpiringNotifyRecordRepository; + + @Autowired + SatisfactionService satisfactionService; + + @Autowired + PersonalNotificationService personalNotificationService; + + @Scheduled(cron = "0 30 8 * * *") + public void sendAppointmentPendingNotifyToConsultant() { + log.info("Starting send appointment pending notify to consultant"); + + Map<String, List<AppointmentCustomerView>> consultantWithPendingAppointments = + appointmentCustomerViewRepository.findAllByCommunicateStatusAndStatus(ContactStatusEnum.RESERVED, AppointmentStatusEnum.AVAILABLE) + .stream() + .filter(appointment -> + appointmentService.isAppointmentDateNotInIntervalFromNow(appointment, Constants.APPOINTMENT_PENDING_PHONE_INTERVAL, Constants.APPOINTMENT_PENDING_EMAIL_INTERVAL) + ) + .collect(Collectors.groupingBy(AppointmentCustomerView::getAgentNo)); + + consultantWithPendingAppointments.forEach((agentNo, pendingAppointments) -> { + int pendingAppointmentsSum = pendingAppointments.size(); + Consultant consultant = consultantService.findByAgentNo(agentNo); + String consultantPhoneNumber = consultant.getPhoneNumber(); + String consultantEmail = consultant.getEmail(); + String emailContent = getAppointmentPendingNotifyEmailContent(pendingAppointmentsSum); + + sendMsgService.sendMsgBySMS(consultantPhoneNumber, String.format("����%s������脰�蝜恬������", pendingAppointmentsSum)); + sendMsgService.sendMsgByEmail(consultantEmail, NOT_CONTACTED_NOTIFY_SUBJECT, emailContent, true); + }); + + log.info("Sending appointment pending notify to consultant finish"); + } + + @Scheduled(cron = "0 30 8 * * *") + public void sendAppointmentExpiringNotifyToCustomer() { + log.info("Starting send appointment expiring notify to customer"); + + List<AppointmentCustomerView> allByCommunicateStatus = + appointmentCustomerViewRepository.findAllByCommunicateStatusAndStatus(ContactStatusEnum.RESERVED, AppointmentStatusEnum.AVAILABLE) + .stream() + .filter(appointment -> + appointmentService.isAppointmentDateNotInIntervalFromNow(appointment, Constants.APPOINTMENT_EXPIRING_PHONE_INTERVAL, Constants.APPOINTMENT_EXPIRING_EMAIL_INTERVAL) + ) + .filter(this::isAppointmentNotifyNotOnLimit) + .collect(Collectors.toList()); + + allByCommunicateStatus.forEach(appointment -> { + Consultant consultant = consultantService.findByAgentNo(appointment.getAgentNo()); + Optional<String> optionalPhone = Optional.ofNullable(appointment.getPhone()).filter(StringUtils::hasText); + Optional<String> optionalEmail = Optional.ofNullable(appointment.getEmail()).filter(StringUtils::hasText); + + optionalPhone.ifPresent(phone -> + sendMsgService.sendMsgBySMS(phone, String.format("敺甇�����%s憿批�迤敹�葉嚗������蒂���隞“������雯��嚗�%s" + , consultant.getName(), getAppointmentUrl(appointment.getId()))) + ); + optionalEmail.ifPresent(email -> + sendMsgService.sendMsgByEmail(email, NOT_CONTACTED_NOTIFY_SUBJECT, getAppointmentExpiringNotifyEmail(consultant.getName(), getAppointmentUrl(appointment.getId())), true) + ); + + AppointmentExpiringNotifyRecord record = new AppointmentExpiringNotifyRecord(); + record.setAppointmentId(appointment.getId()); + record.setSendTime(Instant.now()); + + appointmentExpiringNotifyRecordRepository.save(record); + }); + + log.info("Sending appointment expiring notify to customer finish"); + } + + // todo ��蝣箄�府����, otis todo=134497 + @Scheduled(cron = "0 0 9 * * *") + public void sendNotFillSatisfactionToPersonalNotification() { + Map<Long, List<Satisfaction>> customerNotFillSatisfactions = satisfactionService.getByStatus(SatisfactionStatusEnum.UNFILLED) + .stream() + .collect(Collectors.groupingBy(Satisfaction::getCustomerId)); + + customerNotFillSatisfactions.forEach((customerId, notFillSatisfactions) -> + personalNotificationService.createNotFillSatisfactionSumToCustomer(customerId, notFillSatisfactions.size()) + ); + } + + private boolean isAppointmentNotifyNotOnLimit(AppointmentCustomerView appointment) { + int sendNotifyToCustomerRecordSum = + appointmentExpiringNotifyRecordRepository.findAllByAppointmentId(appointment.getId()).size(); + + return sendNotifyToCustomerRecordSum < Constants.SEND_EXPIRING_NOTIFY_LIMIT; + } + + private String getAppointmentUrl(Long appointmentId) { + return applicationProperties.getFrontEndDomain() + "?notContactAppointmentId=" + appointmentId; + } + + private String getAppointmentPendingNotifyEmailContent(int sum) { + Context context = new Context(); + context.setVariable("pendingAppointmentSum", sum); + return springTemplateEngine.process("mail/appointmentPendingNotifyEmail", context); + } + + private String getAppointmentExpiringNotifyEmail(String consultantName, String notifyUrl) { + Context context = new Context(); + context.setVariable("consultantName", consultantName); + context.setVariable("notifyUrl", notifyUrl); + return springTemplateEngine.process("mail/appointmentExpiringNotifyEmail", context); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java index ca378b3..ac19b72 100644 --- a/pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java @@ -2,16 +2,13 @@ import com.pollex.pam.appointment.process.AppointmentProcess; import com.pollex.pam.domain.Appointment; -import com.pollex.pam.enums.ContactStatusEnum; +import com.pollex.pam.security.SecurityUtils; import com.pollex.pam.service.SendMsgService; import com.pollex.pam.service.dto.AppointmentUpdateDTO; -import com.pollex.pam.service.dto.ClosedProcessDTO; -import com.pollex.pam.service.dto.DoneProcessDTO; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import com.pollex.pam.service.AppointmentService; @@ -20,6 +17,8 @@ import com.pollex.pam.service.dto.AppointmentCloseDTO; import com.pollex.pam.service.dto.AppointmentCreateDTO; import com.pollex.pam.service.dto.AppointmentCustomerViewDTO; + +import java.util.Objects; @RestController @RequestMapping("/api/appointment") @@ -33,10 +32,10 @@ @Autowired SendMsgService sendMsgService; - + @Autowired AppointmentProcess abstractAppointmentProcess; - + @Autowired PersonalNotificationService personalNotificationService; @@ -75,16 +74,35 @@ appointmentService.recordConsultantReadTime(appointmentId); return ResponseEntity.noContent().build(); } - + @PostMapping("/close") public ResponseEntity<Void> closeAppointment(@RequestBody AppointmentCloseDTO closeDTO) { appointmentService.closeAppointment(closeDTO); return ResponseEntity.noContent().build(); } - + + @GetMapping("/customer/expiring/newest") + public ResponseEntity<AppointmentCustomerViewDTO> getNewestExpiringAppointment() { + Long customerId = SecurityUtils.getCustomerDBId(); + AppointmentCustomerViewDTO customerNewestExpiringAppointment = appointmentService.getCustomerNewestExpiringAppointment(customerId); + + if(Objects.nonNull(customerNewestExpiringAppointment)) { + return new ResponseEntity<>(customerNewestExpiringAppointment, HttpStatus.OK); + } + else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @GetMapping("/consultant/pending/sum") + public ResponseEntity<Long> getConsultantPendingAppointmentSum() { + String agentNo = SecurityUtils.getAgentNo(); + return new ResponseEntity<>(appointmentService.getConsultantPendingAppointmentSum(agentNo), HttpStatus.OK); + } + // @PostMapping("/close/info/edit") // public ResponseEntity<Void> editAppointmentClosedInfo(@RequestBody AppointmentCloseDTO closeDTO) { -// +// // if(closeDTO.getContactStatus() == ContactStatusEnum.DONE) { // DoneProcessDTO dto = new DoneProcessDTO(); // BeanUtils.copyProperties(closeDTO, dto); @@ -96,7 +114,7 @@ // }else { // return ResponseEntity.notFound().build(); // } -// +// // return ResponseEntity.noContent().build(); // } } diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/NotFoundExpiringAppointmentException.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/NotFoundExpiringAppointmentException.java new file mode 100644 index 0000000..a2a02f0 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/NotFoundExpiringAppointmentException.java @@ -0,0 +1,8 @@ +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 = "not found any expiring appointment") +public class NotFoundExpiringAppointmentException extends RuntimeException { +} diff --git a/pamapi/src/main/resources/config/application-dev.yml b/pamapi/src/main/resources/config/application-dev.yml index 90c15aa..ced80df 100644 --- a/pamapi/src/main/resources/config/application-dev.yml +++ b/pamapi/src/main/resources/config/application-dev.yml @@ -33,7 +33,7 @@ datasource: type: com.zaxxer.hikari.HikariDataSource url: jdbc:postgresql://dev.pollex.com.tw:5433/pam_p2 - #url: jdbc:postgresql://localhost:5432/omo?currentSchema=omo + #url: jdbc:postgresql://localhost:5432/omo?currentSchema=public username: pamadmin password: pamadmin hikari: diff --git a/pamapi/src/main/resources/i18n/messages.properties b/pamapi/src/main/resources/i18n/messages.properties index 773fdd0..8f0b474 100644 --- a/pamapi/src/main/resources/i18n/messages.properties +++ b/pamapi/src/main/resources/i18n/messages.properties @@ -20,5 +20,11 @@ email.reset.text1=For your pamapi account a password reset was requested, please click on the URL below to reset it: email.reset.text2=Regards, -# satisfaction write email +# satisfaction write email email.write.satisfaction.content={0}\u9867\u554F\u8ACB\u60A8\u586B\u5BEB\u4FDD\u8AA0\u5A92\u5408\u5E73\u53F0\u7684\u6EFF\u610F\u5EA6\u8A55\u6BD4{1} + +# appointment pending notify email +email.write.appointment.pending.content=\u60a8\u6709{0}\u5247\u9810\u7d04\u55ae\u672a\u9032\u884c\u806f\u7e6b\uff0c\u8acb\u76e1\u901f\u8655\u7406 + +# appointment expiring notify email +email.write.appointment.expiring.content=\u5f88\u62b1\u6b49\uff01\u60a8\u9810\u7d04{0}\u9867\u554f\u6b63\u5fd9\u788c\u4e2d\uff0c\u8acb\u60a8\u53d6\u6d88\u9810\u7d04\u4e26\u6539\u9078\u5176\u4ed6\u9867\u554f\uff0c\u8acb\u9ede\u64ca\u7db2\u5740\uff1a{1} diff --git a/pamapi/src/main/resources/i18n/messages_zh_TW.properties b/pamapi/src/main/resources/i18n/messages_zh_TW.properties index 04c4825..f53d7c1 100644 --- a/pamapi/src/main/resources/i18n/messages_zh_TW.properties +++ b/pamapi/src/main/resources/i18n/messages_zh_TW.properties @@ -20,5 +20,12 @@ email.reset.text1=\u60A8\u7684 pamapi \u5E33\u865F\u88AB\u8981\u6C42\u91CD\u65B0\u8A2D\u5B9A\u5BC6\u78BC\uFF0C\u8ACB\u9EDE\u4E0B\u5217\u7DB2\u5740\u8A2D\u5B9A: email.reset.text2=\u795D\u60A8\u4F7F\u7528\u6109\u5FEB\uFF0C -# satisfaction write email +# satisfaction write email email.write.satisfaction.content={0}\u9867\u554F\u8ACB\u60A8\u586B\u5BEB\u4FDD\u8AA0\u5A92\u5408\u5E73\u53F0\u7684\u6EFF\u610F\u5EA6\u8A55\u6BD4{1} + +# appointment pending notify email +email.write.appointment.pending.content=\u60a8\u6709{0}\u5247\u9810\u7d04\u55ae\u672a\u9032\u884c\u806f\u7e6b\uff0c\u8acb\u76e1\u901f\u8655\u7406 + +# appointment expiring notify email +email.write.appointment.expiring.content=\u5f88\u62b1\u6b49\uff01\u60a8\u9810\u7d04{0}\u9867\u554f\u6b63\u5fd9\u788c\u4e2d\uff0c\u8acb\u60a8\u53d6\u6d88\u9810\u7d04\u4e26\u6539\u9078\u5176\u4ed6\u9867\u554f\uff0c\u8acb\u9ede\u64ca\u7db2\u5740\uff1a{1} + diff --git a/pamapi/src/main/resources/templates/mail/appointmentExpiringNotifyEmail.html b/pamapi/src/main/resources/templates/mail/appointmentExpiringNotifyEmail.html new file mode 100644 index 0000000..bec1a0b --- /dev/null +++ b/pamapi/src/main/resources/templates/mail/appointmentExpiringNotifyEmail.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" lang="zh"> +<head> + <title>��������</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +</head> +<body> +<p th:text="#{email.write.appointment.expiring.content(${consultantName}, ${notifyUrl})}">敺甇����xx憿批�迤敹�葉 , 隢�����蒂���隞“���</p> +</body> +</html> + diff --git a/pamapi/src/main/resources/templates/mail/appointmentPendingNotifyEmail.html b/pamapi/src/main/resources/templates/mail/appointmentPendingNotifyEmail.html new file mode 100644 index 0000000..29902c7 --- /dev/null +++ b/pamapi/src/main/resources/templates/mail/appointmentPendingNotifyEmail.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" lang="zh"> +<head> + <title>��������</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +</head> +<body> +<p th:text="#{email.write.appointment.pending.content(${pendingAppointmentSum})}">���������脰�蝜恬������</p> +</body> +</html> + -- Gitblit v1.8.0