Compare commits

..

8 Commits

18 changed files with 484 additions and 154 deletions

View File

@ -45,15 +45,21 @@ class EstimatesRequest extends FormRequest
'nullable' 'nullable'
], ],
'discount' => [ 'discount' => [
'numeric',
'required', 'required',
], ],
'discount_val' => [ 'discount_val' => [
'integer',
'required', 'required',
], ],
'sub_total' => [ 'sub_total' => [
'integer',
'required', 'required',
], ],
'total' => [ 'total' => [
'integer',
'numeric',
'max:99999999',
'required', 'required',
], ],
'tax' => [ 'tax' => [
@ -77,9 +83,11 @@ class EstimatesRequest extends FormRequest
'required', 'required',
], ],
'items.*.quantity' => [ 'items.*.quantity' => [
'integer',
'required', 'required',
], ],
'items.*.price' => [ 'items.*.price' => [
'integer',
'required', 'required',
], ],
]; ];

View File

@ -45,15 +45,21 @@ class InvoicesRequest extends FormRequest
'nullable' 'nullable'
], ],
'discount' => [ 'discount' => [
'numeric',
'required', 'required',
], ],
'discount_val' => [ 'discount_val' => [
'integer',
'required', 'required',
], ],
'sub_total' => [ 'sub_total' => [
'integer',
'required', 'required',
], ],
'total' => [ 'total' => [
'integer',
'numeric',
'max:99999999',
'required', 'required',
], ],
'tax' => [ 'tax' => [
@ -77,9 +83,11 @@ class InvoicesRequest extends FormRequest
'required', 'required',
], ],
'items.*.quantity' => [ 'items.*.quantity' => [
'integer',
'required', 'required',
], ],
'items.*.price' => [ 'items.*.price' => [
'integer',
'required', 'required',
], ],
]; ];

View File

@ -43,15 +43,21 @@ class RecurringInvoiceRequest extends FormRequest
'nullable' 'nullable'
], ],
'discount' => [ 'discount' => [
'numeric',
'required', 'required',
], ],
'discount_val' => [ 'discount_val' => [
'integer',
'required', 'required',
], ],
'sub_total' => [ 'sub_total' => [
'integer',
'required', 'required',
], ],
'total' => [ 'total' => [
'integer',
'numeric',
'max:99999999',
'required', 'required',
], ],
'tax' => [ 'tax' => [

View File

@ -271,23 +271,19 @@ const price = computed({
} else { } else {
updateItemAttribute('price', newValue) updateItemAttribute('price', newValue)
} }
setDiscount()
}, },
}) })
const subtotal = computed(() => props.itemData.price * props.itemData.quantity) const subtotal = computed(() => Math.round(props.itemData.price * props.itemData.quantity))
const discount = computed({ const discount = computed({
get: () => { get: () => {
return props.itemData.discount return props.itemData.discount
}, },
set: (newValue) => { set: (newValue) => {
if (props.itemData.discount_type === 'percentage') {
updateItemAttribute('discount_val', (subtotal.value * newValue) / 100)
} else {
updateItemAttribute('discount_val', Math.round(newValue * 100))
}
updateItemAttribute('discount', newValue) updateItemAttribute('discount', newValue)
setDiscount()
}, },
}) })
@ -313,7 +309,7 @@ const showRemoveButton = computed(() => {
const totalSimpleTax = computed(() => { const totalSimpleTax = computed(() => {
return Math.round( return Math.round(
sumBy(props.itemData.taxes, function (tax) { sumBy(props.itemData.taxes, function (tax) {
if (!tax.compound_tax) { if (tax.amount) {
return tax.amount return tax.amount
} }
return 0 return 0
@ -321,18 +317,7 @@ const totalSimpleTax = computed(() => {
) )
}) })
const totalCompoundTax = computed(() => { const totalTax = computed(() => totalSimpleTax.value)
return Math.round(
sumBy(props.itemData.taxes, function (tax) {
if (tax.compound_tax) {
return tax.amount
}
return 0
})
)
})
const totalTax = computed(() => totalSimpleTax.value + totalCompoundTax.value)
const rules = { const rules = {
name: { name: {
@ -416,6 +401,16 @@ function updateTax(data) {
syncItemToStore() syncItemToStore()
} }
function setDiscount() {
const newValue = props.store[props.storeProp].items[props.index].discount
if (props.itemData.discount_type === 'percentage'){
updateItemAttribute('discount_val', Math.round((subtotal.value * newValue) / 100))
}else{
updateItemAttribute('discount_val', Math.round(newValue * 100))
}
}
function searchVal(val) { function searchVal(val) {
updateItemAttribute('name', val) updateItemAttribute('name', val)
} }
@ -485,10 +480,12 @@ function syncItemToStore() {
total: total.value, total: total.value,
sub_total: subtotal.value, sub_total: subtotal.value,
totalSimpleTax: totalSimpleTax.value, totalSimpleTax: totalSimpleTax.value,
totalCompoundTax: totalCompoundTax.value,
totalTax: totalTax.value, totalTax: totalTax.value,
tax: totalTax.value, tax: totalTax.value,
taxes: [...itemTaxes], taxes: [...itemTaxes],
tax_type_ids: itemTaxes.flatMap(_t =>
_t.tax_type_id ? _t.tax_type_id : [],
),
} }
props.store.updateItem(data) props.store.updateItem(data)

View File

@ -146,14 +146,14 @@ const filteredTypes = computed(() => {
}) })
const taxAmount = computed(() => { const taxAmount = computed(() => {
if (localTax.compound_tax && props.discountedTotal) {
return ((props.discountedTotal + props.totalTax) * localTax.percent) / 100
}
if (props.discountedTotal && localTax.percent) { if (props.discountedTotal && localTax.percent) {
const taxPerItemEnabled = props.store[props.storeProp].tax_per_item === 'YES'
const discountPerItemEnabled = props.store[props.storeProp].discount_per_item === 'YES'
if (taxPerItemEnabled && !discountPerItemEnabled){
return getTaxAmount()
}
return (props.discountedTotal * localTax.percent) / 100 return (props.discountedTotal * localTax.percent) / 100
} }
return 0 return 0
}) })
@ -171,6 +171,13 @@ watch(
} }
) )
watch(
() => taxAmount.value,
() => {
updateRowTax()
},
)
// Set SelectedTax // Set SelectedTax
if (props.taxData.tax_type_id > 0) { if (props.taxData.tax_type_id > 0) {
selectedTax.value = taxTypeStore.taxTypes.find( selectedTax.value = taxTypeStore.taxTypes.find(
@ -183,7 +190,6 @@ updateRowTax()
function onSelectTax(val) { function onSelectTax(val) {
localTax.percent = val.percent localTax.percent = val.percent
localTax.tax_type_id = val.id localTax.tax_type_id = val.id
localTax.compound_tax = val.compound_tax
localTax.name = val.name localTax.name = val.name
updateRowTax() updateRowTax()
@ -220,6 +226,27 @@ function openTaxModal() {
function removeTax(index) { function removeTax(index) {
props.store.$patch((state) => { props.store.$patch((state) => {
state[props.storeProp].items[props.itemIndex].taxes.splice(index, 1) state[props.storeProp].items[props.itemIndex].taxes.splice(index, 1)
state[props.storeProp].items[props.itemIndex].tax = 0
state[props.storeProp].items[props.itemIndex].totalTax = 0
}) })
} }
function getTaxAmount() {
let total = 0
let discount = 0
const itemTotal = props.discountedTotal
const modelDiscount = props.store[props.storeProp].discount ? props.store[props.storeProp].discount : 0
const type = props.store[props.storeProp].discount_type
if (modelDiscount > 0) {
props.store[props.storeProp].items.forEach((_i) => {
total += _i.total
})
const proportion = (itemTotal / total).toFixed(2)
discount = type === 'fixed' ? modelDiscount * 100 : (total * modelDiscount) / 100
const itemDiscount = Math.round(discount * proportion)
const discounted = itemTotal - itemDiscount
return Math.round((discounted * localTax.percent) / 100)
}
return Math.round((props.discountedTotal * localTax.percent) / 100)
}
</script> </script>

View File

@ -191,7 +191,7 @@
</template> </template>
<script setup> <script setup>
import { computed, inject, ref } from 'vue' import { computed, inject, ref, watch } from 'vue'
import Guid from 'guid' import Guid from 'guid'
import Tax from './CreateTotalTaxes.vue' import Tax from './CreateTotalTaxes.vue'
import TaxStub from '@/scripts/admin/stub/abilities' import TaxStub from '@/scripts/admin/stub/abilities'
@ -227,19 +227,20 @@ const utils = inject('$utils')
const companyStore = useCompanyStore() const companyStore = useCompanyStore()
watch(
() => props.store[props.storeProp].items,
(val) => {
setDiscount()
}, { deep: true },
)
const totalDiscount = computed({ const totalDiscount = computed({
get: () => { get: () => {
return props.store[props.storeProp].discount return props.store[props.storeProp].discount
}, },
set: (newValue) => { set: (newValue) => {
if (props.store[props.storeProp].discount_type === 'percentage') {
props.store[props.storeProp].discount_val = Math.round(
(props.store.getSubTotal * newValue) / 100
)
} else {
props.store[props.storeProp].discount_val = Math.round(newValue * 100)
}
props.store[props.storeProp].discount = newValue props.store[props.storeProp].discount = newValue
setDiscount()
}, },
}) })
@ -265,7 +266,7 @@ const itemWiseTaxes = computed(() => {
} else if (tax.tax_type_id) { } else if (tax.tax_type_id) {
taxes.push({ taxes.push({
tax_type_id: tax.tax_type_id, tax_type_id: tax.tax_type_id,
amount: tax.amount, amount: Math.round(tax.amount),
percent: tax.percent, percent: tax.percent,
name: tax.name, name: tax.name,
}) })
@ -284,6 +285,19 @@ const defaultCurrency = computed(() => {
} }
}) })
function setDiscount() {
const newValue = props.store[props.storeProp].discount
if (props.store[props.storeProp].discount_type === 'percentage') {
props.store[props.storeProp].discount_val
= Math.round((props.store.getSubTotal * newValue) / 100)
return
}
props.store[props.storeProp].discount_val = Math.round(newValue * 100)
}
function selectFixed() { function selectFixed() {
if (props.store[props.storeProp].discount_type === 'fixed') { if (props.store[props.storeProp].discount_type === 'fixed') {
return return
@ -295,24 +309,21 @@ function selectFixed() {
} }
function selectPercentage() { function selectPercentage() {
if (props.store[props.storeProp].discount_type === 'percentage') { if (props.store[props.storeProp].discount_type === 'percentage'){
return return
} }
props.store[props.storeProp].discount_val =
(props.store.getSubTotal * props.store[props.storeProp].discount) / 100 const val = Math.round(props.store[props.storeProp].discount * 100) / 100
props.store[props.storeProp].discount_val
= Math.round((props.store.getSubTotal * val) / 100)
props.store[props.storeProp].discount_type = 'percentage' props.store[props.storeProp].discount_type = 'percentage'
} }
function onSelectTax(selectedTax) { function onSelectTax(selectedTax) {
let amount = 0 let amount = 0
if (props.store.getSubtotalWithDiscount && selectedTax.percent) {
if (selectedTax.compound_tax && props.store.getSubtotalWithDiscount) {
amount = Math.round(
((props.store.getSubtotalWithDiscount + props.store.getTotalSimpleTax) *
selectedTax.percent) /
100
)
} else if (props.store.getSubtotalWithDiscount && selectedTax.percent) {
amount = Math.round( amount = Math.round(
(props.store.getSubtotalWithDiscount * selectedTax.percent) / 100 (props.store.getSubtotalWithDiscount * selectedTax.percent) / 100
) )
@ -323,7 +334,6 @@ function onSelectTax(selectedTax) {
id: Guid.raw(), id: Guid.raw(),
name: selectedTax.name, name: selectedTax.name,
percent: selectedTax.percent, percent: selectedTax.percent,
compound_tax: selectedTax.compound_tax,
tax_type_id: selectedTax.id, tax_type_id: selectedTax.id,
amount, amount,
} }

View File

@ -77,17 +77,6 @@
@input="v$.currentTaxType.description.$touch()" @input="v$.currentTaxType.description.$touch()"
/> />
</BaseInputGroup> </BaseInputGroup>
<BaseInputGroup
:label="$t('tax_types.compound_tax')"
variant="horizontal"
class="flex flex-row-reverse"
>
<BaseSwitch
v-model="taxTypeStore.currentTaxType.compound_tax"
class="flex items-center"
/>
</BaseInputGroup>
</BaseInputGrid> </BaseInputGrid>
</div> </div>
<div <div
@ -209,14 +198,7 @@ async function submitTaxTypeData() {
function SelectTax(taxData) { function SelectTax(taxData) {
let amount = 0 let amount = 0
if (taxData.compound_tax && estimateStore.getSubtotalWithDiscount) { if (estimateStore.getSubtotalWithDiscount && taxData.percent) {
amount = Math.round(
((estimateStore.getSubtotalWithDiscount +
estimateStore.getTotalSimpleTax) *
taxData.percent) /
100
)
} else if (estimateStore.getSubtotalWithDiscount && taxData.percent) {
amount = Math.round( amount = Math.round(
(estimateStore.getSubtotalWithDiscount * taxData.percent) / 100 (estimateStore.getSubtotalWithDiscount * taxData.percent) / 100
) )
@ -226,7 +208,6 @@ function SelectTax(taxData) {
id: Guid.raw(), id: Guid.raw(),
name: taxData.name, name: taxData.name,
percent: taxData.percent, percent: taxData.percent,
compound_tax: taxData.compound_tax,
tax_type_id: taxData.id, tax_type_id: taxData.id,
amount, amount,
} }
@ -242,7 +223,6 @@ function selectItemTax(taxData) {
id: Guid.raw(), id: Guid.raw(),
name: taxData.name, name: taxData.name,
percent: taxData.percent, percent: taxData.percent,
compound_tax: taxData.compound_tax,
tax_type_id: taxData.id, tax_type_id: taxData.id,
} }
modalStore.refreshData(data) modalStore.refreshData(data)

View File

@ -143,7 +143,8 @@ export const useEstimateStore = (useWindow = false) => {
axios axios
.get(`/api/v1/estimates/${id}`) .get(`/api/v1/estimates/${id}`)
.then((response) => { .then((response) => {
Object.assign(this.newEstimate, response.data.data) this.setEstimateData(response.data.data)
this.setCustomerAddresses(this.newEstimate.customer)
resolve(response) resolve(response)
}) })
.catch((err) => { .catch((err) => {
@ -154,6 +155,41 @@ export const useEstimateStore = (useWindow = false) => {
}) })
}, },
setEstimateData(estimate) {
Object.assign(this.newEstimate, estimate)
if (this.newEstimate.tax_per_item === 'YES') {
this.newEstimate.items.forEach((_i) => {
if (_i.taxes && !_i.taxes.length){
_i.taxes.push({ ...taxStub, id: Guid.raw() })
}
})
}
if (this.newEstimate.discount_per_item === 'YES') {
this.newEstimate.items.forEach((_i, index) => {
if (_i.discount_type === 'fixed'){
this.newEstimate.items[index].discount = _i.discount / 100
}
})
}
else {
if (this.newEstimate.discount_type === 'fixed'){
this.newEstimate.discount = this.newEstimate.discount / 100
}
}
},
setCustomerAddresses(customer) {
const customer_business = customer.customer_business
if (customer_business?.billing_address){
this.newEstimate.customer.billing_address = customer_business.billing_address
}
if (customer_business?.shipping_address){
this.newEstimate.customer.shipping_address = customer_business.shipping_address
}
},
addSalesTaxUs() { addSalesTaxUs() {
const taxTypeStore = useTaxTypeStore() const taxTypeStore = useTaxTypeStore()
let salesTax = { ...taxStub } let salesTax = { ...taxStub }

View File

@ -133,8 +133,8 @@ export const useInvoiceStore = (useWindow = false) => {
axios axios
.get(`/api/v1/invoices/${id}`) .get(`/api/v1/invoices/${id}`)
.then((response) => { .then((response) => {
Object.assign(this.newInvoice, response.data.data) this.setInvoiceData(response.data.data)
this.newInvoice.customer = response.data.data.customer this.setCustomerAddresses(this.newInvoice.customer)
resolve(response) resolve(response)
}) })
.catch((err) => { .catch((err) => {
@ -144,6 +144,38 @@ export const useInvoiceStore = (useWindow = false) => {
}) })
}, },
setInvoiceData(invoice) {
Object.assign(this.newInvoice, invoice)
if (this.newInvoice.tax_per_item === 'YES') {
this.newInvoice.items.forEach((_i) => {
if (_i.taxes && !_i.taxes.length)
_i.taxes.push({ ...taxStub, id: Guid.raw() })
})
}
if (this.newInvoice.discount_per_item === 'YES') {
this.newInvoice.items.forEach((_i, index) => {
if (_i.discount_type === 'fixed')
this.newInvoice.items[index].discount = _i.discount / 100
})
}
else {
if (this.newInvoice.discount_type === 'fixed')
this.newInvoice.discount = this.newInvoice.discount / 100
}
},
setCustomerAddresses(customer) {
const customer_business = customer.customer_business
if (customer_business?.billing_address)
this.newInvoice.customer.billing_address = customer_business.billing_address
if (customer_business?.shipping_address)
this.newInvoice.customer.shipping_address = customer_business.shipping_address
},
addSalesTaxUs() { addSalesTaxUs() {
const taxTypeStore = useTaxTypeStore() const taxTypeStore = useTaxTypeStore()
let salesTax = { ...taxStub } let salesTax = { ...taxStub }

View File

@ -35,7 +35,16 @@
</div> </div>
<div <div
class="grid col-span-12 mt-6 text-center xl:mt-0 sm:grid-cols-4 xl:text-right xl:col-span-3 xl:grid-cols-1 xxl:col-span-2" class="
grid
col-span-12
mt-6
text-center
xl:mt-0
sm:grid-cols-4
xl:text-right xl:col-span-3 xl:grid-cols-1
xxl:col-span-2
"
> >
<div class="px-6 py-2"> <div class="px-6 py-2">
<span class="text-xs leading-5 lg:text-sm"> <span class="text-xs leading-5 lg:text-sm">
@ -168,12 +177,10 @@ const getChartInvoices = computed(() => {
return [] return []
}) })
const customerId = computed(() => route.params.id)
watch( watch(
() => customerId.value, route,
(id) => { () => {
if (id && route.name === 'customers.view') { if (route.params.id) {
loadCustomer() loadCustomer()
} }
selectedYear.value = 'This year' selectedYear.value = 'This year'

View File

@ -37,10 +37,32 @@
<!-- Sidebar --> <!-- Sidebar -->
<div <div
class="fixed top-0 left-0 hidden h-full pt-16 pb-[6.4rem] ml-56 bg-white xl:ml-64 w-88 xl:block" class="
fixed
top-0
left-0
hidden
h-full
pt-16
pb-[6.4rem]
ml-56
bg-white
xl:ml-64
w-88
xl:block
"
> >
<div <div
class="flex items-center justify-between px-4 pt-8 pb-2 border border-gray-200 border-solid height-full" class="
flex
items-center
justify-between
px-4
pt-8
pb-2
border border-gray-200 border-solid
height-full
"
> >
<div class="mb-6"> <div class="mb-6">
<BaseInput <BaseInput
@ -70,7 +92,14 @@
</template> </template>
<div <div
class="px-4 py-1 pb-2 mb-1 mb-2 text-sm border-b border-gray-200 border-solid" class="
px-4
py-1
pb-2
mb-1 mb-2
text-sm
border-b border-gray-200 border-solid
"
> >
{{ $t('general.sort_by') }} {{ $t('general.sort_by') }}
</div> </div>
@ -127,7 +156,12 @@
<div <div
ref="estimateListSection" ref="estimateListSection"
class="h-full overflow-y-scroll border-l border-gray-200 border-solid base-scroll" class="
h-full
overflow-y-scroll
border-l border-gray-200 border-solid
base-scroll
"
> >
<div v-for="(estimate, index) in estimateList" :key="index"> <div v-for="(estimate, index) in estimateList" :key="index">
<router-link <router-link
@ -147,11 +181,29 @@
<BaseText <BaseText
:text="estimate.customer.name" :text="estimate.customer.name"
:length="30" :length="30"
class="pr-2 mb-2 text-sm not-italic font-normal leading-5 text-black capitalize truncate" class="
pr-2
mb-2
text-sm
not-italic
font-normal
leading-5
text-black
capitalize
truncate
"
/> />
<div <div
class="mt-1 mb-2 text-xs not-italic font-medium leading-5 text-gray-600" class="
mt-1
mb-2
text-xs
not-italic
font-medium
leading-5
text-gray-600
"
> >
{{ estimate.estimate_number }} {{ estimate.estimate_number }}
</div> </div>
@ -168,11 +220,26 @@
<BaseFormatMoney <BaseFormatMoney
:amount="estimate.total" :amount="estimate.total"
:currency="estimate.customer.currency" :currency="estimate.customer.currency"
class="block mb-2 text-xl not-italic font-semibold leading-8 text-right text-gray-900" class="
block
mb-2
text-xl
not-italic
font-semibold
leading-8
text-right text-gray-900
"
/> />
<div <div
class="text-sm not-italic font-normal leading-5 text-right text-gray-600 est-date" class="
text-sm
not-italic
font-normal
leading-5
text-right text-gray-600
est-date
"
> >
{{ estimate.formatted_estimate_date }} {{ estimate.formatted_estimate_date }}
</div> </div>
@ -197,7 +264,13 @@
> >
<iframe <iframe
:src="`${shareableLink}`" :src="`${shareableLink}`"
class="flex-1 border border-gray-400 border-solid rounded-md bg-white frame-style" class="
flex-1
border border-gray-400 border-solid
rounded-md
bg-white
frame-style
"
/> />
</div> </div>
</BasePage> </BasePage>
@ -272,14 +345,11 @@ const getCurrentEstimateId = computed(() => {
return null return null
}) })
const estimateId = computed(() => route.params.id) watch(route, (to, from) => {
if (to.name === 'estimates.view') {
watch( loadEstimate()
() => estimateId.value,
(id) => {
if (id && route.name === 'estimates.view') loadEstimate()
} }
) })
loadEstimates() loadEstimates()
loadEstimate() loadEstimate()

View File

@ -138,6 +138,7 @@
<script setup> <script setup>
import { computed, ref, watch, onMounted } from 'vue' import { computed, ref, watch, onMounted } from 'vue'
import { cloneDeep } from 'lodash'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { import {
@ -257,11 +258,30 @@ async function submitForm() {
isSaving.value = true isSaving.value = true
let data = { let data = cloneDeep({
...estimateStore.newEstimate, ...estimateStore.newEstimate,
sub_total: estimateStore.getSubTotal, sub_total: estimateStore.getSubTotal,
total: estimateStore.getTotal, total: estimateStore.getTotal,
tax: estimateStore.getTotalTax, tax: estimateStore.getTotalTax,
})
if (data.discount_per_item === 'YES') {
data.items.forEach((item, index) => {
if (item.discount_type === 'fixed'){
data.items[index].discount = Math.round(item.discount * 100)
}
})
}
else {
if (data.discount_type === 'fixed'){
data.discount = Math.round(data.discount * 100)
}
}
if (
!estimateStore.newEstimate.tax_per_item === 'YES'
&& data.taxes.length
){
data.tax_type_ids = data.taxes.map(_t => _t.tax_type_id)
} }
const action = isEdit.value const action = isEdit.value

View File

@ -65,14 +65,11 @@ const getCurrentInvoiceId = computed(() => {
return null return null
}) })
const invoiceId = computed(() => route.params.id) watch(route, (to, from) => {
if (to.name === 'invoices.view') {
watch( loadInvoice()
() => invoiceId.value,
(id) => {
if (id && route.name === 'invoices.view') loadInvoice()
} }
) })
async function onMarkAsSent() { async function onMarkAsSent() {
dialogStore dialogStore
@ -289,10 +286,32 @@ onSearched = debounce(onSearched, 500)
<!-- sidebar --> <!-- sidebar -->
<div <div
class="fixed top-0 left-0 hidden h-full pt-16 pb-[6.4rem] ml-56 bg-white xl:ml-64 w-88 xl:block" class="
fixed
top-0
left-0
hidden
h-full
pt-16
pb-[6.4rem]
ml-56
bg-white
xl:ml-64
w-88
xl:block
"
> >
<div <div
class="flex items-center justify-between px-4 pt-8 pb-2 border border-gray-200 border-solid height-full" class="
flex
items-center
justify-between
px-4
pt-8
pb-2
border border-gray-200 border-solid
height-full
"
> >
<div class="mb-6"> <div class="mb-6">
<BaseInput <BaseInput
@ -316,7 +335,14 @@ onSearched = debounce(onSearched, 500)
</BaseButton> </BaseButton>
</template> </template>
<div <div
class="px-2 py-1 pb-2 mb-1 mb-2 text-sm border-b border-gray-200 border-solid" class="
px-2
py-1
pb-2
mb-1 mb-2
text-sm
border-b border-gray-200 border-solid
"
> >
{{ $t('general.sort_by') }} {{ $t('general.sort_by') }}
</div> </div>
@ -373,7 +399,12 @@ onSearched = debounce(onSearched, 500)
<div <div
ref="invoiceListSection" ref="invoiceListSection"
class="h-full overflow-y-scroll border-l border-gray-200 border-solid base-scroll" class="
h-full
overflow-y-scroll
border-l border-gray-200 border-solid
base-scroll
"
> >
<div v-for="(invoice, index) in invoiceList" :key="index"> <div v-for="(invoice, index) in invoiceList" :key="index">
<router-link <router-link
@ -393,11 +424,29 @@ onSearched = debounce(onSearched, 500)
<BaseText <BaseText
:text="invoice.customer.name" :text="invoice.customer.name"
:length="30" :length="30"
class="pr-2 mb-2 text-sm not-italic font-normal leading-5 text-black capitalize truncate" class="
pr-2
mb-2
text-sm
not-italic
font-normal
leading-5
text-black
capitalize
truncate
"
/> />
<div <div
class="mt-1 mb-2 text-xs not-italic font-medium leading-5 text-gray-600" class="
mt-1
mb-2
text-xs
not-italic
font-medium
leading-5
text-gray-600
"
> >
{{ invoice.invoice_number }} {{ invoice.invoice_number }}
</div> </div>
@ -411,12 +460,27 @@ onSearched = debounce(onSearched, 500)
<div class="flex-1 whitespace-nowrap right"> <div class="flex-1 whitespace-nowrap right">
<BaseFormatMoney <BaseFormatMoney
class="mb-2 text-xl not-italic font-semibold leading-8 text-right text-gray-900 block" class="
mb-2
text-xl
not-italic
font-semibold
leading-8
text-right text-gray-900
block
"
:amount="invoice.total" :amount="invoice.total"
:currency="invoice.customer.currency" :currency="invoice.customer.currency"
/> />
<div <div
class="text-sm not-italic font-normal leading-5 text-right text-gray-600 est-date" class="
text-sm
not-italic
font-normal
leading-5
text-right text-gray-600
est-date
"
> >
{{ invoice.formatted_invoice_date }} {{ invoice.formatted_invoice_date }}
</div> </div>
@ -441,7 +505,13 @@ onSearched = debounce(onSearched, 500)
> >
<iframe <iframe
:src="`${shareableLink}`" :src="`${shareableLink}`"
class="flex-1 border border-gray-400 border-solid bg-white rounded-md frame-style" class="
flex-1
border border-gray-400 border-solid
bg-white
rounded-md
frame-style
"
/> />
</div> </div>
</BasePage> </BasePage>

View File

@ -147,6 +147,7 @@ import {
decimal, decimal,
} from '@vuelidate/validators' } from '@vuelidate/validators'
import useVuelidate from '@vuelidate/core' import useVuelidate from '@vuelidate/core'
import { cloneDeep } from 'lodash'
import { useInvoiceStore } from '@/scripts/admin/stores/invoice' import { useInvoiceStore } from '@/scripts/admin/stores/invoice'
import { useModuleStore } from '@/scripts/admin/stores/module' import { useModuleStore } from '@/scripts/admin/stores/module'
@ -258,11 +259,29 @@ async function submitForm() {
isSaving.value = true isSaving.value = true
let data = { let data = cloneDeep({
...invoiceStore.newInvoice, ...invoiceStore.newInvoice,
sub_total: invoiceStore.getSubTotal, sub_total: invoiceStore.getSubTotal,
total: invoiceStore.getTotal, total: invoiceStore.getTotal,
tax: invoiceStore.getTotalTax, tax: invoiceStore.getTotalTax,
})
if (data.discount_per_item === 'YES') {
data.items.forEach((item, index) => {
if (item.discount_type === 'fixed'){
data.items[index].discount = item.discount * 100
}
})
}
else {
if (data.discount_type === 'fixed'){
data.discount = data.discount * 100
}
}
if (
!invoiceStore.newInvoice.tax_per_item === 'YES'
&& data.taxes.length
){
data.tax_type_ids = data.taxes.map(_t => _t.tax_type_id)
} }
try { try {

View File

@ -22,10 +22,31 @@
<!-- Sidebar --> <!-- Sidebar -->
<div <div
class="fixed top-0 left-0 hidden h-full pt-16 pb-[6rem] ml-56 bg-white xl:ml-64 w-88 xl:block" class="
fixed
top-0
left-0
hidden
h-full
pt-16
pb-[6rem]
ml-56
bg-white
xl:ml-64
w-88
xl:block
"
> >
<div <div
class="flex items-center justify-between px-4 pt-8 pb-6 border border-gray-200 border-solid" class="
flex
items-center
justify-between
px-4
pt-8
pb-6
border border-gray-200 border-solid
"
> >
<BaseInput <BaseInput
v-model="searchData.searchText" v-model="searchData.searchText"
@ -49,7 +70,14 @@
</template> </template>
<div <div
class="px-4 py-1 pb-2 mb-2 text-sm border-b border-gray-200 border-solid" class="
px-4
py-1
pb-2
mb-2
text-sm
border-b border-gray-200 border-solid
"
> >
{{ $t('general.sort_by') }} {{ $t('general.sort_by') }}
</div> </div>
@ -131,17 +159,43 @@
<BaseText <BaseText
:text="payment?.customer?.name" :text="payment?.customer?.name"
:length="30" :length="30"
class="pr-2 mb-2 text-sm not-italic font-normal leading-5 text-black capitalize truncate" class="
pr-2
mb-2
text-sm
not-italic
font-normal
leading-5
text-black
capitalize
truncate
"
/> />
<div <div
class="mb-1 text-xs not-italic font-medium leading-5 text-gray-500 capitalize" class="
mb-1
text-xs
not-italic
font-medium
leading-5
text-gray-500
capitalize
"
> >
{{ payment?.payment_number }} {{ payment?.payment_number }}
</div> </div>
<div <div
class="mb-1 text-xs not-italic font-medium leading-5 text-gray-500 capitalize" class="
mb-1
text-xs
not-italic
font-medium
leading-5
text-gray-500
capitalize
"
> >
{{ payment?.invoice_number }} {{ payment?.invoice_number }}
</div> </div>
@ -149,7 +203,15 @@
<div class="flex-1 whitespace-nowrap right"> <div class="flex-1 whitespace-nowrap right">
<BaseFormatMoney <BaseFormatMoney
class="block mb-2 text-xl not-italic font-semibold leading-8 text-right text-gray-900" class="
block
mb-2
text-xl
not-italic
font-semibold
leading-8
text-right text-gray-900
"
:amount="payment?.amount" :amount="payment?.amount"
:currency="payment.customer?.currency" :currency="payment.customer?.currency"
/> />
@ -251,14 +313,9 @@ const paymentDate = computed(() => {
) )
}) })
const paymentId = computed(() => route.params.id) watch(route, () => {
loadPayment()
watch( })
() => paymentId.value,
(id) => {
if (id && route.name === 'payments.view') loadPayment()
}
)
loadPayments() loadPayments()
loadPayment() loadPayment()

View File

@ -78,12 +78,10 @@ let isLoading = computed(() => {
return recurringInvoiceStore.isFetchingViewData return recurringInvoiceStore.isFetchingViewData
}) })
const invoiceId = computed(() => route.params.id)
watch( watch(
() => invoiceId.value, route,
(id) => { () => {
if (id && route.name === 'recurring-invoices.view') { if (route.params.id && route.name === 'recurring-invoices.view') {
loadRecurringInvoice() loadRecurringInvoice()
} }
}, },

View File

@ -20,21 +20,6 @@
:data="fetchData" :data="fetchData"
:columns="taxTypeColumns" :columns="taxTypeColumns"
> >
<template #cell-compound_tax="{ row }">
<BaseBadge
:bg-color="
utils.getBadgeStatusColor(row.data.compound_tax ? 'YES' : 'NO')
.bgColor
"
:color="
utils.getBadgeStatusColor(row.data.compound_tax ? 'YES' : 'NO')
.color
"
>
{{ row.data.compound_tax ? 'Yes' : 'No'.replace('_', ' ') }}
</BaseBadge>
</template>
<template #cell-percent="{ row }"> {{ row.data.percent }} % </template> <template #cell-percent="{ row }"> {{ row.data.percent }} % </template>
<template v-if="hasAtleastOneAbility()" #cell-actions="{ row }"> <template v-if="hasAtleastOneAbility()" #cell-actions="{ row }">
@ -91,11 +76,6 @@ const taxTypeColumns = computed(() => {
thClass: 'extra', thClass: 'extra',
tdClass: 'font-medium text-gray-900', tdClass: 'font-medium text-gray-900',
}, },
{
key: 'compound_tax',
label: t('settings.tax_types.compound_tax'),
tdClass: 'font-medium text-gray-900',
},
{ {
key: 'percent', key: 'percent',
label: t('settings.tax_types.percent'), label: t('settings.tax_types.percent'),

View File

@ -309,6 +309,8 @@ function changeSorting(column) {
} }
if (!usesLocalData.value) { if (!usesLocalData.value) {
if (pagination.value)
pagination.value.currentPage = 1
mapDataToRows() mapDataToRows()
} }
} }
@ -326,7 +328,10 @@ async function pageChange(page) {
await mapDataToRows() await mapDataToRows()
} }
async function refresh() { async function refresh(isPreservePage = false) {
if (pagination.value && !isPreservePage)
pagination.value.currentPage = 1
await mapDataToRows() await mapDataToRows()
} }