Merge branch 'add-customization' into 'master'

Add customization

See merge request mohit.panjvani/crater-web!134
This commit is contained in:
Mohit Panjwani
2019-12-12 10:05:16 +00:00
23 changed files with 1000 additions and 92 deletions

View File

@ -127,14 +127,15 @@
<div class="row mt-4">
<div class="col collapse-input">
<label>{{ $t('estimates.estimate_number') }}<span class="text-danger"> * </span></label>
<base-input
:invalid="$v.newEstimate.estimate_number.$error"
:read-only="true"
v-model="newEstimate.estimate_number"
<base-prefix-input
v-model="estimateNumAttribute"
:invalid="$v.estimateNumAttribute.$error"
:prefix="estimatePrefix"
icon="hashtag"
@input="$v.newEstimate.estimate_number.$touch()"
@input="$v.estimateNumAttribute.$touch()"
/>
<span v-show="$v.newEstimate.estimate_number.$error && !$v.newEstimate.estimate_number.required" class="text-danger mt-1"> {{ $tc('estimates.errors.required') }} </span>
<span v-show="$v.estimateNumAttribute.$error && !$v.estimateNumAttribute.required" class="text-danger mt-1"> {{ $tc('estimates.errors.required') }} </span>
<span v-show="!$v.estimateNumAttribute.numeric" class="text-danger mt-1"> {{ $tc('validation.numbers_only') }} </span>
</div>
<div class="col collapse-input">
<label>{{ $t('estimates.ref_number') }}</label>
@ -320,7 +321,7 @@ import { validationMixin } from 'vuelidate'
import Guid from 'guid'
import TaxStub from '../../stub/tax'
import Tax from './EstimateTax'
const { required, between, maxLength } = require('vuelidate/lib/validators')
const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default {
components: {
@ -361,7 +362,9 @@ export default {
discountPerItem: null,
initLoading: false,
isLoading: false,
maxDiscount: 0
maxDiscount: 0,
estimatePrefix: null,
estimateNumAttribute: null
}
},
validations () {
@ -373,9 +376,6 @@ export default {
expiry_date: {
required
},
estimate_number: {
required
},
discount_val: {
between: between(0, this.subtotal)
},
@ -388,6 +388,10 @@ export default {
},
selectedCustomer: {
required
},
estimateNumAttribute: {
required,
numeric
}
}
},
@ -559,6 +563,8 @@ export default {
this.taxPerItem = response.data.tax_per_item
this.selectedCurrency = this.defaultCurrency
this.estimateTemplates = response.data.estimateTemplates
this.estimatePrefix = response.data.estimate_prefix
this.estimateNumAttribute = response.data.nextEstimateNumber
}
this.initLoading = false
return
@ -574,8 +580,9 @@ export default {
let today = new Date()
this.newEstimate.estimate_date = moment(today).toString()
this.newEstimate.expiry_date = moment(today).add(7, 'days').toString()
this.newEstimate.estimate_number = response.data.nextEstimateNumber
this.itemList = response.data.items
this.estimatePrefix = response.data.estimate_prefix
this.estimateNumAttribute = response.data.nextEstimateNumber
}
this.initLoading = false
},
@ -604,6 +611,7 @@ export default {
}
this.isLoading = true
this.newEstimate.estimate_number = this.estimatePrefix + '-' + this.estimateNumAttribute
let data = {
...this.newEstimate,
@ -637,7 +645,11 @@ export default {
this.isLoading = false
}).catch((err) => {
this.isLoading = false
console.log(err)
if (err.response.data.errors.estimate_number) {
window.toastr['error'](err.response.data.errors.estimate_number)
return true
}
window.toastr['error'](err.response.data.message)
})
},
submitUpdate (data) {
@ -650,7 +662,11 @@ export default {
this.isLoading = false
}).catch((err) => {
this.isLoading = false
console.log(err)
if (err.response.data.errors.estimate_number) {
window.toastr['error'](err.response.data.errors.estimate_number)
return true
}
window.toastr['error'](err.response.data.message)
})
},
checkItemsData (index, isValid) {

View File

@ -127,14 +127,15 @@
<div class="row mt-4">
<div class="col collapse-input">
<label>{{ $t('invoices.invoice_number') }}<span class="text-danger"> * </span></label>
<base-input
:invalid="$v.newInvoice.invoice_number.$error"
:read-only="true"
v-model="newInvoice.invoice_number"
<base-prefix-input
v-model="invoiceNumAttribute"
:invalid="$v.invoiceNumAttribute.$error"
:prefix="invoicePrefix"
icon="hashtag"
@input="$v.newInvoice.invoice_number.$touch()"
@input="$v.invoiceNumAttribute.$touch()"
/>
<span v-show="$v.newInvoice.invoice_number.$error && !$v.newInvoice.invoice_number.required" class="text-danger mt-1"> {{ $tc('validation.required') }} </span>
<span v-show="$v.invoiceNumAttribute.$error && !$v.invoiceNumAttribute.required" class="text-danger mt-1"> {{ $tc('validation.required') }} </span>
<span v-show="!$v.invoiceNumAttribute.numeric" class="text-danger mt-1"> {{ $tc('validation.numbers_only') }} </span>
</div>
<div class="col collapse-input">
<label>{{ $t('invoices.ref_number') }}</label>
@ -320,7 +321,7 @@ import { validationMixin } from 'vuelidate'
import Guid from 'guid'
import TaxStub from '../../stub/tax'
import Tax from './InvoiceTax'
const { required, between, maxLength } = require('vuelidate/lib/validators')
const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default {
components: {
@ -361,7 +362,9 @@ export default {
discountPerItem: null,
initLoading: false,
isLoading: false,
maxDiscount: 0
maxDiscount: 0,
invoicePrefix: null,
invoiceNumAttribute: null
}
},
validations () {
@ -373,9 +376,6 @@ export default {
due_date: {
required
},
invoice_number: {
required
},
discount_val: {
between: between(0, this.subtotal)
},
@ -388,6 +388,10 @@ export default {
},
selectedCustomer: {
required
},
invoiceNumAttribute: {
required,
numeric
}
}
},
@ -559,6 +563,8 @@ export default {
this.taxPerItem = response.data.tax_per_item
this.selectedCurrency = this.defaultCurrency
this.invoiceTemplates = response.data.invoiceTemplates
this.invoicePrefix = response.data.invoice_prefix
this.invoiceNumAttribute = response.data.nextInvoiceNumber
}
this.initLoading = false
return
@ -574,8 +580,9 @@ export default {
let today = new Date()
this.newInvoice.invoice_date = moment(today).toString()
this.newInvoice.due_date = moment(today).add(7, 'days').toString()
this.newInvoice.invoice_number = response.data.nextInvoiceNumber
this.itemList = response.data.items
this.invoicePrefix = response.data.invoice_prefix
this.invoiceNumAttribute = response.data.nextInvoiceNumber
}
this.initLoading = false
},
@ -604,6 +611,7 @@ export default {
}
this.isLoading = true
this.newInvoice.invoice_number = this.invoicePrefix + '-' + this.invoiceNumAttribute
let data = {
...this.newInvoice,
@ -637,6 +645,10 @@ export default {
this.isLoading = false
}).catch((err) => {
this.isLoading = false
if (err.response.data.errors.invoice_number) {
window.toastr['error'](err.response.data.errors.invoice_number)
return true
}
console.log(err)
})
},
@ -653,6 +665,10 @@ export default {
}
}).catch((err) => {
this.isLoading = false
if (err.response.data.errors.invoice_number) {
window.toastr['error'](err.response.data.errors.invoice_number)
return true
}
console.log(err)
})
},

View File

@ -40,16 +40,15 @@
<div class="col-sm-6">
<div class="form-group">
<label class="form-label">{{ $t('payments.payment_number') }}</label><span class="text-danger"> *</span>
<base-input
:invalid="$v.formData.payment_number.$error"
v-model.trim="formData.payment_number"
read-only
type="text"
name="email"
@input="$v.formData.payment_number.$touch()"
<base-prefix-input
:invalid="$v.paymentNumAttribute.$error"
v-model.trim="paymentNumAttribute"
:prefix="paymentPrefix"
@input="$v.paymentNumAttribute.$touch()"
/>
<div v-if="$v.formData.payment_number.$error">
<span v-if="!$v.formData.payment_number.required" class="text-danger">{{ $tc('validation.required') }}</span>
<div v-if="$v.paymentNumAttribute.$error">
<span v-if="!$v.paymentNumAttribute.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.paymentNumAttribute.numeric" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
</div>
</div>
</div>
@ -155,7 +154,7 @@ import { mapActions, mapGetters } from 'vuex'
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
import moment from 'moment'
const { required, between, maxLength } = require('vuelidate/lib/validators')
const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default {
components: { MultiSelect },
@ -184,7 +183,9 @@ export default {
invoiceList: [],
isLoading: false,
maxPayableAmount: Number.MAX_SAFE_INTEGER,
isSettingInitialData: true
isSettingInitialData: true,
paymentNumAttribute: null,
paymentPrefix: ''
}
},
validations () {
@ -193,9 +194,6 @@ export default {
required
},
formData: {
payment_number: {
required
},
payment_date: {
required
},
@ -206,6 +204,10 @@ export default {
notes: {
maxLength: maxLength(255)
}
},
paymentNumAttribute: {
required,
numeric
}
}
},
@ -297,6 +299,8 @@ export default {
this.customer = response.data.payment.user
this.formData.payment_date = moment(response.data.payment.payment_date, 'YYYY-MM-DD').toString()
this.formData.amount = parseFloat(response.data.payment.amount)
this.paymentPrefix = response.data.payment_prefix
this.paymentNumAttribute = response.data.nextPaymentNumber
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
@ -305,7 +309,8 @@ export default {
} else {
let response = await this.fetchCreatePayment()
this.customerList = response.data.customers
this.formData.payment_number = response.data.nextPaymentNumber
this.paymentNumAttribute = response.data.nextPaymentNumber
this.paymentPrefix = response.data.payment_prefix
this.formData.payment_date = moment(new Date()).toString()
}
return true
@ -332,6 +337,9 @@ export default {
if (this.$v.$invalid) {
return true
}
this.formData.payment_number = this.paymentPrefix + '-' + this.paymentNumAttribute
if (this.isEdit) {
let data = {
editData: {
@ -340,35 +348,53 @@ export default {
},
id: this.$route.params.id
}
let response = await this.updatePayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.updated_message'))
this.$router.push('/admin/payments')
return true
try {
let response = await this.updatePayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.updated_message'))
this.$router.push('/admin/payments')
return true
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} catch (err) {
this.isLoading = false
if (err.response.data.errors.payment_number) {
window.toastr['error'](err.response.data.errors.payment_number)
return true
}
window.toastr['error'](err.response.data.message)
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} else {
let data = {
...this.formData,
payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY')
}
this.isLoading = true
let response = await this.addPayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.created_message'))
this.$router.push('/admin/payments')
this.isLoading = true
return true
try {
let response = await this.addPayment(data)
if (response.data.success) {
window.toastr['success'](this.$t('payments.created_message'))
this.$router.push('/admin/payments')
this.isLoading = true
return true
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} catch (err) {
this.isLoading = false
if (err.response.data.errors.payment_number) {
window.toastr['error'](err.response.data.errors.payment_number)
return true
}
window.toastr['error'](err.response.data.message)
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
}
}
}

View File

@ -0,0 +1,385 @@
<template>
<div class="setting-main-container customization">
<div class="card setting-card">
<ul class="tabs">
<li class="tab" @click="setActiveTab('INVOICES')">
<a :class="['tab-link', {'a-active': activeTab === 'INVOICES'}]" href="#">{{ $t('settings.customization.invoices.title') }}</a>
</li>
<li class="tab" @click="setActiveTab('ESTIMATES')">
<a :class="['tab-link', {'a-active': activeTab === 'ESTIMATES'}]" href="#">{{ $t('settings.customization.estimates.title') }}</a>
</li>
<li class="tab" @click="setActiveTab('PAYMENTS')">
<a :class="['tab-link', {'a-active': activeTab === 'PAYMENTS'}]" href="#">{{ $t('settings.customization.payments.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">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.invoices.invoice_prefix') }}</label>
<base-input
v-model="invoices.invoice_prefix"
:invalid="$v.invoices.invoice_prefix.$error"
class="prefix-input"
@input="$v.invoices.invoice_prefix.$touch()"
@keyup="changeToUppercase('INVOICES')"
/>
<span v-show="!$v.invoices.invoice_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.invoices.invoice_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<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="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</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>
</div>
</div>
</div>
</div>
</transition>
<!-- Estimates Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'ESTIMATES'" class="estimate-tab">
<form action="" class="form-section" @submit.prevent="updateEstimateSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.estimates.estimate_prefix') }}</label>
<base-input
v-model="estimates.estimate_prefix"
:invalid="$v.estimates.estimate_prefix.$error"
class="prefix-input"
@input="$v.estimates.estimate_prefix.$touch()"
@keyup="changeToUppercase('ESTIMATES')"
/>
<span v-show="!$v.estimates.estimate_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.estimates.estimate_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<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="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</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>
</div>
</div>
</div>
</transition>
<!-- Payments Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'PAYMENTS'" class="payment-tab">
<form action="" class="form-section" @submit.prevent="updatePaymentSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.payments.payment_prefix') }}</label>
<base-input
v-model="payments.payment_prefix"
:invalid="$v.payments.payment_prefix.$error"
class="prefix-input"
@input="$v.payments.payment_prefix.$touch()"
@keyup="changeToUppercase('PAYMENTS')"
/>
<span v-show="!$v.payments.payment_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.payments.payment_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<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="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</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>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
import { validationMixin } from 'vuelidate'
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
data () {
return {
activeTab: 'INVOICES',
invoiceAutogenerate: false,
estimateAutogenerate: false,
paymentAutogenerate: false,
invoices: {
invoice_prefix: null,
invoice_notes: null,
invoice_terms_and_conditions: null
},
estimates: {
estimate_prefix: null,
estimate_notes: null,
estimate_terms_and_conditions: null
},
payments: {
payment_prefix: null
},
currentData: null
}
},
watch: {
activeTab () {
this.loadData()
}
},
validations: {
invoices: {
invoice_prefix: {
required,
maxLength: maxLength(5),
alpha
}
},
estimates: {
estimate_prefix: {
required,
maxLength: maxLength(5),
alpha
}
},
payments: {
payment_prefix: {
required,
maxLength: maxLength(5),
alpha
}
}
},
created () {
this.loadData()
},
methods: {
async setInvoiceSetting () {
let data = {
key: 'invoice_auto_generate',
value: this.invoiceAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
async setEstimateSetting () {
let data = {
key: 'estimate_auto_generate',
value: this.estimateAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
changeToUppercase (currentTab) {
if (currentTab === 'INVOICES') {
this.invoices.invoice_prefix = this.invoices.invoice_prefix.toUpperCase()
return true
}
if (currentTab === 'ESTIMATES') {
this.estimates.estimate_prefix = this.estimates.estimate_prefix.toUpperCase()
return true
}
if (currentTab === 'PAYMENTS') {
this.payments.payment_prefix = this.payments.payment_prefix.toUpperCase()
return true
}
},
async setPaymentSetting () {
let data = {
key: 'payment_auto_generate',
value: this.paymentAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
async loadData () {
let res = await window.axios.get('/api/settings/get-customize-setting')
if (res.data) {
this.invoices.invoice_prefix = res.data.invoice_prefix
this.invoices.invoice_notes = res.data.invoice_notes
this.invoices.invoice_terms_and_conditions = res.data.invoice_terms_and_conditions
this.estimates.estimate_prefix = res.data.estimate_prefix
this.estimates.estimate_notes = res.data.estimate_notes
this.estimates.estimate_terms_and_conditions = res.data.estimate_terms_and_conditions
this.payments.payment_prefix = res.data.payment_prefix
if (res.data.invoice_auto_generate === 'YES') {
this.invoiceAutogenerate = true
} else {
this.invoiceAutogenerate = false
}
if (res.data.estimate_auto_generate === 'YES') {
this.estimateAutogenerate = true
} else {
this.estimateAutogenerate = false
}
if (res.data.payment_auto_generate === 'YES') {
this.paymentAutogenerate = true
} else {
this.paymentAutogenerate = false
}
}
},
async updateInvoiceSetting () {
this.$v.invoices.$touch()
if (this.$v.invoices.$invalid) {
return false
}
let data = {type: 'INVOICES', ...this.invoices}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.invoices.invoice_setting_updated'))
}
},
async updateEstimateSetting () {
this.$v.estimates.$touch()
if (this.$v.estimates.$invalid) {
return false
}
let data = {type: 'ESTIMATES', ...this.estimates}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.estimates.estimate_setting_updated'))
}
},
async updatePaymentSetting () {
this.$v.payments.$touch()
if (this.$v.payments.$invalid) {
return false
}
let data = {type: 'PAYMENTS', ...this.payments}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.payments.payment_setting_updated'))
}
},
async updateSetting (data) {
let res = await window.axios.put('/api/settings/update-customize-setting', data)
if (res.data.success) {
return true
}
return false
},
setActiveTab (val) {
this.activeTab = val
}
}
}
</script>
<style>
.fade-customize-enter-active {
transition: opacity 0.9s;
}
.fade-customize-leave-active {
transition: opacity 0s;
}
.fade-customize-enter, .fade-customize-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View File

@ -45,6 +45,12 @@ export default {
icon: 'building',
iconType: 'far'
},
{
link: '/admin/settings/customization',
title: 'settings.menu_title.customization',
icon: 'edit',
iconType: 'fa'
},
{
link: '/admin/settings/preferences',
title: 'settings.menu_title.preferences',