Jelajahi Sumber

fix: cart invoice

ali 2 tahun lalu
induk
melakukan
a76cd3c063

+ 16 - 0
src/api/models/Basket.js

@@ -1,4 +1,5 @@
 import { appApi } from 'src/boot/axios.js'
+import { Invoice } from 'src/models/Invoice.js'
 import APIRepository from '../classes/APIRepository.js'
 import { Basket, BasketList } from 'src/models/Basket.js'
 
@@ -10,6 +11,7 @@ export default class BasketAPI extends APIRepository {
       checkoutReview: '/store/baskets/checkout_review',
       byId: (id) => '/store/baskets/' + id,
       send: (id) => '/store/baskets/' + id + '/send',
+      createInvoice: (id) => '/store/baskets/' + id + '/create_invoice',
       submitDiscountCode: (id) => '/store/baskets/' + id + '/submit_discount_code',
       removeDiscountCode: (id) => '/store/baskets/' + id + '/remove_discount_code'
     }
@@ -151,4 +153,18 @@ export default class BasketAPI extends APIRepository {
       }
     })
   }
+
+  createInvoice (basketId) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.createInvoice(basketId),
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
 }

+ 2 - 4
src/components/Widgets/Invoice/InvoiceList/InvoiceList.vue

@@ -77,15 +77,13 @@ export default {
         {
           type: 'date',
           name: 'creation_time',
-          label: 'از تاریخ',
-          placeholder: ' ',
+          outsideLabel: 'از تاریخ',
           col: 'col-md-3 col-12'
         },
         {
           type: 'date',
           name: 'creation_time',
-          label: 'تا تاریخ',
-          placeholder: ' ',
+          outsideLabel: 'تا تاریخ',
           col: 'col-md-3 col-12'
         },
         { type: 'select', name: 'category', options: [], label: 'وضعیت تراکنش', placeholder: ' ', col: 'col-md-2 col-12' },

+ 1 - 1
src/components/Widgets/Other/Shop/ShopCompleteInfo/ShopCompleteInfo.vue

@@ -192,7 +192,7 @@ export default {
         label: 'جنسیت',
         col: ' col-md-3 col-12'
       },
-      { type: 'date', name: 'birthdate', label: 'تولد', responseKey: 'birthdate', placeholder: ' ', className: 'require', col: ' col-md-3 col-12' },
+      { type: 'date', name: 'birthdate', outsideLabel: 'تولد', responseKey: 'birthdate', className: 'require', col: ' col-md-3 col-12' },
       {
         type: 'select',
         name: 'marital_status',

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

@@ -250,7 +250,6 @@ import { User } from 'src/models/User.js'
 import Enums from 'src/assets/Enums/Enums.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 { APIGateway } from 'src/api/APIGateway.js'
 import { Classroom } from 'src/models/Classroom.js'

+ 6 - 1
src/components/Widgets/Store/Cart/components/step1/step1.vue

@@ -2,7 +2,7 @@
   <div class="step1">
     <q-linear-progress v-if="basket.loading"
                        indeterminate />
-    <template v-if="!basket.loading">
+    <template v-if="!basket.loading && basket.items_info.list.length > 0">
       <div class="step1__cart-items">
         <cart-item v-for="(basketItem, basketItemIndex) in basket.items_info.list"
                    :key="basketItemIndex"
@@ -17,6 +17,11 @@
                  @complete="onComplete" />
       </div>
     </template>
+    <template v-else-if="!basket.loading && basket.items_info.list.length === 0">
+      <q-banner>
+        سبد خرید شما خالیست.
+      </q-banner>
+    </template>
   </div>
 </template>
 

+ 70 - 0
src/components/Widgets/Store/Cart/components/step3/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>

+ 172 - 0
src/components/Widgets/Store/Cart/components/step3/step3.vue

@@ -0,0 +1,172 @@
+<template>
+  <div class="step3">
+    <q-linear-progress v-if="basket.loading"
+                       indeterminate />
+    <template v-if="!basket.loading">
+      <invoice-info v-if="!invoice.loading"
+                    :invoice="invoice"
+                    :classroom="classroom"
+                    class="invoice-info" />
+      <invoice-payment-card v-if="!invoice.loading"
+                            :wallet="wallet"
+                            :invoice="invoice"
+                            class="payment-card"
+                            @onCancel="onCancel"
+                            @onAccept="onAccept" />
+    </template>
+  </div>
+</template>
+
+<script>
+import { User } from 'src/models/User.js'
+import InvoiceInfo from './InvoiceInfo.vue'
+import { Wallet } from 'src/models/Wallet.js'
+import { Basket } from 'src/models/Basket.js'
+import { mixinAuth } from 'src/mixin/Mixins.js'
+import { Invoice } from 'src/models/Invoice.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import InvoicePaymentCard from 'src/components/InvoicePaymentCard/InvoicePaymentCard.vue'
+
+export default {
+  name: 'step2',
+  components: { InvoicePaymentCard, InvoiceInfo },
+  mixins: [mixinAuth],
+  props: {
+    type: {
+      type: String,
+      default: 'product'
+    }
+  },
+  emits: ['complete'],
+  data () {
+    return {
+      user: new User(),
+      wallet: new Wallet(),
+      invoice: new Invoice()
+    }
+  },
+  computed: {
+    basket: {
+      get () {
+        return this.$store.getters['Shop/basket']
+      },
+      set (newValue) {
+        this.$store.commit('Shop/updateBasket', newValue)
+      }
+    }
+  },
+  mounted() {
+    this.getMyWallet()
+    this.checkoutReview()
+    this.getBasketInvoice()
+  },
+  methods: {
+    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
+        })
+    },
+    checkoutReview () {
+      this.basket.loading = true
+      APIGateway.basket.checkoutReview()
+        .then((basket) => {
+          this.basket = new Basket(basket)
+        })
+        .finally(() => {
+          this.basket.loading = false
+        })
+    },
+    getBasketInvoice () {
+      this.invoice.loading = true
+      APIGateway.basket.createInvoice(this.basket.id)
+        .then((invoice) => {
+          this.invoice = new Invoice(invoice)
+        })
+        .finally(() => {
+          this.invoice.loading = false
+        })
+    },
+    onCancel () {
+      this.invoice.loading = true
+      APIGateway.invoice.cancel(this.invoice.id)
+        .then(() => {
+          this.invoice.loading = false
+          this.goToShop()
+        })
+        .finally(() => {
+          this.invoice.loading = false
+        })
+    },
+    goToShop () {
+      this.$router.push({ name: 'Public.Shop' })
+    },
+    onAccept () {
+      this.payInvoice()
+    },
+    payInvoice () {
+      if (this.amountOfDepositWalletNeeded() > 0) {
+        this.payInvoiceByDepositWallet()
+      } else {
+        this.payInvoiceByWallet()
+      }
+    },
+    amountOfDepositWalletNeeded () {
+      return this.invoice.amount - this.wallet.inventory
+    },
+    payInvoiceByWallet () {
+      this.invoice.loading = true
+      APIGateway.invoice.pay(this.invoice.id)
+        .then((message) => {
+          this.payMessage = message
+          this.goToShop()
+        })
+        .finally(() => {
+          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
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.step2 {
+  display: flex;
+  flex-flow: row;
+  gap: 28px;
+  $sidebar-width: 312px;
+  .step2__cart-info {
+    width: calc( 100% - #{$sidebar-width} );
+    display: flex;
+    flex-flow: column;
+    gap: 24px;
+  }
+  .step2__sidebar {
+    width: $sidebar-width;
+  }
+}
+
+:global(.step2__confirm-dialog) {
+  width: 900px;
+  max-width: 90vw;
+}
+</style>

+ 4 - 3
src/components/Widgets/Store/Cart/components/stepper.vue

@@ -30,7 +30,7 @@
               done-icon="account_balance_wallet"
               active-icon="account_balance_wallet"
               disable>
-        This step won't show up because it is disabled.
+        <step3 />
       </q-step>
     </q-stepper>
   </div>
@@ -39,10 +39,11 @@
 <script>
 import Step1 from './step1/step1.vue'
 import Step2 from './step2/step2.vue'
+import Step3 from './step3/step3.vue'
 
 export default {
   name: 'stepper',
-  components: { Step1, Step2 },
+  components: { Step1, Step2, Step3 },
   props: {
     type: {
       type: String,
@@ -53,7 +54,7 @@ export default {
   data () {
     return {
       mounted: false,
-      step: 2
+      step: 1
     }
   },
   mounted() {

+ 17 - 10
src/components/Widgets/Store/Shop/Shop.vue

@@ -63,8 +63,9 @@
                             :show-reload-button="false"
                             :show-search-button="false">
                 <template #entity-index-table-item-cell="{inputData}">
-                  <product-item class="shop-widget__result-product-item"
-                                :product="getProduct(inputData.props.row)" />
+                  <div class="shop-widget__result-product-item">
+                    <product-item :product="getProduct(inputData.props.row)" />
+                  </div>
                 </template>
               </entity-index>
             </q-tab-panel>
@@ -80,8 +81,9 @@
                             :show-reload-button="false"
                             :show-search-button="false">
                 <template #entity-index-table-item-cell="{inputData}">
-                  <package-item class="shop-widget__result-product-item"
-                                :package-item="getPackage(inputData.props.row)" />
+                  <div class="shop-widget__result-product-item">
+                    <package-item :package-item="getPackage(inputData.props.row)" />
+                  </div>
                 </template>
               </entity-index>
             </q-tab-panel>
@@ -302,12 +304,17 @@ export default {
         }
       }
       :deep(.shop-widget__result) {
-        .quasar-crud-index-table {
-          .q-table__grid-content {
-            gap: 36px;
-            .shop-widget__result-product-item {
-              width: 33%;
-              flex: 0 0 auto;
+        .q-tab-panel {
+          padding: 0;
+          .quasar-crud-index-table {
+            padding: 0;
+            .q-table__grid-content {
+              margin: -18px;
+              .shop-widget__result-product-item {
+                width: 33%;
+                padding: 18px;
+                flex: 0 0 auto;
+              }
             }
           }
         }

+ 12 - 0
src/components/Widgets/Store/Shop/components/packageItem.vue

@@ -44,6 +44,7 @@
 </template>
 
 <script>
+import { Basket } from 'src/models/Basket.js'
 import { Package } from 'src/models/Package.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 
@@ -66,6 +67,17 @@ export default {
       APIGateway.basketItem.addPackage(this.packageItem.id, 1)
         .finally(() => {
           this.addToCartLoading = false
+          this.checkoutReview()
+        })
+    },
+    checkoutReview () {
+      this.basket.loading = true
+      APIGateway.basket.checkoutReview()
+        .then((basket) => {
+          this.$store.commit('Shop/updateBasket', new Basket(basket))
+        })
+        .finally(() => {
+          this.basket.loading = false
         })
     }
   }

+ 19 - 4
src/components/Widgets/Store/Shop/components/productItem.vue

@@ -52,6 +52,7 @@
 </template>
 
 <script>
+import { Basket } from 'src/models/Basket.js'
 import { Product } from 'src/models/Product.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 
@@ -74,6 +75,17 @@ export default {
       APIGateway.basketItem.addProduct(this.product.id, 1)
         .finally(() => {
           this.addToCartLoading = false
+          this.checkoutReview()
+        })
+    },
+    checkoutReview () {
+      this.basket.loading = true
+      APIGateway.basket.checkoutReview()
+        .then((basket) => {
+          this.$store.commit('Shop/updateBasket', new Basket(basket))
+        })
+        .finally(() => {
+          this.basket.loading = false
         })
     }
   }
@@ -82,7 +94,7 @@ export default {
 
 <style scoped lang="scss">
 .product-item {
-  $transition-time: 0.2s;
+  $transition-time: 0s;
   background: #F6F6F6;
   border: 1px solid #DFE1EC;
   border-radius: 8px;
@@ -121,6 +133,9 @@ export default {
   :deep(.product-item__thumbnail) {
     transition: all $transition-time;
     margin: 15px 58px 12px 58px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
     .q-img {
       width: 100%;
     }
@@ -140,7 +155,7 @@ export default {
     padding: 0 20px;
     margin-bottom: 43px;
     .product-item__price-final {
-      font-size: 20px;
+      font-size: 18px;
       color: #475F4A;
     }
     .product-item__price-base {
@@ -170,9 +185,9 @@ export default {
       }
     }
     :deep(.product-item__thumbnail) {
-      margin: 10px 105px 4px 105px;
+      margin: 10px auto 4px auto;
       .q-img {
-        width: 100%;
+        width: 90px;
       }
     }
     .product-item__title {

+ 12 - 0
src/components/Widgets/Store/StorePackage/StorePackageShow/StorePackageShow.vue

@@ -81,6 +81,7 @@
 </template>
 
 <script>
+import { Basket } from 'src/models/Basket.js'
 import { Package } from 'src/models/Package.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
@@ -112,6 +113,17 @@ export default {
       APIGateway.basketItem.addProduct(this.packageId, 1)
         .finally(() => {
           this.addToCartLoading = false
+          this.checkoutReview()
+        })
+    },
+    checkoutReview () {
+      this.basket.loading = true
+      APIGateway.basket.checkoutReview()
+        .then((basket) => {
+          this.$store.commit('Shop/updateBasket', new Basket(basket))
+        })
+        .finally(() => {
+          this.basket.loading = false
         })
     },
     getPackage () {

+ 12 - 0
src/components/Widgets/Store/StoreProduct/StoreProductShow/StoreProductShow.vue

@@ -94,6 +94,7 @@
 </template>
 
 <script>
+import { Basket } from 'src/models/Basket.js'
 import { Product } from 'src/models/Product.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
@@ -125,6 +126,17 @@ export default {
       APIGateway.basketItem.addProduct(this.productId, 1)
         .finally(() => {
           this.addToCartLoading = false
+          this.checkoutReview()
+        })
+    },
+    checkoutReview () {
+      this.basket.loading = true
+      APIGateway.basket.checkoutReview()
+        .then((basket) => {
+          this.$store.commit('Shop/updateBasket', new Basket(basket))
+        })
+        .finally(() => {
+          this.basket.loading = false
         })
     },
     getPackage () {

+ 1 - 1
src/components/Widgets/Ticket/AdminTicketShow/CreateInstallment.vue

@@ -41,7 +41,7 @@ export default {
         { 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: 'date', name: 'starting_date', outsideLabel: 'تاریخ شروع اولین قسط', col: 'col-md-3 col-12' },
         { type: 'hidden', name: 'invoice', value: null }
       ]
     }

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

@@ -294,7 +294,7 @@ export default {
           placeholder: ' ',
           col: 'col-md-3 col-12'
         },
-        { type: 'date', name: 'birthdate', placeholder: ' ', label: 'تولد', responseKey: 'birthdate', col: 'col-md-3 col-12' },
+        { type: 'date', name: 'birthdate', outsideLabel: 'تولد', responseKey: 'birthdate', col: 'col-md-3 col-12' },
         {
           type: 'select',
           name: 'marital_status',

+ 1 - 1
src/pages/User/Profile/profile.vue

@@ -76,7 +76,7 @@ export default {
           className: 'require',
           col: 'col-md-6 col-12'
         },
-        { type: 'date', name: 'birthdate', placeholder: ' ', label: 'تولد', responseKey: 'birthdate', className: 'require', col: 'col-md-6 col-12' },
+        { type: 'date', name: 'birthdate', outsideLabel: 'تولد', responseKey: 'birthdate', className: 'require', col: 'col-md-6 col-12' },
         {
           type: 'select',
           name: 'marital_status',