Browse Source

Merge branch 'master' of http://git.hghweb.ir/ali.s/palizvani-front-vite into production

hgh 1 năm trước cách đây
mục cha
commit
421f398e42
33 tập tin đã thay đổi với 1476 bổ sung và 150 xóa
  1. 4 1
      src/api/models/Classroom.js
  2. 22 2
      src/api/models/Discount.js
  3. 15 0
      src/api/models/Invoice.js
  4. 7 7
      src/components/Template/SideBar/AdminPanelSideBar.vue
  5. 6 13
      src/components/Widgets/Classroom/AdminClassroomShow/components/ActivitySheetList.vue
  6. 1 2
      src/components/Widgets/Event/ShowEventInfo/ShowEventInfo.vue
  7. 20 1
      src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue
  8. 164 0
      src/components/Widgets/Other/Shop/ShopApplyDiscount/OptionPanel.vue
  9. 712 0
      src/components/Widgets/Other/Shop/ShopApplyDiscount/ShopApplyDiscount.vue
  10. 117 0
      src/components/Widgets/Other/Shop/ShopApplyDiscount/discount.vue
  11. 163 0
      src/components/Widgets/Other/Shop/ShopApplyDiscount/sidebar.vue
  12. 17 32
      src/components/Widgets/Other/Shop/ShopCompleteInfo/ShopCompleteInfo.vue
  13. 16 0
      src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue
  14. 1 2
      src/components/Widgets/Other/ShowClassroomInfo/ShowClassroomInfo.vue
  15. 4 4
      src/components/Widgets/Payment/AdminPaymentList/AdminPaymentList.vue
  16. 1 8
      src/components/Widgets/Payment/PaymentList/PaymentList.vue
  17. 23 20
      src/components/Widgets/Store/Cart/components/step1/discount.vue
  18. 12 31
      src/components/Widgets/Store/Cart/components/step1/sidebar.vue
  19. 4 2
      src/components/Widgets/Store/Discount/AdminStoreDiscountCreate/AdminStoreDiscountCreate.vue
  20. 11 3
      src/components/Widgets/Store/Discount/AdminStoreDiscountList/AdminStoreDiscountList.vue
  21. 3 1
      src/components/Widgets/Store/Discount/AdminStoreDiscountShow/AdminStoreDiscountShow.vue
  22. 1 1
      src/components/Widgets/Transaction/AdminTransactionList/AdminPaymentList.vue
  23. 4 1
      src/models/Classroom.js
  24. 14 1
      src/models/Discount.js
  25. 0 1
      src/pages/Admin/Classroom/project/create.vue
  26. 0 1
      src/pages/Admin/Classroom/project/show.vue
  27. 23 0
      src/pages/Admin/InvoiceDiscount/Create.vue
  28. 23 0
      src/pages/Admin/InvoiceDiscount/List.vue
  29. 23 0
      src/pages/Admin/InvoiceDiscount/Show.vue
  30. 1 16
      src/pages/Admin/User/show.vue
  31. 41 0
      src/pages/User/Shop/applyDiscount.vue
  32. 10 0
      src/router/routes/Admin.js
  33. 13 0
      src/router/routes/UserPanel.js

+ 4 - 1
src/api/models/Classroom.js

@@ -118,10 +118,13 @@ export default class ClassroomAPI extends APIRepository {
     })
   }
 
-  register (id) {
+  register (id, data) {
     return this.sendRequest({
       apiMethod: 'put',
       api: this.api,
+      data: this.getNormalizedSendData({
+        discount_id: null // Number
+      }, data),
       request: this.APIAdresses.register(id),
       resolveCallback: (response) => {
         const data = {

+ 22 - 2
src/api/models/Discount.js

@@ -6,8 +6,9 @@ export default class DiscountAPI extends APIRepository {
   constructor() {
     super('discounts', appApi)
     this.APIAdresses = {
-      base: '/store/discounts',
-      byId: (id) => '/store/discounts/' + id
+      base: '/accounting/discounts',
+      byId: (id) => '/accounting/discounts/' + id,
+      verify: '/accounting/discounts/verify'
     }
     this.CacheList = {
       base: this.name + this.APIAdresses.base
@@ -69,6 +70,25 @@ export default class DiscountAPI extends APIRepository {
     })
   }
 
+  verify (data) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.verify,
+      data: this.getNormalizedSendData({
+        code: null, // String
+        target: null, // String
+        target_id: null // Number
+      }, data),
+      resolveCallback: (response) => {
+        return new Discount(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
   add (data) {
     return this.sendRequest({
       apiMethod: 'post',

+ 15 - 0
src/api/models/Invoice.js

@@ -10,6 +10,7 @@ export default class InvoiceAPI extends APIRepository {
       byId: (id) => '/accounting/invoices/' + id,
       pay: (id) => '/accounting/invoices/' + id + '/pay',
       cancel: (id) => '/accounting/invoices/' + id + '/cancel',
+      cancelAllPayingInvoices: '/accounting/invoices/cancel_all_paying_invoices',
       exportInvoiceReport: '/accounting/invoices/export_report'
     }
     this.CacheList = {
@@ -70,6 +71,20 @@ export default class InvoiceAPI extends APIRepository {
     })
   }
 
+  cancelAllPayingInvoices() {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.cancelAllPayingInvoices,
+      resolveCallback: (response) => {
+        return response.data.detail // String : message
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
   cancel(invoiceId) {
     return this.sendRequest({
       apiMethod: 'put',

+ 7 - 7
src/components/Template/SideBar/AdminPanelSideBar.vue

@@ -193,6 +193,13 @@ export default {
               show: true,
               active: false
             },
+            {
+              title: 'کد تخفیف',
+              roles: ['superuser'],
+              routeName: 'Admin.Invoice.Discount.List',
+              show: true,
+              active: false
+            },
             {
               title: 'درخواست ها',
               roles: ['superuser', 'financial_department_head'],
@@ -246,13 +253,6 @@ export default {
               show: true,
               active: false
             },
-            {
-              title: 'کد تخفیف',
-              roles: ['superuser'],
-              routeName: 'Admin.Store.Discount.List',
-              show: true,
-              active: false
-            },
             {
               title: 'هزینه ارسال',
               roles: ['superuser'],

+ 6 - 13
src/components/Widgets/Classroom/AdminClassroomShow/components/ActivitySheetList.vue

@@ -125,25 +125,18 @@ export default {
             field: row => row.owner_info.national_code
           },
           {
-            name: 'is_allowed_absence_count',
+            name: 'absence_count',
             required: true,
-            label: 'حضور و غیاب',
+            label: 'تعداد غیبت',
             align: 'left',
-            field: row => row.is_passed_attendance_condition
+            field: row => row.absence_count
           },
           {
-            name: 'is_project_done',
+            name: 'done_project_count',
             required: true,
-            label: 'پروژه',
+            label: 'پروژه انجام شده',
             align: 'left',
-            field: row => row.is_passed_project_condition
-          },
-          {
-            name: 'is_passed_assignment_condition',
-            required: true,
-            label: 'تکالیف',
-            align: 'left',
-            field: row => row.is_passed_assignment_condition
+            field: row => row.done_project_count
           },
           {
             name: 'done_assignment_count',

+ 1 - 2
src/components/Widgets/Event/ShowEventInfo/ShowEventInfo.vue

@@ -82,8 +82,7 @@
                    @click="onDropping">
               انصراف
             </q-btn>
-            <q-btn v-if="event.current_user_register_info?.status !== 'REGISTERED' && event.current_user_register_info?.status !== 'ENROLLED'"
-                   outline
+            <q-btn outline
                    color="primary"
                    class="btn-register q-ml-md"
                    :loading="fetchCodesLoading"

+ 20 - 1
src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue

@@ -13,7 +13,15 @@
                   :show-expand-button="false"
                   :show-reload-button="false">
       <template #toolbar>
-        <q-btn color="primary"
+        <q-btn class="q-ml-sm"
+               color="red"
+               outline
+               :loading="exportReportLoading"
+               @click="cancelAllPayingInvoices">
+          لغو همه درحال پرداخت
+        </q-btn>
+        <q-btn class="q-ml-sm"
+               color="primary"
                :loading="exportReportLoading"
                @click="getInvoicesListExcel">
           دریافت
@@ -180,6 +188,17 @@ export default {
     search () {
       this.$refs.entityIndex.search()
     },
+    cancelAllPayingInvoices () {
+      this.exportReportLoading = true
+      APIGateway.invoice.cancelAllPayingInvoices()
+        .then(() => {
+          this.$refs.entityIndex.search()
+          this.exportReportLoading = false
+        })
+        .catch((e) => {
+          this.exportReportLoading = false
+        })
+    },
     getInvoicesListExcel () {
       this.exportReportLoading = true
       const filter = this.$refs.entityIndex.createParams()

+ 164 - 0
src/components/Widgets/Other/Shop/ShopApplyDiscount/OptionPanel.vue

@@ -0,0 +1,164 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div class="option-panel-container">
+        <div class="row q-col-gutter-md">
+          <div class="input-container col-12 q-mt-md">
+
+            <div class="outsideLabel">آدرس فایل عکس</div>
+            <q-input v-model="localOptions.imageSource"
+                     label="image" />
+          </div>
+          <!--          <div class="input-container q-py-md">-->
+          <!--            <div class="outsideLabel">height</div>-->
+          <!--            <q-input v-model="localOptions.height"-->
+          <!--                     label="image" />-->
+          <!--          </div>-->
+          <div class="input-container col-4">
+            <div class="outsideLabel">size</div>
+            <q-select v-model="size"
+                      :options="sizeOptions"
+                      label="page size" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">عرض در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].width"
+                     label="width" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">ارتفاع در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].height"
+                     label="height" />
+          </div>
+          <div class="input-container col-12">
+            <div class="outsideLabel">لینک عکس در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].src"
+                     label="image link" />
+          </div>
+          <div class="action col-md-12">
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.useAEEEvent"
+                          label="استفاده از ایونت GTM"
+                          right-label />
+            </div>
+            <div v-if="localOptions.useAEEEvent"
+                 class="action-container q-gutter-lg-md">
+              <div>فیلد های مورد نظر ایونت GTM :</div>
+              <div class="col-9">
+                <div class="outsideLabel">id</div>
+                <q-input v-model="localOptions.AEEEventBody.id"
+                         label="id" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">name</div>
+                <q-input v-model="localOptions.AEEEventBody.name"
+                         label="name" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">creative</div>
+                <q-input v-model="localOptions.AEEEventBody.creative"
+                         label="creative" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">position</div>
+                <q-input v-model="localOptions.AEEEventBody.position"
+                         label="position" />
+              </div>
+            </div>
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.hasAction"
+                          label="hasAction"
+                          right-label />
+            </div>
+            <div v-if="localOptions.hasAction"
+                 class="action-container">
+              <div>
+                <q-select v-model="localOptions.action.name"
+                          :options="actionTypes" />
+              </div>
+              <div v-if="localOptions.action.name === 'link'">
+                <q-input v-model="localOptions.action.route"
+                         label="route" />
+              </div>
+              <div v-if="localOptions.action.name === 'event'">
+                <q-input v-model="localOptions.action.eventName"
+                         label="event name" />
+                <q-input v-model="localOptions.action.eventArgs"
+                         label="event args" />
+              </div>
+              <div v-if="localOptions.action.name === 'scroll'">
+                <q-input v-model="localOptions.action.scrollTo"
+                         label="scrollTo class" />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: { OptionPanelTabs },
+  mixins: [mixinOptionPanel],
+  data() {
+    return {
+      size: 'xs',
+      sizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
+      actionTypes: ['event', 'scroll', 'link'],
+      defaultOptions: {
+        imageSource: null,
+        ratio: null,
+        hasAction: false,
+        useAEEEvent: false,
+        AEEEventBody: {
+          id: '-',
+          name: '-',
+          creative: null,
+          position: null
+        },
+        action: {
+          name: null,
+          route: null,
+          scrollTo: null,
+          eventName: null,
+          eventArgs: null
+        },
+        xs: {
+          height: null,
+          width: null,
+          src: null
+        },
+        sm: {
+          height: null,
+          width: null,
+          src: null
+        },
+        md: {
+          height: null,
+          width: null,
+          src: null
+        },
+        lg: {
+          height: null,
+          width: null,
+          src: null
+        },
+        xl: {
+          height: null,
+          width: null,
+          src: null
+        }
+      }
+    }
+  }
+})
+</script>

+ 712 - 0
src/components/Widgets/Other/Shop/ShopApplyDiscount/ShopApplyDiscount.vue

@@ -0,0 +1,712 @@
+<template>
+  <div class="apply-discount">
+    <q-banner class="banner apply-discount-top-banner">
+      {{ mainTitle }}
+    </q-banner>
+    <q-card class="apply-discount-steps-card">
+      <div class="apply-discount-step">
+        <svg xmlns="http://www.w3.org/2000/svg"
+             width="17.053"
+             height="19.866"
+             viewBox="0 0 17.053 19.866">
+          <g id="Paper"
+             transform="translate(0 0)">
+            <path id="Combined_Shape"
+                  data-name="Combined Shape"
+                  d="M4.475,19.866A4.585,4.585,0,0,1,0,15.188V4.491A4.606,4.606,0,0,1,4.57.012h6.271a.758.758,0,0,1,.265,0h.131a.755.755,0,0,1,.541.23l5.065,5.277a.753.753,0,0,1,.209.519V15.2A4.615,4.615,0,0,1,12.6,19.865H4.475ZM1.5,4.509V15.2a3.091,3.091,0,0,0,3.009,3.162h8.064a3.109,3.109,0,0,0,2.979-3.155V6.984h-2.01a3.332,3.332,0,0,1-3.318-3.325V1.512H4.572A3.089,3.089,0,0,0,1.5,4.509Zm10.224-.85a1.828,1.828,0,0,0,1.82,1.824H14.73L11.724,2.353ZM5.387,14.108a.75.75,0,0,1,0-1.5h5.4a.75.75,0,0,1,0,1.5Zm0-3.752a.75.75,0,0,1,0-1.5H8.743a.75.75,0,0,1,0,1.5Z"
+                  transform="translate(0.001 0)"
+                  fill="#5b7a5f" />
+          </g>
+        </svg>
+        <span class="title">
+          {{ step1Title }}
+          <svg xmlns="http://www.w3.org/2000/svg"
+               width="15"
+               height="15"
+               viewBox="0 0 15 15">
+            <g id="Group_11510"
+               data-name="Group 11510"
+               transform="translate(-1215 -335)">
+              <circle id="Ellipse_819"
+                      data-name="Ellipse 819"
+                      cx="7.5"
+                      cy="7.5"
+                      r="7.5"
+                      transform="translate(1215 335)"
+                      fill="#19cb99" />
+              <path id="Path"
+                    d="M8.615,1.5l-4.75,4.75a.881.881,0,0,1-1.23,0L.255,3.875A.877.877,0,0,1,1.5,2.635l1.75,1.75L7.375.255A.877.877,0,0,1,8.615,1.5"
+                    transform="translate(1218 339)"
+                    fill="#f6f6f6" />
+            </g>
+          </svg>
+        </span>
+      </div>
+      <div class="apply-discount-step">
+        <svg xmlns="http://www.w3.org/2000/svg"
+             width="15.84"
+             height="19.87"
+             viewBox="0 0 15.84 19.87">
+          <g id="Profile"
+             transform="translate(0 0)">
+            <path id="Combined_Shape"
+                  data-name="Combined Shape"
+                  d="M0,16.174c0-3.3,4.521-3.677,7.921-3.677,1.958,0,7.919,0,7.919,3.7,0,3.295-4.52,3.677-7.919,3.677C5.962,19.87,0,19.87,0,16.174Zm1.5,0c0,1.458,2.16,2.2,6.421,2.2s6.419-.733,6.419-2.177S12.18,14,7.921,14,1.5,14.729,1.5,16.174Zm6.389-5.555A5.31,5.31,0,0,1,7.921,0a5.31,5.31,0,0,1,0,10.619ZM4.038,5.31A3.873,3.873,0,0,0,7.892,9.192l.029.715V9.192A3.882,3.882,0,1,0,4.038,5.31Z"
+                  transform="translate(0 0)"
+                  fill="#bcbcbc" />
+          </g>
+        </svg>
+        <span class="title">
+          بازبینی
+        </span>
+      </div>
+      <div class="apply-discount-step">
+        <svg xmlns="http://www.w3.org/2000/svg"
+             width="20.639"
+             height="19.173"
+             viewBox="0 0 20.639 19.173">
+          <g id="Wallet"
+             transform="translate(0)">
+            <path id="Combined_Shape"
+                  data-name="Combined Shape"
+                  d="M6,19.173a6,6,0,0,1-6-6V6A6,6,0,0,1,6,0h8.645a6,6,0,0,1,6,6v7.178a6,6,0,0,1-6,6ZM1.5,6v7.178a4.5,4.5,0,0,0,4.5,4.5h8.645a4.5,4.5,0,0,0,4.5-4.5V12.9h-3.3a3.441,3.441,0,1,1,0-6.881h3.3V6a4.5,4.5,0,0,0-4.5-4.5H6A4.5,4.5,0,0,0,1.5,6ZM13.9,9.455a1.945,1.945,0,0,0,1.942,1.94h3.3V7.513h-3.3A1.946,1.946,0,0,0,13.9,9.455Zm2.088.688a.75.75,0,0,1,0-1.5H16.3a.75.75,0,0,1,0,1.5ZM5.286,6.038a.75.75,0,0,1,0-1.5h5.4a.75.75,0,1,1,0,1.5Z"
+                  transform="translate(0 0)"
+                  fill="#bcbcbc" />
+          </g>
+        </svg>
+        <span class="title">
+          پرداخت
+        </span>
+      </div>
+    </q-card>
+
+    <div class="step1">
+      <div class="step1__cart-items">
+        <div class="cart-item">
+          <div class="cart-item__use-type">
+            <q-icon :name="usageIcon" />
+          </div>
+          <div class="cart-item__thumbnail">
+            <q-img :src="classroom.thumbnail" />
+          </div>
+          <div class="cart-item__info">
+            <div class="cart-item__title">
+              {{ classroom.title }}
+            </div>
+            <div class="cart-item__attrs">
+              <div class="cart-item__attr">
+                <div class="cart-item__attr-label">
+                  نوع برگزاری:
+                </div>
+                <div class="cart-item__attr-value">
+                  {{ getHoldingType(classroom.holding_type) }}
+                </div>
+              </div>
+              <div class="cart-item__attr">
+                <div class="cart-item__attr-label">
+                  استاد:
+                </div>
+                <div class="cart-item__attr-value">
+                  {{ classroom.professor_info.firstname }}
+                  {{ classroom.professor_info.lastname }}
+                </div>
+              </div>
+              <div class="cart-item__attr">
+                <div class="cart-item__attr-label">
+                  هزینه برگزاری:
+                </div>
+                <div class="cart-item__attr-value">
+                  {{ classroom.price.toLocaleString('fa') }}
+                  ریال
+                </div>
+              </div>
+            </div>
+            <div class="cart-item__price">
+              <div class="cart-item__price-final" />
+            </div>
+          </div>
+        </div>
+        <discount :target-id="classroom.id"
+                  :target="getTarget"
+                  @set-discount="setDiscount" />
+      </div>
+      <div class="step1__sidebar">
+        <sidebar :price="classroom.price"
+                 :order-price="orderPrice"
+                 @complete="onComplete" />
+      </div>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { Invoice } from 'src/models/Invoice.js'
+import { mixinAuth } from 'src/mixin/Mixins.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { Classroom } from 'src/models/Classroom.js'
+import { AnswerBook } from 'src/models/AnswerBook.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import Sidebar from 'components/Widgets/Store/Cart/components/step1/sidebar.vue'
+import Discount from 'components/Widgets/Store/Cart/components/step1/discount.vue'
+
+export default {
+  name: 'ShopCompleteInfo',
+  components: { Discount, Sidebar },
+  mixins: [mixinAuth],
+  data: () => ({
+    mounted: false,
+    inputDataLoaded: false,
+    panel: 'userInfo',
+    step: false,
+    rulesAccept: false,
+    rulesDialog: false,
+    loading: true,
+    classroom: new Classroom(),
+    discountId: null,
+    orderPrice: 0,
+    invoice: new Invoice(),
+    eventTestAnswerBook: new AnswerBook(),
+
+    api: APIGateway.user.APIAdresses.current,
+    entityIdKey: 'id',
+    entityParamKey: 'id',
+    showRouteName: 'UserPanel.ShopPaymentFromWallet',
+    inputs: []
+  }),
+  computed: {
+    mainTitle () {
+      if (this.$route.query.source === 'event') {
+        return 'ثبت نام در رویداد'
+      } else {
+        return 'ثبت نام در دوره آموزشی'
+      }
+    },
+    step1Title () {
+      return 'تکمیل اطلاعات'
+    },
+    step2Title () {
+      if (this.$route.query.source === 'event') {
+        return 'مشاهده جزییات و قوانین رویداد'
+      } else {
+        return 'مشاهده جزییات و قوانین دوره'
+      }
+    },
+    getTarget () {
+      if (this.$route.query.source === 'event') {
+        return 'EVENT'
+      } else if (this.$route.query.source === 'classroom') {
+        return 'TRAINING'
+      } else if (this.$route.query.source === 'store') {
+        return 'STORE'
+      } else {
+        return 'NAV'
+      }
+    }
+  },
+  watch: {
+  },
+  mounted () {
+    this.loadClassroomFromStore()
+    this.orderPrice = this.classroom.price
+    if (this.$route.query.source === 'event') {
+      this.getEventTest()
+    }
+    this.mounted = true
+
+    this.$bus.on('event-confirmation-step-test-sent-answer-sending', () => {
+    })
+    this.$bus.on('event-confirmation-step-test-sent-answer-success', () => {
+      this.successSentTest()
+    })
+    this.$bus.on('event-confirmation-step-test-sent-answer-failed', () => {
+      this.sentTestFailed()
+    })
+  },
+  methods: {
+    onComplete () {
+      if (this.$route.query.type === 'register') {
+        this.createInvoiceForClassroomOrRegisterEvent()
+      } else {
+        this.$router.push({ name: 'UserPanel.ShopPaymentFromWallet', query: this.$route.query })
+      }
+    },
+    setDiscount (discount) {
+      console.log(discount)
+      if (!discount) {
+        this.orderPrice = this.classroom.price
+        this.discountId = null
+      } else {
+        this.orderPrice = discount.order_price
+        this.discountId = discount.id
+      }
+    },
+    getEventTest () {
+      if (this.classroom.tests && this.classroom.tests.length === 0) {
+        return
+      }
+      this.getEventTestAnswerBook(this.classroom.tests[0])
+    },
+    getEventTestAnswerBook (eventTestId) {
+      this.eventTestAnswerBook.loading = true
+      APIGateway.answerBook.getOrCreate(this.user.id, eventTestId)
+        .then((answerBook) => {
+          this.eventTestAnswerBook = new AnswerBook(answerBook)
+          this.eventTestAnswerBook.loading = false
+        })
+        .catch(() => {
+          this.eventTestAnswerBook.loading = false
+        })
+    },
+    loadClassroomFromStore () {
+      this.classroom = this.$store.getters['Shop/onRegisterClassroom']
+    },
+    sentTestFailed () {
+      this.panel = 'userInfo'
+    },
+    successSentTest () {
+      this.editEntity()
+    },
+    editEntity () {
+      if (this.eventTestAnswerBook.id && this.panel !== 'eventTest') {
+        this.panel = 'eventTest'
+        return
+      }
+      this.panel = 'userInfo'
+      this.$nextTick(() => {
+        this.$refs.entityEdit.editEntity(false)
+          .then(() => {
+            if (this.$route.query.type === 'register') {
+              this.createInvoiceForClassroomOrRegisterEvent()
+            } else {
+              this.$router.push({ name: 'UserPanel.ShopPaymentFromWallet', query: this.$route.query })
+            }
+          })
+          .catch(() => {})
+      })
+    },
+    createInvoiceForClassroomOrRegisterEvent () {
+      this.register()
+        .then((data) => {
+          if (data === 'Invoice') {
+            this.$router.push({ name: 'UserPanel.ShopPaymentFromWallet', query: this.$route.query })
+          }
+        })
+        .catch(() => {})
+    },
+    register () {
+      return new Promise((resolve, reject) => {
+        this.invoice.loading = true
+        APIGateway.classroom.register(this.classroom.id, { discount_id: this.discountId })
+          .then((data) => {
+            if (data.type === 'Invoice') {
+              this.invoice = new Invoice(data.model)
+              this.invoice.loading = false
+              this.$store.commit('Shop/updateRegisterClassroomInvoice', this.invoice)
+              this.$q.notify({
+                message: 'صورت حساب شما ایجاد شد، برای ثبت نام اقدام فرمایید.',
+                type: 'info'
+              })
+              resolve('Invoice')
+            } else if (data.type === 'Registration') {
+              this.$q.notify({
+                message: 'ثبت نام شما با موفقیت انجام شد.',
+                type: 'positive'
+              })
+              const eventRegistrationId = data.model.classroom_info.id
+              if (this.$route.query.source === 'event') {
+                this.$router.push({ name: 'UserPanel.Profile.EventInfo', params: { id: eventRegistrationId } })
+              } else {
+                this.$router.push({ name: 'UserPanel.Profile.ClassroomInfo', params: { id: eventRegistrationId } })
+              }
+            }
+          })
+          .catch(() => {
+            this.invoice.loading = false
+            reject()
+          })
+      })
+    },
+    createInvoice () {
+      return new Promise((resolve, reject) => {
+        this.invoice.loading = true
+        APIGateway.classroom.createInvoice(this.classroom.id)
+          .then((invoice) => {
+            this.invoice = new Invoice(invoice)
+            this.invoice.loading = false
+            this.$store.commit('Shop/updateRegisterClassroomInvoice', this.invoice)
+            this.$q.notify({
+              message: 'صورت حساب شما ایجاد شد، برای ثبت نام اقدام فرمایید.',
+              type: 'info'
+            })
+            resolve(this.invoice)
+          })
+          .catch(() => {
+            this.invoice.loading = false
+            reject()
+          })
+      })
+    },
+    afterLoadInputData () {
+      this.checkNationalCode()
+      this.$nextTick(() => {
+        this.inputDataLoaded = true
+      })
+    },
+    checkNationalCode () {
+      if (FormBuilderAssist.getInputsByName(this.inputs, 'national_code')?.value) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'national_code', 'readonly', true)
+      }
+    },
+    openRulesDialog () {
+      this.rulesDialog = true
+    },
+    getHoldingType (type) {
+      const target = (new Classroom()).holding_typeEnums.find(item => item.value === type)
+      if (target) {
+        return target.label
+      }
+      return '-'
+    },
+    getTime (time) {
+      return ShamsiDate.getDateTime(time)
+    },
+    getTerm (classroom) {
+      if (!classroom.beginning_registration_period) {
+        return '-'
+      }
+      return ShamsiDate.getTerm(classroom.beginning_registration_period)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.apply-discount {
+  .apply-discount-top-banner {
+    margin-top: 50px;
+    margin-bottom: 42px;
+  }
+  .apply-discount-steps-card {
+    background-color: #F5F5F5;
+    padding: 35px 0;
+    border-radius: 8px;
+    display: flex;
+    flex-flow: row;
+    justify-content: center;
+    margin-bottom: 44px;
+    .apply-discount-step {
+      display: flex;
+      flex-flow: row;
+      position: relative;
+      width: 20%;
+      &:first-child {
+        .title {
+          color: #5B7A5F;
+        }
+      }
+      &:after {
+        content: ' ';
+        position: absolute;
+        left: 0;
+        top: 10px;
+        width: 95%;
+        border-top: dotted 2px #5B7A5F;
+        z-index: 1;
+      }
+      &:last-child {
+        &:after {
+          width: 0;
+        }
+        .title {
+          color: #BCBCBC;
+        }
+      }
+      .title {
+        position: relative;
+        background-color: #F5F5F5;
+        z-index: 2;
+        padding-right: 20px;
+      }
+      svg {
+        width: 17px;
+        position: relative;
+        background-color: #F5F5F5;
+        z-index: 2;
+        padding-right: 8px;
+      }
+    }
+  }
+  .apply-discount-form-banner {
+    margin-bottom: 26px;
+  }
+  .apply-discount-form-card {
+    background-color: #F5F5F5;
+    padding: 50px 30px;
+    border-radius: 8px;
+    &:deep {
+      .q-card.entity-edit {
+        background: transparent;
+        .q-toolbar,
+        & > .q-separator {
+          display: none;
+        }
+      }
+    }
+  }
+  :deep(.panels) {
+    background: transparent;
+  }
+  .section-edit-btn {
+    margin-top: 50px;
+    margin-bottom: 77px;
+  }
+
+  .step1 {
+  $gap: 28px;
+  display: flex;
+  flex-flow: row;
+  flex-wrap: wrap;
+  gap: $gap;
+  $sidebar-width: 312px;
+  .step1__cart-items {
+    width: calc( 100% - #{$sidebar-width} - #{28px} );
+    display: flex;
+    flex-flow: column;
+    gap: 24px;
+    @media screen and (max-width: 1024px) {
+      & {
+        width: 100%;
+      }
+    }
+
+    .cart-item {
+      $cart-background-color: #F9F9FB;
+      $cart-item__thumbnail-width: 192px;
+      position: relative;
+      background: $cart-background-color;
+      border: 1px solid #DFE1EC;
+      border-radius: 8px;
+      padding: 24px 22px 24px 67px;
+      display: flex;
+      flex-flow: row;
+      flex-wrap: wrap;
+      $gap: 30px;
+      gap: $gap;
+      min-height: 203px;
+
+      .cart-item__use-type {
+        $cart-item__use-type-width: 37px;
+        position: absolute;
+        top: 0;
+        left: 16px;
+        width: $cart-item__use-type-width;
+        color: white;
+        text-align: center;
+        padding-top: 10px;
+        padding-bottom: 22px;
+        background: $primary;
+
+        &:after {
+          position: absolute;
+          content: ' ';
+          bottom: calc((#{$cart-item__use-type-width} * -1 / 2) - 9px);
+          left: 0;
+          transform: rotate(45deg);
+          background: $cart-background-color;
+          width: $cart-item__use-type-width;
+          height: $cart-item__use-type-width;
+          border-radius: 2px;
+        }
+      }
+
+      .cart-item__remove-action {
+        position: absolute;
+        top: 85px;
+        left: 20px;
+
+        .q-btn {
+          width: 32px;
+          height: 32px;
+        }
+
+        @media screen and (max-width: 600px) {
+          & {
+            top: 4px;
+            left: auto;
+            right: 4px;
+          }
+        }
+      }
+
+      .cart-item__thumbnail {
+        width: $cart-item__thumbnail-width;
+        @media screen and (max-width: 600px) {
+          & {
+            width: 100%;
+          }
+        }
+      }
+
+      .cart-item__info {
+        width: calc(100% - #{$cart-item__thumbnail-width} - #{$gap});
+        color: #212121;
+
+        .cart-item__title {
+          font-size: 22px;
+          margin-bottom: 27px;
+        }
+
+        .cart-item__attrs {
+          display: flex;
+          flex-flow: column;
+          gap: 16px;
+          //flex-wrap: nowrap;
+          margin-bottom: 70px;
+
+          .cart-item__attr {
+            display: flex;
+            flex-flow: row;
+            gap: 5px;
+            //flex-wrap: nowrap;
+
+            .cart-item__attr-label {
+              font-size: 16px;
+              color: #797979;
+
+              &:before {
+                display: inline-block;
+                content: ' ';
+                width: 8px;
+                height: 8px;
+                background: #475F4A;
+                border-radius: 100%;
+                margin-right: 4px;
+              }
+            }
+
+            .cart-item__attr-value {
+              font-size: 16px;
+            }
+          }
+
+          @media screen and (max-width: 1200px) {
+            & {
+              flex-flow: column;
+              gap: 8px;
+              margin-bottom: 8px;
+            }
+          }
+        }
+
+        .cart-item__type-info {
+          position: absolute;
+          top: 30px;
+          right: 22px;
+          display: flex;
+          flex-flow: row;
+          align-items: center;
+          justify-content: space-between;
+          gap: 2px;
+          padding: 0 4px 0 16px;
+          background: #FFFFFF;
+          border: 1px solid #DFE1EC;
+          border-radius: 20px;
+          color: #212121;
+          font-size: 16px;
+        }
+
+        .cart-item__count-action {
+          position: absolute;
+          right: 20px;
+          top: 65px;
+          width: 120px;
+          @media screen and (max-width: 1200px) {
+            & {
+              position: relative;
+              top: 0;
+              right: 0;
+              width: 100%;
+            }
+          }
+        }
+
+        .cart-item__price {
+          position: absolute;
+          top: 136px;
+          right: 20px;
+          display: flex;
+          flex-flow: row;
+          align-items: center;
+          justify-content: space-between;
+          gap: 19px;
+
+          .cart-item__price-base {
+            color: #FF3D3D;
+            font-size: 18px;
+            text-decoration: line-through;
+          }
+
+          .cart-item__price-final {
+            color: $primary;
+            font-size: 24px;
+          }
+
+          @media screen and (max-width: 1200px) {
+            & {
+              position: relative;
+              top: 0;
+              right: 0;
+              width: 100%;
+              margin-bottom: 8px;
+            }
+          }
+        }
+
+        .cart-item__products {
+          display: flex;
+          flex-flow: row;
+          flex-wrap: wrap;
+
+          .cart-item__product {
+            width: 33.33%;
+            margin-bottom: 17px;
+
+            &:before {
+              display: inline-block;
+              content: ' ';
+              width: 8px;
+              height: 8px;
+              background: #475F4A;
+              border-radius: 100%;
+              margin-right: 4px;
+            }
+          }
+        }
+
+        @media screen and (max-width: 800px) {
+          & {
+            width: 100%;
+          }
+        }
+      }
+
+      @media screen and (max-width: 600px) {
+        & {
+          padding: 56px 22px 24px 24px;
+        }
+      }
+    }
+  }
+  .step1__sidebar {
+    width: $sidebar-width;
+    @media screen and (max-width: 1024px) {
+      & {
+        width: 100%;
+      }
+    }
+  }
+}
+}
+</style>

+ 117 - 0
src/components/Widgets/Other/Shop/ShopApplyDiscount/discount.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="cart-discount">
+    <div class="cart-discount__title">
+      <q-icon name="percent" />
+      کد تخفیف:
+    </div>
+    <div v-if="mounted"
+         class="cart-discount__info">
+      <div class="cart-discount__selected-discount">
+        <q-input v-model="discountCode"
+                 :disable="!!basket.discount"
+                 placeholder="کدتخفیف" />
+      </div>
+      <div class="cart-discount__edit-btn">
+        <q-btn v-if="!basket.discount"
+               color="info"
+               unelevated
+               :loading="discountLoading"
+               @click="setDiscount">
+          اعمال تخفیف
+        </q-btn>
+        <q-btn v-else
+               color="info"
+               unelevated
+               :loading="discountLoading"
+               @click="removeDiscountCode">
+          حذف کد تخفیف
+        </q-btn>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Basket } from 'src/models/Basket.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+
+export default {
+  name: 'CartDiscount',
+  props: {
+    basket: {
+      type: Basket,
+      default: new Basket()
+    }
+  },
+  emits: ['set-discount'],
+  data () {
+    return {
+      discountCode: null,
+      discountLoading: false,
+      mounted: false
+    }
+  },
+  mounted() {
+    this.mounted = true
+    this.discountCode = this.basket.discount_info.code
+  },
+  methods: {
+    openDialog () {
+      this.dialog = true
+    },
+    closeDialog () {
+      this.dialog = false
+    },
+    setDiscount () {
+      this.discountLoading = true
+      APIGateway.basket.submitDiscountCode(this.basket.id, this.discountCode)
+        .finally(() => {
+          this.discountLoading = false
+          this.$emit('set-discount')
+        })
+    },
+    removeDiscountCode () {
+      this.discountLoading = true
+      APIGateway.basket.removeDiscountCode(this.basket.id)
+        .then(() => {
+          this.discountCode = null
+        })
+        .finally(() => {
+          this.discountLoading = false
+          this.$emit('set-discount')
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.cart-discount {
+  border-radius: 8px;
+  background: #F9F9FB;
+  border: 1px solid #DFE1EC;
+  padding: 20px 30px 46px 38px;
+  .cart-discount__title {
+    color: #727272;
+    font-size: 16px;
+    margin-bottom: 22px;
+  }
+  .cart-discount__info {
+    display: flex;
+    flex-flow: row;
+    gap: 16px;
+    justify-content: flex-start;
+    align-items: center;
+    .cart-discount__selected-discount {
+
+    }
+    .cart-discount__edit-btn {
+
+    }
+  }
+  :global(.cart-discount__dialog-cart) {
+    width: 700px;
+    max-width: 90vw;
+  }
+}
+</style>

+ 163 - 0
src/components/Widgets/Other/Shop/ShopApplyDiscount/sidebar.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="step1__sidebar">
+    <div class="step1__sidebar-total-price">
+      <div class="step1__sidebar-total-price-label">
+        قیمت کالاها
+      </div>
+      <div class="step1__sidebar-total-price-value">
+        {{ basket.price.toLocaleString('fa') }}
+        ریال
+      </div>
+    </div>
+    <div v-if="basket.margin_amount"
+         class="step1__sidebar-total-discount">
+      <div class="step1__sidebar-total-discount-label">
+        تخفیف ها
+      </div>
+      <div class="step1__sidebar-total-discount-value">
+        {{ basket.margin_amount.toLocaleString('fa') }}
+        ریال
+      </div>
+    </div>
+    <div v-if="basket.margin_amount"
+         class="step1__sidebar-separator" />
+    <div v-if="basket.margin_amount"
+         class="step1__sidebar-total-price">
+      <div class="step1__sidebar-total-price-label">
+        قابل سفارش
+      </div>
+      <div class="step1__sidebar-total-price-value">
+        {{ basket.sellable_price.toLocaleString('fa') }}
+        ریال
+      </div>
+    </div>
+    <div v-if="basket.discount"
+         class="step1__sidebar-discount-code">
+      <div class="step1__sidebar-discount-code-label">
+        کد تخفیف
+      </div>
+      <div class="step1__sidebar-discount-code-value">
+        {{ basket.discount_amount.toLocaleString('fa') }}
+        ریال
+      </div>
+    </div>
+    <div class="step1__sidebar-separator" />
+    <div class="step1__sidebar-final-price">
+      <div class="step1__sidebar-final-price-label">
+        مجموع
+      </div>
+      <div class="step1__sidebar-final-price-value">
+        {{ basket.order_price.toLocaleString('fa') }}
+        ریال
+      </div>
+    </div>
+    <div class="step1__sidebar-continue-action">
+      <q-btn color="primary"
+             unelevated
+             @click="onComplete">
+        به خرید ادامه بده
+      </q-btn>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Basket } from 'src/models/Basket.js'
+
+export default {
+  name: 'cartItem',
+  props: {
+    basket: {
+      type: Basket,
+      default: new Basket()
+    }
+  },
+  emits: ['complete'],
+  data () {
+    return {
+      step: 1
+    }
+  },
+  methods: {
+    onComplete () {
+      this.$emit('complete')
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.step1__sidebar {
+  width: 100%;
+  background: #F9F9FB;
+  border: 1px solid #DFE1EC;
+  border-radius: 8px;
+  padding: 34px 16px 30px 16px;
+  .step1__sidebar-total-price {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 26px;
+    letter-spacing: 0;
+    .step1__sidebar-total-price-label {
+      color: #212121;
+      font-size: 18px;
+    }
+    .step1__sidebar-total-price-value {
+      color: $primary;
+      font-size: 18px;
+    }
+  }
+  .step1__sidebar-total-discount {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 26px;
+    letter-spacing: 0;
+    color: #FF3D3D;
+    .step1__sidebar-total-discount-label {
+      font-size: 18px;
+    }
+    .step1__sidebar-total-discount-value {
+      font-size: 18px;
+    }
+  }
+  .step1__sidebar-discount-code {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 22px;
+    letter-spacing: 0;
+    color: #FF3D3D;
+    .step1__sidebar-discount-code-label {
+      font-size: 18px;
+    }
+    .step1__sidebar-discount-code-value {
+      font-size: 18px;
+    }
+  }
+  .step1__sidebar-separator {
+    width: 100%;
+    background: #F9F9FB;
+    border: 1px dashed $primary;
+    margin-bottom: 19px;
+  }
+  .step1__sidebar-final-price {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 40px;
+    letter-spacing: 0;
+    .step1__sidebar-final-price-label {
+      color: #212121;
+      font-size: 18px;
+    }
+    .step1__sidebar-final-price-value {
+      font-size: 20px;
+      color: $primary;
+    }
+  }
+  .step1__sidebar-continue-action {
+    padding: 0 16px;
+    .q-btn {
+      width: 100%;
+    }
+  }
+}
+</style>

+ 17 - 32
src/components/Widgets/Other/Shop/ShopCompleteInfo/ShopCompleteInfo.vue

@@ -20,26 +20,6 @@
         </svg>
         <span class="title">
           {{ step1Title }}
-          <svg xmlns="http://www.w3.org/2000/svg"
-               width="15"
-               height="15"
-               viewBox="0 0 15 15">
-            <g id="Group_11510"
-               data-name="Group 11510"
-               transform="translate(-1215 -335)">
-              <circle id="Ellipse_819"
-                      data-name="Ellipse 819"
-                      cx="7.5"
-                      cy="7.5"
-                      r="7.5"
-                      transform="translate(1215 335)"
-                      fill="#19cb99" />
-              <path id="Path"
-                    d="M8.615,1.5l-4.75,4.75a.881.881,0,0,1-1.23,0L.255,3.875A.877.877,0,0,1,1.5,2.635l1.75,1.75L7.375.255A.877.877,0,0,1,8.615,1.5"
-                    transform="translate(1218 339)"
-                    fill="#f6f6f6" />
-            </g>
-          </svg>
         </span>
       </div>
       <div class="complete-info-step">
@@ -53,11 +33,11 @@
                   data-name="Combined Shape"
                   d="M0,16.174c0-3.3,4.521-3.677,7.921-3.677,1.958,0,7.919,0,7.919,3.7,0,3.295-4.52,3.677-7.919,3.677C5.962,19.87,0,19.87,0,16.174Zm1.5,0c0,1.458,2.16,2.2,6.421,2.2s6.419-.733,6.419-2.177S12.18,14,7.921,14,1.5,14.729,1.5,16.174Zm6.389-5.555A5.31,5.31,0,0,1,7.921,0a5.31,5.31,0,0,1,0,10.619ZM4.038,5.31A3.873,3.873,0,0,0,7.892,9.192l.029.715V9.192A3.882,3.882,0,1,0,4.038,5.31Z"
                   transform="translate(0 0)"
-                  fill="#212121" />
+                  fill="#bcbcbc" />
           </g>
         </svg>
         <span class="title">
-          تکمیل اطلاعات
+          بازبینی
         </span>
       </div>
       <div class="complete-info-step">
@@ -313,6 +293,9 @@ export default {
       }
     },
     step1Title () {
+      return 'تکمیل اطلاعات'
+    },
+    step2Title () {
       if (this.$route.query.source === 'event') {
         return 'مشاهده جزییات و قوانین رویداد'
       } else {
@@ -502,11 +485,7 @@ export default {
       this.$nextTick(() => {
         this.$refs.entityEdit.editEntity(false)
           .then(() => {
-            if (this.$route.query.type === 'register') {
-              this.createInvoiceForClassroomOrRegisterEvent()
-            } else {
-              this.$router.push({ name: 'UserPanel.ShopPaymentFromWallet', query: this.$route.query })
-            }
+            this.$router.push({ name: 'UserPanel.ShopApplyDiscount', query: this.$route.query })
           })
           .catch(() => {})
       })
@@ -530,8 +509,8 @@ export default {
               this.invoice.loading = false
               this.$store.commit('Shop/updateRegisterClassroomInvoice', this.invoice)
               this.$q.notify({
-                message: 'صورت حساب شما ایجاد شد',
-                type: 'positive'
+                message: 'صورت حساب شما ایجاد شد، برای ثبت نام اقدام فرمایید.',
+                type: 'info'
               })
               resolve('Invoice')
             } else if (data.type === 'Registration') {
@@ -562,8 +541,8 @@ export default {
             this.invoice.loading = false
             this.$store.commit('Shop/updateRegisterClassroomInvoice', this.invoice)
             this.$q.notify({
-              message: 'صورت حساب شما ایجاد شد',
-              type: 'positive'
+              message: 'صورت حساب شما ایجاد شد، برای ثبت نام اقدام فرمایید.',
+              type: 'info'
             })
             resolve(this.invoice)
           })
@@ -627,8 +606,11 @@ export default {
       position: relative;
       width: 20%;
       &:first-child {
+      }
+
+      &:not(:first-child) {
         .title {
-          color: #5B7A5F;
+          color: #BCBCBC;
         }
       }
       &:after {
@@ -639,6 +621,9 @@ export default {
         width: 95%;
         border-top: dotted 2px #5B7A5F;
         z-index: 1;
+        .title {
+          color: #BCBCBC;
+        }
       }
       &:last-child {
         &:after {

+ 16 - 0
src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue

@@ -285,6 +285,13 @@ export default {
         return 'مشاهده جزییات و قوانین دوره'
       }
     },
+    step2Title () {
+      if (this.$route.query.source === 'event') {
+        return 'مشاهده جزییات و قوانین رویداد'
+      } else {
+        return 'مشاهده جزییات و قوانین دوره'
+      }
+    },
     isRegisterPage () {
       return this.$route.query.type === 'register'
     },
@@ -298,6 +305,7 @@ export default {
     this.checkStoreClassroom()
     this.getInvoiceFromStore()
     this.getMyWallet()
+    this.showEnrollmentHint()
   },
   methods: {
     enrollClassroom () {
@@ -424,6 +432,14 @@ export default {
         return '-'
       }
       return ShamsiDate.getTerm(classroom.beginning_registration_period)
+    },
+    showEnrollmentHint() {
+      if (this.isEnrollmentPage) {
+        this.$q.notify({
+          type: 'info',
+          message: 'توجه: لطفاً، دکمه تایید و پیش‌ثبت‌نام را بزنید.'
+        })
+      }
     }
   }
 }

+ 1 - 2
src/components/Widgets/Other/ShowClassroomInfo/ShowClassroomInfo.vue

@@ -83,8 +83,7 @@
                    @click="onDropping">
               انصراف
             </q-btn>
-            <q-btn v-if="classroom.current_user_register_info?.status !== 'REGISTERED' && classroom.current_user_register_info?.status !== 'ENROLLED'"
-                   outline
+            <q-btn outline
                    color="primary"
                    class="btn-register q-ml-md"
                    :loading="fetchCodesLoading"

+ 4 - 4
src/components/Widgets/Payment/AdminPaymentList/AdminPaymentList.vue

@@ -4,7 +4,7 @@
     <entity-index v-if="mounted"
                   ref="entityIndex"
                   v-model:value="inputs"
-                  title="لیست پرداخت ها"
+                  title="لیست ۲پرداخت ها"
                   :api="api"
                   :table="table"
                   :table-keys="tableKeys"
@@ -95,7 +95,7 @@ export default {
           outsideLabel: 'تا تاریخ',
           col: 'col-md-3 col-12'
         },
-        { type: 'select', name: 'type', options: (new Payment()).typeEnums, label: 'نوع تراکنش', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'type', options: (new Payment()).typeEnums, label: 'نوع۲ تراکنش', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'search', value: null, col: 'col-md-3 col-12', label: 'جست و جو', placeholder: ' ' },
         { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
       ],
@@ -125,9 +125,9 @@ export default {
           {
             name: 'type',
             required: true,
-            label: 'نوع تراکنش',
+            label: 'عنوان',
             align: 'left',
-            field: row => (new Payment(row)).type_info.label
+            field: row => row.title
           },
           {
             name: 'amount',

+ 1 - 8
src/components/Widgets/Payment/PaymentList/PaymentList.vue

@@ -113,14 +113,7 @@ export default {
             required: true,
             label: 'عنوان',
             align: 'left',
-            field: row => row.invoice_info?.title
-          },
-          {
-            name: 'type',
-            required: true,
-            label: 'نوع تراکنش',
-            align: 'left',
-            field: row => (new Payment(row)).type_info.label
+            field: row => row.title
           },
           {
             name: 'amount',

+ 23 - 20
src/components/Widgets/Store/Cart/components/step1/discount.vue

@@ -8,11 +8,11 @@
          class="cart-discount__info">
       <div class="cart-discount__selected-discount">
         <q-input v-model="discountCode"
-                 :disable="!!basket.discount"
+                 :disable="!!discount"
                  placeholder="کدتخفیف" />
       </div>
       <div class="cart-discount__edit-btn">
-        <q-btn v-if="!basket.discount"
+        <q-btn v-if="!discount"
                color="info"
                unelevated
                :loading="discountLoading"
@@ -32,28 +32,29 @@
 </template>
 
 <script>
-import { Basket } from 'src/models/Basket.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 
 export default {
   name: 'CartDiscount',
   props: {
-    basket: {
-      type: Basket,
-      default: new Basket()
+    targetId: {
+      type: Number
+    },
+    target: {
+      type: String
     }
   },
   emits: ['set-discount'],
   data () {
     return {
       discountCode: null,
+      discount: null,
       discountLoading: false,
       mounted: false
     }
   },
   mounted() {
     this.mounted = true
-    this.discountCode = this.basket.discount_info.code
   },
   methods: {
     openDialog () {
@@ -64,22 +65,24 @@ export default {
     },
     setDiscount () {
       this.discountLoading = true
-      APIGateway.basket.submitDiscountCode(this.basket.id, this.discountCode)
-        .finally(() => {
-          this.discountLoading = false
-          this.$emit('set-discount')
-        })
+      APIGateway.discount.verify({
+        code: this.discountCode,
+        target: this.target,
+        target_id: this.targetId
+      }).then((discount) => {
+        this.discount = discount
+        this.$emit('set-discount', discount)
+      }
+      ).finally(() => {
+        this.discountLoading = false
+      })
     },
     removeDiscountCode () {
       this.discountLoading = true
-      APIGateway.basket.removeDiscountCode(this.basket.id)
-        .then(() => {
-          this.discountCode = null
-        })
-        .finally(() => {
-          this.discountLoading = false
-          this.$emit('set-discount')
-        })
+      this.discountCode = null
+      this.discount = null
+      this.$emit('set-discount', null)
+      this.discountLoading = false
     }
   }
 }

+ 12 - 31
src/components/Widgets/Store/Cart/components/step1/sidebar.vue

@@ -2,42 +2,20 @@
   <div class="step1__sidebar">
     <div class="step1__sidebar-total-price">
       <div class="step1__sidebar-total-price-label">
-        قیمت کالاها
+        هزینه برگزاری
       </div>
       <div class="step1__sidebar-total-price-value">
-        {{ basket.price.toLocaleString('fa') }}
+        {{ price.toLocaleString('fa') }}
         ریال
       </div>
     </div>
-    <div v-if="basket.margin_amount"
-         class="step1__sidebar-total-discount">
-      <div class="step1__sidebar-total-discount-label">
-        تخفیف ها
-      </div>
-      <div class="step1__sidebar-total-discount-value">
-        {{ basket.margin_amount.toLocaleString('fa') }}
-        ریال
-      </div>
-    </div>
-    <div v-if="basket.margin_amount"
-         class="step1__sidebar-separator" />
-    <div v-if="basket.margin_amount"
-         class="step1__sidebar-total-price">
-      <div class="step1__sidebar-total-price-label">
-        قابل سفارش
-      </div>
-      <div class="step1__sidebar-total-price-value">
-        {{ basket.sellable_price.toLocaleString('fa') }}
-        ریال
-      </div>
-    </div>
-    <div v-if="basket.discount"
+    <div v-if="price!==orderPrice"
          class="step1__sidebar-discount-code">
       <div class="step1__sidebar-discount-code-label">
         کد تخفیف
       </div>
       <div class="step1__sidebar-discount-code-value">
-        {{ basket.discount_amount.toLocaleString('fa') }}
+        {{ (price-orderPrice).toLocaleString('fa') }}
         ریال
       </div>
     </div>
@@ -47,7 +25,7 @@
         مجموع
       </div>
       <div class="step1__sidebar-final-price-value">
-        {{ basket.order_price.toLocaleString('fa') }}
+        {{ orderPrice.toLocaleString('fa') }}
         ریال
       </div>
     </div>
@@ -62,14 +40,17 @@
 </template>
 
 <script>
-import { Basket } from 'src/models/Basket.js'
 
 export default {
   name: 'cartItem',
   props: {
-    basket: {
-      type: Basket,
-      default: new Basket()
+    price: {
+      type: Number,
+      default: 0
+    },
+    orderPrice: {
+      type: Number,
+      default: 0
     }
   },
   emits: ['complete'],

+ 4 - 2
src/components/Widgets/Store/Discount/AdminStoreDiscountCreate/AdminStoreDiscountCreate.vue

@@ -32,6 +32,7 @@ import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import BtnControl from 'components/Control/btn.vue'
 import FormBuilderDateTime from 'src/components/FormBuilderCustumComponents/FormBuilderDateTime.vue'
+import { Discount } from 'src/models/Discount'
 
 const BtnControlComp = shallowRef(BtnControl)
 const FormBuilderDateTimeComp = shallowRef(FormBuilderDateTime)
@@ -47,11 +48,12 @@ export default {
       api: APIGateway.discount.APIAdresses.base,
       entityIdKey: 'id',
       entityParamKey: 'id',
-      showRouteName: 'Admin.Store.Discount.Show',
+      showRouteName: 'Admin.Invoice.Discount.Show',
       inputs: [
+        { type: 'select', name: 'target', responseKey: 'target', options: (new Discount()).targetEnums, label: 'هدف', placeholder: ' ', col: 'col-md-4 col-12' },
         { type: 'input', name: 'code', responseKey: 'code', label: 'کد', placeholder: ' ', col: 'col-md-4 col-12' },
         { type: 'input', name: 'percent', responseKey: 'percent', label: 'درصد', placeholder: ' ', col: 'col-md-4 col-12' },
-        { type: FormBuilderDateTimeComp, name: 'expire_time', responseKey: 'expire_time', outsideLabel: 'تاریخ انقضا', col: 'col-md-4 col-12' },
+        { type: FormBuilderDateTimeComp, name: 'expire_time', responseKey: 'expire_time', outsideLabel: 'تاریخ انقضا', col: 'col-md-6 col-12' },
         { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ایجاد تخفیف', placeholder: ' ', atClick: () => {}, col: 'col-md-6' }
       ]
     }

+ 11 - 3
src/components/Widgets/Store/Discount/AdminStoreDiscountList/AdminStoreDiscountList.vue

@@ -5,7 +5,7 @@
          class="more-action">
       <q-btn label="ایجاد تخفیف جدید"
              color="primary"
-             :to="{name: 'Admin.Store.Discount.Create'}" />
+             :to="{name: 'Admin.Invoice.Discount.Create'}" />
     </div>
     <entity-index v-if="mounted"
                   v-model:value="inputs"
@@ -27,7 +27,7 @@
         </template>
         <template v-else-if="inputData.col.name === 'action'">
           <q-btn color="primary"
-                 :to="{name: 'Admin.Store.Discount.Show', params: {id: inputData.props.row.id}}">
+                 :to="{name: 'Admin.Invoice.Discount.Show', params: {id: inputData.props.row.id}}">
             مشاهده جزییات
           </q-btn>
         </template>
@@ -44,9 +44,10 @@ import { EntityIndex } from 'quasar-crud'
 import Assist from 'src/assets/js/Assist.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import { Discount } from 'src/models/Discount'
 
 export default {
-  name: 'AdminStoreDiscountList',
+  name: 'AdminInvoiceDiscountList',
   components: { EntityIndex },
   mixins: [mixinWidget],
   props: {
@@ -82,6 +83,13 @@ export default {
             align: 'left',
             field: row => row.id
           },
+          {
+            name: 'target',
+            required: true,
+            label: 'هدف',
+            align: 'left',
+            field: row => (new Discount(row)).target_info.label
+          },
           {
             name: 'code',
             required: true,

+ 3 - 1
src/components/Widgets/Store/Discount/AdminStoreDiscountShow/AdminStoreDiscountShow.vue

@@ -34,6 +34,7 @@ import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import BtnControl from 'src/components/Control/btn.vue'
 import FormBuilderDateTime from 'src/components/FormBuilderCustumComponents/FormBuilderDateTime.vue'
+import { Discount } from 'src/models/Discount'
 
 const BtnControlComp = shallowRef(BtnControl)
 const FormBuilderDateTimeComp = shallowRef(FormBuilderDateTime)
@@ -54,9 +55,10 @@ export default {
       showRouteName: 'Admin.Store.Discount.Show',
       indexRouteName: 'Admin.Store.Discount.List',
       inputs: [
+        { type: 'select', name: 'target', responseKey: 'target', options: (new Discount()).targetEnums, label: 'هدف', placeholder: ' ', col: 'col-md-4 col-12' },
         { type: 'input', name: 'code', responseKey: 'code', label: 'کد', placeholder: ' ', col: 'col-md-4 col-12' },
         { type: 'input', name: 'percent', responseKey: 'percent', label: 'درصد', placeholder: ' ', col: 'col-md-4 col-12' },
-        { type: FormBuilderDateTimeComp, name: 'expire_time', responseKey: 'expire_time', outsideLabel: 'تاریخ انقضا', col: 'col-md-4 col-12' },
+        { type: FormBuilderDateTimeComp, name: 'expire_time', responseKey: 'expire_time', outsideLabel: 'تاریخ انقضا', col: 'col-md-6 col-12' },
         { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ویرایش تخفیف', placeholder: ' ', ignoreValue: true, atClick: () => {}, col: 'col-md-6' }
       ]
     }

+ 1 - 1
src/components/Widgets/Transaction/AdminTransactionList/AdminPaymentList.vue

@@ -88,7 +88,7 @@ export default {
           outsideLabel: 'تا تاریخ',
           col: 'col-md-3 col-12'
         },
-        { type: 'select', name: 'type', options: (new Payment()).typeEnums, label: 'نوع تراکنش', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'type', options: (new Payment()).typeEnums, label: 'نوع۲ تراکنش', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
       ],
       table: {

+ 4 - 1
src/models/Classroom.js

@@ -50,7 +50,10 @@ class Classroom extends Model {
         relatedModel: SessionList
       },
       { key: 'audience_role' },
-      { key: 'price' },
+      {
+        key: 'price',
+        default: 0
+      },
       { key: 'codes' },
       { key: 'capacity' },
       { key: 'used_capacity' },

+ 14 - 1
src/models/Discount.js

@@ -5,12 +5,25 @@ class Discount extends Model {
     super(data, [
       { key: 'id' },
       { key: 'code' },
+      { key: 'price' },
+      { key: 'order_price' },
+      { key: 'target' },
       {
         key: 'percent',
         default: 0
       },
       { key: 'expire_time' }
-    ])
+    ], {
+      target: {
+        infoKey: 'target_info',
+        enumListKey: 'targetEnums',
+        enums: [
+          { label: 'فروشگاه', value: 'STORE' },
+          { label: 'دوره آموزشی', value: 'TRAINING' },
+          { label: 'رویداد', value: 'EVENT' }
+        ]
+      }
+    })
   }
 }
 

+ 0 - 1
src/pages/Admin/Classroom/project/create.vue

@@ -77,7 +77,6 @@ export default {
         { type: FormBuilderDateTimeComp, name: 'ending_doing_period', responseKey: 'ending_doing_period', outsideLabel: 'زمان پایان', col: 'col-md-6 col-12' },
         { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات پروژه', placeholder: ' ', col: 'col-12' },
-        { type: 'inputEditor', name: 'correct_answer', responseKey: 'correct_answer', label: 'پاسخ صحیح', placeholder: ' ', col: 'col-12' },
         { type: ContentsSelectorComp, name: 'contents', responseKey: 'contents_info', col: 'col-12' },
         { type: 'hidden', name: 'classroom', responseKey: 'classroom', value: parseInt(classroomId) }
       ]

+ 0 - 1
src/pages/Admin/Classroom/project/show.vue

@@ -67,7 +67,6 @@ export default {
         { type: FormBuilderDateTimeComp, name: 'ending_doing_period', responseKey: 'ending_doing_period', outsideLabel: 'زمان پایان', col: 'col-md-6 col-12' },
         { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات پروژه', placeholder: ' ', col: 'col-12' },
-        { type: 'inputEditor', name: 'correct_answer', responseKey: 'correct_answer', label: 'پاسخ صحیح', placeholder: ' ', col: 'col-12' },
         { type: ContentsSelectorComp, name: 'contents', responseKey: 'contents_info', col: 'col-12' },
         { type: 'hidden', name: 'id', responseKey: 'id' },
         { type: 'hidden', name: 'classroom', responseKey: 'classroom' }

+ 23 - 0
src/pages/Admin/InvoiceDiscount/Create.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Store.Discount.Create',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminStoreDiscountCreate' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/InvoiceDiscount/List.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Store.Discount.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminStoreDiscountList' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/InvoiceDiscount/Show.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Store.Discount.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminStoreDiscountShow' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 1 - 16
src/pages/Admin/User/show.vue

@@ -244,7 +244,6 @@
 import Assist from 'assets/js/Assist.js'
 import Enums from 'src/assets/Enums/Enums.js'
 import { Invoice } from 'src/models/Invoice.js'
-import { Payment } from 'src/models/Payment.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { Classroom } from 'src/models/Classroom.js'
 import { EntityEdit, EntityIndex } from 'quasar-crud'
@@ -786,21 +785,7 @@ export default {
             required: true,
             label: 'عنوان',
             align: 'left',
-            field: row => row.invoice_info?.title
-          },
-          {
-            name: 'type',
-            required: true,
-            label: 'نوع تراکنش',
-            align: 'left',
-            field: row => (new Payment(row)).type_info.label
-          },
-          {
-            name: 'invoice_info.status',
-            required: true,
-            label: 'وضعیت',
-            align: 'left',
-            field: row => (new Invoice(row.invoice_info)).status_info.label
+            field: row => row.title
           },
           {
             name: 'amount',

+ 41 - 0
src/pages/User/Shop/applyDiscount.vue

@@ -0,0 +1,41 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+export default {
+  name: 'applyDiscount',
+  data () {
+    return {
+      pageConfig: {},
+      pageBuilderEditable: false,
+      pageBuilderLoading: false,
+      currenSections: [
+        {
+          data: {
+            rows: [
+              {
+                cols: [
+                  {
+                    widgets: [
+                      {
+                        name: 'ShopApplyDiscount'
+                      }
+                    ]
+                  }
+                ],
+                options: {
+                  boxed: true
+                }
+              }
+            ]
+          }
+        }
+      ]
+    }
+  }
+}
+</script>

+ 10 - 0
src/router/routes/Admin.js

@@ -383,6 +383,16 @@ export default {
               component: () => import('src/pages/Admin/Invoice/Ticket/Show.vue')
             }
           ]
+        },
+        {
+          path: 'discount',
+          name: 'Admin.Invoice.Discount',
+          component: () => import('layouts/bareLayout.vue'),
+          children: [
+            { path: '', name: 'Admin.Invoice.Discount.List', component: () => import('src/pages/Admin/InvoiceDiscount/List.vue') },
+            { path: 'create', name: 'Admin.Invoice.Discount.Create', component: () => import('src/pages/Admin/StoreDiscount/Create.vue') },
+            { path: ':id', name: 'Admin.Invoice.Discount.Show', component: () => import('src/pages/Admin/InvoiceDiscount/Show.vue') }
+          ]
         }
       ]
     },

+ 13 - 0
src/router/routes/UserPanel.js

@@ -31,6 +31,19 @@ export default {
       },
       component: () => import('src/pages/User/Shop/completeInfo.vue')
     },
+    {
+      name: 'UserPanel.ShopApplyDiscount',
+      path: 'shop/applyDiscount',
+      layoutConfig: {
+        layoutView: 'hHh LpR fff',
+        layoutHeaderType: 'main',
+        layoutHeaderBordered: true,
+        layoutLeftDrawer: false,
+        layoutLeftDrawerVisible: false,
+        layoutFooter: true
+      },
+      component: () => import('src/pages/User/Shop/applyDiscount.vue')
+    },
     {
       name: 'UserPanel.ShopPaymentFromWallet',
       path: 'shop/payment-from-wallet',