Compare commits
98 commits
version-0.
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65dcd138db | ||
| e6d06b39da | |||
| 3cab6cc0e5 | |||
|
|
9994366c74 | ||
|
|
f4db5ad855 | ||
|
|
15a812b50e | ||
|
|
f7a8416e7a | ||
|
|
79d6482caa | ||
|
|
75d5c7dfe8 | ||
|
|
637eeab3c2 | ||
| 2b1e3b12a4 | |||
|
|
a1ed625d32 | ||
|
|
59a3f964c4 | ||
|
|
2afb5ea7e9 | ||
| aaf776639d | |||
| 04c463a717 | |||
| 8e65a1c5a2 | |||
| 21fc2d5d96 | |||
| 73f43c2a29 | |||
| 16ea66484d | |||
| 90f31a0c87 | |||
| d1785faed2 | |||
| f68e8cf675 | |||
| cd4b087fec | |||
|
|
8a0340f588 | ||
| 2b9c8aa613 | |||
|
|
eebd585554 | ||
|
|
db5262da42 | ||
|
|
72b0e89642 | ||
|
|
2320883cb6 | ||
|
|
0e6bee7b62 | ||
| 5c867a496d | |||
|
|
d06c26c3c8 | ||
|
|
c4f088c5cb | ||
| 6cf8cf28aa | |||
|
|
1249f67a0f | ||
|
|
05d38b1ab3 | ||
|
|
d09484a52a | ||
|
|
d8d02a679d | ||
|
|
11047e569d | ||
|
|
f3b5b25bf3 | ||
|
|
e817e8fd05 | ||
|
|
18e5517325 | ||
|
|
5e13864d4a | ||
| 61dca12e5a | |||
| aa908f0c3d | |||
| 80056f8e0b | |||
|
|
02bb682150 | ||
| d2acd6ba4c | |||
| d53eb15a88 | |||
|
|
35e23aa291 | ||
|
|
00f9b5f4c4 | ||
|
|
7846950802 | ||
|
|
ef8e294ae4 | ||
| 016a54e45e | |||
|
|
4e887fdff8 | ||
| ded56d103b | |||
|
|
0d708405f6 | ||
| 2099031fa8 | |||
|
|
fd12f32ab0 | ||
| f10103b5d0 | |||
|
|
d6e366f788 | ||
| 52c384f0fb | |||
|
|
fcafaeebc0 | ||
|
|
0e57a3daf6 | ||
|
|
0ad017309f | ||
| b9cfb6274b | |||
|
|
5becbae369 | ||
| 7c3a9818c2 | |||
| 56f0a86845 | |||
| 492f341e68 | |||
|
|
49897ff007 | ||
| c430b6082e | |||
|
|
e6f8870cdf | ||
|
|
67cde37e34 | ||
|
|
763ac07be7 | ||
|
|
c07efa7318 | ||
|
|
507141dca5 | ||
| 73b2d52fb0 | |||
|
|
e6ecd39d24 | ||
|
|
c0a2d3769d | ||
|
|
05f7c886d6 | ||
|
|
93c54c0dd1 | ||
|
|
ef4c84341c | ||
| 09b51d601e | |||
| c29e1d4ec5 | |||
|
|
d89925dee9 | ||
|
|
3e24a46f66 | ||
|
|
764d9bab3f | ||
|
|
e5b2114984 | ||
|
|
37c9f5fcd5 | ||
|
|
67a69b85e0 | ||
|
|
eb88cc4269 | ||
|
|
ec780f2018 | ||
|
|
31b4daf42b | ||
|
|
5fad663a6e | ||
|
|
d4a9be9236 | ||
|
|
e33191dcd4 |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 7.4 KiB |
|
|
@ -293,15 +293,11 @@ watch(
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:label="$t('form.birthDate')"
|
:label="$t('form.birthDate')"
|
||||||
:disabled-dates="disabledAfterToday"
|
:disabled-dates="disabledAfterToday"
|
||||||
:rules="
|
:rules="[
|
||||||
employee
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
(val: string) =>
|
(val: string) =>
|
||||||
!!val ||
|
!!val ||
|
||||||
$t('form.error.selectField', { field: $t('form.birthDate') }),
|
$t('form.error.selectField', { field: $t('form.birthDate') }),
|
||||||
]
|
]"
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
|
|
|
||||||
1656
src/components/03_customer-management/DialogEmployee.vue
Normal file
1772
src/components/03_customer-management/DrawerEmployee.vue
Normal file
|
|
@ -20,8 +20,8 @@ const issuePlace = defineModel<string>('issuePlace');
|
||||||
const issueCountry = defineModel<string>('issueCountry');
|
const issueCountry = defineModel<string>('issueCountry');
|
||||||
const issueDate = defineModel<Date | null | string>('issueDate');
|
const issueDate = defineModel<Date | null | string>('issueDate');
|
||||||
const type = defineModel<string>('type');
|
const type = defineModel<string>('type');
|
||||||
const expireDate = defineModel<Date>('expireDate');
|
const expireDate = defineModel<Date | string>('expireDate');
|
||||||
const birthDate = defineModel<Date>('birthDate');
|
const birthDate = defineModel<Date | string>('birthDate');
|
||||||
const workerStatus = defineModel<string>('workerStatus');
|
const workerStatus = defineModel<string>('workerStatus');
|
||||||
const nationality = defineModel<string>('nationality');
|
const nationality = defineModel<string>('nationality');
|
||||||
const gender = defineModel<string>('gender');
|
const gender = defineModel<string>('gender');
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ const arrivalAt = defineModel<string>('arrivalAt');
|
||||||
const arrivalTMNo = defineModel<string>('arrivalTmNo');
|
const arrivalTMNo = defineModel<string>('arrivalTmNo');
|
||||||
const arrivalTM = defineModel<string>('arrivalTm');
|
const arrivalTM = defineModel<string>('arrivalTm');
|
||||||
const mrz = defineModel<string>('mrz');
|
const mrz = defineModel<string>('mrz');
|
||||||
const entryCount = defineModel<number>('entryCount');
|
const entryCount = defineModel<number | string>('entryCount');
|
||||||
const issuePlace = defineModel<string>('issuePlace');
|
const issuePlace = defineModel<string>('issuePlace');
|
||||||
const issueCountry = defineModel<string>('issueCountry');
|
const issueCountry = defineModel<string>('issueCountry');
|
||||||
const issueDate = defineModel<Date | null | string>('visaIssueDate');
|
const issueDate = defineModel<Date | null | string>('visaIssueDate');
|
||||||
const type = defineModel<string>('type');
|
const type = defineModel<string>('type');
|
||||||
const expireDate = defineModel<Date>('expireDate');
|
const expireDate = defineModel<Date | string>('expireDate');
|
||||||
const remark = defineModel<string>('remark');
|
const remark = defineModel<string>('remark');
|
||||||
const workerType = defineModel<string>('workerType');
|
const workerType = defineModel<string>('workerType');
|
||||||
const number = defineModel<string>('number');
|
const number = defineModel<string>('number');
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,9 @@ defineEmits<{
|
||||||
<q-avatar size="md">
|
<q-avatar size="md">
|
||||||
<q-img
|
<q-img
|
||||||
:src="
|
:src="
|
||||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
props.row.selectedImage
|
||||||
`/images/employee-avatar-${props.row.gender}.png`
|
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
|
||||||
|
: `/images/employee-avatar-${props.row.gender}.png`
|
||||||
"
|
"
|
||||||
class="text-center"
|
class="text-center"
|
||||||
:ratio="1"
|
:ratio="1"
|
||||||
|
|
@ -295,9 +296,9 @@ defineEmits<{
|
||||||
$i18n.locale === 'eng'
|
$i18n.locale === 'eng'
|
||||||
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
|
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
|
||||||
: `${props.row.firstName} ${props.row.lastName} `.trim(),
|
: `${props.row.firstName} ${props.row.lastName} `.trim(),
|
||||||
img:
|
img: props.row.selectedImage
|
||||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
|
||||||
`/images/employee-avatar-${props.row.gender}.png`,
|
: `/images/employee-avatar-${props.row.gender}.png`,
|
||||||
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
|
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
|
||||||
male: props.row.gender === 'male',
|
male: props.row.gender === 'male',
|
||||||
female: props.row.gender === 'female',
|
female: props.row.gender === 'female',
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,18 @@
|
||||||
import SelectCustomer from '../shared/select/SelectCustomer.vue';
|
import SelectCustomer from '../shared/select/SelectCustomer.vue';
|
||||||
import SelectBranch from '../shared/select/SelectBranch.vue';
|
import SelectBranch from '../shared/select/SelectBranch.vue';
|
||||||
|
|
||||||
|
import { CustomerBranch } from 'src/stores/customer';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const branchId = defineModel<string>('branchId');
|
const branchId = defineModel<string>('branchId');
|
||||||
const customerBranchId = defineModel<string>('customerBranchId');
|
const customerBranchId = defineModel<string>('customerBranchId');
|
||||||
const agentPrice = defineModel<boolean>('agentPrice');
|
const agentPrice = defineModel<boolean>('agentPrice');
|
||||||
const special = defineModel<boolean>('special');
|
const special = defineModel<boolean>('special');
|
||||||
|
|
||||||
|
const customerBranchOption = defineModel<CustomerBranch>(
|
||||||
|
'customerBranchOption',
|
||||||
|
);
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
outlined?: boolean;
|
outlined?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
|
@ -67,10 +74,12 @@ defineEmits<{
|
||||||
required
|
required
|
||||||
:readonly
|
:readonly
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectCustomer
|
<SelectCustomer
|
||||||
id="about-select-customer-branch-id"
|
id="about-select-customer-branch-id"
|
||||||
for="about-select-customer-branch-id"
|
for="about-select-customer-branch-id"
|
||||||
v-model:value="customerBranchId"
|
v-model:value="customerBranchId"
|
||||||
|
v-model:value-option="customerBranchOption"
|
||||||
:label="$t('quotation.customer')"
|
:label="$t('quotation.customer')"
|
||||||
:creatable-disabled-text="`(${$t('form.error.selectField', {
|
:creatable-disabled-text="`(${$t('form.error.selectField', {
|
||||||
field: $t('quotation.branchVirtual'),
|
field: $t('quotation.branchVirtual'),
|
||||||
|
|
|
||||||
|
|
@ -58,16 +58,15 @@ const currentBtnOpen = ref<{ title: string; opened: boolean[] }[]>([
|
||||||
|
|
||||||
function calcPrice(c: (typeof rows.value)[number]) {
|
function calcPrice(c: (typeof rows.value)[number]) {
|
||||||
const originalPrice = c.pricePerUnit;
|
const originalPrice = c.pricePerUnit;
|
||||||
const finalPriceWithVat = precisionRound(
|
const finalPricePerUnit = precisionRound(
|
||||||
originalPrice + originalPrice * (config.value?.vat || 0.07),
|
originalPrice +
|
||||||
|
(c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
||||||
|
? originalPrice * (config.value?.vat || 0.07)
|
||||||
|
: 0),
|
||||||
);
|
);
|
||||||
const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
const price = finalPricePerUnit * c.amount - c.discount;
|
||||||
|
|
||||||
const price = finalPriceNoVat * c.amount - c.discount;
|
return precisionRound(price);
|
||||||
const vat = c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
|
||||||
? (finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07)
|
|
||||||
: 0;
|
|
||||||
return precisionRound(price + vat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const discount4Show = ref<string[]>([]);
|
const discount4Show = ref<string[]>([]);
|
||||||
|
|
@ -435,8 +434,20 @@ watch(
|
||||||
<q-td align="right">
|
<q-td align="right">
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
|
props.row.product[
|
||||||
|
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
|
||||||
|
]
|
||||||
|
? precisionRound(
|
||||||
|
(props.row.pricePerUnit *
|
||||||
|
(1 + (config?.vat || 0.07)) *
|
||||||
|
props.row.amount -
|
||||||
|
props.row.discount) /
|
||||||
|
(1 + (config?.vat || 0.07)),
|
||||||
|
)
|
||||||
|
: precisionRound(
|
||||||
props.row.pricePerUnit * props.row.amount -
|
props.row.pricePerUnit * props.row.amount -
|
||||||
props.row.discount,
|
props.row.discount,
|
||||||
|
),
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
@ -448,9 +459,12 @@ watch(
|
||||||
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
|
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
|
||||||
]
|
]
|
||||||
? precisionRound(
|
? precisionRound(
|
||||||
(props.row.pricePerUnit * props.row.amount -
|
((props.row.pricePerUnit *
|
||||||
props.row.discount) *
|
(1 + (config?.vat || 0.07)) *
|
||||||
(config?.vat || 0.07),
|
props.row.amount -
|
||||||
|
props.row.discount) /
|
||||||
|
(1 + (config?.vat || 0.07))) *
|
||||||
|
0.07,
|
||||||
)
|
)
|
||||||
: 0,
|
: 0,
|
||||||
2,
|
2,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { QTableProps } from 'quasar';
|
import { QTableProps } from 'quasar';
|
||||||
import { dateFormat } from 'src/utils/datetime';
|
import { dateFormat, dateFormatJS } from 'src/utils/datetime';
|
||||||
|
|
||||||
import { formatNumberDecimal } from 'stores/utils';
|
import { formatNumberDecimal } from 'stores/utils';
|
||||||
|
|
||||||
|
|
@ -86,11 +86,11 @@ defineEmits<{
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td v-if="visibleColumns.includes('createdAt')">
|
<q-td v-if="visibleColumns.includes('createdAt')">
|
||||||
{{ dateFormat(props.row.createdAt) }}
|
{{ dateFormatJS({ date: props.row.createdAt }) }}
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td v-if="visibleColumns.includes('dueDate')">
|
<q-td v-if="visibleColumns.includes('dueDate')">
|
||||||
{{ dateFormat(props.row.dueDate) }}
|
{{ dateFormatJS({ date: props.row.dueDate }) }}
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td v-if="visibleColumns.includes('contactName')">
|
<q-td v-if="visibleColumns.includes('contactName')">
|
||||||
|
|
|
||||||
|
|
@ -27,26 +27,38 @@ withDefaults(
|
||||||
class="app-text-muted q-pr-sm"
|
class="app-text-muted q-pr-sm"
|
||||||
:width="iconSize || '2rem'"
|
:width="iconSize || '2rem'"
|
||||||
/>
|
/>
|
||||||
<span class="row col">
|
<span :id="`dd-wrapper-${label}`" class="row col">
|
||||||
<span class="col-12 app-text-muted-2" style="font-size: 10px">
|
<span
|
||||||
|
:id="`dd-label-${label}`"
|
||||||
|
class="col-12 app-text-muted-2"
|
||||||
|
style="font-size: 10px"
|
||||||
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</span>
|
</span>
|
||||||
<span class="col-12 ellipsis">
|
<span :id="`dd-value-wrapper-${label}`" class="col-12 ellipsis">
|
||||||
<slot name="value">
|
<slot name="value">
|
||||||
<span
|
<span
|
||||||
:class="{ 'link cursor-pointer': clickable }"
|
:class="{ 'link cursor-pointer': clickable }"
|
||||||
v-if="typeof value === 'string'"
|
v-if="typeof value === 'string'"
|
||||||
@click="clickable ? $emit('labelClick', value, null) : undefined"
|
@click="clickable ? $emit('labelClick', value, null) : undefined"
|
||||||
|
:id="`link-${value}`"
|
||||||
|
:for="`link-${value}`"
|
||||||
>
|
>
|
||||||
{{ value }}
|
{{ value }}
|
||||||
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
|
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span v-else :class="{ 'link cursor-pointer': clickable }">
|
<span
|
||||||
|
:id="`dd-value-${label}`"
|
||||||
|
v-else
|
||||||
|
:class="{ 'link cursor-pointer': clickable }"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
v-for="(item, index) in value"
|
v-for="(item, index) in value"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="$emit('labelClick', item, index)"
|
@click="$emit('labelClick', item, index)"
|
||||||
class="link cursor-pointer"
|
class="link cursor-pointer"
|
||||||
|
:id="`link-${item}`"
|
||||||
|
:for="`link-${item}`"
|
||||||
>
|
>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
<span v-if="index < value.length - 1">, </span>
|
<span v-if="index < value.length - 1">, </span>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
UndoButton,
|
UndoButton,
|
||||||
} from 'components/button';
|
} from 'components/button';
|
||||||
|
|
||||||
withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title: string;
|
title: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
|
|
@ -42,6 +42,11 @@ const drawerOpen = defineModel<boolean>('drawerOpen', {
|
||||||
const myForm = ref();
|
const myForm = ref();
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
if (props.beforeClose) {
|
||||||
|
drawerOpen.value = props.beforeClose
|
||||||
|
? props.beforeClose()
|
||||||
|
: !drawerOpen.value;
|
||||||
|
}
|
||||||
if (myForm.value) {
|
if (myForm.value) {
|
||||||
myForm.value.resetValidation();
|
myForm.value.resetValidation();
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +67,6 @@ async function onValidationError(ref: any) {
|
||||||
@show="show"
|
@show="show"
|
||||||
@before-hide="reset"
|
@before-hide="reset"
|
||||||
@hide="close"
|
@hide="close"
|
||||||
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
|
|
||||||
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
|
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
|
||||||
v-model="drawerOpen"
|
v-model="drawerOpen"
|
||||||
behavior="mobile"
|
behavior="mobile"
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,9 @@ defineEmits<{
|
||||||
expandedTree[expandedTree.length - 1] === node.id,
|
expandedTree[expandedTree.length - 1] === node.id,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ node.name }}
|
{{
|
||||||
|
$i18n.locale === 'eng' ? node.nameEN || node.name : node.name
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span class="app-text-muted text-caption ellipsis">
|
<span class="app-text-muted text-caption ellipsis">
|
||||||
{{ node.code }}
|
{{ node.code }}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ defineEmits<{
|
||||||
(e: 'click', v: MouseEvent): void;
|
(e: 'click', v: MouseEvent): void;
|
||||||
}>();
|
}>();
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
id?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
color: string;
|
color: string;
|
||||||
iconOnly?: boolean;
|
iconOnly?: boolean;
|
||||||
|
|
@ -18,6 +19,7 @@ defineProps<{
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
|
:id="id"
|
||||||
@click="(e) => $emit('click', e)"
|
@click="(e) => $emit('click', e)"
|
||||||
class="main-btn"
|
class="main-btn"
|
||||||
:class="{
|
:class="{
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ defineProps<{
|
||||||
outlined?: boolean;
|
outlined?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
dark?: boolean;
|
dark?: boolean;
|
||||||
|
color?: string;
|
||||||
|
|
||||||
label?: string;
|
label?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
@ -23,7 +24,7 @@ defineProps<{
|
||||||
@click="(e) => $emit('click', e)"
|
@click="(e) => $emit('click', e)"
|
||||||
v-bind="{ ...$props, ...$attrs }"
|
v-bind="{ ...$props, ...$attrs }"
|
||||||
:icon="icon || 'mdi-content-save-outline'"
|
:icon="icon || 'mdi-content-save-outline'"
|
||||||
color="207 96% 32%"
|
:color="color || '207 96% 32%'"
|
||||||
:title="iconOnly ? $t('general.save') : undefined"
|
:title="iconOnly ? $t('general.save') : undefined"
|
||||||
>
|
>
|
||||||
{{ label || $t('general.save') }}
|
{{ label || $t('general.save') }}
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,4 @@ export { default as SideMenu } from './SideMenu.vue';
|
||||||
export { default as StatCardComponent } from './StatCardComponent.vue';
|
export { default as StatCardComponent } from './StatCardComponent.vue';
|
||||||
export { default as TooltipComponent } from './TooltipComponent.vue';
|
export { default as TooltipComponent } from './TooltipComponent.vue';
|
||||||
export { default as TreeComponent } from './TreeComponent.vue';
|
export { default as TreeComponent } from './TreeComponent.vue';
|
||||||
|
export { default as PaginationPageSize } from './PaginationPageSize.vue';
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ let defaultFilter: (
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
prefix?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
option: T[];
|
option: T[];
|
||||||
|
|
@ -71,6 +72,7 @@ watch(
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-select
|
<q-select
|
||||||
|
:id="id"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
outlined
|
outlined
|
||||||
:clearable
|
:clearable
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,9 @@ function setDefaultValue() {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
for="select-hq-id"
|
|
||||||
v-model="value"
|
v-model="value"
|
||||||
incremental
|
incremental
|
||||||
|
id="select-hq-id"
|
||||||
:label
|
:label
|
||||||
:placeholder
|
:placeholder
|
||||||
:readonly
|
:readonly
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { getRole } from 'src/services/keycloak';
|
|
||||||
|
|
||||||
import { createSelect, SelectProps } from './select';
|
import { createSelect, SelectProps } from './select';
|
||||||
import SelectInput from '../SelectInput.vue';
|
import SelectInput from '../SelectInput.vue';
|
||||||
|
|
@ -95,6 +94,103 @@ function setDefaultValue() {
|
||||||
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
|
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #no-option v-if="creatable">
|
||||||
|
<q-item
|
||||||
|
:disable="creatableDisabled"
|
||||||
|
clickable
|
||||||
|
v-close-popup
|
||||||
|
@click.stop="$emit('create')"
|
||||||
|
>
|
||||||
|
<q-item-section>
|
||||||
|
<span class="row items-center">
|
||||||
|
<q-icon
|
||||||
|
name="mdi-plus-circle-outline"
|
||||||
|
class="q-mr-sm"
|
||||||
|
style="color: hsl(var(--positive-bg))"
|
||||||
|
/>
|
||||||
|
<b>
|
||||||
|
{{ $t('general.add', { text: $t('businessType.title') }) }}
|
||||||
|
</b>
|
||||||
|
<span
|
||||||
|
v-if="creatableDisabled && creatableDisabledText"
|
||||||
|
class="app-text-muted q-pl-xs"
|
||||||
|
style="font-size: 80%"
|
||||||
|
>
|
||||||
|
{{ creatableDisabledText }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator class="q-mx-sm" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #before-options v-if="creatable">
|
||||||
|
<q-item
|
||||||
|
:disable="creatableDisabled"
|
||||||
|
clickable
|
||||||
|
v-close-popup
|
||||||
|
@click.stop="$emit('create')"
|
||||||
|
for="select-biz-type-add-new"
|
||||||
|
id="select-biz-type-add-new"
|
||||||
|
>
|
||||||
|
<q-item-section>
|
||||||
|
<span class="row items-center">
|
||||||
|
<q-icon
|
||||||
|
name="mdi-plus-circle-outline"
|
||||||
|
class="q-mr-sm"
|
||||||
|
style="color: hsl(var(--positive-bg))"
|
||||||
|
/>
|
||||||
|
<b>
|
||||||
|
{{ $t('general.add', { text: $t('businessType.title') }) }}
|
||||||
|
</b>
|
||||||
|
<span
|
||||||
|
v-if="creatableDisabled && creatableDisabledText"
|
||||||
|
class="app-text-muted q-pl-xs"
|
||||||
|
style="font-size: 80%"
|
||||||
|
>
|
||||||
|
{{ creatableDisabledText }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator class="q-mx-sm" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #before-options v-if="creatable">
|
||||||
|
<q-item
|
||||||
|
:disable="creatableDisabled"
|
||||||
|
clickable
|
||||||
|
v-close-popup
|
||||||
|
@click.stop="$emit('create')"
|
||||||
|
for="select-business-type-add-new"
|
||||||
|
id="select-business-type-add-new"
|
||||||
|
>
|
||||||
|
<q-item-section>
|
||||||
|
<span class="row items-center">
|
||||||
|
<q-icon
|
||||||
|
name="mdi-plus-circle-outline"
|
||||||
|
class="q-mr-sm"
|
||||||
|
style="color: hsl(var(--positive-bg))"
|
||||||
|
/>
|
||||||
|
<b>
|
||||||
|
{{ $t('general.add', { text: $t('menu.manage.businessType') }) }}
|
||||||
|
</b>
|
||||||
|
<span
|
||||||
|
v-if="creatableDisabled && creatableDisabledText"
|
||||||
|
class="app-text-muted q-pl-xs"
|
||||||
|
style="font-size: 80%"
|
||||||
|
>
|
||||||
|
{{ creatableDisabledText }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator class="q-mx-sm" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #option="{ opt, scope }">
|
<template #option="{ opt, scope }">
|
||||||
<q-item v-bind="scope.itemProps">
|
<q-item v-bind="scope.itemProps">
|
||||||
<span class="row items-center">
|
<span class="row items-center">
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ defineEmits<{
|
||||||
type ExclusiveProps = {
|
type ExclusiveProps = {
|
||||||
simple?: boolean;
|
simple?: boolean;
|
||||||
simpleBranchNo?: boolean;
|
simpleBranchNo?: boolean;
|
||||||
|
selectFirstValue?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||||
|
|
@ -64,10 +65,14 @@ onMounted(async () => {
|
||||||
setFirstValue();
|
setFirstValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
await getSelectedOption();
|
if (props.selectFirstValue) {
|
||||||
|
setDefaultValue();
|
||||||
valueOption.value = selectOptions.value.find((v) => v.id === value.value);
|
} else await getSelectedOption();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setDefaultValue() {
|
||||||
|
setFirstValue();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
|
|
@ -160,11 +165,9 @@ onMounted(async () => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #option="{ opt, scope }">
|
<template #option="{ opt, scope }">
|
||||||
<q-item @click="valueOption = opt" v-bind="scope.itemProps">
|
<q-item v-bind="scope.itemProps" class="q-mx-sm bodrder">
|
||||||
<SelectCustomerItem :data="opt" :simple :simple-branch-no />
|
<SelectCustomerItem :data="opt" :simple :simple-branch-no />
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
<q-separator class="q-mx-sm" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #append v-if="clearable">
|
<template #append v-if="clearable">
|
||||||
|
|
@ -177,3 +180,11 @@ onMounted(async () => {
|
||||||
</template>
|
</template>
|
||||||
</SelectInput>
|
</SelectInput>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bodrder {
|
||||||
|
border-bottom: solid;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ defineEmits<{
|
||||||
|
|
||||||
type ExclusiveProps = {
|
type ExclusiveProps = {
|
||||||
selectFirstValue?: boolean;
|
selectFirstValue?: boolean;
|
||||||
|
prefix?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||||
|
|
@ -71,6 +72,7 @@ function setDefaultValue() {
|
||||||
<SelectInput
|
<SelectInput
|
||||||
v-model="value"
|
v-model="value"
|
||||||
incremental
|
incremental
|
||||||
|
:id="`${prefix || 'nome'}-select-user`"
|
||||||
:label
|
:label
|
||||||
:placeholder
|
:placeholder
|
||||||
:readonly
|
:readonly
|
||||||
|
|
|
||||||
|
|
@ -70,22 +70,25 @@ export const createSelect = <T extends Record<string, any>>(
|
||||||
|
|
||||||
if (!currentValue) return;
|
if (!currentValue) return;
|
||||||
|
|
||||||
const option = selectOptions.value.find(
|
|
||||||
(v) => v[valueField] === currentValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (option) {
|
|
||||||
valueOption.value = option;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (valueOption.value && valueOption.value[valueField] === currentValue) {
|
if (valueOption.value && valueOption.value[valueField] === currentValue) {
|
||||||
return selectOptions.value.unshift(valueOption.value);
|
selectOptions.value.unshift(valueOption.value);
|
||||||
|
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
|
||||||
|
return (
|
||||||
|
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ret = await getByValue(currentValue);
|
const ret = await getByValue(currentValue);
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
selectOptions.value.unshift(ret);
|
selectOptions.value.unshift(ret);
|
||||||
|
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
|
||||||
|
return (
|
||||||
|
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
|
||||||
|
);
|
||||||
|
});
|
||||||
valueOption.value = ret;
|
valueOption.value = ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ function selectedIndex(item: Employee) {
|
||||||
<template v-if="col.name === '#check'">
|
<template v-if="col.name === '#check'">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
id="select-worker-all"
|
id="select-worker-all"
|
||||||
|
for="select-worker-all"
|
||||||
v-model="props.selected"
|
v-model="props.selected"
|
||||||
@update:model-value="(v) => handleUpdate()"
|
@update:model-value="(v) => handleUpdate()"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -200,6 +201,7 @@ function selectedIndex(item: Employee) {
|
||||||
v-model="props.selected"
|
v-model="props.selected"
|
||||||
size="sm"
|
size="sm"
|
||||||
:id="`select-worker-${props.row.firstName}`"
|
:id="`select-worker-${props.row.firstName}`"
|
||||||
|
:for="`select-worker-${props.row.firstName}`"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,11 @@ onMounted(() => {
|
||||||
@click="$emit('click')"
|
@click="$emit('click')"
|
||||||
>
|
>
|
||||||
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
|
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
|
||||||
<article class="col column q-pl-md">
|
<div class="col column q-pl-md">
|
||||||
<span class="ellipsis full-width">
|
<div class="ellipsis full-width" style="max-width: 65vw !important">
|
||||||
{{ name }}
|
{{ name }}
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<span class="text-caption app-text-muted-2">
|
<span class="text-caption app-text-muted-2">
|
||||||
{{
|
{{
|
||||||
uploading.loaded
|
uploading.loaded
|
||||||
|
|
@ -79,7 +80,7 @@ onMounted(() => {
|
||||||
/>
|
/>
|
||||||
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
|
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
|
||||||
</span>
|
</span>
|
||||||
</article>
|
</div>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="closeable"
|
v-if="closeable"
|
||||||
icon="mdi-close"
|
icon="mdi-close"
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@ const props = withDefaults(
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
ocr?: (
|
ocr?: (
|
||||||
group: any,
|
group: string,
|
||||||
file: File,
|
file: File,
|
||||||
) => void | Promise<{
|
) => Promise<{
|
||||||
status: boolean;
|
status: boolean;
|
||||||
group: string;
|
group: string;
|
||||||
meta: { name: string; value: string }[];
|
meta: { name: string; value: string }[];
|
||||||
|
|
|
||||||
|
|
@ -198,3 +198,10 @@ i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-i
|
||||||
.q-focus-helper {
|
.q-focus-helper {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clear-btn {
|
||||||
|
opacity: 0.6;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ export default {
|
||||||
documentStatus: 'Document Status',
|
documentStatus: 'Document Status',
|
||||||
advanceSearch: 'Advance Search',
|
advanceSearch: 'Advance Search',
|
||||||
totalPeople: '{meg} people',
|
totalPeople: '{meg} people',
|
||||||
|
price: 'Price {price} Baht',
|
||||||
},
|
},
|
||||||
|
|
||||||
menu: {
|
menu: {
|
||||||
|
|
@ -340,7 +341,7 @@ export default {
|
||||||
requireLength: 'Please enter {msg} character',
|
requireLength: 'Please enter {msg} character',
|
||||||
branchNameField: "Only letters, numbers, or the characters . , - ' &.",
|
branchNameField: "Only letters, numbers, or the characters . , - ' &.",
|
||||||
branchNameENField:
|
branchNameENField:
|
||||||
"Only English letters, numbers, or the characters . , - ' &.",
|
"Only English letters, numbers, or the characters . , - ' &. ( )",
|
||||||
passportFormat: 'Please enter the passport number in the correct format.',
|
passportFormat: 'Please enter the passport number in the correct format.',
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
|
|
@ -507,6 +508,7 @@ export default {
|
||||||
miss: 'MISS.',
|
miss: 'MISS.',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
taxpayyerNo: 'Taxpayer Identification Number',
|
||||||
citizenId: 'Citizen ID',
|
citizenId: 'Citizen ID',
|
||||||
religion: 'Religion',
|
religion: 'Religion',
|
||||||
issueDate: 'Issue Date',
|
issueDate: 'Issue Date',
|
||||||
|
|
@ -777,6 +779,8 @@ export default {
|
||||||
seller: 'Seller',
|
seller: 'Seller',
|
||||||
paymentChannels: 'Payment Channels',
|
paymentChannels: 'Payment Channels',
|
||||||
channelsThat: 'Channels That',
|
channelsThat: 'Channels That',
|
||||||
|
refNo: 'Reference Number',
|
||||||
|
bankAccount: 'Bank Account',
|
||||||
bankAccountNumber: 'Bank Account Number',
|
bankAccountNumber: 'Bank Account Number',
|
||||||
bankAccountName: 'Bank Account Name',
|
bankAccountName: 'Bank Account Name',
|
||||||
inTheNameOf: 'In The Name Of',
|
inTheNameOf: 'In The Name Of',
|
||||||
|
|
@ -1230,6 +1234,9 @@ export default {
|
||||||
taskListNotPending: 'One or more task is not pending.',
|
taskListNotPending: 'One or more task is not pending.',
|
||||||
reqNotMet: 'Not Match',
|
reqNotMet: 'Not Match',
|
||||||
systemError: 'A system error occurred.',
|
systemError: 'A system error occurred.',
|
||||||
|
taskOrderInvalid: 'Please select the product and the organization.',
|
||||||
|
|
||||||
|
flowAccountProductIdNotFound: 'Product not found in flow account',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ export default {
|
||||||
documentStatus: 'สถานะเอกสาร',
|
documentStatus: 'สถานะเอกสาร',
|
||||||
advanceSearch: 'ค้นหาขั้นสูง',
|
advanceSearch: 'ค้นหาขั้นสูง',
|
||||||
totalPeople: '{meg} คน',
|
totalPeople: '{meg} คน',
|
||||||
|
price: 'ราคา {price} บาท',
|
||||||
},
|
},
|
||||||
|
|
||||||
menu: {
|
menu: {
|
||||||
|
|
@ -337,7 +338,7 @@ export default {
|
||||||
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
|
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
|
||||||
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
|
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
|
||||||
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
|
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
|
||||||
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
|
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
|
||||||
branchNameENField:
|
branchNameENField:
|
||||||
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
|
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
|
||||||
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
|
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
|
||||||
|
|
@ -508,6 +509,7 @@ export default {
|
||||||
religion: 'ศาสนา',
|
religion: 'ศาสนา',
|
||||||
issueDate: 'วันที่ออกหนังสือ',
|
issueDate: 'วันที่ออกหนังสือ',
|
||||||
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
|
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
|
||||||
|
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
|
||||||
|
|
||||||
ownerName: 'ชื่อนายจ้าง',
|
ownerName: 'ชื่อนายจ้าง',
|
||||||
firstName: 'ชื่อ ',
|
firstName: 'ชื่อ ',
|
||||||
|
|
@ -775,6 +777,8 @@ export default {
|
||||||
seller: 'ผู้ขาย',
|
seller: 'ผู้ขาย',
|
||||||
paymentChannels: 'ช่องทางชำระเงิน',
|
paymentChannels: 'ช่องทางชำระเงิน',
|
||||||
channelsThat: 'ช่องทางที่',
|
channelsThat: 'ช่องทางที่',
|
||||||
|
refNo: 'เลขที่อ้างอิง',
|
||||||
|
bankAccount: 'บัญชีธนาคาร',
|
||||||
bankAccountNumber: 'เลขบัญชีธนาคาร',
|
bankAccountNumber: 'เลขบัญชีธนาคาร',
|
||||||
bankAccountName: 'ชื่อบัญชี',
|
bankAccountName: 'ชื่อบัญชี',
|
||||||
inTheNameOf: 'ในนาม',
|
inTheNameOf: 'ในนาม',
|
||||||
|
|
@ -1216,6 +1220,8 @@ export default {
|
||||||
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
|
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
|
||||||
reqNotMet: 'ไม่ตรงกัน',
|
reqNotMet: 'ไม่ตรงกัน',
|
||||||
systemError: 'ระบบเกิดข้อผิดพลาด',
|
systemError: 'ระบบเกิดข้อผิดพลาด',
|
||||||
|
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
|
||||||
|
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { ref, onMounted, computed, reactive } from 'vue';
|
import { ref, onMounted, computed, reactive } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { getUserId, getUsername, logout, getRole } from 'src/services/keycloak';
|
import { getUserId, getUsername, getName, logout, getRole } from 'src/services/keycloak';
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
@ -39,7 +39,7 @@ const configStore = useConfigStore();
|
||||||
const { data: notificationData } = storeToRefs(notificationStore);
|
const { data: notificationData } = storeToRefs(notificationStore);
|
||||||
|
|
||||||
const { visible } = storeToRefs(loaderStore);
|
const { visible } = storeToRefs(loaderStore);
|
||||||
const { t } = useI18n({ useScope: 'global' });
|
const { t, locale } = useI18n({ useScope: 'global' });
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const canvasModal = ref(false);
|
const canvasModal = ref(false);
|
||||||
|
|
@ -52,8 +52,14 @@ const unread = computed<number>(
|
||||||
);
|
);
|
||||||
const userImage = ref<string>();
|
const userImage = ref<string>();
|
||||||
const userGender = ref('');
|
const userGender = ref('');
|
||||||
|
const userName = ref({ th: '', en: '' });
|
||||||
const canvasRef = ref();
|
const canvasRef = ref();
|
||||||
|
|
||||||
|
const displayName = computed(() => {
|
||||||
|
if (!userName.value.th && !userName.value.en) return getName() || 'Guest';
|
||||||
|
return locale.value === 'eng' ? userName.value.en : userName.value.th;
|
||||||
|
});
|
||||||
|
|
||||||
const language: {
|
const language: {
|
||||||
value: Lang;
|
value: Lang;
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -161,10 +167,15 @@ onMounted(async () => {
|
||||||
if (user === 'admin') return;
|
if (user === 'admin') return;
|
||||||
if (uid) {
|
if (uid) {
|
||||||
const res = await userStore.fetchById(uid);
|
const res = await userStore.fetchById(uid);
|
||||||
if (res && res.gender) {
|
if (res) {
|
||||||
|
if (res.gender) {
|
||||||
userGender.value = res.gender;
|
userGender.value = res.gender;
|
||||||
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
|
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
|
||||||
}
|
}
|
||||||
|
// เก็บชื่อทั้งสองภาษา
|
||||||
|
userName.value.th = `${res.firstName || ''} ${res.lastName || ''}`.trim();
|
||||||
|
userName.value.en = `${res.firstNameEN || ''} ${res.lastNameEN || ''}`.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -484,6 +495,7 @@ onMounted(async () => {
|
||||||
<!-- User -->
|
<!-- User -->
|
||||||
<ProfileMenu
|
<ProfileMenu
|
||||||
id="btn-profile-menu"
|
id="btn-profile-menu"
|
||||||
|
:user-name="displayName"
|
||||||
@logout="doLogout"
|
@logout="doLogout"
|
||||||
@edit-personal-info="console.log('edit')"
|
@edit-personal-info="console.log('edit')"
|
||||||
@signature="
|
@signature="
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const filterRole = ref<string[]>();
|
||||||
defineProps<{
|
defineProps<{
|
||||||
userImage?: string;
|
userImage?: string;
|
||||||
gender?: string;
|
gender?: string;
|
||||||
|
userName?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const inputFile = document.createElement('input');
|
const inputFile = document.createElement('input');
|
||||||
|
|
@ -147,9 +148,9 @@ onMounted(async () => {
|
||||||
class="text-weight-bold ellipsis"
|
class="text-weight-bold ellipsis"
|
||||||
style="max-width: 9vw"
|
style="max-width: 9vw"
|
||||||
>
|
>
|
||||||
{{ getName() }}
|
{{ userName || getName() }}
|
||||||
<q-tooltip>
|
<q-tooltip>
|
||||||
{{ getName() }}
|
{{ userName || getName() }}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
|
@ -234,12 +235,12 @@ onMounted(async () => {
|
||||||
style="margin-top: 58px"
|
style="margin-top: 58px"
|
||||||
>
|
>
|
||||||
<span v-if="isLoggedIn()">
|
<span v-if="isLoggedIn()">
|
||||||
{{ getName() }}
|
{{ userName || getName() }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ 'Guest' }}</span>
|
<span v-else>{{ 'Guest' }}</span>
|
||||||
<q-tooltip>
|
<q-tooltip>
|
||||||
<span v-if="isLoggedIn()">
|
<span v-if="isLoggedIn()">
|
||||||
{{ getName() }}
|
{{ userName || getName() }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ 'Guest' }}</span>
|
<span v-else>{{ 'Guest' }}</span>
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
|
||||||
import { columnsEmployee } from './constant';
|
import { columnsEmployee } from './constant';
|
||||||
import { useCustomerBranchForm, useEmployeeForm } from './form';
|
import { useCustomerBranchForm, useEmployeeForm } from './form';
|
||||||
|
|
||||||
|
import DialogEmployee from 'src/components/03_customer-management/DialogEmployee.vue';
|
||||||
|
import DrawerEmployee from 'src/components/03_customer-management/DrawerEmployee.vue';
|
||||||
import EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue';
|
import EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue';
|
||||||
import FloatingActionButton from 'components/FloatingActionButton.vue';
|
import FloatingActionButton from 'components/FloatingActionButton.vue';
|
||||||
import SideMenu from 'components/SideMenu.vue';
|
import SideMenu from 'components/SideMenu.vue';
|
||||||
|
|
@ -89,6 +91,11 @@ const prop = withDefaults(
|
||||||
currentCitizenId?: string;
|
currentCitizenId?: string;
|
||||||
gender: string;
|
gender: string;
|
||||||
selectedImage: string;
|
selectedImage: string;
|
||||||
|
fetchImageList: (
|
||||||
|
id: string,
|
||||||
|
selectedName: string,
|
||||||
|
type: 'customer' | 'employee',
|
||||||
|
) => Promise<void>;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
color: 'green',
|
color: 'green',
|
||||||
|
|
@ -96,7 +103,6 @@ const prop = withDefaults(
|
||||||
);
|
);
|
||||||
const currentBranchEmployee = ref<string>('');
|
const currentBranchEmployee = ref<string>('');
|
||||||
const listEmployee = ref<Employee[]>([]);
|
const listEmployee = ref<Employee[]>([]);
|
||||||
|
|
||||||
const customerId = defineModel<string>('customerId', { required: true });
|
const customerId = defineModel<string>('customerId', { required: true });
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
|
|
@ -106,16 +112,6 @@ defineEmits<{
|
||||||
(e: 'dialog'): void;
|
(e: 'dialog'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
customerBranchFormState.value.currentCustomerId = route.params
|
|
||||||
.customerId as string;
|
|
||||||
await fetchList();
|
|
||||||
|
|
||||||
branch.value?.forEach((v) => {
|
|
||||||
currentBtnOpen.value.push(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'branchName',
|
name: 'branchName',
|
||||||
|
|
@ -257,10 +253,6 @@ async function fetchEmployee(opts: { branchId: string; pageSize?: number }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await fetchList();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
|
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
|
||||||
await fetchList();
|
await fetchList();
|
||||||
});
|
});
|
||||||
|
|
@ -280,6 +272,16 @@ watch(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
customerBranchFormState.value.currentCustomerId = route.params
|
||||||
|
.customerId as string;
|
||||||
|
await fetchList();
|
||||||
|
|
||||||
|
branch.value?.forEach((v) => {
|
||||||
|
currentBtnOpen.value.push(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -473,7 +475,6 @@ watch(
|
||||||
<q-tr
|
<q-tr
|
||||||
:class="{
|
:class="{
|
||||||
'app-text-muted': props.row.status === 'INACTIVE',
|
'app-text-muted': props.row.status === 'INACTIVE',
|
||||||
'cursor-pointer': props.row._count?.branch !== 0,
|
|
||||||
}"
|
}"
|
||||||
:props="props"
|
:props="props"
|
||||||
@click="$emit('viewDetail', props.row, props.rowIndex)"
|
@click="$emit('viewDetail', props.row, props.rowIndex)"
|
||||||
|
|
@ -547,7 +548,13 @@ watch(
|
||||||
v-if="branchFieldSelected.includes('businessTypePure')"
|
v-if="branchFieldSelected.includes('businessTypePure')"
|
||||||
class="text-left"
|
class="text-left"
|
||||||
>
|
>
|
||||||
{{ useOptionStore().mapOption(props.row.businessType) || '-' }}
|
{{
|
||||||
|
props.row.businessType
|
||||||
|
? props.row.businessType[
|
||||||
|
$i18n.locale === 'eng' ? 'nameEN' : 'name'
|
||||||
|
]
|
||||||
|
: '-'
|
||||||
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td
|
<q-td
|
||||||
v-if="branchFieldSelected.includes('totalEmployee')"
|
v-if="branchFieldSelected.includes('totalEmployee')"
|
||||||
|
|
@ -566,8 +573,6 @@ watch(
|
||||||
await fetchEmployee({
|
await fetchEmployee({
|
||||||
branchId: currentBranchEmployee,
|
branchId: currentBranchEmployee,
|
||||||
pageSize: 999,
|
pageSize: 999,
|
||||||
passport: true,
|
|
||||||
visa: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
currentBtnOpen.map((v, i) => {
|
currentBtnOpen.map((v, i) => {
|
||||||
|
|
@ -638,10 +643,15 @@ watch(
|
||||||
"
|
"
|
||||||
@history="(item) => {}"
|
@history="(item) => {}"
|
||||||
@view="
|
@view="
|
||||||
(item) => {
|
async (item) => {
|
||||||
employeeFormState.drawerModal = true;
|
employeeFormState.drawerModal = true;
|
||||||
//employeeFormState.isEmployeeEdit = true;
|
//employeeFormState.isEmployeeEdit = true;
|
||||||
employeeFormStore.assignFormDataEmployee(item.id);
|
employeeFormStore.assignFormDataEmployee(item.id);
|
||||||
|
await fetchImageList(
|
||||||
|
item.id,
|
||||||
|
item.selectedImage || '',
|
||||||
|
'employee',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
@ -668,9 +678,11 @@ watch(
|
||||||
? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}`
|
? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}`
|
||||||
: `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`,
|
: `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`,
|
||||||
telephone: props.row.telephoneNo,
|
telephone: props.row.telephoneNo,
|
||||||
businessTypePure: useOptionStore().mapOption(
|
businessTypePure: props.row.businessType
|
||||||
props.row.businessType,
|
? props.row.businessType[
|
||||||
),
|
$i18n.locale === 'eng' ? 'nameEN' : 'name'
|
||||||
|
]
|
||||||
|
: '-',
|
||||||
totalEmployee: props.row._count?.employee,
|
totalEmployee: props.row._count?.employee,
|
||||||
}"
|
}"
|
||||||
:visible-columns="branchFieldSelected"
|
:visible-columns="branchFieldSelected"
|
||||||
|
|
@ -885,15 +897,14 @@ watch(
|
||||||
</div>
|
</div>
|
||||||
<EmployerFormBusiness
|
<EmployerFormBusiness
|
||||||
dense
|
dense
|
||||||
class="q-mb-xl"
|
|
||||||
outlined
|
outlined
|
||||||
prefix-id="employer-branch"
|
prefix-id="employer-branch"
|
||||||
:readonly="customerBranchFormState.dialogType === 'info'"
|
:readonly="customerBranchFormState.dialogType === 'info'"
|
||||||
v-model:bussiness-type="customerBranchFormData.businessType"
|
v-model:business-type-id="customerBranchFormData.businessTypeId"
|
||||||
v-model:job-position="customerBranchFormData.jobPosition"
|
v-model:job-position="customerBranchFormData.jobPosition"
|
||||||
v-model:job-description="customerBranchFormData.jobDescription"
|
v-model:job-description="customerBranchFormData.jobDescription"
|
||||||
v-model:pay-date="customerBranchFormData.payDate"
|
v-model:pay-date="customerBranchFormData.payDate"
|
||||||
v-model:pay-date-e-n="customerBranchFormData.payDateEN"
|
v-model:pay-date-en="customerBranchFormData.payDateEN"
|
||||||
v-model:wage-rate="customerBranchFormData.wageRate"
|
v-model:wage-rate="customerBranchFormData.wageRate"
|
||||||
v-model:wage-rate-text="customerBranchFormData.wageRateText"
|
v-model:wage-rate-text="customerBranchFormData.wageRateText"
|
||||||
/>
|
/>
|
||||||
|
|
@ -984,7 +995,7 @@ watch(
|
||||||
v-model:email="customerBranchFormData.email"
|
v-model:email="customerBranchFormData.email"
|
||||||
v-model:contact-tel="customerBranchFormData.contactTel"
|
v-model:contact-tel="customerBranchFormData.contactTel"
|
||||||
v-model:office-tel="customerBranchFormData.officeTel"
|
v-model:office-tel="customerBranchFormData.officeTel"
|
||||||
v-model:agent="customerBranchFormData.agent"
|
v-model:agent-user-id="customerBranchFormData.agentUserId"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1000,6 +1011,18 @@ watch(
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</DialogFormContainer>
|
</DialogFormContainer>
|
||||||
|
|
||||||
|
<DialogEmployee
|
||||||
|
:fetch-list-employee="fetchEmployee"
|
||||||
|
:fetch-image-list="fetchImageList"
|
||||||
|
current-tab="employer"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DrawerEmployee
|
||||||
|
:fetch-list-employee="fetchEmployee"
|
||||||
|
:fetch-image-list="fetchImageList"
|
||||||
|
current-tab="employer"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
2078
src/pages/03_customer-management/TabCustomer.vue
Normal file
514
src/pages/03_customer-management/TabEmployee.vue
Normal file
|
|
@ -0,0 +1,514 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PaginationComponent,
|
||||||
|
PaginationPageSize,
|
||||||
|
ImageUploadDialog,
|
||||||
|
DialogForm,
|
||||||
|
NoData,
|
||||||
|
} from 'src/components';
|
||||||
|
import DialogEmployee from 'src/components/03_customer-management/DialogEmployee.vue';
|
||||||
|
import HistoryEditComponent from 'src/components/03_customer-management/HistoryEditComponent.vue';
|
||||||
|
import TableEmpoloyee from 'src/components/03_customer-management/TableEmpoloyee.vue';
|
||||||
|
import DrawerEmployee from 'src/components/03_customer-management/DrawerEmployee.vue';
|
||||||
|
|
||||||
|
import useFlowStore from 'src/stores/flow';
|
||||||
|
import useEmployeeStore from 'src/stores/employee';
|
||||||
|
import { Status } from 'src/stores/types';
|
||||||
|
import { Employee, EmployeeHistory } from 'src/stores/employee/types';
|
||||||
|
import { baseUrl, dialog, canAccess } from 'src/stores/utils';
|
||||||
|
import { calculateAge, toISOStringWithTimezone } from 'src/utils/datetime';
|
||||||
|
import { useCustomerForm, useEmployeeForm } from './form';
|
||||||
|
import { columnsEmployee } from './constant';
|
||||||
|
|
||||||
|
const $q = useQuasar();
|
||||||
|
const flowStore = useFlowStore();
|
||||||
|
const employeeStore = useEmployeeStore();
|
||||||
|
const employeeFormStore = useEmployeeForm();
|
||||||
|
const customerFormStore = useCustomerForm();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { state: employeeFormState, currentFromDataEmployee } =
|
||||||
|
storeToRefs(employeeFormStore);
|
||||||
|
const { deleteEmployeeById } = employeeFormStore;
|
||||||
|
const { state: customerFormState } = storeToRefs(customerFormStore);
|
||||||
|
|
||||||
|
const employeeStats = defineModel('employeeStats', { default: 0 });
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
currentTab: 'employee' | 'employer';
|
||||||
|
currentStatus: Status | 'All';
|
||||||
|
gridView: boolean;
|
||||||
|
inputSearch: string;
|
||||||
|
searchDate: string[];
|
||||||
|
fieldSelected: string[];
|
||||||
|
fetchImageList: (
|
||||||
|
id: string,
|
||||||
|
selectedName: string,
|
||||||
|
type: 'customer' | 'employee',
|
||||||
|
) => Promise<void>;
|
||||||
|
triggerChangeStatus: (
|
||||||
|
id: string,
|
||||||
|
status: string,
|
||||||
|
employeeName?: string,
|
||||||
|
) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openSpecificEmployee,
|
||||||
|
fetchListEmployee,
|
||||||
|
toggleStatusEmployee,
|
||||||
|
});
|
||||||
|
|
||||||
|
const listEmployee = ref<Employee[]>([]);
|
||||||
|
const currentPageEmployee = ref<number>(1);
|
||||||
|
const maxPageEmployee = ref<number>(1);
|
||||||
|
const pageSize = ref<number>(30);
|
||||||
|
const employeeHistoryDialog = ref(false);
|
||||||
|
const employeeHistory = ref<EmployeeHistory[]>();
|
||||||
|
|
||||||
|
const splitPercent = computed(() => ($q.screen.lt.md ? 0 : 15));
|
||||||
|
|
||||||
|
async function fetchListEmployee(opt?: {
|
||||||
|
fetchStats?: boolean;
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
customerId?: string;
|
||||||
|
mobileFetch?: boolean;
|
||||||
|
}) {
|
||||||
|
const resultListEmployee = await employeeStore.fetchList({
|
||||||
|
customerId: opt?.customerId,
|
||||||
|
page: opt
|
||||||
|
? opt.mobileFetch
|
||||||
|
? 1
|
||||||
|
: opt.page || currentPageEmployee.value
|
||||||
|
: currentPageEmployee.value,
|
||||||
|
pageSize: opt
|
||||||
|
? opt.mobileFetch
|
||||||
|
? listEmployee.value.length +
|
||||||
|
(employeeStats.value === listEmployee.value.length ? 1 : 0)
|
||||||
|
: opt.pageSize || pageSize.value
|
||||||
|
: pageSize.value,
|
||||||
|
status:
|
||||||
|
props.currentStatus === 'All'
|
||||||
|
? undefined
|
||||||
|
: props.currentStatus === 'ACTIVE'
|
||||||
|
? 'ACTIVE'
|
||||||
|
: 'INACTIVE',
|
||||||
|
query: props.inputSearch,
|
||||||
|
passport: true,
|
||||||
|
visa: true,
|
||||||
|
startDate: props.searchDate[0],
|
||||||
|
endDate: props.searchDate[1],
|
||||||
|
});
|
||||||
|
if (resultListEmployee) {
|
||||||
|
maxPageEmployee.value = Math.ceil(
|
||||||
|
resultListEmployee.total / pageSize.value,
|
||||||
|
);
|
||||||
|
$q.screen.xs && !(opt && opt.mobileFetch)
|
||||||
|
? listEmployee.value.push(...resultListEmployee.result)
|
||||||
|
: (listEmployee.value = resultListEmployee.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt && opt.fetchStats)
|
||||||
|
employeeStats.value = await employeeStore.getStatsEmployee();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openHistory(id: string) {
|
||||||
|
const res = await employeeStore.getEditHistory(id);
|
||||||
|
employeeHistory.value = res.reverse();
|
||||||
|
employeeHistoryDialog.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editEmployeeFormPersonal(id: string) {
|
||||||
|
await employeeFormStore.assignFormDataEmployee(id);
|
||||||
|
await props.fetchImageList(
|
||||||
|
id,
|
||||||
|
currentFromDataEmployee.value.selectedImage || '',
|
||||||
|
'employee',
|
||||||
|
);
|
||||||
|
|
||||||
|
employeeFormState.value.isEmployeeEdit = true;
|
||||||
|
employeeFormState.value.dialogType = 'edit';
|
||||||
|
employeeFormState.value.drawerModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openSpecificEmployee(id: string) {
|
||||||
|
await employeeFormStore.assignFormDataEmployee(id);
|
||||||
|
await props.fetchImageList(
|
||||||
|
id,
|
||||||
|
currentFromDataEmployee.value.selectedImage || '',
|
||||||
|
'employee',
|
||||||
|
);
|
||||||
|
employeeFormState.value.dialogType = 'info';
|
||||||
|
employeeFormState.value.drawerModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleStatusEmployee(
|
||||||
|
id: string,
|
||||||
|
status: boolean,
|
||||||
|
employeeName: string,
|
||||||
|
) {
|
||||||
|
const res = await employeeStore.editById(id, {
|
||||||
|
status: !status ? 'ACTIVE' : 'INACTIVE',
|
||||||
|
firstNameEN: employeeName,
|
||||||
|
});
|
||||||
|
if (res && employeeFormState.value.drawerModal) {
|
||||||
|
currentFromDataEmployee.value.status = res.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
await employeeFormStore.assignFormDataEmployee(id);
|
||||||
|
await fetchListEmployee({ mobileFetch: $q.screen.xs });
|
||||||
|
flowStore.rotate();
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.inputSearch,
|
||||||
|
props.searchDate,
|
||||||
|
props.currentStatus,
|
||||||
|
pageSize.value,
|
||||||
|
],
|
||||||
|
async () => {
|
||||||
|
currentPageEmployee.value = 1;
|
||||||
|
listEmployee.value = [];
|
||||||
|
await fetchListEmployee({ fetchStats: true });
|
||||||
|
|
||||||
|
customerFormState.value.currentCustomerId = undefined;
|
||||||
|
flowStore.rotate();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => employeeFormState.value.currentCustomerBranch,
|
||||||
|
(e) => {
|
||||||
|
if (!e) return;
|
||||||
|
if (employeeFormState.value.formDataEmployeeSameAddr) {
|
||||||
|
currentFromDataEmployee.value.address = e.address;
|
||||||
|
currentFromDataEmployee.value.addressEN = e.addressEN;
|
||||||
|
currentFromDataEmployee.value.provinceId = e.provinceId;
|
||||||
|
currentFromDataEmployee.value.districtId = e.districtId;
|
||||||
|
currentFromDataEmployee.value.subDistrictId = e.subDistrictId;
|
||||||
|
}
|
||||||
|
currentFromDataEmployee.value.customerBranchId = e.id;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => employeeFormState.value.formDataEmployeeSameAddr,
|
||||||
|
(isSame) => {
|
||||||
|
if (!employeeFormState.value.currentCustomerBranch) return;
|
||||||
|
if (isSame) {
|
||||||
|
currentFromDataEmployee.value.address =
|
||||||
|
employeeFormState.value.currentCustomerBranch.address;
|
||||||
|
currentFromDataEmployee.value.addressEN =
|
||||||
|
employeeFormState.value.currentCustomerBranch.addressEN;
|
||||||
|
currentFromDataEmployee.value.provinceId =
|
||||||
|
employeeFormState.value.currentCustomerBranch.provinceId;
|
||||||
|
currentFromDataEmployee.value.districtId =
|
||||||
|
employeeFormState.value.currentCustomerBranch.districtId;
|
||||||
|
currentFromDataEmployee.value.subDistrictId =
|
||||||
|
employeeFormState.value.currentCustomerBranch.subDistrictId;
|
||||||
|
}
|
||||||
|
currentFromDataEmployee.value.customerBranchId =
|
||||||
|
employeeFormState.value.currentCustomerBranch.id;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentFromDataEmployee.value.dateOfBirth,
|
||||||
|
(v) => {
|
||||||
|
const isEdit =
|
||||||
|
employeeFormState.value.drawerModal &&
|
||||||
|
employeeFormState.value.isEmployeeEdit;
|
||||||
|
let currentFormDate = v && toISOStringWithTimezone(new Date(v));
|
||||||
|
let currentDate: string = '';
|
||||||
|
|
||||||
|
if (isEdit && employeeFormState.value.currentEmployee) {
|
||||||
|
currentDate = toISOStringWithTimezone(
|
||||||
|
new Date(employeeFormState.value.currentEmployee.dateOfBirth),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
employeeFormState.value.dialogModal ||
|
||||||
|
(isEdit && currentFormDate !== currentDate)
|
||||||
|
) {
|
||||||
|
const age = calculateAge(
|
||||||
|
currentFromDataEmployee.value.dateOfBirth,
|
||||||
|
'year',
|
||||||
|
);
|
||||||
|
if (currentFromDataEmployee.value.dateOfBirth && Number(age) < 15) {
|
||||||
|
dialog({
|
||||||
|
color: 'warning',
|
||||||
|
icon: 'mdi-alert',
|
||||||
|
title: t('dialog.title.youngWorker15'),
|
||||||
|
cancelText: t('general.edit'),
|
||||||
|
persistent: true,
|
||||||
|
message: t('dialog.message.youngWorker15'),
|
||||||
|
|
||||||
|
cancel: async () => {
|
||||||
|
currentFromDataEmployee.value.dateOfBirth = null;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentFromDataEmployee.value.dateOfBirth &&
|
||||||
|
Number(age) > 15 &&
|
||||||
|
Number(age) <= 18
|
||||||
|
) {
|
||||||
|
dialog({
|
||||||
|
color: 'warning',
|
||||||
|
icon: 'mdi-alert',
|
||||||
|
title: t('dialog.title.youngWorker18'),
|
||||||
|
cancelText: t('general.cancel'),
|
||||||
|
actionText: t('general.confirm'),
|
||||||
|
persistent: true,
|
||||||
|
message: t('dialog.message.youngWorker18'),
|
||||||
|
action: () => {},
|
||||||
|
cancel: async () => {
|
||||||
|
currentFromDataEmployee.value.dateOfBirth = null;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentFromDataEmployee.value.image,
|
||||||
|
() => {
|
||||||
|
if (currentFromDataEmployee.value.image !== null)
|
||||||
|
employeeFormState.value.isImageEdit = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
currentPageEmployee.value = 1;
|
||||||
|
listEmployee.value = [];
|
||||||
|
await fetchListEmployee({ fetchStats: true });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<q-splitter
|
||||||
|
v-model="splitPercent"
|
||||||
|
:limits="[0, 100]"
|
||||||
|
class="col full-width"
|
||||||
|
before-class="overflow-hidden"
|
||||||
|
after-class="overflow-hidden"
|
||||||
|
:disable="$q.screen.lt.sm"
|
||||||
|
>
|
||||||
|
<template v-slot:before>
|
||||||
|
<div
|
||||||
|
class="column q-pa-md surface-1 full-height full-width"
|
||||||
|
style="gap: var(--size-1)"
|
||||||
|
>
|
||||||
|
<q-item
|
||||||
|
active
|
||||||
|
dense
|
||||||
|
active-class="employer-active"
|
||||||
|
class="no-padding items-center rounded full-width"
|
||||||
|
v-close-popup
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<span class="q-px-md ellipsis">
|
||||||
|
{{ $t('general.all') }}
|
||||||
|
</span>
|
||||||
|
</q-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:after>
|
||||||
|
<div class="column full-height no-wrap">
|
||||||
|
<!-- employee -->
|
||||||
|
<template
|
||||||
|
v-if="listEmployee && employeeStats > 0 && currentTab === 'employee'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="listEmployee.length === 0"
|
||||||
|
class="row col full-width items-center justify-center"
|
||||||
|
style="min-height: 250px"
|
||||||
|
>
|
||||||
|
<NoData :not-found="!!inputSearch" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article
|
||||||
|
v-if="listEmployee.length !== 0"
|
||||||
|
class="column scroll q-pa-md col"
|
||||||
|
>
|
||||||
|
<q-infinite-scroll
|
||||||
|
:offset="10"
|
||||||
|
@load="
|
||||||
|
(_, done) => {
|
||||||
|
if (
|
||||||
|
$q.screen.gt.xs ||
|
||||||
|
currentPageEmployee === maxPageEmployee
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
currentPageEmployee = currentPageEmployee + 1;
|
||||||
|
fetchListEmployee().then(() =>
|
||||||
|
done(currentPageEmployee >= maxPageEmployee),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<TableEmpoloyee
|
||||||
|
:hide-delete="!canAccess('customer', 'edit')"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
v-model:current-page="currentPageEmployee"
|
||||||
|
:grid-view="gridView"
|
||||||
|
:list-employee="listEmployee"
|
||||||
|
:columns-employee="columnsEmployee"
|
||||||
|
:field-selected="fieldSelected"
|
||||||
|
@history="
|
||||||
|
(item: any) => {
|
||||||
|
openHistory(item.id);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@view="
|
||||||
|
async (item: any) => {
|
||||||
|
employeeFormState.drawerModal = true;
|
||||||
|
employeeFormState.isEmployeeEdit = false;
|
||||||
|
employeeFormStore.assignFormDataEmployee(item.id);
|
||||||
|
await fetchImageList(
|
||||||
|
item.id,
|
||||||
|
item.selectedImage || '',
|
||||||
|
'employee',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@edit="(item: any) => editEmployeeFormPersonal(item.id)"
|
||||||
|
@delete="
|
||||||
|
(item: any) => {
|
||||||
|
deleteEmployeeById({
|
||||||
|
id: item.id,
|
||||||
|
fetch: async () =>
|
||||||
|
await fetchListEmployee(
|
||||||
|
currentTab === 'employer'
|
||||||
|
? {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 999,
|
||||||
|
customerId: customerFormState.currentCustomerId,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
fetchStats: true,
|
||||||
|
mobileFetch: $q.screen.xs,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@toggle-status="
|
||||||
|
async (item: any) => {
|
||||||
|
triggerChangeStatus(item.id, item.status, item.firstNameEN);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<template v-slot:loading>
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
$q.screen.lt.sm && currentPageEmployee !== maxPageEmployee
|
||||||
|
"
|
||||||
|
class="row justify-center"
|
||||||
|
>
|
||||||
|
<q-spinner-dots color="primary" size="40px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-infinite-scroll>
|
||||||
|
</article>
|
||||||
|
<footer
|
||||||
|
v-if="listEmployee.length !== 0 && $q.screen.gt.xs"
|
||||||
|
class="row justify-between items-center q-px-md q-py-sm"
|
||||||
|
>
|
||||||
|
<div class="row col-4 items-center">
|
||||||
|
<div
|
||||||
|
class="app-text-muted"
|
||||||
|
style="width: 80px"
|
||||||
|
v-if="$q.screen.gt.sm"
|
||||||
|
>
|
||||||
|
{{ $t('general.recordPerPage') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><PaginationPageSize v-model="pageSize" /></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4 flex justify-center app-text-muted">
|
||||||
|
{{
|
||||||
|
$q.screen.gt.sm
|
||||||
|
? $t('general.recordsPage', {
|
||||||
|
resultcurrentPage: listEmployee.length,
|
||||||
|
total: employeeStats,
|
||||||
|
})
|
||||||
|
: $t('general.ofPage', {
|
||||||
|
current: listEmployee.length,
|
||||||
|
total: employeeStats,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4 flex justify-end">
|
||||||
|
<PaginationComponent
|
||||||
|
v-model:current-page="currentPageEmployee"
|
||||||
|
v-model:max-page="maxPageEmployee"
|
||||||
|
:fetch-data="
|
||||||
|
async () => {
|
||||||
|
await fetchListEmployee();
|
||||||
|
flowStore.rotate();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-splitter>
|
||||||
|
|
||||||
|
<!-- add employee -->
|
||||||
|
<DialogEmployee
|
||||||
|
:fetch-list-employee="fetchListEmployee"
|
||||||
|
:fetch-image-list="fetchImageList"
|
||||||
|
:current-tab="currentTab"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- ลูกจ้าง edit employee -->
|
||||||
|
<DrawerEmployee
|
||||||
|
:fetch-list-employee="fetchListEmployee"
|
||||||
|
:fetch-image-list="fetchImageList"
|
||||||
|
:current-tab="currentTab"
|
||||||
|
@change-status="
|
||||||
|
(s) =>
|
||||||
|
triggerChangeStatus(
|
||||||
|
currentFromDataEmployee.id,
|
||||||
|
s,
|
||||||
|
currentFromDataEmployee.firstNameEN,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DialogForm
|
||||||
|
:title="$t('general.historyEdit')"
|
||||||
|
hide-footer
|
||||||
|
v-model:modal="employeeHistoryDialog"
|
||||||
|
>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<HistoryEditComponent
|
||||||
|
v-if="employeeHistory"
|
||||||
|
v-model:history-list="employeeHistory"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DialogForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.employer-active {
|
||||||
|
background-color: hsla(var(--info-bg) / 0.1);
|
||||||
|
color: hsl(var(--info-bg));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -140,7 +140,7 @@ watch(
|
||||||
:rules="[
|
:rules="[
|
||||||
(val) => !!val || $t('form.error.required'),
|
(val) => !!val || $t('form.error.required'),
|
||||||
(val) =>
|
(val) =>
|
||||||
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
|
/^[A-Za-z0-9ก-๙\s&.,'()-]+$/.test(val) ||
|
||||||
$t('form.error.branchNameField'),
|
$t('form.error.branchNameField'),
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|
@ -336,6 +336,7 @@ watch(
|
||||||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
||||||
"
|
"
|
||||||
@clear="prefixName = ''"
|
@clear="prefixName = ''"
|
||||||
|
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||||
>
|
>
|
||||||
<template v-slot:no-option>
|
<template v-slot:no-option>
|
||||||
<q-item>
|
<q-item>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import useOptionStore from 'stores/options';
|
import useOptionStore from 'stores/options';
|
||||||
import SelectBranch from 'src/components/shared/select/SelectBranch.vue';
|
import SelectBranch from 'src/components/shared/select/SelectBranch.vue';
|
||||||
import { isRoleInclude } from 'src/stores/utils';
|
import { isRoleInclude } from 'src/stores/utils';
|
||||||
|
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
@ -142,15 +143,10 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
|
||||||
for="input-tax"
|
for="input-tax"
|
||||||
v-model="legalPersonNo"
|
v-model="legalPersonNo"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<SelectBusinessType
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
:readonly="readonly"
|
|
||||||
hide-bottom-space
|
|
||||||
class="col-6 col-md-3"
|
class="col-6 col-md-3"
|
||||||
:label="$t('customer.table.businessTypePure')"
|
v-model:value="businessType"
|
||||||
for="input-business-type"
|
:readonly
|
||||||
:model-value="optionStore.mapOption(businessType)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -179,15 +175,10 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
|
||||||
:label="$t('personnel.form.citizenId')"
|
:label="$t('personnel.form.citizenId')"
|
||||||
for="input-citizen-id"
|
for="input-citizen-id"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<SelectBusinessType
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
:readonly="readonly"
|
|
||||||
hide-bottom-space
|
|
||||||
class="col-6 col-md-3"
|
class="col-6 col-md-3"
|
||||||
:label="$t('customer.table.businessTypePure')"
|
v-model:value="businessType"
|
||||||
for="input-first-name-en"
|
:readonly
|
||||||
:model-value="optionStore.mapOption(businessType)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import { CustomerCreate } from 'stores/customer/types';
|
||||||
import EmployerFormAbout from './EmployerFormAbout.vue';
|
import EmployerFormAbout from './EmployerFormAbout.vue';
|
||||||
import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
|
import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
|
||||||
import { waitAll } from 'src/stores/utils';
|
import { waitAll } from 'src/stores/utils';
|
||||||
|
import FormEmployeePassport from 'src/components/03_customer-management/FormEmployeePassport.vue';
|
||||||
|
import FormEmployeeVisa from 'src/components/03_customer-management/FormEmployeeVisa.vue';
|
||||||
import {
|
import {
|
||||||
FormCitizen,
|
FormCitizen,
|
||||||
CorpFormBusinessRegistration,
|
CorpFormBusinessRegistration,
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,25 @@ import { useI18n } from 'vue-i18n';
|
||||||
import ThaiBahtText from 'thai-baht-text';
|
import ThaiBahtText from 'thai-baht-text';
|
||||||
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
|
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
|
||||||
|
|
||||||
|
import BusinessTypeDialog from 'src/pages/16_business-type-management/BusinessTypeDialog.vue';
|
||||||
|
|
||||||
|
import useBusinessTypeStore from 'src/stores/business-type';
|
||||||
|
|
||||||
const { locale } = useI18n({ useScope: 'global' });
|
const { locale } = useI18n({ useScope: 'global' });
|
||||||
|
|
||||||
const rawOption = ref();
|
const rawOption = ref();
|
||||||
|
|
||||||
|
const businessTypeDialog = ref<boolean>(false);
|
||||||
|
const formDataBusinessType = ref<{
|
||||||
|
name: string;
|
||||||
|
nameEN: string;
|
||||||
|
}>({
|
||||||
|
name: '',
|
||||||
|
nameEN: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const useBusinessType = useBusinessTypeStore();
|
||||||
|
|
||||||
const businessTypeId = defineModel<string>('businessTypeId');
|
const businessTypeId = defineModel<string>('businessTypeId');
|
||||||
const jobPosition = defineModel<string>('jobPosition');
|
const jobPosition = defineModel<string>('jobPosition');
|
||||||
const jobDescription = defineModel<string>('jobDescription');
|
const jobDescription = defineModel<string>('jobDescription');
|
||||||
|
|
@ -29,6 +44,8 @@ const typeBusinessENOption = ref([]);
|
||||||
const jobPositionOption = ref([]);
|
const jobPositionOption = ref([]);
|
||||||
const jobPositionENOption = ref([]);
|
const jobPositionENOption = ref([]);
|
||||||
|
|
||||||
|
const keySelect = ref<number>(0);
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title?: string;
|
title?: string;
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
|
|
@ -38,6 +55,20 @@ defineProps<{
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
function resetFormBusinessType() {
|
||||||
|
businessTypeDialog.value = false;
|
||||||
|
formDataBusinessType.value = { name: '', nameEN: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitBusinessType() {
|
||||||
|
const res = await useBusinessType.create(formDataBusinessType.value);
|
||||||
|
if (res) {
|
||||||
|
businessTypeId.value = res.id;
|
||||||
|
resetFormBusinessType();
|
||||||
|
keySelect.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const resultOption = await fetch('/option/option.json');
|
const resultOption = await fetch('/option/option.json');
|
||||||
rawOption.value = await resultOption.json();
|
rawOption.value = await resultOption.json();
|
||||||
|
|
@ -121,16 +152,24 @@ let jobPositionENFilter = selectFilterOptionRefMod(
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SelectBusinessType
|
<SelectBusinessType
|
||||||
|
:key="keySelect"
|
||||||
class="col-md-6 col-12"
|
class="col-md-6 col-12"
|
||||||
v-model:value="businessTypeId"
|
v-model:value="businessTypeId"
|
||||||
:readonly
|
:readonly
|
||||||
|
creatable
|
||||||
lang="tha"
|
lang="tha"
|
||||||
|
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||||
|
@create="() => (businessTypeDialog = true)"
|
||||||
/>
|
/>
|
||||||
<SelectBusinessType
|
<SelectBusinessType
|
||||||
|
:key="keySelect"
|
||||||
class="col-md-6 col-12"
|
class="col-md-6 col-12"
|
||||||
v-model:value="businessTypeId"
|
v-model:value="businessTypeId"
|
||||||
lang="eng"
|
|
||||||
:readonly
|
:readonly
|
||||||
|
creatable
|
||||||
|
lang="eng"
|
||||||
|
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||||
|
@create="() => (businessTypeDialog = true)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-select
|
<q-select
|
||||||
|
|
@ -282,4 +321,12 @@ let jobPositionENFilter = selectFilterOptionRefMod(
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<BusinessTypeDialog
|
||||||
|
ref="refBusinessTypeDialog"
|
||||||
|
@close="resetFormBusinessType()"
|
||||||
|
@submit="submitBusinessType()"
|
||||||
|
v-model="businessTypeDialog"
|
||||||
|
v-model:data="formDataBusinessType"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ const contactName = defineModel<string>('contactName');
|
||||||
const email = defineModel<string>('email');
|
const email = defineModel<string>('email');
|
||||||
const contactTel = defineModel<string>('contactTel');
|
const contactTel = defineModel<string>('contactTel');
|
||||||
const officeTel = defineModel<string>('officeTel');
|
const officeTel = defineModel<string>('officeTel');
|
||||||
const agent = defineModel<string>('agent');
|
|
||||||
|
|
||||||
const agentUserId = defineModel<string>('agentUserId');
|
const agentUserId = defineModel<string>('agentUserId');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -109,7 +107,6 @@ const agentUserId = defineModel<string>('agentUserId');
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<SelectAgent
|
<SelectAgent
|
||||||
:label="$t('customer.form.agent')"
|
:label="$t('customer.form.agent')"
|
||||||
v-model:value="agentUserId"
|
v-model:value="agentUserId"
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
export const useCustomerForm = defineStore('form-customer', () => {
|
export const useCustomerForm = defineStore('form-customer', () => {
|
||||||
const customerStore = useCustomerStore();
|
const customerStore = useCustomerStore();
|
||||||
|
const onCreateImageList = ref<{
|
||||||
|
selectedImage: string;
|
||||||
|
list: { url: string; imgFile: File | null; name: string }[];
|
||||||
|
}>({ selectedImage: '', list: [] });
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const flowStore = useFlowStore();
|
const flowStore = useFlowStore();
|
||||||
|
|
@ -30,6 +34,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
||||||
|
|
||||||
const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
|
const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
|
||||||
|
|
||||||
|
const currentBranchRootId = ref<string>('');
|
||||||
|
|
||||||
const tabFieldRequired = ref<{
|
const tabFieldRequired = ref<{
|
||||||
[key: string]: (keyof CustomerBranchCreate)[];
|
[key: string]: (keyof CustomerBranchCreate)[];
|
||||||
}>({
|
}>({
|
||||||
|
|
@ -82,6 +88,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
||||||
formDataOcr: Record<string, any>;
|
formDataOcr: Record<string, any>;
|
||||||
isImageEdit: boolean;
|
isImageEdit: boolean;
|
||||||
currentCustomerId?: string;
|
currentCustomerId?: string;
|
||||||
|
imageList: { list: string[]; selectedImage: string };
|
||||||
}>({
|
}>({
|
||||||
dialogType: 'info',
|
dialogType: 'info',
|
||||||
dialogOpen: false,
|
dialogOpen: false,
|
||||||
|
|
@ -98,6 +105,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
||||||
treeFile: [],
|
treeFile: [],
|
||||||
formDataOcr: {},
|
formDataOcr: {},
|
||||||
isImageEdit: false,
|
isImageEdit: false,
|
||||||
|
imageList: { list: [], selectedImage: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -160,6 +168,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
||||||
state.value.editCustomerCode = data.code;
|
state.value.editCustomerCode = data.code;
|
||||||
state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
|
state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
|
||||||
state.value.defaultCustomerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
|
state.value.defaultCustomerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
|
||||||
|
currentBranchRootId.value = data.branch[0].id || '';
|
||||||
|
|
||||||
resetFormData.registeredBranchId = data.registeredBranchId;
|
resetFormData.registeredBranchId = data.registeredBranchId;
|
||||||
resetFormData.status = data.status;
|
resetFormData.status = data.status;
|
||||||
|
|
@ -255,7 +264,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
||||||
async function addCurrentCustomerBranch() {
|
async function addCurrentCustomerBranch() {
|
||||||
if (currentFormData.value.customerBranch?.some((v) => !v.id)) return;
|
if (currentFormData.value.customerBranch?.some((v) => !v.id)) return;
|
||||||
currentFormData.value.customerBranch?.push({
|
currentFormData.value.customerBranch?.push({
|
||||||
id: '',
|
|
||||||
customerId: '',
|
customerId: '',
|
||||||
branchCode:
|
branchCode:
|
||||||
currentFormData.value.customerBranch.length !== 0
|
currentFormData.value.customerBranch.length !== 0
|
||||||
|
|
@ -495,11 +503,14 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
onCreateImageList,
|
||||||
tabFieldRequired,
|
tabFieldRequired,
|
||||||
registerAbleBranchOption,
|
registerAbleBranchOption,
|
||||||
state,
|
state,
|
||||||
resetFormData,
|
resetFormData,
|
||||||
currentFormData,
|
currentFormData,
|
||||||
|
currentBranchRootId,
|
||||||
|
|
||||||
isFormDataDifferent,
|
isFormDataDifferent,
|
||||||
resetForm,
|
resetForm,
|
||||||
assignFormData,
|
assignFormData,
|
||||||
|
|
@ -779,6 +790,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
ocr: boolean;
|
ocr: boolean;
|
||||||
|
imageList: { list: string[]; selectedImage: string };
|
||||||
}>({
|
}>({
|
||||||
currentBranchId: '',
|
currentBranchId: '',
|
||||||
isImageEdit: false,
|
isImageEdit: false,
|
||||||
|
|
@ -803,6 +815,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
||||||
infoEmployeePersonCard: [],
|
infoEmployeePersonCard: [],
|
||||||
formDataEmployeeOwner: undefined,
|
formDataEmployeeOwner: undefined,
|
||||||
ocr: false,
|
ocr: false,
|
||||||
|
imageList: { list: [], selectedImage: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultFormData: EmployeeCreate & { image?: File } = {
|
const defaultFormData: EmployeeCreate & { image?: File } = {
|
||||||
|
|
@ -955,6 +968,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
||||||
state.value.currentIndexVisa = -1;
|
state.value.currentIndexVisa = -1;
|
||||||
state.value.currentIndexCheckup = -1;
|
state.value.currentIndexCheckup = -1;
|
||||||
state.value.currentIndexWorkHistory = -1;
|
state.value.currentIndexWorkHistory = -1;
|
||||||
|
state.value.imageList = { list: [], selectedImage: '' };
|
||||||
// state.value.currentTab = 'personalInfo';
|
// state.value.currentTab = 'personalInfo';
|
||||||
if (clean) {
|
if (clean) {
|
||||||
state.value.formDataEmployeeOwner = undefined;
|
state.value.formDataEmployeeOwner = undefined;
|
||||||
|
|
@ -1384,12 +1398,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
||||||
statusSave: true,
|
statusSave: true,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
employeeOtherInfo: structuredClone(
|
employeeOtherInfo: structuredClone({
|
||||||
{
|
...(payload.employeeOtherInfo ?? {}),
|
||||||
...payload.employeeOtherInfo,
|
statusSave: true,
|
||||||
statusSave: !!payload.employeeOtherInfo?.id ? true : false,
|
}),
|
||||||
} || {},
|
|
||||||
),
|
|
||||||
employeeWork: structuredClone(
|
employeeWork: structuredClone(
|
||||||
payload.employeeWork?.length === 0
|
payload.employeeWork?.length === 0
|
||||||
? state.value.dialogModal
|
? state.value.dialogModal
|
||||||
|
|
@ -1663,6 +1675,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
||||||
state,
|
state,
|
||||||
currentFromDataEmployee,
|
currentFromDataEmployee,
|
||||||
resetEmployeeData,
|
resetEmployeeData,
|
||||||
|
|
||||||
addPassport,
|
addPassport,
|
||||||
addVisa,
|
addVisa,
|
||||||
addCheckup,
|
addCheckup,
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@ const {
|
||||||
deleteWork,
|
deleteWork,
|
||||||
|
|
||||||
importProduct,
|
importProduct,
|
||||||
|
|
||||||
|
productExport,
|
||||||
} = productServiceStore;
|
} = productServiceStore;
|
||||||
|
|
||||||
const { workNameItems } = storeToRefs(productServiceStore);
|
const { workNameItems } = storeToRefs(productServiceStore);
|
||||||
|
|
@ -1169,6 +1171,7 @@ function clearFormService() {
|
||||||
profileSubmit.value = false;
|
profileSubmit.value = false;
|
||||||
imageProduct.value = undefined;
|
imageProduct.value = undefined;
|
||||||
profileFileImg.value = null;
|
profileFileImg.value = null;
|
||||||
|
serviceTab.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameFormService() {
|
function sameFormService() {
|
||||||
|
|
@ -1896,6 +1899,25 @@ async function submitWorkName(
|
||||||
else await editWork(workId, data);
|
else await editWork(workId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function triggerExport() {
|
||||||
|
productExport({
|
||||||
|
pageSize: 100_000,
|
||||||
|
productGroupId: currentIdGroup.value,
|
||||||
|
query: !!inputSearchProductAndService.value
|
||||||
|
? inputSearchProductAndService.value
|
||||||
|
: undefined,
|
||||||
|
status:
|
||||||
|
currentStatus.value === 'INACTIVE'
|
||||||
|
? 'INACTIVE'
|
||||||
|
: currentStatus.value === 'ACTIVE'
|
||||||
|
? 'ACTIVE'
|
||||||
|
: undefined,
|
||||||
|
|
||||||
|
startDate: searchDate.value[0] ? new Date(searchDate.value[0]) : undefined,
|
||||||
|
endDate: searchDate.value[1] ? new Date(searchDate.value[1]) : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => formService.value.attributes.workflowId,
|
() => formService.value.attributes.workflowId,
|
||||||
async (a, b) => {
|
async (a, b) => {
|
||||||
|
|
@ -2229,7 +2251,6 @@ watch(
|
||||||
</AdvanceSearch>
|
</AdvanceSearch>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div class="row col-md-6" style="white-space: nowrap">
|
<div class="row col-md-6" style="white-space: nowrap">
|
||||||
<q-select
|
<q-select
|
||||||
v-show="$q.screen.gt.sm"
|
v-show="$q.screen.gt.sm"
|
||||||
|
|
@ -2780,6 +2801,13 @@ watch(
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SaveButton
|
||||||
|
icon-only
|
||||||
|
:icon="'material-symbols:download'"
|
||||||
|
color="var(--info-bg)"
|
||||||
|
@click.stop="triggerExport()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row col-md-6" style="white-space: nowrap">
|
<div class="row col-md-6" style="white-space: nowrap">
|
||||||
|
|
@ -4357,6 +4385,7 @@ watch(
|
||||||
|
|
||||||
<!-- add service -->
|
<!-- add service -->
|
||||||
<DialogForm
|
<DialogForm
|
||||||
|
v-if="dialogService"
|
||||||
hide-footer
|
hide-footer
|
||||||
no-address
|
no-address
|
||||||
no-app-box
|
no-app-box
|
||||||
|
|
@ -4722,6 +4751,7 @@ watch(
|
||||||
<!-- edit service edit package-->
|
<!-- edit service edit package-->
|
||||||
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
|
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
|
||||||
<DialogForm
|
<DialogForm
|
||||||
|
v-if="dialogServiceEdit"
|
||||||
hide-footer
|
hide-footer
|
||||||
no-address
|
no-address
|
||||||
height="95vh"
|
height="95vh"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, reactive, ref, watch, computed } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref, watch, computed } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
@ -60,7 +60,6 @@ const flowStore = useFlowStore();
|
||||||
const userBranch = useMyBranch();
|
const userBranch = useMyBranch();
|
||||||
const navigatorStore = useNavigator();
|
const navigatorStore = useNavigator();
|
||||||
const customerStore = useCustomerStore();
|
const customerStore = useCustomerStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fetchListOfOptionBranch,
|
fetchListOfOptionBranch,
|
||||||
customerFormUndo,
|
customerFormUndo,
|
||||||
|
|
@ -76,6 +75,7 @@ const {
|
||||||
const {
|
const {
|
||||||
state: customerFormState,
|
state: customerFormState,
|
||||||
currentFormData: customerFormData,
|
currentFormData: customerFormData,
|
||||||
|
currentBranchRootId,
|
||||||
registerAbleBranchOption,
|
registerAbleBranchOption,
|
||||||
tabFieldRequired,
|
tabFieldRequired,
|
||||||
} = storeToRefs(customerFormStore);
|
} = storeToRefs(customerFormStore);
|
||||||
|
|
@ -89,6 +89,8 @@ const fieldSelectedOption = computed(() => {
|
||||||
value: v.name,
|
value: v.name,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const keyAddDialog = ref<number>(0);
|
||||||
const special = ref(false);
|
const special = ref(false);
|
||||||
const branchId = ref('');
|
const branchId = ref('');
|
||||||
const agentPrice = ref<boolean>(false);
|
const agentPrice = ref<boolean>(false);
|
||||||
|
|
@ -273,6 +275,10 @@ const customerNameInfo = computed(() => {
|
||||||
return name || '-';
|
return name || '-';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleWindowFocus() {
|
||||||
|
fetchQuotationList();
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
pageState.gridView = $q.screen.lt.md ? true : false;
|
pageState.gridView = $q.screen.lt.md ? true : false;
|
||||||
navigatorStore.current.title = 'quotation.title';
|
navigatorStore.current.title = 'quotation.title';
|
||||||
|
|
@ -310,6 +316,12 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
flowStore.rotate();
|
flowStore.rotate();
|
||||||
|
|
||||||
|
window.addEventListener('focus', handleWindowFocus);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('focus', handleWindowFocus);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetchQuotationList(mobileFetch?: boolean) {
|
async function fetchQuotationList(mobileFetch?: boolean) {
|
||||||
|
|
@ -765,8 +777,13 @@ async function filterBySellerId() {
|
||||||
:worker-count="item.row._count.worker"
|
:worker-count="item.row._count.worker"
|
||||||
:worker-max="item.row.workerMax || item.row._count.worker"
|
:worker-max="item.row.workerMax || item.row._count.worker"
|
||||||
:customer-name="
|
:customer-name="
|
||||||
item.row.customerBranch.registerName ||
|
item.row.customerBranch.customer.customerType === 'CORP'
|
||||||
`${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}`
|
? $i18n.locale === 'tha'
|
||||||
|
? item.row.customerBranch.registerName
|
||||||
|
: item.row.customerBranch.registerNameEN
|
||||||
|
: $i18n.locale === 'tha'
|
||||||
|
? `${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}`
|
||||||
|
: `${item.row.customerBranch.firstNameEN || '-'} ${item.row.customerBranch.lastNameEN || ''}`
|
||||||
"
|
"
|
||||||
:reporter="
|
:reporter="
|
||||||
$i18n.locale === 'eng'
|
$i18n.locale === 'eng'
|
||||||
|
|
@ -865,6 +882,7 @@ async function filterBySellerId() {
|
||||||
<!-- NOTE: START - Quotation Form, Add Quotation -->
|
<!-- NOTE: START - Quotation Form, Add Quotation -->
|
||||||
|
|
||||||
<DialogForm
|
<DialogForm
|
||||||
|
:key="keyAddDialog"
|
||||||
:title="$t('general.add', { text: $t('quotation.title') })"
|
:title="$t('general.add', { text: $t('quotation.title') })"
|
||||||
v-model:modal="pageState.addModal"
|
v-model:modal="pageState.addModal"
|
||||||
:submit-label="$t('general.add', { text: $t('quotation.title') })"
|
:submit-label="$t('general.add', { text: $t('quotation.title') })"
|
||||||
|
|
@ -874,13 +892,14 @@ async function filterBySellerId() {
|
||||||
:submit="
|
:submit="
|
||||||
() => {
|
() => {
|
||||||
quotationFormState.mode = 'create';
|
quotationFormState.mode = 'create';
|
||||||
|
quotationFormData.customerBranchId = currentBranchRootId;
|
||||||
triggerQuotationDialog({ statusDialog: 'create' });
|
triggerQuotationDialog({ statusDialog: 'create' });
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
:close="
|
:close="
|
||||||
() => {
|
() => {
|
||||||
branchId = '';
|
branchId = '';
|
||||||
quotationFormData.customerBranchId = '';
|
currentBranchRootId = '';
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
:beforeClose="
|
:beforeClose="
|
||||||
|
|
@ -930,7 +949,7 @@ async function filterBySellerId() {
|
||||||
v-model:agent-price="agentPrice"
|
v-model:agent-price="agentPrice"
|
||||||
v-model:branch-id="branchId"
|
v-model:branch-id="branchId"
|
||||||
v-model:special="special"
|
v-model:special="special"
|
||||||
v-model:customer-branch-id="quotationFormData.customerBranchId"
|
v-model:customer-branch-id="currentBranchRootId"
|
||||||
@add-customer="triggerSelectTypeCustomerd()"
|
@add-customer="triggerSelectTypeCustomerd()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -999,6 +1018,7 @@ async function filterBySellerId() {
|
||||||
() => {
|
() => {
|
||||||
customerFormState.dialogModal = false;
|
customerFormState.dialogModal = false;
|
||||||
onCreateImageList = { selectedImage: '', list: [] };
|
onCreateImageList = { selectedImage: '', list: [] };
|
||||||
|
keyAddDialog++;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|
@ -1190,7 +1210,7 @@ async function filterBySellerId() {
|
||||||
customerFormData.customerBranch[0].legalPersonNo
|
customerFormData.customerBranch[0].legalPersonNo
|
||||||
"
|
"
|
||||||
v-model:business-type="
|
v-model:business-type="
|
||||||
customerFormData.customerBranch[0].businessType
|
customerFormData.customerBranch[0].businessTypeId
|
||||||
"
|
"
|
||||||
v-model:job-position="
|
v-model:job-position="
|
||||||
customerFormData.customerBranch[0].jobPosition
|
customerFormData.customerBranch[0].jobPosition
|
||||||
|
|
@ -1271,7 +1291,6 @@ async function filterBySellerId() {
|
||||||
res = await customerStore.createBranch({
|
res = await customerStore.createBranch({
|
||||||
...customerFormData.customerBranch[idx],
|
...customerFormData.customerBranch[idx],
|
||||||
customerId: customerFormState.editCustomerId || '',
|
customerId: customerFormState.editCustomerId || '',
|
||||||
id: undefined,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { baseUrl, dialog } from 'stores/utils';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
import { QFile, QMenu } from 'quasar';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useConfigStore } from 'stores/config';
|
|
||||||
import { formatNumberDecimal } from 'stores/utils';
|
|
||||||
import { MainButton } from 'components/button';
|
|
||||||
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
import { reactive, ref } from 'vue';
|
|
||||||
import { useQuotationPayment } from 'src/stores/quotations';
|
import { useQuotationPayment } from 'src/stores/quotations';
|
||||||
import {
|
import {
|
||||||
PaymentPayload,
|
PaymentPayload,
|
||||||
|
|
@ -18,16 +12,25 @@ import {
|
||||||
QuotationPaymentData,
|
QuotationPaymentData,
|
||||||
} from 'src/stores/quotations/types';
|
} from 'src/stores/quotations/types';
|
||||||
import { dateFormatJS } from 'src/utils/datetime';
|
import { dateFormatJS } from 'src/utils/datetime';
|
||||||
import { QFile, QMenu } from 'quasar';
|
|
||||||
import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue';
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
import { DebitNote } from 'src/stores/debit-note';
|
import { DebitNote } from 'src/stores/debit-note';
|
||||||
|
import { baseUrl, dialog, formatNumberDecimal } from 'stores/utils';
|
||||||
|
import { useConfigStore } from 'stores/config';
|
||||||
|
import useBranchStore from 'src/stores/branch';
|
||||||
|
import useOptionStore from 'src/stores/options';
|
||||||
|
|
||||||
|
import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue';
|
||||||
|
import SelectInput from 'src/components/shared/SelectInput.vue';
|
||||||
|
import { SaveButton, EditButton, UndoButton } from 'components/button';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { fetchListBankByBranch } = useBranchStore();
|
||||||
|
const { mapOption } = useOptionStore();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const quotationPayment = useQuotationPayment();
|
const quotationPayment = useQuotationPayment();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
|
|
||||||
const prop = defineProps<{
|
const prop = defineProps<{
|
||||||
|
branchId: string;
|
||||||
data?: Quotation | QuotationFull | DebitNote;
|
data?: Quotation | QuotationFull | DebitNote;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
isDebitNote?: boolean;
|
isDebitNote?: boolean;
|
||||||
|
|
@ -38,8 +41,16 @@ const firstCodePayment = defineModel<string>('firstCodePayment');
|
||||||
const refQFile = ref<InstanceType<typeof QFile>[]>([]);
|
const refQFile = ref<InstanceType<typeof QFile>[]>([]);
|
||||||
const refQMenu = ref<InstanceType<typeof QMenu>[]>([]);
|
const refQMenu = ref<InstanceType<typeof QMenu>[]>([]);
|
||||||
const paymentData = ref<QuotationPaymentData[]>([]);
|
const paymentData = ref<QuotationPaymentData[]>([]);
|
||||||
const submitPaymentData = ref<QuotationPaymentData[]>([]);
|
const accountOpt = ref<{ label: string; value: string }[]>([]);
|
||||||
const payAll = ref<boolean>(false);
|
const formPaymentMethod = ref<
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
channel: string | null;
|
||||||
|
reference: string | null;
|
||||||
|
account: string | null;
|
||||||
|
isEdit?: boolean;
|
||||||
|
}[]
|
||||||
|
>([]);
|
||||||
const paymentStatusOpts = [
|
const paymentStatusOpts = [
|
||||||
{
|
{
|
||||||
status: 'PaymentInProcess',
|
status: 'PaymentInProcess',
|
||||||
|
|
@ -172,7 +183,7 @@ async function selectStatus(
|
||||||
payment.paymentStatus = status;
|
payment.paymentStatus = status;
|
||||||
const payload = {
|
const payload = {
|
||||||
paymentStatus: payment.paymentStatus,
|
paymentStatus: payment.paymentStatus,
|
||||||
date: new Date(payment.date),
|
date: new Date(),
|
||||||
amount: payment.amount,
|
amount: payment.amount,
|
||||||
};
|
};
|
||||||
const res = await quotationPayment.updateQuotationPayment(
|
const res = await quotationPayment.updateQuotationPayment(
|
||||||
|
|
@ -188,18 +199,30 @@ async function selectStatus(
|
||||||
refQMenu.value[index].hide();
|
refQMenu.value[index].hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function triggerSubmit() {
|
async function triggerSubmit(id: string) {
|
||||||
submitPaymentData.value.forEach(async (p) => {
|
const index = paymentData.value.findIndex((p) => p.id === id);
|
||||||
|
if (index === -1) return;
|
||||||
|
|
||||||
|
const p = paymentData.value[index];
|
||||||
|
|
||||||
const payload: PaymentPayload = {
|
const payload: PaymentPayload = {
|
||||||
paymentStatus: p.paymentStatus,
|
paymentStatus: p.paymentStatus,
|
||||||
date: new Date(p.date),
|
date: new Date(p.date),
|
||||||
amount: p.amount,
|
amount: p.amount,
|
||||||
|
account: formPaymentMethod.value[index].account,
|
||||||
|
channel: formPaymentMethod.value[index].channel,
|
||||||
|
reference: formPaymentMethod.value[index].reference,
|
||||||
};
|
};
|
||||||
|
|
||||||
await quotationPayment.updateQuotationPayment(p.id, payload);
|
await quotationPayment.updateQuotationPayment(p.id, payload);
|
||||||
});
|
formPaymentMethod.value[index].isEdit = false;
|
||||||
|
setTimeout(async () => {
|
||||||
|
await fetchData();
|
||||||
|
await getSlipList(paymentData.value[index], index);
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
async function fetchData() {
|
||||||
if (!prop.data) return;
|
if (!prop.data) return;
|
||||||
const ret = await quotationPayment.getQuotationPayment({
|
const ret = await quotationPayment.getQuotationPayment({
|
||||||
quotationId: prop.isDebitNote === true ? undefined : prop.data.id,
|
quotationId: prop.isDebitNote === true ? undefined : prop.data.id,
|
||||||
|
|
@ -209,6 +232,13 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
if (ret) {
|
if (ret) {
|
||||||
paymentData.value = ret.result;
|
paymentData.value = ret.result;
|
||||||
|
formPaymentMethod.value = ret.result.map((p, i) => ({
|
||||||
|
id: p.id,
|
||||||
|
channel: p.channel,
|
||||||
|
reference: p.reference,
|
||||||
|
account: p.account,
|
||||||
|
isEdit: formPaymentMethod.value[i]?.isEdit || false,
|
||||||
|
}));
|
||||||
slipFile.value = paymentData.value.map((v, i) => {
|
slipFile.value = paymentData.value.map((v, i) => {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
firstCodePayment.value = v.code;
|
firstCodePayment.value = v.code;
|
||||||
|
|
@ -219,17 +249,31 @@ onMounted(async () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchBankOption() {
|
||||||
|
const bankOption = await fetchListBankByBranch(prop.branchId);
|
||||||
|
accountOpt.value = bankOption
|
||||||
|
.map((b) => {
|
||||||
|
const name =
|
||||||
|
`${b.accountName} ${mapOption(b.bankName)} ${mapOption(b.accountType)} ${b.accountNumber}`.trim();
|
||||||
|
if (!name) return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: name,
|
||||||
|
value: name,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((i) => !!i);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchData();
|
||||||
|
await fetchBankOption();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-form
|
<div class="column no-wrap">
|
||||||
greedy
|
|
||||||
@submit.prevent
|
|
||||||
@validation-success="triggerSubmit"
|
|
||||||
class="column full-height"
|
|
||||||
v-if="data"
|
|
||||||
>
|
|
||||||
<div class="col column no-wrap">
|
|
||||||
<!-- PRICE DETAIL -->
|
<!-- PRICE DETAIL -->
|
||||||
<section class="bordered rounded surface-1" style="overflow: hidden">
|
<section class="bordered rounded surface-1" style="overflow: hidden">
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
|
@ -293,10 +337,8 @@ onMounted(async () => {
|
||||||
<span class="q-ml-auto" style="color: var(--foreground)">
|
<span class="q-ml-auto" style="color: var(--foreground)">
|
||||||
฿
|
฿
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(data.totalPrice - data.totalDiscount, 2) ||
|
||||||
data.totalPrice - data.totalDiscount,
|
'0.00'
|
||||||
2,
|
|
||||||
) || '0.00'
|
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -487,7 +529,7 @@ onMounted(async () => {
|
||||||
<!-- payment item -->
|
<!-- payment item -->
|
||||||
<section class="row">
|
<section class="row">
|
||||||
<div
|
<div
|
||||||
v-for="(p, i) in paymentData"
|
v-for="(payment, i) in paymentData"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="bordered rounded surface-1 q-mb-md col-12"
|
class="bordered rounded surface-1 q-mb-md col-12"
|
||||||
style="overflow: hidden"
|
style="overflow: hidden"
|
||||||
|
|
@ -496,7 +538,7 @@ onMounted(async () => {
|
||||||
hide-expand-icon
|
hide-expand-icon
|
||||||
v-model="state.payExpansion[i]"
|
v-model="state.payExpansion[i]"
|
||||||
header-style="padding:0px"
|
header-style="padding:0px"
|
||||||
@before-show="getSlipList(p, i)"
|
@before-show="getSlipList(payment, i)"
|
||||||
>
|
>
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<div class="column full-width">
|
<div class="column full-width">
|
||||||
|
|
@ -505,7 +547,7 @@ onMounted(async () => {
|
||||||
>
|
>
|
||||||
<span class="text-weight-medium column">
|
<span class="text-weight-medium column">
|
||||||
{{ $t('quotation.periodNo') }} {{ i + 1 }}
|
{{ $t('quotation.periodNo') }} {{ i + 1 }}
|
||||||
{{ monthDisplay(p.createdAt) }}
|
{{ monthDisplay(payment.createdAt) }}
|
||||||
<!-- {{ monthDisplay(p.date) }} -->
|
<!-- {{ monthDisplay(p.date) }} -->
|
||||||
<!-- <span -->
|
<!-- <span -->
|
||||||
<!-- class="text-caption app-text-muted-2" -->
|
<!-- class="text-caption app-text-muted-2" -->
|
||||||
|
|
@ -529,25 +571,29 @@ onMounted(async () => {
|
||||||
padding="4px 8px"
|
padding="4px 8px"
|
||||||
class="rounded text-capitalize text-weight-regular payment-wait row items-center"
|
class="rounded text-capitalize text-weight-regular payment-wait row items-center"
|
||||||
:class="{
|
:class="{
|
||||||
'payment-pending': p.paymentStatus === 'PaymentWait',
|
'payment-pending':
|
||||||
|
payment.paymentStatus === 'PaymentWait',
|
||||||
'payment-process':
|
'payment-process':
|
||||||
p.paymentStatus === 'PaymentInProcess',
|
payment.paymentStatus === 'PaymentInProcess',
|
||||||
'payment-retry': p.paymentStatus === 'PaymentRetry',
|
'payment-retry':
|
||||||
|
payment.paymentStatus === 'PaymentRetry',
|
||||||
'payment-success':
|
'payment-success':
|
||||||
p.paymentStatus === 'PaymentSuccess',
|
payment.paymentStatus === 'PaymentSuccess',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
size="xs"
|
size="xs"
|
||||||
:name="
|
:name="
|
||||||
paymentStatusOpts.find(
|
paymentStatusOpts.find(
|
||||||
(s) => s.status === p.paymentStatus,
|
(s) => s.status === payment.paymentStatus,
|
||||||
)?.icon || 'mdi-hand-coin-outline'
|
)?.icon || 'mdi-hand-coin-outline'
|
||||||
"
|
"
|
||||||
class="q-pr-sm"
|
class="q-pr-sm"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ $t(`quotation.receiptDialog.${p.paymentStatus}`) }}
|
{{
|
||||||
|
$t(`quotation.receiptDialog.${payment.paymentStatus}`)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<q-icon
|
<q-icon
|
||||||
v-if="!readonly"
|
v-if="!readonly"
|
||||||
|
|
@ -568,16 +614,16 @@ onMounted(async () => {
|
||||||
<q-item
|
<q-item
|
||||||
:id="`btn-payment-${opts.status}`"
|
:id="`btn-payment-${opts.status}`"
|
||||||
v-if="
|
v-if="
|
||||||
(p.paymentStatus === 'PaymentWait' &&
|
(payment.paymentStatus === 'PaymentWait' &&
|
||||||
opts.status !== 'PaymentRetry') ||
|
opts.status !== 'PaymentRetry') ||
|
||||||
(p.paymentStatus === 'PaymentInProcess' &&
|
(payment.paymentStatus === 'PaymentInProcess' &&
|
||||||
opts.status !== 'PaymentInProcess') ||
|
opts.status !== 'PaymentInProcess') ||
|
||||||
(p.paymentStatus === 'PaymentRetry' &&
|
(payment.paymentStatus === 'PaymentRetry' &&
|
||||||
opts.status !== 'PaymentRetry')
|
opts.status !== 'PaymentRetry')
|
||||||
"
|
"
|
||||||
clickable
|
clickable
|
||||||
class="row items-center"
|
class="row items-center"
|
||||||
@click="selectStatus(p, opts.status, i)"
|
@click="selectStatus(payment, opts.status, i)"
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
:name="opts.icon"
|
:name="opts.icon"
|
||||||
|
|
@ -599,7 +645,7 @@ onMounted(async () => {
|
||||||
class="q-px-sm q-ml-auto"
|
class="q-px-sm q-ml-auto"
|
||||||
style="color: var(--foreground)"
|
style="color: var(--foreground)"
|
||||||
>
|
>
|
||||||
{{ formatNumberDecimal(p.amount, 2) }}
|
{{ formatNumberDecimal(payment.amount, 2) }}
|
||||||
</span>
|
</span>
|
||||||
<q-btn
|
<q-btn
|
||||||
dense
|
dense
|
||||||
|
|
@ -608,15 +654,125 @@ onMounted(async () => {
|
||||||
padding="0"
|
padding="0"
|
||||||
id="btn-show-file"
|
id="btn-show-file"
|
||||||
:icon="`mdi-chevron-${state.payExpansion[i] ? 'down' : 'up'}`"
|
:icon="`mdi-chevron-${state.payExpansion[i] ? 'down' : 'up'}`"
|
||||||
@click.stop="
|
@click.stop="state.payExpansion[i] = !state.payExpansion[i]"
|
||||||
state.payExpansion[i] = !state.payExpansion[i]
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- วิธีการรับชำระ -->
|
||||||
|
<template v-if="payment.paymentStatus === 'PaymentSuccess'">
|
||||||
|
<section
|
||||||
|
class="q-px-md q-py-xs text-weight-medium row items-center"
|
||||||
|
style="background-color: hsla(var(--info-bg) / 0.1)"
|
||||||
|
>
|
||||||
|
{{ $t('quotation.receiptDialog.paymentMethod') }}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<q-form
|
||||||
|
class="column full-height"
|
||||||
|
@submit.prevent
|
||||||
|
@submit="triggerSubmit(payment.id)"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
|
class="surface-2 q-px-md q-py-sm row q-col-gutter-sm items-center"
|
||||||
|
>
|
||||||
|
<SelectInput
|
||||||
|
id="input-payment-channel"
|
||||||
|
for="input-payment-channel"
|
||||||
|
:readonly="
|
||||||
|
readonly ||
|
||||||
|
(!formPaymentMethod[i].isEdit && !!payment.channel)
|
||||||
|
"
|
||||||
|
v-model="formPaymentMethod[i].channel"
|
||||||
|
class="col-md-2 col-6"
|
||||||
|
:rules="[
|
||||||
|
(val: string) => !!val || $t('form.error.required'),
|
||||||
|
]"
|
||||||
|
:option="[
|
||||||
|
{
|
||||||
|
label: $t('creditNote.label.Cash'),
|
||||||
|
value: 'Cash',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('creditNote.label.BankTransfer'),
|
||||||
|
value: 'BankTransfer',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:label="$t('quotation.receiptDialog.paymentMethod')"
|
||||||
|
@update:model-value="
|
||||||
|
() => {
|
||||||
|
if (formPaymentMethod[i].channel === 'Cash') {
|
||||||
|
formPaymentMethod[i].reference = null;
|
||||||
|
formPaymentMethod[i].account = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
class="col-md-3 col-6"
|
||||||
|
v-model="formPaymentMethod[i].reference"
|
||||||
|
:readonly="
|
||||||
|
readonly ||
|
||||||
|
(!formPaymentMethod[i].isEdit && !!payment.channel)
|
||||||
|
"
|
||||||
|
:label="$t('quotation.refNo')"
|
||||||
|
:rules="[
|
||||||
|
(val: string) => !!val || $t('form.error.required'),
|
||||||
|
]"
|
||||||
|
hide-bottom-space
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
|
||||||
|
id="select-payment-account"
|
||||||
|
for="select-payment-account"
|
||||||
|
:readonly="
|
||||||
|
readonly ||
|
||||||
|
(!formPaymentMethod[i].isEdit && !!payment.channel)
|
||||||
|
"
|
||||||
|
v-model="formPaymentMethod[i].account"
|
||||||
|
class="col"
|
||||||
|
:option="accountOpt"
|
||||||
|
:label="$t('quotation.bankAccount')"
|
||||||
|
:rules="[
|
||||||
|
(val: string) => !!val || $t('form.error.required'),
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<div class="q-ml-auto">
|
||||||
|
<UndoButton
|
||||||
|
v-if="formPaymentMethod[i].isEdit"
|
||||||
|
icon-only
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
formPaymentMethod[i].isEdit = false;
|
||||||
|
formPaymentMethod[i].channel = payment.channel;
|
||||||
|
formPaymentMethod[i].reference = payment.reference;
|
||||||
|
formPaymentMethod[i].account = payment.account;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<SaveButton
|
||||||
|
v-if="!payment.channel || formPaymentMethod[i].isEdit"
|
||||||
|
icon-only
|
||||||
|
type="submit"
|
||||||
|
/>
|
||||||
|
<EditButton
|
||||||
|
v-if="
|
||||||
|
payment.channel && formPaymentMethod[i].isEdit === false
|
||||||
|
"
|
||||||
|
icon-only
|
||||||
|
@click="formPaymentMethod[i].isEdit = true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- อัปโหลดใบเสร็จ -->
|
||||||
|
<section
|
||||||
class="q-px-md q-py-xs text-weight-medium row items-center"
|
class="q-px-md q-py-xs text-weight-medium row items-center"
|
||||||
style="background-color: hsla(var(--info-bg) / 0.1)"
|
style="background-color: hsla(var(--info-bg) / 0.1)"
|
||||||
>
|
>
|
||||||
|
|
@ -625,7 +781,7 @@ onMounted(async () => {
|
||||||
msg: $t('quotation.receiptDialog.slip'),
|
msg: $t('quotation.receiptDialog.slip'),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</section>
|
||||||
<div class="surface-2 q-pa-md">
|
<div class="surface-2 q-pa-md">
|
||||||
<div
|
<div
|
||||||
class="upload-section column rounded q-py-md full-height items-center justify-center no-wrap"
|
class="upload-section column rounded q-py-md full-height items-center justify-center no-wrap"
|
||||||
|
|
@ -652,7 +808,9 @@ onMounted(async () => {
|
||||||
ref="refQFile"
|
ref="refQFile"
|
||||||
v-show="false"
|
v-show="false"
|
||||||
v-model="slipFile[i].file"
|
v-model="slipFile[i].file"
|
||||||
@update:model-value="triggerUpload(p, i, slipFile[i].file)"
|
@update:model-value="
|
||||||
|
triggerUpload(payment, i, slipFile[i].file)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -672,12 +830,12 @@ onMounted(async () => {
|
||||||
:name="d.name"
|
:name="d.name"
|
||||||
:progress="d.progress"
|
:progress="d.progress"
|
||||||
:uploading="{ loaded: d.loaded, total: d.total }"
|
:uploading="{ loaded: d.loaded, total: d.total }"
|
||||||
:url="`/quotation/${data.id}/payment/${p.id}/attachment/${d.name}`"
|
:url="`/payment/${payment.id}/attachment/${d.name}`"
|
||||||
icon="mdi-file-image-outline"
|
icon="mdi-file-image-outline"
|
||||||
color="hsl(var(--text-mute))"
|
color="hsl(var(--text-mute))"
|
||||||
clickable
|
clickable
|
||||||
@click="triggerViewSlip(p, d.name)"
|
@click="triggerViewSlip(payment, d.name)"
|
||||||
@close="triggerDelete(p, d.name, i)"
|
@close="triggerDelete(payment, d.name, i)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -686,7 +844,6 @@ onMounted(async () => {
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</q-form>
|
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bg-color-orange {
|
.bg-color-orange {
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ import { RouterLink, useRoute } from 'vue-router';
|
||||||
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
||||||
import { convertTemplate } from 'src/utils/string-template';
|
import { convertTemplate } from 'src/utils/string-template';
|
||||||
|
|
||||||
|
import { CustomerBranch } from 'src/stores/customer';
|
||||||
|
|
||||||
type Node = {
|
type Node = {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
opened?: boolean;
|
opened?: boolean;
|
||||||
|
|
@ -94,6 +96,8 @@ type ProductGroupId = string;
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||||
|
|
||||||
|
const customerBranchOption = ref<CustomerBranch>();
|
||||||
|
|
||||||
const employeeStore = useEmployeeStore();
|
const employeeStore = useEmployeeStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const useReceiptStore = useReceipt();
|
const useReceiptStore = useReceipt();
|
||||||
|
|
@ -157,49 +161,7 @@ const selectedWorker = ref<
|
||||||
}[];
|
}[];
|
||||||
})[]
|
})[]
|
||||||
>([]);
|
>([]);
|
||||||
const selectedWorkerItem = computed(() => {
|
const selectedWorkerItem = ref([]);
|
||||||
return [
|
|
||||||
...selectedWorker.value.map((e) => ({
|
|
||||||
foreignRefNo: e.employeePassport
|
|
||||||
? e.employeePassport[0]?.number || '-'
|
|
||||||
: '-',
|
|
||||||
employeeName:
|
|
||||||
locale.value === Lang.English
|
|
||||||
? `${e.firstNameEN} ${e.lastNameEN}`
|
|
||||||
: e.firstName
|
|
||||||
? `${e.firstName} ${e.lastName}`
|
|
||||||
: `${e.firstNameEN} ${e.lastNameEN}`,
|
|
||||||
birthDate: dateFormatJS({ date: e.dateOfBirth }),
|
|
||||||
gender: e.gender,
|
|
||||||
age: calculateAge(e.dateOfBirth),
|
|
||||||
nationality: optionStore.mapOption(e.nationality),
|
|
||||||
documentExpireDate:
|
|
||||||
e.employeePassport !== undefined &&
|
|
||||||
e.employeePassport[0]?.expireDate !== undefined
|
|
||||||
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
|
|
||||||
: '-',
|
|
||||||
imgUrl: e.selectedImage
|
|
||||||
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
|
|
||||||
: '',
|
|
||||||
status: e.status,
|
|
||||||
})),
|
|
||||||
|
|
||||||
...newWorkerList.value.map((v: any) => ({
|
|
||||||
foreignRefNo: v.passportNo,
|
|
||||||
employeeName:
|
|
||||||
locale.value === Lang.English
|
|
||||||
? `${v.firstNameEN} ${v.lastNameEN}`
|
|
||||||
: `${v.firstName} ${v.lastName}`,
|
|
||||||
birthDate: dateFormatJS({ date: v.dateOfBirth }),
|
|
||||||
gender: v.gender,
|
|
||||||
age: calculateAge(v.dateOfBirth),
|
|
||||||
nationality: optionStore.mapOption(v.nationality),
|
|
||||||
documentExpireDate: '-',
|
|
||||||
imgUrl: '',
|
|
||||||
status: 'CREATED',
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
const firstCodePayment = ref('');
|
const firstCodePayment = ref('');
|
||||||
const selectedProductGroup = ref('');
|
const selectedProductGroup = ref('');
|
||||||
const selectedInstallmentNo = ref<number[]>([]);
|
const selectedInstallmentNo = ref<number[]>([]);
|
||||||
|
|
@ -234,7 +196,7 @@ function getPrice(
|
||||||
) {
|
) {
|
||||||
if (filterHook) list = list.filter(filterHook);
|
if (filterHook) list = list.filter(filterHook);
|
||||||
|
|
||||||
return list.reduce(
|
const value = list.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
if (
|
if (
|
||||||
selectedInstallmentNo.value.length > 0 &&
|
selectedInstallmentNo.value.length > 0 &&
|
||||||
|
|
@ -244,32 +206,25 @@ function getPrice(
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalPrice = c.pricePerUnit;
|
|
||||||
const finalPriceWithVat = precisionRound(
|
|
||||||
originalPrice * (1 + (config.value?.vat || 0.07)),
|
|
||||||
);
|
|
||||||
const finalPriceNoVat =
|
|
||||||
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
|
||||||
|
|
||||||
const price = finalPriceNoVat * c.amount;
|
|
||||||
const vat =
|
|
||||||
(finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07);
|
|
||||||
|
|
||||||
const calcVat =
|
const calcVat =
|
||||||
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
||||||
|
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price);
|
const pricePerUnit =
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
|
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
|
||||||
a.vat = calcVat ? precisionRound(a.vat + vat) : a.vat;
|
|
||||||
|
const price =
|
||||||
|
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
|
||||||
|
(1 + vatFactor);
|
||||||
|
const vat = price * vatFactor;
|
||||||
|
|
||||||
|
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
|
||||||
|
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
||||||
|
a.vat = precisionRound(a.vat + vat);
|
||||||
a.vatExcluded = calcVat
|
a.vatExcluded = calcVat
|
||||||
? a.vatExcluded
|
? a.vatExcluded
|
||||||
: precisionRound(a.vatExcluded + price);
|
: precisionRound(a.vatExcluded + price);
|
||||||
a.finalPrice = precisionRound(
|
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
|
||||||
a.totalPrice -
|
|
||||||
a.totalDiscount +
|
|
||||||
a.vat -
|
|
||||||
Number(quotationFormData.value.discount || 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
|
|
@ -281,6 +236,8 @@ function getPrice(
|
||||||
finalPrice: 0,
|
finalPrice: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const summaryPrice = computed(() => getPrice(productServiceList.value));
|
const summaryPrice = computed(() => getPrice(productServiceList.value));
|
||||||
|
|
@ -559,7 +516,7 @@ async function convertDataToFormSubmit() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
selectedWorker.value.forEach((v, i) => {
|
selectedWorkerItem.value.forEach((v, i) => {
|
||||||
if (v.attachment !== undefined) {
|
if (v.attachment !== undefined) {
|
||||||
v.attachment.forEach((value) => {
|
v.attachment.forEach((value) => {
|
||||||
fileItemNewWorker.value.push({
|
fileItemNewWorker.value.push({
|
||||||
|
|
@ -576,7 +533,7 @@ async function convertDataToFormSubmit() {
|
||||||
|
|
||||||
quotationFormData.value.worker = JSON.parse(
|
quotationFormData.value.worker = JSON.parse(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
...selectedWorker.value.map((v) => {
|
...selectedWorkerItem.value.map((v) => {
|
||||||
{
|
{
|
||||||
return v.id;
|
return v.id;
|
||||||
}
|
}
|
||||||
|
|
@ -719,19 +676,13 @@ function handleUpdateProductTable(
|
||||||
// handleChangePayType(quotationFormData.value.payCondition);
|
// handleChangePayType(quotationFormData.value.payCondition);
|
||||||
// calc price
|
// calc price
|
||||||
const calc = (c: QuotationPayload['productServiceList'][number]) => {
|
const calc = (c: QuotationPayload['productServiceList'][number]) => {
|
||||||
const originalPrice = c.pricePerUnit || 0;
|
const calcVat =
|
||||||
const finalPriceWithVat = precisionRound(
|
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
||||||
originalPrice * (1 + (config.value?.vat || 0.07)),
|
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||||
);
|
const pricePerUnit =
|
||||||
const finalPriceNoVat =
|
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
|
||||||
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
const price = pricePerUnit * c.amount * (1 + vatFactor) - c.discount;
|
||||||
|
return precisionRound(price);
|
||||||
const price = finalPriceNoVat * c.amount;
|
|
||||||
const vat = c.product.calcVat
|
|
||||||
? (finalPriceNoVat * c.amount - (c.discount || 0)) *
|
|
||||||
(config.value?.vat || 0.07)
|
|
||||||
: 0;
|
|
||||||
return precisionRound(price + vat);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// installment
|
// installment
|
||||||
|
|
@ -784,21 +735,16 @@ function toggleDeleteProduct(index: number) {
|
||||||
|
|
||||||
// cal curr amount
|
// cal curr amount
|
||||||
if (currPaySplit && currTempPaySplit) {
|
if (currPaySplit && currTempPaySplit) {
|
||||||
const price = agentPrice.value
|
const calcVat =
|
||||||
? currProduct.product.agentPrice
|
currProduct.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
||||||
: currProduct.product.price;
|
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||||
const pricePerUnit = currProduct.product.vatIncluded
|
|
||||||
? price / (1 + (config.value?.vat || 0.07))
|
|
||||||
: price;
|
|
||||||
const vat =
|
|
||||||
(pricePerUnit * currProduct.amount - currProduct.discount) *
|
|
||||||
(config.value?.vat || 0.07);
|
|
||||||
const finalPrice =
|
|
||||||
pricePerUnit * currProduct.amount +
|
|
||||||
vat -
|
|
||||||
Number(currProduct.discount || 0);
|
|
||||||
|
|
||||||
currTempPaySplit.amount = currPaySplit.amount - finalPrice;
|
const price = precisionRound(
|
||||||
|
currProduct.pricePerUnit * currProduct.amount * (1 + vatFactor) -
|
||||||
|
currProduct.discount,
|
||||||
|
);
|
||||||
|
|
||||||
|
currTempPaySplit.amount = currPaySplit.amount - price;
|
||||||
currPaySplit.amount = currTempPaySplit.amount;
|
currPaySplit.amount = currTempPaySplit.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -820,7 +766,40 @@ function toggleDeleteProduct(index: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assignWorkerToSelectedWorker() {
|
async function assignWorkerToSelectedWorker() {
|
||||||
selectedWorker.value = quotationFormData.value.worker;
|
selectedWorkerItem.value = quotationFormData.value.worker.map((e) => {
|
||||||
|
return {
|
||||||
|
id: e.id,
|
||||||
|
foreignRefNo: e.employeePassport
|
||||||
|
? e.employeePassport[0]?.number || '-'
|
||||||
|
: '-',
|
||||||
|
employeeName:
|
||||||
|
locale.value === Lang.English
|
||||||
|
? `${e.firstNameEN} ${e.lastNameEN}`
|
||||||
|
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
|
||||||
|
birthDate: dateFormatJS({ date: e.dateOfBirth }),
|
||||||
|
gender: e.gender,
|
||||||
|
age: calculateAge(e.dateOfBirth),
|
||||||
|
nationality: optionStore.mapOption(e.nationality),
|
||||||
|
documentExpireDate:
|
||||||
|
e.employeePassport !== undefined &&
|
||||||
|
e.employeePassport[0]?.expireDate !== undefined
|
||||||
|
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
|
||||||
|
: '-',
|
||||||
|
imgUrl: e.selectedImage
|
||||||
|
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
|
||||||
|
: '',
|
||||||
|
employeePassport: e.employeePassport,
|
||||||
|
status: e.status,
|
||||||
|
workerNew: false,
|
||||||
|
lastNameEN: e.lastNameEN,
|
||||||
|
lastName: e.lastName,
|
||||||
|
middleNameEN: e.middleNameEN,
|
||||||
|
middleName: e.middleName,
|
||||||
|
firstNameEN: e.firstNameEN,
|
||||||
|
firstName: e.firstName,
|
||||||
|
namePrefix: e.namePrefix,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToTable(nodes: Node[]) {
|
function convertToTable(nodes: Node[]) {
|
||||||
|
|
@ -853,8 +832,7 @@ function convertToTable(nodes: Node[]) {
|
||||||
tempTableProduct.value = JSON.parse(JSON.stringify(list));
|
tempTableProduct.value = JSON.parse(JSON.stringify(list));
|
||||||
|
|
||||||
if (nodes.length > 0) {
|
if (nodes.length > 0) {
|
||||||
quotationFormData.value.paySplit = Array.apply(
|
quotationFormData.value.paySplit = Array.from(
|
||||||
null,
|
|
||||||
new Array(quotationFormData.value.paySplitCount),
|
new Array(quotationFormData.value.paySplitCount),
|
||||||
).map((_, i) => ({
|
).map((_, i) => ({
|
||||||
no: i + 1,
|
no: i + 1,
|
||||||
|
|
@ -880,21 +858,21 @@ function convertToTable(nodes: Node[]) {
|
||||||
|
|
||||||
function convertEmployeeToTable(selected: Employee[]) {
|
function convertEmployeeToTable(selected: Employee[]) {
|
||||||
productServiceList.value.forEach((v) => {
|
productServiceList.value.forEach((v) => {
|
||||||
if (selectedWorker.value.length === 0 && v.amount === 1) v.amount -= 1;
|
if (selectedWorkerItem.value.length === 0 && v.amount === 1) v.amount -= 1;
|
||||||
|
|
||||||
v.amount = Math.max(
|
v.amount = Math.max(
|
||||||
v.amount + selected.length - selectedWorker.value.length,
|
v.amount + selected.length - selectedWorkerItem.value.length,
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
|
||||||
const oldWorkerId: string[] = [];
|
const oldWorkerId: string[] = [];
|
||||||
const newWorkerIndex: number[] = [];
|
const newWorkerIndex: number[] = [];
|
||||||
|
|
||||||
selectedWorker.value.forEach((item, i) => {
|
selectedWorkerItem.value.forEach((item, i) => {
|
||||||
if (v.workerIndex.includes(i)) oldWorkerId.push(item.id);
|
if (v.workerIndex.includes(i)) oldWorkerId.push(item.id);
|
||||||
});
|
});
|
||||||
selected.forEach((item, i) => {
|
selected.forEach((item, i) => {
|
||||||
if (selectedWorker.value.find((n) => item.id === n.id)) return;
|
if (selectedWorkerItem.value.find((n) => item.id === n.id)) return;
|
||||||
newWorkerIndex.push(i);
|
newWorkerIndex.push(i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -907,7 +885,7 @@ function convertEmployeeToTable(selected: Employee[]) {
|
||||||
pageState.employeeModal = false;
|
pageState.employeeModal = false;
|
||||||
quotationFormData.value.workerMax = Math.max(
|
quotationFormData.value.workerMax = Math.max(
|
||||||
quotationFormData.value.workerMax || 1,
|
quotationFormData.value.workerMax || 1,
|
||||||
selectedWorker.value.length,
|
selectedWorkerItem.value.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -980,6 +958,71 @@ function viewProductFile(data: ProductRelation) {
|
||||||
pageState.imageDialogUrl = base64 ? base64[1] : '';
|
pageState.imageDialogUrl = base64 ? base64[1] : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function combineWorker(newWorker: any, oldWorker: any) {
|
||||||
|
selectedWorkerItem.value = [
|
||||||
|
...oldWorker.map((e) => ({
|
||||||
|
id: e.id,
|
||||||
|
foreignRefNo: e.employeePassport
|
||||||
|
? e.employeePassport[0]?.number || '-'
|
||||||
|
: '-',
|
||||||
|
employeeName:
|
||||||
|
locale.value === Lang.English
|
||||||
|
? `${e.firstNameEN} ${e.lastNameEN}`
|
||||||
|
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
|
||||||
|
birthDate: dateFormatJS({ date: e.dateOfBirth }),
|
||||||
|
gender: e.gender,
|
||||||
|
age: calculateAge(e.dateOfBirth),
|
||||||
|
nationality: optionStore.mapOption(e.nationality),
|
||||||
|
documentExpireDate:
|
||||||
|
e.employeePassport !== undefined &&
|
||||||
|
e.employeePassport[0]?.expireDate !== undefined
|
||||||
|
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
|
||||||
|
: '-',
|
||||||
|
imgUrl: e.selectedImage
|
||||||
|
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
|
||||||
|
: '',
|
||||||
|
|
||||||
|
employeePassport: e.employeePassport,
|
||||||
|
status: e.status,
|
||||||
|
workerNew: false,
|
||||||
|
lastNameEN: e.lastNameEN,
|
||||||
|
lastName: e.lastName,
|
||||||
|
middleNameEN: e.middleNameEN,
|
||||||
|
middleName: e.middleName,
|
||||||
|
firstNameEN: e.firstNameEN,
|
||||||
|
firstName: e.firstName,
|
||||||
|
namePrefix: e.namePrefix,
|
||||||
|
})),
|
||||||
|
|
||||||
|
...newWorker.map((v: any) => ({
|
||||||
|
id: v.id,
|
||||||
|
foreignRefNo: v.passportNo || '-',
|
||||||
|
employeeName:
|
||||||
|
locale.value === Lang.English
|
||||||
|
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||||
|
: `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
|
||||||
|
birthDate: dateFormatJS({ date: v.dateOfBirth }),
|
||||||
|
gender: v.gender,
|
||||||
|
age: calculateAge(v.dateOfBirth),
|
||||||
|
nationality: optionStore.mapOption(v.nationality),
|
||||||
|
documentExpireDate: '-',
|
||||||
|
imgUrl: '',
|
||||||
|
status: 'CREATED',
|
||||||
|
|
||||||
|
lastNameEN: v.lastNameEN,
|
||||||
|
lastName: v.lastName,
|
||||||
|
middleNameEN: v.middleNameEN,
|
||||||
|
middleName: v.middleName,
|
||||||
|
firstNameEN: v.firstNameEN,
|
||||||
|
firstName: v.firstName,
|
||||||
|
namePrefix: v.namePrefix,
|
||||||
|
|
||||||
|
dateOfBirth: v.dateOfBirth,
|
||||||
|
workerNew: true,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const sessionData = ref<Record<string, any>>();
|
const sessionData = ref<Record<string, any>>();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
@ -1051,7 +1094,7 @@ watch(
|
||||||
() => quotationFormData.value.customerBranchId,
|
() => quotationFormData.value.customerBranchId,
|
||||||
async (v) => {
|
async (v) => {
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
selectedWorker.value = [];
|
selectedWorkerItem.value = [];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1067,6 +1110,15 @@ watch(
|
||||||
|
|
||||||
const productServiceNodes = ref<ProductTree>([]);
|
const productServiceNodes = ref<ProductTree>([]);
|
||||||
|
|
||||||
|
watch(customerBranchOption, () => {
|
||||||
|
if (!customerBranchOption.value) return;
|
||||||
|
|
||||||
|
quotationFormData.value.contactName =
|
||||||
|
customerBranchOption.value.contactName || '';
|
||||||
|
quotationFormData.value.contactTel =
|
||||||
|
customerBranchOption.value.contactTel || '';
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => productServiceList.value,
|
() => productServiceList.value,
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -1074,6 +1126,13 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(customerBranchOption, () => {
|
||||||
|
if (!customerBranchOption.value) return;
|
||||||
|
|
||||||
|
quotationFormData.value.contactName = customerBranchOption.value.contactName;
|
||||||
|
quotationFormData.value.contactTel = customerBranchOption.value.contactTel;
|
||||||
|
});
|
||||||
|
|
||||||
// async function searchEmployee(text: string) {
|
// async function searchEmployee(text: string) {
|
||||||
// let query: string | undefined = text;
|
// let query: string | undefined = text;
|
||||||
// let pageSize = 50;
|
// let pageSize = 50;
|
||||||
|
|
@ -1095,7 +1154,19 @@ watch(
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function storeDataLocal() {
|
function storeDataLocal() {
|
||||||
quotationFormData.value.productServiceList = productService.value;
|
const tempProductService = productService.value.map((v) => {
|
||||||
|
return {
|
||||||
|
...v,
|
||||||
|
vat: v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
||||||
|
? precisionRound(
|
||||||
|
((v.pricePerUnit * (1 + (config?.value.vat || 0.07)) * v.amount -
|
||||||
|
v.discount) /
|
||||||
|
(1 + (config?.value.vat || 0.07))) *
|
||||||
|
0.07,
|
||||||
|
)
|
||||||
|
: 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'quotation-preview',
|
'quotation-preview',
|
||||||
|
|
@ -1104,7 +1175,7 @@ function storeDataLocal() {
|
||||||
codeInvoice: code.value,
|
codeInvoice: code.value,
|
||||||
codePayment: firstCodePayment.value,
|
codePayment: firstCodePayment.value,
|
||||||
...quotationFormData.value,
|
...quotationFormData.value,
|
||||||
productServiceList: productService.value,
|
productServiceList: tempProductService,
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
source: {
|
source: {
|
||||||
|
|
@ -1124,7 +1195,7 @@ function storeDataLocal() {
|
||||||
workName: quotationFormData.value.workName,
|
workName: quotationFormData.value.workName,
|
||||||
dueDate: quotationFormData.value.dueDate,
|
dueDate: quotationFormData.value.dueDate,
|
||||||
},
|
},
|
||||||
selectedWorker: selectedWorker.value,
|
selectedWorker: selectedWorkerItem.value,
|
||||||
createdBy: quotationFormState.value.createdBy('tha'),
|
createdBy: quotationFormState.value.createdBy('tha'),
|
||||||
agentPrice: agentPrice.value,
|
agentPrice: agentPrice.value,
|
||||||
},
|
},
|
||||||
|
|
@ -1209,10 +1280,10 @@ async function getWorkerFromCriteria(
|
||||||
if (!ret) return false; // error, do not close dialog
|
if (!ret) return false; // error, do not close dialog
|
||||||
|
|
||||||
const deduplicate = ret.result.filter(
|
const deduplicate = ret.result.filter(
|
||||||
(a) => !selectedWorker.value.find((b) => a.id === b.id),
|
(a) => !selectedWorkerItem.value.find((b) => a.id === b.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
convertEmployeeToTable([...deduplicate, ...selectedWorker.value]);
|
convertEmployeeToTable([...deduplicate, ...selectedWorkerItem.value]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1519,6 +1590,7 @@ function covertToNode() {
|
||||||
v-model:customer-branch-id="
|
v-model:customer-branch-id="
|
||||||
quotationFormData.customerBranchId
|
quotationFormData.customerBranchId
|
||||||
"
|
"
|
||||||
|
v-model:customer-branch-option="customerBranchOption"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1601,15 +1673,15 @@ function covertToNode() {
|
||||||
(v) =>
|
(v) =>
|
||||||
(quotationFormData.workerMax = Math.max(
|
(quotationFormData.workerMax = Math.max(
|
||||||
v,
|
v,
|
||||||
selectedWorker.length,
|
selectedWorkerItem.length,
|
||||||
))
|
))
|
||||||
"
|
"
|
||||||
:employee-amount="
|
:employee-amount="
|
||||||
quotationFormData.workerMax || selectedWorker.length
|
quotationFormData.workerMax || selectedWorkerItem.length
|
||||||
"
|
"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:rows="selectedWorkerItem"
|
:rows="selectedWorkerItem"
|
||||||
@delete="(i) => deleteItem(selectedWorker, i)"
|
@delete="(i) => deleteItem(selectedWorkerItem, i)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|
@ -1853,7 +1925,7 @@ function covertToNode() {
|
||||||
installments: quotationFormData.paySplit,
|
installments: quotationFormData.paySplit,
|
||||||
},
|
},
|
||||||
'quotation-labor': {
|
'quotation-labor': {
|
||||||
name: selectedWorker.map(
|
name: selectedWorkerItem.map(
|
||||||
(v, i) =>
|
(v, i) =>
|
||||||
`${i + 1}. ` +
|
`${i + 1}. ` +
|
||||||
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''}${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''}${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||||
|
|
@ -1946,6 +2018,7 @@ function covertToNode() {
|
||||||
view !== View.Receipt &&
|
view !== View.Receipt &&
|
||||||
view !== View.Complete
|
view !== View.Complete
|
||||||
"
|
"
|
||||||
|
:branch-id="quotationFull.registeredBranchId"
|
||||||
:readonly="
|
:readonly="
|
||||||
isRoleInclude(['sale', 'head_of_sale']) ||
|
isRoleInclude(['sale', 'head_of_sale']) ||
|
||||||
!canAccess('quotation', 'edit')
|
!canAccess('quotation', 'edit')
|
||||||
|
|
@ -2396,13 +2469,12 @@ function covertToNode() {
|
||||||
<!-- add employee quotation -->
|
<!-- add employee quotation -->
|
||||||
|
|
||||||
<QuotationFormWorkerSelect
|
<QuotationFormWorkerSelect
|
||||||
:preselect-worker="selectedWorker"
|
:preselect-worker="selectedWorkerItem"
|
||||||
:customerBranchId="quotationFormData.customerBranchId"
|
:customerBranchId="quotationFormData.customerBranchId"
|
||||||
v-model:open="pageState.employeeModal"
|
v-model:open="pageState.employeeModal"
|
||||||
v-model:new-worker-list="newWorkerList"
|
|
||||||
@success="
|
@success="
|
||||||
(v) => {
|
(v) => {
|
||||||
selectedWorker = v.worker;
|
combineWorker(v.newWorker, v.worker);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
@ -2445,7 +2517,7 @@ function covertToNode() {
|
||||||
<!-- add Worker -->
|
<!-- add Worker -->
|
||||||
<QuotationFormWorkerAddDialog
|
<QuotationFormWorkerAddDialog
|
||||||
v-if="quotationFormState.source"
|
v-if="quotationFormState.source"
|
||||||
:disabled-worker-id="selectedWorker.map((v) => v.id)"
|
:disabled-worker-id="selectedWorkerItem.map((v) => v.id)"
|
||||||
:product-service-list="quotationFormState.source.productServiceList"
|
:product-service-list="quotationFormState.source.productServiceList"
|
||||||
:quotation-id="quotationFormState.source.id"
|
:quotation-id="quotationFormState.source.id"
|
||||||
:customer-branch-id="quotationFormState.source.customerBranchId"
|
:customer-branch-id="quotationFormState.source.customerBranchId"
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ watch(
|
||||||
<section class="row q-col-gutter-sm col-12 items-center">
|
<section class="row q-col-gutter-sm col-12 items-center">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
class="col-md-6 col-12"
|
class="col-md-6 col-12"
|
||||||
|
id="select-pay-type"
|
||||||
:label="$t('quotation.payType')"
|
:label="$t('quotation.payType')"
|
||||||
:option="
|
:option="
|
||||||
taskOrder
|
taskOrder
|
||||||
|
|
@ -241,7 +242,6 @@ watch(
|
||||||
: payTypeOption
|
: payTypeOption
|
||||||
"
|
"
|
||||||
:readonly="readonly || debitNote"
|
:readonly="readonly || debitNote"
|
||||||
id="pay-type"
|
|
||||||
:model-value="payType"
|
:model-value="payType"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(v) => {
|
(v) => {
|
||||||
|
|
@ -275,6 +275,7 @@ watch(
|
||||||
</div>
|
</div>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="paySplitCount"
|
v-model="paySplitCount"
|
||||||
|
id="input-pay-split-count"
|
||||||
:readonly="readonly || payType === 'Split'"
|
:readonly="readonly || payType === 'Split'"
|
||||||
class="col-3"
|
class="col-3"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -311,6 +312,7 @@ watch(
|
||||||
<q-input
|
<q-input
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:label="$t('general.name')"
|
:label="$t('general.name')"
|
||||||
|
:id="`input-period-name-${i}`"
|
||||||
v-if="payType === 'SplitCustom'"
|
v-if="payType === 'SplitCustom'"
|
||||||
v-model="period.name"
|
v-model="period.name"
|
||||||
class="col q-mx-sm"
|
class="col q-mx-sm"
|
||||||
|
|
@ -320,6 +322,7 @@ watch(
|
||||||
<q-input
|
<q-input
|
||||||
:readonly="readonly || payType === 'Split'"
|
:readonly="readonly || payType === 'Split'"
|
||||||
class="col q-mx-sm"
|
class="col q-mx-sm"
|
||||||
|
:id="`input-period-amount-${i}`"
|
||||||
:label="$t('quotation.amount')"
|
:label="$t('quotation.amount')"
|
||||||
:model-value="
|
:model-value="
|
||||||
amount4Show[i] || commaInput(period.amount.toString())
|
amount4Show[i] || commaInput(period.amount.toString())
|
||||||
|
|
@ -377,6 +380,7 @@ watch(
|
||||||
|
|
||||||
<DatePicker
|
<DatePicker
|
||||||
v-if="payType === 'BillFull'"
|
v-if="payType === 'BillFull'"
|
||||||
|
id="datepicker-bill-date"
|
||||||
:readonly
|
:readonly
|
||||||
class="col-12"
|
class="col-12"
|
||||||
:label="$t('quotation.callDueDate')"
|
:label="$t('quotation.callDueDate')"
|
||||||
|
|
@ -446,7 +450,9 @@ watch(
|
||||||
<span class="q-ml-auto">
|
<span class="q-ml-auto">
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
summaryPrice.totalPrice - summaryPrice.totalDiscount,
|
summaryPrice.totalPrice -
|
||||||
|
summaryPrice.totalDiscount -
|
||||||
|
summaryPrice.vatExcluded,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
@ -482,7 +488,11 @@ watch(
|
||||||
<div class="q-pa-sm row surface-2 items-center text-weight-bold">
|
<div class="q-pa-sm row surface-2 items-center text-weight-bold">
|
||||||
{{ $t('quotation.totalPriceBaht') }}
|
{{ $t('quotation.totalPriceBaht') }}
|
||||||
|
|
||||||
<span class="q-ml-auto" style="color: var(--brand-1)">
|
<span
|
||||||
|
class="q-ml-auto"
|
||||||
|
style="color: var(--brand-1)"
|
||||||
|
id="value-final-price"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
payType === 'SplitCustom' && view === View.Invoice
|
payType === 'SplitCustom' && view === View.Invoice
|
||||||
? formatNumberDecimal(Math.max(installmentAmount || 0, 0), 2) || 0
|
? formatNumberDecimal(Math.max(installmentAmount || 0, 0), 2) || 0
|
||||||
|
|
|
||||||
|
|
@ -341,12 +341,13 @@ watch(() => state.search, getWorkerList);
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="display: inline-block; margin-inline: auto"
|
style="display: inline-block; margin-inline: auto"
|
||||||
v-if="workerList.length === 0"
|
v-if="workerList.length === 0 && state.search"
|
||||||
>
|
>
|
||||||
<NoData :not-found="!!state.search" />
|
<NoData :not-found="!!state.search" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TableWorker
|
<TableWorker
|
||||||
|
v-else
|
||||||
v-model:selected="workerSelected"
|
v-model:selected="workerSelected"
|
||||||
:rows="workerList"
|
:rows="workerList"
|
||||||
:disabledWorkerId
|
:disabledWorkerId
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
customerBranchId?: string;
|
customerBranchId?: string;
|
||||||
disabledWorkerId?: string[];
|
disabledWorkerId?: string[];
|
||||||
preselectWorker?: Employee[];
|
preselectWorker?: (Employee & { workerNew: boolean })[];
|
||||||
}>(),
|
}>(),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
@ -133,7 +133,7 @@ const optionStore = useOptionStore();
|
||||||
const employeeStore = useEmployeeStore();
|
const employeeStore = useEmployeeStore();
|
||||||
|
|
||||||
const open = defineModel<boolean>('open', { default: false });
|
const open = defineModel<boolean>('open', { default: false });
|
||||||
const newWorkerList = defineModel<
|
const newWorkerList = ref<
|
||||||
(EmployeeWorker & {
|
(EmployeeWorker & {
|
||||||
attachment?: {
|
attachment?: {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
@ -143,7 +143,7 @@ const newWorkerList = defineModel<
|
||||||
_meta?: Record<string, any>;
|
_meta?: Record<string, any>;
|
||||||
}[];
|
}[];
|
||||||
})[]
|
})[]
|
||||||
>('newWorkerList', { default: [] });
|
>([]);
|
||||||
const workerSelected = ref<Employee[]>([]);
|
const workerSelected = ref<Employee[]>([]);
|
||||||
const workerList = ref<Employee[]>([]);
|
const workerList = ref<Employee[]>([]);
|
||||||
const importWorkerCriteria = ref<{
|
const importWorkerCriteria = ref<{
|
||||||
|
|
@ -208,7 +208,13 @@ function getEmployeeImageUrl(item: Employee) {
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (props.preselectWorker) {
|
if (props.preselectWorker) {
|
||||||
workerSelected.value = JSON.parse(JSON.stringify(props.preselectWorker));
|
workerSelected.value = JSON.parse(
|
||||||
|
JSON.stringify(props.preselectWorker.filter((v) => !v.workerNew)),
|
||||||
|
);
|
||||||
|
|
||||||
|
newWorkerList.value = JSON.parse(
|
||||||
|
JSON.stringify(props.preselectWorker.filter((v) => v.workerNew)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
getWorkerList();
|
getWorkerList();
|
||||||
}
|
}
|
||||||
|
|
@ -608,11 +614,14 @@ watch(
|
||||||
solid
|
solid
|
||||||
id="btn-success"
|
id="btn-success"
|
||||||
@click="
|
@click="
|
||||||
(emits('success', {
|
() => {
|
||||||
|
$emit('success', {
|
||||||
worker: workerSelected,
|
worker: workerSelected,
|
||||||
newWorker: newWorkerList,
|
newWorker: newWorkerList,
|
||||||
}),
|
});
|
||||||
(open = false))
|
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ $t('general.select', { msg: $t('quotation.employeeList') }) }}
|
{{ $t('general.select', { msg: $t('quotation.employeeList') }) }}
|
||||||
|
|
@ -634,9 +643,11 @@ watch(
|
||||||
if (employeeFormState.currentTab === 'personalInfo') {
|
if (employeeFormState.currentTab === 'personalInfo') {
|
||||||
const currentEmployeeId =
|
const currentEmployeeId =
|
||||||
await employeeFormStore.submitPersonal(onCreateImageList);
|
await employeeFormStore.submitPersonal(onCreateImageList);
|
||||||
|
newWorkerList.push(
|
||||||
quotationForm.injectNewEmployee({
|
quotationForm.injectNewEmployee({
|
||||||
data: { ...currentFromDataEmployee, id: currentEmployeeId },
|
data: { ...currentFromDataEmployee, id: currentEmployeeId },
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
employeeFormState.isEmployeeEdit = false;
|
employeeFormState.isEmployeeEdit = false;
|
||||||
employeeFormState.dialogType = 'info';
|
employeeFormState.dialogType = 'info';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
|
||||||
file?: File;
|
file?: File;
|
||||||
_meta?: Record<string, any>;
|
_meta?: Record<string, any>;
|
||||||
}[];
|
}[];
|
||||||
|
workerNew: boolean;
|
||||||
})[]
|
})[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
|
|
@ -220,7 +221,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
|
||||||
},
|
},
|
||||||
callback?: () => void,
|
callback?: () => void,
|
||||||
) {
|
) {
|
||||||
newWorkerList.value.push({
|
const temp = {
|
||||||
//passportNo: obj.data.passportNo,
|
//passportNo: obj.data.passportNo,
|
||||||
//documentExpireDate: obj.data.documentExpireDate,
|
//documentExpireDate: obj.data.documentExpireDate,
|
||||||
id: obj.data.id,
|
id: obj.data.id,
|
||||||
|
|
@ -235,9 +236,12 @@ export const useQuotationForm = defineStore('form-quotation', () => {
|
||||||
gender: obj.data.gender,
|
gender: obj.data.gender,
|
||||||
dateOfBirth: obj.data.dateOfBirth,
|
dateOfBirth: obj.data.dateOfBirth,
|
||||||
attachment: obj.data.attachment,
|
attachment: obj.data.attachment,
|
||||||
});
|
workerNew: true,
|
||||||
|
};
|
||||||
|
|
||||||
callback?.();
|
callback?.();
|
||||||
|
|
||||||
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dialogDelete(callback: () => void) {
|
function dialogDelete(callback: () => void) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { onMounted, nextTick, ref, watch } from 'vue';
|
import { onMounted, nextTick, ref, watch, toRaw } from 'vue';
|
||||||
import { precisionRound } from 'src/utils/arithmetic';
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import ThaiBahtText from 'thai-baht-text';
|
import ThaiBahtText from 'thai-baht-text';
|
||||||
|
|
@ -175,6 +175,8 @@ enum View {
|
||||||
const view = ref<View>(View.Quotation);
|
const view = ref<View>(View.Quotation);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
await configStore.getConfig();
|
||||||
|
|
||||||
const currentDocumentType = new URL(window.location.href).searchParams.get(
|
const currentDocumentType = new URL(window.location.href).searchParams.get(
|
||||||
'type',
|
'type',
|
||||||
);
|
);
|
||||||
|
|
@ -259,18 +261,6 @@ onMounted(async () => {
|
||||||
productList.value =
|
productList.value =
|
||||||
(data?.value?.productServiceList ?? data.value?.productServiceList).map(
|
(data?.value?.productServiceList ?? data.value?.productServiceList).map(
|
||||||
(v) => {
|
(v) => {
|
||||||
const originalPrice = v.pricePerUnit;
|
|
||||||
const finalPriceWithVat = precisionRound(
|
|
||||||
originalPrice * (1 + (config.value?.vat || 0.07)),
|
|
||||||
);
|
|
||||||
const finalPriceNoVat =
|
|
||||||
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
|
||||||
|
|
||||||
const price = finalPriceNoVat * v.amount - v.discount;
|
|
||||||
const vat =
|
|
||||||
(finalPriceNoVat * v.amount - v.discount) *
|
|
||||||
(config.value?.vat || 0.07);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: v.product.id,
|
id: v.product.id,
|
||||||
code: v.product.code,
|
code: v.product.code,
|
||||||
|
|
@ -279,8 +269,8 @@ onMounted(async () => {
|
||||||
pricePerUnit: v.pricePerUnit || 0,
|
pricePerUnit: v.pricePerUnit || 0,
|
||||||
discount: v.discount || 0,
|
discount: v.discount || 0,
|
||||||
vat: v.vat || 0,
|
vat: v.vat || 0,
|
||||||
value: precisionRound(price + (v.product.calcVat ? vat : 0)),
|
value: 0,
|
||||||
calcVat: v.product.calcVat,
|
calcVat: v.vat > 0,
|
||||||
product: v.product,
|
product: v.product,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -292,23 +282,17 @@ onMounted(async () => {
|
||||||
[]
|
[]
|
||||||
).reduce(
|
).reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const originalPrice = c.pricePerUnit;
|
const calcVat = c.vat > 0;
|
||||||
const finalPriceWithVat = precisionRound(
|
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||||
originalPrice * (1 + (config.value?.vat || 0.07)),
|
const pricePerUnit =
|
||||||
);
|
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
|
||||||
const finalPriceNoVat =
|
const price =
|
||||||
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
|
||||||
|
(1 + vatFactor);
|
||||||
|
|
||||||
const price = finalPriceNoVat * c.amount;
|
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
|
||||||
const vat =
|
|
||||||
(finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07);
|
|
||||||
|
|
||||||
const calcVat =
|
|
||||||
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price);
|
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
|
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
|
||||||
a.vat = calcVat ? precisionRound(a.vat + vat) : a.vat;
|
a.vat = calcVat ? precisionRound(a.vat + c.vat) : a.vat;
|
||||||
a.vatExcluded = calcVat
|
a.vatExcluded = calcVat
|
||||||
? a.vatExcluded
|
? a.vatExcluded
|
||||||
: precisionRound(a.vatExcluded + price);
|
: precisionRound(a.vatExcluded + price);
|
||||||
|
|
@ -334,16 +318,12 @@ onMounted(async () => {
|
||||||
|
|
||||||
function calcPrice(c: Product) {
|
function calcPrice(c: Product) {
|
||||||
const originalPrice = c.pricePerUnit;
|
const originalPrice = c.pricePerUnit;
|
||||||
const finalPriceWithVat = precisionRound(
|
const finalPricePerUnit = precisionRound(
|
||||||
originalPrice + originalPrice * (config.value?.vat || 0.07),
|
originalPrice +
|
||||||
|
(c.calcVat ? originalPrice * (config.value?.vat || 0.07) : 0),
|
||||||
);
|
);
|
||||||
const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
const price = finalPricePerUnit * c.amount - c.discount;
|
||||||
|
return precisionRound(price);
|
||||||
const price = finalPriceNoVat * c.amount - c.discount;
|
|
||||||
const vat = c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat']
|
|
||||||
? (finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07)
|
|
||||||
: 0;
|
|
||||||
return precisionRound(price + vat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeTab() {
|
async function closeTab() {
|
||||||
|
|
@ -427,31 +407,15 @@ function print() {
|
||||||
<td>{{ v.detail }}</td>
|
<td>{{ v.detail }}</td>
|
||||||
<td style="text-align: right">{{ v.amount }}</td>
|
<td style="text-align: right">{{ v.amount }}</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{
|
{{ formatNumberDecimal(v.pricePerUnit, 2) }}
|
||||||
formatNumberDecimal(
|
|
||||||
v.pricePerUnit +
|
|
||||||
(v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
|
||||||
? v.pricePerUnit * (config?.vat || 0.07)
|
|
||||||
: 0),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ formatNumberDecimal(v.discount, 2) }}
|
<template v-if="v.discount !== 0">
|
||||||
|
{{ formatNumberDecimal(v.discount, 2) }} ฿
|
||||||
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{
|
{{ Math.round((v.vat > 0 ? config?.vat || 0.07 : 0) * 100) }}%
|
||||||
formatNumberDecimal(
|
|
||||||
v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
|
||||||
? precisionRound(
|
|
||||||
(v.pricePerUnit * v.amount - v.discount) *
|
|
||||||
(config?.vat || 0.07),
|
|
||||||
)
|
|
||||||
: 0,
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ formatNumberDecimal(calcPrice(v), 2) }}
|
{{ formatNumberDecimal(calcPrice(v), 2) }}
|
||||||
|
|
@ -511,7 +475,9 @@ function print() {
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
summaryPrice.totalPrice - summaryPrice.totalDiscount,
|
summaryPrice.totalPrice -
|
||||||
|
summaryPrice.totalDiscount -
|
||||||
|
summaryPrice.vatExcluded,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
@ -600,7 +566,7 @@ function print() {
|
||||||
details?.worker.map(
|
details?.worker.map(
|
||||||
(v, i) =>
|
(v, i) =>
|
||||||
`${i + 1}. ` +
|
`${i + 1}. ` +
|
||||||
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''}${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||||
) || [],
|
) || [],
|
||||||
},
|
},
|
||||||
}) || '-'
|
}) || '-'
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ function titleMode(mode: View): string {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>เลขประจำตัวผู้เสียภาษี {{ branch.taxNo }}</span>
|
<span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
|
||||||
<span>เบอร์โทร {{ branch.telephoneNo }}</span>
|
<span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
|
||||||
<span>{{ branch.webUrl }}</span>
|
<span>{{ branch.webUrl }}</span>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
|
|
@ -105,8 +105,18 @@ function titleMode(mode: View): string {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>เลขประจำตัวผู้เสียภาษี {{ customer.citizenId }}</span>
|
<span>
|
||||||
<span>เบอร์โทร {{ customer.telephoneNo }}</span>
|
{{
|
||||||
|
customer.customer.customerType === 'CORP'
|
||||||
|
? `${$t('customer.form.legalPersonNo')} `
|
||||||
|
: `${$t('customer.form.taxpayyerNo')} `
|
||||||
|
}}{{
|
||||||
|
customer.customer.customerType === 'CORP'
|
||||||
|
? customer.codeCustomer
|
||||||
|
: customer.citizenId
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span>{{ $t('taskOrder.telephone') }} {{ customer.telephoneNo }}</span>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
<section class="detail-quotation-info">
|
<section class="detail-quotation-info">
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ const props = withDefaults(
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
listDocument: string[];
|
listDocument: string[];
|
||||||
currentId: { customer: string; employee: string };
|
currentId: { customer: string; employee: string };
|
||||||
|
prefix?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
listDocument: () => [],
|
listDocument: () => [],
|
||||||
|
|
@ -244,14 +245,14 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
|
||||||
<nav class="q-ml-auto row" v-if="!readonly">
|
<nav class="q-ml-auto row" v-if="!readonly">
|
||||||
<CancelButton
|
<CancelButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-undo"
|
:id="`btn-docs-${props.prefix || 'nome'}-info-basic-undo`"
|
||||||
icon-only
|
icon-only
|
||||||
type="button"
|
type="button"
|
||||||
@click.stop="triggerCancel"
|
@click.stop="triggerCancel"
|
||||||
/>
|
/>
|
||||||
<EditButton
|
<EditButton
|
||||||
v-if="!state.isEdit"
|
v-if="!state.isEdit"
|
||||||
id="btn-info-basic-edit"
|
:id="`btn-docs-${props.prefix || 'nome'}-info-basic-edit`"
|
||||||
icon-only
|
icon-only
|
||||||
@click.stop="triggerEdit"
|
@click.stop="triggerEdit"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { useRequestList } from 'src/stores/request-list';
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
step: Step;
|
step: Step;
|
||||||
|
prefix?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const requestListStore = useRequestList();
|
const requestListStore = useRequestList();
|
||||||
|
|
@ -100,21 +101,21 @@ function assignToForm() {
|
||||||
<nav class="q-ml-auto row" v-if="!readonly">
|
<nav class="q-ml-auto row" v-if="!readonly">
|
||||||
<UndoButton
|
<UndoButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-undo"
|
:id="`btn-duty-${props.prefix || 'nome'}-info-basic-undo`"
|
||||||
icon-only
|
icon-only
|
||||||
type="button"
|
type="button"
|
||||||
@click.stop="triggerUndo"
|
@click.stop="triggerUndo"
|
||||||
/>
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-save"
|
:id="`btn-duty-${props.prefix || 'nome'}-info-basic-save`"
|
||||||
icon-only
|
icon-only
|
||||||
type="submit"
|
type="submit"
|
||||||
@click.stop="triggerSubmit"
|
@click.stop="triggerSubmit"
|
||||||
/>
|
/>
|
||||||
<EditButton
|
<EditButton
|
||||||
v-if="!state.isEdit"
|
v-if="!state.isEdit"
|
||||||
id="btn-info-basic-edit"
|
:id="`btn-duty-${props.prefix || 'nome'}-info-basic-edit`"
|
||||||
icon-only
|
icon-only
|
||||||
@click.stop="triggerEdit"
|
@click.stop="triggerEdit"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const props = defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
step: Step;
|
step: Step;
|
||||||
requestWorkId: string;
|
requestWorkId: string;
|
||||||
|
prefix?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const requestListStore = useRequestList();
|
const requestListStore = useRequestList();
|
||||||
|
|
@ -90,21 +91,21 @@ function assignToForm() {
|
||||||
<nav class="q-ml-auto row" v-if="!readonly">
|
<nav class="q-ml-auto row" v-if="!readonly">
|
||||||
<UndoButton
|
<UndoButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-undo"
|
:id="`btn-form-${props.prefix || 'nome'}-info-basic-undo`"
|
||||||
icon-only
|
icon-only
|
||||||
type="button"
|
type="button"
|
||||||
@click.stop="triggerUndo"
|
@click.stop="triggerUndo"
|
||||||
/>
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-save"
|
:id="`btn-form-${props.prefix || 'nome'}-info-basic-save`"
|
||||||
icon-only
|
icon-only
|
||||||
type="submit"
|
type="submit"
|
||||||
@click.stop="triggerSubmit"
|
@click.stop="triggerSubmit"
|
||||||
/>
|
/>
|
||||||
<EditButton
|
<EditButton
|
||||||
v-if="!state.isEdit"
|
v-if="!state.isEdit"
|
||||||
id="btn-info-basic-edit"
|
:id="`btn-form-${props.prefix || 'nome'}-info-basic-edit`"
|
||||||
icon-only
|
icon-only
|
||||||
@click.stop="triggerEdit"
|
@click.stop="triggerEdit"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const responsibleUserId = defineModel<string>('responsibleUserId', {
|
||||||
defineProps<{
|
defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
districtId?: string;
|
districtId?: string;
|
||||||
|
prefix?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
watch(responsibleUserLocal, (lhs, rhs) => {
|
watch(responsibleUserLocal, (lhs, rhs) => {
|
||||||
|
|
@ -27,6 +28,8 @@ watch(responsibleUserLocal, (lhs, rhs) => {
|
||||||
:label="$t('requestList.localEmployee')"
|
:label="$t('requestList.localEmployee')"
|
||||||
:disable="readonly"
|
:disable="readonly"
|
||||||
class="col"
|
class="col"
|
||||||
|
:id="`${prefix || 'nome'}-radio-local-employee`"
|
||||||
|
:for="`${prefix || 'nome'}-radio-local-employee`"
|
||||||
/>
|
/>
|
||||||
<q-radio
|
<q-radio
|
||||||
v-model="responsibleUserLocal"
|
v-model="responsibleUserLocal"
|
||||||
|
|
@ -34,6 +37,8 @@ watch(responsibleUserLocal, (lhs, rhs) => {
|
||||||
:label="$t('requestList.nonLocalEmployee')"
|
:label="$t('requestList.nonLocalEmployee')"
|
||||||
:disable="readonly"
|
:disable="readonly"
|
||||||
class="col"
|
class="col"
|
||||||
|
:id="`${prefix || 'nome'}-radio-non-local-employee`"
|
||||||
|
:for="`${prefix || 'nome'}-radio-non-local-employee`"
|
||||||
/>
|
/>
|
||||||
<div class="col" />
|
<div class="col" />
|
||||||
<div class="offset-md-7"></div>
|
<div class="offset-md-7"></div>
|
||||||
|
|
@ -52,6 +57,8 @@ watch(responsibleUserLocal, (lhs, rhs) => {
|
||||||
}"
|
}"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('general.select', { msg: $t('personnel.MESSENGER') })"
|
:label="$t('general.select', { msg: $t('personnel.MESSENGER') })"
|
||||||
|
:id="`${prefix || 'nome'}-select-user`"
|
||||||
|
:for="`${prefix || 'nome'}-select-user`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ function triggerCancel(id: string) {
|
||||||
const res = await requestListStore.cancelRequest(id);
|
const res = await requestListStore.cancelRequest(id);
|
||||||
if (res) {
|
if (res) {
|
||||||
fetchList();
|
fetchList();
|
||||||
|
fetchStats();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const props = defineProps<{
|
||||||
step: Step;
|
step: Step;
|
||||||
responsibleAreaDistrictId?: string;
|
responsibleAreaDistrictId?: string;
|
||||||
defaultMessenger?: string;
|
defaultMessenger?: string;
|
||||||
|
prefix?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
@ -116,21 +117,21 @@ function assignToForm() {
|
||||||
<nav class="q-ml-auto row" v-if="!readonly">
|
<nav class="q-ml-auto row" v-if="!readonly">
|
||||||
<UndoButton
|
<UndoButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-undo"
|
:id="`btn-messenger-${props.prefix || 'nome'}-info-basic-undo`"
|
||||||
icon-only
|
icon-only
|
||||||
type="button"
|
type="button"
|
||||||
@click.stop="triggerUndo"
|
@click.stop="triggerUndo"
|
||||||
/>
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-save"
|
:id="`btn-messenger-${props.prefix || 'nome'}-info-basic-save`"
|
||||||
icon-only
|
icon-only
|
||||||
type="submit"
|
type="submit"
|
||||||
@click.stop="(e) => refForm?.submit(e)"
|
@click.stop="(e) => refForm?.submit(e)"
|
||||||
/>
|
/>
|
||||||
<EditButton
|
<EditButton
|
||||||
v-if="!state.isEdit"
|
v-if="!state.isEdit"
|
||||||
id="btn-info-basic-edit"
|
:id="`btn-messenger-${props.prefix || 'nome'}-info-basic-edit`"
|
||||||
icon-only
|
icon-only
|
||||||
@click.stop="triggerEdit"
|
@click.stop="triggerEdit"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -157,6 +158,7 @@ function assignToForm() {
|
||||||
v-model:responsible-user-local="formData.responsibleUserLocal"
|
v-model:responsible-user-local="formData.responsibleUserLocal"
|
||||||
v-model:responsible-user-id="formData.responsibleUserId"
|
v-model:responsible-user-id="formData.responsibleUserId"
|
||||||
:district-id="responsibleAreaDistrictId"
|
:district-id="responsibleAreaDistrictId"
|
||||||
|
:prefix
|
||||||
/>
|
/>
|
||||||
</q-form>
|
</q-form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
:id="`expansion-${product?.name || name}`"
|
||||||
|
:for="`expansion-${product?.name || name}`"
|
||||||
dense
|
dense
|
||||||
:class="{ 'status-unpaid': !paySuccess }"
|
:class="{ 'status-unpaid': !paySuccess }"
|
||||||
class="overflow-hidden"
|
class="overflow-hidden"
|
||||||
|
|
@ -146,6 +148,8 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
|
||||||
<div class="q-ml-auto q-gutter-y-xs">
|
<div class="q-ml-auto q-gutter-y-xs">
|
||||||
<div class="justify-end flex">
|
<div class="justify-end flex">
|
||||||
<q-btn-dropdown
|
<q-btn-dropdown
|
||||||
|
:id="`btn-dropdown-${product?.name || name}`"
|
||||||
|
:for="`btn-dropdown-${product?.name || name}`"
|
||||||
:disable="
|
:disable="
|
||||||
readonly || changeableStatus(status?.workStatus).length === 0
|
readonly || changeableStatus(status?.workStatus).length === 0
|
||||||
"
|
"
|
||||||
|
|
@ -197,6 +201,8 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
|
||||||
<q-list dense>
|
<q-list dense>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="(value, index) in changeableStatus(status?.workStatus)"
|
v-for="(value, index) in changeableStatus(status?.workStatus)"
|
||||||
|
:id="`btn-dropdown-${product?.name || name}-${value}`"
|
||||||
|
:for="`btn-dropdown-${product?.name || name}-${value}`"
|
||||||
:key="index"
|
:key="index"
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const props = withDefaults(
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
propertiesToShow: (PropString | PropNumber | PropDate | PropOptions)[];
|
propertiesToShow: (PropString | PropNumber | PropDate | PropOptions)[];
|
||||||
requestListData: RequestData;
|
requestListData: RequestData;
|
||||||
|
prefix?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
|
|
@ -128,7 +129,7 @@ defineEmits<{
|
||||||
<nav class="q-ml-auto row" v-if="!readonly">
|
<nav class="q-ml-auto row" v-if="!readonly">
|
||||||
<UndoButton
|
<UndoButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-undo"
|
:id="`btn-properties-${props.prefix || 'nome'}-info-basic-undo`"
|
||||||
icon-only
|
icon-only
|
||||||
type="button"
|
type="button"
|
||||||
@click.stop="triggerUndo"
|
@click.stop="triggerUndo"
|
||||||
|
|
@ -136,13 +137,14 @@ defineEmits<{
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="state.isEdit"
|
v-if="state.isEdit"
|
||||||
id="btn-info-basic-save"
|
id="btn-info-basic-save"
|
||||||
|
:id="`btn-properties-${props.prefix || 'nome'}-info-basic-save`"
|
||||||
icon-only
|
icon-only
|
||||||
type="submit"
|
type="submit"
|
||||||
@click.stop="triggerSubmit"
|
@click.stop="triggerSubmit"
|
||||||
/>
|
/>
|
||||||
<EditButton
|
<EditButton
|
||||||
v-if="!state.isEdit"
|
v-if="!state.isEdit"
|
||||||
id="btn-info-basic-edit"
|
:id="`btn-properties-${props.prefix || 'nome'}-info-basic-edit`"
|
||||||
icon-only
|
icon-only
|
||||||
@click.stop="triggerEdit"
|
@click.stop="triggerEdit"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -881,6 +881,7 @@ function toEmployee(employee: RequestData['employee']) {
|
||||||
:readonly="value._readonly"
|
:readonly="value._readonly"
|
||||||
ref="refDocumentExpansion"
|
ref="refDocumentExpansion"
|
||||||
:attributes="value.attributes"
|
:attributes="value.attributes"
|
||||||
|
:prefix="value.productService.product.name"
|
||||||
@change-status="
|
@change-status="
|
||||||
(opt) => {
|
(opt) => {
|
||||||
triggerChangeStatusFile({
|
triggerChangeStatusFile({
|
||||||
|
|
@ -928,6 +929,7 @@ function toEmployee(employee: RequestData['employee']) {
|
||||||
<MessengerExpansion
|
<MessengerExpansion
|
||||||
v-if="value._messengerExpansion"
|
v-if="value._messengerExpansion"
|
||||||
:readonly="value._readonly"
|
:readonly="value._readonly"
|
||||||
|
:prefix="value.productService.product.name"
|
||||||
:default-messenger="
|
:default-messenger="
|
||||||
value.stepStatus[pageState.currentStep - 1]
|
value.stepStatus[pageState.currentStep - 1]
|
||||||
? undefined
|
? undefined
|
||||||
|
|
@ -953,6 +955,7 @@ function toEmployee(employee: RequestData['employee']) {
|
||||||
<DutyExpansion
|
<DutyExpansion
|
||||||
v-if="value._dutyExpansion"
|
v-if="value._dutyExpansion"
|
||||||
:readonly="value._readonly"
|
:readonly="value._readonly"
|
||||||
|
:prefix="value.productService.product.name"
|
||||||
:step="{
|
:step="{
|
||||||
step: pageState.currentStep,
|
step: pageState.currentStep,
|
||||||
requestWorkId: value.id || '',
|
requestWorkId: value.id || '',
|
||||||
|
|
@ -966,6 +969,7 @@ function toEmployee(employee: RequestData['employee']) {
|
||||||
v-if="value._formExpansion"
|
v-if="value._formExpansion"
|
||||||
:request-work-id="value.id"
|
:request-work-id="value.id"
|
||||||
:readonly="value._readonly"
|
:readonly="value._readonly"
|
||||||
|
:prefix="value.productService.product.name"
|
||||||
:step="{
|
:step="{
|
||||||
step: pageState.currentStep,
|
step: pageState.currentStep,
|
||||||
requestWorkId: value.id || '',
|
requestWorkId: value.id || '',
|
||||||
|
|
@ -979,6 +983,7 @@ function toEmployee(employee: RequestData['employee']) {
|
||||||
:request-list-data="data"
|
:request-list-data="data"
|
||||||
:id="value.id"
|
:id="value.id"
|
||||||
:readonly="value._readonly"
|
:readonly="value._readonly"
|
||||||
|
:prefix="value.productService.product.name"
|
||||||
:properties-to-show="
|
:properties-to-show="
|
||||||
value.productService.work?.attributes.workflowStep[
|
value.productService.work?.attributes.workflowStep[
|
||||||
pageState.currentStep - 1
|
pageState.currentStep - 1
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,8 @@ function handleCheckAll() {
|
||||||
>
|
>
|
||||||
<q-th v-if="checkable">
|
<q-th v-if="checkable">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
|
:for="`list-checkbox-all`"
|
||||||
|
:id="`list-checkbox-all`"
|
||||||
v-if="selected.length > 0"
|
v-if="selected.length > 0"
|
||||||
:model-value="
|
:model-value="
|
||||||
selected.length ===
|
selected.length ===
|
||||||
|
|
@ -229,6 +231,8 @@ function handleCheckAll() {
|
||||||
>
|
>
|
||||||
<q-td v-if="checkable">
|
<q-td v-if="checkable">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
|
:for="`list-checkbox-${props.rowIndex + 1}`"
|
||||||
|
:id="`list-checkbox-${props.rowIndex + 1}`"
|
||||||
:disable="
|
:disable="
|
||||||
selected.length > 0 &&
|
selected.length > 0 &&
|
||||||
!listSameArea.includes(
|
!listSameArea.includes(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// NOTE: Library
|
// NOTE: Library
|
||||||
import { computed, onMounted, reactive, watch, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, reactive, watch, ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
@ -86,6 +86,7 @@ async function fetchTaskOrderList(opts?: { page?: number; pageSize?: number }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
|
taskOrderStore.getTaskOrderStats();
|
||||||
data.value = res.result;
|
data.value = res.result;
|
||||||
pageState.total = res.total;
|
pageState.total = res.total;
|
||||||
pageMax.value = Math.ceil(res.total / pageSize.value);
|
pageMax.value = Math.ceil(res.total / pageSize.value);
|
||||||
|
|
@ -146,6 +147,10 @@ async function deleteTaskOrder(id: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleWindowFocus() {
|
||||||
|
fetchTaskOrderList();
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
pageState.gridView = $q.screen.lt.md ? true : false;
|
pageState.gridView = $q.screen.lt.md ? true : false;
|
||||||
navigatorStore.current.title = 'taskOrder.title';
|
navigatorStore.current.title = 'taskOrder.title';
|
||||||
|
|
@ -156,6 +161,12 @@ onMounted(async () => {
|
||||||
if (route.query['tab'] && typeof route.query['tab'] === 'string') {
|
if (route.query['tab'] && typeof route.query['tab'] === 'string') {
|
||||||
pageState.currentTab = route.query['tab'];
|
pageState.currentTab = route.query['tab'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('focus', handleWindowFocus);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('focus', handleWindowFocus);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -218,7 +229,11 @@ watch(
|
||||||
color: hsl(var(--info-bg));
|
color: hsl(var(--info-bg));
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ Object.values(stats).reduce((s, v) => s + v, 0) }}
|
{{
|
||||||
|
pageState.isMessenger
|
||||||
|
? pageState.total
|
||||||
|
: stats[pageState.currentTab as TaskOrderStatus]
|
||||||
|
}}
|
||||||
</q-badge>
|
</q-badge>
|
||||||
<q-btn
|
<q-btn
|
||||||
class="q-ml-sm"
|
class="q-ml-sm"
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,7 @@ function assignTempGroup() {
|
||||||
class="bordered-b"
|
class="bordered-b"
|
||||||
>
|
>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
:id="`expansion-product-${product.code}`"
|
||||||
dense
|
dense
|
||||||
class="overflow-hidden"
|
class="overflow-hidden"
|
||||||
switch-toggle-side
|
switch-toggle-side
|
||||||
|
|
@ -348,6 +349,7 @@ function assignTempGroup() {
|
||||||
</FormGroupHead>
|
</FormGroupHead>
|
||||||
<div class="q-pa-md full-width">
|
<div class="q-pa-md full-width">
|
||||||
<TableEmployee
|
<TableEmployee
|
||||||
|
:id="`table-employee-${product.code}`"
|
||||||
checkbox-on
|
checkbox-on
|
||||||
check-all
|
check-all
|
||||||
select-ready
|
select-ready
|
||||||
|
|
@ -371,9 +373,15 @@ function assignTempGroup() {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<CancelButton class="q-ml-auto" outlined @click="close" />
|
<CancelButton
|
||||||
|
id="btn-dialog-cancel"
|
||||||
|
class="q-ml-auto"
|
||||||
|
outlined
|
||||||
|
@click="close"
|
||||||
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
:label="$t('general.select')"
|
:label="$t('general.select')"
|
||||||
|
id="btn-dialog-select"
|
||||||
class="q-ml-sm"
|
class="q-ml-sm"
|
||||||
icon="mdi-check"
|
icon="mdi-check"
|
||||||
solid
|
solid
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,7 @@ function disableCheckAll() {
|
||||||
<template>
|
<template>
|
||||||
<q-table
|
<q-table
|
||||||
flat
|
flat
|
||||||
|
id="table-employee"
|
||||||
bordered
|
bordered
|
||||||
row-key="id"
|
row-key="id"
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
|
|
@ -273,6 +274,7 @@ function disableCheckAll() {
|
||||||
>
|
>
|
||||||
<q-th v-if="checkboxOn" class="relative-position">
|
<q-th v-if="checkboxOn" class="relative-position">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
|
id="checkbox-check-all"
|
||||||
v-if="checkAll"
|
v-if="checkAll"
|
||||||
:disable="disableCheckAll()"
|
:disable="disableCheckAll()"
|
||||||
:model-value="
|
:model-value="
|
||||||
|
|
@ -305,6 +307,7 @@ function disableCheckAll() {
|
||||||
class="absolute-right row items-center"
|
class="absolute-right row items-center"
|
||||||
>
|
>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
id="btn-change-all-status"
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
rounded
|
rounded
|
||||||
|
|
@ -339,6 +342,7 @@ function disableCheckAll() {
|
||||||
{{ $t(`taskOrder.status.Complete`) }}
|
{{ $t(`taskOrder.status.Complete`) }}
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
|
id="menu-item-redo"
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
class="items-center"
|
class="items-center"
|
||||||
|
|
@ -358,6 +362,7 @@ function disableCheckAll() {
|
||||||
{{ $t(`taskOrder.status.Redo`) }}
|
{{ $t(`taskOrder.status.Redo`) }}
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
|
id="menu-item-restart"
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
class="items-center"
|
class="items-center"
|
||||||
|
|
@ -379,6 +384,7 @@ function disableCheckAll() {
|
||||||
</q-list>
|
</q-list>
|
||||||
<q-list v-if="!validate" dense>
|
<q-list v-if="!validate" dense>
|
||||||
<q-item
|
<q-item
|
||||||
|
id="menu-item-success"
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
class="items-center"
|
class="items-center"
|
||||||
|
|
@ -398,6 +404,7 @@ function disableCheckAll() {
|
||||||
{{ $t(`taskOrder.status.Success`) }}
|
{{ $t(`taskOrder.status.Success`) }}
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
|
id="menu-item-failed"
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
class="items-center"
|
class="items-center"
|
||||||
|
|
@ -480,6 +487,7 @@ function disableCheckAll() {
|
||||||
<q-td>
|
<q-td>
|
||||||
<span
|
<span
|
||||||
class="cursor-pointer link"
|
class="cursor-pointer link"
|
||||||
|
:id="`link-request-list-${props.row.request.code}`"
|
||||||
@click="goToRequestList(props.row.request.id)"
|
@click="goToRequestList(props.row.request.id)"
|
||||||
>
|
>
|
||||||
{{ props.row.request.code }}
|
{{ props.row.request.code }}
|
||||||
|
|
@ -489,6 +497,7 @@ function disableCheckAll() {
|
||||||
<q-td>
|
<q-td>
|
||||||
<span
|
<span
|
||||||
class="cursor-pointer link"
|
class="cursor-pointer link"
|
||||||
|
:id="`link-quotation-${props.row.request.quotation?.code}`"
|
||||||
@click="goToQuotation(props.row.request.quotation)"
|
@click="goToQuotation(props.row.request.quotation)"
|
||||||
>
|
>
|
||||||
{{ props.row.request.quotation?.code }}
|
{{ props.row.request.quotation?.code }}
|
||||||
|
|
@ -496,7 +505,11 @@ function disableCheckAll() {
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td v-if="stepOn" class="text-left">
|
<q-td v-if="stepOn" class="text-left">
|
||||||
<div v-if="props.row._template" class="column text-left">
|
<div
|
||||||
|
v-if="props.row._template"
|
||||||
|
class="column text-left"
|
||||||
|
:id="`template-step-${props.row.request.code}`"
|
||||||
|
>
|
||||||
<span>{{ props.row._template.templateName }}</span>
|
<span>{{ props.row._template.templateName }}</span>
|
||||||
<span class="app-text-muted text-caption">
|
<span class="app-text-muted text-caption">
|
||||||
{{ $t('flow.stepNo', { msg: props.row._template.step }) }}
|
{{ $t('flow.stepNo', { msg: props.row._template.step }) }}
|
||||||
|
|
@ -522,7 +535,10 @@ function disableCheckAll() {
|
||||||
</template>
|
</template>
|
||||||
</q-img>
|
</q-img>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<div class="column text-left q-ml-sm">
|
<div
|
||||||
|
class="column text-left q-ml-sm"
|
||||||
|
:id="`employee-name-${props.row.request.employee?.code}`"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{{ getEmployeeName(props.row, { locale: $i18n.locale }) }}
|
{{ getEmployeeName(props.row, { locale: $i18n.locale }) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -532,6 +548,7 @@ function disableCheckAll() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Icon
|
<Icon
|
||||||
|
:id="`icon-gender-${props.row.request.employee?.code}`"
|
||||||
class="q-ml-md"
|
class="q-ml-md"
|
||||||
:class="`app-text-${props.row.request.employee?.gender}`"
|
:class="`app-text-${props.row.request.employee?.gender}`"
|
||||||
:icon="`material-symbols:${props.row.request.employee?.gender}`"
|
:icon="`material-symbols:${props.row.request.employee?.gender}`"
|
||||||
|
|
@ -539,13 +556,18 @@ function disableCheckAll() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>{{ calculateAge(props.row.request.employee?.dateOfBirth) }}</q-td>
|
<q-td :id="`employee-age-${props.row.request.employee?.code}`">
|
||||||
<q-td>
|
{{ calculateAge(props.row.request.employee?.dateOfBirth) }}
|
||||||
|
</q-td>
|
||||||
|
<q-td :id="`employee-nationality-${props.row.request.employee?.code}`">
|
||||||
{{
|
{{
|
||||||
useOptionStore().mapOption(props.row.request.employee?.nationality)
|
useOptionStore().mapOption(props.row.request.employee?.nationality)
|
||||||
}}
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>
|
<q-td
|
||||||
|
:id="`quotation-due-date-${props.row.request.quotation?.code}`"
|
||||||
|
v-if="!statusOn"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
dateFormatJS({
|
dateFormatJS({
|
||||||
date: props.row.request.quotation?.dueDate,
|
date: props.row.request.quotation?.dueDate,
|
||||||
|
|
@ -555,13 +577,16 @@ function disableCheckAll() {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>
|
<q-td
|
||||||
|
:id="`expiration-date-${props.row.request.quotation?.code}`"
|
||||||
|
v-if="!statusOn"
|
||||||
|
>
|
||||||
<ExpirationDate
|
<ExpirationDate
|
||||||
:expiration-date="new Date(props.row.request.quotation?.dueDate)"
|
:expiration-date="new Date(props.row.request.quotation?.dueDate)"
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td>
|
<q-td v-if="!statusOn">
|
||||||
<BadgeComponent
|
<BadgeComponent
|
||||||
v-if="props.row.request.quotation?.urgent"
|
v-if="props.row.request.quotation?.urgent"
|
||||||
icon="mdi-fire"
|
icon="mdi-fire"
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ const emit = defineEmits<{
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-table
|
<q-table
|
||||||
|
id="table-task-order"
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
:columns="column"
|
:columns="column"
|
||||||
bordered
|
bordered
|
||||||
|
|
@ -133,7 +134,11 @@ const emit = defineEmits<{
|
||||||
:props="props"
|
:props="props"
|
||||||
>
|
>
|
||||||
<q-th v-if="selection !== 'none'">
|
<q-th v-if="selection !== 'none'">
|
||||||
<q-checkbox v-model="props.selected" size="sm" />
|
<q-checkbox
|
||||||
|
id="checkbox-select-all-task"
|
||||||
|
v-model="props.selected"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
</q-th>
|
</q-th>
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.label && $t(col.label) }}
|
{{ col.label && $t(col.label) }}
|
||||||
|
|
@ -149,13 +154,21 @@ const emit = defineEmits<{
|
||||||
>
|
>
|
||||||
<q-tr class="text-center" :class="{ urgent: props.row.urgent }">
|
<q-tr class="text-center" :class="{ urgent: props.row.urgent }">
|
||||||
<q-td v-if="selection !== 'none'">
|
<q-td v-if="selection !== 'none'">
|
||||||
<q-checkbox v-model="props.selected" size="sm" />
|
<q-checkbox
|
||||||
|
:id="`checkbox-task-${props.row.code}`"
|
||||||
|
v-model="props.selected"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('order')">
|
<q-td v-if="visibleColumns.includes('order')">
|
||||||
{{ props.rowIndex + 1 }}
|
{{ props.rowIndex + 1 }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('taskName')" class="text-left">
|
<q-td
|
||||||
<div>
|
v-if="visibleColumns.includes('taskName')"
|
||||||
|
class="text-left"
|
||||||
|
:id="`task-name-${props.row.code}`"
|
||||||
|
>
|
||||||
|
<div :id="`task-name-div-${props.row.code}`">
|
||||||
{{ props.row.taskName || '-' }}
|
{{ props.row.taskName || '-' }}
|
||||||
<q-tooltip :delay="300">
|
<q-tooltip :delay="300">
|
||||||
{{ props.row.taskName || '-' }}
|
{{ props.row.taskName || '-' }}
|
||||||
|
|
@ -170,31 +183,51 @@ const emit = defineEmits<{
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('issueBranch')">
|
<q-td
|
||||||
|
v-if="visibleColumns.includes('issueBranch')"
|
||||||
|
:id="`task-issue-branch-${props.row.code}`"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
$i18n.locale === 'eng'
|
$i18n.locale === 'eng'
|
||||||
? props.row.registeredBranch.nameEN || '-'
|
? props.row.registeredBranch.nameEN || '-'
|
||||||
: props.row.registeredBranch.name || '-'
|
: props.row.registeredBranch.name || '-'
|
||||||
}}
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('institution')">
|
<q-td
|
||||||
|
v-if="visibleColumns.includes('institution')"
|
||||||
|
:id="`task-institution-${props.row.code}`"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
$i18n.locale === 'eng'
|
$i18n.locale === 'eng'
|
||||||
? props.row.institution.nameEN || '-'
|
? props.row.institution.nameEN || '-'
|
||||||
: props.row.institution.name || '-'
|
: props.row.institution.name || '-'
|
||||||
}}
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('createdAt')">
|
<q-td
|
||||||
|
v-if="visibleColumns.includes('createdAt')"
|
||||||
|
:id="`task-created-at-${props.row.code}`"
|
||||||
|
>
|
||||||
{{ dateFormatJS({ date: props.row.createdAt }) || '-' }}
|
{{ dateFormatJS({ date: props.row.createdAt }) || '-' }}
|
||||||
{{ dateFormatJS({ date: props.row.createdAt, timeOnly: true }) }}
|
{{ dateFormatJS({ date: props.row.createdAt, timeOnly: true }) }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('createdBy')" class="text-left">
|
<q-td
|
||||||
|
v-if="visibleColumns.includes('createdBy')"
|
||||||
|
class="text-left"
|
||||||
|
:id="`task-created-by-${props.row.code}`"
|
||||||
|
>
|
||||||
{{ getCreatedByName(props.row, $i18n) }}
|
{{ getCreatedByName(props.row, $i18n) }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('contactTel')">
|
<q-td
|
||||||
|
v-if="visibleColumns.includes('contactTel')"
|
||||||
|
:id="`task-contact-tel-${props.row.code}`"
|
||||||
|
>
|
||||||
{{ props.row.contactTel || '-' }}
|
{{ props.row.contactTel || '-' }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('contactName')" class="text-left">
|
<q-td
|
||||||
|
v-if="visibleColumns.includes('contactName')"
|
||||||
|
class="text-left"
|
||||||
|
:id="`task-contact-name-${props.row.code}`"
|
||||||
|
>
|
||||||
{{ props.row.contactName || '-' }}
|
{{ props.row.contactName || '-' }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('taskStatus')">
|
<q-td v-if="visibleColumns.includes('taskStatus')">
|
||||||
|
|
@ -202,11 +235,12 @@ const emit = defineEmits<{
|
||||||
hide-icon
|
hide-icon
|
||||||
:hsla-color="taskOrderStatus(props.row.taskOrderStatus, 'color')"
|
:hsla-color="taskOrderStatus(props.row.taskOrderStatus, 'color')"
|
||||||
:title="$t(taskOrderStatus(props.row.taskOrderStatus, 'status'))"
|
:title="$t(taskOrderStatus(props.row.taskOrderStatus, 'status'))"
|
||||||
|
:id="`badge-task-status-${props.row.code}`"
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="selection === 'none'">
|
<q-td v-if="selection === 'none'">
|
||||||
<q-btn
|
<q-btn
|
||||||
:id="`btn-eye-${props.row.taskName}`"
|
:id="`btn-view-task-${props.row.code}`"
|
||||||
icon="mdi-eye-outline"
|
icon="mdi-eye-outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
dense
|
dense
|
||||||
|
|
@ -221,7 +255,7 @@ const emit = defineEmits<{
|
||||||
canAccess('taskOrder', 'edit')
|
canAccess('taskOrder', 'edit')
|
||||||
"
|
"
|
||||||
:hide-delete="!canAccess('taskOrder', 'create')"
|
:hide-delete="!canAccess('taskOrder', 'create')"
|
||||||
:idName="`btn-kebab-${props.row.taskName}`"
|
:idName="`btn-kebab-${props.row.code}`"
|
||||||
status="'ACTIVE'"
|
status="'ACTIVE'"
|
||||||
hide-toggle
|
hide-toggle
|
||||||
@view="$emit('view', props.row)"
|
@view="$emit('view', props.row)"
|
||||||
|
|
@ -233,6 +267,7 @@ const emit = defineEmits<{
|
||||||
<q-btn
|
<q-btn
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
|
:id="`btn-sub-row-${props.row.code}`"
|
||||||
class="rounded"
|
class="rounded"
|
||||||
@click.stop="
|
@click.stop="
|
||||||
() => {
|
() => {
|
||||||
|
|
|
||||||
|
|
@ -60,16 +60,25 @@ function inactiveCheck() {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
:id="`readonly-status-${status}`"
|
||||||
|
:for="`readonly-status-${status}`"
|
||||||
v-if="readonly"
|
v-if="readonly"
|
||||||
class="row rounded bordered surface-2 items-center justify-center q-pa-xs no-wrap"
|
class="row rounded bordered surface-2 items-center justify-center q-pa-xs no-wrap"
|
||||||
:style="`color: hsl(var(--${currStatus?.color}-bg))`"
|
:style="`color: hsl(var(--${currStatus?.color}-bg))`"
|
||||||
>
|
>
|
||||||
<q-icon :name="currStatus?.icon" class="q-pr-xs" size="xs" />
|
<q-icon
|
||||||
{{ $t(`taskOrder.status.${status}`) }}
|
:id="`readonly-status-icon-${status}`"
|
||||||
|
:name="currStatus?.icon"
|
||||||
|
class="q-pr-xs"
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
<span :id="`readonly-status-label-${status}`">{{ $t(`taskOrder.status.${status}`) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="row items-center justify-center no-wrap">
|
<div v-else class="row items-center justify-center no-wrap">
|
||||||
<q-btn-dropdown
|
<q-btn-dropdown
|
||||||
|
:id="`btn-dropdown-status-${status}`"
|
||||||
|
:for="`btn-dropdown-status-${status}`"
|
||||||
dense
|
dense
|
||||||
unelevated
|
unelevated
|
||||||
:label="
|
:label="
|
||||||
|
|
@ -110,6 +119,8 @@ function inactiveCheck() {
|
||||||
>
|
>
|
||||||
<q-list v-if="!noAction" dense>
|
<q-list v-if="!noAction" dense>
|
||||||
<q-item
|
<q-item
|
||||||
|
:for="`menu-item-status-${v.value}`"
|
||||||
|
:id="`menu-item-status-${v.value}`"
|
||||||
v-for="(v, index) in type === 'order'
|
v-for="(v, index) in type === 'order'
|
||||||
? {
|
? {
|
||||||
Success: taskStatusOrderToggle.filter(
|
Success: taskStatusOrderToggle.filter(
|
||||||
|
|
@ -147,6 +158,8 @@ function inactiveCheck() {
|
||||||
</q-btn-dropdown>
|
</q-btn-dropdown>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
|
:id="`btn-failed-remark-${status}`"
|
||||||
|
:for="`btn-failed-remark-${status}`"
|
||||||
v-if="currStatus?.value === TaskStatus.Failed"
|
v-if="currStatus?.value === TaskStatus.Failed"
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
|
|
|
||||||
|
|
@ -202,40 +202,44 @@ onMounted(async () => {
|
||||||
})
|
})
|
||||||
.reduce(
|
.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const priceNoVat = c.product.serviceChargeVatIncluded
|
const vatFactor = c.product.serviceChargeCalcVat
|
||||||
? c.pricePerUnit / (1 + (config.value?.vat || 0.07))
|
? (config.value?.vat ?? 0.07)
|
||||||
: c.pricePerUnit;
|
: 0;
|
||||||
const adjustedPriceWithVat = precisionRound(
|
|
||||||
priceNoVat * (1 + (config.value?.vat || 0.07)),
|
|
||||||
);
|
|
||||||
const adjustedPriceNoVat =
|
|
||||||
adjustedPriceWithVat / (1 + (config.value?.vat || 0.07));
|
|
||||||
const priceDiscountNoVat = adjustedPriceNoVat * c.amount - c.discount;
|
|
||||||
|
|
||||||
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07);
|
const pricePerUnit =
|
||||||
const rawVat = rawVatTotal / c.amount;
|
precisionRound(c.product.serviceCharge * (1 + vatFactor)) /
|
||||||
|
(1 + vatFactor);
|
||||||
|
|
||||||
|
const amount = c.amount;
|
||||||
|
const discount = c.discount || 0;
|
||||||
|
|
||||||
|
const price =
|
||||||
|
(pricePerUnit * amount * (1 + vatFactor) - discount) /
|
||||||
|
(1 + vatFactor);
|
||||||
|
|
||||||
|
const vat = price * vatFactor;
|
||||||
|
|
||||||
product.value.push({
|
product.value.push({
|
||||||
id: c.product.id,
|
id: c.product.id,
|
||||||
code: c.product.code,
|
code: c.product.code,
|
||||||
detail: c.product.name,
|
detail: c.product.name,
|
||||||
priceUnit: precisionRound(priceNoVat),
|
priceUnit: precisionRound(pricePerUnit),
|
||||||
amount: c.amount,
|
amount: c.amount,
|
||||||
discount: c.discount,
|
discount: c.discount,
|
||||||
vat: c.product.serviceChargeCalcVat ? precisionRound(rawVat) : 0,
|
vat: c.product.serviceChargeCalcVat ? precisionRound(vat) : 0,
|
||||||
value: precisionRound(
|
value: precisionRound(
|
||||||
priceNoVat * c.amount +
|
pricePerUnit * c.amount +
|
||||||
(c.product.serviceChargeCalcVat ? rawVatTotal : 0),
|
(c.product.serviceChargeCalcVat ? vat : 0),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
a.totalPrice = a.totalPrice + priceDiscountNoVat;
|
a.totalPrice = precisionRound(a.totalPrice + price + discount);
|
||||||
a.totalDiscount = a.totalDiscount + Number(c.discount);
|
a.totalDiscount = precisionRound(a.totalDiscount + discount);
|
||||||
a.vat = c.product.calcVat ? a.vat + rawVatTotal : a.vat;
|
a.vat = precisionRound(a.vat + vat);
|
||||||
a.vatExcluded = c.product.calcVat
|
a.vatExcluded = c.product.serviceChargeCalcVat
|
||||||
? a.vatExcluded
|
? a.vatExcluded
|
||||||
: precisionRound(a.vatExcluded + priceDiscountNoVat);
|
: precisionRound(a.vatExcluded + price);
|
||||||
a.finalPrice = a.totalPrice - a.totalDiscount + a.vat;
|
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -311,7 +315,7 @@ function closeAble() {
|
||||||
border-bottom: 2px solid var(--main);
|
border-bottom: 2px solid var(--main);
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ $t('taskOrder.goodReceipt') }}
|
{{ $t('preview.productList') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<table ref="elements" class="q-mb-sm" cellpadding="0" style="width: 100%">
|
<table ref="elements" class="q-mb-sm" cellpadding="0" style="width: 100%">
|
||||||
|
|
@ -335,7 +339,9 @@ function closeAble() {
|
||||||
{{ formatNumberDecimal(v.priceUnit, 2) }}
|
{{ formatNumberDecimal(v.priceUnit, 2) }}
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ formatNumberDecimal(v.discount, 2) }}
|
<template v-if="v.discount !== 0">
|
||||||
|
{{ formatNumberDecimal(v.discount, 2) }} ฿
|
||||||
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ formatNumberDecimal(v.vat, 2) }}
|
{{ formatNumberDecimal(v.vat, 2) }}
|
||||||
|
|
@ -346,7 +352,6 @@ function closeAble() {
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<table
|
<table
|
||||||
style="width: 40%; margin-left: auto"
|
style="width: 40%; margin-left: auto"
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
|
|
@ -398,7 +403,9 @@ function closeAble() {
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
summaryPrice.totalPrice - summaryPrice.totalDiscount,
|
summaryPrice.totalPrice -
|
||||||
|
summaryPrice.totalDiscount -
|
||||||
|
summaryPrice.vatExcluded,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ defineProps<{
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>เลขประจำตัวผู้เสียภาษี {{ branch.taxNo }}</span>
|
<span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
|
||||||
<span>เบอร์โทร {{ branch.telephoneNo }}</span>
|
<span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
|
||||||
<span>{{ branch.webUrl }}</span>
|
<span>{{ branch.webUrl }}</span>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ const fileData = defineModel<
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
id="expansion-additional-file"
|
||||||
dense
|
dense
|
||||||
class="overflow-hidden bordered full-width"
|
class="overflow-hidden bordered full-width"
|
||||||
switch-toggle-side
|
switch-toggle-side
|
||||||
|
|
@ -41,6 +42,7 @@ const fileData = defineModel<
|
||||||
|
|
||||||
<main class="q-px-md q-py-sm surface-1">
|
<main class="q-px-md q-py-sm surface-1">
|
||||||
<UploadFileSection
|
<UploadFileSection
|
||||||
|
id="upload-additional-file"
|
||||||
multiple
|
multiple
|
||||||
:layout="$q.screen.gt.sm ? 'column' : 'row'"
|
:layout="$q.screen.gt.sm ? 'column' : 'row'"
|
||||||
:readonly
|
:readonly
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
id="expansion-document"
|
||||||
default-opened
|
default-opened
|
||||||
dense
|
dense
|
||||||
class="overflow-hidden bordered full-width"
|
class="overflow-hidden bordered full-width"
|
||||||
|
|
@ -38,13 +39,16 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
|
|
||||||
<main class="q-px-md q-py-sm surface-1 row q-col-gutter-sm">
|
<main class="q-px-md q-py-sm surface-1 row q-col-gutter-sm">
|
||||||
<SelectBranch
|
<SelectBranch
|
||||||
|
id="select-issue-branch"
|
||||||
:readonly
|
:readonly
|
||||||
|
required
|
||||||
class="col-md-4 col-12"
|
class="col-md-4 col-12"
|
||||||
:label="`${$t('taskOrder.issueBranch')}${$i18n.locale === 'tha' ? $t('taskOrder.title') : ''}`"
|
:label="`${$t('taskOrder.issueBranch')}${$i18n.locale === 'tha' ? $t('taskOrder.title') : ''}`"
|
||||||
v-model:value="registeredBranchId"
|
v-model:value="registeredBranchId"
|
||||||
auto-select-on-single
|
auto-select-on-single
|
||||||
/>
|
/>
|
||||||
<SelectInstitution
|
<SelectInstitution
|
||||||
|
id="select-agencies"
|
||||||
:readonly
|
:readonly
|
||||||
required
|
required
|
||||||
class="col-md-4 col-12"
|
class="col-md-4 col-12"
|
||||||
|
|
@ -55,6 +59,7 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
auto-select-on-single
|
auto-select-on-single
|
||||||
/>
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
id="datepicker-issue-date"
|
||||||
:label="$t('taskOrder.issueDate')"
|
:label="$t('taskOrder.issueDate')"
|
||||||
class="col-md-2 col-6"
|
class="col-md-2 col-6"
|
||||||
:model-value="issueDate || new Date(Date.now())"
|
:model-value="issueDate || new Date(Date.now())"
|
||||||
|
|
@ -62,6 +67,7 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
:disabled="!readonly"
|
:disabled="!readonly"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
|
id="input-task-code"
|
||||||
:label="$t('taskOrder.code')"
|
:label="$t('taskOrder.code')"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
@ -72,6 +78,7 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
|
id="input-task-name"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('general.name', { msg: $t('taskOrder.title') })"
|
:label="$t('general.name', { msg: $t('taskOrder.title') })"
|
||||||
outlined
|
outlined
|
||||||
|
|
@ -80,6 +87,7 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
v-model="taskName"
|
v-model="taskName"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
|
id="input-contact-name"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('taskOrder.contactName')"
|
:label="$t('taskOrder.contactName')"
|
||||||
outlined
|
outlined
|
||||||
|
|
@ -88,6 +96,7 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
v-model="contactName"
|
v-model="contactName"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
|
id="input-contact-tel"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('general.telephone')"
|
:label="$t('general.telephone')"
|
||||||
outlined
|
outlined
|
||||||
|
|
@ -96,6 +105,7 @@ const contactTel = defineModel<string>('contactTel');
|
||||||
v-model="contactTel"
|
v-model="contactTel"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
|
id="input-made-by"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('taskOrder.madeBy')"
|
:label="$t('taskOrder.madeBy')"
|
||||||
outlined
|
outlined
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ const summaryPrice = defineModel<{
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
id="expansion-payment"
|
||||||
dense
|
dense
|
||||||
class="overflow-hidden bordered full-width"
|
class="overflow-hidden bordered full-width"
|
||||||
switch-toggle-side
|
switch-toggle-side
|
||||||
|
|
@ -47,6 +48,7 @@ const summaryPrice = defineModel<{
|
||||||
|
|
||||||
<main class="q-px-md q-py-sm surface-1">
|
<main class="q-px-md q-py-sm surface-1">
|
||||||
<QuotationFormInfo
|
<QuotationFormInfo
|
||||||
|
id="form-info-payment"
|
||||||
task-order
|
task-order
|
||||||
:task-order-complete="complete"
|
:task-order-complete="complete"
|
||||||
v-model:pay-type="payType"
|
v-model:pay-type="payType"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ import { storeToRefs } from 'pinia';
|
||||||
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
||||||
import { TaskStatus } from 'src/stores/task-order/types';
|
import { TaskStatus } from 'src/stores/task-order/types';
|
||||||
|
|
||||||
|
import { useTaskOrderForm } from '../form';
|
||||||
|
|
||||||
|
const taskOrderFormStore = useTaskOrderForm();
|
||||||
|
|
||||||
const currentBtnOpen = ref<boolean[]>([]);
|
const currentBtnOpen = ref<boolean[]>([]);
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
|
|
@ -61,60 +65,28 @@ function openList(index: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcPricePerUnit(product: RequestWork['productService']['product']) {
|
function calcPricePerUnit(product: RequestWork['productService']['product']) {
|
||||||
const val = props.creditNote
|
return product.serviceCharge;
|
||||||
? props.agentPrice
|
|
||||||
? product.agentPrice
|
|
||||||
: product.price
|
|
||||||
: product.serviceCharge;
|
|
||||||
|
|
||||||
if (
|
|
||||||
product[
|
|
||||||
props.creditNote
|
|
||||||
? props.agentPrice
|
|
||||||
? 'agentPriceCalcVat'
|
|
||||||
: 'calcVat'
|
|
||||||
: 'serviceChargeCalcVat'
|
|
||||||
]
|
|
||||||
) {
|
|
||||||
return val / (1 + (config.value?.vat || 0.07));
|
|
||||||
} else {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcPrice(
|
function calcPrice(
|
||||||
product: RequestWork['productService']['product'],
|
product: RequestWork['productService']['product'],
|
||||||
amount: number,
|
amount: number,
|
||||||
) {
|
) {
|
||||||
const pricePerUnit = props.creditNote
|
const vatFactor = product.serviceChargeCalcVat
|
||||||
? props.agentPrice
|
? (config.value?.vat ?? 0.07)
|
||||||
? product.agentPrice
|
|
||||||
: product.price
|
|
||||||
: product.serviceCharge;
|
|
||||||
const discount =
|
|
||||||
taskProduct.value.find((v) => v.productId === product.id)?.discount || 0;
|
|
||||||
const priceNoVat = product[
|
|
||||||
props.creditNote
|
|
||||||
? props.agentPrice
|
|
||||||
? 'agentPriceVatIncluded'
|
|
||||||
: 'vatIncluded'
|
|
||||||
: 'serviceChargeVatIncluded'
|
|
||||||
]
|
|
||||||
? pricePerUnit / (1 + (config.value?.vat || 0.07))
|
|
||||||
: pricePerUnit;
|
|
||||||
const priceDiscountNoVat = priceNoVat * amount - discount;
|
|
||||||
|
|
||||||
const rawVatTotal = product[
|
|
||||||
props.creditNote
|
|
||||||
? props.agentPrice
|
|
||||||
? 'agentPriceCalcVat'
|
|
||||||
: 'calcVat'
|
|
||||||
: 'serviceChargeCalcVat'
|
|
||||||
]
|
|
||||||
? priceDiscountNoVat * (config.value?.vat || 0.07)
|
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return precisionRound(priceNoVat * amount + rawVatTotal);
|
const discount =
|
||||||
|
taskProduct.value.find((v) => v.productId === product.id)?.discount || 0;
|
||||||
|
|
||||||
|
const pricePerUnit = precisionRound(product.serviceCharge);
|
||||||
|
|
||||||
|
const price =
|
||||||
|
(pricePerUnit * amount * (1 + vatFactor) - discount) / (1 + vatFactor);
|
||||||
|
|
||||||
|
const vat = price * vatFactor;
|
||||||
|
|
||||||
|
return price + vat;
|
||||||
}
|
}
|
||||||
|
|
||||||
function taskOrderStatus(value: TaskStatus) {
|
function taskOrderStatus(value: TaskStatus) {
|
||||||
|
|
@ -139,6 +111,7 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
id="expansion-product-list"
|
||||||
dense
|
dense
|
||||||
class="overflow-hidden bordered full-width"
|
class="overflow-hidden bordered full-width"
|
||||||
switch-toggle-side
|
switch-toggle-side
|
||||||
|
|
@ -151,9 +124,11 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
<span
|
<span
|
||||||
class="row items-center justify-between full-width"
|
class="row items-center justify-between full-width"
|
||||||
style="min-height: 31.01px"
|
style="min-height: 31.01px"
|
||||||
|
id="header-product-list"
|
||||||
>
|
>
|
||||||
{{ $t('general.information', { msg: $t('taskOrder.productList') }) }}
|
{{ $t('general.information', { msg: $t('taskOrder.productList') }) }}
|
||||||
<AddButton
|
<AddButton
|
||||||
|
id="btn-add-product"
|
||||||
icon-only
|
icon-only
|
||||||
@click.stop="$emit('addProduct')"
|
@click.stop="$emit('addProduct')"
|
||||||
v-if="!readonly"
|
v-if="!readonly"
|
||||||
|
|
@ -163,6 +138,7 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
|
|
||||||
<main class="q-px-md q-py-sm surface-1">
|
<main class="q-px-md q-py-sm surface-1">
|
||||||
<q-table
|
<q-table
|
||||||
|
id="table-product-list"
|
||||||
:columns="
|
:columns="
|
||||||
creditNote
|
creditNote
|
||||||
? productColumn.filter(
|
? productColumn.filter(
|
||||||
|
|
@ -208,7 +184,7 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||||
>
|
>
|
||||||
<q-tr class="text-center">
|
<q-tr class="text-center">
|
||||||
<q-td>
|
<q-td :id="`product-order-${props.rowIndex}`">
|
||||||
{{ props.rowIndex + 1 }}
|
{{ props.rowIndex + 1 }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>
|
<q-td>
|
||||||
|
|
@ -234,20 +210,21 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td class="text-left" v-if="!creditNote">
|
<q-td class="text-left" v-if="!creditNote">
|
||||||
<BadgeComponent
|
<BadgeComponent
|
||||||
|
:id="`badge-status-${props.row.product.code}`"
|
||||||
hide-icon
|
hide-icon
|
||||||
:hsla-color="taskOrderStatus(props.row.product.taskStatus)"
|
:hsla-color="taskOrderStatus(props.row.product.taskStatus)"
|
||||||
:title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`"
|
:title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`"
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td>
|
<q-td :id="`product-amount-${props.row.product.code}`">
|
||||||
{{ props.row.list.length }}
|
{{ props.row.list.length }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td class="text-right">
|
<q-td :id="`product-price-per-unit-${props.row.product.code}`" class="text-right">
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
calcPricePerUnit(props.row.product) +
|
calcPricePerUnit(props.row.product) +
|
||||||
(props.row.product.calcVat
|
(props.row.product.serviceChargeCalcVat
|
||||||
? calcPricePerUnit(props.row.product) *
|
? calcPricePerUnit(props.row.product) *
|
||||||
(config?.vat || 0.07)
|
(config?.vat || 0.07)
|
||||||
: 0),
|
: 0),
|
||||||
|
|
@ -258,6 +235,7 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
<!-- TODO: display price detail -->
|
<!-- TODO: display price detail -->
|
||||||
<q-td align="center" v-if="!creditNote">
|
<q-td align="center" v-if="!creditNote">
|
||||||
<q-input
|
<q-input
|
||||||
|
:id="`input-discount-${props.row.product.code}`"
|
||||||
:readonly
|
:readonly
|
||||||
:bg-color="readonly ? 'transparent' : ''"
|
:bg-color="readonly ? 'transparent' : ''"
|
||||||
dense
|
dense
|
||||||
|
|
@ -295,37 +273,49 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
<!-- before vat -->
|
<!-- before vat -->
|
||||||
<q-td class="text-right" v-if="!creditNote">
|
<q-td
|
||||||
|
:id="`product-price-before-vat-${props.row.product.code}`"
|
||||||
|
class="text-right"
|
||||||
|
v-if="!creditNote"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
calcPricePerUnit(props.row.product) * props.row.list.length -
|
props.row.product.serviceChargeCalcVat
|
||||||
(taskProduct.find(
|
? (calcPricePerUnit(props.row.product) *
|
||||||
|
props.row.list.length *
|
||||||
|
(1 + (config?.vat || 0.07))) /
|
||||||
|
(1 + (config?.vat || 0.07))
|
||||||
|
: calcPricePerUnit(props.row.product) *
|
||||||
|
props.row.list.length -
|
||||||
|
taskProduct.find(
|
||||||
(v) => v.productId === props.row.product.id,
|
(v) => v.productId === props.row.product.id,
|
||||||
)?.discount || 0),
|
)?.discount || 0,
|
||||||
2,
|
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<!-- vat -->
|
<!-- vat -->
|
||||||
<q-td class="text-right" v-if="!creditNote">
|
<q-td
|
||||||
|
:id="`product-vat-${props.row.product.code}`"
|
||||||
|
class="text-right"
|
||||||
|
v-if="!creditNote"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
props.row.product.calcVat
|
props.row.product.serviceChargeCalcVat
|
||||||
? precisionRound(
|
? ((calcPricePerUnit(props.row.product) *
|
||||||
(calcPricePerUnit(props.row.product) *
|
props.row.list.length *
|
||||||
props.row.list.length -
|
(1 + (config?.vat || 0.07)) -
|
||||||
(taskProduct.find(
|
taskProduct.find(
|
||||||
(v) => v.productId === props.row.product.id,
|
(v) => v.productId === props.row.product.id,
|
||||||
)?.discount || 0)) *
|
)?.discount || 0) /
|
||||||
(config?.vat || 0.07),
|
(1 + (config?.vat || 0.07))) *
|
||||||
)
|
0.07
|
||||||
: 0,
|
: 0,
|
||||||
2,
|
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</q-td>
|
</q-td>
|
||||||
<!-- total -->
|
<!-- total -->
|
||||||
<q-td class="text-right">
|
<q-td :id="`product-total-price-${props.row.product.code}`" class="text-right">
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
calcPrice(props.row.product, props.row.list.length),
|
calcPrice(props.row.product, props.row.list.length),
|
||||||
|
|
@ -335,6 +325,7 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>
|
<q-td>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
:id="`btn-toggle-employee-${props.row.product.code}`"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
class="rounded"
|
class="rounded"
|
||||||
|
|
@ -358,6 +349,7 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
<q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props">
|
<q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props">
|
||||||
<q-td colspan="100%" style="padding: 16px">
|
<q-td colspan="100%" style="padding: 16px">
|
||||||
<TableEmployee
|
<TableEmployee
|
||||||
|
:id="`table-employee-in-product-${props.row.product.code}`"
|
||||||
:step-on="!creditNote"
|
:step-on="!creditNote"
|
||||||
:status-on="creditNote"
|
:status-on="creditNote"
|
||||||
:rows="props.row.list"
|
:rows="props.row.list"
|
||||||
|
|
@ -375,7 +367,9 @@ function taskOrderStatus(value: TaskStatus) {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<div class="surface-3 q-px-sm rounded">{{ taskList.length }}</div>
|
<div class="surface-3 q-px-sm rounded" id="total-product-count">
|
||||||
|
{{ taskList.length }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ const getToolbarConfig = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
id="expansion-remark"
|
||||||
dense
|
dense
|
||||||
class="overflow-hidden bordered full-width"
|
class="overflow-hidden bordered full-width"
|
||||||
switch-toggle-side
|
switch-toggle-side
|
||||||
|
|
@ -48,6 +49,7 @@ const getToolbarConfig = computed(() => {
|
||||||
|
|
||||||
<main class="surface-1 q-pa-md full-width">
|
<main class="surface-1 q-pa-md full-width">
|
||||||
<q-editor
|
<q-editor
|
||||||
|
id="editor-remark"
|
||||||
dense
|
dense
|
||||||
:readonly="readonly || !remarkWrite"
|
:readonly="readonly || !remarkWrite"
|
||||||
:model-value="
|
:model-value="
|
||||||
|
|
@ -90,6 +92,7 @@ const getToolbarConfig = computed(() => {
|
||||||
<template v-if="!readonly" v-slot:toggle>
|
<template v-if="!readonly" v-slot:toggle>
|
||||||
<div class="text-caption row no-wrap q-px-sm">
|
<div class="text-caption row no-wrap q-px-sm">
|
||||||
<MainButton
|
<MainButton
|
||||||
|
id="btn-remark-view"
|
||||||
:solid="!remarkWrite"
|
:solid="!remarkWrite"
|
||||||
icon="mdi-eye-outline"
|
icon="mdi-eye-outline"
|
||||||
color="0 0% 40%"
|
color="0 0% 40%"
|
||||||
|
|
@ -102,6 +105,7 @@ const getToolbarConfig = computed(() => {
|
||||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
<MainButton
|
<MainButton
|
||||||
|
id="btn-remark-edit"
|
||||||
:solid="remarkWrite"
|
:solid="remarkWrite"
|
||||||
icon="mdi-pencil-outline"
|
icon="mdi-pencil-outline"
|
||||||
color="0 0% 40%"
|
color="0 0% 40%"
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,17 @@ defineProps<{
|
||||||
<div class="row q-col-gutter-sm q-px-md q-py-sm">
|
<div class="row q-col-gutter-sm q-px-md q-py-sm">
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
class="col-md col-6"
|
class="col-md col-6"
|
||||||
|
id="dd-recipient"
|
||||||
:label="$t('taskOrder.recipientOrSender')"
|
:label="$t('taskOrder.recipientOrSender')"
|
||||||
>
|
>
|
||||||
<template #value>
|
<template #value>
|
||||||
<q-avatar size="md" class="q-mr-xs">
|
<q-avatar size="md" class="q-mr-xs">
|
||||||
<q-img class="text-center" :ratio="1" :src="contactUrl">
|
<q-img
|
||||||
|
:id="`img-avatar-${contactName}`"
|
||||||
|
class="text-center"
|
||||||
|
:ratio="1"
|
||||||
|
:src="contactUrl"
|
||||||
|
>
|
||||||
<template #error>
|
<template #error>
|
||||||
<div
|
<div
|
||||||
class="no-padding full-width full-height flex items-center justify-center"
|
class="no-padding full-width full-height flex items-center justify-center"
|
||||||
|
|
@ -59,6 +65,7 @@ defineProps<{
|
||||||
>
|
>
|
||||||
<q-img
|
<q-img
|
||||||
v-if="gender"
|
v-if="gender"
|
||||||
|
:id="`img-gender-${contactName}`"
|
||||||
:src="
|
:src="
|
||||||
gender === 'male'
|
gender === 'male'
|
||||||
? '/no-img-man.png'
|
? '/no-img-man.png'
|
||||||
|
|
@ -67,6 +74,7 @@ defineProps<{
|
||||||
/>
|
/>
|
||||||
<q-icon
|
<q-icon
|
||||||
v-else
|
v-else
|
||||||
|
:id="`icon-avatar-${contactName}`"
|
||||||
size="sm"
|
size="sm"
|
||||||
name="mdi-account-outline"
|
name="mdi-account-outline"
|
||||||
style="color: white"
|
style="color: white"
|
||||||
|
|
@ -81,18 +89,21 @@ defineProps<{
|
||||||
|
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
class="col-md col-6"
|
class="col-md col-6"
|
||||||
|
id="dd-telephone"
|
||||||
:label="$t('general.telephone')"
|
:label="$t('general.telephone')"
|
||||||
:value="contactTel || '-'"
|
:value="contactTel || '-'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
class="col-md col-6"
|
class="col-md col-6"
|
||||||
|
id="dd-email"
|
||||||
:label="$t('form.email')"
|
:label="$t('form.email')"
|
||||||
:value="email || '-'"
|
:value="email || '-'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
class="col-md col-6"
|
class="col-md col-6"
|
||||||
|
id="dd-work-start-date"
|
||||||
:label="$t('taskOrder.workStartDate')"
|
:label="$t('taskOrder.workStartDate')"
|
||||||
>
|
>
|
||||||
<template #value>
|
<template #value>
|
||||||
|
|
@ -106,6 +117,7 @@ defineProps<{
|
||||||
|
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
class="col-md col-6"
|
class="col-md col-6"
|
||||||
|
id="dd-work-submission-date"
|
||||||
:label="$t('taskOrder.workSubmissionDate')"
|
:label="$t('taskOrder.workSubmissionDate')"
|
||||||
>
|
>
|
||||||
<template #value>
|
<template #value>
|
||||||
|
|
@ -117,9 +129,14 @@ defineProps<{
|
||||||
</template>
|
</template>
|
||||||
</DataDisplay>
|
</DataDisplay>
|
||||||
|
|
||||||
<DataDisplay class="col-md col-6" :label="$t('general.status')">
|
<DataDisplay
|
||||||
|
id="dd-status"
|
||||||
|
class="col-md col-6"
|
||||||
|
:label="$t('general.status')"
|
||||||
|
>
|
||||||
<template #value>
|
<template #value>
|
||||||
<BadgeComponent
|
<BadgeComponent
|
||||||
|
:id="`badge-status-${contactName}`"
|
||||||
v-if="status"
|
v-if="status"
|
||||||
hide-icon
|
hide-icon
|
||||||
:title="
|
:title="
|
||||||
|
|
|
||||||
|
|
@ -128,32 +128,33 @@ function getPrice(
|
||||||
})[];
|
})[];
|
||||||
}[],
|
}[],
|
||||||
) {
|
) {
|
||||||
return list.reduce(
|
const value = list.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const pricePerUnit = c.product.serviceCharge;
|
const vatFactor = c.product.serviceChargeCalcVat
|
||||||
|
? (config.value?.vat ?? 0.07)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const pricePerUnit =
|
||||||
|
precisionRound(c.product.serviceCharge * (1 + vatFactor)) /
|
||||||
|
(1 + vatFactor);
|
||||||
|
|
||||||
const amount = c.list.length;
|
const amount = c.list.length;
|
||||||
const discount =
|
const discount =
|
||||||
taskProduct.value.find((v) => v.productId === c.product.id)?.discount ||
|
taskProduct.value.find((v) => v.productId === c.product.id)?.discount ||
|
||||||
0;
|
0;
|
||||||
const priceNoVat = c.product.serviceChargeVatIncluded
|
|
||||||
? pricePerUnit / (1 + (config.value?.vat || 0.07))
|
|
||||||
: pricePerUnit;
|
|
||||||
const adjustedPriceWithVat = precisionRound(
|
|
||||||
priceNoVat * (1 + (config.value?.vat || 0.07)),
|
|
||||||
);
|
|
||||||
const adjustedPriceNoVat =
|
|
||||||
adjustedPriceWithVat / (1 + (config.value?.vat || 0.07));
|
|
||||||
const priceDiscountNoVat = adjustedPriceNoVat * amount - discount;
|
|
||||||
|
|
||||||
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07);
|
const price =
|
||||||
|
(pricePerUnit * amount * (1 + vatFactor) - discount) / (1 + vatFactor);
|
||||||
|
|
||||||
a.totalPrice = a.totalPrice + priceDiscountNoVat;
|
const vat = price * vatFactor;
|
||||||
a.totalDiscount = a.totalDiscount + Number(discount);
|
|
||||||
a.vat = c.product.serviceChargeCalcVat ? a.vat + rawVatTotal : a.vat;
|
a.totalPrice = precisionRound(a.totalPrice + price + discount);
|
||||||
|
a.totalDiscount = precisionRound(a.totalDiscount + discount);
|
||||||
|
a.vat = precisionRound(a.vat + vat);
|
||||||
a.vatExcluded = c.product.serviceChargeCalcVat
|
a.vatExcluded = c.product.serviceChargeCalcVat
|
||||||
? a.vatExcluded
|
? a.vatExcluded
|
||||||
: precisionRound(a.vatExcluded + priceDiscountNoVat);
|
: precisionRound(a.vatExcluded + price);
|
||||||
a.finalPrice = a.totalPrice - a.totalDiscount + a.vat;
|
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -164,6 +165,8 @@ function getPrice(
|
||||||
finalPrice: 0,
|
finalPrice: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTemplateData(
|
function getTemplateData(
|
||||||
|
|
@ -811,7 +814,7 @@ watch(
|
||||||
>
|
>
|
||||||
<section class="banner" :class="{ dark: $q.dark.isActive }"></section>
|
<section class="banner" :class="{ dark: $q.dark.isActive }"></section>
|
||||||
<div style="flex: 1" class="row items-center">
|
<div style="flex: 1" class="row items-center">
|
||||||
<RouterLink to="/task-order">
|
<RouterLink to="/task-order" id="link-task-order">
|
||||||
<q-img src="/icons/favicon-512x512.png" width="3rem" />
|
<q-img src="/icons/favicon-512x512.png" width="3rem" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span class="column text-h6 text-bold q-ml-md">
|
<span class="column text-h6 text-bold q-ml-md">
|
||||||
|
|
@ -860,6 +863,7 @@ watch(
|
||||||
<!-- TODO: replace step and status -->
|
<!-- TODO: replace step and status -->
|
||||||
<StateButton
|
<StateButton
|
||||||
v-for="i in statusTabForm"
|
v-for="i in statusTabForm"
|
||||||
|
:id="`btn-status-${i.title}`"
|
||||||
:key="i.title"
|
:key="i.title"
|
||||||
:label="$t(`taskOrder.${i.title}`)"
|
:label="$t(`taskOrder.${i.title}`)"
|
||||||
:status-active="i.active?.()"
|
:status-active="i.active?.()"
|
||||||
|
|
@ -913,6 +917,7 @@ watch(
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<DocumentExpansion
|
<DocumentExpansion
|
||||||
|
id="expansion-document"
|
||||||
:readonly="!['create', 'edit'].includes(state.mode || '')"
|
:readonly="!['create', 'edit'].includes(state.mode || '')"
|
||||||
v-model:registered-branch-id="currentFormData.registeredBranchId"
|
v-model:registered-branch-id="currentFormData.registeredBranchId"
|
||||||
v-model:institution-id="currentFormData.institutionId"
|
v-model:institution-id="currentFormData.institutionId"
|
||||||
|
|
@ -932,6 +937,7 @@ watch(
|
||||||
/>
|
/>
|
||||||
</q-form>
|
</q-form>
|
||||||
<ProductExpansion
|
<ProductExpansion
|
||||||
|
id="expansion-product"
|
||||||
ref="refProductExpansion"
|
ref="refProductExpansion"
|
||||||
v-if="
|
v-if="
|
||||||
view === TaskOrderStatus.Pending ||
|
view === TaskOrderStatus.Pending ||
|
||||||
|
|
@ -944,6 +950,7 @@ watch(
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PaymentExpansion
|
<PaymentExpansion
|
||||||
|
id="expansion-payment"
|
||||||
v-model:summary-price="summaryPrice"
|
v-model:summary-price="summaryPrice"
|
||||||
:complete="view === TaskOrderStatus.Complete"
|
:complete="view === TaskOrderStatus.Complete"
|
||||||
v-if="
|
v-if="
|
||||||
|
|
@ -953,6 +960,7 @@ watch(
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AdditionalFileExpansion
|
<AdditionalFileExpansion
|
||||||
|
id="expansion-additional-file"
|
||||||
:readonly="!['create', 'edit'].includes(state.mode || '')"
|
:readonly="!['create', 'edit'].includes(state.mode || '')"
|
||||||
v-if="
|
v-if="
|
||||||
view === TaskOrderStatus.Pending ||
|
view === TaskOrderStatus.Pending ||
|
||||||
|
|
@ -1011,6 +1019,7 @@ watch(
|
||||||
/>
|
/>
|
||||||
<!-- TODO: blind remark, urgent -->
|
<!-- TODO: blind remark, urgent -->
|
||||||
<RemarkExpansion
|
<RemarkExpansion
|
||||||
|
id="expansion-remark"
|
||||||
v-if="
|
v-if="
|
||||||
view === TaskOrderStatus.Pending ||
|
view === TaskOrderStatus.Pending ||
|
||||||
view === TaskOrderStatus.Complete
|
view === TaskOrderStatus.Complete
|
||||||
|
|
@ -1035,6 +1044,7 @@ watch(
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<InfoMessengerExpansion
|
<InfoMessengerExpansion
|
||||||
|
:id="`expansion-messenger-${messengerIndex}`"
|
||||||
v-for="(v, messengerIndex) in messengerListGroup"
|
v-for="(v, messengerIndex) in messengerListGroup"
|
||||||
:key="messengerIndex"
|
:key="messengerIndex"
|
||||||
:gender="getMessengerName(v.responsibleUser, { gender: true })"
|
:gender="getMessengerName(v.responsibleUser, { gender: true })"
|
||||||
|
|
@ -1070,6 +1080,7 @@ watch(
|
||||||
class="bordered-b"
|
class="bordered-b"
|
||||||
>
|
>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
:id="`expansion-product-${productIndex}`"
|
||||||
dense
|
dense
|
||||||
class="overflow-hidden"
|
class="overflow-hidden"
|
||||||
switch-toggle-side
|
switch-toggle-side
|
||||||
|
|
@ -1243,6 +1254,7 @@ watch(
|
||||||
<nav class="row justify-end">
|
<nav class="row justify-end">
|
||||||
<MainButton
|
<MainButton
|
||||||
class="q-mr-auto"
|
class="q-mr-auto"
|
||||||
|
id="btn-view-example"
|
||||||
v-if="currentFormData.id"
|
v-if="currentFormData.id"
|
||||||
outlined
|
outlined
|
||||||
icon="mdi-play-box-outline"
|
icon="mdi-play-box-outline"
|
||||||
|
|
@ -1257,10 +1269,16 @@ watch(
|
||||||
{{ $t('general.view', { msg: $t('general.example') }) }}
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
<div class="row" style="gap: var(--size-2)">
|
<div class="row" style="gap: var(--size-2)">
|
||||||
<UndoButton outlined @click="undo()" v-if="state.mode === 'edit'" />
|
<UndoButton
|
||||||
|
id="btn-undo"
|
||||||
|
outlined
|
||||||
|
@click="undo()"
|
||||||
|
v-if="state.mode === 'edit'"
|
||||||
|
/>
|
||||||
<CancelButton
|
<CancelButton
|
||||||
v-if="state.mode !== 'edit'"
|
v-if="state.mode !== 'edit'"
|
||||||
:label="$t('dialog.action.close')"
|
:label="$t('dialog.action.close')"
|
||||||
|
id="btn-close"
|
||||||
outlined
|
outlined
|
||||||
@click="closeTab()"
|
@click="closeTab()"
|
||||||
/>
|
/>
|
||||||
|
|
@ -1272,12 +1290,14 @@ watch(
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
|
id="btn-save"
|
||||||
v-if="state.mode && ['create', 'edit'].includes(state.mode)"
|
v-if="state.mode && ['create', 'edit'].includes(state.mode)"
|
||||||
@click="() => formDocument.submit()"
|
@click="() => formDocument.submit()"
|
||||||
solid
|
solid
|
||||||
/>
|
/>
|
||||||
<EditButton
|
<EditButton
|
||||||
v-else
|
v-else
|
||||||
|
id="btn-edit"
|
||||||
class="no-print"
|
class="no-print"
|
||||||
@click="state.mode = 'edit'"
|
@click="state.mode = 'edit'"
|
||||||
solid
|
solid
|
||||||
|
|
@ -1285,6 +1305,7 @@ watch(
|
||||||
</template>
|
</template>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="
|
v-if="
|
||||||
|
canAccess('taskOrder', 'edit') &&
|
||||||
state.mode !== 'create' &&
|
state.mode !== 'create' &&
|
||||||
view === TaskOrderStatus.Validate &&
|
view === TaskOrderStatus.Validate &&
|
||||||
fullTaskOrder?.taskOrderStatus !== TaskOrderStatus.Pending &&
|
fullTaskOrder?.taskOrderStatus !== TaskOrderStatus.Pending &&
|
||||||
|
|
@ -1323,6 +1344,7 @@ watch(
|
||||||
"
|
"
|
||||||
:label="$t('taskOrder.confirmEndWork')"
|
:label="$t('taskOrder.confirmEndWork')"
|
||||||
icon="mdi-check"
|
icon="mdi-check"
|
||||||
|
id="btn-confirm-end-work"
|
||||||
solid
|
solid
|
||||||
></SaveButton>
|
></SaveButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1332,6 +1354,7 @@ watch(
|
||||||
|
|
||||||
<!-- SEC: Dialog -->
|
<!-- SEC: Dialog -->
|
||||||
<SelectReadyRequestWork
|
<SelectReadyRequestWork
|
||||||
|
id="dialog-select-request-work"
|
||||||
:task-list-group="taskListGroup"
|
:task-list-group="taskListGroup"
|
||||||
:fetch-params="{ readyToTask: true }"
|
:fetch-params="{ readyToTask: true }"
|
||||||
v-model:open="pageState.productDialog"
|
v-model:open="pageState.productDialog"
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ function tooltip(id: string) {
|
||||||
<main class="q-py-sm q-px-md scroll">
|
<main class="q-py-sm q-px-md scroll">
|
||||||
<q-select
|
<q-select
|
||||||
:readonly
|
:readonly
|
||||||
|
id="select-fail-task"
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
multiple
|
multiple
|
||||||
|
|
@ -131,6 +132,7 @@ function tooltip(id: string) {
|
||||||
>
|
>
|
||||||
<template #selected-item="scope">
|
<template #selected-item="scope">
|
||||||
<q-chip
|
<q-chip
|
||||||
|
:id="`chip-fail-task-${taskStatusList[scope.index].code}`"
|
||||||
dense
|
dense
|
||||||
:removable="!readonly"
|
:removable="!readonly"
|
||||||
@remove="scope.removeAtIndex(scope.index)"
|
@remove="scope.removeAtIndex(scope.index)"
|
||||||
|
|
@ -170,6 +172,7 @@ function tooltip(id: string) {
|
||||||
|
|
||||||
<template #option="scope">
|
<template #option="scope">
|
||||||
<q-item
|
<q-item
|
||||||
|
:id="`option-fail-task-${scope.opt.request.code}`"
|
||||||
clickable
|
clickable
|
||||||
v-bind="scope.itemProps"
|
v-bind="scope.itemProps"
|
||||||
class="row items-start col-12 no-padding"
|
class="row items-start col-12 no-padding"
|
||||||
|
|
@ -230,6 +233,7 @@ function tooltip(id: string) {
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
:readonly
|
:readonly
|
||||||
|
id="select-fail-type"
|
||||||
:option="[
|
:option="[
|
||||||
{
|
{
|
||||||
label: $t('taskOrder.documentSubmitFailed'),
|
label: $t('taskOrder.documentSubmitFailed'),
|
||||||
|
|
@ -260,6 +264,7 @@ function tooltip(id: string) {
|
||||||
|
|
||||||
<div v-if="failedType === 'other'" class="q-mt-sm rounded">
|
<div v-if="failedType === 'other'" class="q-mt-sm rounded">
|
||||||
<q-editor
|
<q-editor
|
||||||
|
id="editor-fail-comment"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
v-model="failedComment"
|
v-model="failedComment"
|
||||||
|
|
@ -279,8 +284,18 @@ function tooltip(id: string) {
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<template #footer v-if="!readonly">
|
<template #footer v-if="!readonly">
|
||||||
<CancelButton class="q-ml-auto" outlined @click="$emit('close')" />
|
<CancelButton
|
||||||
<SaveButton class="q-ml-sm" solid @click="submit" />
|
id="btn-fail-remark-cancel"
|
||||||
|
class="q-ml-auto"
|
||||||
|
outlined
|
||||||
|
@click="$emit('close')"
|
||||||
|
/>
|
||||||
|
<SaveButton
|
||||||
|
id="btn-fail-remark-save"
|
||||||
|
class="q-ml-sm"
|
||||||
|
solid
|
||||||
|
@click="submit"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</DialogFormContainer>
|
</DialogFormContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -394,8 +394,14 @@ watch(
|
||||||
{
|
{
|
||||||
label: $t('general.customer'),
|
label: $t('general.customer'),
|
||||||
value:
|
value:
|
||||||
item.row.quotation.customerBranch.registerName ||
|
item.row.quotation.customerBranch.customer
|
||||||
`${item.row.quotation.customerBranch?.firstName || '-'} ${item.row.quotation.customerBranch?.lastName || ''}`,
|
.customerType === 'CORP'
|
||||||
|
? $i18n.locale === 'tha'
|
||||||
|
? item.row.quotation.customerBranch.registerName
|
||||||
|
: item.row.quotation.customerBranch.registerNameEN
|
||||||
|
: $i18n.locale === 'tha'
|
||||||
|
? `${item.row.quotation.customerBranch?.firstName || '-'} ${item.row.quotation.customerBranch?.lastName || ''}`
|
||||||
|
: `${item.row.quotation.customerBranch?.firstNameEN || '-'} ${item.row.quotation.customerBranch?.lastNameEN || ''}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('taskOrder.issueDate'),
|
label: $t('taskOrder.issueDate'),
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,16 @@ defineEmits<{
|
||||||
<!-- NOTE: custom column will starts with # -->
|
<!-- NOTE: custom column will starts with # -->
|
||||||
<template v-if="!col.name.startsWith('#')">
|
<template v-if="!col.name.startsWith('#')">
|
||||||
<span>
|
<span>
|
||||||
|
<template v-if="typeof col.field === 'function'">
|
||||||
{{
|
{{
|
||||||
typeof col.field === 'string'
|
typeof col.field(props.row) === 'object'
|
||||||
? props.row[col.field as keyof Invoice]
|
? col.field(props.row)[$i18n.locale]
|
||||||
: col.field(props.row)
|
: col.field(props.row)
|
||||||
}}
|
}}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ props.row[col.field as keyof Invoice] }}
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="col.name === '#order'">
|
<template v-if="col.name === '#order'">
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,16 @@ export const columns = [
|
||||||
name: 'customer',
|
name: 'customer',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
label: 'general.customer',
|
label: 'general.customer',
|
||||||
field: (v: Invoice) => v.quotation.customerBranch.registerName,
|
field: (v: Invoice) =>
|
||||||
|
v.quotation.customerBranch.customer.customerType === 'CORP'
|
||||||
|
? {
|
||||||
|
tha: v.quotation.customerBranch.registerName,
|
||||||
|
eng: v.quotation.customerBranch.registerNameEN,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
tha: `${v.quotation.customerBranch.firstName || ''} ${v.quotation.customerBranch.lastName || ''}`,
|
||||||
|
eng: `${v.quotation.customerBranch.firstNameEN || ''} ${v.quotation.customerBranch.lastNameEN || ''}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import {
|
||||||
EditButton,
|
EditButton,
|
||||||
UndoButton,
|
UndoButton,
|
||||||
} from 'src/components/button';
|
} from 'src/components/button';
|
||||||
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import { DebitNote, useDebitNote } from 'src/stores/debit-note';
|
import { DebitNote, useDebitNote } from 'src/stores/debit-note';
|
||||||
import { RequestWork } from 'src/stores/request-list/types';
|
import { RequestWork } from 'src/stores/request-list/types';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
@ -40,6 +41,8 @@ import { useI18n } from 'vue-i18n';
|
||||||
import { QForm } from 'quasar';
|
import { QForm } from 'quasar';
|
||||||
import { getName } from 'src/services/keycloak';
|
import { getName } from 'src/services/keycloak';
|
||||||
|
|
||||||
|
import { RequestWorkStatus } from 'src/stores/request-list/types';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const creditNote = useCreditNote();
|
const creditNote = useCreditNote();
|
||||||
|
|
@ -49,6 +52,7 @@ const configStore = useConfigStore();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const agentPrice = ref<boolean>(false);
|
||||||
const refForm = ref<InstanceType<typeof QForm>>();
|
const refForm = ref<InstanceType<typeof QForm>>();
|
||||||
const creditNoteData = ref<CreditNote>();
|
const creditNoteData = ref<CreditNote>();
|
||||||
const quotationData = ref<DebitNote | QuotationFull>();
|
const quotationData = ref<DebitNote | QuotationFull>();
|
||||||
|
|
@ -206,22 +210,23 @@ function getPrice(
|
||||||
) {
|
) {
|
||||||
return list.reduce(
|
return list.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const pricePerUnit =
|
const value = creditNote.getfinalPriceCredit(c.list || []);
|
||||||
c.product.pricePerUnit - c.product.discount / c.product.amount;
|
|
||||||
const amount = c.list.length;
|
|
||||||
const priceNoVat = pricePerUnit;
|
|
||||||
const priceDiscountNoVat = priceNoVat * amount;
|
|
||||||
|
|
||||||
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07);
|
a.totalPrice = precisionRound(a.totalPrice + value.totalPrice);
|
||||||
|
a.totalDiscount = precisionRound(a.totalDiscount + value.totalDiscount);
|
||||||
|
a.vat = precisionRound(a.vat + value.vat);
|
||||||
|
a.vatIncluded = precisionRound(a.vatIncluded + value.vatIncluded);
|
||||||
|
a.vatExcluded = precisionRound(a.vatExcluded + value.vatExcluded);
|
||||||
|
a.finalPrice = precisionRound(a.finalPrice + value.finalPrice);
|
||||||
|
|
||||||
a.totalPrice = a.totalPrice + priceDiscountNoVat;
|
|
||||||
a.vat = c.product.vat !== 0 ? a.vat + rawVatTotal : a.vat;
|
|
||||||
a.finalPrice = a.totalPrice + a.vat;
|
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
totalPrice: 0,
|
totalPrice: 0,
|
||||||
|
totalDiscount: 0,
|
||||||
vat: 0,
|
vat: 0,
|
||||||
|
vatIncluded: 0,
|
||||||
|
vatExcluded: 0,
|
||||||
finalPrice: 0,
|
finalPrice: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -296,6 +301,8 @@ async function getQuotation() {
|
||||||
if (!ret) return;
|
if (!ret) return;
|
||||||
|
|
||||||
quotationData.value = ret;
|
quotationData.value = ret;
|
||||||
|
|
||||||
|
agentPrice.value = quotationData.value.agentPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@ onMounted(async () => {
|
||||||
|
|
||||||
creditNote.getCreditNoteStats().then((res) => res && (stats.value = res));
|
creditNote.getCreditNoteStats().then((res) => res && (stats.value = res));
|
||||||
getList();
|
getList();
|
||||||
|
|
||||||
|
window.addEventListener('focus', () => {
|
||||||
|
creditNote.getCreditNoteStats().then((res) => res && (stats.value = res));
|
||||||
|
getList();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -406,10 +411,12 @@ watch(
|
||||||
value:
|
value:
|
||||||
item.row.quotation.customerBranch.customer
|
item.row.quotation.customerBranch.customer
|
||||||
.customerType === 'CORP'
|
.customerType === 'CORP'
|
||||||
|
? $i18n.locale === 'tha'
|
||||||
? item.row.quotation.customerBranch.registerName
|
? item.row.quotation.customerBranch.registerName
|
||||||
|
: item.row.quotation.customerBranch.registerNameEN
|
||||||
: $i18n.locale === 'tha'
|
: $i18n.locale === 'tha'
|
||||||
? `${item.row.quotation.customerBranch.firstName} ${item.row.quotation.customerBranch.lastName}`
|
? `${item.row.quotation.customerBranch.firstName || ''} ${item.row.quotation.customerBranch.lastName || ''}`
|
||||||
: `${item.row.quotation.customerBranch.firstNameEN} ${item.row.quotation.customerBranch.lastNameEN}`,
|
: `${item.row.quotation.customerBranch.firstNameEN || ''} ${item.row.quotation.customerBranch.lastNameEN || ''}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('requestList.quotationCode'),
|
label: $t('requestList.quotationCode'),
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { columns } from './constants';
|
||||||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||||
|
|
||||||
const creditNote = useCreditNote();
|
const creditNote = useCreditNote();
|
||||||
const { data, page } = storeToRefs(creditNote);
|
const { data, page, pageSize } = storeToRefs(creditNote);
|
||||||
|
|
||||||
const prop = defineProps<{
|
const prop = defineProps<{
|
||||||
grid: boolean;
|
grid: boolean;
|
||||||
|
|
@ -26,7 +26,14 @@ const visible = computed(() =>
|
||||||
<template>
|
<template>
|
||||||
<q-table
|
<q-table
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
:rows="data.map((item, i) => ({ ...item, _index: i, _page: page }))"
|
:rows="
|
||||||
|
data.map((item, i) => ({
|
||||||
|
...item,
|
||||||
|
_index: i,
|
||||||
|
_page: page,
|
||||||
|
_pageSize: pageSize,
|
||||||
|
}))
|
||||||
|
"
|
||||||
:columns="visible"
|
:columns="visible"
|
||||||
:grid
|
:grid
|
||||||
hide-bottom
|
hide-bottom
|
||||||
|
|
@ -52,7 +59,7 @@ const visible = computed(() =>
|
||||||
|
|
||||||
<template
|
<template
|
||||||
v-slot:body="props: {
|
v-slot:body="props: {
|
||||||
row: CreditNote & { _index: number; _page: number };
|
row: CreditNote & { _index: number; _page: number; _pageSize: number };
|
||||||
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||||
>
|
>
|
||||||
<q-tr :class="{ dark: $q.dark.isActive }" class="text-center">
|
<q-tr :class="{ dark: $q.dark.isActive }" class="text-center">
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@ export const columns = [
|
||||||
name: 'order',
|
name: 'order',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
label: 'general.order',
|
label: 'general.order',
|
||||||
field: (data: CreditNote & { _index: number; _page: number }) =>
|
field: (
|
||||||
data._page * (data._index + 1),
|
data: CreditNote & { _index: number; _page: number; _pageSize: number },
|
||||||
|
) => (data._page - 1) * data._pageSize + (data._index + 1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'code',
|
name: 'code',
|
||||||
|
|
|
||||||
|
|
@ -237,30 +237,12 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function calcPricePerUnit(product: RequestWork['productService']['product']) {
|
function calcPricePerUnit(product: RequestWork['productService']['product']) {
|
||||||
return product.vatIncluded
|
return agentPrice.value ? product.agentPrice : product.price;
|
||||||
? (agentPrice.value ? product.agentPrice : product.price) /
|
|
||||||
(1 + (config.value?.vat || 0.07))
|
|
||||||
: agentPrice.value
|
|
||||||
? product.agentPrice
|
|
||||||
: product.price;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcPrice(
|
function calcPrice(c: RequestWork[]): number {
|
||||||
product: RequestWork['productService']['product'],
|
const price = creditNoteStore.getfinalPriceCredit(c);
|
||||||
amount: number,
|
return price.finalPrice;
|
||||||
vat: number = 0,
|
|
||||||
) {
|
|
||||||
const pricePerUnit = agentPrice.value ? product.agentPrice : product.price;
|
|
||||||
|
|
||||||
const priceNoVat = product.vatIncluded
|
|
||||||
? pricePerUnit / (1 + (config.value?.vat || 0.07))
|
|
||||||
: pricePerUnit;
|
|
||||||
const priceDiscountNoVat = priceNoVat * amount - 0;
|
|
||||||
|
|
||||||
const rawVatTotal =
|
|
||||||
vat === 0 ? 0 : priceDiscountNoVat * (config.value?.vat || 0.07);
|
|
||||||
|
|
||||||
return precisionRound(priceNoVat * amount + rawVatTotal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(elements, () => {});
|
watch(elements, () => {});
|
||||||
|
|
@ -327,31 +309,15 @@ function closeAble() {
|
||||||
<th>{{ $t('preview.pricePerUnit') }}</th>
|
<th>{{ $t('preview.pricePerUnit') }}</th>
|
||||||
<th>{{ $t('preview.value') }}</th>
|
<th>{{ $t('preview.value') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{ console.log(chunks) }}
|
|
||||||
{{ console.log(chunk) }}
|
|
||||||
<tr v-for="(v, i) in chunk" :key="i">
|
<tr v-for="(v, i) in chunk" :key="i">
|
||||||
<td class="text-center">{{ i + 1 }}</td>
|
<td class="text-center">{{ i + 1 }}</td>
|
||||||
<td>{{ v.product.product.code }}</td>
|
<td>{{ v.product.product.code }}</td>
|
||||||
<td>{{ v.product.product.name }}</td>
|
<td>{{ v.product.product.name }}</td>
|
||||||
<td style="text-align: center">
|
<td style="text-align: right">
|
||||||
{{
|
{{ formatNumberDecimal(calcPricePerUnit(v.product.product), 2) }}
|
||||||
formatNumberDecimal(
|
|
||||||
calcPricePerUnit(v.product.product) +
|
|
||||||
(v.product.product.calcVat
|
|
||||||
? calcPricePerUnit(v.product.product) *
|
|
||||||
(config?.vat || 0.07)
|
|
||||||
: 0),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: center">
|
<td style="text-align: right">
|
||||||
{{
|
{{ formatNumberDecimal(calcPrice(v.list), 2) }}
|
||||||
formatNumberDecimal(
|
|
||||||
calcPrice(v.product.product, v.list.length, v.product.vat),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -409,8 +375,8 @@ function closeAble() {
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
summaryPrice.totalPrice -
|
summaryPrice.totalPrice -
|
||||||
summaryPrice.totalDiscount +
|
summaryPrice.totalDiscount -
|
||||||
summaryPrice.vat,
|
summaryPrice.vatExcluded,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ function titleMode(mode: View): string {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>เลขประจำตัวผู้เสียภาษี {{ branch.taxNo }}</span>
|
<span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
|
||||||
<span>เบอร์โทร {{ branch.telephoneNo }}</span>
|
<span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
|
||||||
<span>{{ branch.webUrl }}</span>
|
<span>{{ branch.webUrl }}</span>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
|
|
@ -105,8 +105,18 @@ function titleMode(mode: View): string {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>เลขประจำตัวผู้เสียภาษี {{ customer.citizenId }}</span>
|
<span>
|
||||||
<span>เบอร์โทร {{ customer.telephoneNo }}</span>
|
{{
|
||||||
|
customer.customer.customerType === 'CORP'
|
||||||
|
? `${$t('customer.form.legalPersonNo')} `
|
||||||
|
: `${$t('customer.form.taxpayyerNo')} `
|
||||||
|
}}{{
|
||||||
|
customer.customer.customerType === 'CORP'
|
||||||
|
? customer.codeCustomer
|
||||||
|
: customer.citizenId
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span>{{ $t('taskOrder.telephone') }} {{ customer.telephoneNo }}</span>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
<section class="detail-quotation-info">
|
<section class="detail-quotation-info">
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,13 @@ import { baseUrl, formatNumberDecimal } from 'src/stores/utils';
|
||||||
import { precisionRound } from 'src/utils/arithmetic';
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import { productColumn } from '../constants';
|
import { productColumn } from '../constants';
|
||||||
|
|
||||||
|
import { useCreditNote } from 'src/stores/credit-note';
|
||||||
|
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const { data: config } = storeToRefs(configStore);
|
const { data: config } = storeToRefs(configStore);
|
||||||
|
|
||||||
const currentExpanded = ref<boolean[]>([]);
|
const currentExpanded = ref<boolean[]>([]);
|
||||||
|
const creditNote = useCreditNote();
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
|
@ -44,15 +47,22 @@ function calcPricePerUnit(product: RequestWork['productService']) {
|
||||||
return product.pricePerUnit - product.discount / product.amount;
|
return product.pricePerUnit - product.discount / product.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcPrice(c: RequestWork['productService'], amount: number) {
|
function calcVat(c: RequestWork['productService']) {
|
||||||
const pricePerUnit = c.pricePerUnit - c.discount / c.amount;
|
const vatFactor = c.product.serviceChargeCalcVat
|
||||||
const priceNoVat = pricePerUnit;
|
? (config.value?.vat ?? 0.07)
|
||||||
const priceDiscountNoVat = priceNoVat * amount;
|
: 0;
|
||||||
|
|
||||||
const rawVatTotal =
|
const price = precisionRound(
|
||||||
c.vat === 0 ? 0 : priceDiscountNoVat * (config.value?.vat || 0.07);
|
c.pricePerUnit * (1 + vatFactor) - c.discount / (1 + vatFactor),
|
||||||
|
);
|
||||||
|
|
||||||
return precisionRound(priceNoVat * amount + rawVatTotal);
|
const vat = (price / (1 + vatFactor)) * vatFactor;
|
||||||
|
return vat;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPrice(c: RequestWork[]) {
|
||||||
|
const price = creditNote.getfinalPriceCredit(c);
|
||||||
|
return price.finalPrice;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -162,12 +172,7 @@ function calcPrice(c: RequestWork['productService'], amount: number) {
|
||||||
|
|
||||||
<!-- total -->
|
<!-- total -->
|
||||||
<q-td class="text-right">
|
<q-td class="text-right">
|
||||||
{{
|
{{ formatNumberDecimal(calcPrice(props.row.list), 2) }}
|
||||||
formatNumberDecimal(
|
|
||||||
calcPrice(props.row.product, props.row.list.length),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td>
|
<q-td>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
|
||||||
|
|
@ -209,47 +209,7 @@ const selectedWorker = ref<
|
||||||
}[];
|
}[];
|
||||||
})[]
|
})[]
|
||||||
>([]);
|
>([]);
|
||||||
const selectedWorkerItem = computed(() => {
|
const selectedWorkerItem = ref([]);
|
||||||
return [
|
|
||||||
...selectedWorker.value.map((e) => ({
|
|
||||||
foreignRefNo: e.employeePassport
|
|
||||||
? e.employeePassport[0]?.number || '-'
|
|
||||||
: '-',
|
|
||||||
employeeName:
|
|
||||||
locale.value === Lang.English
|
|
||||||
? `${e.firstNameEN} ${e.lastNameEN}`
|
|
||||||
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
|
|
||||||
birthDate: dateFormatJS({ date: e.dateOfBirth }),
|
|
||||||
gender: e.gender,
|
|
||||||
age: calculateAge(e.dateOfBirth),
|
|
||||||
nationality: optionStore.mapOption(e.nationality),
|
|
||||||
documentExpireDate:
|
|
||||||
e.employeePassport !== undefined &&
|
|
||||||
e.employeePassport[0]?.expireDate !== undefined
|
|
||||||
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
|
|
||||||
: '-',
|
|
||||||
imgUrl: e.selectedImage
|
|
||||||
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
|
|
||||||
: '',
|
|
||||||
status: e.status,
|
|
||||||
})),
|
|
||||||
|
|
||||||
...newWorkerList.value.map((v: any) => ({
|
|
||||||
foreignRefNo: v.passportNo || '-',
|
|
||||||
employeeName:
|
|
||||||
locale.value === Lang.English
|
|
||||||
? `${v.firstNameEN} ${v.lastNameEN}`
|
|
||||||
: `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
|
|
||||||
birthDate: dateFormatJS({ date: v.dateOfBirth }),
|
|
||||||
gender: v.gender,
|
|
||||||
age: calculateAge(v.dateOfBirth),
|
|
||||||
nationality: optionStore.mapOption(v.nationality),
|
|
||||||
documentExpireDate: '-',
|
|
||||||
imgUrl: '',
|
|
||||||
status: 'CREATED',
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedInstallmentNo = ref<number[]>([]);
|
const selectedInstallmentNo = ref<number[]>([]);
|
||||||
const installmentAmount = ref<number>(0);
|
const installmentAmount = ref<number>(0);
|
||||||
|
|
@ -374,7 +334,39 @@ function assignProductServiceList() {
|
||||||
|
|
||||||
function assignSelectedWorker() {
|
function assignSelectedWorker() {
|
||||||
if (debitNoteData.value)
|
if (debitNoteData.value)
|
||||||
selectedWorker.value = debitNoteData.value.worker.map((v) => v.employee);
|
selectedWorkerItem.value = debitNoteData.value.worker.map((e) => {
|
||||||
|
return {
|
||||||
|
id: e.employee.id,
|
||||||
|
foreignRefNo: e.employee.employeePassport
|
||||||
|
? e.employee.employeePassport[0]?.number || '-'
|
||||||
|
: '-',
|
||||||
|
employeeName:
|
||||||
|
locale.value === Lang.English
|
||||||
|
? `${e.employee.firstNameEN} ${e.employee.lastNameEN}`
|
||||||
|
: `${e.employee.firstName || e.employee.firstNameEN} ${e.employee.lastName || e.employee.lastNameEN}`,
|
||||||
|
birthDate: dateFormatJS({ date: e.employee.dateOfBirth }),
|
||||||
|
gender: e.employee.gender,
|
||||||
|
age: calculateAge(e.employee.dateOfBirth),
|
||||||
|
nationality: optionStore.mapOption(e.employee.nationality),
|
||||||
|
documentExpireDate:
|
||||||
|
e.employee.employeePassport !== undefined &&
|
||||||
|
e.employee.employeePassport[0]?.expireDate !== undefined
|
||||||
|
? dateFormatJS({ date: e.employee.employeePassport[0]?.expireDate })
|
||||||
|
: '-',
|
||||||
|
imgUrl: e.employee.selectedImage
|
||||||
|
? `${API_BASE_URL}/employee/${e.id}/image/${e.employee.selectedImage}`
|
||||||
|
: '',
|
||||||
|
status: e.employee.status,
|
||||||
|
workerNew: false,
|
||||||
|
lastNameEN: e.employee.lastNameEN,
|
||||||
|
lastName: e.employee.lastName,
|
||||||
|
middleNameEN: e.employee.middleNameEN,
|
||||||
|
middleName: e.employee.middleName,
|
||||||
|
firstNameEN: e.employee.firstNameEN,
|
||||||
|
firstName: e.employee.firstName,
|
||||||
|
namePrefix: e.employee.namePrefix,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assignFormData(id: string) {
|
async function assignFormData(id: string) {
|
||||||
|
|
@ -434,6 +426,7 @@ async function getQuotation(id?: string) {
|
||||||
const data = await quotationStore.getQuotation(quotationId);
|
const data = await quotationStore.getQuotation(quotationId);
|
||||||
if (!!data) {
|
if (!!data) {
|
||||||
quotationData.value = data;
|
quotationData.value = data;
|
||||||
|
agentPrice.value = quotationData.value.agentPrice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -549,25 +542,28 @@ function getPrice(
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
const price = precisionRound(c.pricePerUnit * c.amount);
|
const calcVat =
|
||||||
const vat =
|
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
||||||
precisionRound(
|
|
||||||
(c.pricePerUnit * (c.discount ? c.amount : 1) - c.discount) *
|
|
||||||
(config.value?.vat || 0.07),
|
|
||||||
) * (!c.discount ? c.amount : 1);
|
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price);
|
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
|
|
||||||
a.vat = c.product.calcVat ? precisionRound(a.vat + vat) : a.vat;
|
const pricePerUnit = precisionRound(
|
||||||
|
(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor),
|
||||||
|
);
|
||||||
|
|
||||||
|
const price = precisionRound(
|
||||||
|
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
|
||||||
|
(1 + vatFactor),
|
||||||
|
);
|
||||||
|
const vat = price * vatFactor;
|
||||||
|
|
||||||
|
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
|
||||||
|
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
||||||
|
a.vat = precisionRound(a.vat + vat);
|
||||||
a.vatExcluded = c.product.calcVat
|
a.vatExcluded = c.product.calcVat
|
||||||
? a.vatExcluded
|
? a.vatExcluded
|
||||||
: precisionRound(a.vat + vat);
|
: precisionRound(a.vatExcluded + price);
|
||||||
a.finalPrice = precisionRound(
|
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
|
||||||
a.totalPrice -
|
|
||||||
a.totalDiscount +
|
|
||||||
a.vat -
|
|
||||||
Number(currentFormData.value.discount || 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
|
|
@ -575,6 +571,7 @@ function getPrice(
|
||||||
totalPrice: 0,
|
totalPrice: 0,
|
||||||
totalDiscount: 0,
|
totalDiscount: 0,
|
||||||
vat: 0,
|
vat: 0,
|
||||||
|
vatIncluded: 0,
|
||||||
vatExcluded: 0,
|
vatExcluded: 0,
|
||||||
finalPrice: 0,
|
finalPrice: 0,
|
||||||
},
|
},
|
||||||
|
|
@ -808,15 +805,11 @@ async function submit() {
|
||||||
|
|
||||||
worker: JSON.parse(
|
worker: JSON.parse(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
...selectedWorker.value.map((v) => {
|
...selectedWorkerItem.value.map((v) => {
|
||||||
{
|
{
|
||||||
return v.id;
|
return v.id;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
...newWorkerList.value.map((v) => {
|
|
||||||
const { attachment, ...payload } = v;
|
|
||||||
return pageState.mode === 'edit' ? payload.id : payload;
|
|
||||||
}),
|
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
dueDate: currentFormData.value.dueDate,
|
dueDate: currentFormData.value.dueDate,
|
||||||
|
|
@ -869,6 +862,7 @@ async function exampleReceipt(id: string) {
|
||||||
|
|
||||||
function storeDataLocal() {
|
function storeDataLocal() {
|
||||||
// quotationFormData.value.productServiceList = productServiceList.value;
|
// quotationFormData.value.productServiceList = productServiceList.value;
|
||||||
|
//
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'debit-note-preview',
|
'debit-note-preview',
|
||||||
|
|
@ -877,6 +871,7 @@ function storeDataLocal() {
|
||||||
...currentFormData.value,
|
...currentFormData.value,
|
||||||
customerBranchId: quotationData.value?.customerBranchId,
|
customerBranchId: quotationData.value?.customerBranchId,
|
||||||
registeredBranchId: quotationData.value?.registeredBranchId,
|
registeredBranchId: quotationData.value?.registeredBranchId,
|
||||||
|
agentPrice: quotationData.value.agentPrice,
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
source: {
|
source: {
|
||||||
|
|
@ -893,7 +888,7 @@ function storeDataLocal() {
|
||||||
dueDate: currentFormData.value.dueDate,
|
dueDate: currentFormData.value.dueDate,
|
||||||
},
|
},
|
||||||
productServicelist: productService.value,
|
productServicelist: productService.value,
|
||||||
selectedWorker: selectedWorker.value,
|
selectedWorker: selectedWorkerItem.value,
|
||||||
createdBy: getName(),
|
createdBy: getName(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -918,6 +913,69 @@ function closeAble() {
|
||||||
return window.opener !== null;
|
return window.opener !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function combineWorker(newWorker: any, oldWorker: any) {
|
||||||
|
selectedWorkerItem.value = [
|
||||||
|
...oldWorker.map((e) => ({
|
||||||
|
id: e.id,
|
||||||
|
foreignRefNo: e.employeePassport
|
||||||
|
? e.employeePassport[0]?.number || '-'
|
||||||
|
: '-',
|
||||||
|
employeeName:
|
||||||
|
locale.value === Lang.English
|
||||||
|
? `${e.firstNameEN} ${e.lastNameEN}`
|
||||||
|
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
|
||||||
|
birthDate: dateFormatJS({ date: e.dateOfBirth }),
|
||||||
|
gender: e.gender,
|
||||||
|
age: calculateAge(e.dateOfBirth),
|
||||||
|
nationality: optionStore.mapOption(e.nationality),
|
||||||
|
documentExpireDate:
|
||||||
|
e.employeePassport !== undefined &&
|
||||||
|
e.employeePassport[0]?.expireDate !== undefined
|
||||||
|
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
|
||||||
|
: '-',
|
||||||
|
imgUrl: e.selectedImage
|
||||||
|
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
|
||||||
|
: '',
|
||||||
|
status: e.status,
|
||||||
|
workerNew: false,
|
||||||
|
lastNameEN: e.lastNameEN,
|
||||||
|
lastName: e.lastName,
|
||||||
|
middleNameEN: e.middleNameEN,
|
||||||
|
middleName: e.middleName,
|
||||||
|
firstNameEN: e.firstNameEN,
|
||||||
|
firstName: e.firstName,
|
||||||
|
namePrefix: e.namePrefix,
|
||||||
|
})),
|
||||||
|
|
||||||
|
...newWorker.map((v: any) => ({
|
||||||
|
id: v.id,
|
||||||
|
foreignRefNo: v.passportNo || '-',
|
||||||
|
employeeName:
|
||||||
|
locale.value === Lang.English
|
||||||
|
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||||
|
: `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
|
||||||
|
birthDate: dateFormatJS({ date: v.dateOfBirth }),
|
||||||
|
gender: v.gender,
|
||||||
|
age: calculateAge(v.dateOfBirth),
|
||||||
|
nationality: optionStore.mapOption(v.nationality),
|
||||||
|
documentExpireDate: '-',
|
||||||
|
imgUrl: '',
|
||||||
|
status: 'CREATED',
|
||||||
|
|
||||||
|
lastNameEN: v.lastNameEN,
|
||||||
|
lastName: v.lastName,
|
||||||
|
middleNameEN: v.middleNameEN,
|
||||||
|
middleName: v.middleName,
|
||||||
|
firstNameEN: v.firstNameEN,
|
||||||
|
firstName: v.firstName,
|
||||||
|
namePrefix: v.namePrefix,
|
||||||
|
|
||||||
|
dateOfBirth: v.dateOfBirth,
|
||||||
|
workerNew: true,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => pageState.mode,
|
() => pageState.mode,
|
||||||
() => toggleMode(pageState.mode),
|
() => toggleMode(pageState.mode),
|
||||||
|
|
@ -1081,6 +1139,7 @@ async function submitAccepted() {
|
||||||
<PaymentForm
|
<PaymentForm
|
||||||
v-if="view === QuotationStatus.PaymentPending"
|
v-if="view === QuotationStatus.PaymentPending"
|
||||||
is-debit-note
|
is-debit-note
|
||||||
|
:branch-id="quotationData?.registeredBranchId"
|
||||||
:readonly="isRoleInclude(['sale', 'head_of_sale'])"
|
:readonly="isRoleInclude(['sale', 'head_of_sale'])"
|
||||||
:data="debitNoteData"
|
:data="debitNoteData"
|
||||||
@fetch-status="
|
@fetch-status="
|
||||||
|
|
@ -1102,7 +1161,7 @@ async function submitAccepted() {
|
||||||
"
|
"
|
||||||
:row-worker="selectedWorkerItem"
|
:row-worker="selectedWorkerItem"
|
||||||
@add-worker="() => (pageState.employeeModal = true)"
|
@add-worker="() => (pageState.employeeModal = true)"
|
||||||
@delete="(i) => deleteItem(selectedWorker, i)"
|
@delete="(i) => deleteItem(selectedWorkerItem, i)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- #TODO add openProductDialog at @add-product-->
|
<!-- #TODO add openProductDialog at @add-product-->
|
||||||
|
|
@ -1322,7 +1381,7 @@ async function submitAccepted() {
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="pageState.mode !== 'info'"
|
v-if="pageState.mode !== 'info'"
|
||||||
:disabled="
|
:disabled="
|
||||||
selectedWorkerItem.length === 0 && productService.length === 0
|
selectedWorkerItem.length === 0 || productService.length === 0
|
||||||
"
|
"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
solid
|
solid
|
||||||
|
|
@ -1367,13 +1426,12 @@ async function submitAccepted() {
|
||||||
<!-- add employee quotation -->
|
<!-- add employee quotation -->
|
||||||
|
|
||||||
<QuotationFormWorkerSelect
|
<QuotationFormWorkerSelect
|
||||||
:preselect-worker="selectedWorker"
|
:preselect-worker="selectedWorkerItem"
|
||||||
:customerBranchId="quotationData?.customerBranchId"
|
:customerBranchId="quotationData?.customerBranchId"
|
||||||
v-model:open="pageState.employeeModal"
|
v-model:open="pageState.employeeModal"
|
||||||
v-model:new-worker-list="newWorkerList"
|
|
||||||
@success="
|
@success="
|
||||||
(v) => {
|
(v) => {
|
||||||
selectedWorker = v.worker;
|
combineWorker(v.newWorker, v.worker);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,15 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
getList();
|
getList();
|
||||||
|
|
||||||
|
window.addEventListener('focus', () => {
|
||||||
|
debitNote.getDebitNoteStats().then((res) => {
|
||||||
|
if (res) {
|
||||||
|
stats.value = res;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getList();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -411,7 +420,7 @@ watch(
|
||||||
@delete="() => triggerDelete(item.row.id)"
|
@delete="() => triggerDelete(item.row.id)"
|
||||||
:title="item.row.debitNoteQuotation?.workName"
|
:title="item.row.debitNoteQuotation?.workName"
|
||||||
:code="item.row.code"
|
:code="item.row.code"
|
||||||
:status="$t(`quotation.status.${item.row.quotationStatus}`)"
|
:status="$t(`debitNote.stats.${item.row.quotationStatus}`)"
|
||||||
:badge-color="hslaColors[item.row.quotationStatus] || ''"
|
:badge-color="hslaColors[item.row.quotationStatus] || ''"
|
||||||
:custom-data="[
|
:custom-data="[
|
||||||
{
|
{
|
||||||
|
|
@ -425,10 +434,12 @@ watch(
|
||||||
label: $t('quotation.customer'),
|
label: $t('quotation.customer'),
|
||||||
value:
|
value:
|
||||||
item.row.customerBranch.customer.customerType === 'CORP'
|
item.row.customerBranch.customer.customerType === 'CORP'
|
||||||
|
? $i18n.locale === 'tha'
|
||||||
? item.row.customerBranch.registerName
|
? item.row.customerBranch.registerName
|
||||||
|
: item.row.customerBranch.registerNameEN
|
||||||
: $i18n.locale === 'tha'
|
: $i18n.locale === 'tha'
|
||||||
? `${item.row.customerBranch.firstName} ${item.row.customerBranch.lastName}`
|
? `${item.row.customerBranch.firstName || ''} ${item.row.customerBranch.lastName || ''}`
|
||||||
: `${item.row.customerBranch.firstNameEN} ${item.row.customerBranch.lastNameEN}`,
|
: `${item.row.customerBranch.firstNameEN || ''} ${item.row.customerBranch.lastNameEN || ''}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('requestList.quotationCode'),
|
label: $t('requestList.quotationCode'),
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,8 @@ const data = ref<
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const productServiceList = ref<ProductServiceList[]>([]);
|
const productServiceList = ref<ProductServiceList[]>([]);
|
||||||
|
const selectedInstallmentNo = ref<number[]>([]);
|
||||||
|
const agentPrice = ref<boolean>(false);
|
||||||
|
|
||||||
const summaryPrice = ref<SummaryPrice>({
|
const summaryPrice = ref<SummaryPrice>({
|
||||||
totalPrice: 0,
|
totalPrice: 0,
|
||||||
|
|
@ -217,6 +219,8 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
productServiceList.value = parsed.meta.productServicelist;
|
productServiceList.value = parsed.meta.productServicelist;
|
||||||
|
selectedInstallmentNo.value = parsed.meta.selectedInstallmentNo;
|
||||||
|
agentPrice.value = parsed.meta.agentPrice;
|
||||||
|
|
||||||
productList.value =
|
productList.value =
|
||||||
productServiceList.value?.map((v) => ({
|
productServiceList.value?.map((v) => ({
|
||||||
|
|
@ -224,40 +228,56 @@ onMounted(async () => {
|
||||||
code: v.product.code,
|
code: v.product.code,
|
||||||
detail: v.product.name,
|
detail: v.product.name,
|
||||||
amount: v.amount || 0,
|
amount: v.amount || 0,
|
||||||
priceUnit: v.pricePerUnit || 0,
|
priceUnit:
|
||||||
|
v.pricePerUnit +
|
||||||
|
(v.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat']
|
||||||
|
? v.pricePerUnit * (config.value?.vat || 0.07)
|
||||||
|
: 0),
|
||||||
discount: v.discount || 0,
|
discount: v.discount || 0,
|
||||||
vat: v.vat || 0,
|
vat: v.vat || 0,
|
||||||
value: precisionRound(
|
value: precisionRound(
|
||||||
(v.pricePerUnit || 0) * v.amount -
|
(v.pricePerUnit +
|
||||||
(v.discount || 0) +
|
(v.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat']
|
||||||
(v.product.calcVat
|
? v.pricePerUnit * (config.value?.vat || 0.07)
|
||||||
? ((v.pricePerUnit || 0) * v.amount - (v.discount || 0)) *
|
: 0)) *
|
||||||
(config.value?.vat || 0.07)
|
v.amount -
|
||||||
: 0),
|
v.discount,
|
||||||
),
|
),
|
||||||
})) || [];
|
})) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
summaryPrice.value = (productServiceList.value || []).reduce(
|
summaryPrice.value = (productServiceList.value || []).reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
const price = precisionRound((c.pricePerUnit || 0) * c.amount);
|
if (
|
||||||
const vat = precisionRound(
|
selectedInstallmentNo.value?.length > 0 &&
|
||||||
((c.pricePerUnit || 0) * c.amount - (c.discount || 0)) *
|
c.installmentNo &&
|
||||||
(config.value?.vat || 0.07),
|
!selectedInstallmentNo.value?.includes(c.installmentNo)
|
||||||
|
) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcVat =
|
||||||
|
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
||||||
|
|
||||||
|
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||||
|
|
||||||
|
const pricePerUnit = precisionRound(
|
||||||
|
(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor),
|
||||||
);
|
);
|
||||||
|
|
||||||
a.totalPrice = precisionRound(a.totalPrice + price);
|
const price = precisionRound(
|
||||||
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
|
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
|
||||||
a.vat = c.product.calcVat ? precisionRound(a.vat + vat) : a.vat;
|
(1 + vatFactor),
|
||||||
|
);
|
||||||
|
const vat = price * vatFactor;
|
||||||
|
|
||||||
|
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
|
||||||
|
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
|
||||||
|
a.vat = precisionRound(a.vat + vat);
|
||||||
a.vatExcluded = c.product.calcVat
|
a.vatExcluded = c.product.calcVat
|
||||||
? a.vatExcluded
|
? a.vatExcluded
|
||||||
: precisionRound(a.vat + vat);
|
: precisionRound(a.vatExcluded + price);
|
||||||
a.finalPrice = precisionRound(
|
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
|
||||||
a.totalPrice -
|
|
||||||
a.totalDiscount +
|
|
||||||
a.vat -
|
|
||||||
Number(data.value?.discount || 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
|
|
@ -349,7 +369,9 @@ function print() {
|
||||||
{{ formatNumberDecimal(v.priceUnit, 2) }}
|
{{ formatNumberDecimal(v.priceUnit, 2) }}
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ formatNumberDecimal(v.discount, 2) }}
|
<template v-if="v.discount !== 0">
|
||||||
|
{{ formatNumberDecimal(v.discount, 2) }} ฿
|
||||||
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{{ formatNumberDecimal(v.vat, 2) }}
|
{{ formatNumberDecimal(v.vat, 2) }}
|
||||||
|
|
@ -413,8 +435,8 @@ function print() {
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
summaryPrice.totalPrice -
|
summaryPrice.totalPrice -
|
||||||
summaryPrice.totalDiscount +
|
summaryPrice.totalDiscount -
|
||||||
summaryPrice.vat,
|
summaryPrice.vatExcluded,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ function titleMode(mode: View): string {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>เลขประจำตัวผู้เสียภาษี {{ branch.taxNo }}</span>
|
<span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
|
||||||
<span>เบอร์โทร {{ branch.telephoneNo }}</span>
|
<span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
|
||||||
<span>{{ branch.webUrl }}</span>
|
<span>{{ branch.webUrl }}</span>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
|
|
@ -105,8 +105,18 @@ function titleMode(mode: View): string {
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span>เลขประจำตัวผู้เสียภาษี {{ customer.citizenId }}</span>
|
<span>
|
||||||
<span>เบอร์โทร {{ customer.telephoneNo }}</span>
|
{{
|
||||||
|
customer.customer.customerType === 'CORP'
|
||||||
|
? `${$t('customer.form.legalPersonNo')} `
|
||||||
|
: `${$t('customer.form.taxpayyerNo')} `
|
||||||
|
}}{{
|
||||||
|
customer.customer.customerType === 'CORP'
|
||||||
|
? customer.codeCustomer
|
||||||
|
: customer.citizenId
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span>{{ $t('taskOrder.telephone') }} {{ customer.telephoneNo }}</span>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
<section class="detail-quotation-info">
|
<section class="detail-quotation-info">
|
||||||
|
|
|
||||||
|
|
@ -343,9 +343,16 @@ watch(
|
||||||
{
|
{
|
||||||
label: $t('general.customer'),
|
label: $t('general.customer'),
|
||||||
value:
|
value:
|
||||||
item.row.invoice.quotation.customerBranch
|
item.row.invoice.quotation.customerBranch.customer
|
||||||
.registerName ||
|
.customerType === 'CORP'
|
||||||
`${item.row.invoice.quotation.customerBranch?.firstName || '-'} ${item.row.invoice.quotation.customerBranch?.lastName || ''}`,
|
? $i18n.locale === 'tha'
|
||||||
|
? item.row.invoice.quotation.customerBranch
|
||||||
|
.registerName
|
||||||
|
: item.row.invoice.quotation.customerBranch
|
||||||
|
.registerNameEN
|
||||||
|
: $i18n.locale === 'tha'
|
||||||
|
? `${item.row.invoice.quotation.customerBranch?.firstName || '-'} ${item.row.invoice.quotation.customerBranch?.lastName || ''}`
|
||||||
|
: `${item.row.invoice.quotation.customerBranch?.firstNameEN || '-'} ${item.row.invoice.quotation.customerBranch?.lastNameEN || ''}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('taskOrder.issueDate'),
|
label: $t('taskOrder.issueDate'),
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const fieldSelected = ref<('no' | 'name' | 'nameEN')[]>([
|
||||||
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
||||||
{
|
{
|
||||||
label: 'general.order',
|
label: 'general.order',
|
||||||
value: 'orderNumber',
|
value: 'no',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -312,25 +312,6 @@ watch(
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||||
<q-select
|
|
||||||
v-if="$q.screen.gt.sm"
|
|
||||||
v-model="statusFilter"
|
|
||||||
outlined
|
|
||||||
dense
|
|
||||||
option-value="value"
|
|
||||||
option-label="label"
|
|
||||||
class="col"
|
|
||||||
:class="{ 'offset-md-5': pageState.gridView }"
|
|
||||||
map-options
|
|
||||||
emit-value
|
|
||||||
:for="'field-select-status'"
|
|
||||||
:hide-dropdown-icon="$q.screen.lt.sm"
|
|
||||||
:options="[
|
|
||||||
{ label: $t('general.all'), value: 'all' },
|
|
||||||
{ label: $t('general.active'), value: 'statusACTIVE' },
|
|
||||||
{ label: $t('general.inactive'), value: 'statusINACTIVE' },
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<q-select
|
<q-select
|
||||||
v-if="!pageState.gridView"
|
v-if="!pageState.gridView"
|
||||||
id="select-field"
|
id="select-field"
|
||||||
|
|
@ -595,10 +576,18 @@ watch(
|
||||||
></q-badge>
|
></q-badge>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<span class="text-weight-bold column q-pl-md">
|
<span class="text-weight-bold column q-pl-md">
|
||||||
{{ props.row.name }}
|
{{
|
||||||
|
$i18n.locale === 'tha'
|
||||||
|
? props.row.name
|
||||||
|
: props.row.nameEN
|
||||||
|
}}
|
||||||
|
|
||||||
<span class="text-caption app-text-muted-2">
|
<span class="text-caption app-text-muted-2">
|
||||||
{{ props.row.nameEN }}
|
{{
|
||||||
|
$i18n.locale === 'tha'
|
||||||
|
? props.row.nameEN
|
||||||
|
: props.row.name
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<nav
|
<nav
|
||||||
|
|
|
||||||
|
|
@ -447,6 +447,17 @@ const useBranchStore = defineStore('api-branch', () => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchListBankByBranch(branchId: string) {
|
||||||
|
const res = await api.get(`/branch/${branchId}/bank`, {
|
||||||
|
headers: { 'X-Rtid': flowStore.rtid },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (res.status === 200) return res.data;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
map,
|
map,
|
||||||
|
|
@ -475,6 +486,8 @@ const useBranchStore = defineStore('api-branch', () => {
|
||||||
fetchByIdAttachment,
|
fetchByIdAttachment,
|
||||||
putAttachment,
|
putAttachment,
|
||||||
deleteByIdAttachment,
|
deleteByIdAttachment,
|
||||||
|
|
||||||
|
fetchListBankByBranch,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,13 @@ const useBusinessTypeStore = defineStore('business-type-store', () => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function create(data: BusinessTypePayLoad) {
|
async function create(
|
||||||
const res = await api.post('/business-type', {
|
data: BusinessTypePayLoad,
|
||||||
|
): Promise<BusinessType | null> {
|
||||||
|
const res = await api.post<BusinessType>('/business-type', {
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status < 400) {
|
if (res.status < 400) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
|
||||||