ali 3 лет назад
Родитель
Сommit
807f0deb7a
33 измененных файлов с 1354 добавлено и 469 удалено
  1. 2 0
      src/api/APIGateway.js
  2. 85 0
      src/api/models/InstalmentOffer.js
  3. 22 16
      src/components/InvoicePaymentCard.vue
  4. 22 0
      src/components/Template/SideBard/AdminPanelSideBar.vue
  5. 75 61
      src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue
  6. 204 269
      src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue
  7. 84 13
      src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue
  8. 8 10
      src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue
  9. 52 4
      src/components/Widgets/Ticket/AdminTicketList/AdminTicketList.vue
  10. 7 1
      src/components/Widgets/Ticket/AdminTicketList/OptionPanel.vue
  11. 83 36
      src/components/Widgets/Ticket/AdminTicketShow/AdminTicketShow.vue
  12. 71 0
      src/components/Widgets/Ticket/AdminTicketShow/CreateInstallment.vue
  13. 106 0
      src/components/Widgets/Ticket/AdminTicketShow/InstallmentOffers.vue
  14. 7 1
      src/components/Widgets/Ticket/AdminTicketShow/OptionPanel.vue
  15. 7 1
      src/components/Widgets/Ticket/TicketCreate/OptionPanel.vue
  16. 22 4
      src/components/Widgets/Ticket/TicketCreate/TicketCreate.vue
  17. 7 1
      src/components/Widgets/Ticket/TicketList/OptionPanel.vue
  18. 106 0
      src/components/Widgets/Ticket/TicketShow/InstallmentOffers.vue
  19. 7 1
      src/components/Widgets/Ticket/TicketShow/OptionPanel.vue
  20. 72 50
      src/components/Widgets/Ticket/TicketShow/TicketShow.vue
  21. 29 0
      src/models/InstalmentOffer.js
  22. 3 0
      src/models/Ticket.js
  23. 24 0
      src/pages/Admin/Invoice/List.vue
  24. 24 0
      src/pages/Admin/Invoice/Show.vue
  25. 24 0
      src/pages/Admin/Invoice/Thankyou.vue
  26. 24 0
      src/pages/Admin/Invoice/Ticket/Create.vue
  27. 24 0
      src/pages/Admin/Invoice/Ticket/List.vue
  28. 24 0
      src/pages/Admin/Invoice/Ticket/Show.vue
  29. 1 1
      src/pages/Admin/Ticket/Show.vue
  30. 24 0
      src/pages/User/Invoice/Ticket/Create.vue
  31. 24 0
      src/pages/User/Invoice/Ticket/List.vue
  32. 24 0
      src/pages/User/Invoice/Ticket/Show.vue
  33. 56 0
      src/router/routes.js

+ 2 - 0
src/api/APIGateway.js

@@ -19,6 +19,7 @@ import MediaCategoryAPI from 'src/api/models/MediaCategory.js'
 import TicketCategoryAPI from 'src/api/models/TicketCategory.js'
 import SessionTemplateAPI from 'src/api/models/SessionTemplate.js'
 import ContentCategoryAPI from 'src/api/models/ContentCategory.js'
+import InstalmentOfferAPI from 'src/api/models/InstalmentOffer.js'
 import ClassroomRegistrationAPI from 'src/api/models/ClassroomRegistration.js'
 
 /* Exporting the APIGateway object. */
@@ -44,5 +45,6 @@ export const APIGateway = {
   ticketCategory: new TicketCategoryAPI(),
   sessionTemplate: new SessionTemplateAPI(),
   contentCategory: new ContentCategoryAPI(),
+  instalmentOffer: new InstalmentOfferAPI(),
   classroomRegistration: new ClassroomRegistrationAPI()
 }

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

@@ -0,0 +1,85 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { InstalmentOffer, InstalmentOfferList } from 'src/models/InstalmentOffer.js'
+
+export default class InstalmentOfferAPI extends APIRepository {
+  constructor() {
+    super('invoice', appApi)
+    this.APIAdresses = {
+      base: '/accounting/instalment_offers',
+      byId: (id) => '/accounting/instalment_offers/' + id,
+      accept: (id) => '/accounting/instalment_offers/' + id + '/accept',
+      reject: (id) => '/accounting/instalment_offers/' + id + '/reject'
+    }
+    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 InstalmentOffer(response.data) },
+      post: (response) => { return new InstalmentOffer(response.data) },
+      put: (response) => { return new InstalmentOffer(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 InstalmentOfferList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  accept(id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.accept(id),
+      resolveCallback: (response) => {
+        return response.data
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  reject(id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.reject(id),
+      resolveCallback: (response) => {
+        return response.data
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 22 - 16
src/components/Widgets/Other/Shop/ShopPaymentFromWallet/components/PaymentCard.vue → src/components/InvoicePaymentCard.vue

@@ -1,20 +1,19 @@
 <template>
-  <div class="PaymentCard">
-    <div class="PaymentCard-head">
-      <div class="PaymentCard-head-title">
+  <div class="InvoicePaymentCard">
+    <div class="InvoicePaymentCard-head">
+      <div class="InvoicePaymentCard-head-title">
         پرداخت از طریق کیف پول
       </div>
-      <div class="PaymentCard-head-caption">
+      <div class="InvoicePaymentCard-head-caption">
         خرید شما قطعی نشده است و در صورت عدم پرداخت تا پایان زمان، صورتحساب شما لغو خواهد شد
       </div>
-      <div v-if="false"
-           class="PaymentCard-head-remaining-time">
+      <div class="InvoicePaymentCard-head-remaining-time">
         <q-icon name="isax:clock" />
         زمان باقی مانده :
         {{ timerString }}
       </div>
     </div>
-    <div class="PaymentCard-body">
+    <div class="InvoicePaymentCard-body">
       <div class="final-price">
         <div class="final-price-label">
           <q-img src="/assets/images/web/user-top-menu-logo.png"
@@ -75,10 +74,12 @@
           افزایش موجودی و پرداخت
         </q-btn>
       </div>
-      <div class="show-invoice-row">
+      <div v-if="showInvoiceLinkBtn"
+           class="show-invoice-row">
         <q-btn class="btn-show-invoice"
                color="primary"
-               flat>
+               flat
+               :to="{name: 'UserPanel.Invoice.Show', params: {id: invoice.id}}">
           مشاهده صورتحساب
         </q-btn>
       </div>
@@ -91,7 +92,7 @@ import { Wallet } from 'src/models/Wallet.js'
 import { Invoice } from 'src/models/Invoice.js'
 
 export default {
-  name: 'PaymentCard',
+  name: 'InvoicePaymentCard',
   props: {
     invoice: {
       type: Invoice,
@@ -113,6 +114,11 @@ export default {
       interval: null
     }
   },
+  computed: {
+    showInvoiceLinkBtn () {
+      return this.$route.name !== 'UserPanel.Invoice.Show'
+    }
+  },
   mounted() {
     this.runTimer()
   },
@@ -149,11 +155,11 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.PaymentCard{
+.InvoicePaymentCard{
   background: #F5F5F5;
   border: 1px solid #DFE1EC;
   border-radius: 8px;
-  .PaymentCard-head {
+  .InvoicePaymentCard-head {
     background: #FFFFFF;
     border-bottom: 1px solid #DFE1EC;
     border-radius: 8px 8px 0 0;
@@ -162,7 +168,7 @@ export default {
     flex-flow: column;
     justify-content: flex-start;
     align-items: center;
-    .PaymentCard-head-title {
+    .InvoicePaymentCard-head-title {
       margin-top: 24px;
       width: 100%;
       text-align: left;
@@ -175,7 +181,7 @@ export default {
         margin-bottom: 35px;
       }
     }
-    .PaymentCard-head-caption {
+    .InvoicePaymentCard-head-caption {
       margin-bottom: 17px;
       width: 100%;
       text-align: left;
@@ -188,7 +194,7 @@ export default {
         padding-left: 20px;
       }
     }
-    .PaymentCard-head-remaining-time {
+    .InvoicePaymentCard-head-remaining-time {
       position: absolute;
       right: 23px;
       top: 26px;
@@ -200,7 +206,7 @@ export default {
       }
     }
   }
-  .PaymentCard-body {
+  .InvoicePaymentCard-body {
     padding: 20px 22px 26px;
     color: #212121;
     .final-price {

+ 22 - 0
src/components/Template/SideBard/AdminPanelSideBar.vue

@@ -75,6 +75,28 @@ export default {
               active: false
             }
           ]
+        },
+        {
+          title: 'مدیریت مالی',
+          icon: 'isax:card',
+          routeName: null,
+          show: true,
+          open: true,
+          active: false,
+          children: [
+            {
+              title: 'صورتحساب ها',
+              routeName: 'Admin.Invoice.List',
+              show: true,
+              active: false
+            },
+            {
+              title: 'درخواست ها',
+              routeName: 'Admin.Invoice.Ticket.List',
+              show: true,
+              active: false
+            }
+          ]
         }
       ]
     }

+ 75 - 61
src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue

@@ -1,44 +1,42 @@
 <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>
+    <entity-index v-if="mounted"
+                  ref="entityIndex"
+                  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">
+      <template #entity-index-table-cell="{inputData}">
+        <template v-if="inputData.col.name === 'action'">
+          <q-btn color="primary"
+                 :to="{name: 'Admin.Invoice.Show', params: {id: inputData.props.row.id}}">
+            مشاهده جزییات
+          </q-btn>
         </template>
-      </entity-index>
-    </q-card>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
   </div>
 </template>
 
 <script>
+import { shallowRef } from 'vue'
+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'
-import Assist from 'assets/js/Assist'
-import { Invoice } from 'src/models/Invoice'
+import BtnControl from 'src/components/Control/btn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
 
 export default {
   name: 'AdminInvoiceList',
@@ -46,9 +44,6 @@ export default {
   mixins: [mixinWidget],
   data: () => {
     return {
-      defaultOptions: {
-        invoiceType: null
-      },
       api: APIGateway.invoice.APIAdresses.base,
       tableKeys: {
         data: 'results',
@@ -57,50 +52,68 @@ export default {
         perPage: 'per_page',
         pageKey: 'page'
       },
-      inputs: [],
+      inputs: [
+        {
+          type: 'date',
+          name: 'creation_time',
+          label: 'از تاریخ',
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        {
+          type: 'date',
+          name: 'creation_time',
+          label: 'تا تاریخ',
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: 'select', name: 'category', options: [], label: 'وضعیت تراکنش', placeholder: ' ', col: 'col-md-2 col-12' },
+        { type: 'input', name: 'category', label: 'نوع صورتحساب', placeholder: ' ', col: 'col-md-2 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
+      ],
       table: {
         columns: [
           {
             name: 'id',
             required: true,
-            label: 'صورت‌حساب',
+            label: 'شناسه',
             align: 'left',
             field: row => row.id
           },
           {
             name: 'title',
             required: true,
-            label: 'پکیج یا خدمت',
+            label: 'عنوان',
             align: 'left',
             field: row => row.title
           },
           {
-            name: 'owner_info',
+            name: 'amount',
             required: true,
-            label: 'صاحب سفارش',
+            label: 'مبلغ تراکنش(تومان)',
             align: 'left',
-            field: row => row.owner_info.firstname + ' ' + row.owner_info.lastname
+            field: row => row.amount.toLocaleString('fa')
           },
           {
-            name: 'creation_time',
+            name: 'status',
             required: true,
-            label: 'تاریخ صورت‌حساب',
+            label: 'وضعیت',
             align: 'left',
-            field: row => Assist.miladiToShamsi(row.creation_time)
+            field: row => (new Invoice(row)).status_info.label
           },
           {
-            name: 'amount',
+            name: 'creation_time',
             required: true,
-            label: 'کل',
+            label: 'زمان ثبت',
             align: 'left',
-            field: row => row.amount.toLocaleString('fa')
+            field: row => Assist.miladiToShamsi(row.creation_time)
           },
           {
-            name: 'status',
+            name: 'creation_time',
             required: true,
-            label: 'وضعیت',
+            label: 'زمان آخرین بروز رسانی',
             align: 'left',
-            field: row => (new Invoice(row)).status_info.label
+            field: row => Assist.miladiToShamsi(row.creation_time)
           },
           {
             name: 'action',
@@ -111,23 +124,24 @@ export default {
           }
         ]
       },
+      mounted: false,
       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'
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].atClick = this.search
+        }
       })
+    },
+    search () {
+      this.$refs.entityIndex.search()
     }
   }
 }

+ 204 - 269
src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue

@@ -1,233 +1,249 @@
 <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">
+    <div class="title q-pb-lg">
+      <div class="static-title" />
+      <div class="dynamic-title" />
+      <div v-if="localOptions.showBackBtn"
+           class="back-action">
         <q-btn flat
-               :to="{name: 'AdminPanel.Invoice.List'}"
+               :to="{name: 'Admin.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-card v-if="invoice.item_info"
+            class="q-mt-lg">
+      <q-linear-progress v-if="invoice.loading"
+                         indeterminate />
+      <q-card-section>
+        صورتحساب شناسه
+        {{ invoice.id }}
+      </q-card-section>
+      <q-separator />
+      <q-card-section>
+        <q-banner>
+          مشخصات خریدار
+        </q-banner>
+        <div class="row q-mt-md">
+          <div class="col-md-3 col-12">
+            نام و نام خانوادگی:
+            {{ invoice.owner_info?.firstname }}
+            {{ invoice.owner_info?.lastname }}
+          </div>
+          <div class="col-md-3 col-12">
+            کد ملی:
+            {{ invoice.owner_info?.national_code }}
+          </div>
+          <div class="col-md-3 col-12">
+            تلفن همراه:
+            {{ invoice.owner_info?.on_call_mobile_number }}
+          </div>
+          <div v-if="invoice.owner_info?.id"
+               class="col-md-3 col-12">
+            <q-btn outline
+                   color="primary"
+                   :to="{name: 'Admin.User.Show', params: {id: invoice.owner_info.id}}">
+              مشخصات کاربر
             </q-btn>
           </div>
         </div>
-      </div>
-    </q-card>
-    <q-card v-if="invoice.products_info.list.length > 0"
-            class="q-mt-lg">
+      </q-card-section>
+      <q-separator />
       <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 class="row">
+          <div class="col-md-3 col-12">
+            <q-banner>
+              <q-icon name="isax:stickynote" />
+              {{ getDateTime(invoice.creation_time) }}
+            </q-banner>
           </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 class="col-md-3 col-12">
+            <q-banner>
+              {{ (invoice.amount ? invoice.amount.toLocaleString('fa') : 0) }}
               تومان
-            </div>
+            </q-banner>
+          </div>
+          <div class="col-md-3 col-12">
+            <q-banner>
+              شماره پیگیری
+              {{ invoice.id }}
+            </q-banner>
+          </div>
+          <div class="col-md-3 col-12">
+            <q-banner>
+              وضعیت:
+              {{ invoice.status_info.label }}
+            </q-banner>
+          </div>
+        </div>
+      </q-card-section>
+      <q-separator />
+      <q-card-section>
+        <div class="row">
+          <div class="col-md-4 col-12">
+            <q-img :src="invoice.item_info.thumbnail"
+                   width="100px" />
+            {{ invoice.item_info.title }}
+          </div>
+          <div class="col-md-4 col-12">
+            <q-img :src="invoice.item_info.thumbnail"
+                   width="100px" />
+            <q-icon name="isax:user" />
+            استاد:
+            {{ invoice.item_info.professor_info.firstname }}
+            {{ invoice.item_info.professor_info.lastname }}
+          </div>
+          <div class="col-md-4 col-12">
+            <q-img :src="invoice.item_info.thumbnail"
+                   width="100px" />
+            <q-icon name="isax:card" />
+            قیمت:
+            {{ invoice.item_info?.price ? invoice.item_info.price.toLocaleString('fa') : 0 }}
+            تومان
           </div>
         </div>
       </q-card-section>
     </q-card>
+    <div v-if="false"
+         class="action q-mt-lg">
+      <q-card v-if="!invoice.loading && invoice.status!=='PAYING' && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
+              class="q-mb-md">
+        <q-card-section>
+          تنها در صورتی که وضعیت سفارش در حال پرداخت باشد امکان پرداخت با کیف پول وجود دارد
+        </q-card-section>
+      </q-card>
+      <q-card>
+        <q-card-section>
+          پرداخت
+        </q-card-section>
+        <q-separator />
+        <q-card-section>
+          <invoice-payment-card v-if="!invoice.loading && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
+                                :wallet="wallet"
+                                :invoice="invoice"
+                                class="payment-card"
+                                @onCancel="onCancel"
+                                @onAccept="onAccept" />
+        </q-card-section>
+      </q-card>
+
+    </div>
   </div>
 </template>
 
 <script>
-import { EntityEdit } from 'quasar-crud'
+import Assist from 'assets/js/Assist.js'
+import { User } from 'src/models/User.js'
+import { Wallet } from 'src/models/Wallet.js'
 import { Invoice } from 'src/models/Invoice.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import InvoicePaymentCard from 'src/components/InvoicePaymentCard.vue'
 
 export default {
   name: 'AdminInvoiceShow',
-  components: {
-    EntityEdit
-  },
+  components: { InvoicePaymentCard },
   mixins: [mixinWidget],
+  props: {
+    invoiceId: {
+      type: Number,
+      default: null
+    }
+  },
   data: () => {
     return {
-      mounted: false,
-      entityLoading: true,
-      serviceTitle: '',
-      services: [],
+      user: new User(),
+      wallet: new Wallet(),
       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'
-        }
-      ]
+      defaultOptions: {
+        showBackBtn: true
+      }
     }
   },
-  created() {
-    this.api = this.api + '/' + this.$route.params.id
-  },
   mounted() {
-    this.mounted = true
+    this.loadAuthData()
+    this.getMyWallet()
+    this.getInvoice()
   },
   methods: {
-    onUpdateServices () {
-      this.$refs.entityEdit.getData()
+    loadAuthData() { // prevent Hydration node mismatch
+      this.user = this.$store.getters['Auth/user']
+    },
+    getDateTime (dateTime) {
+      return Assist.miladiToShamsi(dateTime)
+    },
+    onCancel () {
+      this.$router.push({ name: 'UserPanel.Invoice.List' })
+    },
+    onAccept () {
+      this.payInvoice()
     },
-    afterLoadInputData (response) {
-      this.invoice = new Invoice(response)
-      this.serviceTitle = response.title
-      this.entityLoading = false
+    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
+        })
+        .catch(() => {
+          this.wallet.loading = false
+        })
+    },
+    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.invoice.loading = false
+          this.getInvoice()
+          this.$q.notify({
+            message,
+            type: 'positive'
+          })
+        })
+        .catch(() => {
+          this.invoice.loading = false
+        })
     },
-    edit() {
-      this.entityLoading = true
-      this.$refs.entityEdit.editEntity(false)
-        .then(() => {
-          this.$refs.entityEdit.getData()
-          this.entityLoading = false
+    payInvoiceByDepositWallet () {
+      this.invoice.loading = true
+      APIGateway.wallet.deposit({
+        amount: this.amountOfDepositWalletNeeded(),
+        invoice: this.invoice.id
+      })
+        .then((url) => {
+          window.location.href = url
         })
         .catch(() => {
-          this.$refs.entityEdit.getData()
-          this.entityLoading = false
+          this.depositLoading = false
+        })
+    },
+    getInvoiceId () {
+      return this.localOptions.invoiceId || this.$route.params.id
+    },
+    getInvoice () {
+      this.invoice.loading = true
+      const invoiceId = this.getInvoiceId()
+      APIGateway.invoice.get({ data: { id: invoiceId } })
+        .then((invoice) => {
+          this.invoice = new Invoice(invoice)
+          this.invoice.loading = false
+        })
+        .catch(() => {
+          this.invoice.loading = false
         })
     }
   }
@@ -245,6 +261,7 @@ export default {
     margin-bottom: 27px;
     display: flex;
     flex-flow: row;
+    position: relative;
     .static-title {
 
     }
@@ -257,87 +274,5 @@ export default {
       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>

+ 84 - 13
src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue

@@ -20,6 +20,13 @@
       <q-card-section>
         صورتحساب شناسه
         {{ invoice.id }}
+        <q-btn v-if="!invoice.loading && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
+               outline
+               color="primary"
+               class="btn-need-installment"
+               :to="{name: 'UserPanel.Invoice.Ticket.Create', query: {source_type: 'INVOICE', source_id: invoice.id}}">
+          درخواست مساعدت
+        </q-btn>
       </q-card-section>
       <q-separator />
       <q-card-section>
@@ -78,31 +85,42 @@
       </q-card-section>
     </q-card>
     <div class="action q-mt-lg">
-      <q-card v-if="invoice.status!=='PAYING' && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
+      <q-card v-if="!invoice.loading && invoice.status!=='PAYING' && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
               class="q-mb-md">
         <q-card-section>
           تنها در صورتی که وضعیت سفارش در حال پرداخت باشد امکان پرداخت با کیف پول وجود دارد
         </q-card-section>
       </q-card>
-      <q-btn v-if="invoice.status!=='PAYING' && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
-             color="green"
-             :disable="invoice.status!=='PAYING'"
-             class="q-px-xl"
-             @click="pay">
-        پرداخت با کیف پول
-      </q-btn>
+      <q-card v-if="!invoice.loading && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'">
+        <q-card-section>
+          پرداخت
+        </q-card-section>
+        <q-separator />
+        <q-card-section>
+          <invoice-payment-card :wallet="wallet"
+                                :invoice="invoice"
+                                class="payment-card"
+                                @onCancel="onCancel"
+                                @onAccept="onAccept" />
+        </q-card-section>
+      </q-card>
+
     </div>
   </div>
 </template>
 
 <script>
 import Assist from 'assets/js/Assist.js'
+import { User } from 'src/models/User.js'
+import { Wallet } from 'src/models/Wallet.js'
 import { Invoice } from 'src/models/Invoice.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import InvoicePaymentCard from 'src/components/InvoicePaymentCard.vue'
 
 export default {
   name: 'InvoiceShow',
+  components: { InvoicePaymentCard },
   mixins: [mixinWidget],
   props: {
     invoiceId: {
@@ -112,27 +130,79 @@ export default {
   },
   data: () => {
     return {
+      user: new User(),
+      wallet: new Wallet(),
       invoice: new Invoice()
     }
   },
   mounted() {
+    this.loadAuthData()
+    this.getMyWallet()
     this.getInvoice()
   },
   methods: {
+    loadAuthData() { // prevent Hydration node mismatch
+      this.user = this.$store.getters['Auth/user']
+    },
     getDateTime (dateTime) {
       return Assist.miladiToShamsi(dateTime)
     },
-    pay () {
+    onCancel () {
+      this.$router.push({ name: 'UserPanel.Invoice.List' })
+    },
+    onAccept () {
+      this.payInvoice()
+    },
+    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
+        })
+        .catch(() => {
+          this.wallet.loading = false
+        })
+    },
+    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
+          this.getInvoice()
+          this.$q.notify({
+            message,
+            type: 'positive'
+          })
         })
         .catch(() => {
           this.invoice.loading = false
         })
     },
+    payInvoiceByDepositWallet () {
+      this.invoice.loading = true
+      APIGateway.wallet.deposit({
+        amount: this.amountOfDepositWalletNeeded(),
+        invoice: this.invoice.id
+      })
+        .then((url) => {
+          window.location.href = url
+        })
+        .catch(() => {
+          this.depositLoading = false
+        })
+    },
     getInvoice () {
       this.invoice.loading = true
       const invoiceId = this.$route.params.id
@@ -173,9 +243,10 @@ export default {
       top: 0;
     }
   }
-  :deep(.form) {
-    padding: 24px;
+  .btn-need-installment {
+    position: absolute;
+    right: 18px;
+    top: 8px;
   }
-
 }
 </style>

+ 8 - 10
src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue

@@ -109,13 +109,12 @@
                     :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" />
+      <invoice-payment-card v-if="!invoice.loading"
+                            :wallet="wallet"
+                            :invoice="invoice"
+                            class="payment-card"
+                            @onCancel="onCancel"
+                            @onAccept="onAccept" />
     </div>
 
     <template v-if="false">
@@ -246,11 +245,11 @@ 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'
+import InvoicePaymentCard from 'src/components/InvoicePaymentCard.vue'
 
 export default {
   name: 'PaymentFromWallet',
-  components: { InvoiceInfo, PaymentCard },
+  components: { InvoiceInfo, InvoicePaymentCard },
   data: () => ({
     step: false,
     user: new User(),
@@ -319,7 +318,6 @@ export default {
           this.wallet = new Wallet(wallet)
           this.wallet.loading = false
           this.walletLoaded = true
-          this.checkForNeedToDeposit()
         })
         .catch(() => {
           this.wallet.loading = false

+ 52 - 4
src/components/Widgets/Ticket/AdminTicketList/AdminTicketList.vue

@@ -2,12 +2,12 @@
   <div class="AdminTicketList"
        :style="localOptions.style">
     <entity-index v-if="mounted"
+                  ref="entityIndex"
                   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">
@@ -27,10 +27,16 @@
 </template>
 
 <script>
+import { shallowRef } from 'vue'
 import { EntityIndex } from 'quasar-crud'
 import { Ticket } from 'src/models/Ticket.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+import { TicketCategoryList } from 'src/models/TicketCategory.js'
+
+const BtnControlComp = shallowRef(BtnControl)
 
 export default {
   name: 'AdminTicketList',
@@ -46,7 +52,11 @@ export default {
         perPage: 'per_page',
         pageKey: 'page'
       },
-      inputs: [],
+      inputs: [
+        { type: 'select', name: 'category', responseKey: 'category', placeholder: ' ', options: [], label: 'دسته', col: 'col-md-4 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' },
+        { type: 'hidden', name: 'source_type', value: null }
+      ],
       table: {
         columns: [
           {
@@ -80,11 +90,49 @@ export default {
         ]
       },
       mounted: false,
-      createRouteName: ''
+      ticketCategoryList: new TicketCategoryList()
     }
   },
   mounted() {
-    this.mounted = true
+    this.loadOptions()
+    this.setActionBtn()
+    this.checkSource()
+    this.$nextTick(() => {
+      this.mounted = true
+    })
+  },
+  methods: {
+    checkSource () {
+      if (this.localOptions.defaultSourceType) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'value', this.localOptions.defaultSourceType)
+      }
+    },
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.search)
+    },
+    loadOptions () {
+      return this.loadCategories()
+    },
+    loadCategories () {
+      return new Promise((resolve, reject) => {
+        APIGateway.ticketCategory.index()
+          .then(({ list }) => {
+            FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'options', list.list.map(item => {
+              return {
+                value: item.id,
+                label: item.title
+              }
+            }))
+            resolve()
+          })
+          .catch(() => {
+            reject()
+          })
+      })
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    }
   }
 }
 </script>

+ 7 - 1
src/components/Widgets/Ticket/AdminTicketList/OptionPanel.vue

@@ -1,7 +1,13 @@
 <template>
   <option-panel-tabs v-model:options="localOptions">
     <template #main-tab>
-      ...
+      <div>
+        defaultSourceType
+      </div>
+      <q-select v-model="localOptions.defaultSourceType"
+                :options="[{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }]"
+                emit-value
+                map-options />
     </template>
   </option-panel-tabs>
 </template>

+ 83 - 36
src/components/Widgets/Ticket/AdminTicketShow/AdminTicketShow.vue

@@ -17,7 +17,7 @@
       </div>
       <div class="back-action">
         <q-btn flat
-               :to="{name: 'AdminPanel.Ticket.List'}"
+               :to="{name: 'Admin.Invoice.Ticket'}"
                color="grey">
           بازگشت
           >
@@ -41,7 +41,7 @@
                    :show-reload-button="false"
                    :default-layout="false"
                    :redirect-after-edit="false"
-                   :after-load-input-data="afterLoadInputData" />
+                   :before-load-input-data="beforeLoadInputData" />
 
       <div class="row q-mt-lg justify-end">
         <div class="col-md-4 col-12">
@@ -104,6 +104,29 @@
         </div>
       </div>
     </q-card>
+
+    <admin-invoice-show v-if="ticket.source_type === 'INVOICE' && ticket.source_id"
+                        :options="{invoiceId: ticket.source_id, showBackBtn: false}"
+                        class="q-mt-lg" />
+
+    <q-card v-if="ticket.source_type === 'INVOICE' && ticket.source_id"
+            class="q-mt-lg">
+      <q-card-section>
+        اقساط
+      </q-card-section>
+      <q-separator />
+      <q-card-section>
+        <installment-offers :installment-offers="instalmentOffers"
+                            :loading="entityLoading"
+                            @onReject="onReject"
+                            @onAccept="onAccept" />
+      </q-card-section>
+      <q-separator />
+      <q-card-section>
+        <create-installment :invoice-id="ticket.source_id"
+                            @onCreated="onCreateInstallmentOffer" />
+      </q-card-section>
+    </q-card>
   </div>
 </template>
 
@@ -113,12 +136,20 @@ import { User } from 'src/models/User.js'
 import { Ticket } from 'src/models/Ticket.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
-import { TicketCategoryList } from 'src/models/TicketCategory'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import { TicketCategoryList } from 'src/models/TicketCategory.js'
+import { InstalmentOfferList } from 'src/models/InstalmentOffer.js'
+import AdminInvoiceShow from 'src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue'
+import InstallmentOffers from 'src/components/Widgets/Ticket/AdminTicketShow/InstallmentOffers.vue'
+import CreateInstallment from 'src/components/Widgets/Ticket/AdminTicketShow/CreateInstallment.vue'
 
 export default {
   name: 'AdminTicketShow',
   components: {
-    EntityEdit
+    EntityEdit,
+    AdminInvoiceShow,
+    CreateInstallment,
+    InstallmentOffers
   },
   mixins: [mixinWidget],
   data: () => {
@@ -127,6 +158,7 @@ export default {
       mounted: false,
       entityLoading: true,
       packageTitle: '',
+      ticket: new Ticket(),
       authenticatedUser: new User(),
       api: APIGateway.ticket.APIAdresses.base,
       ticketCategoryList: new TicketCategoryList(),
@@ -135,25 +167,12 @@ export default {
       showRouteName: 'AdminPanel.Ticket.Show',
       indexRouteName: 'AdminPanel.Ticket.List',
       inputs: [
-        {
-          type: 'select',
-          name: 'category',
-          responseKey: 'category',
-          options: [],
-          label: 'دسته',
-          col: 'col-md-6 col-12'
-        },
-        {
-          type: 'select',
-          name: 'state',
-          responseKey: 'state',
-          options: (new Ticket()).statusEnums,
-          multiple: false,
-          label: 'وضعیت',
-          col: 'col-md-6 col-12'
-        },
-        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', col: 'col-12' },
-        { type: 'inputEditor', name: 'body', responseKey: 'body', label: 'متن', col: 'col-12' },
+        { type: 'select', name: 'source_type', responseKey: 'source_type', options: [{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }], label: 'دپارتمان', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'select', name: 'category', responseKey: 'category', placeholder: ' ', options: [], label: 'دسته', col: 'col-md-4 col-12' },
+        { type: 'select', name: 'status', responseKey: 'status', options: (new Ticket()).statusEnums, multiple: false, label: 'وضعیت', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-md-12 col-12' },
+        { type: 'inputEditor', name: 'body', responseKey: 'body', label: 'متن', placeholder: ' ', col: 'col-md-12 col-12' },
+        { type: 'hidden', name: 'source_id', responseKey: 'source_id', value: null },
         { type: 'hidden', name: 'id', responseKey: 'id' },
         { type: 'hidden', name: 'owner', responseKey: 'owner' },
         { type: 'hidden', name: 'replies_info', responseKey: 'replies_info' }
@@ -161,6 +180,9 @@ export default {
     }
   },
   computed: {
+    instalmentOffers () {
+      return new InstalmentOfferList(this.ticket.source?.instalment_offers_info)
+    },
     repliesInfo () {
       return this.inputs.find(input => input.name === 'replies_info').value
     }
@@ -169,17 +191,18 @@ export default {
     this.api = this.api + '/' + this.$route.params.id
   },
   mounted() {
+    this.checkSource()
     this.loadOptions()
       .then(() => {
-        this.mounted = true
         this.loadAuthData()
+        FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'options', this.ticketCategoryList.list.map(item => {
+          return {
+            value: item.id,
+            label: item.title
+          }
+        }))
         this.$nextTick(() => {
-          this.setInputOptions('category', this.ticketCategoryList.list.map(item => {
-            return {
-              value: item.id,
-              label: item.title
-            }
-          }))
+          this.mounted = true
         })
       })
       .catch(() => {
@@ -187,22 +210,49 @@ export default {
       })
   },
   methods: {
+    onReject (offer) {
+      this.entityLoading = true
+      APIGateway.instalmentOffer.reject(offer.id)
+        .then(() => {
+          this.$refs.entityEdit.getData()
+        })
+        .catch(() => {
+          this.$refs.entityEdit.getData()
+        })
+    },
+    onAccept (offer) {
+      this.entityLoading = true
+      APIGateway.instalmentOffer.accept(offer.id)
+        .then(() => {
+          this.$refs.entityEdit.getData()
+        })
+        .catch(() => {
+          this.$refs.entityEdit.getData()
+        })
+    },
+    onCreateInstallmentOffer () {
+      this.$refs.entityEdit.getData()
+    },
+    checkSource () {
+      if (this.localOptions.defaultSourceType) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'value', this.localOptions.defaultSourceType)
+      }
+    },
     loadAuthData () {
       this.authenticatedUser = this.$store.getters['Auth/user']
     },
-    afterLoadInputData () {
+    beforeLoadInputData (data) {
       this.entityLoading = false
+      this.ticket = new Ticket(data)
     },
     edit() {
       this.entityLoading = true
       this.$refs.entityEdit.editEntity()
         .then(() => {
           this.$refs.entityEdit.getData()
-          this.entityLoading = false
         })
         .catch(() => {
           this.$refs.entityEdit.getData()
-          this.entityLoading = false
         })
     },
     loadOptions () {
@@ -233,16 +283,13 @@ export default {
           this.$refs.entityEdit.editEntity()
             .then(() => {
               this.$refs.entityEdit.getData()
-              this.entityLoading = false
             })
             .catch(() => {
               this.$refs.entityEdit.getData()
-              this.entityLoading = false
             })
         })
         .catch(() => {
           this.$refs.entityEdit.getData()
-          this.entityLoading = false
         })
     }
   }

+ 71 - 0
src/components/Widgets/Ticket/AdminTicketShow/CreateInstallment.vue

@@ -0,0 +1,71 @@
+<template>
+  <div class="CreateInstallment">
+    <entity-create ref="entityCreate"
+                   v-model:value="inputs"
+                   :api="api"
+                   :default-layout="false"
+                   :entity-id-key-in-response="entityIdKeyInResponse"
+                   :show-route-param-key="showRouteParamKey" />
+    <q-btn color="primary"
+           class="q-mt-md"
+           :loading="entityLoading"
+           @click="create">
+      تعریف پیشنهاد قسط
+    </q-btn>
+  </div>
+</template>
+
+<script>
+import { EntityCreate } from 'quasar-crud'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+
+export default {
+  name: 'CreateInstallment',
+  components: {
+    EntityCreate
+  },
+  props: {
+    invoiceId: {
+      type: Number,
+      default: null
+    }
+  },
+  data: () => {
+    return {
+      entityLoading: false,
+      api: APIGateway.instalmentOffer.APIAdresses.base,
+      entityIdKeyInResponse: 'id',
+      showRouteParamKey: 'id',
+      inputs: [
+        { type: 'input', name: 'amount', label: 'مبلغ', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'count', label: 'تعداد اقساط', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'due_date_interval', label: 'فاصله هر قسط(روز)', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'date', name: 'starting_date', label: 'تاریخ شروع اولین قسط', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'hidden', name: 'invoice', value: null }
+      ]
+    }
+  },
+  mounted() {
+    FormBuilderAssist.setAttributeByName(this.inputs, 'invoice', 'value', this.invoiceId)
+  },
+  methods: {
+    create() {
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity(false)
+        .then(() => {
+          this.entityLoading = false
+          this.$emit('onCreated')
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.CreateInstallment {
+}
+</style>

+ 106 - 0
src/components/Widgets/Ticket/AdminTicketShow/InstallmentOffers.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="InstallmentOffers">
+    <q-list separator>
+      <q-item v-for="offer in installmentOffers.list"
+              :key="offer.id"
+              dense>
+        <q-item-section>
+          <div class="row q-col-gutter-md">
+            <div class="col-md-3 col-12">
+              مبلغ:
+              {{ offer.amount ? offer.amount.toLocaleString('fa') : 0 }}
+            </div>
+            <div class="col-md-3 col-12">
+              تعداد اقساط:
+              {{ offer.count }}
+            </div>
+            <div class="col-md-3 col-12">
+              فاصله هر فسط(روز):
+              {{ offer.due_date_interval }}
+            </div>
+            <div class="col-md-3 col-12">
+              تاریخ شروع اولین قسط:
+              {{ toShamsi(offer.starting_date) }}
+              <q-badge v-if="offer.is_accepted === true"
+                       color="green">
+                تایید شده
+              </q-badge>
+              <q-badge v-if="offer.is_accepted === false"
+                       color="red">
+                رد شده
+              </q-badge>
+            </div>
+            <div v-if="offer.is_accepted === null"
+                 class="col-12 flex justify-end">
+              <q-btn color="red"
+                     :loading="loading"
+                     @click="onReject(offer)">
+                رد درخواست
+              </q-btn>
+              <q-btn color="green"
+                     class="q-ml-xl"
+                     :loading="loading"
+                     @click="onAccept(offer)">
+                تایید درخواست
+              </q-btn>
+            </div>
+          </div>
+        </q-item-section>
+      </q-item>
+    </q-list>
+  </div>
+</template>
+
+<script>
+import Assist from 'src/assets/js/Assist.js'
+import { InstalmentOfferList } from 'src/models/InstalmentOffer.js'
+
+export default {
+  name: 'InstallmentOffers',
+  props: {
+    installmentOffers: {
+      type: InstalmentOfferList,
+      default: new InstalmentOfferList()
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data: () => {
+    return {
+    }
+  },
+  methods: {
+    toShamsi (miladi) {
+      return Assist.miladiToShamsi(miladi, 'jYYYY/jMM/jDD')
+    },
+    create() {
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity(false)
+        .then(() => {
+          this.entityLoading = false
+          this.$emit('onCreated')
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    },
+    onReject (offer) {
+      this.$emit('onReject', offer)
+    },
+    onAccept (offer) {
+      this.$emit('onAccept', offer)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.InstallmentOffers {
+  .q-btn {
+    padding: 5px;
+    height: 40px;
+  }
+}
+</style>

+ 7 - 1
src/components/Widgets/Ticket/AdminTicketShow/OptionPanel.vue

@@ -1,7 +1,13 @@
 <template>
   <option-panel-tabs v-model:options="localOptions">
     <template #main-tab>
-      ...
+      <div>
+        defaultSourceType
+      </div>
+      <q-select v-model="localOptions.defaultSourceType"
+                :options="[{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }]"
+                emit-value
+                map-options />
     </template>
   </option-panel-tabs>
 </template>

+ 7 - 1
src/components/Widgets/Ticket/TicketCreate/OptionPanel.vue

@@ -1,7 +1,13 @@
 <template>
   <option-panel-tabs v-model:options="localOptions">
     <template #main-tab>
-      ...
+      <div>
+        defaultSourceType
+      </div>
+      <q-select v-model="localOptions.defaultSourceType"
+                :options="[{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }]"
+                emit-value
+                map-options />
     </template>
   </option-panel-tabs>
 </template>

+ 22 - 4
src/components/Widgets/Ticket/TicketCreate/TicketCreate.vue

@@ -44,6 +44,7 @@
 import { EntityCreate } from 'quasar-crud'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
 
 export default {
   name: 'TicketCreate',
@@ -59,20 +60,37 @@ export default {
       showRouteName: 'UserPanel.Ticket.Show',
       indexRouteName: 'UserPanel.Ticket.List',
       inputs: [
-        { type: 'select', name: 'category', responseKey: 'category', options: [], label: 'دسته', col: 'col-md-12 col-12' },
-        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', col: 'col-md-12 col-12' },
-        { type: 'inputEditor', name: 'body', responseKey: 'body', label: 'متن', col: 'col-md-12 col-12' },
-        { type: 'hidden', name: 'owner', responseKey: 'owner', value: 1, label: 'owner', col: 'col-md-12 col-12' }
+        { type: 'select', name: 'source_type', options: [{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }], label: 'دپارتمان', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'select', name: 'category', responseKey: 'category', options: [], label: 'دسته', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'separator', name: 'separator', size: '0', label: 'توضیحات عنوان اینجا قرار میگیرد', col: 'col-12' },
+        { type: 'inputEditor', name: 'body', label: 'متن درخواست', col: 'col-md-12 col-12' },
+        { type: 'file', name: 'attachment', label: 'انتخاب فایل ضمیمه', col: 'col-md-3 col-12' },
+        { type: 'hidden', name: 'owner', value: 1 },
+        { type: 'hidden', name: 'source_id', value: null }
       ]
     }
   },
   mounted() {
+    this.checkSource()
     this.loadOptions()
     this.$nextTick(() => {
       this.mounted = true
     })
   },
   methods: {
+    checkSource () {
+      const sourceId = this.$route.query.source_id
+      const sourceType = this.$route.query.source_type
+      if (sourceType && sourceId) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_id', 'value', sourceId)
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'value', sourceType)
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'readonly', true)
+      } else if (this.localOptions.defaultSourceType) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'value', this.localOptions.defaultSourceType)
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'readonly', true)
+      }
+    },
     create() {
       this.entityLoading = true
       this.$refs.entityCreate.createEntity()

+ 7 - 1
src/components/Widgets/Ticket/TicketList/OptionPanel.vue

@@ -1,7 +1,13 @@
 <template>
   <option-panel-tabs v-model:options="localOptions">
     <template #main-tab>
-      ...
+      <div>
+        defaultSourceType
+      </div>
+      <q-select v-model="localOptions.defaultSourceType"
+                :options="[{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }]"
+                emit-value
+                map-options />
     </template>
   </option-panel-tabs>
 </template>

+ 106 - 0
src/components/Widgets/Ticket/TicketShow/InstallmentOffers.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="InstallmentOffers">
+    <q-list separator>
+      <q-item v-for="offer in installmentOffers.list"
+              :key="offer.id"
+              dense>
+        <q-item-section>
+          <div class="row q-col-gutter-md">
+            <div class="col-md-3 col-12">
+              مبلغ:
+              {{ offer.amount ? offer.amount.toLocaleString('fa') : 0 }}
+            </div>
+            <div class="col-md-3 col-12">
+              تعداد اقساط:
+              {{ offer.count }}
+            </div>
+            <div class="col-md-3 col-12">
+              فاصله هر فسط(روز):
+              {{ offer.due_date_interval }}
+            </div>
+            <div class="col-md-3 col-12">
+              تاریخ شروع اولین قسط:
+              {{ toShamsi(offer.starting_date) }}
+              <q-badge v-if="offer.is_accepted === true"
+                       color="green">
+                تایید شده
+              </q-badge>
+              <q-badge v-if="offer.is_accepted === false"
+                       color="red">
+                رد شده
+              </q-badge>
+            </div>
+            <div v-if="offer.is_accepted === null"
+                 class="col-12 flex justify-end">
+              <q-btn color="red"
+                     :loading="loading"
+                     @click="onReject(offer)">
+                رد درخواست
+              </q-btn>
+              <q-btn color="green"
+                     class="q-ml-xl"
+                     :loading="loading"
+                     @click="onAccept(offer)">
+                تایید درخواست
+              </q-btn>
+            </div>
+          </div>
+        </q-item-section>
+      </q-item>
+    </q-list>
+  </div>
+</template>
+
+<script>
+import Assist from 'src/assets/js/Assist.js'
+import { InstalmentOfferList } from 'src/models/InstalmentOffer.js'
+
+export default {
+  name: 'InstallmentOffers',
+  props: {
+    installmentOffers: {
+      type: InstalmentOfferList,
+      default: new InstalmentOfferList()
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data: () => {
+    return {
+    }
+  },
+  methods: {
+    toShamsi (miladi) {
+      return Assist.miladiToShamsi(miladi, 'jYYYY/jMM/jDD')
+    },
+    create() {
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity(false)
+        .then(() => {
+          this.entityLoading = false
+          this.$emit('onCreated')
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    },
+    onReject (offer) {
+      this.$emit('onReject', offer)
+    },
+    onAccept (offer) {
+      this.$emit('onAccept', offer)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.InstallmentOffers {
+  .q-btn {
+    padding: 5px;
+    height: 40px;
+  }
+}
+</style>

+ 7 - 1
src/components/Widgets/Ticket/TicketShow/OptionPanel.vue

@@ -1,7 +1,13 @@
 <template>
   <option-panel-tabs v-model:options="localOptions">
     <template #main-tab>
-      ...
+      <div>
+        defaultSourceType
+      </div>
+      <q-select v-model="localOptions.defaultSourceType"
+                :options="[{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }]"
+                emit-value
+                map-options />
     </template>
   </option-panel-tabs>
 </template>

+ 72 - 50
src/components/Widgets/Ticket/TicketShow/TicketShow.vue

@@ -41,6 +41,7 @@
                    :show-reload-button="false"
                    :default-layout="false"
                    :redirect-after-edit="false"
+                   :before-load-input-data="beforeLoadInputData"
                    :after-load-input-data="afterLoadInputData" />
 
       <q-card v-if="mounted"
@@ -77,7 +78,7 @@
       <div class="action">
         <div class="row q-mt-lg justify-end">
           <div class="col-md-12 col-12">
-            متن پیام
+            پاسخ
           </div>
           <div class="col-md-12 col-12 q-mb-md">
             <q-input v-model="replyText"
@@ -94,6 +95,20 @@
         </div>
       </div>
     </q-card>
+
+    <q-card v-if="ticket.source_type === 'INVOICE' && ticket.source_id"
+            class="q-mt-lg">
+      <q-card-section>
+        اقساط
+      </q-card-section>
+      <q-separator />
+      <q-card-section>
+        <installment-offers :installment-offers="instalmentOffers"
+                            :loading="entityLoading"
+                            @onReject="onReject"
+                            @onAccept="onAccept" />
+      </q-card-section>
+    </q-card>
   </div>
 </template>
 
@@ -103,16 +118,21 @@ import { User } from 'src/models/User.js'
 import { Ticket } from 'src/models/Ticket.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
 import { TicketCategoryList } from 'src/models/TicketCategory.js'
+import { InstalmentOfferList } from 'src/models/InstalmentOffer.js'
+import InstallmentOffers from 'src/components/Widgets/Ticket/TicketShow/InstallmentOffers.vue'
 
 export default {
   name: 'TicketShow',
   components: {
-    EntityShow
+    EntityShow,
+    InstallmentOffers
   },
   mixins: [mixinWidget],
   data: () => {
     return {
+      ticket: new Ticket(),
       replyText: null,
       mounted: false,
       entityLoading: true,
@@ -125,48 +145,22 @@ export default {
       showRouteName: 'AdminPanel.Ticket.Show',
       indexRouteName: 'AdminPanel.Ticket.List',
       inputs: [
-        {
-          type: 'select',
-          name: 'category',
-          responseKey: 'category',
-          placeholder: ' ',
-          options: [],
-          label: 'دسته',
-          col: 'col-md-6 col-12'
-        },
-        {
-          type: 'select',
-          name: 'status',
-          responseKey: 'status',
-          options: (new Ticket()).statusEnums,
-          multiple: false,
-          label: 'وضعیت',
-          placeholder: ' ',
-          col: 'col-md-6 col-12'
-        },
-        {
-          type: 'input',
-          name: 'title',
-          responseKey: 'title',
-          label: 'عنوان',
-          placeholder: ' ',
-          col: 'col-md-12 col-12'
-        },
-        {
-          type: 'inputEditor',
-          name: 'body',
-          responseKey: 'body',
-          label: 'متن',
-          placeholder: ' ',
-          col: 'col-md-12 col-12'
-        },
-        { type: 'hidden', name: 'id', responseKey: 'id', label: 'شناسه', col: 'col-md-12' },
-        { type: 'hidden', name: 'owner', responseKey: 'owner', label: 'owner', col: 'col-md-12' },
-        { type: 'hidden', name: 'replies_info', responseKey: 'replies_info', label: 'replies_info', col: 'col-md-12' }
+        { type: 'select', name: 'source_type', responseKey: 'source_type', options: [{ label: 'مالی', value: 'INVOICE' }, { label: 'آموزش', value: 'CLASSROOM' }], label: 'دپارتمان', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'select', name: 'category', responseKey: 'category', placeholder: ' ', options: [], label: 'دسته', col: 'col-md-4 col-12' },
+        { type: 'select', name: 'status', responseKey: 'status', options: (new Ticket()).statusEnums, multiple: false, label: 'وضعیت', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-md-12 col-12' },
+        { type: 'inputEditor', name: 'body', responseKey: 'body', label: 'متن', placeholder: ' ', col: 'col-md-12 col-12' },
+        { type: 'hidden', name: 'source_id', responseKey: 'source_id', value: null },
+        { type: 'hidden', name: 'id', responseKey: 'id' },
+        { type: 'hidden', name: 'owner', responseKey: 'owner' },
+        { type: 'hidden', name: 'replies_info', responseKey: 'replies_info' }
       ]
     }
   },
   computed: {
+    instalmentOffers () {
+      return new InstalmentOfferList(this.ticket.source?.instalment_offers_info)
+    },
     repliesInfo () {
       return this.inputs.find(input => input.name === 'replies_info').value
     }
@@ -175,17 +169,18 @@ export default {
     this.api = this.api + '/' + this.$route.params.id
   },
   mounted() {
+    this.checkSource()
     this.loadOptions()
       .then(() => {
-        this.mounted = true
         this.loadAuthData()
+        FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'options', this.ticketCategoryList.list.map(item => {
+          return {
+            value: item.id,
+            label: item.title
+          }
+        }))
         this.$nextTick(() => {
-          this.setInputOptions('category', this.ticketCategoryList.list.map(item => {
-            return {
-              value: item.id,
-              label: item.title
-            }
-          }))
+          this.mounted = true
         })
       })
       .catch(() => {
@@ -193,9 +188,39 @@ export default {
       })
   },
   methods: {
+    onReject (offer) {
+      this.entityLoading = true
+      APIGateway.instalmentOffer.reject(offer.id)
+        .then(() => {
+          this.$refs.entityShow.getData()
+        })
+        .catch(() => {
+          this.$refs.entityShow.getData()
+        })
+    },
+    onAccept (offer) {
+      this.entityLoading = true
+      APIGateway.instalmentOffer.accept(offer.id)
+        .then(() => {
+          this.$refs.entityShow.getData()
+        })
+        .catch(() => {
+          this.$refs.entityShow.getData()
+        })
+    },
+    checkSource () {
+      if (this.localOptions.defaultSourceType) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'value', this.localOptions.defaultSourceType)
+        FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'readonly', true)
+      }
+    },
     loadAuthData () {
       this.authenticatedUser = this.$store.getters['Auth/user']
     },
+    beforeLoadInputData (data) {
+      this.ticket = new Ticket(data)
+      this.entityLoading = false
+    },
     afterLoadInputData (response) {
       this.ticketTitle = response.title
       this.entityLoading = false
@@ -212,16 +237,13 @@ export default {
             .then(() => {
               this.replyText = null
               this.$refs.entityShow.getData()
-              this.entityLoading = false
             })
             .catch(() => {
               this.$refs.entityShow.getData()
-              this.entityLoading = false
             })
         })
         .catch(() => {
           this.$refs.entityShow.getData()
-          this.entityLoading = false
         })
     },
     loadOptions () {

+ 29 - 0
src/models/InstalmentOffer.js

@@ -0,0 +1,29 @@
+import { Invoice } from 'src/models/Invoice.js'
+import { Model, Collection } from 'js-abstract-model'
+
+class InstalmentOffer extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'amount' },
+      { key: 'count' },
+      { key: 'due_date_interval' },
+      { key: 'starting_date' },
+      { key: 'description' },
+      { key: 'is_accepted' },
+      { key: 'invoice' },
+      {
+        key: 'invoice_info',
+        relatedModel: Invoice
+      }
+    ])
+  }
+}
+
+class InstalmentOfferList extends Collection {
+  model() {
+    return InstalmentOffer
+  }
+}
+
+export { InstalmentOffer, InstalmentOfferList }

+ 3 - 0
src/models/Ticket.js

@@ -11,6 +11,9 @@ class Ticket extends Model {
       { key: 'body' }, //
       { key: 'priority' }, // HIGH - ...
       { key: 'status' }, // ADMIN_REPLIED - ...
+      { key: 'source_type' }, // INVOICE - CLASSROOM
+      { key: 'source_id' },
+      { key: 'source' },
       { key: 'category' },
       {
         key: 'category_info',

+ 24 - 0
src/pages/Admin/Invoice/List.vue

@@ -0,0 +1,24 @@
+<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: 'AdminInvoiceList' }], options: { style: { paddingTop: '100px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 24 - 0
src/pages/Admin/Invoice/Show.vue

@@ -0,0 +1,24 @@
+<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: 'AdminInvoiceShow' }], options: { style: { paddingTop: '124px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 24 - 0
src/pages/Admin/Invoice/Thankyou.vue

@@ -0,0 +1,24 @@
+<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: '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
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 24 - 0
src/pages/Admin/Invoice/Ticket/Create.vue

@@ -0,0 +1,24 @@
+<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: 'TicketCreate', options: { style: {}, defaultSourceType: { label: 'مالی', value: 'INVOICE' } } }], options: { style: { marginTop: '124px', marginBottom: '100px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 24 - 0
src/pages/Admin/Invoice/Ticket/List.vue

@@ -0,0 +1,24 @@
+<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.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminTicketList', options: { style: {}, defaultSourceType: 'INVOICE' } }], options: { style: { paddingTop: '124px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 24 - 0
src/pages/Admin/Invoice/Ticket/Show.vue

@@ -0,0 +1,24 @@
+<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: 'TicketShow', options: { style: {}, defaultSourceType: { label: 'مالی', value: 'INVOICE' } } }], options: { style: { paddingTop: '124px', paddingBottom: '100px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 1 - 1
src/pages/Admin/Ticket/Show.vue

@@ -13,7 +13,7 @@ export default {
   mixins: [mixinPageOptions],
   data: () => {
     return {
-      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminTicketShow' }], options: { style: { paddingTop: '124px' } } }], options: {} }] } }]
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminTicketShow' }], options: { style: { paddingTop: '10px' } } }], options: {} }] } }]
     }
   },
   created() {

+ 24 - 0
src/pages/User/Invoice/Ticket/Create.vue

@@ -0,0 +1,24 @@
+<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: 'TicketCreate', options: { style: {}, defaultSourceType: { label: 'مالی', value: 'INVOICE' } } }], options: { style: { marginTop: '124px', marginBottom: '100px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 24 - 0
src/pages/User/Invoice/Ticket/List.vue

@@ -0,0 +1,24 @@
+<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.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'TicketList' }], options: { style: { paddingTop: '124px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 24 - 0
src/pages/User/Invoice/Ticket/Show.vue

@@ -0,0 +1,24 @@
+<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: 'TicketShow', options: { style: {}, defaultSourceType: { label: 'مالی', value: 'INVOICE' } } }], options: { style: { paddingTop: '124px', paddingBottom: '100px' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+    this.pageBuilderLoading = false
+  }
+}
+</script>

+ 56 - 0
src/router/routes.js

@@ -165,6 +165,28 @@ const routes = [
                 path: ':id',
                 name: 'UserPanel.Invoice.Show',
                 component: () => import('src/pages/User/Invoice/Show.vue')
+              },
+              {
+                path: 'ticket',
+                name: 'UserPanel.Invoice.Ticket',
+                component: () => import('layouts/bareLayout.vue'),
+                children: [
+                  {
+                    path: '',
+                    name: 'UserPanel.Invoice.Ticket.List',
+                    component: () => import('src/pages/User/Invoice/Ticket/List.vue')
+                  },
+                  {
+                    path: ':id',
+                    name: 'UserPanel.Invoice.Ticket.Show',
+                    component: () => import('src/pages/User/Invoice/Ticket/Show.vue')
+                  },
+                  {
+                    path: 'create',
+                    name: 'UserPanel.Invoice.Ticket.Create',
+                    component: () => import('src/pages/User/Invoice/Ticket/Create.vue')
+                  }
+                ]
               }
             ]
           },
@@ -340,6 +362,40 @@ const routes = [
               { name: 'Admin.Session.Create', path: 'create', component: () => import('src/pages/Admin/Session/create.vue') },
               { name: 'Admin.Session.Show', path: ':id', component: () => import('src/pages/Admin/Session/show.vue') }
             ]
+          },
+          {
+            path: 'invoice',
+            name: 'Admin.Invoice',
+            component: () => import('layouts/bareLayout.vue'),
+            children: [
+              {
+                path: '',
+                name: 'Admin.Invoice.List',
+                component: () => import('src/pages/Admin/Invoice/List.vue')
+              },
+              {
+                path: ':id',
+                name: 'Admin.Invoice.Show',
+                component: () => import('src/pages/Admin/Invoice/Show.vue')
+              },
+              {
+                path: 'ticket',
+                name: 'Admin.Invoice.Ticket',
+                component: () => import('layouts/bareLayout.vue'),
+                children: [
+                  {
+                    path: '',
+                    name: 'Admin.Invoice.Ticket.List',
+                    component: () => import('src/pages/Admin/Invoice/Ticket/List.vue')
+                  },
+                  {
+                    path: ':id',
+                    name: 'Admin.Invoice.Ticket.Show',
+                    component: () => import('src/pages/Admin/Invoice/Ticket/Show.vue')
+                  }
+                ]
+              }
+            ]
           }
         ]
       },