mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-28 04:01:10 -04:00
v5.0.0 update
This commit is contained in:
517
resources/scripts/views/payments/Create.vue
Normal file
517
resources/scripts/views/payments/Create.vue
Normal file
@ -0,0 +1,517 @@
|
||||
<template>
|
||||
<PaymentModeModal />
|
||||
|
||||
<BasePage class="relative payment-create">
|
||||
<form action="" @submit.prevent="submitPaymentData">
|
||||
<BasePageHeader :title="pageTitle" class="mb-5">
|
||||
<BaseBreadcrumb>
|
||||
<BaseBreadcrumbItem
|
||||
:title="$t('general.home')"
|
||||
to="/admin/dashboard"
|
||||
/>
|
||||
<BaseBreadcrumbItem
|
||||
:title="$tc('payments.payment', 2)"
|
||||
to="/admin/payments"
|
||||
/>
|
||||
<BaseBreadcrumbItem :title="pageTitle" to="#" active />
|
||||
</BaseBreadcrumb>
|
||||
|
||||
<template #actions>
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
class="hidden sm:flex"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!isSaving"
|
||||
name="SaveIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
</template>
|
||||
{{
|
||||
isEdit
|
||||
? $t('payments.update_payment')
|
||||
: $t('payments.save_payment')
|
||||
}}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</BasePageHeader>
|
||||
|
||||
<BaseCard>
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t('payments.date')"
|
||||
:content-loading="isLoadingContent"
|
||||
required
|
||||
:error="
|
||||
v$.currentPayment.payment_date.$error &&
|
||||
v$.currentPayment.payment_date.$errors[0].$message
|
||||
"
|
||||
>
|
||||
<BaseDatePicker
|
||||
v-model="paymentStore.currentPayment.payment_date"
|
||||
:content-loading="isLoadingContent"
|
||||
:calendar-button="true"
|
||||
calendar-button-icon="calendar"
|
||||
:invalid="v$.currentPayment.payment_date.$error"
|
||||
@update:modelValue="v$.currentPayment.payment_date.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('payments.payment_number')"
|
||||
:content-loading="isLoadingContent"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="paymentStore.currentPayment.payment_number"
|
||||
:content-loading="isLoadingContent"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('payments.customer')"
|
||||
:error="
|
||||
v$.currentPayment.customer_id.$error &&
|
||||
v$.currentPayment.customer_id.$errors[0].$message
|
||||
"
|
||||
:content-loading="isLoadingContent"
|
||||
required
|
||||
>
|
||||
<BaseCustomerSelectInput
|
||||
v-model="paymentStore.currentPayment.customer_id"
|
||||
:content-loading="isLoadingContent"
|
||||
:invalid="v$.currentPayment.customer_id.$error"
|
||||
:placeholder="$t('customers.select_a_customer')"
|
||||
:fetch-all="isEdit"
|
||||
show-action
|
||||
@update:modelValue="
|
||||
selectNewCustomer(paymentStore.currentPayment.customer_id)
|
||||
"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:content-loading="isLoadingContent"
|
||||
:label="$t('payments.invoice')"
|
||||
:help-text="
|
||||
selectedInvoice
|
||||
? `Due Amount: ${
|
||||
paymentStore.currentPayment.maxPayableAmount / 100
|
||||
}`
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="paymentStore.currentPayment.invoice_id"
|
||||
:content-loading="isLoadingContent"
|
||||
value-prop="id"
|
||||
track-by="invoice_number"
|
||||
label="invoice_number"
|
||||
:options="invoiceList"
|
||||
:loading="isLoadingInvoices"
|
||||
:placeholder="$t('invoices.select_invoice')"
|
||||
@select="onSelectInvoice"
|
||||
>
|
||||
<template #singlelabel="{ value }">
|
||||
<div class="absolute left-3.5">
|
||||
{{ value.invoice_number }} ({{
|
||||
utils.formatMoney(value.total, value.customer.currency)
|
||||
}})
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
{{ option.invoice_number }} ({{
|
||||
utils.formatMoney(option.total, option.customer.currency)
|
||||
}})
|
||||
</template>
|
||||
</BaseMultiselect>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('payments.amount')"
|
||||
:content-loading="isLoadingContent"
|
||||
:error="
|
||||
v$.currentPayment.amount.$error &&
|
||||
v$.currentPayment.amount.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<div class="relative w-full">
|
||||
<BaseMoney
|
||||
:key="paymentStore.currentPayment.currency"
|
||||
v-model="amount"
|
||||
:currency="paymentStore.currentPayment.currency"
|
||||
:content-loading="isLoadingContent"
|
||||
:invalid="v$.currentPayment.amount.$error"
|
||||
@update:modelValue="v$.currentPayment.amount.$touch()"
|
||||
/>
|
||||
</div>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:content-loading="isLoadingContent"
|
||||
:label="$t('payments.payment_mode')"
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="paymentStore.currentPayment.payment_method_id"
|
||||
:content-loading="isLoadingContent"
|
||||
label="name"
|
||||
value-prop="id"
|
||||
track-by="name"
|
||||
:options="paymentStore.paymentModes"
|
||||
:placeholder="$t('payments.select_payment_mode')"
|
||||
searchable
|
||||
>
|
||||
<template #action>
|
||||
<BaseSelectAction @click="addPaymentMode">
|
||||
<BaseIcon
|
||||
name="PlusIcon"
|
||||
class="h-4 mr-2 -ml-2 text-center text-primary-400"
|
||||
/>
|
||||
{{ $t('settings.payment_modes.add_payment_mode') }}
|
||||
</BaseSelectAction>
|
||||
</template>
|
||||
</BaseMultiselect>
|
||||
</BaseInputGroup>
|
||||
|
||||
<ExchangeRateConverter
|
||||
:store="paymentStore"
|
||||
store-prop="currentPayment"
|
||||
:v="v$.currentPayment"
|
||||
:is-loading="isLoadingContent"
|
||||
:is-edit="isEdit"
|
||||
:customer-currency="paymentStore.currentPayment.currency_id"
|
||||
/>
|
||||
</BaseInputGrid>
|
||||
|
||||
<!-- Payment Custom Fields -->
|
||||
<PaymentCustomFields
|
||||
type="Payment"
|
||||
:is-edit="isEdit"
|
||||
:is-loading="isLoadingContent"
|
||||
:store="paymentStore"
|
||||
store-prop="currentPayment"
|
||||
:custom-field-scope="paymentValidationScope"
|
||||
class="mt-6"
|
||||
/>
|
||||
|
||||
<!-- Payment Note field -->
|
||||
<div class="relative mt-6">
|
||||
<div
|
||||
class="
|
||||
z-20
|
||||
float-right
|
||||
text-sm
|
||||
font-semibold
|
||||
leading-5
|
||||
text-primary-400
|
||||
"
|
||||
>
|
||||
<SelectNotePopup type="Payment" @select="onSelectNote" />
|
||||
</div>
|
||||
|
||||
<label class="mb-4 text-sm font-medium text-primary-800">
|
||||
{{ $t('estimates.notes') }}
|
||||
</label>
|
||||
|
||||
<BaseCustomInput
|
||||
v-model="paymentStore.currentPayment.notes"
|
||||
:content-loading="isLoadingContent"
|
||||
:fields="PaymentFields"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:content-loading="isLoadingContent"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
class="flex justify-center w-full mt-4 sm:hidden md:hidden"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!isSaving"
|
||||
name="SaveIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
</template>
|
||||
{{
|
||||
isEdit ? $t('payments.update_payment') : $t('payments.save_payment')
|
||||
}}
|
||||
</BaseButton>
|
||||
</BaseCard>
|
||||
</form>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ExchangeRateConverter from '@/scripts/components/estimate-invoice-common/ExchangeRateConverter.vue'
|
||||
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
computed,
|
||||
inject,
|
||||
watchEffect,
|
||||
onBeforeUnmount,
|
||||
} from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
required,
|
||||
numeric,
|
||||
helpers,
|
||||
between,
|
||||
requiredIf,
|
||||
decimal,
|
||||
} from '@vuelidate/validators'
|
||||
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useCustomerStore } from '@/scripts/stores/customer'
|
||||
import { usePaymentStore } from '@/scripts/stores/payment'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { useCustomFieldStore } from '@/scripts/stores/custom-field'
|
||||
import { useCompanyStore } from '@/scripts/stores/company'
|
||||
import { useModalStore } from '@/scripts/stores/modal'
|
||||
import { useInvoiceStore } from '@/scripts/stores/invoice'
|
||||
import { useGlobalStore } from '@/scripts/stores/global'
|
||||
|
||||
import SelectNotePopup from '@/scripts/components/SelectNotePopup.vue'
|
||||
import PaymentCustomFields from '@/scripts/components/custom-fields/CreateCustomFields.vue'
|
||||
import PaymentModeModal from '@/scripts/components/modal-components/PaymentModeModal.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const paymentStore = usePaymentStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
const customerStore = useCustomerStore()
|
||||
const customFieldStore = useCustomFieldStore()
|
||||
const companyStore = useCompanyStore()
|
||||
const modalStore = useModalStore()
|
||||
const invoiceStore = useInvoiceStore()
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
const utils = inject('utils')
|
||||
const { t } = useI18n()
|
||||
|
||||
let isSaving = ref(false)
|
||||
let isLoadingInvoices = ref(false)
|
||||
let invoiceList = ref([])
|
||||
const selectedInvoice = ref(null)
|
||||
|
||||
const paymentValidationScope = 'newEstimate'
|
||||
|
||||
const PaymentFields = reactive([
|
||||
'customer',
|
||||
'company',
|
||||
'customerCustom',
|
||||
'payment',
|
||||
'paymentCustom',
|
||||
])
|
||||
|
||||
const amount = computed({
|
||||
get: () => paymentStore.currentPayment.amount / 100,
|
||||
set: (value) => {
|
||||
paymentStore.currentPayment.amount = Math.round(value * 100)
|
||||
},
|
||||
})
|
||||
|
||||
const isLoadingContent = computed(() => paymentStore.isFetchingInitialData)
|
||||
|
||||
const isEdit = computed(() => route.name === 'payments.edit')
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
if (isEdit.value) {
|
||||
return t('payments.edit_payment')
|
||||
}
|
||||
return t('payments.new_payment')
|
||||
})
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
currentPayment: {
|
||||
customer_id: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
payment_date: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
amount: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
between: helpers.withMessage(
|
||||
t('validation.payment_greater_than_due_amount'),
|
||||
between(1, paymentStore.currentPayment.maxPayableAmount)
|
||||
),
|
||||
},
|
||||
exchange_rate: {
|
||||
required: requiredIf(function () {
|
||||
helpers.withMessage(t('validation.required'), required)
|
||||
return paymentStore.showExchangeRate
|
||||
}),
|
||||
decimal: helpers.withMessage(
|
||||
t('validation.valid_exchange_rate'),
|
||||
decimal
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(rules, paymentStore, {
|
||||
$scope: paymentValidationScope,
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
// fetch customer and its invoices
|
||||
paymentStore.currentPayment.customer_id
|
||||
? onCustomerChange(paymentStore.currentPayment.customer_id)
|
||||
: ''
|
||||
if (route.query.customer) {
|
||||
paymentStore.currentPayment.customer_id = route.query.customer
|
||||
}
|
||||
})
|
||||
|
||||
// Reset State on Create
|
||||
paymentStore.resetCurrentPayment()
|
||||
|
||||
if (route.query.customer) {
|
||||
paymentStore.currentPayment.customer_id = route.query.customer
|
||||
}
|
||||
|
||||
paymentStore.fetchPaymentInitialData(isEdit.value)
|
||||
|
||||
if (route.params.id && !isEdit.value) {
|
||||
setInvoiceFromUrl()
|
||||
}
|
||||
|
||||
async function addPaymentMode() {
|
||||
modalStore.openModal({
|
||||
title: t('settings.payment_modes.add_payment_mode'),
|
||||
componentName: 'PaymentModeModal',
|
||||
})
|
||||
}
|
||||
|
||||
function onSelectNote(data) {
|
||||
paymentStore.currentPayment.notes = '' + data.notes
|
||||
}
|
||||
|
||||
async function setInvoiceFromUrl() {
|
||||
let res = await invoiceStore.fetchInvoice(route?.params?.id)
|
||||
|
||||
paymentStore.currentPayment.customer_id = res.data.data.customer.id
|
||||
paymentStore.currentPayment.invoice_id = res.data.data.id
|
||||
}
|
||||
|
||||
async function onSelectInvoice(id) {
|
||||
if (id) {
|
||||
selectedInvoice.value = invoiceList.value.find((inv) => inv.id === id)
|
||||
|
||||
amount.value = selectedInvoice.value.due_amount / 100
|
||||
paymentStore.currentPayment.maxPayableAmount =
|
||||
selectedInvoice.value.due_amount
|
||||
}
|
||||
}
|
||||
|
||||
function onCustomerChange(customer_id) {
|
||||
if (customer_id) {
|
||||
let data = {
|
||||
customer_id: customer_id,
|
||||
status: 'DUE',
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
data.status = ''
|
||||
}
|
||||
|
||||
isLoadingInvoices.value = true
|
||||
|
||||
Promise.all([
|
||||
invoiceStore.fetchInvoices(data),
|
||||
customerStore.fetchCustomer(customer_id),
|
||||
])
|
||||
.then(async ([res1, res2]) => {
|
||||
if (res1) {
|
||||
invoiceList.value = [...res1.data.data]
|
||||
}
|
||||
|
||||
if (res2 && res2.data) {
|
||||
paymentStore.currentPayment.selectedCustomer = res2.data.data
|
||||
paymentStore.currentPayment.customer = res2.data.data
|
||||
paymentStore.currentPayment.currency = res2.data.data.currency
|
||||
}
|
||||
|
||||
if (isEdit.value && paymentStore.currentPayment.invoice_id) {
|
||||
selectedInvoice.value = invoiceList.value.find(
|
||||
(inv) => inv.id === paymentStore.currentPayment.invoice_id
|
||||
)
|
||||
|
||||
paymentStore.currentPayment.maxPayableAmount =
|
||||
selectedInvoice.value.due_amount +
|
||||
paymentStore.currentPayment.amount
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
invoiceList.value = invoiceList.value.filter((v) => {
|
||||
return (
|
||||
v.due_amount > 0 || v.id == paymentStore.currentPayment.invoice_id
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
isLoadingInvoices.value = false
|
||||
})
|
||||
.catch((error) => {
|
||||
isLoadingInvoices.value = false
|
||||
console.error(error, 'error')
|
||||
})
|
||||
}
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
paymentStore.resetCurrentPayment()
|
||||
invoiceList.value = []
|
||||
})
|
||||
|
||||
async function submitPaymentData() {
|
||||
v$.value.$touch()
|
||||
|
||||
if (v$.value.$invalid) {
|
||||
return false
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
let data = {
|
||||
...paymentStore.currentPayment,
|
||||
}
|
||||
|
||||
let response = null
|
||||
|
||||
try {
|
||||
const action = isEdit.value
|
||||
? paymentStore.updatePayment
|
||||
: paymentStore.addPayment
|
||||
|
||||
response = await action(data)
|
||||
|
||||
router.push(`/admin/payments/${response.data.data.id}/view`)
|
||||
} catch (err) {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function selectNewCustomer(id) {
|
||||
let params = {
|
||||
userId: id,
|
||||
}
|
||||
if (route.params.id) params.model_id = route.params.id
|
||||
|
||||
paymentStore.getNextNumber(params, true)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user