mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-30 05:01:10 -04:00 
			
		
		
		
	Fix Invoice/Estimate template issues and Add Payment Receipt, Custom Payment Modes and Item units
This commit is contained in:
		
				
					committed by
					
						 Mohit Panjwani
						Mohit Panjwani
					
				
			
			
				
	
			
			
			
						parent
						
							56a955befd
						
					
				
				
					commit
					4c33a5d88c
				
			| @ -85,7 +85,8 @@ | ||||
|             </div> | ||||
|             <div class="customer-content mb-1"> | ||||
|               <label class="email">{{ selectedCustomer.name }}</label> | ||||
|               <label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label> | ||||
|               <label class="action" @click="editCustomer">{{ $t('general.edit') }}</label> | ||||
|               <label class="action" @click="removeCustomer">{{ $t('general.deselect') }}</label> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -195,6 +196,7 @@ | ||||
|             :index="index" | ||||
|             :item-data="item" | ||||
|             :currency="currency" | ||||
|             :estimate-items="newEstimate.items" | ||||
|             :tax-per-item="taxPerItem" | ||||
|             :discount-per-item="discountPerItem" | ||||
|             @remove="removeItem" | ||||
| @ -589,6 +591,14 @@ export default { | ||||
|     removeCustomer () { | ||||
|       this.resetSelectedCustomer() | ||||
|     }, | ||||
|     editCustomer () { | ||||
|       this.openModal({ | ||||
|         'title': this.$t('customers.edit_customer'), | ||||
|         'componentName': 'CustomerModal', | ||||
|         'id': this.selectedCustomer.id, | ||||
|         'data': this.selectedCustomer | ||||
|       }) | ||||
|     }, | ||||
|     openTemplateModal () { | ||||
|       this.openModal({ | ||||
|         'title': this.$t('general.choose_template'), | ||||
|  | ||||
| @ -24,6 +24,8 @@ | ||||
|                   :invalid="$v.item.name.$error" | ||||
|                   :invalid-description="$v.item.description.$error" | ||||
|                   :item="item" | ||||
|                   :tax-per-item="taxPerItem" | ||||
|                   :taxes="item.taxes" | ||||
|                   @search="searchVal" | ||||
|                   @select="onSelectItem" | ||||
|                   @deselect="deselectItem" | ||||
| @ -108,7 +110,7 @@ | ||||
|  | ||||
|                 <div class="remove-icon-wrapper"> | ||||
|                   <font-awesome-icon | ||||
|                     v-if="index > 0" | ||||
|                     v-if="isShowRemoveItemIcon" | ||||
|                     class="remove-icon" | ||||
|                     icon="trash-alt" | ||||
|                     @click="removeItem" | ||||
| @ -180,6 +182,10 @@ export default { | ||||
|     discountPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     estimateItems: { | ||||
|       type: Array, | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
| @ -221,6 +227,12 @@ export default { | ||||
|         return this.defaultCurrencyForInput | ||||
|       } | ||||
|     }, | ||||
|     isShowRemoveItemIcon () { | ||||
|       if (this.estimateItems.length == 1) { | ||||
|         return false | ||||
|       } | ||||
|       return true | ||||
|     }, | ||||
|     subtotal () { | ||||
|       return this.item.price * this.item.quantity | ||||
|     }, | ||||
| @ -324,6 +336,9 @@ export default { | ||||
|   created () { | ||||
|     window.hub.$on('checkItems', this.validateItem) | ||||
|     window.hub.$on('newItem', (val) => { | ||||
|       if (this.taxPerItem === 'YES') { | ||||
|         this.item.taxes = val.taxes | ||||
|       } | ||||
|       if (!this.item.item_id && this.modalActive && this.isSelected) { | ||||
|         this.onSelectItem(val) | ||||
|       } | ||||
| @ -363,7 +378,13 @@ export default { | ||||
|       this.item.price = item.price | ||||
|       this.item.item_id = item.id | ||||
|       this.item.description = item.description | ||||
|  | ||||
|       if (this.taxPerItem === 'YES' && item.taxes) { | ||||
|         let index = 0 | ||||
|         item.taxes.forEach(tax => { | ||||
|           this.updateTax({index, item: { ...tax }}) | ||||
|           index++ | ||||
|         }) | ||||
|       } | ||||
|       // if (this.item.taxes.length) { | ||||
|       //   this.item.taxes = {...item.taxes} | ||||
|       // } | ||||
|  | ||||
| @ -68,6 +68,14 @@ export default { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     }, | ||||
|     taxPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     taxes: { | ||||
|       type: Array, | ||||
|       default: null | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
| @ -129,7 +137,8 @@ export default { | ||||
|       this.$emit('onSelectItem') | ||||
|       this.openModal({ | ||||
|         'title': 'Add Item', | ||||
|         'componentName': 'ItemModal' | ||||
|         'componentName': 'ItemModal', | ||||
|         'data': {taxPerItem: this.taxPerItem, taxes: this.taxes} | ||||
|       }) | ||||
|     }, | ||||
|     deselectItem () { | ||||
|  | ||||
| @ -69,7 +69,9 @@ | ||||
|                 <font-awesome-icon icon="filter" /> | ||||
|               </base-button> | ||||
|             </a> | ||||
|  | ||||
|             <div class="filter-title"> | ||||
|               {{ $t('general.sort_by') }} | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_estimate_date" | ||||
| @ -107,7 +109,7 @@ | ||||
|               <label class="inv-label" for="filter_estimate_number">{{ $t('estimates.estimate_number') }}</label> | ||||
|             </div> | ||||
|           </v-dropdown> | ||||
|           <base-button class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData"> | ||||
|           <base-button v-tooltip.top-center="{ content: getOrderName }" class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData"> | ||||
|             <font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" /> | ||||
|             <font-awesome-icon v-else icon="sort-amount-down" /> | ||||
|           </base-button> | ||||
| @ -172,7 +174,12 @@ export default { | ||||
|       } | ||||
|       return false | ||||
|     }, | ||||
|  | ||||
|     getOrderName () { | ||||
|       if (this.getOrderBy) { | ||||
|         return this.$t('general.ascending') | ||||
|       } | ||||
|       return this.$t('general.descending') | ||||
|     }, | ||||
|     shareableLink () { | ||||
|       return `/estimates/pdf/${this.estimate.unique_hash}` | ||||
|     } | ||||
|  | ||||
| @ -83,7 +83,8 @@ | ||||
|             </div> | ||||
|             <div class="customer-content mb-1"> | ||||
|               <label class="email">{{ selectedCustomer.name }}</label> | ||||
|               <label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label> | ||||
|               <label class="action" @click="editCustomer">{{ $t('general.edit') }}</label> | ||||
|               <label class="action" @click="removeCustomer">{{ $t('general.deselect') }}</label> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -193,6 +194,7 @@ | ||||
|             :key="item.id" | ||||
|             :index="index" | ||||
|             :item-data="item" | ||||
|             :invoice-items="newInvoice.items" | ||||
|             :currency="currency" | ||||
|             :tax-per-item="taxPerItem" | ||||
|             :discount-per-item="discountPerItem" | ||||
| @ -589,6 +591,14 @@ export default { | ||||
|     removeCustomer () { | ||||
|       this.resetSelectedCustomer() | ||||
|     }, | ||||
|     editCustomer () { | ||||
|       this.openModal({ | ||||
|         'title': this.$t('customers.edit_customer'), | ||||
|         'componentName': 'CustomerModal', | ||||
|         'id': this.selectedCustomer.id, | ||||
|         'data': this.selectedCustomer | ||||
|       }) | ||||
|     }, | ||||
|     openTemplateModal () { | ||||
|       this.openModal({ | ||||
|         'title': this.$t('general.choose_template'), | ||||
|  | ||||
| @ -259,6 +259,18 @@ | ||||
|                   {{ $t('invoices.mark_as_sent') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item v-if="row.status === 'SENT' || row.status === 'VIEWED' || row.status === 'OVERDUE'"> | ||||
|                 <router-link :to="`/admin/payments/${row.id}/create`" class="dropdown-item"> | ||||
|                   <font-awesome-icon :icon="['fas', 'credit-card']" class="dropdown-item-icon"/> | ||||
|                   {{ $t('payments.record_payment') }} | ||||
|                 </router-link> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <a class="dropdown-item" href="#/" @click="onCloneInvoice(row.id)"> | ||||
|                   <font-awesome-icon icon="copy" class="dropdown-item-icon" /> | ||||
|                   {{ $t('invoices.clone_invoice') }} | ||||
|                 </a> | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removeInvoice(row.id)"> | ||||
|                   <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
| @ -378,7 +390,8 @@ export default { | ||||
|       'deleteMultipleInvoices', | ||||
|       'sendEmail', | ||||
|       'markAsSent', | ||||
|       'setSelectAllState' | ||||
|       'setSelectAllState', | ||||
|       'cloneInvoice' | ||||
|     ]), | ||||
|     ...mapActions('customer', [ | ||||
|       'fetchCustomers' | ||||
| @ -429,6 +442,27 @@ export default { | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async onCloneInvoice (id) { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$t('invoices.confirm_clone'), | ||||
|         icon: '/assets/icon/check-circle-solid.svg', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (value) => { | ||||
|         if (value) { | ||||
|           const data = { | ||||
|             id: id | ||||
|           } | ||||
|           let response = await this.cloneInvoice(data) | ||||
|           this.refreshTable() | ||||
|           if (response.data) { | ||||
|             window.toastr['success'](this.$tc('invoices.cloned_successfully')) | ||||
|             this.$router.push(`/admin/invoices/${response.data.invoice.id}/edit`) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     getStatus (val) { | ||||
|       this.filters.status = { | ||||
|         name: val, | ||||
|  | ||||
| @ -24,6 +24,8 @@ | ||||
|                   :invalid="$v.item.name.$error" | ||||
|                   :invalid-description="$v.item.description.$error" | ||||
|                   :item="item" | ||||
|                   :tax-per-item="taxPerItem" | ||||
|                   :taxes="item.taxes" | ||||
|                   @search="searchVal" | ||||
|                   @select="onSelectItem" | ||||
|                   @deselect="deselectItem" | ||||
| @ -109,7 +111,7 @@ | ||||
|  | ||||
|                 <div class="remove-icon-wrapper"> | ||||
|                   <font-awesome-icon | ||||
|                     v-if="index > 0" | ||||
|                     v-if="showRemoveItemIcon" | ||||
|                     class="remove-icon" | ||||
|                     icon="trash-alt" | ||||
|                     @click="removeItem" | ||||
| @ -181,6 +183,10 @@ export default { | ||||
|     discountPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     invoiceItems: { | ||||
|       type: Array, | ||||
|       default: null | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
| @ -222,6 +228,12 @@ export default { | ||||
|         return this.defaultCurrenctForInput | ||||
|       } | ||||
|     }, | ||||
|     showRemoveItemIcon () { | ||||
|       if (this.invoiceItems.length == 1) { | ||||
|         return false | ||||
|       } | ||||
|       return true | ||||
|     }, | ||||
|     subtotal () { | ||||
|       return this.item.price * this.item.quantity | ||||
|     }, | ||||
| @ -325,6 +337,9 @@ export default { | ||||
|   created () { | ||||
|     window.hub.$on('checkItems', this.validateItem) | ||||
|     window.hub.$on('newItem', (val) => { | ||||
|       if (this.taxPerItem === 'YES') { | ||||
|         this.item.taxes = val.taxes | ||||
|       } | ||||
|       if (!this.item.item_id && this.modalActive && this.isSelected) { | ||||
|         this.onSelectItem(val) | ||||
|       } | ||||
| @ -364,7 +379,13 @@ export default { | ||||
|       this.item.price = item.price | ||||
|       this.item.item_id = item.id | ||||
|       this.item.description = item.description | ||||
|  | ||||
|       if (this.taxPerItem === 'YES' && item.taxes) { | ||||
|         let index = 0 | ||||
|         item.taxes.forEach(tax => { | ||||
|           this.updateTax({index, item: { ...tax }}) | ||||
|           index++ | ||||
|         }) | ||||
|       } | ||||
|       // if (this.item.taxes.length) { | ||||
|       //   this.item.taxes = {...item.taxes} | ||||
|       // } | ||||
|  | ||||
| @ -66,6 +66,14 @@ export default { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     }, | ||||
|     taxPerItem: { | ||||
|       type: String, | ||||
|       default: '' | ||||
|     }, | ||||
|     taxes: { | ||||
|       type: Array, | ||||
|       default: null | ||||
|     } | ||||
|   }, | ||||
|   data () { | ||||
| @ -118,7 +126,8 @@ export default { | ||||
|       this.$emit('onSelectItem') | ||||
|       this.openModal({ | ||||
|         'title': 'Add Item', | ||||
|         'componentName': 'ItemModal' | ||||
|         'componentName': 'ItemModal', | ||||
|         'data': {taxPerItem: this.taxPerItem, taxes: this.taxes} | ||||
|       }) | ||||
|     }, | ||||
|     deselectItem () { | ||||
|  | ||||
| @ -73,7 +73,9 @@ | ||||
|                 <font-awesome-icon icon="filter" /> | ||||
|               </base-button> | ||||
|             </a> | ||||
|  | ||||
|             <div class="filter-title"> | ||||
|               {{ $t('general.sort_by') }} | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_invoice_date" | ||||
| @ -111,7 +113,7 @@ | ||||
|               <label class="inv-label" for="filter_invoice_number">{{ $t('invoices.invoice_number') }}</label> | ||||
|             </div> | ||||
|           </v-dropdown> | ||||
|           <base-button class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData"> | ||||
|           <base-button v-tooltip.top-center="{ content: getOrderName }" class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData"> | ||||
|             <font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" /> | ||||
|             <font-awesome-icon v-else icon="sort-amount-down" /> | ||||
|           </base-button> | ||||
| @ -168,13 +170,18 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|  | ||||
|     getOrderBy () { | ||||
|       if (this.searchData.orderBy === 'asc' || this.searchData.orderBy == null) { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     }, | ||||
|     getOrderName () { | ||||
|       if (this.getOrderBy) { | ||||
|         return this.$t('general.ascending') | ||||
|       } | ||||
|       return this.$t('general.descending') | ||||
|     }, | ||||
|     shareableLink () { | ||||
|       return `/invoices/pdf/${this.invoice.unique_hash}` | ||||
|     } | ||||
|  | ||||
| @ -50,11 +50,31 @@ | ||||
|                 <label>{{ $t('items.unit') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="formData.unit" | ||||
|                   :options="units" | ||||
|                   :options="itemUnits" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :placeholder="$t('items.select_a_unit')" | ||||
|                   label="name" | ||||
|                 > | ||||
|                   <div slot="afterList"> | ||||
|                     <button type="button" class="list-add-button" @click="addItemUnit"> | ||||
|                       <font-awesome-icon class="icon" icon="cart-plus" /> | ||||
|                       <label>{{ $t('settings.customization.items.add_item_unit') }}</label> | ||||
|                     </button> | ||||
|                   </div> | ||||
|                 </base-select> | ||||
|               </div> | ||||
|               <div v-if="isTaxPerItem" class="form-group"> | ||||
|                 <label>{{ $t('items.taxes') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="formData.taxes" | ||||
|                   :options="getTaxTypes" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :allow-empty="true" | ||||
|                   :multiple="true" | ||||
|                   track-by="tax_type_id" | ||||
|                   label="tax_name" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
| @ -66,7 +86,9 @@ | ||||
|                   @input="$v.formData.description.$touch()" | ||||
|                 /> | ||||
|                 <div v-if="$v.formData.description.$error"> | ||||
|                   <span v-if="!$v.formData.description.maxLength" class="text-danger">{{ $t('validation.description_maxlength') }}</span> | ||||
|                   <span v-if="!$v.formData.description.maxLength" class="text-danger"> | ||||
|                     {{ $t('validation.description_maxlength') }} | ||||
|                   </span> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div class="form-group"> | ||||
| @ -102,24 +124,17 @@ export default { | ||||
|     return { | ||||
|       isLoading: false, | ||||
|       title: 'Add Item', | ||||
|       units: [ | ||||
|         { name: 'box', value: 'box' }, | ||||
|         { name: 'cm', value: 'cm' }, | ||||
|         { name: 'dz', value: 'dz' }, | ||||
|         { name: 'ft', value: 'ft' }, | ||||
|         { name: 'g', value: 'g' }, | ||||
|         { name: 'in', value: 'in' }, | ||||
|         { name: 'kg', value: 'kg' }, | ||||
|         { name: 'km', value: 'km' }, | ||||
|         { name: 'lb', value: 'lb' }, | ||||
|         { name: 'mg', value: 'mg' }, | ||||
|         { name: 'pc', value: 'pc' } | ||||
|       ], | ||||
|       units: [], | ||||
|       taxes: [], | ||||
|       taxPerItem: '', | ||||
|       formData: { | ||||
|         name: '', | ||||
|         description: '', | ||||
|         price: '', | ||||
|         unit: null | ||||
|         unit_id: null, | ||||
|         unit: null, | ||||
|         taxes: [], | ||||
|         tax_per_item: false | ||||
|       }, | ||||
|       money: { | ||||
|         decimal: '.', | ||||
| @ -134,6 +149,9 @@ export default { | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrencyForInput' | ||||
|     ]), | ||||
|     ...mapGetters('item', [ | ||||
|       'itemUnits' | ||||
|     ]), | ||||
|     price: { | ||||
|       get: function () { | ||||
|         return this.formData.price / 100 | ||||
| @ -142,14 +160,26 @@ export default { | ||||
|         this.formData.price = newValue * 100 | ||||
|       } | ||||
|     }, | ||||
|     ...mapGetters('taxType', [ | ||||
|       'taxTypes' | ||||
|     ]), | ||||
|     isEdit () { | ||||
|       if (this.$route.name === 'items.edit') { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     }, | ||||
|     isTaxPerItem () { | ||||
|       return this.taxPerItem === 'YES' ? 1 : 0 | ||||
|     }, | ||||
|     getTaxTypes () { | ||||
|       return this.taxTypes.map(tax => { | ||||
|         return {...tax, tax_type_id: tax.id, tax_name: tax.name + ' (' + tax.percent + '%)'} | ||||
|       }) | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.setTaxPerItem() | ||||
|     if (this.isEdit) { | ||||
|       this.loadEditData() | ||||
|     } | ||||
| @ -177,10 +207,26 @@ export default { | ||||
|       'fetchItem', | ||||
|       'updateItem' | ||||
|     ]), | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     async setTaxPerItem () { | ||||
|       let res = await axios.get('/api/settings/get-setting?key=tax_per_item') | ||||
|       if (res.data && res.data.tax_per_item === 'YES') { | ||||
|         this.taxPerItem = 'YES' | ||||
|       } else { | ||||
|         this.taxPerItem = 'FALSE' | ||||
|       } | ||||
|     }, | ||||
|     async loadEditData () { | ||||
|       let response = await this.fetchItem(this.$route.params.id) | ||||
|       this.formData = response.data.item | ||||
|       this.formData.unit = this.units.find(_unit => response.data.item.unit === _unit.name) | ||||
|  | ||||
|       this.formData = {...response.data.item, unit: null} | ||||
|       this.formData.taxes = response.data.item.taxes.map(tax => { | ||||
|         return {...tax, tax_name: tax.name + ' (' + tax.percent + '%)'} | ||||
|       }) | ||||
|  | ||||
|       this.formData.unit = this.itemUnits.find(_unit => response.data.item.unit_id === _unit.id) | ||||
|       this.fractional_price = response.data.item.price | ||||
|     }, | ||||
|     async submitItem () { | ||||
| @ -189,30 +235,40 @@ export default { | ||||
|         return false | ||||
|       } | ||||
|       if (this.formData.unit) { | ||||
|         this.formData.unit = this.formData.unit.name | ||||
|         this.formData.unit_id = this.formData.unit.id | ||||
|       } | ||||
|       let response | ||||
|       if (this.isEdit) { | ||||
|         this.isLoading = true | ||||
|         let response = await this.updateItem(this.formData) | ||||
|         if (response.data) { | ||||
|           this.isLoading = false | ||||
|           window.toastr['success'](this.$tc('items.updated_message')) | ||||
|           this.$router.push('/admin/items') | ||||
|           return true | ||||
|         } | ||||
|         window.toastr['error'](response.data.error) | ||||
|         response = await this.updateItem(this.formData) | ||||
|       } else { | ||||
|         this.isLoading = true | ||||
|         let response = await this.addItem(this.formData) | ||||
|  | ||||
|         if (response.data) { | ||||
|           window.toastr['success'](this.$tc('items.created_message')) | ||||
|           this.$router.push('/admin/items') | ||||
|           this.isLoading = false | ||||
|           return true | ||||
|         let data = { | ||||
|           ...this.formData, | ||||
|           taxes: this.formData.taxes.map(tax => { | ||||
|             return { | ||||
|               tax_type_id: tax.id, | ||||
|               amount: ((this.formData.price * tax.percent) / 100), | ||||
|               percent: tax.percent, | ||||
|               name: tax.name, | ||||
|               collective_tax: 0 | ||||
|             } | ||||
|           }) | ||||
|         } | ||||
|         window.toastr['success'](response.data.success) | ||||
|         response = await this.addItem(data) | ||||
|       } | ||||
|       if (response.data) { | ||||
|         this.isLoading = false | ||||
|         window.toastr['success'](this.$tc('items.updated_message')) | ||||
|         this.$router.push('/admin/items') | ||||
|         return true | ||||
|       } | ||||
|       window.toastr['error'](response.data.error) | ||||
|     }, | ||||
|     async addItemUnit () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Item Unit', | ||||
|         'componentName': 'ItemUnit' | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -64,7 +64,7 @@ | ||||
|             <label class="form-label"> {{ $tc('items.unit') }} </label> | ||||
|             <base-select | ||||
|               v-model="filters.unit" | ||||
|               :options="units" | ||||
|               :options="itemUnits" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('items.select_a_unit')" | ||||
| @ -169,7 +169,7 @@ | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('items.unit')" | ||||
|           show="unit" | ||||
|           show="unit_name" | ||||
|         /> | ||||
|         <table-column | ||||
|           :label="$t('items.price')" | ||||
| @ -235,19 +235,6 @@ export default { | ||||
|       id: null, | ||||
|       showFilters: false, | ||||
|       sortedBy: 'created_at', | ||||
|       units: [ | ||||
|         { name: 'box', value: 'box' }, | ||||
|         { name: 'cm', value: 'cm' }, | ||||
|         { name: 'dz', value: 'dz' }, | ||||
|         { name: 'ft', value: 'ft' }, | ||||
|         { name: 'g', value: 'g' }, | ||||
|         { name: 'in', value: 'in' }, | ||||
|         { name: 'kg', value: 'kg' }, | ||||
|         { name: 'km', value: 'km' }, | ||||
|         { name: 'lb', value: 'lb' }, | ||||
|         { name: 'mg', value: 'mg' }, | ||||
|         { name: 'pc', value: 'pc' } | ||||
|       ], | ||||
|       isRequestOngoing: true, | ||||
|       filtersApplied: false, | ||||
|       filters: { | ||||
| @ -262,7 +249,8 @@ export default { | ||||
|       'items', | ||||
|       'selectedItems', | ||||
|       'totalItems', | ||||
|       'selectAllField' | ||||
|       'selectAllField', | ||||
|       'itemUnits' | ||||
|     ]), | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrency' | ||||
| @ -296,6 +284,7 @@ export default { | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   destroyed () { | ||||
|     if (this.selectAllField) { | ||||
|       this.selectAllItems() | ||||
| @ -316,7 +305,7 @@ export default { | ||||
|     async fetchData ({ page, filter, sort }) { | ||||
|       let data = { | ||||
|         search: this.filters.name !== null ? this.filters.name : '', | ||||
|         unit: this.filters.unit !== null ? this.filters.unit.name : '', | ||||
|         unit_id: this.filters.unit !== null ? this.filters.unit.id : '', | ||||
|         price: this.filters.price * 100, | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
| @ -395,7 +384,7 @@ export default { | ||||
|       }).then(async (willDelete) => { | ||||
|         if (willDelete) { | ||||
|           let res = await this.deleteMultipleItems() | ||||
|           if (res.data.success) { | ||||
|           if (res.data.success || res.data.items) { | ||||
|             window.toastr['success'](this.$tc('items.deleted_message', 2)) | ||||
|             this.$refs.table.refresh() | ||||
|           } else if (res.data.error) { | ||||
|  | ||||
| @ -109,12 +109,20 @@ | ||||
|               <div class="form-group"> | ||||
|                 <label class="form-label">{{ $t('payments.payment_mode') }}</label> | ||||
|                 <base-select | ||||
|                   v-model="formData.payment_mode" | ||||
|                   :options="getPaymentMode" | ||||
|                   v-model="formData.payment_method" | ||||
|                   :options="paymentModes" | ||||
|                   :searchable="true" | ||||
|                   :show-labels="false" | ||||
|                   :placeholder="$t('payments.select_payment_mode')" | ||||
|                 /> | ||||
|                   label="name" | ||||
|                 > | ||||
|                   <div slot="afterList"> | ||||
|                     <button type="button" class="list-add-button" @click="addPaymentMode"> | ||||
|                       <font-awesome-icon class="icon" icon="cart-plus" /> | ||||
|                       <label>{{ $t('settings.customization.payments.add_payment_mode') }}</label> | ||||
|                     </button> | ||||
|                   </div> | ||||
|                 </base-select> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="col-sm-12 "> | ||||
| @ -166,9 +174,10 @@ export default { | ||||
|         payment_number: null, | ||||
|         payment_date: null, | ||||
|         amount: 0, | ||||
|         payment_mode: null, | ||||
|         payment_method: null, | ||||
|         invoice_id: null, | ||||
|         notes: null | ||||
|         notes: null, | ||||
|         payment_method_id: null | ||||
|       }, | ||||
|       money: { | ||||
|         decimal: '.', | ||||
| @ -215,9 +224,9 @@ export default { | ||||
|     ...mapGetters('currency', [ | ||||
|       'defaultCurrencyForInput' | ||||
|     ]), | ||||
|     getPaymentMode () { | ||||
|       return ['Cash', 'Check', 'Credit Card', 'Bank Transfer'] | ||||
|     }, | ||||
|     ...mapGetters('payment', [ | ||||
|       'paymentModes' | ||||
|     ]), | ||||
|     amount: { | ||||
|       get: function () { | ||||
|         return this.formData.amount / 100 | ||||
| @ -286,14 +295,23 @@ export default { | ||||
|       'fetchCreatePayment', | ||||
|       'addPayment', | ||||
|       'updatePayment', | ||||
|       'fetchPayment' | ||||
|       'fetchEditPaymentData' | ||||
|     ]), | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     invoiceWithAmount ({ invoice_number, due_amount }) { | ||||
|       return `${invoice_number} (${this.$utils.formatGraphMoney(due_amount, this.customer.currency)})` | ||||
|     }, | ||||
|     async addPaymentMode () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Payment Mode', | ||||
|         'componentName': 'PaymentMode' | ||||
|       }) | ||||
|     }, | ||||
|     async loadData () { | ||||
|       if (this.isEdit) { | ||||
|         let response = await this.fetchPayment(this.$route.params.id) | ||||
|         let response = await this.fetchEditPaymentData(this.$route.params.id) | ||||
|         this.customerList = response.data.customers | ||||
|         this.formData = { ...response.data.payment } | ||||
|         this.customer = response.data.payment.user | ||||
| @ -301,6 +319,7 @@ export default { | ||||
|         this.formData.amount = parseFloat(response.data.payment.amount) | ||||
|         this.paymentPrefix = response.data.payment_prefix | ||||
|         this.paymentNumAttribute = response.data.nextPaymentNumber | ||||
|         this.formData.payment_method = response.data.payment.payment_method | ||||
|         if (response.data.payment.invoice !== null) { | ||||
|           this.maxPayableAmount = parseInt(response.data.payment.amount) + parseInt(response.data.payment.invoice.due_amount) | ||||
|           this.invoice = response.data.payment.invoice | ||||
| @ -344,6 +363,7 @@ export default { | ||||
|         let data = { | ||||
|           editData: { | ||||
|             ...this.formData, | ||||
|             payment_method_id: this.formData.payment_method.id, | ||||
|             payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY') | ||||
|           }, | ||||
|           id: this.$route.params.id | ||||
| @ -371,6 +391,7 @@ export default { | ||||
|       } else { | ||||
|         let data = { | ||||
|           ...this.formData, | ||||
|           payment_method_id: this.formData.payment_method.id, | ||||
|           payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY') | ||||
|         } | ||||
|         this.isLoading = true | ||||
|  | ||||
| @ -67,10 +67,11 @@ | ||||
|             <label class="form-label">{{ $t('payments.payment_mode') }}</label> | ||||
|             <base-select | ||||
|               v-model="filters.payment_mode" | ||||
|               :options="payment_mode" | ||||
|               :options="paymentModes" | ||||
|               :searchable="true" | ||||
|               :show-labels="false" | ||||
|               :placeholder="$t('payments.payment_mode')" | ||||
|               label="name" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
| @ -203,6 +204,14 @@ | ||||
|                   {{ $t('general.edit') }} | ||||
|                 </router-link> | ||||
|  | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|  | ||||
|                 <router-link :to="{path: `payments/${row.id}/view`}" class="dropdown-item"> | ||||
|                   <font-awesome-icon icon="eye" class="dropdown-item-icon" /> | ||||
|                   {{ $t('general.view') }} | ||||
|                 </router-link> | ||||
|  | ||||
|               </v-dropdown-item> | ||||
|               <v-dropdown-item> | ||||
|                 <div class="dropdown-item" @click="removePayment(row.id)"> | ||||
| @ -237,7 +246,6 @@ export default { | ||||
|       sortedBy: 'created_at', | ||||
|       filtersApplied: false, | ||||
|       isRequestOngoing: true, | ||||
|       payment_mode: ['Cash', 'Check', 'Credit Card', 'Bank Transfer'], | ||||
|       filters: { | ||||
|         customer: null, | ||||
|         payment_mode: '', | ||||
| @ -259,7 +267,8 @@ export default { | ||||
|       'selectedPayments', | ||||
|       'totalPayments', | ||||
|       'payments', | ||||
|       'selectAllField' | ||||
|       'selectAllField', | ||||
|       'paymentModes' | ||||
|     ]), | ||||
|     selectField: { | ||||
|       get: function () { | ||||
| @ -308,7 +317,7 @@ export default { | ||||
|       let data = { | ||||
|         customer_id: this.filters.customer !== null ? this.filters.customer.id : '', | ||||
|         payment_number: this.filters.payment_number, | ||||
|         payment_mode: this.filters.payment_mode ? this.filters.payment_mode : '', | ||||
|         payment_method_id: this.filters.payment_mode ? this.filters.payment_mode.id : '', | ||||
|         orderByField: sort.fieldName || 'created_at', | ||||
|         orderBy: sort.order || 'desc', | ||||
|         page | ||||
|  | ||||
							
								
								
									
										286
									
								
								resources/assets/js/views/payments/View.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								resources/assets/js/views/payments/View.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,286 @@ | ||||
| <template> | ||||
|   <div v-if="payment" class="main-content payment-view-page"> | ||||
|     <div class="page-header"> | ||||
|       <h3 class="page-title"> {{ payment.payment_number }}</h3> | ||||
|       <div class="page-actions row"> | ||||
|         <base-button | ||||
|           :loading="isSendingEmail" | ||||
|           :disabled="isSendingEmail" | ||||
|           :outline="true" | ||||
|           color="theme" | ||||
|           @click="onPaymentSend" | ||||
|         > | ||||
|           {{ $t('payments.send_payment_receipt') }} | ||||
|         </base-button> | ||||
|         <v-dropdown :close-on-select="false" align="left" class="filter-container"> | ||||
|           <a slot="activator" href="#"> | ||||
|             <base-button color="theme"> | ||||
|               <font-awesome-icon icon="ellipsis-h" /> | ||||
|             </base-button> | ||||
|           </a> | ||||
|           <v-dropdown-item> | ||||
|             <router-link :to="{path: `/admin/payments/${$route.params.id}/edit`}" class="dropdown-item"> | ||||
|               <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon"/> | ||||
|               {{ $t('general.edit') }} | ||||
|             </router-link> | ||||
|             <div class="dropdown-item" @click="removePayment($route.params.id)"> | ||||
|               <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|               {{ $t('general.delete') }} | ||||
|             </div> | ||||
|           </v-dropdown-item> | ||||
|         </v-dropdown> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="payment-sidebar"> | ||||
|       <div class="side-header"> | ||||
|         <base-input | ||||
|           v-model="searchData.searchText" | ||||
|           :placeholder="$t('general.search')" | ||||
|           input-class="inv-search" | ||||
|           icon="search" | ||||
|           type="text" | ||||
|           align-icon="right" | ||||
|           @input="onSearch" | ||||
|         /> | ||||
|         <div | ||||
|           class="btn-group ml-3" | ||||
|           role="group" | ||||
|           aria-label="First group" | ||||
|         > | ||||
|           <v-dropdown :close-on-select="false" align="left" class="filter-container"> | ||||
|             <a slot="activator" href="#"> | ||||
|               <base-button class="inv-button inv-filter-fields-btn" color="default" size="medium"> | ||||
|                 <font-awesome-icon icon="filter" /> | ||||
|               </base-button> | ||||
|             </a> | ||||
|             <div class="filter-title"> | ||||
|               {{ $t('general.sort_by') }} | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_invoice_number" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="invoice_number" | ||||
|                 @change="onSearch" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_invoice_number">{{ $t('invoices.title') }}</label> | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_payment_date" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="payment_date" | ||||
|                 @change="onSearch" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_payment_date">{{ $t('payments.date') }}</label> | ||||
|             </div> | ||||
|             <div class="filter-items"> | ||||
|               <input | ||||
|                 id="filter_payment_number" | ||||
|                 v-model="searchData.orderByField" | ||||
|                 type="radio" | ||||
|                 name="filter" | ||||
|                 class="inv-radio" | ||||
|                 value="payment_number" | ||||
|                 @change="onSearch" | ||||
|               > | ||||
|               <label class="inv-label" for="filter_payment_number">{{ $t('payments.payment_number') }}</label> | ||||
|             </div> | ||||
|           </v-dropdown> | ||||
|           <base-button v-tooltip.top-center="{ content: getOrderName }" class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData"> | ||||
|             <font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" /> | ||||
|             <font-awesome-icon v-else icon="sort-amount-down" /> | ||||
|           </base-button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <base-loader v-if="isSearching" /> | ||||
|       <div v-else class="side-content"> | ||||
|         <router-link | ||||
|           v-for="(payment,index) in payments" | ||||
|           :to="`/admin/payments/${payment.id}/view`" | ||||
|           :key="index" | ||||
|           class="side-payment" | ||||
|         > | ||||
|           <div class="left"> | ||||
|             <div class="inv-name">{{ payment.user.name }}</div> | ||||
|             <div class="inv-number">{{ payment.payment_number }}</div> | ||||
|             <div class="inv-number">{{ payment.invoice_number }}</div> | ||||
|           </div> | ||||
|           <div class="right"> | ||||
|             <div class="inv-amount" v-html="$utils.formatMoney(payment.amount, payment.user.currency)" /> | ||||
|             <div class="inv-date">{{ payment.formattedPaymentDate }}</div> | ||||
|             <!-- <div class="inv-number">{{ payment.payment_method.name }}</div> --> | ||||
|           </div> | ||||
|         </router-link> | ||||
|         <p v-if="!payments.length" class="no-result"> | ||||
|           {{ $t('payments.no_matching_invoices') }} | ||||
|         </p> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="payment-view-page-container" > | ||||
|       <iframe :src="`${shareableLink}`" class="frame-style"/> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| const _ = require('lodash') | ||||
| export default { | ||||
|   data () { | ||||
|     return { | ||||
|       id: null, | ||||
|       count: null, | ||||
|       payments: [], | ||||
|       payment: null, | ||||
|       currency: null, | ||||
|       searchData: { | ||||
|         orderBy: null, | ||||
|         orderByField: null, | ||||
|         searchText: null | ||||
|       }, | ||||
|       isRequestOnGoing: false, | ||||
|       isSearching: false, | ||||
|       isSendingEmail: false, | ||||
|       isMarkingAsSent: false | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     getOrderBy () { | ||||
|       if (this.searchData.orderBy === 'asc' || this.searchData.orderBy == null) { | ||||
|         return true | ||||
|       } | ||||
|       return false | ||||
|     }, | ||||
|     getOrderName () { | ||||
|       if (this.getOrderBy) { | ||||
|         return this.$t('general.ascending') | ||||
|       } | ||||
|       return this.$t('general.descending') | ||||
|     }, | ||||
|     shareableLink () { | ||||
|       return `/payments/pdf/${this.payment.unique_hash}` | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     $route (to, from) { | ||||
|       this.loadPayment() | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.loadPayments() | ||||
|     this.loadPayment() | ||||
|     this.onSearch = _.debounce(this.onSearch, 500) | ||||
|   }, | ||||
|   methods: { | ||||
|     // ...mapActions('invoice', [ | ||||
|     //   'fetchInvoices', | ||||
|     //   'getRecord', | ||||
|     //   'searchInvoice', | ||||
|     //   'markAsSent', | ||||
|     //   'sendEmail', | ||||
|     //   'deleteInvoice', | ||||
|     //   'fetchViewInvoice' | ||||
|     // ]), | ||||
|     ...mapActions('payment', [ | ||||
|       'fetchPayments', | ||||
|       'fetchPayment', | ||||
|       'sendEmail', | ||||
|       'deletePayment', | ||||
|       'searchPayment' | ||||
|     ]), | ||||
|     async loadPayments () { | ||||
|       let response = await this.fetchPayments() | ||||
|       if (response.data) { | ||||
|         this.payments = response.data.payments.data | ||||
|       } | ||||
|     }, | ||||
|     async loadPayment () { | ||||
|       let response = await this.fetchPayment(this.$route.params.id) | ||||
|  | ||||
|       if (response.data) { | ||||
|         this.payment = response.data.payment | ||||
|       } | ||||
|     }, | ||||
|     async onSearch () { | ||||
|       let data = '' | ||||
|       if (this.searchData.searchText !== '' && this.searchData.searchText !== null && this.searchData.searchText !== undefined) { | ||||
|         data += `search=${this.searchData.searchText}&` | ||||
|       } | ||||
|  | ||||
|       if (this.searchData.orderBy !== null && this.searchData.orderBy !== undefined) { | ||||
|         data += `orderBy=${this.searchData.orderBy}&` | ||||
|       } | ||||
|  | ||||
|       if (this.searchData.orderByField !== null && this.searchData.orderByField !== undefined) { | ||||
|         data += `orderByField=${this.searchData.orderByField}` | ||||
|       } | ||||
|       this.isSearching = true | ||||
|       let response = await this.searchPayment(data) | ||||
|       this.isSearching = false | ||||
|       if (response.data) { | ||||
|         this.payments = response.data.payments.data | ||||
|       } | ||||
|     }, | ||||
|     sortData () { | ||||
|       if (this.searchData.orderBy === 'asc') { | ||||
|         this.searchData.orderBy = 'desc' | ||||
|         this.onSearch() | ||||
|         return true | ||||
|       } | ||||
|       this.searchData.orderBy = 'asc' | ||||
|       this.onSearch() | ||||
|       return true | ||||
|     }, | ||||
|     async onPaymentSend () { | ||||
|       window.swal({ | ||||
|         title: this.$tc('general.are_you_sure'), | ||||
|         text: this.$tc('payments.confirm_send_payment'), | ||||
|         icon: '/assets/icon/paper-plane-solid.svg', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (value) => { | ||||
|         if (value) { | ||||
|           this.isSendingEmail = true | ||||
|           let response = await this.sendEmail({id: this.payment.id}) | ||||
|           this.isSendingEmail = false | ||||
|           if (response.data.success) { | ||||
|             window.toastr['success'](this.$tc('payments.send_payment_successfully')) | ||||
|             return true | ||||
|           } | ||||
|           if (response.data.error === 'user_email_does_not_exist') { | ||||
|             window.toastr['error'](this.$tc('payments.user_email_does_not_exist')) | ||||
|             return false | ||||
|           } | ||||
|           window.toastr['error'](this.$tc('payments.something_went_wrong')) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async removePayment (id) { | ||||
|       this.id = id | ||||
|       window.swal({ | ||||
|         title: 'Deleted', | ||||
|         text: 'you will not be able to recover this payment!', | ||||
|         icon: '/assets/icon/trash-solid.svg', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (value) => { | ||||
|         if (value) { | ||||
|           let request = await this.deletePayment(this.id) | ||||
|           if (request.data.success) { | ||||
|             window.toastr['success'](this.$tc('payments.deleted_message', 1)) | ||||
|             this.$router.push('/admin/payments') | ||||
|           } else if (request.data.error) { | ||||
|             window.toastr['error'](request.data.message) | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -11,12 +11,15 @@ | ||||
|         <li class="tab" @click="setActiveTab('PAYMENTS')"> | ||||
|           <a :class="['tab-link', {'a-active': activeTab === 'PAYMENTS'}]" href="#">{{ $t('settings.customization.payments.title') }}</a> | ||||
|         </li> | ||||
|         <li class="tab" @click="setActiveTab('ITEMS')"> | ||||
|           <a :class="['tab-link', {'a-active': activeTab === 'ITEMS'}]" href="#">{{ $t('settings.customization.items.title') }}</a> | ||||
|         </li> | ||||
|       </ul> | ||||
|  | ||||
|       <!-- Invoices Tab --> | ||||
|       <transition name="fade-customize"> | ||||
|         <div v-if="activeTab === 'INVOICES'" class="invoice-tab"> | ||||
|           <form action="" class="form-section" @submit.prevent="updateInvoiceSetting"> | ||||
|           <form action="" class="mt-3" @submit.prevent="updateInvoiceSetting"> | ||||
|             <div class="row"> | ||||
|               <div class="col-md-12 mb-4"> | ||||
|                 <label class="input-label">{{ $t('settings.customization.invoices.invoice_prefix') }}</label> | ||||
| @ -32,7 +35,7 @@ | ||||
|                 <span v-if="!$v.invoices.invoice_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="row mb-3"> | ||||
|             <div class="row pb-3"> | ||||
|               <div class="col-md-12"> | ||||
|                 <base-button | ||||
|                   icon="save" | ||||
| @ -43,25 +46,23 @@ | ||||
|                 </base-button> | ||||
|               </div> | ||||
|             </div> | ||||
|             <hr> | ||||
|           </form> | ||||
|           <div class="col-md-12 mt-3"> | ||||
|             <div class="page-header"> | ||||
|               <h3 class="page-title"> | ||||
|                 {{ $t('settings.customization.invoices.invoice_settings') }} | ||||
|               </h3> | ||||
|               <div class="flex-box"> | ||||
|                 <div class="left"> | ||||
|                   <base-switch | ||||
|                     v-model="invoiceAutogenerate" | ||||
|                     class="btn-switch" | ||||
|                     @change="setInvoiceSetting" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div class="right ml-15"> | ||||
|                   <p class="box-title">  {{ $t('settings.customization.invoices.autogenerate_invoice_number') }} </p> | ||||
|                   <p class="box-desc">  {{ $t('settings.customization.invoices.invoice_setting_description') }} </p> | ||||
|                 </div> | ||||
|           <hr> | ||||
|           <div class="page-header pt-3"> | ||||
|             <h3 class="page-title"> | ||||
|               {{ $t('settings.customization.invoices.invoice_settings') }} | ||||
|             </h3> | ||||
|             <div class="flex-box"> | ||||
|               <div class="left"> | ||||
|                 <base-switch | ||||
|                   v-model="invoiceAutogenerate" | ||||
|                   class="btn-switch" | ||||
|                   @change="setInvoiceSetting" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="right ml-15"> | ||||
|                 <p class="box-title">  {{ $t('settings.customization.invoices.autogenerate_invoice_number') }} </p> | ||||
|                 <p class="box-desc">  {{ $t('settings.customization.invoices.invoice_setting_description') }} </p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
| @ -71,7 +72,7 @@ | ||||
|       <!-- Estimates Tab --> | ||||
|       <transition name="fade-customize"> | ||||
|         <div v-if="activeTab === 'ESTIMATES'" class="estimate-tab"> | ||||
|           <form action="" class="form-section" @submit.prevent="updateEstimateSetting"> | ||||
|           <form action="" class="mt-3" @submit.prevent="updateEstimateSetting"> | ||||
|             <div class="row"> | ||||
|               <div class="col-md-12 mb-4"> | ||||
|                 <label class="input-label">{{ $t('settings.customization.estimates.estimate_prefix') }}</label> | ||||
| @ -87,7 +88,7 @@ | ||||
|                 <span v-if="!$v.estimates.estimate_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="row mb-3"> | ||||
|             <div class="row pb-3"> | ||||
|               <div class="col-md-12"> | ||||
|                 <base-button | ||||
|                   icon="save" | ||||
| @ -100,23 +101,21 @@ | ||||
|             </div> | ||||
|             <hr> | ||||
|           </form> | ||||
|           <div class="col-md-12 mt-3"> | ||||
|             <div class="page-header"> | ||||
|               <h3 class="page-title"> | ||||
|                 {{ $t('settings.customization.estimates.estimate_settings') }} | ||||
|               </h3> | ||||
|               <div class="flex-box"> | ||||
|                 <div class="left"> | ||||
|                   <base-switch | ||||
|                     v-model="estimateAutogenerate" | ||||
|                     class="btn-switch" | ||||
|                     @change="setEstimateSetting" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div class="right ml-15"> | ||||
|                   <p class="box-title">  {{ $t('settings.customization.estimates.autogenerate_estimate_number') }} </p> | ||||
|                   <p class="box-desc">  {{ $t('settings.customization.estimates.estimate_setting_description') }} </p> | ||||
|                 </div> | ||||
|           <div class="page-header pt-3"> | ||||
|             <h3 class="page-title"> | ||||
|               {{ $t('settings.customization.estimates.estimate_settings') }} | ||||
|             </h3> | ||||
|             <div class="flex-box"> | ||||
|               <div class="left"> | ||||
|                 <base-switch | ||||
|                   v-model="estimateAutogenerate" | ||||
|                   class="btn-switch" | ||||
|                   @change="setEstimateSetting" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="right ml-15"> | ||||
|                 <p class="box-title">  {{ $t('settings.customization.estimates.autogenerate_estimate_number') }} </p> | ||||
|                 <p class="box-desc">  {{ $t('settings.customization.estimates.estimate_setting_description') }} </p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
| @ -126,7 +125,66 @@ | ||||
|       <!-- Payments Tab --> | ||||
|       <transition name="fade-customize"> | ||||
|         <div v-if="activeTab === 'PAYMENTS'" class="payment-tab"> | ||||
|           <form action="" class="form-section" @submit.prevent="updatePaymentSetting"> | ||||
|           <div class="page-header"> | ||||
|             <div class="row"> | ||||
|               <div class="col-md-8"> | ||||
|                 <!-- <h3 class="page-title"> | ||||
|                   {{ $t('settings.customization.payments.payment_mode') }} | ||||
|                 </h3> --> | ||||
|               </div> | ||||
|               <div class="col-md-4 d-flex flex-row-reverse"> | ||||
|                 <base-button | ||||
|                   outline | ||||
|                   class="add-new-tax" | ||||
|                   color="theme" | ||||
|                   @click="addPaymentMode" | ||||
|                 > | ||||
|                   {{ $t('settings.customization.payments.add_payment_mode') }} | ||||
|                 </base-button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <table-component | ||||
|             ref="table" | ||||
|             :show-filter="false" | ||||
|             :data="paymentModes" | ||||
|             table-class="table tax-table" | ||||
|             class="mb-3" | ||||
|           > | ||||
|             <table-column | ||||
|               :sortable="true" | ||||
|               :label="$t('settings.customization.payments.payment_mode')" | ||||
|               show="name" | ||||
|             /> | ||||
|             <table-column | ||||
|               :sortable="false" | ||||
|               :filterable="false" | ||||
|               cell-class="action-dropdown" | ||||
|             > | ||||
|               <template slot-scope="row"> | ||||
|                 <span>{{ $t('settings.tax_types.action') }}</span> | ||||
|                 <v-dropdown> | ||||
|                   <a slot="activator" href="#"> | ||||
|                     <dot-icon /> | ||||
|                   </a> | ||||
|                   <v-dropdown-item> | ||||
|                     <div class="dropdown-item" @click="editPaymentMode(row)"> | ||||
|                       <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" /> | ||||
|                       {{ $t('general.edit') }} | ||||
|                     </div> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item> | ||||
|                     <div class="dropdown-item" @click="removePaymentMode(row.id)"> | ||||
|                       <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                       {{ $t('general.delete') }} | ||||
|                     </div> | ||||
|                   </v-dropdown-item> | ||||
|                 </v-dropdown> | ||||
|               </template> | ||||
|             </table-column> | ||||
|           </table-component> | ||||
|           <hr> | ||||
|           <form action="" class="pt-3" @submit.prevent="updatePaymentSetting"> | ||||
|             <div class="row"> | ||||
|               <div class="col-md-12 mb-4"> | ||||
|                 <label class="input-label">{{ $t('settings.customization.payments.payment_prefix') }}</label> | ||||
| @ -142,7 +200,7 @@ | ||||
|                 <span v-if="!$v.payments.payment_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="row mb-3"> | ||||
|             <div class="row pb-3"> | ||||
|               <div class="col-md-12"> | ||||
|                 <base-button | ||||
|                   icon="save" | ||||
| @ -155,33 +213,97 @@ | ||||
|             </div> | ||||
|           </form> | ||||
|           <hr> | ||||
|           <div class="col-md-12 mt-4"> | ||||
|             <div class="page-header"> | ||||
|               <h3 class="page-title"> | ||||
|                 {{ $t('settings.customization.payments.payment_settings') }} | ||||
|               </h3> | ||||
|               <div class="flex-box"> | ||||
|                 <div class="left"> | ||||
|                   <base-switch | ||||
|                     v-model="paymentAutogenerate" | ||||
|                     class="btn-switch" | ||||
|                     @change="setPaymentSetting" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div class="right ml-15"> | ||||
|                   <p class="box-title">  {{ $t('settings.customization.payments.autogenerate_payment_number') }} </p> | ||||
|                   <p class="box-desc">  {{ $t('settings.customization.payments.payment_setting_description') }} </p> | ||||
|                 </div> | ||||
|           <div class="page-header pt-3"> | ||||
|             <h3 class="page-title"> | ||||
|               {{ $t('settings.customization.payments.payment_settings') }} | ||||
|             </h3> | ||||
|             <div class="flex-box"> | ||||
|               <div class="left"> | ||||
|                 <base-switch | ||||
|                   v-model="paymentAutogenerate" | ||||
|                   class="btn-switch" | ||||
|                   @change="setPaymentSetting" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="right ml-15"> | ||||
|                 <p class="box-title">  {{ $t('settings.customization.payments.autogenerate_payment_number') }} </p> | ||||
|                 <p class="box-desc">  {{ $t('settings.customization.payments.payment_setting_description') }} </p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </transition> | ||||
|  | ||||
|       <!-- Items Tab --> | ||||
|       <transition name="fade-customize"> | ||||
|         <div v-if="activeTab === 'ITEMS'" class="item-tab"> | ||||
|           <div class="page-header"> | ||||
|             <div class="row"> | ||||
|               <div class="col-md-8"> | ||||
|                 <!-- <h3 class="page-title"> | ||||
|                   {{ $t('settings.customization.items.title') }} | ||||
|                 </h3> --> | ||||
|               </div> | ||||
|               <div class="col-md-4 d-flex flex-row-reverse"> | ||||
|                 <base-button | ||||
|                   outline | ||||
|                   class="add-new-tax" | ||||
|                   color="theme" | ||||
|                   @click="addItemUnit" | ||||
|                 > | ||||
|                   {{ $t('settings.customization.items.add_item_unit') }} | ||||
|                 </base-button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <table-component | ||||
|             ref="itemTable" | ||||
|             :show-filter="false" | ||||
|             :data="itemUnits" | ||||
|             table-class="table tax-table" | ||||
|             class="mb-3" | ||||
|           > | ||||
|             <table-column | ||||
|               :sortable="true" | ||||
|               :label="$t('settings.customization.items.units')" | ||||
|               show="name" | ||||
|             /> | ||||
|             <table-column | ||||
|               :sortable="false" | ||||
|               :filterable="false" | ||||
|               cell-class="action-dropdown" | ||||
|             > | ||||
|               <template slot-scope="row"> | ||||
|                 <span>{{ $t('settings.tax_types.action') }}</span> | ||||
|                 <v-dropdown> | ||||
|                   <a slot="activator" href="#"> | ||||
|                     <dot-icon /> | ||||
|                   </a> | ||||
|                   <v-dropdown-item> | ||||
|                     <div class="dropdown-item" @click="editItemUnit(row)"> | ||||
|                       <font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" /> | ||||
|                       {{ $t('general.edit') }} | ||||
|                     </div> | ||||
|                   </v-dropdown-item> | ||||
|                   <v-dropdown-item> | ||||
|                     <div class="dropdown-item" @click="removeItemUnit(row.id)"> | ||||
|                       <font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" /> | ||||
|                       {{ $t('general.delete') }} | ||||
|                     </div> | ||||
|                   </v-dropdown-item> | ||||
|                 </v-dropdown> | ||||
|               </template> | ||||
|             </table-column> | ||||
|           </table-component> | ||||
|         </div> | ||||
|       </transition> | ||||
|  | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapActions, mapGetters } from 'vuex' | ||||
| const { required, maxLength, alpha } = require('vuelidate/lib/validators') | ||||
| export default { | ||||
|   mixins: [validationMixin], | ||||
| @ -204,9 +326,20 @@ export default { | ||||
|       payments: { | ||||
|         payment_prefix: null | ||||
|       }, | ||||
|       items: { | ||||
|         units: [] | ||||
|       }, | ||||
|       currentData: null | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters('item', [ | ||||
|       'itemUnits' | ||||
|     ]), | ||||
|     ...mapGetters('payment', [ | ||||
|       'paymentModes' | ||||
|     ]) | ||||
|   }, | ||||
|   watch: { | ||||
|     activeTab () { | ||||
|       this.loadData() | ||||
| @ -239,6 +372,15 @@ export default { | ||||
|     this.loadData() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     ...mapActions('payment', [ | ||||
|       'deletePaymentMode' | ||||
|     ]), | ||||
|     ...mapActions('item', [ | ||||
|       'deleteItemUnit' | ||||
|     ]), | ||||
|     async setInvoiceSetting () { | ||||
|       let data = { | ||||
|         key: 'invoice_auto_generate', | ||||
| @ -259,6 +401,78 @@ export default { | ||||
|         window.toastr['success'](this.$t('general.setting_updated')) | ||||
|       } | ||||
|     }, | ||||
|     async addItemUnit () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Item Unit', | ||||
|         'componentName': 'ItemUnit' | ||||
|       }) | ||||
|       this.$refs.itemTable.refresh() | ||||
|     }, | ||||
|     async editItemUnit (data) { | ||||
|       this.openModal({ | ||||
|         'title': 'Edit Item Unit', | ||||
|         'componentName': 'ItemUnit', | ||||
|         'id': data.id, | ||||
|         'data': data | ||||
|       }) | ||||
|       this.$refs.itemTable.refresh() | ||||
|     }, | ||||
|     async removeItemUnit (id) { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$t('settings.customization.items.item_unit_confirm_delete'), | ||||
|         icon: '/assets/icon/trash-solid.svg', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (value) => { | ||||
|         if (value) { | ||||
|           let response = await this.deleteItemUnit(id) | ||||
|           if (response.data.success) { | ||||
|             window.toastr['success'](this.$t('settings.customization.items.deleted_message')) | ||||
|             this.id = null | ||||
|             this.$refs.itemTable.refresh() | ||||
|             return true | ||||
|           } | ||||
|           window.toastr['error'](this.$t('settings.customization.items.already_in_use')) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     async addPaymentMode () { | ||||
|       this.openModal({ | ||||
|         'title': 'Add Payment Mode', | ||||
|         'componentName': 'PaymentMode' | ||||
|       }) | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     async editPaymentMode (data) { | ||||
|       this.openModal({ | ||||
|         'title': 'Edit Payment Mode', | ||||
|         'componentName': 'PaymentMode', | ||||
|         'id': data.id, | ||||
|         'data': data | ||||
|       }) | ||||
|       this.$refs.table.refresh() | ||||
|     }, | ||||
|     removePaymentMode (id) { | ||||
|       swal({ | ||||
|         title: this.$t('general.are_you_sure'), | ||||
|         text: this.$t('settings.customization.payments.payment_mode_confirm_delete'), | ||||
|         icon: '/assets/icon/trash-solid.svg', | ||||
|         buttons: true, | ||||
|         dangerMode: true | ||||
|       }).then(async (value) => { | ||||
|         if (value) { | ||||
|           let response = await this.deletePaymentMode(id) | ||||
|           if (response.data.success) { | ||||
|             window.toastr['success'](this.$t('settings.customization.payments.deleted_message')) | ||||
|             this.id = null | ||||
|             this.$refs.table.refresh() | ||||
|             return true | ||||
|           } | ||||
|           window.toastr['error'](this.$t('settings.customization.payments.already_in_use')) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     changeToUppercase (currentTab) { | ||||
|       if (currentTab === 'INVOICES') { | ||||
|         this.invoices.invoice_prefix = this.invoices.invoice_prefix.toUpperCase() | ||||
|  | ||||
| @ -15,7 +15,19 @@ | ||||
|           :mail-drivers="mail_drivers" | ||||
|           @on-change-driver="(val) => mail_driver = mailConfigData.mail_driver = val" | ||||
|           @submit-data="saveEmailConfig" | ||||
|         /> | ||||
|         > | ||||
|           <base-button | ||||
|             :loading="loading" | ||||
|             outline | ||||
|             class="pull-right mt-4 ml-2" | ||||
|             icon="check" | ||||
|             color="theme" | ||||
|             type="button" | ||||
|             @click="openMailTestModal" | ||||
|           > | ||||
|             {{ $t('general.test_mail_conf') }} | ||||
|           </base-button> | ||||
|         </component> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| @ -27,6 +39,7 @@ import Smtp from './mailDriver/Smtp' | ||||
| import Mailgun from './mailDriver/Mailgun' | ||||
| import Ses from './mailDriver/Ses' | ||||
| import Basic from './mailDriver/Basic' | ||||
| import { mapActions } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
| @ -50,6 +63,9 @@ export default { | ||||
|     this.loadData() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('modal', [ | ||||
|       'openModal' | ||||
|     ]), | ||||
|     async loadData () { | ||||
|       this.loading = true | ||||
|  | ||||
| @ -79,6 +95,12 @@ export default { | ||||
|       } catch (e) { | ||||
|         window.toastr['error']('Something went wrong') | ||||
|       } | ||||
|     }, | ||||
|     openMailTestModal () { | ||||
|       this.openModal({ | ||||
|         'title': 'Test Mail Configuration', | ||||
|         'componentName': 'MailTestModal' | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -73,15 +73,18 @@ | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button | ||||
|       :loading="loading" | ||||
|       class="pull-right mt-4" | ||||
|       icon="save" | ||||
|       color="theme" | ||||
|       type="submit" | ||||
|     > | ||||
|       {{ $t('general.save') }} | ||||
|     </base-button> | ||||
|     <div class="d-flex"> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right mt-4" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('general.save') }} | ||||
|       </base-button> | ||||
|       <slot/> | ||||
|     </div> | ||||
|   </form> | ||||
| </template> | ||||
| <script> | ||||
|  | ||||
| @ -167,15 +167,18 @@ | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button | ||||
|       :loading="loading" | ||||
|       class="pull-right mt-4" | ||||
|       icon="save" | ||||
|       color="theme" | ||||
|       type="submit" | ||||
|     > | ||||
|       {{ $t('general.save') }} | ||||
|     </base-button> | ||||
|     <div class="d-flex"> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right mt-4" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('general.save') }} | ||||
|       </base-button> | ||||
|       <slot/> | ||||
|     </div> | ||||
|   </form> | ||||
| </template> | ||||
| <script> | ||||
|  | ||||
| @ -146,15 +146,18 @@ | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button | ||||
|       :loading="loading" | ||||
|       class="pull-right mt-4" | ||||
|       icon="save" | ||||
|       color="theme" | ||||
|       type="submit" | ||||
|     > | ||||
|       {{ $t('general.save') }} | ||||
|     </base-button> | ||||
|     <div class="d-flex"> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right mt-4" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('general.save') }} | ||||
|       </base-button> | ||||
|       <slot/> | ||||
|     </div> | ||||
|   </form> | ||||
| </template> | ||||
| <script> | ||||
|  | ||||
| @ -146,15 +146,18 @@ | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <base-button | ||||
|       :loading="loading" | ||||
|       class="pull-right mt-4" | ||||
|       icon="save" | ||||
|       color="theme" | ||||
|       type="submit" | ||||
|     > | ||||
|       {{ $t('general.save') }} | ||||
|     </base-button> | ||||
|     <div class="d-flex"> | ||||
|       <base-button | ||||
|         :loading="loading" | ||||
|         class="pull-right mt-4" | ||||
|         icon="save" | ||||
|         color="theme" | ||||
|         type="submit" | ||||
|       > | ||||
|         {{ $t('general.save') }} | ||||
|       </base-button> | ||||
|       <slot/> | ||||
|     </div> | ||||
|   </form> | ||||
| </template> | ||||
| <script> | ||||
|  | ||||
| @ -54,7 +54,6 @@ | ||||
|             :placeholder="$t('general.select_country')" | ||||
|             track-by="id" | ||||
|             label="name" | ||||
|             @input="fetchState()" | ||||
|           /> | ||||
|           <div v-if="$v.companyData.country_id.$error"> | ||||
|             <span v-if="!$v.companyData.country_id.required" class="text-danger">{{ $tc('validation.required') }}</span> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user