jws-frontend/src/components/04_product-service/PriceDataComponent.vue
2025-02-17 11:49:49 +07:00

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>