ali 3 лет назад
Родитель
Сommit
2cc9cd982c
35 измененных файлов с 2431 добавлено и 428 удалено
  1. 4 0
      src/api/APIGateway.js
  2. 64 3
      src/api/models/Classroom.js
  3. 130 0
      src/api/models/Invoice.js
  4. 85 0
      src/api/models/Wallet.js
  5. 15 61
      src/boot/axios.js
  6. 160 0
      src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue
  7. 21 0
      src/components/Widgets/Invoice/AdminInvoiceList/OptionPanel.vue
  8. 343 0
      src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue
  9. 21 0
      src/components/Widgets/Invoice/AdminInvoiceShow/OptionPanel.vue
  10. 146 0
      src/components/Widgets/Invoice/InvoiceList/InvoiceList.vue
  11. 21 0
      src/components/Widgets/Invoice/InvoiceList/OptionPanel.vue
  12. 297 0
      src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue
  13. 21 0
      src/components/Widgets/Invoice/InvoiceShow/OptionPanel.vue
  14. 42 0
      src/components/Widgets/Invoice/InvoiceThankyou/InvoiceThankyou.vue
  15. 21 0
      src/components/Widgets/Invoice/InvoiceThankyou/OptionPanel.vue
  16. 245 124
      src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue
  17. 70 0
      src/components/Widgets/Other/Shop/ShopPaymentFromWallet/components/InvoiceInfo.vue
  18. 288 0
      src/components/Widgets/Other/Shop/ShopPaymentFromWallet/components/PaymentCard.vue
  19. 0 41
      src/css/Theme/FormBuilder/checkbox.scss
  20. 0 23
      src/css/Theme/FormBuilder/editor.scss
  21. 0 4
      src/css/Theme/FormBuilder/formBuilderCol.scss
  22. 0 14
      src/css/Theme/FormBuilder/select.scss
  23. 18 11
      src/css/Theme/FormBuilder/separator.scss
  24. 0 45
      src/css/Theme/FormBuilder/toggleButton.scss
  25. 1 1
      src/css/Theme/theme.scss
  26. 83 0
      src/models/Basket.js
  27. 94 0
      src/models/Invoice.js
  28. 34 19
      src/models/Registration.js
  29. 29 0
      src/models/Wallet.js
  30. 80 75
      src/pages/Admin/Classroom/show.vue
  31. 23 0
      src/pages/User/Invoice/List.vue
  32. 23 0
      src/pages/User/Invoice/Show.vue
  33. 22 0
      src/pages/User/Invoice/Thankyou.vue
  34. 29 7
      src/router/routes.js
  35. 1 0
      src/store/index.js

+ 4 - 0
src/api/APIGateway.js

@@ -2,6 +2,8 @@ import SeoAPI from 'src/api/models/Seo.js'
 import AuthAPI from 'src/api/models/Auth.js'
 import AuthAPI from 'src/api/models/Auth.js'
 import UserAPI from 'src/api/models/User.js'
 import UserAPI from 'src/api/models/User.js'
 import UnitAPI from 'src/api/models/Unit.js'
 import UnitAPI from 'src/api/models/Unit.js'
+import WalletAPI from 'src/api/models/Wallet.js'
+import InvoiceAPI from 'src/api/models/Invoice.js'
 import SessionAPI from 'src/api/models/Session.js'
 import SessionAPI from 'src/api/models/Session.js'
 import ClassroomAPI from 'src/api/models/Classroom.js'
 import ClassroomAPI from 'src/api/models/Classroom.js'
 import PageSettingAPI from 'src/api/models/PageSetting.js'
 import PageSettingAPI from 'src/api/models/PageSetting.js'
@@ -16,6 +18,8 @@ export const APIGateway = {
   auth: new AuthAPI(),
   auth: new AuthAPI(),
   user: new UserAPI(),
   user: new UserAPI(),
   unit: new UnitAPI(),
   unit: new UnitAPI(),
+  wallet: new WalletAPI(),
+  invoice: new InvoiceAPI(),
   session: new SessionAPI(),
   session: new SessionAPI(),
   classroom: new ClassroomAPI(),
   classroom: new ClassroomAPI(),
   pageSetting: new PageSettingAPI(),
   pageSetting: new PageSettingAPI(),

+ 64 - 3
src/api/models/Classroom.js

@@ -1,4 +1,5 @@
 import { appApi } from 'src/boot/axios.js'
 import { appApi } from 'src/boot/axios.js'
+import { Invoice } from 'src/models/Invoice.js'
 import APIRepository from '../classes/APIRepository.js'
 import APIRepository from '../classes/APIRepository.js'
 import { Classroom, ClassroomList } from 'src/models/Classroom.js'
 import { Classroom, ClassroomList } from 'src/models/Classroom.js'
 
 
@@ -7,7 +8,11 @@ export default class ClassroomAPI extends APIRepository {
     super('classrooms', appApi, '/lma/classrooms', Classroom)
     super('classrooms', appApi, '/lma/classrooms', Classroom)
     this.APIAdresses = {
     this.APIAdresses = {
       base: '/lma/classrooms',
       base: '/lma/classrooms',
-      byId: (id) => '/lma/classrooms/' + id
+      byId: (id) => '/lma/classrooms/' + id,
+      enroll: (id) => '/lma/classrooms/' + id + '/enroll',
+      enrollByAdmin: (classroomId, userId) => '/lma/classrooms/' + classroomId + '/enrolll?user_id=' + userId,
+      createInvoice: (id) => '/lma/classrooms/' + id + '/create_invoice',
+      createInvoiceByAdmin: (classroomId, userId) => '/lma/classrooms/' + classroomId + '/create_invoice?user_id=' + userId
     }
     }
     this.CacheList = {
     this.CacheList = {
       base: this.name + this.APIAdresses.base,
       base: this.name + this.APIAdresses.base,
@@ -15,7 +20,7 @@ export default class ClassroomAPI extends APIRepository {
     }
     }
   }
   }
 
 
-  index(data) {
+  index (data) {
     return this.sendRequest({
     return this.sendRequest({
       apiMethod: 'get',
       apiMethod: 'get',
       api: this.api,
       api: this.api,
@@ -47,7 +52,7 @@ export default class ClassroomAPI extends APIRepository {
     })
     })
   }
   }
 
 
-  get(id) {
+  get (id) {
     return this.sendRequest({
     return this.sendRequest({
       apiMethod: 'get',
       apiMethod: 'get',
       api: this.api,
       api: this.api,
@@ -60,4 +65,60 @@ export default class ClassroomAPI extends APIRepository {
       }
       }
     })
     })
   }
   }
+
+  enrollByUser (id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.enroll(id),
+      resolveCallback: (response) => {
+        return new Classroom(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  enrollByAdmin (classroomId, userId) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.enrollByAdmin(classroomId, userId),
+      resolveCallback: (response) => {
+        return new Classroom(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  createInvoice (id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.createInvoice(id),
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  createInvoiceByAdmin (classroomId, userId) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.createInvoiceByAdmin(classroomId, userId),
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
 }
 }

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

@@ -0,0 +1,130 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { Invoice, InvoiceList } from 'src/models/Invoice.js'
+
+export default class InvoiceAPI extends APIRepository {
+  constructor() {
+    super('invoice', appApi)
+    this.APIAdresses = {
+      base: '/accounting/invoices',
+      byId: (id) => '/accounting/invoices/' + id,
+      pay: (id) => '/accounting/invoices/' + id + '/pay'
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base
+    }
+    this.restUrl = (id) => this.APIAdresses.base + '/' + id
+    /* Setting the callback functions for the CRUD operations. */
+    this.setCrudCallbacks({
+      get: (response) => { return new Invoice(response.data) },
+      post: (response) => { return new Invoice(response.data) },
+      put: (response) => { return new Invoice(response.data) },
+      delete: (response) => { return response.data }
+    })
+  }
+
+  index(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        page: 1 // Number
+      }, data),
+      resolveCallback: (response) => {
+        const paginate = response.data
+        const results = response.data.results
+        delete paginate.results
+        return {
+          list: new InvoiceList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  pay(invoiceId) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.pay(invoiceId),
+      resolveCallback: (response) => {
+        return response.data.detail // String : message
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  createService (products = []) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: {
+        type: 'SERVICE', // String
+        products // Array of Object
+      },
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  createBasket (basketType, basketId) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: {
+        type: basketType.toUpperCase(), // String
+        products: [
+          {
+            product_type: basketType + '_basket',
+            product_id: basketId,
+            count: 1
+          }
+        ]
+      },
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  create (type, products = []) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: {
+        type, // String
+        products // Array of Object
+      },
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 85 - 0
src/api/models/Wallet.js

@@ -0,0 +1,85 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { Wallet, WalletList } from 'src/models/Wallet.js'
+
+export default class WalletAPI extends APIRepository {
+  constructor() {
+    super('wallets', appApi)
+    this.APIAdresses = {
+      base: '/accounting/wallets',
+      deposit: '/accounting/wallets/deposit'
+    }
+    this.restUrl = (id) => this.url + '/' + id
+    /* Setting the callback functions for the CRUD operations. */
+    this.setCrudCallbacks({
+      get: (response) => { return new Wallet(response.data) },
+      post: (response) => { return new Wallet(response.data) },
+      put: (response) => { return new Wallet(response.data) },
+      delete: (response) => { return response.data }
+    })
+  }
+
+  index(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        owner: null, // Number
+        page: 1 // Number
+      }, data),
+      resolveCallback: (response) => {
+        const paginate = response.data
+        const results = response.data.results
+        delete paginate.results
+        return {
+          list: new WalletList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  deposit(data) {
+    const amount = data.amount ? data.amount * 10 : 0
+    const invoice = data.invoice ? data.invoice : null
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.deposit,
+      data: { amount, invoice },
+      resolveCallback: (response) => {
+        return response.data.url // String -> redirect url // thankyou
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  getMyWallet (userId) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: { owner: userId },
+      resolveCallback: (response) => {
+        return new Wallet(response.data.results[0])
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 15 - 61
src/boot/axios.js

@@ -13,38 +13,6 @@ const apiServerTarget = process.env.APP_API_SERVER
 // const webServer = process.env.ALAA_WEB
 // const webServer = process.env.ALAA_WEB
 // const webServerTarget = process.env.ALAA_WEB_SERVER
 // const webServerTarget = process.env.ALAA_WEB_SERVER
 
 
-const AjaxResponseMessages = (function () {
-  const messageMap = {
-    0: 'مشکلی پیش آمده است. مجدد تلاش کنید.',
-    400: 'ابتدا وارد سامانه شوید.',
-    401: 'ابتدا وارد سامانه شوید.',
-    1: 'پیش از این در این آزمون ثبت نام انجام شده است.',
-    2: 'زمان آزمون فرا نرسیده است',
-    3: 'ثبت نام در این آزمون انجام نشده است.',
-    4: 'دانش آموز برای این آزمون ثبت نام نکرده است.',
-    5: 'نتیجه آزمونی برای این آزمون وجود ندارد.',
-    6: 'پاسخنامه داوطلب پیش از این ارسال شده است.',
-    7: 'زمان پاسخگویی قبل از شروع آزمون است.',
-    8: 'آزمون متعلق به کاربر نیست.',
-    13: 'سوالات آزمون آماده نشده است.',
-    14: 'آزمون بسته شده است.',
-    17: 'ثبت درس تکراری در یک دفترچه امکان پذیر نیست.'
-  }
-
-  function isCustomMessage(statusCode) {
-    return !!(messageMap[statusCode.toString()])
-  }
-
-  function getMessage(statusCode) {
-    return messageMap[statusCode]
-  }
-
-  return {
-    isCustomMessage,
-    getMessage
-  }
-}())
-
 const AxiosHooks = (function () {
 const AxiosHooks = (function () {
   let $notify = null
   let $notify = null
 
 
@@ -55,8 +23,8 @@ const AxiosHooks = (function () {
     $notify = $q.notify
     $notify = $q.notify
   }
   }
 
 
-  function handleErrors(error, router, store) {
-    let messages = []
+  function handleErrors (error, router, store) {
+    const messages = []
     if (!error || !error.response) {
     if (!error || !error.response) {
       return
       return
     }
     }
@@ -66,27 +34,22 @@ const AxiosHooks = (function () {
     } else if (statusCode === 404) {
     } else if (statusCode === 404) {
       messages.push('موردی یافت نشد.')
       messages.push('موردی یافت نشد.')
     } else if (statusCode === 401) {
     } else if (statusCode === 401) {
-      messages.push('ابتدا وارد سامانه شوید.')
+      const message = error.response.data.message
+      messages.push(message)
       deAuthorizeUser(router, store)
       deAuthorizeUser(router, store)
-    } else if (error.response.data.error && AjaxResponseMessages.isCustomMessage(error.response.data.error.code)) {
-      console.error('error.response.data.error.code', AjaxResponseMessages.getMessage(error.response.data.error.code))
-      messages.push(AjaxResponseMessages.getMessage(error.response.data.error.code))
-    } else if (error.response.data.error && !AjaxResponseMessages.isCustomMessage(error.response.data.error.code)) {
-      for (const [key, value] of Object.entries(error.response.data.error)) {
-        if (typeof error.response.data.error[key] === 'string') {
-          messages.push(value)
-        }
-      }
-    } else if (error.response.data.errors) {
-      for (const [key, value] of Object.entries(error.response.data.errors)) {
-        if (typeof error.response.data.errors[key] === 'string') {
-          messages.push(value)
-        } else {
-          messages = messages.concat(getMessagesFromArrayWithRecursion(value))
+    } else if (statusCode === 403) {
+      const message = error.response.data.detail
+      messages.push(message)
+    } else if (error.response.data) {
+      for (const key of Object.keys(error.response.data)) {
+        if (typeof error.response.data[key] === 'string') {
+          messages.push(key + ': ' + error.response.data[key])
+        } else if (Array.isArray(error.response.data[key])) {
+          error.response.data[key].forEach(message => {
+            messages.push(key + ': ' + message)
+          })
         }
         }
       }
       }
-    } else if (!error.response.data.errors && error.response.data.message) {
-      messages.push(error.response.data.message)
     }
     }
 
 
     toastMessages(messages)
     toastMessages(messages)
@@ -126,15 +89,6 @@ const AxiosHooks = (function () {
     })
     })
   }
   }
 
 
-  function getMessagesFromArrayWithRecursion(array) {
-    if (array) {
-      if (Array.isArray(array)) {
-        return array.flat(Math.min())
-      }
-      return array[0]
-    }
-  }
-
   return {
   return {
     handleErrors,
     handleErrors,
     setNotifyInstance
     setNotifyInstance

+ 160 - 0
src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="AdminInvoiceList"
+       :style="localOptions.style">
+    <div class="title">
+      سفارشات
+    </div>
+    <q-card class="list"
+            flat>
+      <entity-index v-model:value="inputs"
+                    title=""
+                    :api="api"
+                    :table="table"
+                    :table-keys="tableKeys"
+                    :create-route-name="createRouteName"
+                    :show-search-button="false"
+                    :show-expand-button="false"
+                    :show-reload-button="false"
+                    :default-layout="false">
+        <template #entity-index-table-cell="{inputData}">
+          <template v-if="inputData.col.name === 'action'">
+            <q-btn flat
+                   color="primary"
+                   :to="{name: 'AdminPanel.Invoice.Show', params: {id: inputData.props.row.id}}">
+              مشاهده جزییات
+            </q-btn>
+          </template>
+          <template v-else>
+            {{ inputData.col.value }}
+          </template>
+        </template>
+      </entity-index>
+    </q-card>
+  </div>
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import Assist from 'assets/js/Assist'
+import { Invoice } from 'src/models/Invoice'
+
+export default {
+  name: 'AdminInvoiceList',
+  components: { EntityIndex },
+  mixins: [mixinWidget],
+  data: () => {
+    return {
+      defaultOptions: {
+        invoiceType: null
+      },
+      api: APIGateway.invoice.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [],
+      table: {
+        columns: [
+          {
+            name: 'id',
+            required: true,
+            label: 'صورت‌حساب',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'پکیج یا خدمت',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'owner_info',
+            required: true,
+            label: 'صاحب سفارش',
+            align: 'left',
+            field: row => row.owner_info.firstname + ' ' + row.owner_info.lastname
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'تاریخ صورت‌حساب',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.creation_time)
+          },
+          {
+            name: 'amount',
+            required: true,
+            label: 'کل',
+            align: 'left',
+            field: row => row.amount.toLocaleString('fa')
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => (new Invoice(row)).status_info.label
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: row => ''
+          }
+        ]
+      },
+      createRouteName: ''
+    }
+  },
+  created() {
+    const shopServiceNameInRouteParams = this.$route.params?.shopServiceName
+    if (shopServiceNameInRouteParams) {
+      this.inputs.push({
+        type: 'hidden',
+        name: 'type',
+        value: shopServiceNameInRouteParams.toUpperCase()
+      })
+    } else {
+      this.inputs.push({
+        type: 'hidden',
+        name: 'type',
+        value: 'SERVICE'
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminInvoiceList {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+  }
+  :deep(.list) {
+    .quasar-crud-index-table {
+      padding: 0;
+      .q-table__container {
+        background-color: transparent;
+        box-shadow: none;
+        border: none;
+        .q-table__top {
+          display: none;
+        }
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Invoice/AdminInvoiceList/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </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]
+})
+</script>

+ 343 - 0
src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue

@@ -0,0 +1,343 @@
+<template>
+  <div class="AdminInvoiceShow"
+       :style="localOptions.style">
+    <div class="title">
+      <div class="static-title">
+        سفارش
+      </div>
+      <div class="dynamic-title">
+        <template v-if="entityLoading">
+          <q-skeleton type="text"
+                      class="q-ml-lg"
+                      width="100px" />
+        </template>
+        <template v-else>
+          {{ serviceTitle }}
+        </template>
+      </div>
+      <div class="back-action">
+        <q-btn flat
+               :to="{name: 'AdminPanel.Invoice.List'}"
+               color="grey">
+          بازگشت
+          >
+        </q-btn>
+      </div>
+    </div>
+    <q-card class="form"
+            flat>
+      <entity-edit v-if="mounted"
+                   ref="entityEdit"
+                   v-model:value="inputs"
+                   :api="api"
+                   :entity-id-key="entityIdKey"
+                   :entity-param-key="entityParamKey"
+                   :show-route-name="showRouteName"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false"
+                   :default-layout="false"
+                   :redirect-after-edit="false"
+                   :after-load-input-data="afterLoadInputData" />
+      <div class="action">
+        <div class="row q-mt-lg justify-end">
+          <div class="col-md-4 col-12">
+            <q-btn color="primary"
+                   class="full-width"
+                   :loading="entityLoading"
+                   @click="edit">
+              ثبت تغییرات
+            </q-btn>
+          </div>
+        </div>
+      </div>
+    </q-card>
+    <q-card v-if="invoice.products_info.list.length > 0"
+            class="q-mt-lg">
+      <q-card-section>
+        <div class="selectedProductsList">
+          <div class="selectedProductsList-row head">
+            <div class="selectedProductsList-title">
+              شرح خدمات
+            </div>
+            <div class="selectedProductsList-count">
+              تعداد
+            </div>
+            <div class="selectedProductsList-price">
+              مبلغ
+            </div>
+          </div>
+          <div v-for="item in invoice.products_info.list"
+               :key="item.id">
+            <template v-if="item.registrations_info.list.length > 0">
+              <div v-for="registrationsInfoItem in item.registrations_info.list"
+                   :key="registrationsInfoItem.id"
+                   class="selectedProductsList-row">
+                <div class="selectedProductsList-title">
+                  {{ registrationsInfoItem.item_info.title }}
+                </div>
+                <div class="selectedProductsList-count">
+                  {{ registrationsInfoItem.count }}
+                </div>
+                <div class="selectedProductsList-price">
+                  {{ (registrationsInfoItem.item_info.unit_price).toLocaleString('fa') }}
+                  تومان
+                </div>
+              </div>
+            </template>
+            <template v-else>
+              <div class="selectedProductsList-row">
+                <div class="selectedProductsList-title">
+                  {{ item.title }}
+                </div>
+                <div class="selectedProductsList-count">
+                  -
+                </div>
+                <div class="selectedProductsList-price">
+                  {{ (item.unit_price).toLocaleString('fa') }}
+                  تومان
+                </div>
+              </div>
+            </template>
+          </div>
+          <div class="selectedProductsList-row footer">
+            <div class="selectedProductsList-title">
+              قابل پرداخت
+            </div>
+            <div class="selectedProductsList-count" />
+            <div class="selectedProductsList-price">
+              {{ (invoice.amount) ? invoice.amount.toLocaleString('fa') : 0 }}
+              تومان
+            </div>
+          </div>
+        </div>
+      </q-card-section>
+    </q-card>
+  </div>
+</template>
+
+<script>
+import { EntityEdit } from 'quasar-crud'
+import { Invoice } from 'src/models/Invoice.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+
+export default {
+  name: 'AdminInvoiceShow',
+  components: {
+    EntityEdit
+  },
+  mixins: [mixinWidget],
+  data: () => {
+    return {
+      mounted: false,
+      entityLoading: true,
+      serviceTitle: '',
+      services: [],
+      invoice: new Invoice(),
+      api: APIGateway.invoice.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'AdminPanel.Invoice.Show',
+      indexRouteName: 'AdminPanel.Invoice.List',
+      inputs: [
+        {
+          type: 'date',
+          name: 'creation_time',
+          responseKey: 'creation_time',
+          label: 'تاریخ صورت‌حساب',
+          placeholder: ' ',
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'date',
+          name: 'creation_time',
+          responseKey: 'creation_time',
+          label: 'تاریخ سررسید',
+          placeholder: ' ',
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'input',
+          name: 'amount',
+          responseKey: 'amount',
+          label: 'مبلغ کل',
+          placeholder: ' ',
+          disable: true,
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'select',
+          name: 'status',
+          responseKey: 'status',
+          label: 'وضعیت',
+          placeholder: ' ',
+          options: (new Invoice()).statusEnums,
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'input',
+          name: 'amount',
+          responseKey: 'owner_info.firstname',
+          label: 'نام صاحب سفارش',
+          placeholder: ' ',
+          disable: true,
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'input',
+          name: 'amount',
+          responseKey: 'owner_info.lastname',
+          label: 'نام خانوادگی صاحب سفارش',
+          placeholder: ' ',
+          disable: true,
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'hidden',
+          name: 'id',
+          responseKey: 'id'
+        }
+      ]
+    }
+  },
+  created() {
+    this.api = this.api + '/' + this.$route.params.id
+  },
+  mounted() {
+    this.mounted = true
+  },
+  methods: {
+    onUpdateServices () {
+      this.$refs.entityEdit.getData()
+    },
+    afterLoadInputData (response) {
+      this.invoice = new Invoice(response)
+      this.serviceTitle = response.title
+      this.entityLoading = false
+    },
+    edit() {
+      this.entityLoading = true
+      this.$refs.entityEdit.editEntity(false)
+        .then(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminInvoiceShow {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+    display: flex;
+    flex-flow: row;
+    .static-title {
+
+    }
+    .dynamic-title {
+
+    }
+    .back-action {
+      position: absolute;
+      right: 0;
+      top: 0;
+    }
+  }
+  :deep(.form) {
+    padding: 24px;
+  }
+
+  .selectedProductsList {
+    width: 100%;
+
+    .selectedProductsList-row {
+      display: flex;
+      flex-flow: row;
+      justify-content: flex-start;
+      align-items: center;
+      padding: 16px 0;
+      border-bottom: solid 1px #F6F6F6;
+
+      &.head {
+        .selectedProductsList-title {
+          color: #6589C3;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+
+        .selectedProductsList-price,
+        .selectedProductsList-count {
+          color: #6589C3;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+      }
+
+      &.footer {
+        .selectedProductsList-title {
+          color: #2FA84A;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+
+        .selectedProductsList-price {
+          color: #2FA84A;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+      }
+
+      .selectedProductsList-title {
+        width: calc(100% - 200px);
+        color: #272727;
+        font-size: 16px;
+        font-weight: 500;
+        line-height: 140%;
+      }
+
+      .selectedProductsList-count {
+        width: 50px;
+        color: #272727;
+        font-size: 16px;
+        line-height: 140%;
+      }
+
+      .selectedProductsList-price {
+        width: 150px;
+        color: #272727;
+        font-size: 16px;
+        line-height: 140%;
+      }
+    }
+
+    :deep(.form) {
+      padding: 24px;
+
+      .action {
+        display: flex;
+        flex-flow: row;
+        justify-content: flex-end;
+        margin-top: 32px;
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Invoice/AdminInvoiceShow/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </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]
+})
+</script>

+ 146 - 0
src/components/Widgets/Invoice/InvoiceList/InvoiceList.vue

@@ -0,0 +1,146 @@
+<template>
+  <div class="InvoiceList"
+       :style="localOptions.style">
+    <div class="title">
+      صورت‌حساب‌ها
+    </div>
+    <q-card class="list"
+            flat>
+      <entity-index v-if="mounted"
+                    v-model:value="inputs"
+                    title=""
+                    :api="api"
+                    :table="table"
+                    :table-keys="tableKeys"
+                    :create-route-name="createRouteName"
+                    :show-search-button="false"
+                    :show-expand-button="false"
+                    :show-reload-button="false"
+                    :default-layout="false">
+        <template #entity-index-table-cell="{inputData}">
+          <template v-if="inputData.col.name === 'action'">
+            <q-btn flat
+                   color="primary"
+                   :to="{name: 'UserPanel.Invoice.Show', params: {id: inputData.props.row.id}}">
+              مشاهده جزییات
+            </q-btn>
+          </template>
+          <template v-else>
+            {{ inputData.col.value }}
+          </template>
+        </template>
+      </entity-index>
+    </q-card>
+  </div>
+</template>
+
+<script>
+import Assist from 'assets/js/Assist.js'
+import { EntityIndex } from 'quasar-crud'
+import { Invoice } from 'src/models/Invoice.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+
+export default {
+  name: 'InvoiceList',
+  components: { EntityIndex },
+  mixins: [mixinWidget],
+  data: () => {
+    return {
+      api: APIGateway.invoice.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [],
+      table: {
+        columns: [
+          {
+            name: 'id',
+            required: true,
+            label: 'صورت‌حساب',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'پکیج یا خدمت',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'تاریخ صورت‌حساب',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.creation_time)
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'تاریخ سررسید',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.creation_time)
+          },
+          {
+            name: 'amount',
+            required: true,
+            label: 'کل',
+            align: 'left',
+            field: row => row.amount.toLocaleString('fa')
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => (new Invoice(row)).status_info.label
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: row => ''
+          }
+        ]
+      },
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  mounted() {
+    this.mounted = true
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.InvoiceList {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+  }
+  :deep(.list) {
+    .quasar-crud-index-table {
+      padding: 0;
+      .q-table__container {
+        background-color: transparent;
+        box-shadow: none;
+        border: none;
+        .q-table__top {
+          display: none;
+        }
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Invoice/InvoiceList/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </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]
+})
+</script>

+ 297 - 0
src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="InvoiceShow"
+       :style="localOptions.style">
+    <div class="title">
+      <div class="static-title">
+        مشاهده صورتحساب
+        شماره:
+      </div>
+      <div class="dynamic-title">
+        <template v-if="entityLoading">
+          <q-skeleton type="text"
+                      class="q-ml-lg"
+                      width="100px" />
+        </template>
+        <template v-else>
+          {{ entityId }}
+        </template>
+      </div>
+      <div class="back-action">
+        <q-btn flat
+               :to="{name: 'UserPanel.Invoice.List'}"
+               color="grey">
+          بازگشت
+          >
+        </q-btn>
+      </div>
+    </div>
+    <q-card class="form"
+            flat>
+      <entity-show v-if="mounted"
+                   ref="entityEdit"
+                   v-model:value="inputs"
+                   :api="api"
+                   :entity-id-key="entityIdKey"
+                   :entity-param-key="entityParamKey"
+                   :show-route-name="showRouteName"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false"
+                   :default-layout="false"
+                   :redirect-after-edit="false"
+                   :after-load-input-data="afterLoadInputData" />
+    </q-card>
+    <q-card v-if="invoice.products_info.list.length > 0"
+            class="q-mt-lg">
+      <q-card-section>
+        <div class="selectedProductsList">
+          <div class="selectedProductsList-row head">
+            <div class="selectedProductsList-title">
+              شرح خدمات
+            </div>
+            <div class="selectedProductsList-price">
+              مبلغ
+            </div>
+          </div>
+          <div v-for="item in invoice.products_info.list[0].registrations_info.list"
+               :key="item.id"
+               class="selectedProductsList-row">
+            <div class="selectedProductsList-title">
+              {{ item.item_info.title }}
+            </div>
+            <div class="selectedProductsList-price">
+              {{ (item.count * item.item_info.unit_price).toLocaleString('fa') }}
+              تومان
+            </div>
+          </div>
+          <div class="selectedProductsList-row footer">
+            <div class="selectedProductsList-title">
+              قابل پرداخت
+            </div>
+            <div class="selectedProductsList-price">
+              {{ (invoice.products_info.list[0].amount) ? invoice.products_info.list[0].amount.toLocaleString('fa') : 0 }}
+              تومان
+            </div>
+          </div>
+        </div>
+      </q-card-section>
+    </q-card>
+    <div class="action q-mt-lg">
+      <q-card v-if="invoice.status!=='PAYING'"
+              class="q-mb-md">
+        <q-card-section>
+          تنها در صورتی که وضعیت سفارش در حال پرداخت باشد امکان پرداخت با کیف پول وجود دارد
+        </q-card-section>
+      </q-card>
+      <q-btn color="green"
+             :disable="invoice.status!=='PAYING'"
+             class="q-px-xl"
+             @click="pay">
+        پرداخت با کیف پول
+      </q-btn>
+    </div>
+  </div>
+</template>
+
+<script>
+import { EntityShow } from 'quasar-crud'
+import { Invoice } from 'src/models/Invoice.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+
+export default {
+  name: 'InvoiceShow',
+  components: {
+    EntityShow
+  },
+  mixins: [mixinWidget],
+  props: {
+    invoiceId: {
+      type: Number,
+      default: null
+    }
+  },
+  data: () => {
+    return {
+      mounted: false,
+      entityLoading: true,
+      invoice: new Invoice(),
+      entityId: '',
+      api: null,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'UserPanel.Invoice.Show',
+      indexRouteName: 'UserPanel.Invoice.List',
+      inputs: [
+        {
+          type: 'date',
+          name: 'creation_time',
+          responseKey: 'creation_time',
+          label: 'تاریخ صورت‌حساب',
+          placeholder: ' ',
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'select',
+          name: 'status',
+          responseKey: 'status',
+          label: 'وضعیت',
+          placeholder: ' ',
+          options: (new Invoice()).statusEnums,
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'hidden',
+          name: 'id',
+          responseKey: 'id'
+        }
+      ]
+    }
+  },
+  computed: {
+    localInvoiceId () {
+      return this.invoiceId || this.$route.params.id
+    }
+  },
+  mounted() {
+    this.api = APIGateway.invoice.APIAdresses.byId(this.localInvoiceId)
+    this.mounted = true
+  },
+  methods: {
+    pay () {
+      this.invoice.loading = true
+      APIGateway.invoice.pay(this.invoice.id)
+        .then((message) => {
+          this.payMessage = message
+          this.invoice.loading = false
+        })
+        .catch(() => {
+          this.invoice.loading = false
+        })
+    },
+    onUpdateServices () {
+      this.$refs.entityEdit.getData()
+    },
+    afterLoadInputData (response) {
+      this.invoice = new Invoice(response)
+      this.entityId = response.id
+      this.entityLoading = false
+    },
+    edit() {
+      this.entityLoading = true
+      this.$refs.entityEdit.editEntity()
+        .then(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.InvoiceShow {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+    display: flex;
+    flex-flow: row;
+    position: relative;
+    .static-title {
+
+    }
+    .dynamic-title {
+
+    }
+    .back-action {
+      position: absolute;
+      right: 0;
+      top: 0;
+    }
+  }
+  :deep(.form) {
+    padding: 24px;
+  }
+
+  .selectedProductsList {
+    width: 100%;
+
+    .selectedProductsList-row {
+      display: flex;
+      flex-flow: row;
+      justify-content: flex-start;
+      align-items: center;
+      padding: 16px 0;
+      border-bottom: solid 1px #F6F6F6;
+
+      &.head {
+        .selectedProductsList-title {
+          color: #6589C3;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+
+        .selectedProductsList-price {
+          color: #6589C3;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+      }
+
+      &.footer {
+        .selectedProductsList-title {
+          color: #2FA84A;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+
+        .selectedProductsList-price {
+          color: #2FA84A;
+          font-size: 16px;
+          font-weight: 700;
+          line-height: 140%;
+        }
+      }
+
+      .selectedProductsList-title {
+        width: calc(100% - 150px);
+        color: #272727;
+        font-size: 16px;
+        font-weight: 500;
+        line-height: 140%;
+      }
+
+      .selectedProductsList-price {
+        width: 150px;
+        color: #272727;
+        font-size: 16px;
+        line-height: 140%;
+      }
+    }
+
+    :deep(.form) {
+      padding: 24px;
+
+      .action {
+        display: flex;
+        flex-flow: row;
+        justify-content: flex-end;
+        margin-top: 32px;
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Invoice/InvoiceShow/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </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]
+})
+</script>

+ 42 - 0
src/components/Widgets/Invoice/InvoiceThankyou/InvoiceThankyou.vue

@@ -0,0 +1,42 @@
+<template>
+  <div class="InvoiceShow"
+       :style="localOptions.style">
+    <div class="row justify-center items-center">
+      <div class="col-md-6 q-mt-lg">
+        <q-card>
+          <q-card-section class="text-center">
+            <div v-if="$route.query.status === 'true'">
+              پرداخت با موفقیت انجام شد
+            </div>
+            <div v-else>
+              پرداخت نا موفق بود
+            </div>
+            <div>
+              کد پیگیری:
+              {{ $route.query.code }}
+            </div>
+          </q-card-section>
+        </q-card>
+      </div>
+    </div>
+    <div v-if="$route.query.invoice"
+         class="row justify-center items-center">
+      <div class="col-md-6 q-mt-lg">
+        <invoice-show :invoice-id="$route.query.invoice" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import InvoiceShow from 'src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue'
+
+export default {
+  name: 'InvoiceThankyou',
+  components: {
+    InvoiceShow
+  },
+  mixins: [mixinWidget]
+}
+</script>

+ 21 - 0
src/components/Widgets/Invoice/InvoiceThankyou/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </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]
+})
+</script>

+ 245 - 124
src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue

@@ -100,155 +100,258 @@
       </div>
       </div>
     </q-card>
     </q-card>
 
 
-    <q-banner class="banner complete-info-pay-banner">
-      پرداخت
-    </q-banner>
-    <q-card class="complete-info-pay-form-card">
-      <q-card-section>
-        <div class="complete-info-pay-form-card-title">
-          مبلغ قابل پرداخت:
-        </div>
-        <q-card>
-          <q-card-section v-if="classroom"
-                          class="product-item">
-            <div class="product-item-title">
-              <div class="product-item-icon">
-                <svg xmlns="http://www.w3.org/2000/svg"
-                     width="23.771"
-                     height="16.991"
-                     viewBox="0 0 23.771 16.991">
-                  <path id="import_contacts_FILL0_wght400_GRAD0_opsz48"
-                        d="M7.618,21.506a11.7,11.7,0,0,1,2.823.338,12.529,12.529,0,0,1,2.688,1.013V11.322A10.276,10.276,0,0,0,10.5,10.066a9.542,9.542,0,0,0-2.877-.446,7.957,7.957,0,0,0-2.012.257,19.465,19.465,0,0,0-1.985.635h0V22.235h0a7.623,7.623,0,0,1,1.9-.554A12.88,12.88,0,0,1,7.618,21.506Zm7.131,1.351A13.593,13.593,0,0,1,17.4,21.844a10.9,10.9,0,0,1,2.755-.338,14.467,14.467,0,0,1,2.12.162,10.571,10.571,0,0,1,1.877.432h0V10.512h0A8.027,8.027,0,0,0,22.2,9.837a9.94,9.94,0,0,0-2.053-.216,9.031,9.031,0,0,0-2.823.446,10.525,10.525,0,0,0-2.58,1.256Zm-.81,2.134a1.745,1.745,0,0,1-.392-.041.766.766,0,0,1-.311-.149,11.392,11.392,0,0,0-2.7-1.216,10.041,10.041,0,0,0-2.917-.432,7.778,7.778,0,0,0-1.945.243,19.068,19.068,0,0,0-1.891.594,1.157,1.157,0,0,1-1.2-.081A1.214,1.214,0,0,1,2,22.83V10.323a1.494,1.494,0,0,1,.189-.743,1.29,1.29,0,0,1,.567-.527,10.442,10.442,0,0,1,2.364-.8A12.192,12.192,0,0,1,7.618,8a11.986,11.986,0,0,1,3.309.459,10.558,10.558,0,0,1,3.012,1.4,10.255,10.255,0,0,1,2.958-1.4A11.6,11.6,0,0,1,20.152,8a12,12,0,0,1,2.485.257,10.487,10.487,0,0,1,2.35.8,1.376,1.376,0,0,1,.581.527,1.417,1.417,0,0,1,.2.743V22.83a1.28,1.28,0,0,1-.608,1.148,1.035,1.035,0,0,1-1.2.014,12.862,12.862,0,0,0-1.864-.608,8.211,8.211,0,0,0-1.945-.23,9.683,9.683,0,0,0-2.863.432A11.018,11.018,0,0,0,14.642,24.8a.766.766,0,0,1-.311.149A1.745,1.745,0,0,1,13.939,24.991ZM8.375,16.4Z"
-                        transform="translate(-2 -8)"
-                        fill="#475f4a" />
-                </svg>
+    <div class="info-column">
+      <q-skeleton v-if="invoice.loading"
+                  height="200px"
+                  width="100%"
+                  square />
+      <invoice-info v-if="!invoice.loading"
+                    :invoice="invoice"
+                    :classroom="classroom"
+                    class="invoice-info" />
+      <payment-card v-if="!invoice.loading"
+                    :wallet="wallet"
+                    :invoice="invoice"
+                    :classroom="classroom"
+                    class="payment-card"
+                    @onCancel="onCancel"
+                    @onAccept="onAccept" />
+    </div>
+
+    <template v-if="false">
+      <q-banner class="banner complete-info-pay-banner">
+        پرداخت
+      </q-banner>
+      <q-card class="complete-info-pay-form-card">
+        <q-card-section>
+          <div class="complete-info-pay-form-card-title">
+            مبلغ قابل پرداخت:
+          </div>
+          <q-card>
+            <q-card-section v-if="classroom"
+                            class="product-item">
+              <div class="product-item-title">
+                <div class="product-item-icon">
+                  <svg xmlns="http://www.w3.org/2000/svg"
+                       width="23.771"
+                       height="16.991"
+                       viewBox="0 0 23.771 16.991">
+                    <path id="import_contacts_FILL0_wght400_GRAD0_opsz48"
+                          d="M7.618,21.506a11.7,11.7,0,0,1,2.823.338,12.529,12.529,0,0,1,2.688,1.013V11.322A10.276,10.276,0,0,0,10.5,10.066a9.542,9.542,0,0,0-2.877-.446,7.957,7.957,0,0,0-2.012.257,19.465,19.465,0,0,0-1.985.635h0V22.235h0a7.623,7.623,0,0,1,1.9-.554A12.88,12.88,0,0,1,7.618,21.506Zm7.131,1.351A13.593,13.593,0,0,1,17.4,21.844a10.9,10.9,0,0,1,2.755-.338,14.467,14.467,0,0,1,2.12.162,10.571,10.571,0,0,1,1.877.432h0V10.512h0A8.027,8.027,0,0,0,22.2,9.837a9.94,9.94,0,0,0-2.053-.216,9.031,9.031,0,0,0-2.823.446,10.525,10.525,0,0,0-2.58,1.256Zm-.81,2.134a1.745,1.745,0,0,1-.392-.041.766.766,0,0,1-.311-.149,11.392,11.392,0,0,0-2.7-1.216,10.041,10.041,0,0,0-2.917-.432,7.778,7.778,0,0,0-1.945.243,19.068,19.068,0,0,0-1.891.594,1.157,1.157,0,0,1-1.2-.081A1.214,1.214,0,0,1,2,22.83V10.323a1.494,1.494,0,0,1,.189-.743,1.29,1.29,0,0,1,.567-.527,10.442,10.442,0,0,1,2.364-.8A12.192,12.192,0,0,1,7.618,8a11.986,11.986,0,0,1,3.309.459,10.558,10.558,0,0,1,3.012,1.4,10.255,10.255,0,0,1,2.958-1.4A11.6,11.6,0,0,1,20.152,8a12,12,0,0,1,2.485.257,10.487,10.487,0,0,1,2.35.8,1.376,1.376,0,0,1,.581.527,1.417,1.417,0,0,1,.2.743V22.83a1.28,1.28,0,0,1-.608,1.148,1.035,1.035,0,0,1-1.2.014,12.862,12.862,0,0,0-1.864-.608,8.211,8.211,0,0,0-1.945-.23,9.683,9.683,0,0,0-2.863.432A11.018,11.018,0,0,0,14.642,24.8a.766.766,0,0,1-.311.149A1.745,1.745,0,0,1,13.939,24.991ZM8.375,16.4Z"
+                          transform="translate(-2 -8)"
+                          fill="#475f4a" />
+                  </svg>
+                </div>
+                {{ classroom.title }}
+                {{ classroom.unit_info.title }}
+                ویژه
+                {{ getAudienceGenderType(classroom.audience_gender_type) }}
+                به صورت
+                {{ getHoldingType(classroom.holding_type) }}
+                {{ getTerm(classroom) }}
               </div>
               </div>
-              {{ classroom.title }}
-              {{ classroom.unit_info.title }}
-              ویژه
-              {{ getAudienceGenderType(classroom.audience_gender_type) }}
-              به صورت
-              {{ getHoldingType(classroom.holding_type) }}
-              {{ getTerm(classroom) }}
-            </div>
-            <div class="product-item-price">
-              <svg xmlns="http://www.w3.org/2000/svg"
-                   width="22"
-                   height="16"
-                   viewBox="0 0 22 16">
-                <g id="Group_11468"
-                   data-name="Group 11468"
-                   transform="translate(240 -290) rotate(90)">
-                  <g id="Rectangle_2935"
-                     data-name="Rectangle 2935"
-                     transform="translate(290 218)"
-                     fill="none"
-                     stroke="#aaa095"
-                     stroke-width="1.5">
-                    <rect width="16"
-                          height="22"
-                          rx="5"
-                          stroke="none" />
-                    <rect x="0.75"
-                          y="0.75"
-                          width="14.5"
-                          height="20.5"
-                          rx="4.25"
-                          fill="none" />
-                  </g>
-                  <line id="Line_737"
-                        data-name="Line 737"
-                        y1="2"
-                        transform="translate(294 233.5)"
-                        fill="none"
-                        stroke="#aaa095"
-                        stroke-linecap="round"
-                        stroke-width="2" />
-                  <line id="Line_738"
-                        data-name="Line 738"
-                        y1="2"
-                        transform="translate(302 222.5)"
-                        fill="none"
-                        stroke="#aaa095"
-                        stroke-linecap="round"
-                        stroke-width="2" />
-                  <g id="Ellipse_892"
-                     data-name="Ellipse 892"
-                     transform="translate(295 226)"
-                     fill="#fff"
-                     stroke="#aaa095"
-                     stroke-width="1.5">
-                    <circle cx="3"
-                            cy="3"
-                            r="3"
+              <div class="product-item-price">
+                <svg xmlns="http://www.w3.org/2000/svg"
+                     width="22"
+                     height="16"
+                     viewBox="0 0 22 16">
+                  <g id="Group_11468"
+                     data-name="Group 11468"
+                     transform="translate(240 -290) rotate(90)">
+                    <g id="Rectangle_2935"
+                       data-name="Rectangle 2935"
+                       transform="translate(290 218)"
+                       fill="none"
+                       stroke="#aaa095"
+                       stroke-width="1.5">
+                      <rect width="16"
+                            height="22"
+                            rx="5"
                             stroke="none" />
                             stroke="none" />
-                    <circle cx="3"
-                            cy="3"
-                            r="2.25"
+                      <rect x="0.75"
+                            y="0.75"
+                            width="14.5"
+                            height="20.5"
+                            rx="4.25"
                             fill="none" />
                             fill="none" />
+                    </g>
+                    <line id="Line_737"
+                          data-name="Line 737"
+                          y1="2"
+                          transform="translate(294 233.5)"
+                          fill="none"
+                          stroke="#aaa095"
+                          stroke-linecap="round"
+                          stroke-width="2" />
+                    <line id="Line_738"
+                          data-name="Line 738"
+                          y1="2"
+                          transform="translate(302 222.5)"
+                          fill="none"
+                          stroke="#aaa095"
+                          stroke-linecap="round"
+                          stroke-width="2" />
+                    <g id="Ellipse_892"
+                       data-name="Ellipse 892"
+                       transform="translate(295 226)"
+                       fill="#fff"
+                       stroke="#aaa095"
+                       stroke-width="1.5">
+                      <circle cx="3"
+                              cy="3"
+                              r="3"
+                              stroke="none" />
+                      <circle cx="3"
+                              cy="3"
+                              r="2.25"
+                              fill="none" />
+                    </g>
                   </g>
                   </g>
-                </g>
-              </svg>
-              {{ classroom.price }}
-              تومان
-            </div>
-          </q-card-section>
-        </q-card>
-      </q-card-section>
-    </q-card>
-
-    <div class="flex justify-end section-edit-btn">
-      <q-btn color="primary"
-             @click="register">
-        تایید و ادامه
-        <svg xmlns="http://www.w3.org/2000/svg"
-             width="18.387"
-             height="11.502"
-             viewBox="0 0 18.387 11.502">
-          <path id="Combined_Shape"
-                data-name="Combined Shape"
-                d="M-9.338,11.408a.748.748,0,0,0,.388-.656V6.5h8.2A.751.751,0,0,0,0,5.75.75.75,0,0,0-.75,5h-8.2V.75A.751.751,0,0,0-9.338.093.769.769,0,0,0-9.7,0a.735.735,0,0,0-.4.115l-7.938,5a.747.747,0,0,0-.35.635.742.742,0,0,0,.35.634l7.938,5a.751.751,0,0,0,.4.116A.746.746,0,0,0-9.338,11.408ZM-10.45,9.392l-5.78-3.641,5.78-3.642Z"
-                transform="translate(18.387)"
-                fill="#fff" />
-        </svg>
-      </q-btn>
-    </div>
+                </svg>
+                {{ classroom.price }}
+                تومان
+              </div>
+            </q-card-section>
+          </q-card>
+        </q-card-section>
+      </q-card>
+      <div class="flex justify-end section-edit-btn">
+        <q-btn color="primary"
+               @click="register">
+          تایید و ادامه
+          <svg xmlns="http://www.w3.org/2000/svg"
+               width="18.387"
+               height="11.502"
+               viewBox="0 0 18.387 11.502">
+            <path id="Combined_Shape"
+                  data-name="Combined Shape"
+                  d="M-9.338,11.408a.748.748,0,0,0,.388-.656V6.5h8.2A.751.751,0,0,0,0,5.75.75.75,0,0,0-.75,5h-8.2V.75A.751.751,0,0,0-9.338.093.769.769,0,0,0-9.7,0a.735.735,0,0,0-.4.115l-7.938,5a.747.747,0,0,0-.35.635.742.742,0,0,0,.35.634l7.938,5a.751.751,0,0,0,.4.116A.746.746,0,0,0-9.338,11.408ZM-10.45,9.392l-5.78-3.641,5.78-3.642Z"
+                  transform="translate(18.387)"
+                  fill="#fff" />
+          </svg>
+        </q-btn>
+      </div>
+    </template>
 
 
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
+import { User } from 'src/models/User.js'
 import Enums from 'src/assets/Enums/Enums.js'
 import Enums from 'src/assets/Enums/Enums.js'
-import API_ADDRESS from 'src/api/Addresses.js'
+import { Wallet } from 'src/models/Wallet.js'
+import { Invoice } from 'src/models/Invoice.js'
+// import API_ADDRESS from 'src/api/Addresses.js'
 import ShamsiDate from 'src/assets/ShamsiDate.js'
 import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { Classroom } from 'src/models/Classroom.js'
+import InvoiceInfo from './components/InvoiceInfo.vue'
+import PaymentCard from './components/PaymentCard.vue'
 
 
 export default {
 export default {
   name: 'PaymentFromWallet',
   name: 'PaymentFromWallet',
+  components: { InvoiceInfo, PaymentCard },
   data: () => ({
   data: () => ({
     step: false,
     step: false,
+    user: new User(),
+    wallet: new Wallet(),
+    invoice: new Invoice(),
+    classroom: new Classroom(),
     rulesAccept: false,
     rulesAccept: false,
     rulesDialog: false,
     rulesDialog: false,
     loading: true
     loading: true
   }),
   }),
-  computed: {
-    classroom () {
-      return this.$store.getters['Shop/onRegisterClassroom']
-    }
-  },
-  created () {
-    if (this.classroom === null || typeof this.classroom.id === 'undefined' || this.classroom.id === null) {
-      this.$router.push({ name: 'Public.AllClassrooms' })
-    }
+  mounted () {
+    this.loadAuthData()
+    this.loadClassroomFromStore()
+    this.checkStoreClassroom()
+    this.createInvoice()
+    this.getMyWallet()
   },
   },
   methods: {
   methods: {
-    register () {
-      this.$axios.put(API_ADDRESS.classroom.register(this.classroom.id), {
-        classroom: this.classroom.id
+    onCancel () {
+      this.$router.push({ name: 'Public.AllClassrooms' })
+    },
+    onAccept () {
+      this.payInvoice()
+    },
+    amountOfDepositWalletNeeded () {
+      return this.invoice.amount - this.wallet.inventory
+    },
+    payInvoice () {
+      if (this.amountOfDepositWalletNeeded() > 0) {
+        this.payInvoiceByDepositWallet()
+      } else {
+        this.payInvoiceByWallet()
+      }
+    },
+    payInvoiceByWallet () {
+      this.invoice.loading = true
+      APIGateway.invoice.pay(this.invoice.id)
+        .then((message) => {
+          this.payMessage = message
+          this.invoice.loading = false
+        })
+        .catch(() => {
+          this.invoice.loading = false
+        })
+    },
+    payInvoiceByDepositWallet () {
+      this.invoice.loading = true
+      APIGateway.wallet.deposit({
+        amount: this.amountOfDepositWalletNeeded(),
+        invoice: this.invoice.id
       })
       })
-        .then(() => {
-          this.$router.push({ name: 'UserPanel.PaymentResult' })
+        .then((url) => {
+          window.location.href = url
+        })
+        .catch(() => {
+          this.depositLoading = false
+        })
+    },
+    loadAuthData() { // prevent Hydration node mismatch
+      this.user = this.$store.getters['Auth/user']
+    },
+    getMyWallet () {
+      this.wallet.loading = true
+      APIGateway.wallet.getMyWallet(this.user.id)
+        .then((wallet) => {
+          this.wallet = new Wallet(wallet)
+          this.wallet.loading = false
+          this.walletLoaded = true
+          this.checkForNeedToDeposit()
+        })
+        .catch(() => {
+          this.wallet.loading = false
+        })
+    },
+    createInvoice () {
+      this.invoice.loading = true
+      APIGateway.classroom.createInvoice(this.classroom.id)
+        .then((invoice) => {
+          this.invoice = new Invoice(invoice)
+          this.invoice.loading = false
+        })
+        .catch(() => {
+          this.invoice.loading = false
         })
         })
     },
     },
+    loadClassroomFromStore () {
+      this.classroom = this.$store.getters['Shop/onRegisterClassroom']
+    },
+    checkStoreClassroom () {
+      if (this.classroom === null || typeof this.classroom.id === 'undefined' || this.classroom.id === null) {
+        this.$router.push({ name: 'Public.AllClassrooms' })
+      }
+    },
+    register () {
+      // this.$axios.put(API_ADDRESS.classroom.register(this.classroom.id), {
+      //   classroom: this.classroom.id
+      // })
+      //   .then(() => {
+      //     this.$router.push({ name: 'UserPanel.PaymentResult' })
+      //   })
+    },
     getHoldingType (type) {
     getHoldingType (type) {
       const target = Enums.classroomHoldingTypes.find(item => item.value === type)
       const target = Enums.classroomHoldingTypes.find(item => item.value === type)
       if (target) {
       if (target) {
@@ -278,6 +381,7 @@ export default {
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
 .complete-info {
 .complete-info {
+  margin-bottom: 102px;
   .complete-info-top-banner {
   .complete-info-top-banner {
     margin-top: 50px;
     margin-top: 50px;
     margin-bottom: 42px;
     margin-bottom: 42px;
@@ -333,6 +437,23 @@ export default {
       }
       }
     }
     }
   }
   }
+  .info-column {
+    width: 100%;
+    display: flex;
+    flex-flow: column;
+    justify-content: center;
+    align-items: center;
+    .invoice-info {
+      width: 100%;
+      max-width: 643px;
+      margin-bottom: 40px;
+    }
+    .payment-card {
+      width: 100%;
+      max-width: 643px;
+    }
+  }
+
   .complete-info-pay-banner {
   .complete-info-pay-banner {
     margin-bottom: 26px;
     margin-bottom: 26px;
   }
   }

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

@@ -0,0 +1,70 @@
+<template>
+  <div class="invoice-info">
+    <div class="invoice-info-title">
+      مشخصات صورت حساب
+    </div>
+    <div class="invoice-info-body">
+      <div class="invoice-info-body-item">
+        عنوان:
+        {{ classroom.title }}
+      </div>
+      <div class="invoice-info-body-item">
+        شناسه:
+        {{ invoice.id }}
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Invoice } from 'src/models/Invoice.js'
+import { Classroom } from 'src/models/Classroom.js'
+
+export default {
+  name: 'InvoiceInfo',
+  props: {
+    invoice: {
+      type: Invoice,
+      default: new Invoice()
+    },
+    classroom: {
+      type: Classroom,
+      default: new Classroom()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.invoice-info{
+  background: #F5F5F5;
+  border: 1px solid #DFE1EC;
+  border-radius: 8px;
+  .invoice-info-title {
+    background: #FFFFFF;
+    height: 70px;
+    border-bottom: 1px solid #DFE1EC;
+    border-radius: 8px 8px 0 0;
+    font-size: 18px;
+    letter-spacing: 0;
+    color: #212121;
+    display: flex;
+    flex-flow: row;
+    justify-content: flex-start;
+    align-items: center;
+    padding-left: 10px;
+  }
+  .invoice-info-body {
+    padding: 29px 38px 42px;
+    .invoice-info-body-item {
+      font-size: 16px;
+      letter-spacing: 0;
+      color: #212121;
+      margin-bottom: 31px;
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+</style>

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

@@ -0,0 +1,288 @@
+<template>
+  <div class="PaymentCard">
+    <div class="PaymentCard-head">
+      <div class="PaymentCard-head-title">
+        پرداخت از طریق کیف پول
+      </div>
+      <div class="PaymentCard-head-caption">
+        خرید شما قطعی نشده است و در صورت عدم پرداخت تا پایان زمان، صورتحساب شما لغو خواهد شد
+      </div>
+      <div class="PaymentCard-head-remaining-time">
+        <q-icon name="isax:clock" />
+        زمان باقی مانده :
+        {{ timerString }}
+      </div>
+    </div>
+    <div class="PaymentCard-body">
+      <div class="final-price">
+        <div class="final-price-label">
+          <q-img src="/assets/images/web/user-top-menu-logo.png"
+                 width="32px" />
+          خرید محصول از فروشگاه
+        </div>
+        <div class="final-price-value">
+          {{ invoice.amount ? invoice.amount.toLocaleString('fa') : 0 }}
+          <span class="unit">
+            تومان
+          </span>
+        </div>
+      </div>
+      <div class="other-items">
+        <div class="other-item">
+          <div class="other-item-label">
+            <q-icon name="isax:wallet" />
+            موجودی کیف پول
+          </div>
+          <div class="other-item-value">
+            <q-skeleton v-if="wallet.loading"
+                        type="text"
+                        width="200px" />
+            <template v-else>
+              {{ wallet.inventory ? wallet.inventory.toLocaleString('fa') : 0 }}
+            </template>
+            <span class="unit">
+              تومان
+            </span>
+          </div>
+        </div>
+        <div v-if="needToDeposit"
+             class="other-item">
+          <div class="other-item-label">
+            <q-icon name="isax:card" />
+            نیازمند افزایش موجودی
+          </div>
+          <div class="other-item-value">
+            {{ (invoice.amount - wallet.inventory) ? (invoice.amount - wallet.inventory).toLocaleString('fa') : 0 }}
+            <span class="unit">
+              تومان
+            </span>
+          </div>
+        </div>
+      </div>
+      <div class="action-area">
+        <q-btn color="red"
+               class="btn-cancel"
+               outline
+               :loading="invoice.loading || wallet.loading"
+               @click="onCancel">
+          انصراف
+        </q-btn>
+        <q-btn color="primary"
+               class="btn-pay"
+               :loading="invoice.loading || wallet.loading"
+               @click="onAccept">
+          افزایش موجودی و پرداخت
+        </q-btn>
+      </div>
+      <div class="show-invoice-row">
+        <q-btn class="btn-show-invoice"
+               color="primary"
+               flat>
+          مشاهده صورتحساب
+        </q-btn>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Wallet } from 'src/models/Wallet.js'
+import { Invoice } from 'src/models/Invoice.js'
+
+export default {
+  name: 'PaymentCard',
+  props: {
+    invoice: {
+      type: Invoice,
+      default: new Invoice()
+    },
+    wallet: {
+      type: Wallet,
+      default: new Wallet()
+    }
+  },
+  data: () => {
+    return {
+      walletLoaded: false,
+      needToDeposit: false,
+      timer: 10 * 60,
+      timerString: '10:00',
+      min: 0,
+      sec: 0,
+      interval: null
+    }
+  },
+  mounted() {
+    this.runTimer()
+  },
+  methods: {
+    onCancel () {
+      this.$emit('onCancel')
+    },
+    onAccept () {
+      this.$emit('onAccept')
+    },
+    checkForNeedToDeposit () {
+      this.needToDeposit = this.invoice.amount > this.wallet.inventory
+    },
+    runTimer () {
+      this.interval = setInterval(() => {
+        this.updateTimer()
+      }, 1000)
+    },
+    updateTimer () {
+      this.timer--
+      if (this.timer < 0) {
+        this.stopTimer()
+        return
+      }
+      const min = String(Math.floor(this.timer / 60)).padStart(2, '0')
+      const sec = String(this.timer % 60).padStart(2, '0')
+      this.timerString = min + ':' + sec
+    },
+    stopTimer () {
+      clearInterval(this.interval)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.PaymentCard{
+  background: #F5F5F5;
+  border: 1px solid #DFE1EC;
+  border-radius: 8px;
+  .PaymentCard-head {
+    background: #FFFFFF;
+    border-bottom: 1px solid #DFE1EC;
+    border-radius: 8px 8px 0 0;
+    position: relative;
+    display: flex;
+    flex-flow: column;
+    justify-content: flex-start;
+    align-items: center;
+    .PaymentCard-head-title {
+      margin-top: 24px;
+      width: 100%;
+      text-align: left;
+      font-size: 18px;
+      letter-spacing: 0;
+      color: #212121;
+      padding-left: 12px;
+      margin-bottom: 21px;
+      @media screen and (max-width: 600px) {
+        margin-bottom: 35px;
+      }
+    }
+    .PaymentCard-head-caption {
+      margin-bottom: 17px;
+      width: 100%;
+      text-align: left;
+      font-size: 14px;
+      letter-spacing: 0;
+      color: #212121;
+      padding-left: 53px;
+      padding-right: 20px;
+      @media screen and (max-width: 600px) {
+        padding-left: 20px;
+      }
+    }
+    .PaymentCard-head-remaining-time {
+      position: absolute;
+      right: 23px;
+      top: 26px;
+      .q-icon {
+        color: #AAA095;
+      }
+      @media screen and (max-width: 600px) {
+        top: 55px;
+      }
+    }
+  }
+  .PaymentCard-body {
+    padding: 20px 22px 26px;
+    color: #212121;
+    .final-price {
+      display: flex;
+      flex-flow: row;
+      justify-content: space-between;
+      padding: 13px 22px 13px 27px;
+      margin-bottom: 33px;
+      background: #FFFFFF;
+      border-radius: 8px;
+      .final-price-label {
+        display: flex;
+        flex-flow: row;
+        justify-content: flex-start;
+        align-items: center;
+        font-size: 17px;
+        .q-img {
+          width: 32px;
+          margin-right: 15px;
+        }
+      }
+      .final-price-value {
+        font-size: 20px;
+        font-weight: bold;
+        .unit {
+          font-size: 17px;
+          font-weight: normal;
+        }
+      }
+    }
+    .other-items {
+      margin-bottom: 60px;
+      .other-item {
+        display: flex;
+        flex-flow: row;
+        justify-content: space-between;
+        padding: 19px 22px 18px 27px;
+        border-bottom: 1px solid #E1E3ED;
+        .other-item-label {
+          display: flex;
+          flex-flow: row;
+          justify-content: flex-start;
+          align-items: center;
+          font-size: 16px;
+          .q-icon {
+            color: #475F4A;
+            margin-right: 10px;
+          }
+        }
+        .other-item-value {
+          display: flex;
+          flex-flow: row;
+          justify-content: flex-end;
+          align-items: center;
+          font-size: 18px;
+          font-weight: bold;
+          .unit {
+            font-size: 16px;
+            font-weight: normal;
+          }
+        }
+      }
+    }
+    .action-area {
+      display: flex;
+      flex-flow: row;
+      justify-content: flex-start;
+      align-items: center;
+      margin-bottom: 28px;
+      .btn-pay {
+        width: 60%;
+        margin-left: 19px;
+      }
+      .btn-cancel {
+        width: calc( 40% - 19px );
+      }
+    }
+    .show-invoice-row {
+      display: flex;
+      flex-flow: row;
+      justify-content: center;
+      align-items: center;
+    }
+  }
+}
+</style>

+ 0 - 41
src/css/Theme/FormBuilder/checkbox.scss

@@ -1,41 +0,0 @@
-@mixin global-check-box {
-  .q-checkbox {
-    margin-right: 8px;
-
-    .q-checkbox__inner {
-      &.q-checkbox__inner--falsy {
-        color: #aeaeae;
-      }
-
-      .q-checkbox__bg {
-        border-radius: 5px;
-        border: 1px solid;
-      }
-    }
-  }
-}
-.q-checkbox__inner--truthy {
-  .q-checkbox__bg {
-    background: var(--primary-1);
-    border: 1px solid var(--primary-1) !important;
-    border-radius: 5px;
-  }
-}
-.q-checkbox__inner--falsy{
-  .q-checkbox__bg{
-    border: 1px solid #8A8CA6;
-    border-radius: 5px;
-  }
-}
-
-.form-builder-Checkbox-col {
-  @include global-check-box;
-  .q-checkbox__inner--truthy {
-    .q-checkbox__bg {
-      background: var(--primary-1);
-      border: 1px solid var(--primary-1) !important;
-    }
-
-  }
-
-}

+ 0 - 23
src/css/Theme/FormBuilder/editor.scss

@@ -1,23 +0,0 @@
-
-.form-builder-tiptapEditor-col {
-  padding: 8px 12px 0 12px ;
-  &:deep(p) {
-    &:first-child {
-      margin-bottom: 0 ;
-    }
-  }
-  .tiptap-container {
-     background-color: #FFFFFF;
-     border-radius: 20px;
-   }
-
-  .vue-tiptap-katex {
-    .tiptap-header {
-      border-radius: 20px 20px 0 0;
-      background-color: #FFFFFF;
-      border-bottom: 1px solid #DEDEDE;
-      margin: 24px;
-      padding: 0;
-    }
-  }
- }

+ 0 - 4
src/css/Theme/FormBuilder/formBuilderCol.scss

@@ -1,4 +0,0 @@
-.form-builder-col {
-  padding: 0;
-
-}

+ 0 - 14
src/css/Theme/FormBuilder/select.scss

@@ -1,14 +0,0 @@
-@import "mixin";
-@mixin global-select{
-  .q-field__inner{
-    .q-field__native{
-      padding: 8px 16px;
-    }
-  }
-}
-.form-builder-Select-col {
-  @include global-select;
-  .outsideLabel {
-    @include global-outsideLabel;
-  }
-}

+ 18 - 11
src/css/Theme/FormBuilder/separator.scss

@@ -1,14 +1,21 @@
 .form-builder-separator-col {
 .form-builder-separator-col {
-  display: flex;
-  align-items: center;
-  padding: 15px 12px 0 12px;
-  @media screen and (max-width: 1919px) {
-    padding: 15px 10px 0 10px;
-  }
-  @media screen and (max-width: 1023px) {
-    padding: 12px 8px 0 8px;
-  }
-  @media screen and (max-width: 599px) {
-    padding: 10px 0 0;
+  .custom-separator {
+    position: relative;
+    padding-left: 12px;
+    height: 23px;
+    margin-top: 30px;
+    &:before {
+      content: ' ';
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 4px;
+      height: 100%;
+      background: #EAC38A;
+      border-radius: 2px;
+    }
+    hr {
+      display: none;
+    }
   }
   }
 }
 }

+ 0 - 45
src/css/Theme/FormBuilder/toggleButton.scss

@@ -1,45 +0,0 @@
-@import "mixin";
-
-.form-builder-toggle-button-col {
-  .outsideLabel {
-    @include global-outsideLabel;
-  }
-
-  .q-btn-group {
-    border-radius: 10px;
-    overflow: hidden;
-  }
-  .q-btn-toggle {
-    height: 32px;
-    box-shadow: none;
-    background-color: var(--3a-Neutral3);
-    justify-content: space-between;
-    :first-child {
-      &.q-btn {
-        border-radius: unset;
-      }
-    }
-    .q-btn {
-      height: 32px;
-      width: 58px;
-      font-style: normal;
-      font-weight: 400;
-      font-size: 14px !important;
-      line-height: 24px;
-      text-align: center;
-
-      .q-focus-helper {
-        background: transparent !important;
-      }
-
-      .q-btn__content {
-        margin: 0;
-      }
-
-    }
-  }
-}
-
-//q-btn q-btn-item non-selectable no-outline
-
-//q-btn q-btn-item non-selectable no-outline

+ 1 - 1
src/css/Theme/theme.scss

@@ -13,7 +13,7 @@
 @import 'src/css/Theme/Widgets/widgets.scss';
 @import 'src/css/Theme/Widgets/widgets.scss';
 @import 'src/css/Theme/Controls/controles.scss';
 @import 'src/css/Theme/Controls/controles.scss';
 @import 'src/css/Theme/Typography/typography.scss';
 @import 'src/css/Theme/Typography/typography.scss';
-//@import 'src/css/Theme/FormBuilder/formBuilder.scss';
+@import 'src/css/Theme/FormBuilder/formBuilder.scss';
 
 
 * {
 * {
   font-family: IRANSans, 'Arial', '-apple-system', 'Helvetica Neue', Helvetica, sans-serif;
   font-family: IRANSans, 'Arial', '-apple-system', 'Helvetica Neue', Helvetica, sans-serif;

+ 83 - 0
src/models/Basket.js

@@ -0,0 +1,83 @@
+import { User } from 'src/models/User.js'
+import { Model, Collection } from 'js-abstract-model'
+import { RegistrationList } from 'src/models/Registration.js'
+
+class Basket extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      // -----------------
+      { key: 'unit_price' },
+      { key: 'title' },
+      { key: 'thumbnail' },
+      { key: 'slug' },
+      { key: 'slug' },
+      { key: 'product_type' },
+      { key: 'description' },
+      { key: 'capacity' },
+      // -----------------
+      { key: 'creator' },
+      {
+        key: 'creator_info',
+        relatedModel: User
+      },
+      { key: 'owner' },
+      {
+        key: 'owner_info',
+        relatedModel: User
+      },
+      {
+        key: 'registrations_info',
+        relatedModel: RegistrationList
+      },
+      { key: 'status' },
+      { key: 'amount' },
+      { key: 'description' },
+      { key: 'creation_time' },
+      { key: 'last_modification_time' },
+      { key: 'is_active' },
+      { key: 'is_deleted' }
+    ])
+
+    this.statusEnums = [
+      {
+        label: 'باز',
+        value: 'OPEN'
+      },
+      {
+        label: 'لغو شده',
+        value: 'CANCELED'
+      },
+      {
+        label: 'بسته شده',
+        value: 'CLOSED'
+      },
+      {
+        label: 'پرداخت شده',
+        value: 'PAID'
+      }
+    ]
+
+    this.loadStatusInfo()
+  }
+
+  loadStatusInfo () {
+    const target = this.statusEnums.find(type => type.value === this.status)
+    if (!target) {
+      this.status_info = {
+        label: null,
+        value: null
+      }
+    } else {
+      this.status_info = target
+    }
+  }
+}
+
+class BasketList extends Collection {
+  model() {
+    return Basket
+  }
+}
+
+export { Basket, BasketList }

+ 94 - 0
src/models/Invoice.js

@@ -0,0 +1,94 @@
+import { User } from 'src/models/User.js'
+import { BasketList } from 'src/models/Basket.js'
+import { Model, Collection } from 'js-abstract-model'
+
+class Invoice extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      {
+        key: 'amount',
+        default: 0
+      },
+      { key: 'description' },
+      { key: 'status' },
+      { key: 'type' }, // CLASSROOM
+      { key: 'additional_data' },
+      { key: 'creation_time' },
+      { key: 'last_modification_time' },
+      { key: 'is_active' },
+      { key: 'is_deleted' },
+      { key: 'creator' },
+      {
+        key: 'products_info',
+        relatedModel: BasketList
+      },
+      {
+        key: 'creator_info',
+        relatedModel: User
+      },
+      { key: 'owner' },
+      {
+        key: 'owner_info',
+        relatedModel: User
+      }
+    ])
+
+    this.statusEnums = [
+      {
+        label: 'در حال تأیید',
+        value: 'VERIFYING'
+      },
+      {
+        label: 'لغو شد',
+        value: 'CANCELED'
+      },
+      {
+        label: 'تأیید نشده',
+        value: 'NOT_VERIFIED'
+      },
+      {
+        label: 'درحال پرداخت',
+        value: 'PAYING'
+      },
+      {
+        label: 'درحال تایید پرداخت',
+        value: 'TRANSACTION_VERIFYING'
+      },
+      {
+        label: 'پرداخت شده',
+        value: 'PAID_FULL'
+      },
+      {
+        label: 'درحال پرداخت اقساط',
+        value: 'PAYING_IN_INSTALMENT'
+      },
+      {
+        label: 'اقساط پرداخت شده',
+        value: 'PAID_IN_INSTALMENT'
+      }
+    ]
+
+    this.loadStatusInfo()
+  }
+
+  loadStatusInfo () {
+    const target = this.statusEnums.find(type => type.value === this.status)
+    if (!target) {
+      this.status_info = {
+        label: null,
+        value: null
+      }
+    } else {
+      this.status_info = target
+    }
+  }
+}
+
+class InvoiceList extends Collection {
+  model() {
+    return Invoice
+  }
+}
+
+export { Invoice, InvoiceList }

+ 34 - 19
src/models/Registration.js

@@ -1,45 +1,59 @@
 import { User } from 'src/models/User.js'
 import { User } from 'src/models/User.js'
-import { Classroom } from 'src/models/Classroom.js'
-import { Model, Collection } from 'js-abstract-model'
+// import { Package } from 'src/models/Package.js'
+// import { Service } from 'src/models/Service.js'
+import { Collection, Model } from 'js-abstract-model'
 
 
 class Registration extends Model {
 class Registration extends Model {
   constructor (data) {
   constructor (data) {
     super(data, [
     super(data, [
       { key: 'id' },
       { key: 'id' },
-      { key: 'owner' },
+      { key: 'product_info' },
+      { key: 'item' },
+      { key: 'item_info' },
+      { key: 'basket' },
+      { key: 'count' },
+      { key: 'user' },
       {
       {
-        key: 'owner_info',
+        key: 'user_info',
         relatedModel: User
         relatedModel: User
       },
       },
-      { key: 'classroom' },
-      {
-        key: 'classroom_info',
-        relatedModel: Classroom
-      },
-      { key: 'status' }
+      { key: 'time_status' },
+      { key: 'termination_datetime' },
+      { key: 'creation_time' },
+      { key: 'last_modification_time' }
     ])
     ])
 
 
-    this.statusEnums = [
+    this.timeStatusEnums = [
       {
       {
-        label: 'ثبت نام شده',
-        value: 'REGISTERED'
+        label: 'فعال',
+        value: 'ONGOING',
+        color: '#2FA84A'
       }
       }
     ]
     ]
 
 
-    this.loadStatusInfo()
+    this.loadTimeStatusInfo()
   }
   }
 
 
-  loadStatusInfo () {
-    const target = this.statusEnums.find(type => type.value === this.status)
+  loadTimeStatusInfo () {
+    const target = this.timeStatusEnums.find(type => type.value === this.time_status)
     if (!target) {
     if (!target) {
-      this.status_info = {
+      this.time_status_info = {
         label: null,
         label: null,
-        value: null
+        value: null,
+        color: ''
       }
       }
     } else {
     } else {
-      this.status_info = target
+      this.time_status_info = target
     }
     }
   }
   }
+
+  loadProductData () {
+    // if (this.product_info.product_type === 'package') {
+    //   this.product_data = new Package(this.product_info)
+    // } else if (this.product_info.product_type === 'service') {
+    //   this.product_data = new Service(this.product_info)
+    // }
+  }
 }
 }
 
 
 class RegistrationList extends Collection {
 class RegistrationList extends Collection {
@@ -47,4 +61,5 @@ class RegistrationList extends Collection {
     return Registration
     return Registration
   }
   }
 }
 }
+
 export { Registration, RegistrationList }
 export { Registration, RegistrationList }

+ 29 - 0
src/models/Wallet.js

@@ -0,0 +1,29 @@
+import { Model, Collection } from 'js-abstract-model'
+
+class Wallet extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'is_active' },
+      { key: 'is_deleted' },
+      {
+        key: 'inventory',
+        default: 0
+      },
+      { key: 'description' },
+      { key: 'additional_data' },
+      { key: 'creation_time' },
+      { key: 'last_modification_time' },
+      { key: 'creator' },
+      { key: 'owner' }
+    ])
+  }
+}
+
+class WalletList extends Collection {
+  model() {
+    return Wallet
+  }
+}
+
+export { Wallet, WalletList }

+ 80 - 75
src/pages/Admin/Classroom/show.vue

@@ -89,8 +89,8 @@
 
 
 <script>
 <script>
 import Enums from 'src/assets/Enums/Enums.js'
 import Enums from 'src/assets/Enums/Enums.js'
-import { APIGateway } from 'src/api/APIGateway.js'
 import ShamsiDate from 'src/assets/ShamsiDate.js'
 import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
 import { EntityEdit, EntityIndex } from 'quasar-crud'
 import { EntityEdit, EntityIndex } from 'quasar-crud'
 import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
 import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
 
 
@@ -110,93 +110,98 @@ export default {
       entityParamKey: 'id',
       entityParamKey: 'id',
       showRouteName: 'Admin.Classroom.Show',
       showRouteName: 'Admin.Classroom.Show',
       inputs: [
       inputs: [
-        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'آپلود عکس دوره', col: 'col-md-3' },
-        { type: 'input', name: 'title', responseKey: 'title', label: 'نام دوره', col: 'col-md-12' },
-        {
-          type: 'select',
-          name: 'category',
-          responseKey: 'unit_info.category',
-          options: [],
-          value: null,
-          label: 'دسته بندی',
-          col: 'col-md-3'
-        },
-        {
-          type: 'select',
-          name: 'unit',
-          responseKey: 'unit',
-          options: [],
-          value: null,
-          label: 'درس',
-          col: 'col-md-3'
-        },
-        {
-          type: 'select',
-          name: 'audience_gender_type',
-          responseKey: 'audience_gender_type',
-          options: Enums.genders,
-          value: null,
-          label: 'جنسیت',
-          col: 'col-md-3'
-        },
-        {
-          type: 'select',
-          name: 'audience_role',
-          responseKey: 'audience_role',
-          options: Enums.groups,
-          value: null,
-          label: 'نقش',
-          col: 'col-md-3'
-        },
-        {
-          type: 'select',
-          name: 'category',
-          responseKey: 'category',
-          options: [],
-          value: null,
-          label: 'مجموعه',
-          col: 'col-md-3'
-        },
+        { type: 'separator', name: 'space', label: 'مشخصات دوره', className: 'custom-separator', col: 'col-12' },
+        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'آپلود عکس دوره', col: 'col-md-3 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
+        { type: 'select', name: 'category', responseKey: 'unit_info.category', options: [], value: null, placeholder: ' ', label: 'دسته بندی', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'unit', responseKey: 'unit', options: [], value: null, placeholder: ' ', label: 'درس', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'holding_type', responseKey: 'holding_type', options: Enums.classroomHoldingTypes, value: null, placeholder: ' ', label: 'نوع برگزاری', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'price', responseKey: 'price', placeholder: ' ', label: 'هزینه برگزاری', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'live_streaming_url', responseKey: 'live_streaming_url', placeholder: ' ', label: 'لینک مکان مجازی دوره', col: 'col-12' },
+        { type: 'inputEditor', name: 'classroom_address', responseKey: 'classroom_address', placeholder: ' ', label: 'آدرس مکان فیزیکی دوره', col: 'col-12' },
         {
         {
           type: 'select',
           type: 'select',
-          name: 'professor',
-          responseKey: 'professor',
-          options: [],
+          name: 'holding_year',
+          responseKey: 'holding_year',
+          options: [1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410],
           value: null,
           value: null,
-          label: 'استاد دوره',
+          label: 'انتخاب سال دوره',
           col: 'col-md-3'
           col: 'col-md-3'
         },
         },
-        { type: 'input', name: 'price', responseKey: 'price', label: 'هزینه برگزاری', col: 'col-md-3' },
-        { type: 'input', name: 'capacity', responseKey: 'capacity', label: 'ظرفیت گروه درسی', col: 'col-md-3' },
-        { type: 'input', name: 'allowed_absence_count', responseKey: 'allowed_absence_count', label: 'تعداد مجاز غیبت', col: 'col-md-3' },
-        { type: 'input', name: 'effective_absence_coefficient', responseKey: 'effective_absence_coefficient', label: 'ضریب نمره حضور و غیاب', col: 'col-md-3' },
-        { type: 'input', name: 'mandatory_assignment_count', responseKey: 'mandatory_assignment_count', label: 'تعداد تکالیف اجباری', col: 'col-md-3' },
-        { type: 'input', name: 'maximum_failing_mark', responseKey: 'maximum_failing_mark', label: 'حداقل نمره قبولی', col: 'col-md-3' },
-        { type: 'input', name: 'minimum_conditional_passing_mark', responseKey: 'minimum_conditional_passing_mark', label: 'حداقل نمره عدم مردودی', col: 'col-md-3' },
-        { type: 'input', name: 'minimum_clean_passing_mark', responseKey: 'minimum_clean_passing_mark', label: 'حداقل نمره قبولی مشروط', col: 'col-md-3' },
         {
         {
           type: 'select',
           type: 'select',
-          name: 'holding_type',
-          responseKey: 'holding_type',
-          options: Enums.classroomHoldingTypes,
+          name: 'holding_month',
+          responseKey: 'holding_month',
+          options: [
+            'فروردین',
+            'اردیبهشت',
+            'خرداد',
+            'تیر',
+            'مرداد',
+            'شهریور',
+            'مهر',
+            'آبان',
+            'آذر',
+            'دی',
+            'بهمن',
+            'اسفند'
+          ],
           value: null,
           value: null,
-          label: 'نوع برگزاری',
+          label: 'انتخاب ماه دوره',
           col: 'col-md-3'
           col: 'col-md-3'
         },
         },
-        { type: 'input', name: 'live_streaming_url', responseKey: 'live_streaming_url', label: 'لینک مکان برگزاری', col: 'col-md-12' },
-        { type: 'inputEditor', name: 'classroom_address', responseKey: 'classroom_address', label: 'آدرس مکان برگزاری', col: 'col-md-12' },
-        { type: 'dateTime', name: 'beginning_enrollment_period', responseKey: 'beginning_enrollment_period', label: 'تاریخ شروع پیش ثبت نام', col: 'col-md-6' },
-        { type: 'dateTime', name: 'ending_enrollment_period', responseKey: 'ending_enrollment_period', label: 'تاریخ پایان پیش ثبت نام', col: 'col-md-6' },
-        { type: 'dateTime', name: 'beginning_registration_period', responseKey: 'beginning_registration_period', label: 'تاریخ شروع ثبت نام', col: 'col-md-6' },
-        { type: 'dateTime', name: 'ending_registration_period', responseKey: 'ending_registration_period', label: 'تاریخ پایان ثبت نام', col: 'col-md-6' },
-        { type: 'inputEditor', name: 'rules', responseKey: 'rules', label: 'قوانین دوره آموزشی', col: 'col-md-12' },
-        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-md-12' },
-        { type: 'dateTime', name: 'publish_time', responseKey: 'publish_time', label: 'تاریخ انتشار', col: 'col-md-6' },
-        { type: 'hidden', name: 'id', responseKey: 'id', label: 'id', col: 'col-md-3' }
+        { type: 'input', name: 'sessions_frequency', responseKey: 'sessions_frequency', placeholder: ' ', label: 'دوره تناوب', col: 'col-12' },
+        { type: 'select', name: 'professor', responseKey: 'professor', options: [], value: null, placeholder: ' ', label: 'استاد دوره', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'مشخصات اندیشه جو', className: 'custom-separator', col: 'col-12' },
+        { type: 'select', name: 'audience_status_wwwww', responseKey: 'audience_status_wwwww', options: [], placeholder: ' ', label: 'وضعیت(؟)', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'category', responseKey: 'category', options: [], placeholder: ' ', label: 'مجموعه(؟)', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'audience_gender_type', responseKey: 'audience_gender_type', options: Enums.genders, placeholder: ' ', label: 'جنسیت', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'audience_bashgah_wwwww', responseKey: 'audience_bashgah_wwwww', options: [], placeholder: ' ', label: 'باشگاه(؟)', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'تنظیمات دوره', className: 'custom-separator', col: 'col-12' },
+        { type: 'input', name: 'capacity', responseKey: 'capacity', placeholder: ' ', label: 'ظرفیت گروه درسی', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'allowed_absence_count', responseKey: 'allowed_absence_count', placeholder: ' ', label: 'تعداد مجاز غیبت', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'effective_absence_coefficient', responseKey: 'effective_absence_coefficient', placeholder: ' ', label: 'ضریب نمره حضور و غیاب', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'mandatory_assignment_count', responseKey: 'mandatory_assignment_count', placeholder: ' ', label: 'تعداد تکالیف اجباری', col: 'col-md-3 col-12' },
+
+        { type: 'input', name: 'maximum_failing_mark', responseKey: 'maximum_failing_mark', placeholder: ' ', label: 'حداقل نمره قبولی', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'minimum_conditional_passing_mark', responseKey: 'minimum_conditional_passing_mark', placeholder: ' ', label: 'حداقل نمره عدم مردودی', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'minimum_clean_passing_mark', responseKey: 'minimum_clean_passing_mark', placeholder: ' ', label: 'حداقل نمره قبولی مشروط', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'ثبت نام و حذف و اضافه', className: 'custom-separator', col: 'col-12' },
+        { type: 'dateTime', name: 'beginning_enrollment_period', responseKey: 'beginning_enrollment_period', placeholder: ' ', label: 'تاریخ شروع پیش ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_enrollment_period', responseKey: 'ending_enrollment_period', placeholder: ' ', label: 'تاریخ پایان پیش ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'beginning_registration_period', responseKey: 'beginning_registration_period', placeholder: ' ', label: 'تاریخ شروع ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_registration_period', responseKey: 'ending_registration_period', placeholder: ' ', label: 'تاریخ پایان ثبت نام', col: 'col-md-6 col-12' },
+
+        { type: 'input', name: 'registration_period_refund_percent', responseKey: 'registration_period_refund_percent', placeholder: ' ', label: 'درصد برگشت هزینه انصراف در بازه ثبت نام', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_drop_add_period', responseKey: 'allow_dropping_on_drop_add_period', label: 'امکان انصراف در بازه ثبت نام', col: 'col-md-9 col-12' },
+
+        { type: 'dateTime', name: 'beginning_drop_add_period', responseKey: 'beginning_drop_add_period', placeholder: ' ', label: 'تاریخ شروع حذف و اضافه', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_drop_add_period', responseKey: 'ending_drop_add_period', placeholder: ' ', label: 'تاریخ پایان حذف و اضافه', col: 'col-md-6 col-12' },
+
+        { type: 'input', name: 'drop_add_period_refund_percent', responseKey: 'drop_add_period_refund_percent', placeholder: ' ', label: 'درصد برگشت هزینه انصراف در بازه حذف و اضافه', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_drop_add_period', responseKey: 'allow_dropping_on_drop_add_period', label: 'امکان انصراف در بازه ثبت نام', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_drop_add_period', responseKey: 'allow_dropping_on_drop_add_period', label: 'امکان انصراف در بازه ثبت نام', col: 'col-md-6 col-12' },
+
+        { type: 'dateTime', name: 'publish_time', responseKey: 'publish_time', placeholder: ' ', label: 'تاریخ انتشار', col: 'col-md-6 col-12' },
+
+        { type: 'separator', name: 'space', label: 'قوانین', className: 'custom-separator', col: 'col-12' },
+        { type: 'inputEditor', name: 'rules', responseKey: 'rules', label: 'قوانین دوره آموزشی', col: 'col-12' },
+        // -----------------------------------------------------------------------------------------------------------
+
+        { type: 'separator', name: 'space', label: 'موارد زیر در طرح نبودند', className: 'custom-separator', col: 'col-12' },
+        { type: 'input', name: 'title', responseKey: 'title', placeholder: ' ', label: '??نام دوره', col: 'col-md-4 col-12' },
+        { type: 'select', name: 'audience_role', responseKey: 'audience_role', options: Enums.groups, placeholder: ' ', label: 'نقش', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'unit', responseKey: 'unit', placeholder: ' ', label: '??unit', col: 'col-md-4 col-12' },
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: '??توضیحات', col: 'col-12' },
+
+        { type: 'hidden', name: 'id', responseKey: 'id', label: 'id', col: 'col-md-3 col-12' }
       ],
       ],
 
 
       sessionListInputs: [
       sessionListInputs: [
-        { type: 'hidden', name: 'classroom', value: this.$route.params.id, label: 'نام دوره', col: 'col-md-12' }
+        { type: 'hidden', name: 'classroom', value: this.$route.params.id, label: 'نام دوره', col: 'col-12' }
       ],
       ],
       sessionListApi: APIGateway.session.APIAdresses.base,
       sessionListApi: APIGateway.session.APIAdresses.base,
       sessionListTable: {
       sessionListTable: {

+ 23 - 0
src/pages/User/Invoice/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: 'AdminPanel.Invoice.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'InvoiceList' }], options: { style: { paddingTop: '124px' } } }], options: {} }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/User/Invoice/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: 'AdminPanel.Package.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'InvoiceShow' }], options: { style: { paddingTop: '124px' } } }], options: {} }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 22 - 0
src/pages/User/Invoice/Thankyou.vue

@@ -0,0 +1,22 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'UserPanel.Invoice.Thankyou',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'InvoiceThankyou' }] }], options: {} }] }, options: { fullHeight: true, backgrounds: { xs: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, sm: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, md: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, lg: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, xl: { size: null, color: null, image: null, repeat: null, position: null, attachment: null } }, verticalAlign: 'center', style: { minHeight: '100vh' } } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 29 - 7
src/router/routes.js

@@ -86,7 +86,7 @@ const routes = [
         path: 'panel',
         path: 'panel',
         name: 'UserPanel',
         name: 'UserPanel',
         layoutConfig: {
         layoutConfig: {
-          layoutView: 'hHh LpR fFf',
+          layoutView: 'hHh LpR fff',
           layoutHeaderType: 'user-panel',
           layoutHeaderType: 'user-panel',
           layoutHeaderBordered: true,
           layoutHeaderBordered: true,
           layoutLeftDrawer: true,
           layoutLeftDrawer: true,
@@ -94,7 +94,7 @@ const routes = [
           layoutLeftSideBarType: 'user-panel',
           layoutLeftSideBarType: 'user-panel',
           layoutLeftDrawerBordered: true,
           layoutLeftDrawerBordered: true,
           layoutLeftDrawerWidth: 240,
           layoutLeftDrawerWidth: 240,
-          layoutFooter: false
+          layoutFooter: true
         },
         },
         meta: { middlewares: [Authenticated, canSeeUserProfilePanel] },
         meta: { middlewares: [Authenticated, canSeeUserProfilePanel] },
         component: () => import('src/layouts/bareLayout.vue'),
         component: () => import('src/layouts/bareLayout.vue'),
@@ -103,12 +103,12 @@ const routes = [
             name: 'UserPanel.ShopCompleteInfo',
             name: 'UserPanel.ShopCompleteInfo',
             path: 'shop/completeInfo',
             path: 'shop/completeInfo',
             layoutConfig: {
             layoutConfig: {
-              layoutView: 'hHh LpR fFf',
+              layoutView: 'hHh LpR fff',
               layoutHeaderType: 'main',
               layoutHeaderType: 'main',
               layoutHeaderBordered: true,
               layoutHeaderBordered: true,
               layoutLeftDrawer: false,
               layoutLeftDrawer: false,
               layoutLeftDrawerVisible: false,
               layoutLeftDrawerVisible: false,
-              layoutFooter: false
+              layoutFooter: true
             },
             },
             component: () => import('src/pages/User/Shop/completeInfo.vue')
             component: () => import('src/pages/User/Shop/completeInfo.vue')
           },
           },
@@ -116,12 +116,12 @@ const routes = [
             name: 'UserPanel.ShopPaymentFromWallet',
             name: 'UserPanel.ShopPaymentFromWallet',
             path: 'shop/payment-from-wallet',
             path: 'shop/payment-from-wallet',
             layoutConfig: {
             layoutConfig: {
-              layoutView: 'hHh LpR fFf',
+              layoutView: 'hHh LpR fff',
               layoutHeaderType: 'main',
               layoutHeaderType: 'main',
               layoutHeaderBordered: true,
               layoutHeaderBordered: true,
               layoutLeftDrawer: false,
               layoutLeftDrawer: false,
               layoutLeftDrawerVisible: false,
               layoutLeftDrawerVisible: false,
-              layoutFooter: false
+              layoutFooter: true
             },
             },
             meta: { middlewares: [Authenticated] },
             meta: { middlewares: [Authenticated] },
             component: () => import('src/pages/User/Shop/paymentFromWallet.vue')
             component: () => import('src/pages/User/Shop/paymentFromWallet.vue')
@@ -143,6 +143,28 @@ const routes = [
               { name: 'UserPanel.Profile.SessionInfo', path: 'session/:id', component: () => import('src/pages/User/Profile/sessionInfo.vue') },
               { name: 'UserPanel.Profile.SessionInfo', path: 'session/:id', component: () => import('src/pages/User/Profile/sessionInfo.vue') },
               { name: 'UserPanel.Profile.UserInfo', path: 'user-info', component: () => import('src/pages/User/Profile/profile.vue') }
               { name: 'UserPanel.Profile.UserInfo', path: 'user-info', component: () => import('src/pages/User/Profile/profile.vue') }
             ]
             ]
+          },
+          {
+            path: 'invoice',
+            name: 'UserPanel.Invoice',
+            component: () => import('layouts/bareLayout.vue'),
+            children: [
+              {
+                path: '',
+                name: 'UserPanel.Invoice.List',
+                component: () => import('src/pages/User/Invoice/List.vue')
+              },
+              {
+                path: 'thankyou',
+                name: 'UserPanel.Invoice.Thankyou',
+                component: () => import('src/pages/User/Invoice/Thankyou.vue')
+              },
+              {
+                path: ':id',
+                name: 'UserPanel.Invoice.Show',
+                component: () => import('src/pages/User/Invoice/Show.vue')
+              }
+            ]
           }
           }
         ]
         ]
       },
       },
@@ -150,7 +172,7 @@ const routes = [
         path: 'admin',
         path: 'admin',
         name: 'Admin',
         name: 'Admin',
         layoutConfig: {
         layoutConfig: {
-          layoutView: 'hHh LpR fFf',
+          layoutView: 'hHh LpR fff',
           layoutHeaderType: 'admin',
           layoutHeaderType: 'admin',
           layoutHeaderBordered: true,
           layoutHeaderBordered: true,
           layoutLeftDrawer: true,
           layoutLeftDrawer: true,

+ 1 - 0
src/store/index.js

@@ -18,6 +18,7 @@ if (process.browser) {
       storage: window.localStorage,
       storage: window.localStorage,
       fetchBeforeUse: true,
       fetchBeforeUse: true,
       paths: [
       paths: [
+        'Shop',
         'Auth.user',
         'Auth.user',
         'Auth.redirectTo',
         'Auth.redirectTo',
         'Auth.accessToken'
         'Auth.accessToken'