jws-frontend/src/components/05_quotation/ProductItem.vue
2024-10-07 14:13:07 +07:00

320 lines
7.6 KiB
Vue

<script lang="ts" setup>
import { computed, watch } from 'vue';
import { precisionRound } from 'src/utils/arithmetic';
import ThaiBahtText from 'thai-baht-text';
import { toWords } from 'number-to-words';
import TableComponents from 'src/components/TableComponents.vue';
import { QuotationPayload } from 'src/stores/quotations/types';
import { formatNumberDecimal } from 'stores/utils';
import { QTableProps } from 'quasar';
defineProps<{
agentPrice: boolean;
}>();
defineEmits<{
(e: 'delete', index: number): void;
}>();
const rows = defineModel<
Required<QuotationPayload['productServiceList'][number]>[]
>('rows', { required: true });
const summaryPrice = defineModel<{
totalPrice: number;
totalDiscount: number;
vat: number;
finalPrice: number;
}>('summaryPrice', {
required: true,
default: {
totalPrice: 0,
totalDiscount: 0,
vat: 0,
finalPrice: 0,
},
});
function calcPrice(c: (typeof rows.value)[number]) {
const price = precisionRound(c.pricePerUnit * c.amount);
const discount = precisionRound(price * (c.discount || 0));
const vat = precisionRound((price - discount) * c.vat);
return precisionRound(price - discount + vat);
}
const summary = computed(() =>
rows.value.reduce(
(a, c) => {
const price = precisionRound(c.pricePerUnit * c.amount);
const discount = precisionRound(price - (c.discount || 0));
const vat = precisionRound((price - discount) * c.vat);
a.totalPrice = precisionRound(a.totalPrice + price);
a.totalDiscount = precisionRound(a.totalDiscount + discount);
a.vat = precisionRound(a.vat + vat);
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
return a;
},
{
totalPrice: 0,
totalDiscount: 0,
vat: 0,
finalPrice: 0,
},
),
);
const columns = [
{
name: 'order',
align: 'center',
label: 'general.order',
field: 'order',
},
{
name: 'code',
align: 'center',
label: 'productService.group.code',
field: (v) => v.product.code,
},
{
name: 'name',
align: 'left',
label: 'productService.service.list',
field: (v) => v.product.name,
},
{
name: 'amount',
align: 'center',
label: 'general.amount',
field: 'amount',
},
{
name: 'pricePerUnit',
align: 'right',
label: 'quotation.pricePerUnit',
field: 'pricePerUnit',
},
{
name: 'discount',
align: 'center',
label: 'general.discount',
field: 'discount',
},
{
name: 'tax',
align: 'center',
label: 'quotation.tax',
field: 'tax',
},
{
name: 'sumPrice',
align: 'right',
label: 'quotation.sumPrice',
field: 'sumPrice',
},
{
name: 'action',
align: 'left',
label: '',
field: 'action',
},
] satisfies QTableProps['columns'];
const EngBahtText = (number: number) => {
const [baht, satang] = number.toString().split('.');
return `${toWords(baht)} Baht${satang && ` and ${toWords(satang)} Satang`}`;
};
watch(
() => summary.value,
() => {
summaryPrice.value = summary.value;
},
);
</script>
<template>
<div class="column">
<div class="full-width">
<TableComponents
flat
bordered
hidePagination
button-delete
:columns="columns"
:rows="rows"
:customColumn="[
'name',
'amount',
'pricePerUnit',
'discount',
'tax',
'sumPrice',
]"
@delete="(i) => $emit('delete', i)"
>
<template v-slot:body-cell-name="{ props }">
<q-td>
<q-avatar class="q-mr-sm" size="md">
<q-icon
class="full-width full-height"
name="mdi-shopping-outline"
:style="`color: var(--teal-10); background: hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`"
/>
</q-avatar>
{{ props.row.product.name }}
</q-td>
</template>
<template v-slot:body-cell-amount="{ props }">
<q-td align="center">
<q-input
dense
outlined
type="number"
style="width: 70px"
min="0"
v-model="props.row.amount"
/>
</q-td>
</template>
<template v-slot:body-cell-pricePerUnit="{ props }">
<q-td align="right">
{{
formatNumberDecimal(
(props.row.pricePerUnit = agentPrice
? props.row.product.agentPrice
: props.row.product.price),
2,
)
}}
</q-td>
</template>
<template v-slot:body-cell-discount="{ props }">
<q-td align="center">
<q-input
dense
min="0"
outlined
type="number"
style="width: 70px"
v-model="props.row.discount"
/>
</q-td>
</template>
<template v-slot:body-cell-tax="{ props }">
<q-td align="center">
<q-input
dense
min="0"
outlined
type="number"
readonly
class="bordered rounded"
style="width: 70px"
v-model="props.row.vat"
>
<template v-slot:append>
<span class="text-caption no-padding">%</span>
</template>
</q-input>
</q-td>
</template>
<template v-slot:body-cell-sumPrice="{ props }">
<q-td align="right">
{{ formatNumberDecimal(calcPrice(props.row), 2) }}
</q-td>
</template>
<template #button>
<q-btn
icon="mdi-monitor"
size="sm"
class="rounded q-mr-xs"
padding="4px 8px"
dense
flat
style="
background-color: hsla(var(--positive-bg) / 0.1);
color: hsl(var(--positive-bg));
"
/>
</template>
</TableComponents>
</div>
<div
class="column q-ml-auto text-caption app-text-muted-2 q-pt-md"
style="width: 15vw"
>
<div class="row">
{{ $t('quotation.allProductPrice') }}
<span class="q-ml-auto">
{{ formatNumberDecimal(summary.totalPrice, 2) }} ฿
</span>
</div>
<div class="row">
{{ $t('general.discount') }}
<span class="q-ml-auto">
{{ formatNumberDecimal(summary.totalDiscount, 2) }} ฿
</span>
</div>
<div class="row">
{{ $t('quotation.tax') }}
<span class="q-ml-auto">
{{ formatNumberDecimal(summary.vat, 2) }} ฿
</span>
</div>
<q-separator spaced="md" />
<div class="row text-bold text-body2" style="color: var(--foreground)">
{{ $t('quotation.totalPrice') }}
<span class="q-ml-auto">
{{ formatNumberDecimal(summary.finalPrice, 2) }} ฿
</span>
</div>
</div>
<span
v-if="summary.finalPrice"
class="text-caption app-text-muted-2 flex self-end"
>
({{
$i18n.locale === 'eng'
? EngBahtText(summary.finalPrice)
: ThaiBahtText(summary.finalPrice)
}})
</span>
</div>
</template>
<style scoped>
.worker-item {
--side-color: var(--brand-1);
position: relative;
overflow-x: hidden;
&.worker-item__female {
--side-color: hsl(var(--gender-female));
}
&.worker-item__male {
--side-color: hsl(var(--gender-male));
}
}
.worker-item::before {
position: absolute;
content: ' ';
left: 0;
width: 7px;
top: 0;
bottom: 0;
background: var(--side-color);
}
</style>