mirror of
https://github.com/crater-invoice/crater.git
synced 2025-12-16 18:32:55 -05: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
parent
56a955befd
commit
4c33a5d88c
@@ -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>
|
||||
Reference in New Issue
Block a user