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