403 lines
11 KiB
Vue
403 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch } from 'vue';
|
|
import { formatNumberDecimal, commaInput } from 'stores/utils';
|
|
import { QTableProps } from 'quasar';
|
|
import { calculatePrice } from 'src/utils/arithmetic';
|
|
|
|
const serviceCharge = defineModel<number>('serviceCharge');
|
|
const agentPrice = defineModel<number>('agentPrice');
|
|
const price = defineModel<number>('price');
|
|
const vatIncluded = defineModel<boolean>('vatIncluded');
|
|
const calcVat = defineModel<boolean>('calcVat');
|
|
|
|
const price4Show = ref<string>(commaInput(price.value?.toString() || '0'));
|
|
const agentPrice4Show = ref<string>(
|
|
commaInput(agentPrice.value?.toString() || '0'),
|
|
);
|
|
const serviceCharge4Show = ref<string>(
|
|
commaInput(serviceCharge.value?.toString() || '0'),
|
|
);
|
|
|
|
const column = [
|
|
{
|
|
name: 'label',
|
|
align: 'center',
|
|
label: 'productService.product.priceInformation',
|
|
field: 'label',
|
|
},
|
|
{
|
|
name: 'pricePerUnit',
|
|
align: 'center',
|
|
label: 'quotation.pricePerUnit',
|
|
field: 'pricePerUnit',
|
|
},
|
|
{
|
|
name: 'beforeVat',
|
|
align: 'right',
|
|
label: 'quotation.priceBeforeVat',
|
|
field: 'beforeVat',
|
|
},
|
|
{
|
|
name: 'vat',
|
|
align: 'right',
|
|
label: 'general.vat',
|
|
field: 'vat',
|
|
},
|
|
{
|
|
name: 'total',
|
|
align: 'right',
|
|
label: 'quotation.sumPrice',
|
|
field: 'total',
|
|
},
|
|
] as const satisfies QTableProps['columns'];
|
|
|
|
const row = [
|
|
{
|
|
label: 'productService.product.salePrice',
|
|
beforeVat: 0,
|
|
vat: 0,
|
|
total: 0,
|
|
},
|
|
{
|
|
label: 'productService.product.agentPrice',
|
|
beforeVat: 0,
|
|
vat: 0,
|
|
total: 0,
|
|
},
|
|
{
|
|
label: 'productService.product.processingPrice',
|
|
beforeVat: 0,
|
|
vat: 0,
|
|
total: 0,
|
|
},
|
|
] as const satisfies QTableProps['rows'];
|
|
|
|
watch(calcVat, () => {
|
|
if (calcVat.value === false) vatIncluded.value = false;
|
|
});
|
|
|
|
withDefaults(
|
|
defineProps<{
|
|
dense?: boolean;
|
|
outlined?: boolean;
|
|
readonly?: boolean;
|
|
separator?: boolean;
|
|
isType?: boolean;
|
|
priceDisplay?: {
|
|
price: boolean;
|
|
agentPrice: boolean;
|
|
serviceCharge: boolean;
|
|
};
|
|
}>(),
|
|
{
|
|
priceDisplay: () => ({
|
|
price: true,
|
|
agentPrice: true,
|
|
serviceCharge: true,
|
|
}),
|
|
},
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="row col-12">
|
|
<div class="col-12 q-pb-sm row items-center">
|
|
<q-icon
|
|
flat
|
|
size="xs"
|
|
class="q-pa-sm rounded q-mr-sm"
|
|
color="info"
|
|
name="mdi-cash"
|
|
style="background-color: var(--surface-3)"
|
|
/>
|
|
<span class="text-body1 text-weight-bold">
|
|
{{ $t('productService.product.priceInformation') }}
|
|
</span>
|
|
<section class="q-px-md">
|
|
<input
|
|
id="input-calc-vat"
|
|
type="checkbox"
|
|
v-model="calcVat"
|
|
:disabled="readonly"
|
|
/>
|
|
<label
|
|
class="q-pl-sm"
|
|
for="input-calc-vat"
|
|
:style="{ opacity: readonly ? '.5' : undefined }"
|
|
>
|
|
{{ $t('general.calculateVat') }}
|
|
</label>
|
|
</section>
|
|
<div
|
|
class="surface-3 q-px-sm q-py-xs row text-caption app-text-muted"
|
|
style="border-radius: var(--radius-3)"
|
|
v-if="calcVat"
|
|
>
|
|
<span
|
|
id="btn-include-vat"
|
|
for="btn-include-vat"
|
|
class="q-px-sm q-mr-lg rounded cursor-pointer"
|
|
:class="{
|
|
dark: $q.dark.isActive,
|
|
'active-addr': vatIncluded,
|
|
'cursor-not-allowed': readonly,
|
|
}"
|
|
@click="readonly ? '' : (vatIncluded = true)"
|
|
>
|
|
{{ $t('productService.product.vatIncluded') }}
|
|
</span>
|
|
<span
|
|
id="btn-no-include-vat"
|
|
for="btn-no-include-vat"
|
|
class="q-px-sm rounded cursor-pointer"
|
|
:class="{
|
|
dark: $q.dark.isActive,
|
|
'active-addr': !vatIncluded,
|
|
'cursor-not-allowed': readonly,
|
|
}"
|
|
@click="readonly ? '' : (vatIncluded = false)"
|
|
>
|
|
{{ $t('productService.product.vatExcluded') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<q-table
|
|
:columns="column"
|
|
:rows="row"
|
|
:rows-per-page-options="[0]"
|
|
bordered
|
|
flat
|
|
hide-pagination
|
|
class="full-width"
|
|
:no-data-label="$t('general.noDataTable')"
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr
|
|
style="background-color: hsla(var(--info-bg) / 0.07)"
|
|
:props="props"
|
|
>
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
{{ col.label && $t(col.label) }}
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
|
|
<template v-slot:body="props">
|
|
<q-tr>
|
|
<q-td>{{ $t(props.row.label) }}</q-td>
|
|
<q-td class="text-right" style="width: 15%">
|
|
<q-input
|
|
v-if="priceDisplay?.price && props.rowIndex === 0"
|
|
id="input-price"
|
|
for="input-price"
|
|
:dense="dense"
|
|
outlined
|
|
:readonly="readonly"
|
|
:borderless="readonly"
|
|
hide-bottom-space
|
|
input-class="text-right"
|
|
:model-value="price4Show"
|
|
@blur="
|
|
() => {
|
|
price = Number(price4Show.replace(/,/g, ''));
|
|
if (price % 1 === 0) {
|
|
const [, dec] = price4Show.split('.');
|
|
if (!dec) {
|
|
price4Show += '.00';
|
|
}
|
|
}
|
|
}
|
|
"
|
|
@update:model-value="
|
|
(v) => {
|
|
price4Show = commaInput(v?.toString() || '0', 'string');
|
|
}
|
|
"
|
|
/>
|
|
<q-input
|
|
v-if="priceDisplay?.agentPrice && props.rowIndex === 1"
|
|
id="input-agent-price"
|
|
for="input-agent-price"
|
|
:dense="dense"
|
|
outlined
|
|
:readonly="readonly"
|
|
:borderless="readonly"
|
|
hide-bottom-space
|
|
input-class="text-right"
|
|
:model-value="agentPrice4Show"
|
|
@blur="
|
|
() => {
|
|
agentPrice = Number(agentPrice4Show.replace(/,/g, ''));
|
|
if (agentPrice % 1 === 0) {
|
|
const [, dec] = agentPrice4Show.split('.');
|
|
if (!dec) {
|
|
agentPrice4Show += '.00';
|
|
}
|
|
}
|
|
}
|
|
"
|
|
@update:model-value="
|
|
(v) => {
|
|
agentPrice4Show = commaInput(
|
|
v?.toString() || '0',
|
|
'string',
|
|
);
|
|
}
|
|
"
|
|
/>
|
|
<q-input
|
|
v-if="priceDisplay?.serviceCharge && props.rowIndex === 2"
|
|
id="input-service-charge"
|
|
for="input-service-charge"
|
|
:dense="dense"
|
|
outlined
|
|
:readonly="readonly"
|
|
:borderless="readonly"
|
|
input-class="text-right"
|
|
hide-bottom-space
|
|
:model-value="serviceCharge4Show"
|
|
@blur="
|
|
() => {
|
|
serviceCharge = Number(
|
|
serviceCharge4Show.replace(/,/g, ''),
|
|
);
|
|
if (serviceCharge % 1 === 0) {
|
|
const [, dec] = serviceCharge4Show.split('.');
|
|
if (!dec) {
|
|
serviceCharge4Show += '.00';
|
|
}
|
|
}
|
|
}
|
|
"
|
|
@update:model-value="
|
|
(v) => {
|
|
serviceCharge4Show = commaInput(
|
|
v?.toString() || '0',
|
|
'string',
|
|
);
|
|
}
|
|
"
|
|
/>
|
|
</q-td>
|
|
<q-td class="text-right" style="width: 15%">
|
|
{{
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'beforeVat',
|
|
vatIncluded: vatIncluded,
|
|
price:
|
|
(props.rowIndex === 0
|
|
? price
|
|
: props.rowIndex === 1
|
|
? agentPrice
|
|
: serviceCharge) || 0,
|
|
}),
|
|
2,
|
|
)
|
|
}}
|
|
</q-td>
|
|
<q-td class="text-right" style="width: 15%">
|
|
{{
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'vat',
|
|
calcVat: calcVat,
|
|
price: Number(
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'beforeVat',
|
|
vatIncluded: vatIncluded,
|
|
price:
|
|
(props.rowIndex === 0
|
|
? price
|
|
: props.rowIndex === 1
|
|
? agentPrice
|
|
: serviceCharge) || 0,
|
|
}),
|
|
2,
|
|
).replaceAll(',', ''),
|
|
),
|
|
}),
|
|
2,
|
|
)
|
|
}}
|
|
</q-td>
|
|
<q-td class="text-right" style="width: 15%">
|
|
<span
|
|
class="text-weight-bold"
|
|
:class="{
|
|
'tags-color-orange': props.rowIndex === 0,
|
|
'tags-color-purple': props.rowIndex === 1,
|
|
'tags-color-pink': props.rowIndex === 2,
|
|
dark: $q.dark.isActive,
|
|
}"
|
|
>
|
|
{{
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'total',
|
|
vat: 0.03,
|
|
price:
|
|
(props.rowIndex === 0
|
|
? price
|
|
: props.rowIndex === 1
|
|
? agentPrice
|
|
: serviceCharge) || 0,
|
|
}) +
|
|
(!vatIncluded
|
|
? calculatePrice({
|
|
output: 'vat',
|
|
calcVat: calcVat,
|
|
price:
|
|
(props.rowIndex === 0
|
|
? price
|
|
: props.rowIndex === 1
|
|
? agentPrice
|
|
: serviceCharge) || 0,
|
|
})
|
|
: 0),
|
|
2,
|
|
)
|
|
}}
|
|
</span>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.active-addr {
|
|
color: hsl(var(--info-bg));
|
|
background-color: hsla(var(--info-bg) / 0.1);
|
|
border-radius: var(--radius-3);
|
|
|
|
&.dark {
|
|
background-color: var(--surface-1);
|
|
}
|
|
}
|
|
|
|
.tags-color-orange {
|
|
color: var(--orange-5);
|
|
}
|
|
|
|
.dark .tags-color-orange {
|
|
color: var(--orange-6);
|
|
}
|
|
|
|
.tags-color-purple {
|
|
color: var(--violet-11);
|
|
}
|
|
|
|
.dark .tags-color-purple {
|
|
color: var(--violet-10);
|
|
}
|
|
|
|
.tags-color-pink {
|
|
color: var(--pink-6);
|
|
}
|
|
</style>
|