refactor: separate product expansion in credit note
This commit is contained in:
parent
c1afeee1da
commit
0be0ce4c49
3 changed files with 317 additions and 1 deletions
|
|
@ -19,7 +19,7 @@ import AdditionalFileExpansion from '../09_task-order/expansion/AdditionalFileEx
|
||||||
import PaymentExpansion from './expansion/PaymentExpansion.vue';
|
import PaymentExpansion from './expansion/PaymentExpansion.vue';
|
||||||
import CreditNoteExpansion from './expansion/CreditNoteExpansion.vue';
|
import CreditNoteExpansion from './expansion/CreditNoteExpansion.vue';
|
||||||
import StateButton from 'src/components/button/StateButton.vue';
|
import StateButton from 'src/components/button/StateButton.vue';
|
||||||
import ProductExpansion from '../09_task-order/expansion/ProductExpansion.vue';
|
import ProductExpansion from './expansion/ProductExpansion.vue';
|
||||||
import SelectReadyRequestWork from '../09_task-order/SelectReadyRequestWork.vue';
|
import SelectReadyRequestWork from '../09_task-order/SelectReadyRequestWork.vue';
|
||||||
import RefundInformation from './RefundInformation.vue';
|
import RefundInformation from './RefundInformation.vue';
|
||||||
import QuotationFormReceipt from '../05_quotation/QuotationFormReceipt.vue';
|
import QuotationFormReceipt from '../05_quotation/QuotationFormReceipt.vue';
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,60 @@ export const hslaColors: Record<string, string> = {
|
||||||
Pending: '--orange-5-hsl',
|
Pending: '--orange-5-hsl',
|
||||||
Success: '--blue-6-hsl',
|
Success: '--blue-6-hsl',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const productColumn = [
|
||||||
|
{
|
||||||
|
name: 'order',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.order',
|
||||||
|
field: 'no',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
align: 'center',
|
||||||
|
label: 'productService.product.code',
|
||||||
|
field: 'code',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'productList',
|
||||||
|
align: 'center',
|
||||||
|
label: 'taskOrder.productList',
|
||||||
|
field: 'productList',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amountOfEmployee',
|
||||||
|
align: 'center',
|
||||||
|
label: 'taskOrder.amountOfEmployee',
|
||||||
|
field: 'amountOfEmployee',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pricePerUnit',
|
||||||
|
align: 'center',
|
||||||
|
label: 'quotation.pricePerUnit',
|
||||||
|
field: 'pricePerUnit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'discount',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.discount',
|
||||||
|
field: 'discount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priceBeforeVat',
|
||||||
|
align: 'center',
|
||||||
|
label: 'quotation.priceBeforeVat',
|
||||||
|
field: 'priceBeforeVat',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vat',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.vat',
|
||||||
|
field: 'vat',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalPriceBaht',
|
||||||
|
align: 'center',
|
||||||
|
label: 'quotation.totalPriceBaht',
|
||||||
|
field: 'totalPriceBaht',
|
||||||
|
},
|
||||||
|
] as const satisfies QTableProps['columns'];
|
||||||
|
|
|
||||||
259
src/pages/11_credit-note/expansion/ProductExpansion.vue
Normal file
259
src/pages/11_credit-note/expansion/ProductExpansion.vue
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { QTableSlots } from 'quasar';
|
||||||
|
|
||||||
|
import { AddButton } from 'src/components/button';
|
||||||
|
import TableEmployee from '../../09_task-order/TableEmployee.vue';
|
||||||
|
|
||||||
|
import { RequestWork } from 'src/stores/request-list';
|
||||||
|
import { productColumn } from '../constants';
|
||||||
|
import { baseUrl, formatNumberDecimal } from 'src/stores/utils';
|
||||||
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
|
import { useConfigStore } from 'stores/config';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
const currentExpanded = ref<boolean[]>([]);
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
const { data: config } = storeToRefs(configStore);
|
||||||
|
|
||||||
|
const taskProduct = defineModel<{ productId: string; discount?: number }[]>(
|
||||||
|
'taskProduct',
|
||||||
|
{
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
readonly?: boolean;
|
||||||
|
agentPrice?: boolean;
|
||||||
|
taskList: {
|
||||||
|
product: RequestWork['productService']['product'];
|
||||||
|
list: RequestWork[];
|
||||||
|
}[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'addProduct'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
calcPricePerUnit,
|
||||||
|
});
|
||||||
|
|
||||||
|
function openList(index: number) {
|
||||||
|
if (!currentExpanded.value[index]) {
|
||||||
|
currentExpanded.value.map((_, i) => {
|
||||||
|
if (i !== index) currentExpanded.value[i] = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentExpanded.value[index] = !currentExpanded.value[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPricePerUnit(product: RequestWork['productService']['product']) {
|
||||||
|
const val = props.agentPrice ? product.agentPrice : product.price;
|
||||||
|
|
||||||
|
if (product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']) {
|
||||||
|
return val / (1 + (config.value?.vat || 0.07));
|
||||||
|
} else {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPrice(
|
||||||
|
product: RequestWork['productService']['product'],
|
||||||
|
amount: number,
|
||||||
|
) {
|
||||||
|
const pricePerUnit = props.agentPrice ? product.agentPrice : product.price;
|
||||||
|
const discount =
|
||||||
|
taskProduct.value.find((v) => v.productId === product.id)?.discount || 0;
|
||||||
|
const priceNoVat = product[
|
||||||
|
props.agentPrice ? 'agentPriceVatIncluded' : 'vatIncluded'
|
||||||
|
]
|
||||||
|
? pricePerUnit / (1 + (config.value?.vat || 0.07))
|
||||||
|
: pricePerUnit;
|
||||||
|
const priceDiscountNoVat = priceNoVat * amount - discount;
|
||||||
|
|
||||||
|
const rawVatTotal = product[
|
||||||
|
props.agentPrice ? 'agentPriceCalcVat' : 'calcVat'
|
||||||
|
]
|
||||||
|
? priceDiscountNoVat * (config.value?.vat || 0.07)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return precisionRound(priceNoVat * amount + rawVatTotal);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-expansion-item
|
||||||
|
dense
|
||||||
|
class="overflow-hidden bordered full-width"
|
||||||
|
switch-toggle-side
|
||||||
|
style="border-radius: var(--radius-2)"
|
||||||
|
expand-icon="mdi-chevron-down-circle"
|
||||||
|
header-class="surface-1 q-py-sm text-medium text-body1"
|
||||||
|
default-opened
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span
|
||||||
|
class="row items-center justify-between full-width"
|
||||||
|
style="min-height: 31.01px"
|
||||||
|
>
|
||||||
|
{{ $t('general.information', { msg: $t('taskOrder.productList') }) }}
|
||||||
|
<AddButton
|
||||||
|
icon-only
|
||||||
|
@click.stop="$emit('addProduct')"
|
||||||
|
v-if="!readonly"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<main class="q-px-md q-py-sm surface-1">
|
||||||
|
<q-table
|
||||||
|
:columns="
|
||||||
|
productColumn.filter(
|
||||||
|
(v) =>
|
||||||
|
v.name !== 'discount' &&
|
||||||
|
v.name !== 'priceBeforeVat' &&
|
||||||
|
v.name !== 'vat',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:rows="taskList"
|
||||||
|
bordered
|
||||||
|
flat
|
||||||
|
hide-pagination
|
||||||
|
card-container-class="q-col-gutter-sm"
|
||||||
|
class="full-width"
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
: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) }}
|
||||||
|
{{ col.label === 'quotation.vat' ? '%' : '' }}
|
||||||
|
</q-th>
|
||||||
|
<q-th></q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template
|
||||||
|
v-slot:body="props: {
|
||||||
|
row: {
|
||||||
|
product: RequestWork['productService']['product'];
|
||||||
|
list: RequestWork[];
|
||||||
|
};
|
||||||
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||||
|
>
|
||||||
|
<q-tr class="text-center">
|
||||||
|
<q-td>
|
||||||
|
{{ props.rowIndex + 1 }}
|
||||||
|
</q-td>
|
||||||
|
<q-td>
|
||||||
|
{{ props.row.product.code }}
|
||||||
|
</q-td>
|
||||||
|
<q-td style="width: 100%" class="text-left">
|
||||||
|
<q-avatar class="q-mr-sm" size="md">
|
||||||
|
<q-img
|
||||||
|
class="text-center"
|
||||||
|
:ratio="1"
|
||||||
|
:src="`${baseUrl}/product/${props.row.product.id}/image/${props.row.product.selectedImage}`"
|
||||||
|
>
|
||||||
|
<template #error>
|
||||||
|
<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)`"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-img>
|
||||||
|
</q-avatar>
|
||||||
|
{{ props.row.product.name }}
|
||||||
|
</q-td>
|
||||||
|
<q-td>
|
||||||
|
{{ props.row.list.length }}
|
||||||
|
</q-td>
|
||||||
|
<q-td class="text-right">
|
||||||
|
{{
|
||||||
|
formatNumberDecimal(
|
||||||
|
calcPricePerUnit(props.row.product) +
|
||||||
|
(props.row.product.calcVat
|
||||||
|
? calcPricePerUnit(props.row.product) *
|
||||||
|
(config?.vat || 0.07)
|
||||||
|
: 0),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</q-td>
|
||||||
|
|
||||||
|
<!-- total -->
|
||||||
|
<q-td class="text-right">
|
||||||
|
{{
|
||||||
|
formatNumberDecimal(
|
||||||
|
calcPrice(props.row.product, props.row.list.length),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</q-td>
|
||||||
|
<q-td>
|
||||||
|
<q-btn
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
class="rounded"
|
||||||
|
@click.stop="openList(props.rowIndex)"
|
||||||
|
>
|
||||||
|
<div class="row items-center no-wrap">
|
||||||
|
<q-icon name="mdi-account-group-outline" />
|
||||||
|
<q-icon
|
||||||
|
class="btn-arrow-right"
|
||||||
|
:class="{
|
||||||
|
active: currentExpanded[props.rowIndex],
|
||||||
|
}"
|
||||||
|
size="xs"
|
||||||
|
:name="`mdi-chevron-${currentExpanded[props.rowIndex] ? 'down' : 'up'}`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-btn>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
|
||||||
|
<q-tr v-show="currentExpanded[props.rowIndex]" :props="props">
|
||||||
|
<q-td colspan="100%" style="padding: 16px">
|
||||||
|
<TableEmployee :rows="props.row.list" />
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
|
||||||
|
<div class="q-pt-md row items-center">
|
||||||
|
<span class="q-ml-auto q-mr-sm">
|
||||||
|
{{
|
||||||
|
$t('general.numberOf', {
|
||||||
|
msg: $t('productService.product.product'),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<div class="surface-3 q-px-sm rounded">{{ taskList.length }}</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</q-expansion-item>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.product-status {
|
||||||
|
padding-left: 8px;
|
||||||
|
border-radius: 20px;
|
||||||
|
color: hsl(var(--_color));
|
||||||
|
background: hsla(var(--_color) / 0.15);
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
--_color: var(--warning-bg);
|
||||||
|
}
|
||||||
|
&.positive {
|
||||||
|
--_color: var(--positive-bg);
|
||||||
|
}
|
||||||
|
&.negative {
|
||||||
|
--_color: var(--negative-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue