429 lines
13 KiB
Vue
429 lines
13 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch } from 'vue';
|
|
import { formatNumberDecimal, commaInput } from 'stores/utils';
|
|
import { QTableProps, QTableSlots } 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 agentPriceVatIncluded = defineModel<boolean>('agentPriceVatIncluded');
|
|
const agentPriceCalcVat = defineModel<boolean>('agentPriceCalcVat');
|
|
const serviceChargeVatIncluded = defineModel<boolean>(
|
|
'serviceChargeVatIncluded',
|
|
);
|
|
const serviceChargeCalcVat = defineModel<boolean>('serviceChargeCalcVat');
|
|
|
|
const formattedPrice = ref<string>(commaInput(price.value?.toString() || '0'));
|
|
const formattedAgentPrice = ref<string>(
|
|
commaInput(agentPrice.value?.toString() || '0'),
|
|
);
|
|
const formattedServiceCharge = ref<string>(
|
|
commaInput(serviceCharge.value?.toString() || '0'),
|
|
);
|
|
|
|
type RowData = {
|
|
pricePerUnit: number;
|
|
calcVat: boolean;
|
|
vatIncluded: boolean;
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
name: 'label',
|
|
align: 'left',
|
|
label: 'productService.product.priceInformation',
|
|
field: 'label',
|
|
},
|
|
{
|
|
name: '#calcVat',
|
|
align: 'center',
|
|
label: 'general.calculateVat',
|
|
field: '#calcVat',
|
|
},
|
|
{
|
|
name: '#vatIncluded',
|
|
align: 'center',
|
|
label: 'productService.product.vatIncluded',
|
|
field: '#vatIncluded',
|
|
},
|
|
{
|
|
name: '#pricePerUnit',
|
|
align: 'center',
|
|
label: 'quotation.pricePerUnit',
|
|
field: 'pricePerUnit',
|
|
},
|
|
{
|
|
name: 'beforeVat',
|
|
align: 'right',
|
|
label: 'quotation.priceBeforeVat',
|
|
field: (data: RowData) =>
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'beforeVat',
|
|
vatIncluded: data.vatIncluded,
|
|
price: data.pricePerUnit || 0,
|
|
}),
|
|
2,
|
|
),
|
|
},
|
|
{
|
|
name: 'vat',
|
|
align: 'right',
|
|
label: 'general.vat',
|
|
field: (data: RowData) =>
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'vat',
|
|
calcVat: data.calcVat,
|
|
price: Number(
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'beforeVat',
|
|
vatIncluded: data.vatIncluded,
|
|
price: data.pricePerUnit,
|
|
}),
|
|
2,
|
|
).replaceAll(',', ''),
|
|
),
|
|
}),
|
|
2,
|
|
),
|
|
},
|
|
{
|
|
name: 'total',
|
|
align: 'right',
|
|
label: 'quotation.sumPrice',
|
|
field: (data: RowData) =>
|
|
formatNumberDecimal(
|
|
calculatePrice({
|
|
output: 'total',
|
|
vat: 0.07,
|
|
price: data.pricePerUnit,
|
|
}) +
|
|
(!data.vatIncluded
|
|
? calculatePrice({
|
|
output: 'vat',
|
|
calcVat: data.calcVat,
|
|
price: data.pricePerUnit,
|
|
})
|
|
: 0),
|
|
2,
|
|
),
|
|
},
|
|
] as QTableProps['columns'];
|
|
|
|
watch([calcVat, agentPriceCalcVat, serviceChargeCalcVat], () => {
|
|
if (calcVat.value === false) {
|
|
vatIncluded.value = false;
|
|
}
|
|
if (agentPriceCalcVat.value === false) {
|
|
agentPriceVatIncluded.value = false;
|
|
}
|
|
if (serviceChargeCalcVat.value === false) {
|
|
serviceChargeVatIncluded.value = false;
|
|
}
|
|
});
|
|
|
|
withDefaults(
|
|
defineProps<{
|
|
readonly?: boolean;
|
|
priceDisplay?: {
|
|
price: boolean;
|
|
agentPrice: boolean;
|
|
serviceCharge: boolean;
|
|
};
|
|
}>(),
|
|
{
|
|
priceDisplay: () => ({
|
|
price: true,
|
|
agentPrice: true,
|
|
serviceCharge: true,
|
|
}),
|
|
},
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="row col-12 full-width">
|
|
<div class="col-12 q-pb-sm row items-center">
|
|
<div :class="{ 'col-12': $q.screen.lt.sm }">
|
|
<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 q-pr-md text-weight-bold">
|
|
{{ $t('productService.product.priceInformation') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 full-width">
|
|
<q-table
|
|
:rows-per-page-options="[0]"
|
|
:rows="[
|
|
{
|
|
label: $t('productService.product.salePrice'),
|
|
pricePerUnit: price,
|
|
calcVat,
|
|
vatIncluded,
|
|
},
|
|
{
|
|
label: $t('productService.product.agentPrice'),
|
|
calcVat: agentPriceCalcVat,
|
|
vatIncluded: agentPriceVatIncluded,
|
|
pricePerUnit: agentPrice,
|
|
},
|
|
{
|
|
label: $t('productService.product.processingPrice'),
|
|
calcVat: serviceChargeCalcVat,
|
|
vatIncluded: serviceChargeVatIncluded,
|
|
pricePerUnit: serviceCharge,
|
|
},
|
|
]"
|
|
:columns
|
|
hide-bottom
|
|
bordered
|
|
flat
|
|
hide-pagination
|
|
selection="multiple"
|
|
class="full-width"
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr
|
|
style="background-color: hsla(var(--info-bg) / 0.07)"
|
|
:props="props"
|
|
>
|
|
<q-th v-for="col in columns" :key="col.name" :props="props">
|
|
<template v-if="!!col.label">
|
|
{{ $t(col.label) }}
|
|
</template>
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
|
|
<template
|
|
v-slot:body="props: {
|
|
row: RowData;
|
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
|
>
|
|
<q-tr :class="{ dark: $q.dark.isActive }" class="text-center">
|
|
<q-td v-for="(col, i) in columns" :align="col.align" :key="i">
|
|
<!-- NOTE: custom column will starts with # -->
|
|
<template v-if="!col.name.startsWith('#')">
|
|
<span
|
|
v-if="col.name === 'total'"
|
|
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,
|
|
}"
|
|
>
|
|
{{
|
|
typeof col.field === 'string'
|
|
? props.row[col.field as keyof RowData]
|
|
: col.field(props.row)
|
|
}}
|
|
</span>
|
|
<template v-else>
|
|
{{
|
|
typeof col.field === 'string'
|
|
? props.row[col.field as keyof RowData]
|
|
: col.field(props.row)
|
|
}}
|
|
</template>
|
|
</template>
|
|
<template v-if="col.name === '#calcVat'">
|
|
<q-checkbox
|
|
v-if="priceDisplay?.price && props.rowIndex === 0"
|
|
v-model="calcVat"
|
|
size="xs"
|
|
/>
|
|
<q-checkbox
|
|
v-if="priceDisplay?.agentPrice && props.rowIndex === 1"
|
|
v-model="agentPriceCalcVat"
|
|
size="xs"
|
|
/>
|
|
<q-checkbox
|
|
v-if="priceDisplay?.serviceCharge && props.rowIndex === 2"
|
|
v-model="serviceChargeCalcVat"
|
|
size="xs"
|
|
/>
|
|
</template>
|
|
<template v-if="col.name === '#vatIncluded'">
|
|
<q-select
|
|
for="select-vat-included"
|
|
:options="[
|
|
{ label: $t('general.included'), value: true },
|
|
{ label: $t('general.notIncluded'), value: false },
|
|
]"
|
|
v-if="priceDisplay?.price && props.rowIndex === 0"
|
|
map-options
|
|
emit-value
|
|
flat
|
|
outlined
|
|
dense
|
|
v-model="vatIncluded"
|
|
></q-select>
|
|
<q-select
|
|
:options="[
|
|
{ label: $t('general.included'), value: true },
|
|
{ label: $t('general.notIncluded'), value: false },
|
|
]"
|
|
v-if="priceDisplay?.agentPrice && props.rowIndex === 1"
|
|
for="select-vat-included"
|
|
map-options
|
|
emit-value
|
|
flat
|
|
outlined
|
|
dense
|
|
v-model="agentPriceVatIncluded"
|
|
></q-select>
|
|
<q-select
|
|
:options="[
|
|
{ label: $t('general.included'), value: true },
|
|
{ label: $t('general.notIncluded'), value: false },
|
|
]"
|
|
v-if="priceDisplay?.serviceCharge && props.rowIndex === 2"
|
|
for="select-vat-included"
|
|
map-options
|
|
emit-value
|
|
flat
|
|
outlined
|
|
dense
|
|
v-model="serviceChargeVatIncluded"
|
|
></q-select>
|
|
</template>
|
|
<template v-if="col.name === '#pricePerUnit'">
|
|
<q-input
|
|
v-if="priceDisplay?.price && props.rowIndex === 0"
|
|
id="input-price"
|
|
for="input-price"
|
|
dense
|
|
outlined
|
|
hide-bottom-space
|
|
input-class="text-right"
|
|
:readonly
|
|
:borderless="readonly"
|
|
:model-value="formattedPrice"
|
|
@blur="
|
|
() => {
|
|
price = Number(formattedPrice.replace(/,/g, ''));
|
|
if (price % 1 === 0 && !formattedPrice.split('.').at(1)) {
|
|
formattedPrice += '.00';
|
|
}
|
|
}
|
|
"
|
|
@update:model-value="
|
|
(v) => {
|
|
formattedPrice = commaInput(
|
|
v?.toString() || '0',
|
|
'string',
|
|
);
|
|
}
|
|
"
|
|
/>
|
|
<q-input
|
|
v-if="priceDisplay?.agentPrice && props.rowIndex === 1"
|
|
id="input-agent-price"
|
|
for="input-agent-price"
|
|
dense
|
|
outlined
|
|
hide-bottom-space
|
|
input-class="text-right"
|
|
:readonly
|
|
:borderless="readonly"
|
|
:model-value="formattedAgentPrice"
|
|
@blur="
|
|
() => {
|
|
agentPrice = Number(
|
|
formattedAgentPrice.replace(/,/g, ''),
|
|
);
|
|
if (
|
|
agentPrice % 1 === 0 &&
|
|
!formattedAgentPrice.split('.').at(1)
|
|
) {
|
|
formattedAgentPrice += '.00';
|
|
}
|
|
}
|
|
"
|
|
@update:model-value="
|
|
(v) => {
|
|
formattedAgentPrice = commaInput(
|
|
v?.toString() || '0',
|
|
'string',
|
|
);
|
|
}
|
|
"
|
|
/>
|
|
<q-input
|
|
v-if="priceDisplay?.serviceCharge && props.rowIndex === 2"
|
|
id="input-service-charge"
|
|
for="input-service-charge"
|
|
dense
|
|
outlined
|
|
input-class="text-right"
|
|
hide-bottom-space
|
|
:readonly
|
|
:borderless="readonly"
|
|
:model-value="formattedServiceCharge"
|
|
@blur="
|
|
() => {
|
|
serviceCharge = Number(
|
|
formattedServiceCharge.replace(/,/g, ''),
|
|
);
|
|
if (
|
|
serviceCharge % 1 === 0 &&
|
|
!formattedServiceCharge.split('.').at(1)
|
|
) {
|
|
formattedServiceCharge += '.00';
|
|
}
|
|
}
|
|
"
|
|
@update:model-value="
|
|
(v) => {
|
|
formattedServiceCharge = commaInput(
|
|
v?.toString() || '0',
|
|
'string',
|
|
);
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.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>
|