Compare commits
255 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 | ||
|
|
fd28f36876 | ||
|
|
a05c1e7004 | ||
|
|
db2a094471 | ||
|
|
9aba48401a | ||
|
|
a3c51f5f52 | ||
|
|
d44850a9ae | ||
|
|
b86891c8c2 | ||
|
|
473e272328 | ||
|
|
02d02cf3a1 | ||
|
|
044a530b8d | ||
|
|
b21949712b | ||
|
|
42e545dd66 | ||
|
|
263c703e69 | ||
|
|
968aa04aa9 | ||
|
|
8ca3f784f1 | ||
|
|
d60f858582 | ||
|
|
4ec3506e62 | ||
|
|
f3342dfbda | ||
|
|
9b56896695 | ||
|
|
7d4b38369c | ||
|
|
b977f86de9 | ||
|
|
da52bfbcbd | ||
|
|
cdb38e301e | ||
|
|
7f56a6219a | ||
|
|
642dec8de9 | ||
|
|
1360aca7e9 | ||
|
|
dbca22f639 | ||
|
|
b5abf693c2 | ||
|
|
8ef2ca2e96 | ||
|
|
ad715b20a2 | ||
|
|
915ce6f70b | ||
|
|
12b49a2a07 | ||
|
|
40e6d1ba1c | ||
|
|
22e11cf699 | ||
|
|
af1f74bdda | ||
|
|
ed55d07e38 | ||
|
|
ac50ef1c7c | ||
|
|
d4f021d0e6 | ||
|
|
3ddea74b73 | ||
|
|
2ac31c2e4c | ||
|
|
e3f86136e7 | ||
|
|
577da39cf0 | ||
|
|
30d2126161 | ||
|
|
b86c0a1e7a | ||
|
|
d59642bcb3 | ||
|
|
0e5378455b | ||
|
|
0c59ef89ca | ||
|
|
052722eb14 | ||
|
|
db7f96fc02 | ||
|
|
04765a656a | ||
|
|
c3989768ed | ||
|
|
e19d3f05f1 | ||
|
|
fe2860d818 | ||
|
|
25216de820 | ||
|
|
76cfb5fcec | ||
|
|
7d1a32efb4 | ||
|
|
9999a49fa0 | ||
|
|
6b55701afb | ||
|
|
fc7a94d7a2 | ||
|
|
2ba4758e50 | ||
|
|
5bed71053e | ||
|
|
bc53399153 | ||
|
|
059c6d3afc | ||
|
|
6117867aba | ||
|
|
7d425332c3 | ||
|
|
9539adee36 | ||
|
|
14487ed849 | ||
|
|
4a195494d6 | ||
|
|
5e155cfb0c | ||
|
|
78a59e277f | ||
|
|
844cf176df | ||
|
|
a89de83fe9 | ||
|
|
2b310c667d | ||
|
|
7679c076a7 | ||
|
|
088f829146 | ||
|
|
b9f1d04105 | ||
|
|
34f5b6474b | ||
|
|
84591ae719 | ||
|
|
8b2e3f76c4 | ||
|
|
1e34f18366 | ||
|
|
f08c83c98b | ||
|
|
c481266654 | ||
|
|
a59e0c5157 | ||
|
|
a5791a1b54 | ||
|
|
f646b3c9ba | ||
|
|
9dcec6b4c6 | ||
|
|
03adabeabd | ||
|
|
942449e373 | ||
|
|
dd09a8cb23 | ||
|
|
33e040c21a | ||
|
|
ca57f4790c | ||
|
|
e957672c91 | ||
|
|
436bfa41bb | ||
|
|
d4cab27aaf | ||
|
|
f286f6a16e | ||
|
|
f516212b6c | ||
|
|
011c65dcf8 | ||
|
|
3c4573192f | ||
|
|
48a1800b54 | ||
|
|
963ed11073 | ||
|
|
8b933455d1 | ||
|
|
e9d995fa3e | ||
|
|
2269038e11 | ||
|
|
d880e1a1c5 | ||
|
|
5e76c4d50d | ||
|
|
2d7b0189ee | ||
|
|
c991e9e03f | ||
|
|
aaa448fa0c | ||
|
|
58231aa936 | ||
|
|
701b90d89a | ||
|
|
8799799214 | ||
|
|
57660d7991 | ||
|
|
9ab61d8ded | ||
|
|
09f368f516 | ||
|
|
d831cd0799 | ||
|
|
860b0b8f47 | ||
|
|
41d02273ee | ||
|
|
19ee1040d4 | ||
|
|
5c01882a34 | ||
|
|
5edff6a5a8 | ||
|
|
1b475933da | ||
|
|
40942fc420 | ||
|
|
060b5980dd | ||
|
|
04c47b2700 | ||
|
|
df9430af24 | ||
|
|
ccf1a55052 | ||
|
|
a74c4648c6 | ||
|
|
62ac7503a1 | ||
|
|
3f15ce3ca6 | ||
|
|
0916ce7af2 | ||
|
|
105e91a655 | ||
|
|
de281ea79f | ||
|
|
cd86e7718a | ||
|
|
0f252b3080 | ||
|
|
80c8a0d8b4 | ||
|
|
0150f80ba2 | ||
|
|
1efa90816a | ||
|
|
543c28e162 | ||
|
|
bcd54813d1 | ||
|
|
2b78abcd3b | ||
|
|
4d023a7c7c | ||
|
|
70e9755952 | ||
|
|
9a57056ffa | ||
|
|
8354b6b40a | ||
|
|
4d8eebdd04 | ||
|
|
5ec924fe14 | ||
|
|
0871a8b899 | ||
|
|
7a2be56ef4 | ||
|
|
c4c4b76973 | ||
|
|
2192041e35 | ||
|
|
c1ffbef565 | ||
|
|
46de2412df | ||
|
|
ff5767cdd1 | ||
|
|
02c7598aec | ||
|
|
93700a2b54 | ||
|
|
d67b9b2f02 | ||
|
|
18844c70bc |
|
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 |
|
|
@ -183,15 +183,15 @@
|
|||
|
||||
"prefix": [
|
||||
{
|
||||
"label": "Mr",
|
||||
"label": "MR",
|
||||
"value": "mr"
|
||||
},
|
||||
{
|
||||
"label": "Mrs",
|
||||
"label": "MRS",
|
||||
"value": "mrs"
|
||||
},
|
||||
{
|
||||
"label": "Miss",
|
||||
"label": "MISS",
|
||||
"value": "miss"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -89,15 +89,7 @@ defineProps<{
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: hsla(0 0% 0% / 0.1);
|
||||
margin-bottom: var(--size-2);
|
||||
"
|
||||
/>
|
||||
<q-separator />
|
||||
<slot name="data"></slot>
|
||||
<template v-if="!$slots.data">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -159,42 +159,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
]"
|
||||
for="input-name-en"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
v-if="
|
||||
typeBranch !== 'headOffice' &&
|
||||
isRoleInclude(['head_of_admin', 'head_of_account'])
|
||||
"
|
||||
outlined
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
input-debounce="0"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
class="col-2"
|
||||
dense
|
||||
for="input-branch-status"
|
||||
:readonly="readonly || isRoleInclude(['head_of_account'])"
|
||||
:options="['Virtual', 'Branch']"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:label="$t('general.branchStatus')"
|
||||
:model-value="virtual ? 'Virtual' : 'Branch'"
|
||||
@update:model-value="(v) => (virtual = v === 'Virtual')"
|
||||
:rules="[(val) => val && val.length > 0]"
|
||||
:error-message="$t('form.error.required')"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ function deleteFile(name: string) {
|
|||
</div>
|
||||
|
||||
<q-file
|
||||
v-if="userType"
|
||||
v-if="userType && !readonly"
|
||||
ref="attachmentRef"
|
||||
for="input-attachment"
|
||||
:dense="dense"
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ watch(
|
|||
:id="`${prefixId}-select-prefix-name-en`"
|
||||
:for="`${prefixId}-select-prefix-name-en`"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
:label="$t('personnel.form.prefixName')"
|
||||
label="Prefix"
|
||||
class="col-md-1 col-6"
|
||||
v-model="prefixName"
|
||||
/>
|
||||
|
|
@ -253,16 +253,13 @@ watch(
|
|||
hide-bottom-space
|
||||
:readonly="readonly"
|
||||
:label="$t('form.email')"
|
||||
:rules="
|
||||
readonly
|
||||
? undefined
|
||||
: [
|
||||
(v: string) =>
|
||||
!v ||
|
||||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
|
||||
$t('form.error.invalid'),
|
||||
]
|
||||
"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(v: string) =>
|
||||
!v ||
|
||||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
|
||||
$t('form.error.invalid'),
|
||||
]"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="readonly ? email || '-' : email"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
|
||||
|
|
@ -296,15 +293,11 @@ watch(
|
|||
:readonly="readonly"
|
||||
:label="$t('form.birthDate')"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
:rules="
|
||||
employee
|
||||
? []
|
||||
: [
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', { field: $t('form.birthDate') }),
|
||||
]
|
||||
"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', { field: $t('form.birthDate') }),
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
|
|||
1656
src/components/03_customer-management/DialogEmployee.vue
Normal file
1772
src/components/03_customer-management/DrawerEmployee.vue
Normal file
|
|
@ -268,6 +268,7 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
|
|||
|
||||
<div class="col-md col-6">
|
||||
<DatePicker
|
||||
:label="$t('customerEmployee.formHealthCheck.coverageStartDate')"
|
||||
v-model="checkup.coverageStartDate"
|
||||
:id="`${prefixId}-input-coverage-start-date`"
|
||||
:readonly="readonly || checkup.statusSave"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import useOptionStore from 'stores/options';
|
|||
|
||||
import DatePicker from '../shared/DatePicker.vue';
|
||||
|
||||
import { dateFormat } from 'src/utils/datetime';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
const { locale } = useI18n();
|
||||
|
||||
|
|
@ -18,8 +20,8 @@ const issuePlace = defineModel<string>('issuePlace');
|
|||
const issueCountry = defineModel<string>('issueCountry');
|
||||
const issueDate = defineModel<Date | null | string>('issueDate');
|
||||
const type = defineModel<string>('type');
|
||||
const expireDate = defineModel<Date>('expireDate');
|
||||
const birthDate = defineModel<Date>('birthDate');
|
||||
const expireDate = defineModel<Date | string>('expireDate');
|
||||
const birthDate = defineModel<Date | string>('birthDate');
|
||||
const workerStatus = defineModel<string>('workerStatus');
|
||||
const nationality = defineModel<string>('nationality');
|
||||
const gender = defineModel<string>('gender');
|
||||
|
|
@ -32,6 +34,8 @@ const firstName = defineModel<string>('firstName');
|
|||
const namePrefix = defineModel<string>('namePrefix');
|
||||
const passportNumber = defineModel<string>('passportNumber');
|
||||
|
||||
const file = defineModel<File>('file');
|
||||
|
||||
const passportValidator = /[a-zA-Z]{1}[a-zA-Z0-9]{1}[0-9]{5,7}$/;
|
||||
|
||||
const genderOptions = ref<Record<string, unknown>[]>([]);
|
||||
|
|
@ -177,6 +181,30 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
function browse() {
|
||||
inputFile?.click();
|
||||
}
|
||||
|
||||
const inputFile = (() => {
|
||||
const _element = document.createElement('input');
|
||||
_element.type = 'file';
|
||||
_element.accept = 'image/jpeg,image/png';
|
||||
_element.addEventListener('change', change);
|
||||
return _element;
|
||||
})();
|
||||
|
||||
async function change(e: Event) {
|
||||
const _element = e.target as HTMLInputElement | null;
|
||||
const _file = _element?.files?.[0];
|
||||
|
||||
if (_file) {
|
||||
const newFileName = `passport-${dateFormat(new Date().toISOString())}-${_file.name}`;
|
||||
const renamedFile = new File([_file], newFileName, { type: _file.type });
|
||||
|
||||
file.value = renamedFile;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => namePrefix.value,
|
||||
(v) => {
|
||||
|
|
@ -221,20 +249,14 @@ watch(
|
|||
</div>
|
||||
|
||||
<div class="q-col-gutter-sm" :class="{ row: $q.screen.gt.sm }">
|
||||
<div
|
||||
class="col row justify-center q-col-gutter-sml"
|
||||
style="max-height: 50%"
|
||||
v-if="!ocr"
|
||||
>
|
||||
<q-avatar
|
||||
style="border: 1px dashed; border-color: black"
|
||||
square
|
||||
size="100px"
|
||||
font-size="50px"
|
||||
color="grey-4"
|
||||
text-color="grey"
|
||||
icon="mdi-image-outline"
|
||||
/>
|
||||
<div v-if="!ocr">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="mdi-upload-box-outline"
|
||||
@click="() => browse()"
|
||||
:disable="readonly"
|
||||
></q-btn>
|
||||
</div>
|
||||
<div
|
||||
class="row q-col-gutter-sm"
|
||||
|
|
@ -258,7 +280,7 @@ watch(
|
|||
:options="workerStatusOptions"
|
||||
:hide-dropdown-icon="readonly"
|
||||
:for="`${prefixId}-select-visa-type`"
|
||||
:label="$t('customerEmployee.form.workerType')"
|
||||
:label="$t('customerEmployee.form.workerStatus')"
|
||||
@filter="workerStatusFilter"
|
||||
:model-value="readonly ? workerStatus || '-' : workerStatus"
|
||||
@update:model-value="
|
||||
|
|
|
|||
|
|
@ -28,20 +28,22 @@ const arrivalAt = defineModel<string>('arrivalAt');
|
|||
const arrivalTMNo = defineModel<string>('arrivalTmNo');
|
||||
const arrivalTM = defineModel<string>('arrivalTm');
|
||||
const mrz = defineModel<string>('mrz');
|
||||
const entryCount = defineModel<number>('entryCount');
|
||||
const entryCount = defineModel<number | string>('entryCount');
|
||||
const issuePlace = defineModel<string>('issuePlace');
|
||||
const issueCountry = defineModel<string>('issueCountry');
|
||||
const issueDate = defineModel<Date | null | string>('visaIssueDate');
|
||||
const type = defineModel<string>('type');
|
||||
const expireDate = defineModel<Date>('expireDate');
|
||||
const expireDate = defineModel<Date | string>('expireDate');
|
||||
const remark = defineModel<string>('remark');
|
||||
const workerType = defineModel<string>('workerType');
|
||||
const number = defineModel<string>('number');
|
||||
const reportDate = defineModel<Date | null | string>('reportDate');
|
||||
|
||||
const calculatedVisaDate = computed(() => {
|
||||
if (!issueDate.value) return undefined;
|
||||
return calculate90DayNext(issueDate.value);
|
||||
});
|
||||
//
|
||||
// const calculatedVisaDate = computed(() => {
|
||||
// if (!issueDate.value) return undefined;
|
||||
// return calculate90DayNext(issueDate.value);
|
||||
// });
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
|
|
@ -138,6 +140,10 @@ watch(
|
|||
);
|
||||
},
|
||||
);
|
||||
//
|
||||
// watch([() => issueDate.value], () => {
|
||||
// reportDate.value = calculate90DayNext(issueDate.value);
|
||||
// });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -151,7 +157,7 @@ watch(
|
|||
name="mdi-passport"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
{{ title }}
|
||||
{{ $t(title) }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -371,10 +377,12 @@ watch(
|
|||
<DatePicker
|
||||
:id="`${prefixId}-date-picker-visa-issuance`"
|
||||
:readonly
|
||||
:disabled="!readonly"
|
||||
:label="$t('customerEmployee.form.visa90Day')"
|
||||
:model-value="calculatedVisaDate"
|
||||
v-model="reportDate"
|
||||
clearable
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ const prop = withDefaults(
|
|||
inTable?: boolean;
|
||||
addButton?: boolean;
|
||||
prefixId?: string;
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
}>(),
|
||||
{
|
||||
gridView: false,
|
||||
|
|
@ -139,8 +141,9 @@ defineEmits<{
|
|||
<q-avatar size="md">
|
||||
<q-img
|
||||
:src="
|
||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
||||
`/images/employee-avatar-${props.row.gender}.png`
|
||||
props.row.selectedImage
|
||||
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
|
||||
: `/images/employee-avatar-${props.row.gender}.png`
|
||||
"
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
|
|
@ -265,9 +268,10 @@ defineEmits<{
|
|||
@click.stop="$emit('view', props.row)"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="!inTable"
|
||||
v-if="!inTable && !hideAction"
|
||||
:id-name="props.row.firstName"
|
||||
:status="props.row.status"
|
||||
:hide-delete="hideDelete"
|
||||
@view="$emit('view', props.row)"
|
||||
@edit="$emit('edit', props.row)"
|
||||
@delete="$emit('delete', props.row)"
|
||||
|
|
@ -280,9 +284,11 @@ defineEmits<{
|
|||
<template v-slot:item="props">
|
||||
<div class="col-12 col-md-3 col-sm-6">
|
||||
<PersonCard
|
||||
history
|
||||
:hide-delete="hideDelete"
|
||||
:hide-action="hideAction"
|
||||
:id="`card-${props.row.firstNameEN}`"
|
||||
:field-selected="fieldSelected"
|
||||
history
|
||||
:prefix-id="props.row.firstNameEN ?? props.rowIndex"
|
||||
:data="{
|
||||
code: props.row.code,
|
||||
|
|
@ -290,9 +296,9 @@ defineEmits<{
|
|||
$i18n.locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName} `.trim(),
|
||||
img:
|
||||
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
|
||||
`/images/employee-avatar-${props.row.gender}.png`,
|
||||
img: props.row.selectedImage
|
||||
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
|
||||
: `/images/employee-avatar-${props.row.gender}.png`,
|
||||
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
|
||||
male: props.row.gender === 'male',
|
||||
female: props.row.gender === 'female',
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ defineProps<{
|
|||
employeeOwnerOption?: CustomerBranch[];
|
||||
prefixId: string;
|
||||
showBtnSave?: boolean;
|
||||
disableCustomerSelect?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -117,12 +118,16 @@ defineEmits<{
|
|||
|
||||
<div class="col-12 row" style="gap: var(--size-2)">
|
||||
<SelectCustomer
|
||||
id="form-select-customer-branch-id"
|
||||
for="form-select-customer-branch-id"
|
||||
v-model:value="customerBranchId"
|
||||
v-model:value-option="currentCustomerBranch"
|
||||
:label="$t('customer.form.branchCode')"
|
||||
class="col-12 field-two"
|
||||
simple
|
||||
required
|
||||
:readonly
|
||||
:disabled="disableCustomerSelect && !readonly"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { QField } from 'quasar';
|
|||
defineProps<{
|
||||
readonly?: boolean;
|
||||
onDrawer?: boolean;
|
||||
hideAction?: boolean;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
|
@ -201,6 +202,7 @@ onMounted(async () => {
|
|||
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
|
||||
>
|
||||
<ToggleButton
|
||||
:disable="hideAction"
|
||||
class="q-mr-sm"
|
||||
two-way
|
||||
:model-value="flowData.status !== 'INACTIVE'"
|
||||
|
|
@ -609,7 +611,7 @@ onMounted(async () => {
|
|||
</span>
|
||||
</q-item>
|
||||
</template>
|
||||
<template #append>
|
||||
<template v-if="!readonly" #append>
|
||||
<q-icon
|
||||
name="mdi-menu-down"
|
||||
:class="{ rotated: responsibleMenu }"
|
||||
|
|
|
|||
|
|
@ -167,26 +167,28 @@ withDefaults(
|
|||
<div class="col-12 full-width">
|
||||
<q-table
|
||||
:rows-per-page-options="[0]"
|
||||
:rows="[
|
||||
{
|
||||
label: $t('productService.product.salePrice'),
|
||||
pricePerUnit: price,
|
||||
calcVat,
|
||||
vatIncluded,
|
||||
},
|
||||
{
|
||||
label: $t('productService.product.agentPrice'),
|
||||
calcVat: agentPriceCalcVat,
|
||||
vatIncluded: agentPriceVatIncluded,
|
||||
pricePerUnit: agentPrice,
|
||||
},
|
||||
{
|
||||
label: $t('productService.product.processingPrice'),
|
||||
calcVat: serviceChargeCalcVat,
|
||||
vatIncluded: serviceChargeVatIncluded,
|
||||
pricePerUnit: serviceCharge,
|
||||
},
|
||||
]"
|
||||
:rows="
|
||||
[
|
||||
priceDisplay.price && {
|
||||
label: $t('productService.product.salePrice'),
|
||||
pricePerUnit: price,
|
||||
calcVat,
|
||||
vatIncluded,
|
||||
},
|
||||
priceDisplay.agentPrice && {
|
||||
label: $t('productService.product.agentPrice'),
|
||||
calcVat: agentPriceCalcVat,
|
||||
vatIncluded: agentPriceVatIncluded,
|
||||
pricePerUnit: agentPrice,
|
||||
},
|
||||
priceDisplay.serviceCharge && {
|
||||
label: $t('productService.product.processingPrice'),
|
||||
calcVat: serviceChargeCalcVat,
|
||||
vatIncluded: serviceChargeVatIncluded,
|
||||
pricePerUnit: serviceCharge,
|
||||
},
|
||||
].filter(Boolean)
|
||||
"
|
||||
:columns
|
||||
hide-bottom
|
||||
bordered
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ watch(
|
|||
(c, o) => {
|
||||
const list = c.map((v: { name: string }) => v.name);
|
||||
const oldList = o.map((v: { name: string }) => v.name);
|
||||
const index = oldList.indexOf(workName.value || '');
|
||||
const index = workName.value ? oldList.indexOf(workName.value) : -1;
|
||||
if (index === -1) return;
|
||||
|
||||
if (
|
||||
list[index] !== oldList[index] &&
|
||||
|
|
@ -704,8 +705,8 @@ watch(
|
|||
}
|
||||
|
||||
:deep(
|
||||
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
|
||||
) {
|
||||
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
|
||||
) {
|
||||
justify-content: start !important;
|
||||
padding-right: 8px !important;
|
||||
padding-top: 16px;
|
||||
|
|
@ -735,8 +736,8 @@ watch(
|
|||
}
|
||||
|
||||
:deep(
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { DeleteButton, EditButton, SaveButton, UndoButton } from '../button';
|
||||
import { scrollToElement } from 'stores/utils';
|
||||
// import { useI18n } from 'vue-i18n';
|
||||
// import { storeToRefs } from 'pinia';
|
||||
|
||||
|
|
@ -138,11 +137,8 @@ watch(
|
|||
@click="
|
||||
() => {
|
||||
assignClone();
|
||||
if (nameList[nameList.length - 1].name === '') {
|
||||
$emit('delete', cloneList[cloneList.length - 1].id, true);
|
||||
cloneList = cloneList.filter((item) => item.name !== '');
|
||||
nameList = nameList.filter((item) => item.name !== '');
|
||||
}
|
||||
cloneList = cloneList.filter((item) => item.name !== '');
|
||||
nameList = nameList.filter((item) => item.name !== '');
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ defineProps<{
|
|||
readonly?: boolean;
|
||||
onDrawer?: boolean;
|
||||
inputOnly?: boolean;
|
||||
disableToggle?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -76,6 +77,7 @@ defineEmits<{
|
|||
<ToggleButton
|
||||
class="q-mr-sm"
|
||||
two-way
|
||||
:disable="disableToggle"
|
||||
:model-value="status !== 'INACTIVE'"
|
||||
@click="
|
||||
() => {
|
||||
|
|
@ -195,8 +197,8 @@ defineEmits<{
|
|||
}
|
||||
|
||||
:deep(
|
||||
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
|
||||
) {
|
||||
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
|
||||
) {
|
||||
justify-content: start !important;
|
||||
padding-right: 8px !important;
|
||||
padding-top: 16px;
|
||||
|
|
@ -208,15 +210,15 @@ defineEmits<{
|
|||
}
|
||||
|
||||
:deep(
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
|
||||
:deep(
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
|
||||
.q-focus-helper
|
||||
) {
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
|
||||
.q-focus-helper
|
||||
) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,18 @@
|
|||
import SelectCustomer from '../shared/select/SelectCustomer.vue';
|
||||
import SelectBranch from '../shared/select/SelectBranch.vue';
|
||||
|
||||
import { CustomerBranch } from 'src/stores/customer';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const branchId = defineModel<string>('branchId');
|
||||
const customerBranchId = defineModel<string>('customerBranchId');
|
||||
const agentPrice = defineModel<boolean>('agentPrice');
|
||||
const special = defineModel<boolean>('special');
|
||||
|
||||
const customerBranchOption = defineModel<CustomerBranch>(
|
||||
'customerBranchOption',
|
||||
);
|
||||
|
||||
defineProps<{
|
||||
outlined?: boolean;
|
||||
readonly?: boolean;
|
||||
|
|
@ -67,8 +74,12 @@ defineEmits<{
|
|||
required
|
||||
:readonly
|
||||
/>
|
||||
|
||||
<SelectCustomer
|
||||
id="about-select-customer-branch-id"
|
||||
for="about-select-customer-branch-id"
|
||||
v-model:value="customerBranchId"
|
||||
v-model:value-option="customerBranchOption"
|
||||
:label="$t('quotation.customer')"
|
||||
:creatable-disabled-text="`(${$t('form.error.selectField', {
|
||||
field: $t('quotation.branchVirtual'),
|
||||
|
|
@ -88,14 +99,14 @@ defineEmits<{
|
|||
|
||||
<style scoped>
|
||||
:deep(
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one
|
||||
) {
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one
|
||||
) {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
:deep(
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two
|
||||
) {
|
||||
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two
|
||||
) {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -58,16 +58,15 @@ const currentBtnOpen = ref<{ title: string; opened: boolean[] }[]>([
|
|||
|
||||
function calcPrice(c: (typeof rows.value)[number]) {
|
||||
const originalPrice = c.pricePerUnit;
|
||||
const finalPriceWithVat = precisionRound(
|
||||
originalPrice + originalPrice * (config.value?.vat || 0.07),
|
||||
const finalPricePerUnit = precisionRound(
|
||||
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;
|
||||
const vat = c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
||||
? (finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07)
|
||||
: 0;
|
||||
return precisionRound(price + vat);
|
||||
return precisionRound(price);
|
||||
}
|
||||
|
||||
const discount4Show = ref<string[]>([]);
|
||||
|
|
@ -435,8 +434,20 @@ watch(
|
|||
<q-td align="right">
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
props.row.pricePerUnit * props.row.amount -
|
||||
props.row.discount,
|
||||
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.discount,
|
||||
),
|
||||
2,
|
||||
)
|
||||
}}
|
||||
|
|
@ -448,9 +459,12 @@ watch(
|
|||
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
|
||||
]
|
||||
? precisionRound(
|
||||
(props.row.pricePerUnit * props.row.amount -
|
||||
props.row.discount) *
|
||||
(config?.vat || 0.07),
|
||||
((props.row.pricePerUnit *
|
||||
(1 + (config?.vat || 0.07)) *
|
||||
props.row.amount -
|
||||
props.row.discount) /
|
||||
(1 + (config?.vat || 0.07))) *
|
||||
0.07,
|
||||
)
|
||||
: 0,
|
||||
2,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { QTableProps } from 'quasar';
|
||||
import { dateFormat } from 'src/utils/datetime';
|
||||
import { dateFormat, dateFormatJS } from 'src/utils/datetime';
|
||||
|
||||
import { formatNumberDecimal } from 'stores/utils';
|
||||
|
||||
|
|
@ -19,6 +19,8 @@ const props = withDefaults(
|
|||
page?: number;
|
||||
pageSize?: number;
|
||||
hideBtnPreview?: boolean;
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
}>(),
|
||||
{
|
||||
row: () => [],
|
||||
|
|
@ -84,11 +86,11 @@ defineEmits<{
|
|||
</q-td>
|
||||
|
||||
<q-td v-if="visibleColumns.includes('createdAt')">
|
||||
{{ dateFormat(props.row.createdAt) }}
|
||||
{{ dateFormatJS({ date: props.row.createdAt }) }}
|
||||
</q-td>
|
||||
|
||||
<q-td v-if="visibleColumns.includes('dueDate')">
|
||||
{{ dateFormat(props.row.dueDate) }}
|
||||
{{ dateFormatJS({ date: props.row.dueDate }) }}
|
||||
</q-td>
|
||||
|
||||
<q-td v-if="visibleColumns.includes('contactName')">
|
||||
|
|
@ -147,12 +149,12 @@ defineEmits<{
|
|||
flat
|
||||
@click.stop="$emit('view', props.row)"
|
||||
/>
|
||||
|
||||
<KebabAction
|
||||
v-if="!hideAction"
|
||||
:idName="`btn-kebab-${props.row.workName}`"
|
||||
status="'ACTIVE'"
|
||||
hide-toggle
|
||||
hide-delete
|
||||
:hide-delete
|
||||
:hide-edit="hideEdit"
|
||||
@view="$emit('view', props.row)"
|
||||
@edit="$emit('edit', props.row)"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SelectInput from '../shared/SelectInput.vue';
|
||||
import useOptionStore from 'src/stores/options';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { useInstitution } from 'src/stores/institution';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
|
||||
|
|
@ -10,6 +14,11 @@ defineProps<{
|
|||
readonly?: boolean;
|
||||
onDrawer?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'deleteAttachment', name: string): void;
|
||||
}>();
|
||||
|
||||
const attachmentRef = ref();
|
||||
|
||||
const group = defineModel('group', { default: '' });
|
||||
const name = defineModel('name', { default: '' });
|
||||
|
|
@ -17,8 +26,19 @@ const nameEn = defineModel('nameEn', { default: '' });
|
|||
const contactName = defineModel('contactName', { default: '' });
|
||||
const email = defineModel('email', { default: '' });
|
||||
const contactTel = defineModel('contactTel', { default: '' });
|
||||
const attachment = defineModel<File[]>('attachment');
|
||||
const attachmentList =
|
||||
defineModel<{ name: string; url: string }[]>('attachmentList');
|
||||
|
||||
type Options = { label: string; value: string };
|
||||
|
||||
function openNewTab(url: string) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
function deleteAttachment(name: string) {
|
||||
emit('deleteAttachment', name);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
|
|
@ -68,7 +88,12 @@ type Options = { label: string; value: string };
|
|||
class="col-md-4 col-12"
|
||||
:label="$t('agencies.name')"
|
||||
v-model="name"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
:rules="[
|
||||
(val) => !!val || $t('form.error.required'),
|
||||
(val) =>
|
||||
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
|
||||
$t('form.error.branchNameField'),
|
||||
]"
|
||||
/>
|
||||
<q-input
|
||||
for="input-agencies-name-en"
|
||||
|
|
@ -79,6 +104,15 @@ type Options = { label: string; value: string };
|
|||
class="col-md-4 col-12"
|
||||
:label="'Agencies Name'"
|
||||
v-model="nameEn"
|
||||
:rules="
|
||||
nameEn
|
||||
? [
|
||||
(val) =>
|
||||
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
|
||||
$t('form.error.branchNameENField'),
|
||||
]
|
||||
: []
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
@ -148,6 +182,78 @@ type Options = { label: string; value: string };
|
|||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-file
|
||||
v-if="!readonly"
|
||||
ref="attachmentRef"
|
||||
for="input-attachment"
|
||||
dense
|
||||
outlined
|
||||
multiple
|
||||
append
|
||||
:readonly
|
||||
:label="$t('personnel.form.attachment')"
|
||||
class="col"
|
||||
v-model="attachment"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<Icon
|
||||
icon="material-symbols:attach-file"
|
||||
width="20px"
|
||||
style="color: var(--brand-1)"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:file="file">
|
||||
<div class="row full-width items-center">
|
||||
<span class="col ellipsis">
|
||||
{{ file.file.name }}
|
||||
</span>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
flat
|
||||
padding="2 2"
|
||||
class="app-text-muted"
|
||||
icon="mdi-close-circle"
|
||||
@click.stop="attachmentRef.removeAtIndex(file.index)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</q-file>
|
||||
|
||||
<div v-if="attachmentList && attachmentList?.length > 0" class="col-12">
|
||||
<q-list bordered separator class="rounded" style="padding: 0">
|
||||
<q-item
|
||||
id="attachment-file"
|
||||
for="attachment-file"
|
||||
v-for="item in attachmentList"
|
||||
clickable
|
||||
:key="item.url"
|
||||
class="items-center row"
|
||||
@click="() => openNewTab(item.url)"
|
||||
>
|
||||
<q-item-section>
|
||||
<div class="row items-center justify-between">
|
||||
<div class="col">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<q-btn
|
||||
v-if="!readonly"
|
||||
id="delete-file"
|
||||
rounded
|
||||
flat
|
||||
dense
|
||||
unelevated
|
||||
size="md"
|
||||
icon="mdi-trash-can-outline"
|
||||
class="app-text-negative"
|
||||
@click.stop="deleteAttachment(item.name)"
|
||||
/>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -27,26 +27,38 @@ withDefaults(
|
|||
class="app-text-muted q-pr-sm"
|
||||
:width="iconSize || '2rem'"
|
||||
/>
|
||||
<span class="row col">
|
||||
<span class="col-12 app-text-muted-2" style="font-size: 10px">
|
||||
<span :id="`dd-wrapper-${label}`" class="row col">
|
||||
<span
|
||||
:id="`dd-label-${label}`"
|
||||
class="col-12 app-text-muted-2"
|
||||
style="font-size: 10px"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<span class="col-12 ellipsis">
|
||||
<span :id="`dd-value-wrapper-${label}`" class="col-12 ellipsis">
|
||||
<slot name="value">
|
||||
<span
|
||||
:class="{ 'link cursor-pointer': clickable }"
|
||||
v-if="typeof value === 'string'"
|
||||
@click="$emit('labelClick', value, null)"
|
||||
@click="clickable ? $emit('labelClick', value, null) : undefined"
|
||||
:id="`link-${value}`"
|
||||
:for="`link-${value}`"
|
||||
>
|
||||
{{ value }}
|
||||
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
|
||||
</span>
|
||||
<span v-else :class="{ 'link cursor-pointer': clickable }">
|
||||
<span
|
||||
:id="`dd-value-${label}`"
|
||||
v-else
|
||||
:class="{ 'link cursor-pointer': clickable }"
|
||||
>
|
||||
<span
|
||||
v-for="(item, index) in value"
|
||||
:key="index"
|
||||
@click="$emit('labelClick', item, index)"
|
||||
class="link cursor-pointer"
|
||||
:id="`link-${item}`"
|
||||
:for="`link-${item}`"
|
||||
>
|
||||
{{ item }}
|
||||
<span v-if="index < value.length - 1">, </span>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ defineProps<{
|
|||
const quotationId = defineModel<string>('quotationId', {
|
||||
required: true,
|
||||
});
|
||||
const isDebitNote = defineModel<boolean>('isDebitNote', {
|
||||
required: false,
|
||||
default: false,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
|
|
@ -37,6 +41,7 @@ const quotationId = defineModel<string>('quotationId', {
|
|||
cancelIncludeDebitNote: true,
|
||||
hasCancel: true,
|
||||
}"
|
||||
@selected="(v) => (isDebitNote = v.isDebitNote)"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,15 @@ defineProps<{
|
|||
|
||||
const modal = defineModel('modal', { default: false });
|
||||
const currentTab = defineModel<string>('currentTab');
|
||||
|
||||
async function onValidationError(ref: any) {
|
||||
const el = ref.$el as Element;
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-dialog
|
||||
|
|
@ -60,6 +69,7 @@ const currentTab = defineModel<string>('currentTab');
|
|||
@submit.prevent
|
||||
@validation-success="submit"
|
||||
class="column full-height"
|
||||
@validation-error="onValidationError"
|
||||
>
|
||||
<!-- header -->
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
UndoButton,
|
||||
} from 'components/button';
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title: string;
|
||||
category?: string;
|
||||
|
|
@ -42,10 +42,24 @@ const drawerOpen = defineModel<boolean>('drawerOpen', {
|
|||
const myForm = ref();
|
||||
|
||||
function reset() {
|
||||
if (props.beforeClose) {
|
||||
drawerOpen.value = props.beforeClose
|
||||
? props.beforeClose()
|
||||
: !drawerOpen.value;
|
||||
}
|
||||
if (myForm.value) {
|
||||
myForm.value.resetValidation();
|
||||
}
|
||||
}
|
||||
|
||||
async function onValidationError(ref: any) {
|
||||
const el = ref.$el as Element;
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<q-drawer
|
||||
|
|
@ -53,7 +67,6 @@ function reset() {
|
|||
@show="show"
|
||||
@before-hide="reset"
|
||||
@hide="close"
|
||||
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
|
||||
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
|
||||
v-model="drawerOpen"
|
||||
behavior="mobile"
|
||||
|
|
@ -66,6 +79,7 @@ function reset() {
|
|||
greedy
|
||||
@submit.prevent
|
||||
@validation-success="submit"
|
||||
@validation-error="onValidationError"
|
||||
>
|
||||
<div
|
||||
class="column justify-between full-height"
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ defineProps<{
|
|||
color="grey"
|
||||
icon="mdi-close"
|
||||
v-close-popup
|
||||
@click="cancel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
const pageSize = defineModel<number>({ required: true });
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
fetchData?: (...args: unknown[]) => void;
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -10,7 +17,12 @@ const pageSize = defineModel<number>({ required: true });
|
|||
:key="v"
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="pageSize = v"
|
||||
@click="
|
||||
() => {
|
||||
pageSize = v;
|
||||
fetchData();
|
||||
}
|
||||
"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ v }}</q-item-label>
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ const smallBanner = ref(false);
|
|||
|
||||
<ToggleButton
|
||||
v-if="useToggle"
|
||||
:disable="readonly"
|
||||
two-way
|
||||
:model-value="toggleStatus !== 'INACTIVE'"
|
||||
@click="$emit('update:toggleStatus', toggleStatus)"
|
||||
|
|
@ -265,6 +266,7 @@ const smallBanner = ref(false);
|
|||
class="app-text-muted full-width"
|
||||
align="left"
|
||||
v-if="typeof tabsList === 'object'"
|
||||
@update:model-value="(v) => $emit('update:currentTab', v)"
|
||||
>
|
||||
<q-tab
|
||||
v-for="tab in tabsList"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { BranchWithChildren } from 'stores/branch/types';
|
||||
import KebabAction from './shared/KebabAction.vue';
|
||||
import { isRoleInclude } from 'stores/utils';
|
||||
|
||||
const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', {
|
||||
default: [],
|
||||
|
|
@ -17,6 +16,7 @@ withDefaults(
|
|||
labelKey?: string;
|
||||
childrenKey: string;
|
||||
action?: boolean;
|
||||
hideCreate?: boolean;
|
||||
}>(),
|
||||
{
|
||||
color: 'transparent',
|
||||
|
|
@ -96,7 +96,9 @@ defineEmits<{
|
|||
expandedTree[expandedTree.length - 1] === node.id,
|
||||
}"
|
||||
>
|
||||
{{ node.name }}
|
||||
{{
|
||||
$i18n.locale === 'eng' ? node.nameEN || node.name : node.name
|
||||
}}
|
||||
</span>
|
||||
<span class="app-text-muted text-caption ellipsis">
|
||||
{{ node.code }}
|
||||
|
|
@ -120,11 +122,7 @@ defineEmits<{
|
|||
/>
|
||||
|
||||
<q-btn
|
||||
v-if="
|
||||
node.isHeadOffice &&
|
||||
typeTree === 'branch' &&
|
||||
isRoleInclude(['head_of_admin', 'admin', 'system'])
|
||||
"
|
||||
v-if="node.isHeadOffice && typeTree === 'branch' && !hideCreate"
|
||||
:id="`create-sub-branch-btn-${node.name}`"
|
||||
@click.stop="$emit('create', node)"
|
||||
icon="mdi-file-plus-outline"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ defineEmits<{
|
|||
(e: 'click', v: MouseEvent): void;
|
||||
}>();
|
||||
defineProps<{
|
||||
id?: string;
|
||||
icon?: string;
|
||||
color: string;
|
||||
iconOnly?: boolean;
|
||||
|
|
@ -18,6 +19,7 @@ defineProps<{
|
|||
|
||||
<template>
|
||||
<button
|
||||
:id="id"
|
||||
@click="(e) => $emit('click', e)"
|
||||
class="main-btn"
|
||||
:class="{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ defineProps<{
|
|||
outlined?: boolean;
|
||||
disabled?: boolean;
|
||||
dark?: boolean;
|
||||
color?: string;
|
||||
|
||||
label?: string;
|
||||
icon?: string;
|
||||
|
|
@ -23,7 +24,7 @@ defineProps<{
|
|||
@click="(e) => $emit('click', e)"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:icon="icon || 'mdi-content-save-outline'"
|
||||
color="207 96% 32%"
|
||||
:color="color || '207 96% 32%'"
|
||||
:title="iconOnly ? $t('general.save') : undefined"
|
||||
>
|
||||
{{ label || $t('general.save') }}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ function update(value: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
async function onValidationError(ref: any) {
|
||||
const el = ref.$el as Element;
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
|
||||
const state = defineModel({ default: false });
|
||||
</script>
|
||||
|
||||
|
|
@ -41,6 +50,7 @@ const state = defineModel({ default: false });
|
|||
}"
|
||||
>
|
||||
<q-form
|
||||
@validation-error="onValidationError"
|
||||
@submit.prevent="(e) => $emit('submit', e)"
|
||||
@reset="$emit('reset')"
|
||||
greedy
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { formatAddress } from 'src/utils/address';
|
|||
import useOptionStore from 'stores/options';
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
addressTitle?: string;
|
||||
addressTitleEN?: string;
|
||||
|
|
@ -30,6 +30,7 @@ defineProps<{
|
|||
|
||||
useEmployment?: boolean;
|
||||
useWorkPlace?: boolean;
|
||||
useForeignAddress?: boolean;
|
||||
}>();
|
||||
|
||||
const addressStore = useAddressStore();
|
||||
|
|
@ -57,6 +58,25 @@ const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
|
|||
const zipCode = defineModel<string | null | undefined>('zipCode');
|
||||
const sameWithEmployer = defineModel<boolean>('sameWithEmployer');
|
||||
|
||||
const provinceTextEN = defineModel<string | null | undefined>(
|
||||
'provinceTextEn',
|
||||
{
|
||||
default: '',
|
||||
},
|
||||
);
|
||||
const districtTextEN = defineModel<string | null | undefined>(
|
||||
'districtTextEn',
|
||||
{
|
||||
default: '',
|
||||
},
|
||||
);
|
||||
const subDistrictTextEN = defineModel<string | null | undefined>(
|
||||
'subDistrictTextEn',
|
||||
{
|
||||
default: '',
|
||||
},
|
||||
);
|
||||
|
||||
const homeCode = defineModel<string | null | undefined>('homeCode');
|
||||
const employmentOffice = defineModel<string | null | undefined>(
|
||||
'employmentOffice',
|
||||
|
|
@ -64,6 +84,7 @@ const employmentOffice = defineModel<string | null | undefined>(
|
|||
const employmentOfficeEN = defineModel<string | null | undefined>(
|
||||
'employmentOfficeEn',
|
||||
);
|
||||
const addressForeign = defineModel<boolean>('addressForeign');
|
||||
|
||||
const addrOptions = reactive<{
|
||||
provinceOps: Province[];
|
||||
|
|
@ -78,14 +99,18 @@ const addrOptions = reactive<{
|
|||
const area = ref<Office[]>([]);
|
||||
|
||||
const fullAddress = computed(() => {
|
||||
const province = provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = subDistrictOptions.value.find(
|
||||
(v) => v.id === subDistrictId.value,
|
||||
);
|
||||
const province = addressForeign.value
|
||||
? { id: '1', name: provinceId.value }
|
||||
: provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = addressForeign.value
|
||||
? { id: '1', name: districtId.value }
|
||||
: districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = addressForeign.value
|
||||
? { id: '1', name: subDistrictId.value }
|
||||
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
|
||||
|
||||
if (province && district && sDistrict) {
|
||||
const fullAddress = formatAddress({
|
||||
if (province?.name && district?.name && sDistrict?.name) {
|
||||
const fullAddressText = formatAddress({
|
||||
address: address.value,
|
||||
addressEN: addressEN.value,
|
||||
moo: moo.value ? moo.value : '',
|
||||
|
|
@ -97,21 +122,26 @@ const fullAddress = computed(() => {
|
|||
province: province as unknown as Province,
|
||||
district: district as unknown as District,
|
||||
subDistrict: sDistrict as unknown as SubDistrict,
|
||||
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
|
||||
});
|
||||
return fullAddress;
|
||||
return fullAddressText;
|
||||
}
|
||||
return '-';
|
||||
});
|
||||
|
||||
const fullAddressEN = computed(() => {
|
||||
const province = provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = subDistrictOptions.value.find(
|
||||
(v) => v.id === subDistrictId.value,
|
||||
);
|
||||
const province = addressForeign.value
|
||||
? { nameEN: provinceTextEN.value }
|
||||
: provinceOptions.value.find((v) => v.id === provinceId.value);
|
||||
const district = addressForeign.value
|
||||
? { nameEN: districtTextEN.value }
|
||||
: districtOptions.value.find((v) => v.id === districtId.value);
|
||||
const sDistrict = addressForeign.value
|
||||
? { nameEN: subDistrictTextEN.value }
|
||||
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
|
||||
|
||||
if (province && district && sDistrict) {
|
||||
const fullAddress = formatAddress({
|
||||
if (province?.nameEN && district?.nameEN && sDistrict?.nameEN) {
|
||||
const fullAddressText = formatAddress({
|
||||
address: address.value,
|
||||
addressEN: addressEN.value,
|
||||
moo: moo.value ? moo.value : '',
|
||||
|
|
@ -124,8 +154,9 @@ const fullAddressEN = computed(() => {
|
|||
district: district as unknown as District,
|
||||
subDistrict: sDistrict as unknown as SubDistrict,
|
||||
en: true,
|
||||
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
|
||||
});
|
||||
return fullAddress;
|
||||
return fullAddressText;
|
||||
}
|
||||
return '-';
|
||||
});
|
||||
|
|
@ -149,7 +180,7 @@ async function fetchProvince() {
|
|||
}
|
||||
|
||||
async function fetchDistrict() {
|
||||
if (!provinceId.value) return;
|
||||
if (!provinceId.value || addressForeign.value) return;
|
||||
|
||||
const result = await addressStore.fetchDistrictByProvinceId(provinceId.value);
|
||||
if (result) addrOptions.districtOps = result;
|
||||
|
|
@ -168,7 +199,7 @@ async function fetchDistrict() {
|
|||
}
|
||||
|
||||
async function fetchSubDistrict() {
|
||||
if (!districtId.value) return;
|
||||
if (!districtId.value || addressForeign.value) return;
|
||||
const result = await addressStore.fetchSubDistrictByProvinceId(
|
||||
districtId.value,
|
||||
);
|
||||
|
|
@ -255,6 +286,16 @@ onMounted(async () => {
|
|||
await fetchSubDistrict();
|
||||
});
|
||||
|
||||
function clearAddress() {
|
||||
provinceId.value = null;
|
||||
districtId.value = null;
|
||||
subDistrictId.value = null;
|
||||
provinceTextEN.value = null;
|
||||
districtTextEN.value = null;
|
||||
subDistrictTextEN.value = null;
|
||||
zipCode.value = null;
|
||||
}
|
||||
|
||||
watch(provinceId, fetchDistrict);
|
||||
watch(districtId, fetchSubDistrict);
|
||||
|
||||
|
|
@ -313,6 +354,15 @@ watchEffect(async () => {
|
|||
{{ $t('customerEmployee.form.addressCustom') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="useForeignAddress" class="text-caption q-ml-md app-text-muted">
|
||||
<q-checkbox
|
||||
size="xs"
|
||||
v-model="addressForeign"
|
||||
@update:model-value="clearAddress"
|
||||
/>
|
||||
{{ $t('personnel.form.addressForeign') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-y-md">
|
||||
|
|
@ -449,7 +499,24 @@ watchEffect(async () => {
|
|||
(v) => (typeof v === 'string' ? (street = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="provinceId"
|
||||
:dense="dense"
|
||||
:label="$t('form.province')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-province-${indexId}` : 'input-province'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -493,7 +560,24 @@ watchEffect(async () => {
|
|||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="districtId"
|
||||
:dense="dense"
|
||||
:label="$t('form.district')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-district-${indexId}` : 'input-district'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -536,7 +620,25 @@ watchEffect(async () => {
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="subDistrictId"
|
||||
:dense="dense"
|
||||
:label="$t('form.district')"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-${indexId}` : 'input-sub-district'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -580,17 +682,27 @@ watchEffect(async () => {
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
:key="Number(addressForeign)"
|
||||
hide-bottom-space
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
readonly
|
||||
:disable="!addressForeign && !readonly && !sameWithEmployer"
|
||||
:readonly="!addressForeign || readonly"
|
||||
:label="$t('form.zipCode')"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="
|
||||
addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? ''
|
||||
!addressForeign
|
||||
? (addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? '')
|
||||
: zipCode
|
||||
"
|
||||
@update:model-value="(v) => (zipCode = v.toString())"
|
||||
:rules="
|
||||
!addressForeign
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
|
|
@ -689,7 +801,24 @@ watchEffect(async () => {
|
|||
(v) => (typeof v === 'string' ? (streetEN = v) : '')
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="provinceTextEN"
|
||||
:dense="dense"
|
||||
label="Province"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-province-en-${indexId}` : 'input-province-en'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -732,7 +861,25 @@ watchEffect(async () => {
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="districtTextEN"
|
||||
:dense="dense"
|
||||
label="District"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-district-en-${indexId}` : 'input-district-en'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -775,7 +922,25 @@ watchEffect(async () => {
|
|||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
v-if="addressForeign"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
v-model="subDistrictTextEN"
|
||||
:dense="dense"
|
||||
label="Sub-District"
|
||||
:readonly="readonly || sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-en-${indexId}` : 'input-sub-district-en'}`"
|
||||
:rules="
|
||||
disabledRule
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-select
|
||||
v-else
|
||||
autocomplete="off"
|
||||
outlined
|
||||
clearable
|
||||
|
|
@ -819,19 +984,28 @@ watchEffect(async () => {
|
|||
</template>
|
||||
</q-select>
|
||||
<q-input
|
||||
:key="Number(addressForeign)"
|
||||
hide-bottom-space
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
readonly
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
:readonly="!addressForeign || readonly"
|
||||
:disable="!addressForeign && !readonly && !sameWithEmployer"
|
||||
zip="zip-en"
|
||||
label="Zip Code"
|
||||
class="col-md-3 col-6"
|
||||
:model-value="
|
||||
addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? ''
|
||||
!addressForeign
|
||||
? (addrOptions.subDistrictOps
|
||||
?.filter((x) => x.id === subDistrictId)
|
||||
.map((x) => x.zipCode)[0] ?? '')
|
||||
: zipCode
|
||||
"
|
||||
@update:model-value="(v) => (zipCode = v.toString())"
|
||||
:rules="
|
||||
!addressForeign
|
||||
? []
|
||||
: [(val) => (val && val.length > 0) || $t('form.error.required')]
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@ export { default as SideMenu } from './SideMenu.vue';
|
|||
export { default as StatCardComponent } from './StatCardComponent.vue';
|
||||
export { default as TooltipComponent } from './TooltipComponent.vue';
|
||||
export { default as TreeComponent } from './TreeComponent.vue';
|
||||
export { default as PaginationPageSize } from './PaginationPageSize.vue';
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ watch(
|
|||
:persistent="isDateSelect"
|
||||
>
|
||||
<div class="q-pa-sm">
|
||||
<slot name="prepend"></slot>
|
||||
<div class="text-weight-medium">
|
||||
{{ $t('general.advanceSearch') }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ defineProps<{
|
|||
history?: boolean;
|
||||
prefixId?: string;
|
||||
separateEnter?: boolean;
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -76,8 +78,10 @@ defineEmits<{
|
|||
/>
|
||||
|
||||
<KebabAction
|
||||
v-if="!hideAction"
|
||||
:id-name="prefixId"
|
||||
:status="disabled ? 'INACTIVE' : 'ACTIVE'"
|
||||
:hide-delete="hideDelete"
|
||||
@view="
|
||||
separateEnter
|
||||
? $emit('viewCard', 'INFO')
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ let defaultFilter: (
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
prefix?: string;
|
||||
id?: string;
|
||||
label?: string;
|
||||
option: T[];
|
||||
|
|
@ -71,6 +72,7 @@ watch(
|
|||
</script>
|
||||
<template>
|
||||
<q-select
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
outlined
|
||||
:clearable
|
||||
|
|
|
|||
|
|
@ -72,7 +72,11 @@ onMounted(async () => {
|
|||
:option="selectOptions"
|
||||
:hide-selected="false"
|
||||
:fill-input="false"
|
||||
:rules="[(v: string) => !!v || $t('form.error.required')]"
|
||||
:rules="[
|
||||
(v: string) => {
|
||||
return !!v?.length || $t('form.error.required');
|
||||
},
|
||||
]"
|
||||
@filter="filter"
|
||||
>
|
||||
<template #before-options v-if="creatable">
|
||||
|
|
|
|||
|
|
@ -75,9 +75,9 @@ function setDefaultValue() {
|
|||
</script>
|
||||
<template>
|
||||
<SelectInput
|
||||
for="select-hq-id"
|
||||
v-model="value"
|
||||
incremental
|
||||
id="select-hq-id"
|
||||
:label
|
||||
:placeholder
|
||||
:readonly
|
||||
|
|
|
|||
213
src/components/shared/select/SelectBusinessType.vue
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
import { createSelect, SelectProps } from './select';
|
||||
import SelectInput from '../SelectInput.vue';
|
||||
|
||||
import { BusinessType } from 'src/stores/business-type/types';
|
||||
|
||||
import useStore from 'src/stores/business-type';
|
||||
|
||||
type SelectOption = BusinessType;
|
||||
|
||||
const value = defineModel<string | null | undefined>('value', {
|
||||
required: true,
|
||||
});
|
||||
const valueOption = defineModel<SelectOption>('valueOption', {
|
||||
required: false,
|
||||
});
|
||||
|
||||
const selectOptions = ref<SelectOption[]>([]);
|
||||
|
||||
const { fetchList: getList, fetchById: getById } = useStore();
|
||||
|
||||
defineEmits<{
|
||||
(e: 'create'): void;
|
||||
}>();
|
||||
|
||||
type ExclusiveProps = {
|
||||
lang?: string;
|
||||
codeOnly?: boolean;
|
||||
selectFirstValue?: boolean;
|
||||
branchVirtual?: boolean;
|
||||
checkRole?: string[];
|
||||
};
|
||||
|
||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||
|
||||
const { getOptions, setFirstValue, getSelectedOption, filter } =
|
||||
createSelect<SelectOption>(
|
||||
{
|
||||
value,
|
||||
valueOption,
|
||||
selectOptions,
|
||||
getList: async (query) => {
|
||||
const ret = await getList({
|
||||
query,
|
||||
...props.params,
|
||||
pageSize: 99999,
|
||||
});
|
||||
if (ret) return ret.result;
|
||||
},
|
||||
getByValue: async (id) => {
|
||||
const ret = await getById(id);
|
||||
if (ret) return ret;
|
||||
},
|
||||
},
|
||||
{ valueField: 'id' },
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
await getOptions();
|
||||
|
||||
if (props.autoSelectOnSingle && selectOptions.value.length === 1) {
|
||||
setFirstValue();
|
||||
}
|
||||
|
||||
if (props.selectFirstValue) {
|
||||
setDefaultValue();
|
||||
} else await getSelectedOption();
|
||||
});
|
||||
|
||||
function setDefaultValue() {
|
||||
setFirstValue();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<SelectInput
|
||||
v-model="value"
|
||||
incremental
|
||||
option-value="id"
|
||||
:label="label || $t('menu.manage.businessType')"
|
||||
:placeholder
|
||||
:readonly
|
||||
:disable="disabled"
|
||||
:option="selectOptions"
|
||||
:hide-selected="false"
|
||||
:fill-input="false"
|
||||
:rules="[
|
||||
(v: string) => !props.required || !!v || $t('form.error.required'),
|
||||
]"
|
||||
@filter="filter"
|
||||
>
|
||||
<template #selected-item="{ opt }">
|
||||
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
|
||||
</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 }">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<span class="row items-center">
|
||||
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
|
||||
</span>
|
||||
</q-item>
|
||||
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template #append v-if="clearable">
|
||||
<q-icon
|
||||
v-if="!readonly && value"
|
||||
name="mdi-close-circle"
|
||||
@click.stop="value = ''"
|
||||
class="cursor-pointer clear-btn"
|
||||
/>
|
||||
</template>
|
||||
</SelectInput>
|
||||
</template>
|
||||
|
|
@ -30,6 +30,7 @@ defineEmits<{
|
|||
type ExclusiveProps = {
|
||||
simple?: boolean;
|
||||
simpleBranchNo?: boolean;
|
||||
selectFirstValue?: boolean;
|
||||
};
|
||||
|
||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||
|
|
@ -64,10 +65,14 @@ onMounted(async () => {
|
|||
setFirstValue();
|
||||
}
|
||||
|
||||
await getSelectedOption();
|
||||
|
||||
valueOption.value = selectOptions.value.find((v) => v.id === value.value);
|
||||
if (props.selectFirstValue) {
|
||||
setDefaultValue();
|
||||
} else await getSelectedOption();
|
||||
});
|
||||
|
||||
function setDefaultValue() {
|
||||
setFirstValue();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<SelectInput
|
||||
|
|
@ -160,11 +165,9 @@ onMounted(async () => {
|
|||
</template>
|
||||
|
||||
<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 />
|
||||
</q-item>
|
||||
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
|
||||
<template #append v-if="clearable">
|
||||
|
|
@ -177,3 +180,11 @@ onMounted(async () => {
|
|||
</template>
|
||||
</SelectInput>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bodrder {
|
||||
border-bottom: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const { getQuotationList: getList, getQuotation: getById } = useStore();
|
|||
|
||||
defineEmits<{
|
||||
(e: 'create'): void;
|
||||
(e: 'selected', value: SelectOption): void;
|
||||
}>();
|
||||
|
||||
type ExclusiveProps = {
|
||||
|
|
@ -117,6 +118,14 @@ function setDefaultValue() {
|
|||
(v: string) => !props.required || !!v || $t('form.error.required'),
|
||||
]"
|
||||
@filter="filter"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
$emit(
|
||||
'selected',
|
||||
selectOptions.find((opt) => opt.id === v),
|
||||
);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #append v-if="clearable">
|
||||
<q-icon
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ defineEmits<{
|
|||
|
||||
type ExclusiveProps = {
|
||||
selectFirstValue?: boolean;
|
||||
prefix?: string;
|
||||
};
|
||||
|
||||
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||
|
|
@ -71,6 +72,7 @@ function setDefaultValue() {
|
|||
<SelectInput
|
||||
v-model="value"
|
||||
incremental
|
||||
:id="`${prefix || 'nome'}-select-user`"
|
||||
:label
|
||||
:placeholder
|
||||
:readonly
|
||||
|
|
@ -92,7 +94,9 @@ function setDefaultValue() {
|
|||
:hide-selected="false"
|
||||
:fill-input="false"
|
||||
:rules="
|
||||
required ? [(v: string) => !!v || $t('form.error.required')] : undefined
|
||||
required && !readonly
|
||||
? [(v: string) => !!v || $t('form.error.required')]
|
||||
: undefined
|
||||
"
|
||||
@filter="filter"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,13 @@ export const createSelect = <T extends Record<string, any>>(
|
|||
let previousSearch = '';
|
||||
|
||||
watch(value, (v) => {
|
||||
if (!v || (cache && cache.find((opt) => opt[valueField] === v))) return;
|
||||
if (!v) return;
|
||||
|
||||
if (cache && cache.find((opt) => opt[valueField] === v)) {
|
||||
valueOption.value = cache.find((opt) => opt[valueField] === v);
|
||||
return;
|
||||
}
|
||||
|
||||
getSelectedOption();
|
||||
});
|
||||
|
||||
|
|
@ -63,15 +69,26 @@ export const createSelect = <T extends Record<string, any>>(
|
|||
const currentValue = value.value;
|
||||
|
||||
if (!currentValue) return;
|
||||
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
|
||||
|
||||
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);
|
||||
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,10 @@ function selectedIndex(item: any) {
|
|||
>
|
||||
<!-- NOTE: custom column will starts with # -->
|
||||
<template v-if="!col.name.startsWith('#')">
|
||||
<span>
|
||||
<span v-if="col.name === 'serviceDetail'">
|
||||
{{ props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') || '-' }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{
|
||||
typeof col.field === 'string'
|
||||
? props.row[col.field as keyof (Product | Service)]
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ function selectedIndex(item: Employee) {
|
|||
<template v-if="col.name === '#check'">
|
||||
<q-checkbox
|
||||
id="select-worker-all"
|
||||
for="select-worker-all"
|
||||
v-model="props.selected"
|
||||
@update:model-value="(v) => handleUpdate()"
|
||||
size="sm"
|
||||
|
|
@ -200,6 +201,7 @@ function selectedIndex(item: Employee) {
|
|||
v-model="props.selected"
|
||||
size="sm"
|
||||
:id="`select-worker-${props.row.firstName}`"
|
||||
:for="`select-worker-${props.row.firstName}`"
|
||||
/>
|
||||
</template>
|
||||
</q-td>
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.citizenId')"
|
||||
for="input-citizen-id"
|
||||
v-model="citizenId"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
|
|
@ -221,6 +222,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.religion')"
|
||||
for="input-religion"
|
||||
v-model="religion"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
<q-select
|
||||
outlined
|
||||
|
|
@ -305,6 +307,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.firstName')"
|
||||
for="input-first-name"
|
||||
v-model="firstName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
@ -316,6 +319,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
|
|||
:label="$t('customer.form.lastName')"
|
||||
for="input-last-name"
|
||||
v-model="lastName"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ type Props = {
|
|||
autoSave?: boolean;
|
||||
data?: Data;
|
||||
hideBtn?: boolean;
|
||||
disabledSubmit?: boolean;
|
||||
};
|
||||
|
||||
type HandleProps = {
|
||||
|
|
@ -109,6 +110,7 @@ async function change(e: Event) {
|
|||
hide-delete
|
||||
hide-btn
|
||||
edit
|
||||
:disabledSubmit
|
||||
:title
|
||||
:is-edit
|
||||
:readonly
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const currentIndexDropdownList = ref(0);
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
treeFile: { label: string; file: { label: string }[] }[];
|
||||
treeFile?: { label: string; file: { label: string }[] }[];
|
||||
readonly?: boolean;
|
||||
dropdownList?: { label: string; value: string }[];
|
||||
hideAction?: boolean;
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ onMounted(() => {
|
|||
@click="$emit('click')"
|
||||
>
|
||||
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
|
||||
<article class="col column q-pl-md">
|
||||
<span class="ellipsis full-width">
|
||||
<div class="col column q-pl-md">
|
||||
<div class="ellipsis full-width" style="max-width: 65vw !important">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="text-caption app-text-muted-2">
|
||||
{{
|
||||
uploading.loaded
|
||||
|
|
@ -79,7 +80,7 @@ onMounted(() => {
|
|||
/>
|
||||
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
|
||||
</span>
|
||||
</article>
|
||||
</div>
|
||||
<q-btn
|
||||
v-if="closeable"
|
||||
icon="mdi-close"
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ const props = withDefaults(
|
|||
readonly?: boolean;
|
||||
showTitle?: boolean;
|
||||
ocr?: (
|
||||
group: any,
|
||||
group: string,
|
||||
file: File,
|
||||
) => void | Promise<{
|
||||
) => Promise<{
|
||||
status: boolean;
|
||||
group: string;
|
||||
meta: { name: string; value: string }[];
|
||||
|
|
@ -123,7 +123,7 @@ async function change(e: Event) {
|
|||
...obj.value,
|
||||
{
|
||||
_meta: structuredClone(toRaw(selectedMenu.value)._meta || {}),
|
||||
group: selectedMenu.value?.value,
|
||||
group: selectedMenu.value?.group,
|
||||
file: renamedFile,
|
||||
},
|
||||
];
|
||||
|
|
@ -168,8 +168,8 @@ async function change(e: Event) {
|
|||
type: map['doc_type'],
|
||||
number: map['doc_number'],
|
||||
gender: map['sex'],
|
||||
firstName: map['first_name'],
|
||||
lastName: map['last_name'],
|
||||
firstName: map['last_name'],
|
||||
lastName: map['first_name'],
|
||||
issueDate: map['issue_date'],
|
||||
expireDate: map['expire_date'],
|
||||
issuePlace: map['nationality'],
|
||||
|
|
@ -327,7 +327,7 @@ defineEmits<{
|
|||
:rows="
|
||||
obj
|
||||
.filter((v) => {
|
||||
if (!autoSave && v.group !== selectedMenu?.value) {
|
||||
if (!autoSave && v.group !== selectedMenu?.group) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -198,3 +198,10 @@ i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-i
|
|||
.q-focus-helper {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
opacity: 0.6;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export default {
|
|||
save: 'Save',
|
||||
open: 'Open',
|
||||
close: 'Close',
|
||||
edit: 'Edit',
|
||||
edit: 'Edit{text}',
|
||||
cancel: 'Cancel',
|
||||
back: 'Back',
|
||||
undo: 'Undo',
|
||||
|
|
@ -31,6 +31,7 @@ export default {
|
|||
displayField: 'Display Fields',
|
||||
order: 'Order',
|
||||
name: '{msg} Name',
|
||||
nameEN: 'Name (English)',
|
||||
fullName: 'Full Name',
|
||||
detail: '{msg} Detail',
|
||||
remark: '{msg} Remark',
|
||||
|
|
@ -160,6 +161,7 @@ export default {
|
|||
documentStatus: 'Document Status',
|
||||
advanceSearch: 'Advance Search',
|
||||
totalPeople: '{meg} people',
|
||||
price: 'Price {price} Baht',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -202,12 +204,14 @@ export default {
|
|||
title: 'Manage',
|
||||
branch: 'Branch',
|
||||
personnel: 'Personnel',
|
||||
group: 'Group',
|
||||
productService: 'Product and Service',
|
||||
workflow: 'Workflow',
|
||||
property: 'Property',
|
||||
customer: 'Customer',
|
||||
mainData: 'Main Data',
|
||||
agencies: 'Agencies',
|
||||
businessType: 'Business Type',
|
||||
},
|
||||
|
||||
sales: {
|
||||
|
|
@ -337,7 +341,7 @@ export default {
|
|||
requireLength: 'Please enter {msg} character',
|
||||
branchNameField: "Only letters, numbers, or the characters . , - ' &.",
|
||||
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.',
|
||||
},
|
||||
warning: {
|
||||
|
|
@ -456,7 +460,7 @@ export default {
|
|||
regisNo: 'Registration Number',
|
||||
startDate: 'Start Date',
|
||||
retireDate: 'Retire Date',
|
||||
responsibleArea: 'Responsibel Area',
|
||||
responsibleArea: 'Responsible Area',
|
||||
discount: 'Discount Condition',
|
||||
sourceNationality: 'Source Nationality',
|
||||
importNationality: 'Import Nationality',
|
||||
|
|
@ -473,6 +477,7 @@ export default {
|
|||
blacklist: 'Black list',
|
||||
contactName: 'Contact Person',
|
||||
contactTel: 'Contact Number',
|
||||
addressForeign: 'Use foreign address',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -488,7 +493,7 @@ export default {
|
|||
},
|
||||
employer: 'Employer',
|
||||
employerLegalEntity: 'Legal Entity',
|
||||
employerNaturalPerson: 'Natrual Person',
|
||||
employerNaturalPerson: 'Natural Person',
|
||||
employerType: 'Employer Type',
|
||||
employee: 'Employee',
|
||||
form: {
|
||||
|
|
@ -498,11 +503,12 @@ export default {
|
|||
},
|
||||
|
||||
prefix: {
|
||||
mr: 'Mr.',
|
||||
mrs: 'Mrs.',
|
||||
miss: 'Miss.',
|
||||
mr: 'MR.',
|
||||
mrs: 'MRS.',
|
||||
miss: 'MISS.',
|
||||
},
|
||||
|
||||
taxpayyerNo: 'Taxpayer Identification Number',
|
||||
citizenId: 'Citizen ID',
|
||||
religion: 'Religion',
|
||||
issueDate: 'Issue Date',
|
||||
|
|
@ -618,7 +624,7 @@ export default {
|
|||
placeOfBirth: 'Place of Birth',
|
||||
countryOfbirth: 'Country of Birth',
|
||||
issueCountry: 'Issue Country',
|
||||
entryCount: 'Entry Count',
|
||||
entryCount: 'Number of Days in the Country',
|
||||
employerSelect: {
|
||||
branchName: 'Branch Name',
|
||||
customerName: 'Employer Name',
|
||||
|
|
@ -768,10 +774,13 @@ export default {
|
|||
},
|
||||
|
||||
quotation: {
|
||||
ownOnly: 'View Own Quotation Only',
|
||||
quotationDate: 'Quotation Date',
|
||||
seller: 'Seller',
|
||||
paymentChannels: 'Payment Channels',
|
||||
channelsThat: 'Channels That',
|
||||
refNo: 'Reference Number',
|
||||
bankAccount: 'Bank Account',
|
||||
bankAccountNumber: 'Bank Account Number',
|
||||
bankAccountName: 'Bank Account Name',
|
||||
inTheNameOf: 'In The Name Of',
|
||||
|
|
@ -923,6 +932,7 @@ export default {
|
|||
contactName: 'Contact Person',
|
||||
contactTel: 'Contact Number',
|
||||
bankInfo: 'Bank Information',
|
||||
attachment: 'Attachment',
|
||||
},
|
||||
|
||||
requestList: {
|
||||
|
|
@ -1117,7 +1127,7 @@ export default {
|
|||
oneOrMoreBranchMissing:
|
||||
'One or more branch cannot be delete and is missing.',
|
||||
cantMakeHQAndBranchSameTime:
|
||||
'Cannot make this as headquaters and branch at the same time.',
|
||||
'Cannot make this as headquarters and branch at the same time.',
|
||||
unknowHowToVerify: 'Unknown how to verify identity.',
|
||||
noPermission:
|
||||
'You do not have permission to access or perform with this resource.',
|
||||
|
|
@ -1224,6 +1234,9 @@ export default {
|
|||
taskListNotPending: 'One or more task is not pending.',
|
||||
reqNotMet: 'Not Match',
|
||||
systemError: 'A system error occurred.',
|
||||
taskOrderInvalid: 'Please select the product and the organization.',
|
||||
|
||||
flowAccountProductIdNotFound: 'Product not found in flow account',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -1508,4 +1521,11 @@ export default {
|
|||
last90Days: 'Last 90 Days',
|
||||
customDateRange: 'Custom Date Range',
|
||||
},
|
||||
|
||||
businessType: {
|
||||
title: 'Business Type',
|
||||
caption: 'Manage Business Type',
|
||||
name: 'Business Type Name',
|
||||
nameEn: 'Business Type Name (English)',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export default {
|
|||
save: 'บันทึก',
|
||||
open: 'เปิด',
|
||||
close: 'ปิด',
|
||||
edit: 'แก้ไข',
|
||||
edit: 'แก้ไข{text}',
|
||||
cancel: 'ยกเลิก',
|
||||
back: 'ย้อนกลับ',
|
||||
undo: 'ย้อนกลับ',
|
||||
|
|
@ -31,6 +31,7 @@ export default {
|
|||
displayField: 'ฟิลด์แสดงผล',
|
||||
order: 'ลำดับ',
|
||||
name: 'ชื่อ{msg}',
|
||||
nameEN: 'ชื่อ (ภาษาอังกฤษ)',
|
||||
fullName: 'ชื่อ-สกุล',
|
||||
detail: 'รายละเอียด{msg}',
|
||||
remark: 'หมายเหตุ{msg}',
|
||||
|
|
@ -160,6 +161,7 @@ export default {
|
|||
documentStatus: 'สถานะเอกสาร',
|
||||
advanceSearch: 'ค้นหาขั้นสูง',
|
||||
totalPeople: '{meg} คน',
|
||||
price: 'ราคา {price} บาท',
|
||||
},
|
||||
|
||||
menu: {
|
||||
|
|
@ -202,12 +204,14 @@ export default {
|
|||
title: 'จัดการ',
|
||||
branch: 'สาขา',
|
||||
personnel: 'บุคลากร',
|
||||
group: 'กลุ่ม',
|
||||
productService: 'สินค้าและบริการ',
|
||||
workflow: 'ขั้นตอนการทำงาน',
|
||||
property: 'คุณสมบัติ',
|
||||
customer: 'ลูกค้า',
|
||||
mainData: 'ข้อมูลหลัก',
|
||||
agencies: 'หน่วยงาน',
|
||||
businessType: 'ประเภทกิจการ',
|
||||
},
|
||||
|
||||
sales: {
|
||||
|
|
@ -334,7 +338,7 @@ export default {
|
|||
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
|
||||
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
|
||||
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
|
||||
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
|
||||
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
|
||||
branchNameENField:
|
||||
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
|
||||
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
|
||||
|
|
@ -469,6 +473,7 @@ export default {
|
|||
blacklist: 'แบล็คลิสต์',
|
||||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
|
||||
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -495,15 +500,16 @@ export default {
|
|||
},
|
||||
|
||||
prefix: {
|
||||
mr: 'Mr.',
|
||||
mrs: 'Mrs.',
|
||||
miss: 'Miss.',
|
||||
mr: 'นาย',
|
||||
mrs: 'นาง',
|
||||
miss: 'นางสาว',
|
||||
},
|
||||
|
||||
citizenId: 'บัตรประจำตัวประชาชน',
|
||||
religion: 'ศาสนา',
|
||||
issueDate: 'วันที่ออกหนังสือ',
|
||||
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
|
||||
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
|
||||
|
||||
ownerName: 'ชื่อนายจ้าง',
|
||||
firstName: 'ชื่อ ',
|
||||
|
|
@ -587,7 +593,7 @@ export default {
|
|||
family: 'ข้อมูลครอบครัว',
|
||||
},
|
||||
workerStatus: 'สถานะคนงาน',
|
||||
previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
|
||||
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า',
|
||||
employerBranch: 'สาขานายจ้าง',
|
||||
employeeCode: 'รหัสลูกจ้าง',
|
||||
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
|
||||
|
|
@ -620,7 +626,7 @@ export default {
|
|||
placeOfBirth: 'สถานที่เกิด',
|
||||
countryOfbirth: 'ประเทศที่เกิด',
|
||||
issueCountry: 'ประเทศที่ออก',
|
||||
entryCount: 'จำนวนที่เข้าประเทศ',
|
||||
entryCount: 'จำนวนวันที่เข้าประเทศ',
|
||||
employerSelect: {
|
||||
branchName: 'ชื่อสาขา',
|
||||
customerName: 'ชื่อนายจ้าง',
|
||||
|
|
@ -648,7 +654,7 @@ export default {
|
|||
permitIssuedAt: 'สถานที่ออกใบอนุญาต',
|
||||
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
|
||||
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
|
||||
identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
|
||||
identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)',
|
||||
},
|
||||
formFamily: {
|
||||
citizenId:
|
||||
|
|
@ -766,10 +772,13 @@ export default {
|
|||
},
|
||||
|
||||
quotation: {
|
||||
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
|
||||
quotationDate: 'วันที่ใบเสนอราคา',
|
||||
seller: 'ผู้ขาย',
|
||||
paymentChannels: 'ช่องทางชำระเงิน',
|
||||
channelsThat: 'ช่องทางที่',
|
||||
refNo: 'เลขที่อ้างอิง',
|
||||
bankAccount: 'บัญชีธนาคาร',
|
||||
bankAccountNumber: 'เลขบัญชีธนาคาร',
|
||||
bankAccountName: 'ชื่อบัญชี',
|
||||
inTheNameOf: 'ในนาม',
|
||||
|
|
@ -794,7 +803,7 @@ export default {
|
|||
branch: 'สาขาที่ออกใบเสนอราคา',
|
||||
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
|
||||
customer: 'ลูกค้า',
|
||||
newCustomer: 'ลูกค้าใหม่',
|
||||
newCustomer: 'แรงงานใหม่',
|
||||
employeeList: 'รายชื่อแรงงาน',
|
||||
employee: 'แรงงาน',
|
||||
employeeName: 'ชื่อ-นามสกุล แรงงาน',
|
||||
|
|
@ -920,6 +929,7 @@ export default {
|
|||
contactName: 'ชื่อผู้ติดต่อ',
|
||||
contactTel: 'เบอร์โทรผู้ติดต่อ',
|
||||
bankInfo: 'ข้อมูลธนาคาร',
|
||||
attachment: 'เอกสารเพิ่มเติม',
|
||||
},
|
||||
|
||||
requestList: {
|
||||
|
|
@ -1210,6 +1220,8 @@ export default {
|
|||
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
|
||||
reqNotMet: 'ไม่ตรงกัน',
|
||||
systemError: 'ระบบเกิดข้อผิดพลาด',
|
||||
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
|
||||
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -1496,4 +1508,11 @@ export default {
|
|||
last90Days: '90 วันที่ผ่านมา',
|
||||
customDateRange: 'กำหนดช่วงวันที่เอง',
|
||||
},
|
||||
|
||||
businessType: {
|
||||
title: 'ประเภทกิจการ',
|
||||
caption: 'จัดการประเภทกิจการ',
|
||||
name: 'ชื่อประเภทกิจการ',
|
||||
nameEn: 'ชื่อประเภทกิจการ (ภาษาอังกฤษ)',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import useMyBranch from 'stores/my-branch';
|
|||
import { getUserId, getRole } from 'src/services/keycloak';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { isRoleInclude } from 'src/stores/utils';
|
||||
import { canAccess } from 'src/stores/utils';
|
||||
|
||||
type Menu = {
|
||||
label: string;
|
||||
|
|
@ -71,82 +71,50 @@ function initMenu() {
|
|||
{
|
||||
label: 'branch',
|
||||
route: '/branch-management',
|
||||
hidden: !isRoleInclude([
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'branch_manager',
|
||||
'head_of_accountant',
|
||||
]),
|
||||
hidden: !canAccess('branch'),
|
||||
},
|
||||
{
|
||||
label: 'personnel',
|
||||
route: '/personnel-management',
|
||||
hidden: !isRoleInclude([
|
||||
'owner',
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'branch_manager',
|
||||
]),
|
||||
hidden: !canAccess('personnel'),
|
||||
},
|
||||
{
|
||||
label: 'group',
|
||||
route: '/group-management',
|
||||
hidden: !canAccess('personnel'),
|
||||
},
|
||||
{
|
||||
label: 'workflow',
|
||||
route: '/workflow',
|
||||
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
|
||||
hidden: !canAccess('workflow'),
|
||||
},
|
||||
{
|
||||
label: 'property',
|
||||
route: '/property',
|
||||
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
|
||||
hidden: !canAccess('workflow'),
|
||||
},
|
||||
{
|
||||
label: 'businessType',
|
||||
route: '/business-type',
|
||||
},
|
||||
{
|
||||
label: 'productService',
|
||||
route: '/product-service',
|
||||
hidden: !isRoleInclude([
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'branch_manager',
|
||||
'head_of_accountant',
|
||||
'head_of_sale',
|
||||
'sale',
|
||||
]),
|
||||
},
|
||||
{
|
||||
label: 'customer',
|
||||
route: '/customer-management',
|
||||
hidden: !isRoleInclude([
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'branch_manager',
|
||||
'head_of_accountant',
|
||||
'accountant',
|
||||
'head_of_sale',
|
||||
'sale',
|
||||
]),
|
||||
hidden: !canAccess('customer'),
|
||||
},
|
||||
{
|
||||
label: 'agencies',
|
||||
route: '/agencies-management',
|
||||
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'menu.sales',
|
||||
icon: 'mdi-store-settings-outline',
|
||||
hidden: !isRoleInclude([
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'branch_manager',
|
||||
'head_of_accountant',
|
||||
'accountant',
|
||||
'head_of_sale',
|
||||
'sale',
|
||||
]),
|
||||
children: [
|
||||
{ label: 'quotation', route: '/quotation' },
|
||||
{ label: 'invoice', route: '/invoice' },
|
||||
|
|
@ -169,16 +137,6 @@ function initMenu() {
|
|||
label: 'menu.account',
|
||||
icon: 'mdi-bank-outline',
|
||||
disabled: false,
|
||||
hidden: !isRoleInclude([
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'branch_manager',
|
||||
'head_of_accountant',
|
||||
'accountant',
|
||||
'head_of_sale',
|
||||
'sale',
|
||||
]),
|
||||
children: [
|
||||
{ label: 'receipt', route: '/receipt' },
|
||||
{ label: 'creditNote', route: '/credit-note' },
|
||||
|
|
@ -200,10 +158,13 @@ function initMenu() {
|
|||
{
|
||||
label: 'menu.overall',
|
||||
icon: 'mdi-monitor-dashboard',
|
||||
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin', 'executive']),
|
||||
children: [
|
||||
{ label: 'report', route: '/report' },
|
||||
{ label: 'dashboard', route: '/dash-board' },
|
||||
{
|
||||
label: 'dashboard',
|
||||
route: '/dash-board',
|
||||
hidden: !canAccess('dashBoard'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { ref, onMounted, computed, reactive } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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 { useI18n } from 'vue-i18n';
|
||||
import moment from 'moment';
|
||||
|
|
@ -39,7 +39,7 @@ const configStore = useConfigStore();
|
|||
const { data: notificationData } = storeToRefs(notificationStore);
|
||||
|
||||
const { visible } = storeToRefs(loaderStore);
|
||||
const { t } = useI18n({ useScope: 'global' });
|
||||
const { t, locale } = useI18n({ useScope: 'global' });
|
||||
const userStore = useUserStore();
|
||||
|
||||
const canvasModal = ref(false);
|
||||
|
|
@ -52,8 +52,14 @@ const unread = computed<number>(
|
|||
);
|
||||
const userImage = ref<string>();
|
||||
const userGender = ref('');
|
||||
const userName = ref({ th: '', en: '' });
|
||||
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: {
|
||||
value: Lang;
|
||||
label: string;
|
||||
|
|
@ -161,9 +167,14 @@ onMounted(async () => {
|
|||
if (user === 'admin') return;
|
||||
if (uid) {
|
||||
const res = await userStore.fetchById(uid);
|
||||
if (res && res.gender) {
|
||||
userGender.value = res.gender;
|
||||
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
|
||||
if (res) {
|
||||
if (res.gender) {
|
||||
userGender.value = res.gender;
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -484,6 +495,7 @@ onMounted(async () => {
|
|||
<!-- User -->
|
||||
<ProfileMenu
|
||||
id="btn-profile-menu"
|
||||
:user-name="displayName"
|
||||
@logout="doLogout"
|
||||
@edit-personal-info="console.log('edit')"
|
||||
@signature="
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const filterRole = ref<string[]>();
|
|||
defineProps<{
|
||||
userImage?: string;
|
||||
gender?: string;
|
||||
userName?: string;
|
||||
}>();
|
||||
|
||||
const inputFile = document.createElement('input');
|
||||
|
|
@ -147,9 +148,9 @@ onMounted(async () => {
|
|||
class="text-weight-bold ellipsis"
|
||||
style="max-width: 9vw"
|
||||
>
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
<q-tooltip>
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
</q-tooltip>
|
||||
</span>
|
||||
<span
|
||||
|
|
@ -234,12 +235,12 @@ onMounted(async () => {
|
|||
style="margin-top: 58px"
|
||||
>
|
||||
<span v-if="isLoggedIn()">
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
</span>
|
||||
<span v-else>{{ 'Guest' }}</span>
|
||||
<q-tooltip>
|
||||
<span v-if="isLoggedIn()">
|
||||
{{ getName() }}
|
||||
{{ userName || getName() }}
|
||||
</span>
|
||||
<span v-else>{{ 'Guest' }}</span>
|
||||
</q-tooltip>
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ import { onMounted, watch } from 'vue';
|
|||
import { useManualStore } from 'src/stores/manual';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
// NOTE: Variable
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const manualStore = useManualStore();
|
||||
const navigatorStore = useNavigator();
|
||||
const { dataManual, dataTroubleshooting } = storeToRefs(manualStore);
|
||||
|
|
@ -33,6 +34,16 @@ watch(
|
|||
if (route.name === 'Troubleshooting') {
|
||||
const res = await manualStore.getTroubleshooting();
|
||||
dataTroubleshooting.value = res ? res : [];
|
||||
if (
|
||||
res.length &&
|
||||
res.length === 1 &&
|
||||
res[0].page &&
|
||||
res[0].page.length === 1
|
||||
) {
|
||||
router.replace(
|
||||
`/troubleshooting/${res[0].category}/${res[0].page[0].name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ async function scrollTo(id: string) {
|
|||
|
||||
<template>
|
||||
<main
|
||||
class="full-height q-gutter-sm"
|
||||
:class="{ 'row reverse': $q.screen.gt.xs, column: $q.screen.xs }"
|
||||
class="full-height q-gutter-sm no-wrap"
|
||||
:class="{ column: !toc && $q.screen.lt.md, 'row reverse': $q.screen.gt.sm }"
|
||||
>
|
||||
<section
|
||||
v-if="toc"
|
||||
|
|
@ -168,7 +168,7 @@ async function scrollTo(id: string) {
|
|||
</q-list>
|
||||
</section>
|
||||
|
||||
<section v-if="!toc && $q.screen.xs">
|
||||
<section v-if="!toc && $q.screen.lt.md">
|
||||
<q-btn
|
||||
dense
|
||||
class="full-width text-capitalize"
|
||||
|
|
@ -181,7 +181,7 @@ async function scrollTo(id: string) {
|
|||
</section>
|
||||
|
||||
<section
|
||||
v-if="content || (!toc && $q.screen.xs)"
|
||||
v-if="$q.screen.gt.xs || (!toc && $q.screen.xs)"
|
||||
ref="wrapper"
|
||||
class="markdown col scroll full-height rounded"
|
||||
>
|
||||
|
|
@ -328,6 +328,14 @@ async function scrollTo(id: string) {
|
|||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.markdown :deep(h4) {
|
||||
text-align: left;
|
||||
margin-block: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.markdown :deep(video) {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,21 +94,22 @@ onMounted(async () => {
|
|||
<template>
|
||||
<main class="column full-height no-wrap">
|
||||
<div class="surface-1 col bordered rounded column">
|
||||
<div class="q-px-lg q-py-xs row items-center">
|
||||
<div class="q-py-xs row items-center" style="padding-inline: 38px">
|
||||
<q-checkbox
|
||||
size="xs"
|
||||
:model-value="selectedNoti.length === noti.length"
|
||||
class="q-px-sm"
|
||||
:model-value="noti.length > 0 && selectedNoti.length === noti.length"
|
||||
:disable="noti.length === 0"
|
||||
@click="toggleSelection('', true)"
|
||||
/>
|
||||
<q-separator vertical inset spaced="md" />
|
||||
<q-btn
|
||||
v-if="selectedNoti.length === 0"
|
||||
icon="mdi-refresh"
|
||||
rounded
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
class="app-text-muted-2 q-ml-sm q-mt-xs"
|
||||
size="sm"
|
||||
class="app-text-muted-2 q-mt-xs"
|
||||
@click="async () => await fetchNoti()"
|
||||
>
|
||||
<q-tooltip>Refresh</q-tooltip>
|
||||
|
|
@ -119,8 +120,8 @@ onMounted(async () => {
|
|||
rounded
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
class="app-text-muted-2 q-ml-sm"
|
||||
size="sm"
|
||||
class="app-text-muted-2"
|
||||
@click="async () => await deleteNoti()"
|
||||
>
|
||||
<q-tooltip>{{ $t('general.delete') }}</q-tooltip>
|
||||
|
|
@ -131,7 +132,7 @@ onMounted(async () => {
|
|||
rounded
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
size="sm"
|
||||
class="app-text-muted-2 q-mx-sm"
|
||||
@click="async () => await markAsRead()"
|
||||
>
|
||||
|
|
@ -177,7 +178,13 @@ onMounted(async () => {
|
|||
<q-badge
|
||||
rounded
|
||||
class="q-ml-md"
|
||||
style="background: hsl(var(--info-bg))"
|
||||
:color="
|
||||
(tab.value === 'all'
|
||||
? noti.length
|
||||
: noti.filter((v) => !v.read).length) > 0
|
||||
? 'info'
|
||||
: 'grey'
|
||||
"
|
||||
>
|
||||
{{
|
||||
tab.value === 'all'
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type { QTableProps, QTableSlots } from 'quasar';
|
|||
import { resetScrollBar } from 'src/stores/utils';
|
||||
import useBranchStore from 'stores/branch';
|
||||
import useFlowStore from 'stores/flow';
|
||||
import { isRoleInclude } from 'stores/utils';
|
||||
import { isRoleInclude, canAccess } from 'stores/utils';
|
||||
import {
|
||||
BranchWithChildren,
|
||||
BranchCreate,
|
||||
|
|
@ -791,6 +791,8 @@ async function onSubmit(submitSelectedItem?: boolean) {
|
|||
);
|
||||
|
||||
if (!res) return;
|
||||
|
||||
formType.value = 'view';
|
||||
formData.value.codeHeadOffice = formData.value.code = res.code;
|
||||
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
|
||||
|
||||
|
|
@ -865,7 +867,9 @@ async function onSubmit(submitSelectedItem?: boolean) {
|
|||
actionText: t('dialog.action.ok'),
|
||||
persistent: true,
|
||||
title: t('form.warning.title'),
|
||||
cancel: () => {},
|
||||
cancel: () => {
|
||||
formType.value = 'create';
|
||||
},
|
||||
action: async () => {
|
||||
await createBranch();
|
||||
},
|
||||
|
|
@ -1046,7 +1050,7 @@ watch(currentHq, () => {
|
|||
{{ $t('branch.allBranch') }}
|
||||
</div>
|
||||
<q-btn
|
||||
v-if="isRoleInclude(['head_of_admin', 'admin', 'system'])"
|
||||
v-if="isRoleInclude(['system'])"
|
||||
round
|
||||
flat
|
||||
size="md"
|
||||
|
|
@ -1070,6 +1074,7 @@ watch(currentHq, () => {
|
|||
<div class="col full-width scroll">
|
||||
<div class="q-pa-md">
|
||||
<TreeComponent
|
||||
:hide-create="!canAccess('branch', 'create')"
|
||||
v-model:nodes="treeData"
|
||||
v-model:expanded-tree="expandedTree"
|
||||
node-key="id"
|
||||
|
|
@ -1557,8 +1562,18 @@ watch(currentHq, () => {
|
|||
</q-td>
|
||||
<q-td>
|
||||
<KebabAction
|
||||
v-if="
|
||||
!currentHq.id
|
||||
? canAccess('branch', 'create')
|
||||
: true
|
||||
"
|
||||
:status="props.row.status"
|
||||
:idName="props.row.name"
|
||||
:hide-delete="
|
||||
!currentHq.id
|
||||
? !isRoleInclude(['system'])
|
||||
: !canAccess('branch', 'create')
|
||||
"
|
||||
@view="
|
||||
if (props.row.isHeadOffice) {
|
||||
triggerEdit(
|
||||
|
|
@ -1698,8 +1713,18 @@ watch(currentHq, () => {
|
|||
>
|
||||
<template v-slot:action>
|
||||
<KebabAction
|
||||
v-if="
|
||||
!currentHq.id
|
||||
? canAccess('branch', 'create')
|
||||
: true
|
||||
"
|
||||
:status="props.row.status"
|
||||
:idName="props.row.name"
|
||||
:hide-delete="
|
||||
!currentHq.id
|
||||
? !isRoleInclude(['system'])
|
||||
: !canAccess('branch', 'create')
|
||||
"
|
||||
@view="
|
||||
if (props.row.isHeadOffice) {
|
||||
triggerEdit(
|
||||
|
|
@ -2247,13 +2272,22 @@ watch(currentHq, () => {
|
|||
@click="drawerEdit()"
|
||||
type="button"
|
||||
/>
|
||||
<DeleteButton
|
||||
v-if="formType !== 'edit'"
|
||||
id="btn-info-basic-delete"
|
||||
icon-only
|
||||
@click="triggerDelete(currentEdit.id)"
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<template
|
||||
v-if="
|
||||
formType !== 'edit' && formTypeBranch === 'headOffice'
|
||||
? isRoleInclude(['system'])
|
||||
: canAccess('branch', 'create')
|
||||
"
|
||||
>
|
||||
<DeleteButton
|
||||
v-if="formType !== 'edit'"
|
||||
id="btn-info-basic-delete"
|
||||
icon-only
|
||||
@click="triggerDelete(currentEdit.id)"
|
||||
type="button"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
67
src/pages/02_group-management/MainPage.vue
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { getInstance } from 'src/services/keycloak';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { initLang } from 'src/utils/ui';
|
||||
|
||||
const $q = useQuasar();
|
||||
const { locale } = useI18n();
|
||||
const navigatorStore = useNavigator();
|
||||
const EDM_SERVICE = import.meta.env.VITE_EDM_MICRO_FRONTEND_URL;
|
||||
const kc = getInstance();
|
||||
const at = ref(kc.token);
|
||||
const rt = ref(kc.refreshToken);
|
||||
const iframe = ref<InstanceType<typeof HTMLIFrameElement>>();
|
||||
|
||||
function sendMessage() {
|
||||
iframe?.value?.contentWindow?.postMessage(
|
||||
{
|
||||
i18n: locale.value,
|
||||
darkMode: $q.dark.isActive,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initLang();
|
||||
currentLocale.value = locale.value;
|
||||
navigatorStore.current.title = 'menu.dms';
|
||||
navigatorStore.current.path = [
|
||||
{
|
||||
text: '',
|
||||
i18n: true,
|
||||
handler: () => {},
|
||||
},
|
||||
];
|
||||
sendMessage();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => kc.token,
|
||||
() => {
|
||||
at.value = kc.token;
|
||||
rt.value = kc.refreshToken;
|
||||
},
|
||||
);
|
||||
|
||||
watch([locale, $q.dark], () => {
|
||||
sendMessage();
|
||||
});
|
||||
|
||||
const currentLocale = ref(locale.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe
|
||||
ref="iframe"
|
||||
:src="`${EDM_SERVICE}/user?at=${at}&rt=${rt}&lang=${currentLocale}`"
|
||||
frameborder="0"
|
||||
class="full-width full-height rounded"
|
||||
allowtransparency="true"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -157,6 +157,14 @@ const defaultFormData = {
|
|||
contactTel: '',
|
||||
remark: '',
|
||||
agencyStatus: '',
|
||||
addressForeign: false,
|
||||
provinceText: null,
|
||||
districtText: null,
|
||||
subDistrictText: null,
|
||||
provinceTextEN: null,
|
||||
districtTextEN: null,
|
||||
subDistrictTextEN: null,
|
||||
zipCodeText: null,
|
||||
};
|
||||
|
||||
const formData = ref<UserCreate>({
|
||||
|
|
@ -209,6 +217,14 @@ const formData = ref<UserCreate>({
|
|||
contactTel: '',
|
||||
remark: '',
|
||||
agencyStatus: '',
|
||||
addressForeign: false,
|
||||
provinceText: null,
|
||||
districtText: null,
|
||||
subDistrictText: null,
|
||||
provinceTextEN: null,
|
||||
districtTextEN: null,
|
||||
subDistrictTextEN: null,
|
||||
zipCodeText: null,
|
||||
});
|
||||
|
||||
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
||||
|
|
@ -431,6 +447,28 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
...formData.value,
|
||||
checkpointEN: formData.value.checkpoint,
|
||||
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE',
|
||||
provinceId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.provinceId,
|
||||
districtId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.districtId,
|
||||
subDistrictId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.subDistrictId,
|
||||
|
||||
provinceText: formData.value.addressForeign
|
||||
? formData.value.provinceId
|
||||
: null,
|
||||
districtText: formData.value.addressForeign
|
||||
? formData.value.districtId
|
||||
: null,
|
||||
subDistrictText: formData.value.addressForeign
|
||||
? formData.value.subDistrictId
|
||||
: null,
|
||||
zipCodeText: formData.value.addressForeign
|
||||
? formData.value.zipCode
|
||||
: null,
|
||||
} as const;
|
||||
|
||||
await userStore.editById(currentUser.value.id, formDataEdit);
|
||||
|
|
@ -462,7 +500,31 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
: '';
|
||||
formData.value.checkpointEN = formData.value.checkpoint;
|
||||
const result = await userStore.create(
|
||||
formData.value,
|
||||
{
|
||||
...formData.value,
|
||||
provinceId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.provinceId,
|
||||
districtId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.districtId,
|
||||
subDistrictId: formData.value.addressForeign
|
||||
? null
|
||||
: formData.value.subDistrictId,
|
||||
|
||||
provinceText: formData.value.addressForeign
|
||||
? formData.value.provinceId
|
||||
: null,
|
||||
districtText: formData.value.addressForeign
|
||||
? formData.value.districtId
|
||||
: null,
|
||||
subDistrictText: formData.value.addressForeign
|
||||
? formData.value.subDistrictId
|
||||
: null,
|
||||
zipCodeText: formData.value.addressForeign
|
||||
? formData.value.zipCode
|
||||
: null,
|
||||
},
|
||||
onCreateImageList.value,
|
||||
);
|
||||
|
||||
|
|
@ -555,18 +617,25 @@ async function triggerChangeStatus(id: string, status: string) {
|
|||
async function assignFormData(idEdit: string) {
|
||||
if (!userData.value) return;
|
||||
const foundUser = userData.value.result.find((user) => user.id === idEdit);
|
||||
console.log(foundUser);
|
||||
|
||||
if (foundUser) {
|
||||
currentUser.value = foundUser;
|
||||
formData.value = {
|
||||
branchId: foundUser.branch[0]?.id,
|
||||
provinceId: foundUser.provinceId,
|
||||
districtId: foundUser.districtId,
|
||||
subDistrictId: foundUser.subDistrictId,
|
||||
provinceId: foundUser.addressForeign
|
||||
? foundUser.provinceText
|
||||
: foundUser.provinceId,
|
||||
districtId: foundUser.addressForeign
|
||||
? foundUser.districtText
|
||||
: foundUser.districtId,
|
||||
subDistrictId: foundUser.addressForeign
|
||||
? foundUser.subDistrictText
|
||||
: foundUser.subDistrictId,
|
||||
telephoneNo: foundUser.telephoneNo,
|
||||
email: foundUser.email,
|
||||
zipCode: foundUser.zipCode,
|
||||
zipCode: foundUser.addressForeign
|
||||
? foundUser.zipCodeText
|
||||
: foundUser.zipCode,
|
||||
gender: foundUser.gender,
|
||||
addressEN: foundUser.addressEN,
|
||||
address: foundUser.address,
|
||||
|
|
@ -600,8 +669,8 @@ async function assignFormData(idEdit: string) {
|
|||
responsibleArea: foundUser.responsibleArea,
|
||||
status: foundUser.status,
|
||||
selectedImage: foundUser.selectedImage,
|
||||
contactName: foundUser.contactName,
|
||||
contactTel: foundUser.contactTel,
|
||||
contactName: foundUser.contactName || '',
|
||||
contactTel: foundUser.contactTel || '',
|
||||
licenseExpireDate:
|
||||
(foundUser.licenseExpireDate &&
|
||||
new Date(foundUser.licenseExpireDate)) ||
|
||||
|
|
@ -620,6 +689,10 @@ async function assignFormData(idEdit: string) {
|
|||
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null,
|
||||
remark: foundUser.remark || '',
|
||||
agencyStatus: foundUser.agencyStatus || '',
|
||||
addressForeign: foundUser.addressForeign || false,
|
||||
provinceTextEN: foundUser.provinceTextEN,
|
||||
districtTextEN: foundUser.districtTextEN,
|
||||
subDistrictTextEN: foundUser.subDistrictTextEN,
|
||||
};
|
||||
|
||||
formData.value.status === 'ACTIVE' || 'CREATED'
|
||||
|
|
@ -746,7 +819,17 @@ watch(
|
|||
|
||||
watch(
|
||||
() => formData.value.userType,
|
||||
async () => {
|
||||
async (type) => {
|
||||
if (type !== 'AGENCY') {
|
||||
formData.value.addressForeign = false;
|
||||
formData.value.provinceId = null;
|
||||
formData.value.districtId = null;
|
||||
formData.value.subDistrictId = null;
|
||||
formData.value.provinceTextEN = null;
|
||||
formData.value.districtTextEN = null;
|
||||
formData.value.subDistrictTextEN = null;
|
||||
formData.value.zipCodeText = null;
|
||||
}
|
||||
if (!infoDrawerEdit.value) return;
|
||||
formData.value.registrationNo = null;
|
||||
formData.value.startDate = null;
|
||||
|
|
@ -1279,7 +1362,7 @@ watch(
|
|||
{{
|
||||
locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName}`.trim()
|
||||
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
|
||||
}}
|
||||
<q-tooltip
|
||||
anchor="bottom left"
|
||||
|
|
@ -1289,7 +1372,7 @@ watch(
|
|||
{{
|
||||
locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName}`.trim()
|
||||
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
|
||||
}}
|
||||
</q-tooltip>
|
||||
|
||||
|
|
@ -1555,9 +1638,12 @@ watch(
|
|||
hide-action
|
||||
:is-edit="infoDrawerEdit"
|
||||
:title="
|
||||
locale === 'eng'
|
||||
(currentUser.namePrefix
|
||||
? $t('customer.form.prefix.' + currentUser.namePrefix) + ' '
|
||||
: '') +
|
||||
(locale === 'eng'
|
||||
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
|
||||
: `${currentUser.firstName} ${currentUser.lastName}`
|
||||
: `${currentUser.firstName || currentUser.firstNameEN} ${currentUser.lastName || currentUser.lastNameEN}`)
|
||||
"
|
||||
v-model:drawerOpen="infoDrawer"
|
||||
:submit="() => onSubmit()"
|
||||
|
|
@ -1593,8 +1679,8 @@ watch(
|
|||
setPrefixName(
|
||||
{
|
||||
namePrefix: formData.namePrefix,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
firstName: formData.firstName || formData.firstNameEN,
|
||||
lastName: formData.lastName || formData.lastNameEN,
|
||||
firstNameEN: formData.firstNameEN,
|
||||
lastNameEN: formData.lastNameEN,
|
||||
},
|
||||
|
|
@ -1811,10 +1897,15 @@ watch(
|
|||
v-model:district-id="formData.districtId"
|
||||
v-model:sub-district-id="formData.subDistrictId"
|
||||
v-model:zip-code="formData.zipCode"
|
||||
v-model:address-foreign="formData.addressForeign"
|
||||
v-model:province-text-en="formData.provinceTextEN"
|
||||
v-model:district-text-en="formData.districtTextEN"
|
||||
v-model:sub-district-text-en="formData.subDistrictTextEN"
|
||||
:readonly="!infoDrawerEdit"
|
||||
prefix-id="drawer-info-personnel"
|
||||
:title="'personnel.form.addressInformation'"
|
||||
dense
|
||||
:use-foreign-address="formData.userType === 'AGENCY'"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<FormByType
|
||||
|
|
@ -2040,8 +2131,13 @@ watch(
|
|||
v-model:district-id="formData.districtId"
|
||||
v-model:sub-district-id="formData.subDistrictId"
|
||||
v-model:zip-code="formData.zipCode"
|
||||
v-model:address-foreign="formData.addressForeign"
|
||||
v-model:province-text-en="formData.provinceTextEN"
|
||||
v-model:district-text-en="formData.districtTextEN"
|
||||
v-model:sub-district-text-en="formData.subDistrictTextEN"
|
||||
prefix-id="drawer-info-personnel"
|
||||
dense
|
||||
:use-foreign-address="formData.userType === 'AGENCY'"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<FormByType
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { baseUrl } from 'src/stores/utils';
|
|||
import useCustomerStore from 'stores/customer';
|
||||
import useFlowStore from 'stores/flow';
|
||||
import useOptionStore from 'stores/options';
|
||||
import { dialog } from 'stores/utils';
|
||||
import { dialog, canAccess } from 'stores/utils';
|
||||
|
||||
import { Status } from 'stores/types';
|
||||
import { Employee } from 'stores/employee/types';
|
||||
|
|
@ -18,6 +18,8 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
|
|||
import { columnsEmployee } from './constant';
|
||||
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 FloatingActionButton from 'components/FloatingActionButton.vue';
|
||||
import SideMenu from 'components/SideMenu.vue';
|
||||
|
|
@ -89,6 +91,11 @@ const prop = withDefaults(
|
|||
currentCitizenId?: string;
|
||||
gender: string;
|
||||
selectedImage: string;
|
||||
fetchImageList: (
|
||||
id: string,
|
||||
selectedName: string,
|
||||
type: 'customer' | 'employee',
|
||||
) => Promise<void>;
|
||||
}>(),
|
||||
{
|
||||
color: 'green',
|
||||
|
|
@ -96,7 +103,6 @@ const prop = withDefaults(
|
|||
);
|
||||
const currentBranchEmployee = ref<string>('');
|
||||
const listEmployee = ref<Employee[]>([]);
|
||||
|
||||
const customerId = defineModel<string>('customerId', { required: true });
|
||||
|
||||
defineEmits<{
|
||||
|
|
@ -106,16 +112,6 @@ defineEmits<{
|
|||
(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 = [
|
||||
{
|
||||
name: 'branchName',
|
||||
|
|
@ -257,10 +253,6 @@ async function fetchEmployee(opts: { branchId: string; pageSize?: number }) {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchList();
|
||||
});
|
||||
|
||||
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
|
||||
await fetchList();
|
||||
});
|
||||
|
|
@ -280,12 +272,22 @@ watch(
|
|||
}
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
customerBranchFormState.value.currentCustomerId = route.params
|
||||
.customerId as string;
|
||||
await fetchList();
|
||||
|
||||
branch.value?.forEach((v) => {
|
||||
currentBtnOpen.value.push(false);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
style="z-index: 999"
|
||||
v-if="$route.name !== 'CustomerManagement'"
|
||||
v-if="$route.name !== 'CustomerManagement' && canAccess('customer', 'edit')"
|
||||
@click="openEmployerBranchForm('create')"
|
||||
hide-icon
|
||||
></FloatingActionButton>
|
||||
|
|
@ -473,7 +475,6 @@ watch(
|
|||
<q-tr
|
||||
:class="{
|
||||
'app-text-muted': props.row.status === 'INACTIVE',
|
||||
'cursor-pointer': props.row._count?.branch !== 0,
|
||||
}"
|
||||
:props="props"
|
||||
@click="$emit('viewDetail', props.row, props.rowIndex)"
|
||||
|
|
@ -547,7 +548,13 @@ watch(
|
|||
v-if="branchFieldSelected.includes('businessTypePure')"
|
||||
class="text-left"
|
||||
>
|
||||
{{ useOptionStore().mapOption(props.row.businessType) || '-' }}
|
||||
{{
|
||||
props.row.businessType
|
||||
? props.row.businessType[
|
||||
$i18n.locale === 'eng' ? 'nameEN' : 'name'
|
||||
]
|
||||
: '-'
|
||||
}}
|
||||
</q-td>
|
||||
<q-td
|
||||
v-if="branchFieldSelected.includes('totalEmployee')"
|
||||
|
|
@ -566,8 +573,6 @@ watch(
|
|||
await fetchEmployee({
|
||||
branchId: currentBranchEmployee,
|
||||
pageSize: 999,
|
||||
passport: true,
|
||||
visa: true,
|
||||
});
|
||||
|
||||
currentBtnOpen.map((v, i) => {
|
||||
|
|
@ -615,7 +620,7 @@ watch(
|
|||
<div class="text-center">
|
||||
<TableEmpoloyee
|
||||
:prefix-id="props.row.registerName || props.row.firstName"
|
||||
add-button
|
||||
:add-button="canAccess('customer', 'edit')"
|
||||
in-table
|
||||
:list-employee="listEmployee"
|
||||
:columns-employee="columnsEmployee"
|
||||
|
|
@ -638,10 +643,15 @@ watch(
|
|||
"
|
||||
@history="(item) => {}"
|
||||
@view="
|
||||
(item) => {
|
||||
async (item) => {
|
||||
employeeFormState.drawerModal = true;
|
||||
//employeeFormState.isEmployeeEdit = true;
|
||||
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.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`,
|
||||
telephone: props.row.telephoneNo,
|
||||
businessTypePure: useOptionStore().mapOption(
|
||||
props.row.businessType,
|
||||
),
|
||||
businessTypePure: props.row.businessType
|
||||
? props.row.businessType[
|
||||
$i18n.locale === 'eng' ? 'nameEN' : 'name'
|
||||
]
|
||||
: '-',
|
||||
totalEmployee: props.row._count?.employee,
|
||||
}"
|
||||
:visible-columns="branchFieldSelected"
|
||||
|
|
@ -759,7 +771,10 @@ watch(
|
|||
/>
|
||||
<DeleteButton
|
||||
icon-only
|
||||
v-if="customerBranchFormState.dialogType === 'info'"
|
||||
v-if="
|
||||
customerBranchFormState.dialogType === 'info' &&
|
||||
canAccess('customer', 'edit')
|
||||
"
|
||||
@click="
|
||||
() => {
|
||||
deleteBranchById(customerBranchFormData.id || '');
|
||||
|
|
@ -853,7 +868,6 @@ watch(
|
|||
v-model:last-name-en="customerBranchFormData.lastNameEN"
|
||||
v-model:gender="customerBranchFormData.gender"
|
||||
v-model:birth-date="customerBranchFormData.birthDate"
|
||||
v-model:customer-name="customerBranchFormData.customerName"
|
||||
v-model:legal-person-no="customerBranchFormData.legalPersonNo"
|
||||
v-model:branch-code="customerBranchFormData.code"
|
||||
v-model:register-name="customerBranchFormData.registerName"
|
||||
|
|
@ -883,15 +897,14 @@ watch(
|
|||
</div>
|
||||
<EmployerFormBusiness
|
||||
dense
|
||||
class="q-mb-xl"
|
||||
outlined
|
||||
prefix-id="employer-branch"
|
||||
: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-description="customerBranchFormData.jobDescription"
|
||||
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-text="customerBranchFormData.wageRateText"
|
||||
/>
|
||||
|
|
@ -982,7 +995,7 @@ watch(
|
|||
v-model:email="customerBranchFormData.email"
|
||||
v-model:contact-tel="customerBranchFormData.contactTel"
|
||||
v-model:office-tel="customerBranchFormData.officeTel"
|
||||
v-model:agent="customerBranchFormData.agent"
|
||||
v-model:agent-user-id="customerBranchFormData.agentUserId"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -998,6 +1011,18 @@ watch(
|
|||
/>
|
||||
</template>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
|
@ -137,7 +137,12 @@ watch(
|
|||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (registerName = v.trim()) : '')
|
||||
"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
:rules="[
|
||||
(val) => !!val || $t('form.error.required'),
|
||||
(val) =>
|
||||
/^[A-Za-z0-9ก-๙\s&.,'()-]+$/.test(val) ||
|
||||
$t('form.error.branchNameField'),
|
||||
]"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
|
|
@ -153,29 +158,15 @@ watch(
|
|||
(v) => (typeof v === 'string' ? (registerNameEN = v.trim()) : '')
|
||||
"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
val === '' ||
|
||||
/^[0-9A-Za-z\s.,]+$/.test(val) ||
|
||||
$t('form.error.letterOnly'),
|
||||
(val) => !!val || $t('form.error.required'),
|
||||
(val) =>
|
||||
/^[A-Za-z0-9\s&.,'-]+$/.test(val) ||
|
||||
$t('form.error.branchNameENField'),
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-12 col-md-5"
|
||||
:label="$t('customer.form.employerName')"
|
||||
for="input-legal-person-no"
|
||||
:model-value="customerName"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (customerName = v.trim()) : '')
|
||||
"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
|
|
@ -345,6 +336,7 @@ watch(
|
|||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
||||
"
|
||||
@clear="prefixName = ''"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
|
|
@ -393,11 +385,11 @@ watch(
|
|||
:readonly="readonly"
|
||||
:disable="!readonly"
|
||||
class="col-md-2 col-6"
|
||||
label="Title"
|
||||
label="Prefix"
|
||||
:model-value="
|
||||
readonly
|
||||
? capitalize(prefixName || '') || '-'
|
||||
: capitalize(prefixName || '')
|
||||
? prefixName.toUpperCase() || '-'
|
||||
: prefixName.toUpperCase() || ''
|
||||
"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import useOptionStore from 'stores/options';
|
||||
import SelectBranch from 'src/components/shared/select/SelectBranch.vue';
|
||||
import { isRoleInclude } from 'src/stores/utils';
|
||||
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
|
|
@ -102,7 +103,13 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
|
|||
class="col-md-6"
|
||||
:readonly
|
||||
:disabled="
|
||||
!isRoleInclude(['admin', 'system', 'head_of_admin']) && !readonly
|
||||
!isRoleInclude([
|
||||
'admin',
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'executive',
|
||||
'accountant',
|
||||
]) && !readonly
|
||||
"
|
||||
:label="$t('customer.form.registeredBranch')"
|
||||
select-first-value
|
||||
|
|
@ -136,15 +143,10 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
|
|||
for="input-tax"
|
||||
v-model="legalPersonNo"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
<SelectBusinessType
|
||||
class="col-6 col-md-3"
|
||||
:label="$t('customer.table.businessTypePure')"
|
||||
for="input-business-type"
|
||||
:model-value="optionStore.mapOption(businessType)"
|
||||
v-model:value="businessType"
|
||||
:readonly
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -173,15 +175,10 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
|
|||
:label="$t('personnel.form.citizenId')"
|
||||
for="input-citizen-id"
|
||||
/>
|
||||
<q-input
|
||||
dense
|
||||
outlined
|
||||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
<SelectBusinessType
|
||||
class="col-6 col-md-3"
|
||||
:label="$t('customer.table.businessTypePure')"
|
||||
for="input-first-name-en"
|
||||
:model-value="optionStore.mapOption(businessType)"
|
||||
v-model:value="businessType"
|
||||
:readonly
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { CustomerCreate } from 'stores/customer/types';
|
|||
import EmployerFormAbout from './EmployerFormAbout.vue';
|
||||
import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
|
||||
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 {
|
||||
FormCitizen,
|
||||
CorpFormBusinessRegistration,
|
||||
|
|
@ -51,6 +53,7 @@ withDefaults(
|
|||
actionDisabled?: boolean;
|
||||
customerType?: 'CORP' | 'PERS';
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
}>(),
|
||||
{
|
||||
hideAction: false,
|
||||
|
|
@ -81,7 +84,7 @@ withDefaults(
|
|||
/>
|
||||
<DeleteButton
|
||||
icon-only
|
||||
v-if="readonly"
|
||||
v-if="readonly && !hideDelete"
|
||||
@click="$emit('delete')"
|
||||
type="button"
|
||||
:disabled="actionDisabled"
|
||||
|
|
@ -141,7 +144,6 @@ withDefaults(
|
|||
v-model:last-name-en="item.lastNameEN"
|
||||
v-model:gender="item.gender"
|
||||
v-model:birth-date="item.birthDate"
|
||||
v-model:customer-name="item.customerName"
|
||||
v-model:legal-person-no="item.legalPersonNo"
|
||||
v-model:branch-code="item.code"
|
||||
v-model:register-name="item.registerName"
|
||||
|
|
@ -158,7 +160,7 @@ withDefaults(
|
|||
outlined
|
||||
:prefix-id="prefixId || 'employer'"
|
||||
:readonly="readonly"
|
||||
v-model:bussiness-type="item.businessType"
|
||||
v-model:business-type-id="item.businessTypeId"
|
||||
v-model:job-position="item.jobPosition"
|
||||
v-model:job-description="item.jobDescription"
|
||||
v-model:pay-date="item.payDate"
|
||||
|
|
@ -220,7 +222,6 @@ withDefaults(
|
|||
hide-action
|
||||
:ocr="
|
||||
async (group, file) => {
|
||||
console.log(group);
|
||||
if (group !== 'attachment') {
|
||||
const res = await ocrStore.sendOcr({
|
||||
file: file,
|
||||
|
|
@ -245,12 +246,20 @@ withDefaults(
|
|||
:auto-save="item.id !== ''"
|
||||
:download="
|
||||
(obj) => {
|
||||
customerStore.getFile({
|
||||
parentId: item.id || '',
|
||||
group: obj.group,
|
||||
fileId: obj._meta.id,
|
||||
download: true,
|
||||
});
|
||||
if (obj.group === 'citizen') {
|
||||
customerStore.getFile({
|
||||
parentId: item.id || '',
|
||||
group: obj.group,
|
||||
fileId: obj._meta.id,
|
||||
download: true,
|
||||
});
|
||||
} else {
|
||||
customerStore.getAttachment({
|
||||
parentId: item.id || '',
|
||||
name: obj._meta.id,
|
||||
download: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
"
|
||||
:delete-item="
|
||||
|
|
@ -279,7 +288,7 @@ withDefaults(
|
|||
_meta: any,
|
||||
file: File | undefined,
|
||||
) => {
|
||||
if (group !== 'attachment') {
|
||||
if (group === 'citizen') {
|
||||
if (file !== undefined && item.id) {
|
||||
const res = await customerStore.postMeta({
|
||||
parentId: item.id || '',
|
||||
|
|
@ -347,7 +356,6 @@ withDefaults(
|
|||
});
|
||||
|
||||
const tempValue = resMeta.map(async (v: any) => {
|
||||
console.log(v.expireDate);
|
||||
return {
|
||||
_meta: { ...v },
|
||||
name: `${group}-${dateFormat(v.expireDate)}`,
|
||||
|
|
@ -368,7 +376,6 @@ withDefaults(
|
|||
});
|
||||
|
||||
const tempValue = (res as string[]).map(async (i: any) => {
|
||||
console.log(i);
|
||||
return {
|
||||
_meta: { id: i, name: i },
|
||||
name: i || '',
|
||||
|
|
|
|||
|
|
@ -8,12 +8,28 @@ import { onMounted, watch } from 'vue';
|
|||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import ThaiBahtText from 'thai-baht-text';
|
||||
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 rawOption = ref();
|
||||
|
||||
const bussinessType = defineModel<string>('bussinessType');
|
||||
const businessTypeDialog = ref<boolean>(false);
|
||||
const formDataBusinessType = ref<{
|
||||
name: string;
|
||||
nameEN: string;
|
||||
}>({
|
||||
name: '',
|
||||
nameEN: '',
|
||||
});
|
||||
|
||||
const useBusinessType = useBusinessTypeStore();
|
||||
|
||||
const businessTypeId = defineModel<string>('businessTypeId');
|
||||
const jobPosition = defineModel<string>('jobPosition');
|
||||
const jobDescription = defineModel<string>('jobDescription');
|
||||
const payDate = defineModel<string>('payDate');
|
||||
|
|
@ -28,6 +44,8 @@ const typeBusinessENOption = ref([]);
|
|||
const jobPositionOption = ref([]);
|
||||
const jobPositionENOption = ref([]);
|
||||
|
||||
const keySelect = ref<number>(0);
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
dense?: boolean;
|
||||
|
|
@ -37,35 +55,33 @@ defineProps<{
|
|||
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 () => {
|
||||
const resultOption = await fetch('/option/option.json');
|
||||
rawOption.value = await resultOption.json();
|
||||
typeBusinessENOption.value = rawOption.value.eng.businessType;
|
||||
jobPositionENOption.value = rawOption.value.eng.position;
|
||||
|
||||
if (locale.value === 'eng') {
|
||||
typeBusinessOption.value = rawOption.value.eng.businessType;
|
||||
jobPositionOption.value = rawOption.value.eng.position;
|
||||
}
|
||||
if (locale.value === 'tha') {
|
||||
typeBusinessOption.value = rawOption.value.tha.businessType;
|
||||
jobPositionOption.value = rawOption.value.tha.position;
|
||||
}
|
||||
});
|
||||
|
||||
watch([typeBusinessOption, typeBusinessENOption], () => {
|
||||
typeBusinessFilter = selectFilterOptionRefMod(
|
||||
typeBusinessOption,
|
||||
typeBusinessOptions,
|
||||
'label',
|
||||
);
|
||||
typeBusinessENFilter = selectFilterOptionRefMod(
|
||||
typeBusinessENOption,
|
||||
typeBusinessENOptions,
|
||||
'label',
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => rate.value,
|
||||
(newValue) => {
|
||||
|
|
@ -135,69 +151,26 @@ let jobPositionENFilter = selectFilterOptionRefMod(
|
|||
<span>{{ $t('customerBranch.tab.business') }}</span>
|
||||
</div>
|
||||
|
||||
<q-select
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
:hide-dropdown-icon="readonly"
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
v-model="bussinessType"
|
||||
<SelectBusinessType
|
||||
:key="keySelect"
|
||||
class="col-md-6 col-12"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:label="$t('customer.form.businessType')"
|
||||
:options="typeBusinessOptions"
|
||||
:for="`${prefixId}-select-business-type`"
|
||||
@filter="typeBusinessFilter"
|
||||
v-model:value="businessTypeId"
|
||||
:readonly
|
||||
creatable
|
||||
lang="tha"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-select
|
||||
:for="`${prefixId}-input-bussiness-type-en`"
|
||||
:id="`${prefixId}-input-bussiness-type-en`"
|
||||
:label="`${$t('customer.form.businessType')} (EN)`"
|
||||
outlined
|
||||
clearable
|
||||
use-input
|
||||
fill-input
|
||||
emit-value
|
||||
map-options
|
||||
hide-selected
|
||||
hide-bottom-space
|
||||
:hide-dropdown-icon="readonly"
|
||||
input-debounce="0"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
v-model="bussinessType"
|
||||
@create="() => (businessTypeDialog = true)"
|
||||
/>
|
||||
<SelectBusinessType
|
||||
:key="keySelect"
|
||||
class="col-md-6 col-12"
|
||||
:dense="dense"
|
||||
:readonly="readonly"
|
||||
:options="typeBusinessENOptions"
|
||||
@filter="typeBusinessENFilter"
|
||||
v-model:value="businessTypeId"
|
||||
:readonly
|
||||
creatable
|
||||
lang="eng"
|
||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||
>
|
||||
<template v-slot:no-option>
|
||||
<q-item>
|
||||
<q-item-section class="text-grey">
|
||||
{{ $t('general.noData') }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
@create="() => (businessTypeDialog = true)"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
outlined
|
||||
|
|
@ -348,4 +321,12 @@ let jobPositionENFilter = selectFilterOptionRefMod(
|
|||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<BusinessTypeDialog
|
||||
ref="refBusinessTypeDialog"
|
||||
@close="resetFormBusinessType()"
|
||||
@submit="submitBusinessType()"
|
||||
v-model="businessTypeDialog"
|
||||
v-model:data="formDataBusinessType"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ const contactName = defineModel<string>('contactName');
|
|||
const email = defineModel<string>('email');
|
||||
const contactTel = defineModel<string>('contactTel');
|
||||
const officeTel = defineModel<string>('officeTel');
|
||||
const agent = defineModel<string>('agent');
|
||||
|
||||
const agentUserId = defineModel<string>('agentUserId');
|
||||
</script>
|
||||
|
||||
|
|
@ -109,7 +107,6 @@ const agentUserId = defineModel<string>('agentUserId');
|
|||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<SelectAgent
|
||||
:label="$t('customer.form.agent')"
|
||||
v-model:value="agentUserId"
|
||||
|
|
|
|||
|
|
@ -69,19 +69,19 @@ export const uploadFileListCustomer: {
|
|||
}[] = [
|
||||
{
|
||||
label: 'customer.typeFile.citizenId',
|
||||
value: 'citizen',
|
||||
group: 'citizen',
|
||||
value: 'citizen',
|
||||
},
|
||||
{
|
||||
label: 'customer.typeFile.registrationBook',
|
||||
value: 'attachment',
|
||||
group: 'houseRegistration',
|
||||
value: 'attachment',
|
||||
},
|
||||
|
||||
{
|
||||
label: 'customer.typeFile.houseMap',
|
||||
value: 'attachment',
|
||||
group: 'vatRegistration',
|
||||
value: 'attachment',
|
||||
},
|
||||
|
||||
{
|
||||
|
|
@ -92,7 +92,7 @@ export const uploadFileListCustomer: {
|
|||
|
||||
{
|
||||
label: 'customer.typeFile.dbdCertificate',
|
||||
group: 'powerOfAttorney',
|
||||
group: 'dbdCertificate',
|
||||
value: 'attachment',
|
||||
},
|
||||
|
||||
|
|
@ -144,43 +144,43 @@ export const uploadFileListEmployee: {
|
|||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.tm6',
|
||||
value: 'attachment',
|
||||
group: 'tm6',
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.workPermit',
|
||||
value: 'attachment',
|
||||
group: 'workPermit',
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.noticeJobEmployment',
|
||||
value: 'attachment',
|
||||
group: 'noticeJobEmployment',
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.noticeJobEntry',
|
||||
value: 'attachment',
|
||||
group: 'noticeJobEntry',
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.historyJob',
|
||||
value: 'attachment',
|
||||
group: 'historyJob',
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.acceptJob',
|
||||
value: 'attachment',
|
||||
group: 'acceptJob',
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.receipt',
|
||||
value: 'attachment',
|
||||
group: 'receipt',
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.other',
|
||||
value: 'attachment',
|
||||
group: 'other',
|
||||
value: 'attachment',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ import { useRoute } from 'vue-router';
|
|||
|
||||
export const useCustomerForm = defineStore('form-customer', () => {
|
||||
const customerStore = useCustomerStore();
|
||||
const onCreateImageList = ref<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>({ selectedImage: '', list: [] });
|
||||
|
||||
const { t } = useI18n();
|
||||
const flowStore = useFlowStore();
|
||||
|
|
@ -30,11 +34,13 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
|
||||
const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
|
||||
|
||||
const currentBranchRootId = ref<string>('');
|
||||
|
||||
const tabFieldRequired = ref<{
|
||||
[key: string]: (keyof CustomerBranchCreate)[];
|
||||
}>({
|
||||
main: [],
|
||||
business: ['businessType', 'jobPosition'],
|
||||
business: ['businessTypeId', 'jobPosition'],
|
||||
address: [
|
||||
'address',
|
||||
'addressEN',
|
||||
|
|
@ -82,6 +88,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
formDataOcr: Record<string, any>;
|
||||
isImageEdit: boolean;
|
||||
currentCustomerId?: string;
|
||||
imageList: { list: string[]; selectedImage: string };
|
||||
}>({
|
||||
dialogType: 'info',
|
||||
dialogOpen: false,
|
||||
|
|
@ -98,6 +105,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
treeFile: [],
|
||||
formDataOcr: {},
|
||||
isImageEdit: false,
|
||||
imageList: { list: [], selectedImage: '' },
|
||||
});
|
||||
|
||||
watch(
|
||||
|
|
@ -160,6 +168,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
state.value.editCustomerCode = data.code;
|
||||
state.value.customerImageUrl = `${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.status = data.status;
|
||||
|
|
@ -181,7 +190,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
payDate: v.payDate,
|
||||
jobDescription: v.jobDescription,
|
||||
jobPosition: v.jobPosition,
|
||||
businessType: v.businessType,
|
||||
businessTypeId: v.businessTypeId,
|
||||
employmentOffice: v.employmentOffice,
|
||||
employmentOfficeEN: v.employmentOfficeEN,
|
||||
telephoneNo: v.telephoneNo,
|
||||
|
|
@ -218,7 +227,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
contactTel: v.contactTel,
|
||||
officeTel: v.officeTel,
|
||||
agentUserId: v.agentUserId || undefined,
|
||||
customerName: v.customerName,
|
||||
authorizedName: v.authorizedName,
|
||||
authorizedNameEN: v.authorizedNameEN,
|
||||
|
||||
|
|
@ -256,7 +264,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
async function addCurrentCustomerBranch() {
|
||||
if (currentFormData.value.customerBranch?.some((v) => !v.id)) return;
|
||||
currentFormData.value.customerBranch?.push({
|
||||
id: '',
|
||||
customerId: '',
|
||||
branchCode:
|
||||
currentFormData.value.customerBranch.length !== 0
|
||||
|
|
@ -290,8 +297,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
birthDate:
|
||||
currentFormData.value.customerBranch?.at(0)?.birthDate || undefined,
|
||||
|
||||
businessType:
|
||||
currentFormData.value.customerBranch?.at(0)?.businessType || '',
|
||||
businessTypeId:
|
||||
currentFormData.value.customerBranch?.at(0)?.businessTypeId || '',
|
||||
jobPosition:
|
||||
currentFormData.value.customerBranch?.at(0)?.jobPosition || '',
|
||||
jobDescription:
|
||||
|
|
@ -328,8 +335,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
currentFormData.value.customerBranch?.at(0)?.agentUserId || undefined,
|
||||
status: 'CREATED',
|
||||
|
||||
customerName:
|
||||
currentFormData.value.customerBranch?.at(0)?.customerName || '',
|
||||
registerName:
|
||||
currentFormData.value.customerBranch?.at(0)?.registerName || '',
|
||||
registerNameEN:
|
||||
|
|
@ -498,11 +503,14 @@ export const useCustomerForm = defineStore('form-customer', () => {
|
|||
}
|
||||
|
||||
return {
|
||||
onCreateImageList,
|
||||
tabFieldRequired,
|
||||
registerAbleBranchOption,
|
||||
state,
|
||||
resetFormData,
|
||||
currentFormData,
|
||||
currentBranchRootId,
|
||||
|
||||
isFormDataDifferent,
|
||||
resetForm,
|
||||
assignFormData,
|
||||
|
|
@ -541,7 +549,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
gender: '',
|
||||
birthDate: undefined,
|
||||
|
||||
businessType: '',
|
||||
businessTypeId: '',
|
||||
jobPosition: '',
|
||||
jobDescription: '',
|
||||
payDate: '',
|
||||
|
|
@ -571,7 +579,6 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
agentUserId: undefined,
|
||||
status: 'CREATED',
|
||||
|
||||
customerName: '',
|
||||
registerName: '',
|
||||
registerNameEN: '',
|
||||
registerDate: undefined,
|
||||
|
|
@ -623,7 +630,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
payDateEN: _data.payDateEN,
|
||||
jobDescription: _data.jobDescription,
|
||||
jobPosition: _data.jobPosition,
|
||||
businessType: _data.businessType,
|
||||
businessTypeId: _data.businessTypeId,
|
||||
employmentOffice: _data.employmentOffice,
|
||||
employmentOfficeEN: _data.employmentOfficeEN,
|
||||
telephoneNo: _data.telephoneNo,
|
||||
|
|
@ -657,7 +664,6 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
|
|||
officeTel: _data.officeTel,
|
||||
agentUserId: _data.agentUserId || undefined,
|
||||
codeCustomer: _data.codeCustomer,
|
||||
customerName: _data.customerName,
|
||||
homeCode: _data.homeCode,
|
||||
authorizedName: _data.authorizedName,
|
||||
authorizedNameEN: _data.authorizedNameEN,
|
||||
|
|
@ -784,6 +790,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
}
|
||||
| undefined;
|
||||
ocr: boolean;
|
||||
imageList: { list: string[]; selectedImage: string };
|
||||
}>({
|
||||
currentBranchId: '',
|
||||
isImageEdit: false,
|
||||
|
|
@ -808,9 +815,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
infoEmployeePersonCard: [],
|
||||
formDataEmployeeOwner: undefined,
|
||||
ocr: false,
|
||||
imageList: { list: [], selectedImage: '' },
|
||||
});
|
||||
|
||||
const defaultFormData: EmployeeCreate = {
|
||||
const defaultFormData: EmployeeCreate & { image?: File } = {
|
||||
id: '',
|
||||
code: '',
|
||||
customerBranchId: '',
|
||||
|
|
@ -892,6 +900,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
expireDate: new Date(),
|
||||
remark: undefined,
|
||||
workerType: '',
|
||||
reportDate: null,
|
||||
number: '',
|
||||
},
|
||||
],
|
||||
|
|
@ -937,7 +946,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
};
|
||||
|
||||
let resetEmployeeData = structuredClone(defaultFormData);
|
||||
const currentFromDataEmployee = ref<EmployeeCreate>(
|
||||
const currentFromDataEmployee = ref<EmployeeCreate & { image?: File }>(
|
||||
structuredClone(defaultFormData),
|
||||
);
|
||||
|
||||
|
|
@ -959,12 +968,14 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
state.value.currentIndexVisa = -1;
|
||||
state.value.currentIndexCheckup = -1;
|
||||
state.value.currentIndexWorkHistory = -1;
|
||||
state.value.currentTab = 'personalInfo';
|
||||
state.value.imageList = { list: [], selectedImage: '' };
|
||||
// state.value.currentTab = 'personalInfo';
|
||||
if (clean) {
|
||||
state.value.formDataEmployeeOwner = undefined;
|
||||
resetEmployeeData = structuredClone(defaultFormData);
|
||||
state.value.statusSavePersonal = false;
|
||||
state.value.profileUrl = '';
|
||||
state.value.currentBranchId = '';
|
||||
} else {
|
||||
resetEmployeeData.selectedImage =
|
||||
currentFromDataEmployee.value.selectedImage;
|
||||
|
|
@ -985,12 +996,16 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
state.value.currentIndexPassport
|
||||
].id === undefined
|
||||
) {
|
||||
const { id, employeeId, updatedAt, createdAt, file, ...payload } =
|
||||
currentFromDataEmployee.value.employeePassport?.[
|
||||
state.value.currentIndexPassport
|
||||
];
|
||||
|
||||
const res = await employeeStore.postMeta({
|
||||
parentId: currentFromDataEmployee.value.id || '',
|
||||
group: 'passport',
|
||||
meta: currentFromDataEmployee.value.employeePassport?.[
|
||||
state.value.currentIndexPassport
|
||||
],
|
||||
meta: payload,
|
||||
file: file,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
|
@ -1004,7 +1019,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
state.value.currentIndexPassport
|
||||
].id !== undefined
|
||||
) {
|
||||
const { id, employeeId, updatedAt, createdAt, ...payload } =
|
||||
const { id, employeeId, updatedAt, createdAt, file, ...payload } =
|
||||
currentFromDataEmployee.value.employeePassport?.[
|
||||
state.value.currentIndexPassport
|
||||
];
|
||||
|
|
@ -1017,6 +1032,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
state.value.currentIndexPassport
|
||||
].id || '',
|
||||
meta: payload,
|
||||
file: file || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1244,7 +1260,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
await assignFormDataEmployee(currentFromDataEmployee.value.id);
|
||||
}
|
||||
|
||||
async function submitPersonal(imgList: {
|
||||
async function submitPersonal(imgList?: {
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}) {
|
||||
|
|
@ -1265,6 +1281,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
currentFromDataEmployee.value.lastNameEN.trim();
|
||||
|
||||
if (state.value.dialogType === 'create') {
|
||||
delete currentFromDataEmployee.value.image;
|
||||
const res = await employeeStore.create(
|
||||
{
|
||||
...currentFromDataEmployee.value,
|
||||
|
|
@ -1293,7 +1310,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
state.value.currentEmployee?.status === 'CREATED'
|
||||
? 'ACTIVE'
|
||||
: state.value.currentEmployee?.status,
|
||||
customerBranchId: state.value.formDataEmployeeOwner?.id || '',
|
||||
customerBranchId: state.value.currentBranchId || '',
|
||||
employeeWork: [],
|
||||
employeeCheckup: [],
|
||||
employeeOtherInfo: undefined,
|
||||
|
|
@ -1381,12 +1398,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
statusSave: true,
|
||||
})),
|
||||
),
|
||||
employeeOtherInfo: structuredClone(
|
||||
{
|
||||
...payload.employeeOtherInfo,
|
||||
statusSave: !!payload.employeeOtherInfo?.id ? true : false,
|
||||
} || {},
|
||||
),
|
||||
employeeOtherInfo: structuredClone({
|
||||
...(payload.employeeOtherInfo ?? {}),
|
||||
statusSave: true,
|
||||
}),
|
||||
employeeWork: structuredClone(
|
||||
payload.employeeWork?.length === 0
|
||||
? state.value.dialogModal
|
||||
|
|
@ -1535,6 +1550,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
expireDate: new Date(),
|
||||
remark: undefined,
|
||||
workerType: '',
|
||||
reportDate: null,
|
||||
number: '',
|
||||
});
|
||||
|
||||
|
|
@ -1659,6 +1675,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
|
|||
state,
|
||||
currentFromDataEmployee,
|
||||
resetEmployeeData,
|
||||
|
||||
addPassport,
|
||||
addVisa,
|
||||
addCheckup,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ withDefaults(
|
|||
defineProps<{
|
||||
readonly?: boolean;
|
||||
isEdit?: boolean;
|
||||
hideAction?: boolean;
|
||||
}>(),
|
||||
{ readonly: false, isEdit: false },
|
||||
);
|
||||
|
|
@ -207,7 +208,7 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
|
|||
style="position: absolute; z-index: 999; top: 0; right: 0"
|
||||
>
|
||||
<div
|
||||
v-if="flowData.status !== 'INACTIVE'"
|
||||
v-if="flowData.status !== 'INACTIVE' && !hideAction"
|
||||
class="surface-1 row rounded"
|
||||
>
|
||||
<UndoButton
|
||||
|
|
@ -287,6 +288,7 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
|
|||
>
|
||||
<template v-slot:btn-form-flow-step-drawer>
|
||||
<q-btn
|
||||
v-if="!hideAction"
|
||||
dense
|
||||
flat
|
||||
icon="mdi-plus"
|
||||
|
|
@ -315,6 +317,7 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
|
|||
<FormFlow
|
||||
:readonly
|
||||
onDrawer
|
||||
:hide-action="hideAction"
|
||||
v-model:user-in-table="userInTable"
|
||||
v-model:flow-data="flowData"
|
||||
v-model:register-branch-id="registerBranchId"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from 'src/stores/workflow-template/types';
|
||||
import { useWorkflowTemplate } from 'src/stores/workflow-template';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { dialog } from 'src/stores/utils';
|
||||
import { dialog, canAccess } from 'src/stores/utils';
|
||||
|
||||
import FloatingActionButton from 'components/FloatingActionButton.vue';
|
||||
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
||||
|
|
@ -334,6 +334,7 @@ watch(
|
|||
</script>
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
v-if="canAccess('workflow', 'edit')"
|
||||
style="z-index: 999"
|
||||
hide-icon
|
||||
@click="triggerDialog('add')"
|
||||
|
|
@ -682,6 +683,7 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="canAccess('workflow', 'edit')"
|
||||
:id-name="props.row.name"
|
||||
:status="props.row.status"
|
||||
@view="
|
||||
|
|
@ -763,7 +765,8 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
:id-name="props.row.id"
|
||||
v-if="canAccess('workflow', 'edit')"
|
||||
:id-name="props.row.name"
|
||||
:status="props.row.status"
|
||||
@view="
|
||||
() => {
|
||||
|
|
@ -846,6 +849,7 @@ watch(
|
|||
@drawer-undo="undo"
|
||||
@close="resetForm"
|
||||
@submit="submit"
|
||||
:hide-action="!canAccess('workflow', 'edit')"
|
||||
:readonly="!pageState.isDrawerEdit"
|
||||
:isEdit="pageState.isDrawerEdit"
|
||||
v-model="pageState.addModal"
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
|||
import useFlowStore from 'stores/flow';
|
||||
|
||||
import { dateFormat } from 'src/utils/datetime';
|
||||
import { formatNumberDecimal, isRoleInclude, notify } from 'stores/utils';
|
||||
import { formatNumberDecimal, isRoleInclude, canAccess } from 'stores/utils';
|
||||
const { getWorkflowTemplate } = useWorkflowTemplate();
|
||||
|
||||
import { Status } from 'stores/types';
|
||||
|
|
@ -60,6 +60,7 @@ import {
|
|||
ServiceById,
|
||||
WorkItems,
|
||||
Attributes,
|
||||
WorkCreate,
|
||||
} from 'stores/product-service/types';
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
|
|
@ -101,6 +102,8 @@ const {
|
|||
deleteWork,
|
||||
|
||||
importProduct,
|
||||
|
||||
productExport,
|
||||
} = productServiceStore;
|
||||
|
||||
const { workNameItems } = storeToRefs(productServiceStore);
|
||||
|
|
@ -142,27 +145,25 @@ const { t } = useI18n();
|
|||
const baseUrl = ref<string>(import.meta.env.VITE_API_BASE_URL);
|
||||
|
||||
const priceDisplay = computed(() => ({
|
||||
price: !isRoleInclude(['sale_agent']),
|
||||
// price: !isRoleInclude(['sale_agent']),
|
||||
price: true,
|
||||
agentPrice: isRoleInclude([
|
||||
'admin',
|
||||
'head_of_admin',
|
||||
'head_of_sale',
|
||||
'system',
|
||||
'owner',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'executive',
|
||||
'accountant',
|
||||
'sale_agent',
|
||||
'head_of_sale',
|
||||
]),
|
||||
serviceCharge: isRoleInclude([
|
||||
'admin',
|
||||
'head_of_admin',
|
||||
'system',
|
||||
'owner',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'executive',
|
||||
'accountant',
|
||||
]),
|
||||
}));
|
||||
const actionDisplay = computed(() =>
|
||||
isRoleInclude(['admin', 'head_of_admin', 'system', 'owner', 'accountant']),
|
||||
);
|
||||
const actionDisplay = computed(() => canAccess('product', 'edit'));
|
||||
const splitterModel = computed(() =>
|
||||
$q.screen.lt.md ? (productMode.value !== 'group' ? 0 : 100) : 25,
|
||||
);
|
||||
|
|
@ -1170,6 +1171,7 @@ function clearFormService() {
|
|||
profileSubmit.value = false;
|
||||
imageProduct.value = undefined;
|
||||
profileFileImg.value = null;
|
||||
serviceTab.value = 1;
|
||||
}
|
||||
|
||||
function sameFormService() {
|
||||
|
|
@ -1396,6 +1398,7 @@ function submitAddWorkProduct() {
|
|||
if (!s.hasOwnProperty('productsId')) {
|
||||
s.productsId = [];
|
||||
}
|
||||
if (s.productsId.length === 0) return;
|
||||
s.productsId.push(i.id);
|
||||
},
|
||||
);
|
||||
|
|
@ -1448,17 +1451,11 @@ function confirmDeleteWork(id: string, noDialog?: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
function triggerConfirmCloseWork() {
|
||||
function triggerConfirmCloseWorkName() {
|
||||
dialogWarningClose(t, {
|
||||
message: t('dialog.message.warningClose'),
|
||||
action: () => {
|
||||
manageWorkNameDialog.value = false;
|
||||
if (workNameItems.value[workNameItems.value.length - 1].name === '') {
|
||||
confirmDeleteWork(
|
||||
workNameItems.value[workNameItems.value.length - 1].id,
|
||||
true,
|
||||
);
|
||||
}
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
|
|
@ -1888,6 +1885,39 @@ async function copy(id: string) {
|
|||
dialogService.value = true;
|
||||
}
|
||||
|
||||
function addWorkName(data: { name: string; order: number }) {
|
||||
workNameItems.value.push({ id: '', name: data.name, isEdit: true });
|
||||
}
|
||||
|
||||
async function submitWorkName(
|
||||
workId: string,
|
||||
data: Partial<WorkCreate & { status: string }>,
|
||||
) {
|
||||
if (workNameItems.value.length === 0) return;
|
||||
|
||||
if (!workId) await createWork({ ...data, order: 1 });
|
||||
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(
|
||||
() => formService.value.attributes.workflowId,
|
||||
async (a, b) => {
|
||||
|
|
@ -2221,7 +2251,6 @@ watch(
|
|||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="row col-md-6" style="white-space: nowrap">
|
||||
<q-select
|
||||
v-show="$q.screen.gt.sm"
|
||||
|
|
@ -2456,7 +2485,10 @@ watch(
|
|||
)
|
||||
"
|
||||
>
|
||||
{{ props.row.detail || '-' }}
|
||||
{{
|
||||
props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') ||
|
||||
'-'
|
||||
}}
|
||||
</q-td>
|
||||
|
||||
<q-td
|
||||
|
|
@ -2506,6 +2538,7 @@ watch(
|
|||
/>
|
||||
|
||||
<KebabAction
|
||||
v-if="actionDisplay"
|
||||
:disable-delete="props.row.status !== 'CREATED'"
|
||||
:status="props.row.status"
|
||||
:id-name="props.row.name"
|
||||
|
|
@ -2648,7 +2681,15 @@ watch(
|
|||
{{ $t('general.recordPerPage') }}
|
||||
</div>
|
||||
<div>
|
||||
<PaginationPageSize v-model="pageSizeGroup" />
|
||||
<PaginationPageSize
|
||||
v-model="pageSizeGroup"
|
||||
:fetch-data="
|
||||
async () => {
|
||||
await fetchListGroups();
|
||||
flowStore.rotate();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2760,6 +2801,13 @@ watch(
|
|||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<SaveButton
|
||||
icon-only
|
||||
:icon="'material-symbols:download'"
|
||||
color="var(--info-bg)"
|
||||
@click.stop="triggerExport()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row col-md-6" style="white-space: nowrap">
|
||||
|
|
@ -3252,6 +3300,7 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="actionDisplay"
|
||||
:use-copy="productAndServiceTab === 'service'"
|
||||
:status="props.row.status"
|
||||
:id-name="props.row.name"
|
||||
|
|
@ -3406,7 +3455,15 @@ watch(
|
|||
{{ $t('general.recordPerPage') }}
|
||||
</div>
|
||||
<div>
|
||||
<PaginationPageSize v-model="pageSizeServiceAndProduct" />
|
||||
<PaginationPageSize
|
||||
v-model="pageSizeServiceAndProduct"
|
||||
:fetch-data="
|
||||
async () => {
|
||||
await alternativeFetch();
|
||||
flowStore.rotate();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3538,7 +3595,7 @@ watch(
|
|||
</div>
|
||||
<div
|
||||
class="col-12 col-md-10"
|
||||
id="customer-form-content"
|
||||
id="group-create"
|
||||
:class="{
|
||||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
||||
|
|
@ -4057,7 +4114,7 @@ watch(
|
|||
</div>
|
||||
<div
|
||||
class="col-12 col-md-10"
|
||||
id="customer-form-content"
|
||||
id="product-create"
|
||||
:class="{
|
||||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
'q-pa-sm': !$q.screen.gt.sm,
|
||||
|
|
@ -4283,7 +4340,7 @@ watch(
|
|||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
'q-pa-sm': !$q.screen.gt.sm,
|
||||
}"
|
||||
id="customer-form-content"
|
||||
id="product-info"
|
||||
style="height: 100%; max-height: 100%; overflow-y: auto"
|
||||
>
|
||||
<BasicInfoProduct
|
||||
|
|
@ -4328,6 +4385,7 @@ watch(
|
|||
|
||||
<!-- add service -->
|
||||
<DialogForm
|
||||
v-if="dialogService"
|
||||
hide-footer
|
||||
no-address
|
||||
no-app-box
|
||||
|
|
@ -4464,6 +4522,11 @@ watch(
|
|||
sub: true,
|
||||
}))
|
||||
"
|
||||
:active="{
|
||||
background: 'hsla(var(--blue-6-hsl) / .2)',
|
||||
foreground: 'var(--blue-6)',
|
||||
}"
|
||||
scroll-element="#service-create"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
|
|
@ -4486,7 +4549,7 @@ watch(
|
|||
</div>
|
||||
<div
|
||||
class="col-12 col-md-10"
|
||||
id="customer-form-content"
|
||||
id="service-create"
|
||||
:class="{
|
||||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
'q-pa-sm': !$q.screen.gt.sm,
|
||||
|
|
@ -4667,7 +4730,7 @@ watch(
|
|||
? workNameRef.isWorkNameEdit()
|
||||
: false;
|
||||
if (isWorkNameEdit) {
|
||||
triggerConfirmCloseWork();
|
||||
triggerConfirmCloseWorkName();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -4679,15 +4742,16 @@ watch(
|
|||
ref="workNameRef"
|
||||
v-model:name-list="workNameItems"
|
||||
@delete="confirmDeleteWork"
|
||||
@edit="editWork"
|
||||
@add="createWork"
|
||||
@edit="submitWorkName"
|
||||
@add="addWorkName"
|
||||
/>
|
||||
</div>
|
||||
</DialogForm>
|
||||
|
||||
<!-- edit service -->
|
||||
<!-- edit service edit package-->
|
||||
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
|
||||
<DialogForm
|
||||
v-if="dialogServiceEdit"
|
||||
hide-footer
|
||||
no-address
|
||||
height="95vh"
|
||||
|
|
@ -4940,6 +5004,7 @@ watch(
|
|||
background: 'hsla(var(--blue-6-hsl) / .2)',
|
||||
foreground: 'var(--blue-6)',
|
||||
}"
|
||||
scroll-element="#service-info"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
|
|
@ -4962,7 +5027,7 @@ watch(
|
|||
</div>
|
||||
<div
|
||||
class="col-12 col-md-10"
|
||||
id="customer-form-content"
|
||||
id="service-info"
|
||||
:class="{
|
||||
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
||||
'q-pa-sm': !$q.screen.gt.sm,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { FloatingActionButton, PaginationComponent } from 'src/components';
|
|||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||
import PropertyDialog from './PropertyDialog.vue';
|
||||
import { Property } from 'src/stores/property/types';
|
||||
import { dialog, toCamelCase } from 'src/stores/utils';
|
||||
import { dialog, toCamelCase, canAccess } from 'src/stores/utils';
|
||||
import CreateButton from 'src/components/AddButton.vue';
|
||||
import useOptionStore from 'stores/options';
|
||||
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||
|
|
@ -331,6 +331,7 @@ watch(
|
|||
</script>
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
v-if="canAccess('workflow', 'edit')"
|
||||
style="z-index: 999"
|
||||
hide-icon
|
||||
@click="triggerDialog('add')"
|
||||
|
|
@ -536,11 +537,19 @@ watch(
|
|||
class="col surface-2 flex items-center justify-center"
|
||||
>
|
||||
<NoData
|
||||
v-if="pageState.total !== 0 || pageState.searchDate.length > 0"
|
||||
v-if="
|
||||
pageState.total !== 0 ||
|
||||
pageState.searchDate.length > 0 ||
|
||||
!canAccess('workflow', 'edit')
|
||||
"
|
||||
:not-found="!!pageState.inputSearch"
|
||||
/>
|
||||
<CreateButton
|
||||
v-if="pageState.total === 0 && pageState.searchDate.length === 0"
|
||||
v-if="
|
||||
pageState.total === 0 &&
|
||||
pageState.searchDate.length === 0 &&
|
||||
canAccess('workflow', 'edit')
|
||||
"
|
||||
@click="triggerDialog('add')"
|
||||
label="general.add"
|
||||
:i18n-args="{ text: $t('flow.title') }"
|
||||
|
|
@ -698,6 +707,7 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="canAccess('workflow', 'edit')"
|
||||
:id-name="props.row.name"
|
||||
:status="props.row.status"
|
||||
@view="
|
||||
|
|
@ -815,6 +825,7 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="canAccess('workflow', 'edit')"
|
||||
:id-name="props.row.id"
|
||||
:status="props.row.status"
|
||||
@view="
|
||||
|
|
@ -906,6 +917,7 @@ watch(
|
|||
@drawer-undo="() => undo()"
|
||||
@close="() => resetForm()"
|
||||
@submit="() => submit()"
|
||||
:hide-action="!canAccess('workflow', 'edit')"
|
||||
:readonly="!pageState.isDrawerEdit"
|
||||
:isEdit="pageState.isDrawerEdit"
|
||||
v-model="pageState.addModal"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ withDefaults(
|
|||
defineProps<{
|
||||
readonly?: boolean;
|
||||
isEdit?: boolean;
|
||||
hideAction?: boolean;
|
||||
}>(),
|
||||
{ readonly: false, isEdit: false },
|
||||
);
|
||||
|
|
@ -151,7 +152,7 @@ defineEmits<{
|
|||
style="position: absolute; z-index: 999; top: 0; right: 0"
|
||||
>
|
||||
<div
|
||||
v-if="propertyData.status !== 'INACTIVE'"
|
||||
v-if="propertyData.status !== 'INACTIVE' && !hideAction"
|
||||
class="surface-1 row rounded"
|
||||
>
|
||||
<UndoButton
|
||||
|
|
@ -236,6 +237,7 @@ defineEmits<{
|
|||
<FormProperty
|
||||
onDrawer
|
||||
:readonly="!isEdit"
|
||||
:disable-toggle="hideAction"
|
||||
v-model:name="formProperty.name"
|
||||
v-model:name-en="formProperty.nameEN"
|
||||
v-model:type="formProperty.type"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<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 { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
|
@ -14,7 +14,8 @@ import useMyBranch from 'stores/my-branch';
|
|||
import { useQuotationForm } from './form';
|
||||
import { hslaColors } from './constants';
|
||||
import { pageTabs, columnQuotation } from './constants';
|
||||
import { toCamelCase } from 'stores/utils';
|
||||
import { toCamelCase, canAccess } from 'stores/utils';
|
||||
import { getUserId } from 'src/services/keycloak';
|
||||
|
||||
// NOTE Import Types
|
||||
import { CustomerBranchCreate, CustomerType } from 'stores/customer/types';
|
||||
|
|
@ -59,7 +60,6 @@ const flowStore = useFlowStore();
|
|||
const userBranch = useMyBranch();
|
||||
const navigatorStore = useNavigator();
|
||||
const customerStore = useCustomerStore();
|
||||
|
||||
const {
|
||||
fetchListOfOptionBranch,
|
||||
customerFormUndo,
|
||||
|
|
@ -75,6 +75,7 @@ const {
|
|||
const {
|
||||
state: customerFormState,
|
||||
currentFormData: customerFormData,
|
||||
currentBranchRootId,
|
||||
registerAbleBranchOption,
|
||||
tabFieldRequired,
|
||||
} = storeToRefs(customerFormStore);
|
||||
|
|
@ -88,6 +89,8 @@ const fieldSelectedOption = computed(() => {
|
|||
value: v.name,
|
||||
}));
|
||||
});
|
||||
|
||||
const keyAddDialog = ref<number>(0);
|
||||
const special = ref(false);
|
||||
const branchId = ref('');
|
||||
const agentPrice = ref<boolean>(false);
|
||||
|
|
@ -104,6 +107,7 @@ const pageState = reactive({
|
|||
fieldSelected: [''],
|
||||
gridView: false,
|
||||
total: 0,
|
||||
sellerId: '',
|
||||
|
||||
currentTab: 'Issued',
|
||||
addModal: false,
|
||||
|
|
@ -271,6 +275,10 @@ const customerNameInfo = computed(() => {
|
|||
return name || '-';
|
||||
});
|
||||
|
||||
function handleWindowFocus() {
|
||||
fetchQuotationList();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
pageState.gridView = $q.screen.lt.md ? true : false;
|
||||
navigatorStore.current.title = 'quotation.title';
|
||||
|
|
@ -297,6 +305,7 @@ onMounted(async () => {
|
|||
pageSize: quotationPageSize.value,
|
||||
status: 'Issued',
|
||||
urgentFirst: true,
|
||||
sellerId: pageState.sellerId || undefined,
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -307,6 +316,12 @@ onMounted(async () => {
|
|||
}
|
||||
|
||||
flowStore.rotate();
|
||||
|
||||
window.addEventListener('focus', handleWindowFocus);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('focus', handleWindowFocus);
|
||||
});
|
||||
|
||||
async function fetchQuotationList(mobileFetch?: boolean) {
|
||||
|
|
@ -331,6 +346,7 @@ async function fetchQuotationList(mobileFetch?: boolean) {
|
|||
urgentFirst: true,
|
||||
startDate: pageState.searchDate[0],
|
||||
endDate: pageState.searchDate[1],
|
||||
sellerId: pageState.sellerId || undefined,
|
||||
});
|
||||
|
||||
if (ret) {
|
||||
|
|
@ -406,6 +422,11 @@ async function storeDataLocal(id: string) {
|
|||
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
async function filterBySellerId() {
|
||||
pageState.sellerId = pageState.sellerId ? '' : getUserId();
|
||||
await fetchQuotationList();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -413,6 +434,7 @@ async function storeDataLocal(id: string) {
|
|||
hide-icon
|
||||
style="z-index: 999"
|
||||
@click.stop="triggerAddQuotationDialog"
|
||||
v-if="canAccess('quotation', 'create')"
|
||||
/>
|
||||
|
||||
<div class="column full-height no-wrap">
|
||||
|
|
@ -528,7 +550,18 @@ async function storeDataLocal(id: string) {
|
|||
</template>
|
||||
<template v-slot:append>
|
||||
<q-separator vertical inset class="q-mr-xs" />
|
||||
<AdvanceSearch v-model="pageState.searchDate" />
|
||||
<AdvanceSearch v-model="pageState.searchDate">
|
||||
<template #prepend>
|
||||
<div class="text-weight-medium q-mb-sm">
|
||||
<q-checkbox
|
||||
size="xs"
|
||||
:model-value="!!pageState.sellerId"
|
||||
@click="filterBySellerId"
|
||||
/>
|
||||
{{ $t('quotation.ownOnly') }}
|
||||
</div>
|
||||
</template>
|
||||
</AdvanceSearch>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
|
|
@ -647,12 +680,20 @@ async function storeDataLocal(id: string) {
|
|||
class="col surface-2 flex items-center justify-center"
|
||||
>
|
||||
<NoData
|
||||
v-if="pageState.inputSearch || pageState.currentTab !== 'Issued'"
|
||||
v-if="
|
||||
pageState.inputSearch ||
|
||||
!canAccess('quotation', 'create') ||
|
||||
pageState.currentTab !== 'Issued'
|
||||
"
|
||||
:not-found="!!pageState.inputSearch"
|
||||
/>
|
||||
|
||||
<CreateButton
|
||||
v-if="!pageState.inputSearch && pageState.currentTab === 'Issued'"
|
||||
v-if="
|
||||
!pageState.inputSearch &&
|
||||
pageState.currentTab === 'Issued' &&
|
||||
canAccess('quotation', 'create')
|
||||
"
|
||||
@click="triggerAddQuotationDialog"
|
||||
label="general.add"
|
||||
:i18n-args="{ text: $t('quotation.title') }"
|
||||
|
|
@ -682,6 +723,8 @@ async function storeDataLocal(id: string) {
|
|||
:visible-columns="pageState.fieldSelected"
|
||||
:grid="pageState.gridView"
|
||||
:hide-edit="pageState.currentTab !== 'Issued'"
|
||||
:hide-action="!canAccess('quotation', 'edit')"
|
||||
:hide-delete="!canAccess('quotation', 'delete')"
|
||||
:hide-btn-preview="pageState.currentTab === 'PaymentSuccess'"
|
||||
@preview="(id: any) => storeDataLocal(id)"
|
||||
@view="
|
||||
|
|
@ -707,7 +750,8 @@ async function storeDataLocal(id: string) {
|
|||
<div class="col-md-4 col-sm-6 col-12 column">
|
||||
<QuotationCard
|
||||
class="col"
|
||||
hide-kebab-delete
|
||||
:hide-action="!canAccess('quotation', 'edit')"
|
||||
:hide-kebab-delete="!canAccess('quotation', 'delete')"
|
||||
:hide-kebab-edit="!(pageState.currentTab === 'Issued')"
|
||||
:hide-preview="pageState.currentTab === 'PaymentSuccess'"
|
||||
:urgent="item.row.urgent"
|
||||
|
|
@ -733,8 +777,13 @@ async function storeDataLocal(id: string) {
|
|||
:worker-count="item.row._count.worker"
|
||||
:worker-max="item.row.workerMax || item.row._count.worker"
|
||||
:customer-name="
|
||||
item.row.customerBranch.registerName ||
|
||||
`${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}`
|
||||
item.row.customerBranch.customer.customerType === 'CORP'
|
||||
? $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="
|
||||
$i18n.locale === 'eng'
|
||||
|
|
@ -833,6 +882,7 @@ async function storeDataLocal(id: string) {
|
|||
<!-- NOTE: START - Quotation Form, Add Quotation -->
|
||||
|
||||
<DialogForm
|
||||
:key="keyAddDialog"
|
||||
:title="$t('general.add', { text: $t('quotation.title') })"
|
||||
v-model:modal="pageState.addModal"
|
||||
:submit-label="$t('general.add', { text: $t('quotation.title') })"
|
||||
|
|
@ -842,13 +892,14 @@ async function storeDataLocal(id: string) {
|
|||
:submit="
|
||||
() => {
|
||||
quotationFormState.mode = 'create';
|
||||
quotationFormData.customerBranchId = currentBranchRootId;
|
||||
triggerQuotationDialog({ statusDialog: 'create' });
|
||||
}
|
||||
"
|
||||
:close="
|
||||
() => {
|
||||
branchId = '';
|
||||
quotationFormData.customerBranchId = '';
|
||||
currentBranchRootId = '';
|
||||
}
|
||||
"
|
||||
:beforeClose="
|
||||
|
|
@ -898,7 +949,7 @@ async function storeDataLocal(id: string) {
|
|||
v-model:agent-price="agentPrice"
|
||||
v-model:branch-id="branchId"
|
||||
v-model:special="special"
|
||||
v-model:customer-branch-id="quotationFormData.customerBranchId"
|
||||
v-model:customer-branch-id="currentBranchRootId"
|
||||
@add-customer="triggerSelectTypeCustomerd()"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -967,6 +1018,7 @@ async function storeDataLocal(id: string) {
|
|||
() => {
|
||||
customerFormState.dialogModal = false;
|
||||
onCreateImageList = { selectedImage: '', list: [] };
|
||||
keyAddDialog++;
|
||||
}
|
||||
"
|
||||
>
|
||||
|
|
@ -1158,7 +1210,7 @@ async function storeDataLocal(id: string) {
|
|||
customerFormData.customerBranch[0].legalPersonNo
|
||||
"
|
||||
v-model:business-type="
|
||||
customerFormData.customerBranch[0].businessType
|
||||
customerFormData.customerBranch[0].businessTypeId
|
||||
"
|
||||
v-model:job-position="
|
||||
customerFormData.customerBranch[0].jobPosition
|
||||
|
|
@ -1239,7 +1291,6 @@ async function storeDataLocal(id: string) {
|
|||
res = await customerStore.createBranch({
|
||||
...customerFormData.customerBranch[idx],
|
||||
customerId: customerFormState.editCustomerId || '',
|
||||
id: undefined,
|
||||
});
|
||||
}
|
||||
if (res) {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { QSelect, useQuasar } from 'quasar';
|
||||
import { getUserId } from 'src/services/keycloak';
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
||||
import {
|
||||
baseUrl,
|
||||
dialogCheckData,
|
||||
dialogWarningClose,
|
||||
formatNumberDecimal,
|
||||
canAccess,
|
||||
isRoleInclude,
|
||||
} from 'stores/utils';
|
||||
import { ProductTree, quotationProductTree } from './utils';
|
||||
|
||||
|
|
@ -76,7 +79,8 @@ import { api } from 'src/boot/axios';
|
|||
import { RouterLink, useRoute } from 'vue-router';
|
||||
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
||||
import { convertTemplate } from 'src/utils/string-template';
|
||||
import { getRole } from 'src/services/keycloak';
|
||||
|
||||
import { CustomerBranch } from 'src/stores/customer';
|
||||
|
||||
type Node = {
|
||||
[key: string]: any;
|
||||
|
|
@ -92,6 +96,8 @@ type ProductGroupId = string;
|
|||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const customerBranchOption = ref<CustomerBranch>();
|
||||
|
||||
const employeeStore = useEmployeeStore();
|
||||
const route = useRoute();
|
||||
const useReceiptStore = useReceipt();
|
||||
|
|
@ -155,50 +161,7 @@ const selectedWorker = ref<
|
|||
}[];
|
||||
})[]
|
||||
>([]);
|
||||
const selectedWorkerItem = computed(() => {
|
||||
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 workerList = ref<Employee[]>([]);
|
||||
const selectedWorkerItem = ref([]);
|
||||
const firstCodePayment = ref('');
|
||||
const selectedProductGroup = ref('');
|
||||
const selectedInstallmentNo = ref<number[]>([]);
|
||||
|
|
@ -214,17 +177,6 @@ const attachmentData = ref<
|
|||
url?: string;
|
||||
}[]
|
||||
>([]);
|
||||
const hideBtnApproveInvoice = computed(() => {
|
||||
const role = getRole();
|
||||
const allowedRoles = [
|
||||
'system',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'head_of_accountant',
|
||||
'accountant',
|
||||
];
|
||||
return !role || !role.some((r) => allowedRoles.includes(r));
|
||||
});
|
||||
|
||||
const getToolbarConfig = computed(() => {
|
||||
const toolbar = [['left', 'center', 'justify'], ['toggle'], ['clip']];
|
||||
|
|
@ -244,7 +196,7 @@ function getPrice(
|
|||
) {
|
||||
if (filterHook) list = list.filter(filterHook);
|
||||
|
||||
return list.reduce(
|
||||
const value = list.reduce(
|
||||
(a, c) => {
|
||||
if (
|
||||
selectedInstallmentNo.value.length > 0 &&
|
||||
|
|
@ -254,32 +206,25 @@ function getPrice(
|
|||
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 =
|
||||
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
||||
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||
|
||||
a.totalPrice = precisionRound(a.totalPrice + price);
|
||||
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
|
||||
a.vat = calcVat ? precisionRound(a.vat + vat) : a.vat;
|
||||
const pricePerUnit =
|
||||
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
|
||||
|
||||
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
|
||||
: precisionRound(a.vatExcluded + price);
|
||||
a.finalPrice = precisionRound(
|
||||
a.totalPrice -
|
||||
a.totalDiscount +
|
||||
a.vat -
|
||||
Number(quotationFormData.value.discount || 0),
|
||||
);
|
||||
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
|
||||
|
||||
return a;
|
||||
},
|
||||
|
|
@ -291,6 +236,8 @@ function getPrice(
|
|||
finalPrice: 0,
|
||||
},
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const summaryPrice = computed(() => getPrice(productServiceList.value));
|
||||
|
|
@ -569,7 +516,7 @@ async function convertDataToFormSubmit() {
|
|||
),
|
||||
);
|
||||
|
||||
selectedWorker.value.forEach((v, i) => {
|
||||
selectedWorkerItem.value.forEach((v, i) => {
|
||||
if (v.attachment !== undefined) {
|
||||
v.attachment.forEach((value) => {
|
||||
fileItemNewWorker.value.push({
|
||||
|
|
@ -586,7 +533,7 @@ async function convertDataToFormSubmit() {
|
|||
|
||||
quotationFormData.value.worker = JSON.parse(
|
||||
JSON.stringify([
|
||||
...selectedWorker.value.map((v) => {
|
||||
...selectedWorkerItem.value.map((v) => {
|
||||
{
|
||||
return v.id;
|
||||
}
|
||||
|
|
@ -626,6 +573,7 @@ async function convertDataToFormSubmit() {
|
|||
discount: quotationFormData.value.discount,
|
||||
remark: quotationFormData.value.remark || '',
|
||||
agentPrice: agentPrice.value,
|
||||
sellerId: quotationFormData.value.sellerId,
|
||||
};
|
||||
|
||||
newWorkerList.value = [];
|
||||
|
|
@ -728,19 +676,13 @@ function handleUpdateProductTable(
|
|||
// handleChangePayType(quotationFormData.value.payCondition);
|
||||
// calc price
|
||||
const calc = (c: QuotationPayload['productServiceList'][number]) => {
|
||||
const originalPrice = c.pricePerUnit || 0;
|
||||
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 = c.product.calcVat
|
||||
? (finalPriceNoVat * c.amount - (c.discount || 0)) *
|
||||
(config.value?.vat || 0.07)
|
||||
: 0;
|
||||
return precisionRound(price + vat);
|
||||
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);
|
||||
const price = pricePerUnit * c.amount * (1 + vatFactor) - c.discount;
|
||||
return precisionRound(price);
|
||||
};
|
||||
|
||||
// installment
|
||||
|
|
@ -793,21 +735,16 @@ function toggleDeleteProduct(index: number) {
|
|||
|
||||
// cal curr amount
|
||||
if (currPaySplit && currTempPaySplit) {
|
||||
const price = agentPrice.value
|
||||
? currProduct.product.agentPrice
|
||||
: currProduct.product.price;
|
||||
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);
|
||||
const calcVat =
|
||||
currProduct.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
|
||||
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 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;
|
||||
}
|
||||
|
||||
|
|
@ -829,7 +766,40 @@ function toggleDeleteProduct(index: number) {
|
|||
}
|
||||
|
||||
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[]) {
|
||||
|
|
@ -862,8 +832,7 @@ function convertToTable(nodes: Node[]) {
|
|||
tempTableProduct.value = JSON.parse(JSON.stringify(list));
|
||||
|
||||
if (nodes.length > 0) {
|
||||
quotationFormData.value.paySplit = Array.apply(
|
||||
null,
|
||||
quotationFormData.value.paySplit = Array.from(
|
||||
new Array(quotationFormData.value.paySplitCount),
|
||||
).map((_, i) => ({
|
||||
no: i + 1,
|
||||
|
|
@ -889,21 +858,21 @@ function convertToTable(nodes: Node[]) {
|
|||
|
||||
function convertEmployeeToTable(selected: Employee[]) {
|
||||
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 + selected.length - selectedWorker.value.length,
|
||||
v.amount + selected.length - selectedWorkerItem.value.length,
|
||||
1,
|
||||
);
|
||||
|
||||
const oldWorkerId: string[] = [];
|
||||
const newWorkerIndex: number[] = [];
|
||||
|
||||
selectedWorker.value.forEach((item, i) => {
|
||||
selectedWorkerItem.value.forEach((item, i) => {
|
||||
if (v.workerIndex.includes(i)) oldWorkerId.push(item.id);
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
||||
|
|
@ -916,7 +885,7 @@ function convertEmployeeToTable(selected: Employee[]) {
|
|||
pageState.employeeModal = false;
|
||||
quotationFormData.value.workerMax = Math.max(
|
||||
quotationFormData.value.workerMax || 1,
|
||||
selectedWorker.value.length,
|
||||
selectedWorkerItem.value.length,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -989,6 +958,71 @@ function viewProductFile(data: ProductRelation) {
|
|||
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>>();
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
@ -1025,6 +1059,7 @@ onMounted(async () => {
|
|||
quotationFormData.value.customerBranchId = parsed.customerBranchId;
|
||||
currentQuotationId.value = parsed.quotationId;
|
||||
agentPrice.value = parsed.agentPrice;
|
||||
quotationFormData.value.sellerId = getUserId();
|
||||
await fetchQuotation();
|
||||
await assignWorkerToSelectedWorker();
|
||||
sessionData.value = parsed;
|
||||
|
|
@ -1059,12 +1094,7 @@ watch(
|
|||
() => quotationFormData.value.customerBranchId,
|
||||
async (v) => {
|
||||
if (!v) return;
|
||||
|
||||
const retEmp = await customerStore.fetchBranchEmployee(v, {
|
||||
passport: true,
|
||||
});
|
||||
|
||||
if (retEmp) workerList.value = retEmp.data.result;
|
||||
selectedWorkerItem.value = [];
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -1080,6 +1110,15 @@ watch(
|
|||
|
||||
const productServiceNodes = ref<ProductTree>([]);
|
||||
|
||||
watch(customerBranchOption, () => {
|
||||
if (!customerBranchOption.value) return;
|
||||
|
||||
quotationFormData.value.contactName =
|
||||
customerBranchOption.value.contactName || '';
|
||||
quotationFormData.value.contactTel =
|
||||
customerBranchOption.value.contactTel || '';
|
||||
});
|
||||
|
||||
watch(
|
||||
() => productServiceList.value,
|
||||
() => {
|
||||
|
|
@ -1087,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) {
|
||||
// let query: string | undefined = text;
|
||||
// let pageSize = 50;
|
||||
|
|
@ -1108,7 +1154,19 @@ watch(
|
|||
// }
|
||||
|
||||
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(
|
||||
'quotation-preview',
|
||||
|
|
@ -1117,7 +1175,7 @@ function storeDataLocal() {
|
|||
codeInvoice: code.value,
|
||||
codePayment: firstCodePayment.value,
|
||||
...quotationFormData.value,
|
||||
productServiceList: productService.value,
|
||||
productServiceList: tempProductService,
|
||||
},
|
||||
meta: {
|
||||
source: {
|
||||
|
|
@ -1137,7 +1195,7 @@ function storeDataLocal() {
|
|||
workName: quotationFormData.value.workName,
|
||||
dueDate: quotationFormData.value.dueDate,
|
||||
},
|
||||
selectedWorker: selectedWorker.value,
|
||||
selectedWorker: selectedWorkerItem.value,
|
||||
createdBy: quotationFormState.value.createdBy('tha'),
|
||||
agentPrice: agentPrice.value,
|
||||
},
|
||||
|
|
@ -1222,10 +1280,10 @@ async function getWorkerFromCriteria(
|
|||
if (!ret) return false; // error, do not close dialog
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -1515,11 +1573,13 @@ function covertToNode() {
|
|||
:quotation-status="
|
||||
quotationFormState.source?.quotationStatus === 'Expired'
|
||||
"
|
||||
:created-at="quotationFormState.createdAt"
|
||||
v-model:urgent="quotationFormData.urgent"
|
||||
v-model:work-name="quotationFormData.workName"
|
||||
v-model:contactor="quotationFormData.contactName"
|
||||
v-model:telephone="quotationFormData.contactTel"
|
||||
v-model:due-date="quotationFormData.dueDate"
|
||||
v-model:seller-id="quotationFormData.sellerId"
|
||||
>
|
||||
<template #issue-info>
|
||||
<FormAbout
|
||||
|
|
@ -1530,6 +1590,7 @@ function covertToNode() {
|
|||
v-model:customer-branch-id="
|
||||
quotationFormData.customerBranchId
|
||||
"
|
||||
v-model:customer-branch-option="customerBranchOption"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -1574,7 +1635,7 @@ function covertToNode() {
|
|||
}}
|
||||
</template>
|
||||
</div>
|
||||
<nav class="q-ml-auto">
|
||||
<nav v-if="canAccess('quotation', 'edit')" class="q-ml-auto">
|
||||
<AddButton
|
||||
id="btn-add-worker"
|
||||
for="btn-add-worker"
|
||||
|
|
@ -1612,15 +1673,15 @@ function covertToNode() {
|
|||
(v) =>
|
||||
(quotationFormData.workerMax = Math.max(
|
||||
v,
|
||||
selectedWorker.length,
|
||||
selectedWorkerItem.length,
|
||||
))
|
||||
"
|
||||
:employee-amount="
|
||||
quotationFormData.workerMax || selectedWorker.length
|
||||
quotationFormData.workerMax || selectedWorkerItem.length
|
||||
"
|
||||
:readonly="readonly"
|
||||
:rows="selectedWorkerItem"
|
||||
@delete="(i) => deleteItem(selectedWorker, i)"
|
||||
@delete="(i) => deleteItem(selectedWorkerItem, i)"
|
||||
/>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
|
|
@ -1751,7 +1812,9 @@ function covertToNode() {
|
|||
:readonly="
|
||||
{
|
||||
quotation: quotationFormState.mode !== 'edit',
|
||||
invoice: false,
|
||||
invoice:
|
||||
isRoleInclude(['sale', 'head_of_sale']) ||
|
||||
!canAccess('quotation', 'edit'),
|
||||
accepted: true,
|
||||
}[view]
|
||||
"
|
||||
|
|
@ -1862,10 +1925,10 @@ function covertToNode() {
|
|||
installments: quotationFormData.paySplit,
|
||||
},
|
||||
'quotation-labor': {
|
||||
name: selectedWorker.map(
|
||||
name: selectedWorkerItem.map(
|
||||
(v, i) =>
|
||||
`${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(),
|
||||
),
|
||||
},
|
||||
})
|
||||
|
|
@ -1955,6 +2018,11 @@ function covertToNode() {
|
|||
view !== View.Receipt &&
|
||||
view !== View.Complete
|
||||
"
|
||||
:branch-id="quotationFull.registeredBranchId"
|
||||
:readonly="
|
||||
isRoleInclude(['sale', 'head_of_sale']) ||
|
||||
!canAccess('quotation', 'edit')
|
||||
"
|
||||
:data="quotationFormState.source"
|
||||
v-model:first-code-payment="firstCodePayment"
|
||||
@fetch-status="
|
||||
|
|
@ -2093,7 +2161,14 @@ function covertToNode() {
|
|||
:style="`background-color:hsla(var(--info-bg) / 0.07)`"
|
||||
>
|
||||
<q-th auto-width>
|
||||
<q-checkbox v-model="props.selected" />
|
||||
<q-checkbox
|
||||
v-if="
|
||||
!quotationFormData.paySplit.every(
|
||||
(p) => p.invoiceId,
|
||||
)
|
||||
"
|
||||
v-model="props.selected"
|
||||
/>
|
||||
</q-th>
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
|
|
@ -2123,8 +2198,6 @@ function covertToNode() {
|
|||
|
||||
installmentAmount = props.row.amount;
|
||||
view = View.Invoice;
|
||||
|
||||
console.log(code);
|
||||
}
|
||||
}
|
||||
"
|
||||
|
|
@ -2286,6 +2359,7 @@ function covertToNode() {
|
|||
class="q-ml-sm"
|
||||
v-if="
|
||||
view === View.Accepted &&
|
||||
canAccess('quotation', 'edit') &&
|
||||
quotationFormData.quotationStatus === 'Issued'
|
||||
"
|
||||
>
|
||||
|
|
@ -2304,9 +2378,15 @@ function covertToNode() {
|
|||
</MainButton>
|
||||
</div>
|
||||
|
||||
<template v-if="view === View.InvoicePre">
|
||||
<template
|
||||
v-if="
|
||||
view === View.InvoicePre &&
|
||||
!quotationFormData.paySplit.every((p) => p.invoiceId)
|
||||
"
|
||||
>
|
||||
<MainButton
|
||||
solid
|
||||
:disabled="selectedInstallment.length === 0"
|
||||
icon="mdi-account-multiple-check-outline"
|
||||
class="q-ml-sm"
|
||||
color="207 96% 32%"
|
||||
|
|
@ -2325,6 +2405,7 @@ function covertToNode() {
|
|||
class="q-ml-sm"
|
||||
v-if="
|
||||
view === View.Invoice &&
|
||||
canAccess('quotation', 'edit') &&
|
||||
((quotationFormData.quotationStatus !== 'PaymentPending' &&
|
||||
quotationFormData.payCondition !== 'Full') ||
|
||||
quotationFormData.quotationStatus === 'Accepted') &&
|
||||
|
|
@ -2332,7 +2413,6 @@ function covertToNode() {
|
|||
"
|
||||
>
|
||||
<MainButton
|
||||
v-if="!hideBtnApproveInvoice"
|
||||
solid
|
||||
icon="mdi-account-multiple-check-outline"
|
||||
color="207 96% 32%"
|
||||
|
|
@ -2352,6 +2432,7 @@ function covertToNode() {
|
|||
style="gap: var(--size-2)"
|
||||
v-if="
|
||||
(view === View.Quotation &&
|
||||
canAccess('quotation', 'edit') &&
|
||||
(quotationFormData.quotationStatus === 'Issued' ||
|
||||
quotationFormData.quotationStatus === 'Expired')) ||
|
||||
!quotationFormData.quotationStatus
|
||||
|
|
@ -2388,13 +2469,12 @@ function covertToNode() {
|
|||
<!-- add employee quotation -->
|
||||
|
||||
<QuotationFormWorkerSelect
|
||||
:preselect-worker="selectedWorker"
|
||||
:preselect-worker="selectedWorkerItem"
|
||||
:customerBranchId="quotationFormData.customerBranchId"
|
||||
v-model:open="pageState.employeeModal"
|
||||
v-model:new-worker-list="newWorkerList"
|
||||
@success="
|
||||
(v) => {
|
||||
selectedWorker = v.worker;
|
||||
combineWorker(v.newWorker, v.worker);
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
|
@ -2437,7 +2517,7 @@ function covertToNode() {
|
|||
<!-- add Worker -->
|
||||
<QuotationFormWorkerAddDialog
|
||||
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"
|
||||
:quotation-id="quotationFormState.source.id"
|
||||
:customer-branch-id="quotationFormState.source.customerBranchId"
|
||||
|
|
@ -2531,8 +2611,8 @@ function covertToNode() {
|
|||
}
|
||||
|
||||
:deep(
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
|
||||
|
|
@ -2549,9 +2629,9 @@ function covertToNode() {
|
|||
}
|
||||
|
||||
:deep(
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
|
||||
.q-focus-helper
|
||||
) {
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
|
||||
.q-focus-helper
|
||||
) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,14 +234,14 @@ watch(
|
|||
<section class="row q-col-gutter-sm col-12 items-center">
|
||||
<SelectInput
|
||||
class="col-md-6 col-12"
|
||||
id="select-pay-type"
|
||||
:label="$t('quotation.payType')"
|
||||
:option="
|
||||
taskOrder
|
||||
? payTypeOption.filter((v) => v.value === 'Full')
|
||||
: payTypeOption
|
||||
"
|
||||
:readonly
|
||||
id="pay-type"
|
||||
:readonly="readonly || debitNote"
|
||||
:model-value="payType"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
|
|
@ -275,6 +275,7 @@ watch(
|
|||
</div>
|
||||
<q-input
|
||||
v-model="paySplitCount"
|
||||
id="input-pay-split-count"
|
||||
:readonly="readonly || payType === 'Split'"
|
||||
class="col-3"
|
||||
type="number"
|
||||
|
|
@ -311,6 +312,7 @@ watch(
|
|||
<q-input
|
||||
:readonly="readonly"
|
||||
:label="$t('general.name')"
|
||||
:id="`input-period-name-${i}`"
|
||||
v-if="payType === 'SplitCustom'"
|
||||
v-model="period.name"
|
||||
class="col q-mx-sm"
|
||||
|
|
@ -320,6 +322,7 @@ watch(
|
|||
<q-input
|
||||
:readonly="readonly || payType === 'Split'"
|
||||
class="col q-mx-sm"
|
||||
:id="`input-period-amount-${i}`"
|
||||
:label="$t('quotation.amount')"
|
||||
:model-value="
|
||||
amount4Show[i] || commaInput(period.amount.toString())
|
||||
|
|
@ -377,6 +380,7 @@ watch(
|
|||
|
||||
<DatePicker
|
||||
v-if="payType === 'BillFull'"
|
||||
id="datepicker-bill-date"
|
||||
:readonly
|
||||
class="col-12"
|
||||
:label="$t('quotation.callDueDate')"
|
||||
|
|
@ -446,7 +450,9 @@ watch(
|
|||
<span class="q-ml-auto">
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
summaryPrice.totalPrice - summaryPrice.totalDiscount,
|
||||
summaryPrice.totalPrice -
|
||||
summaryPrice.totalDiscount -
|
||||
summaryPrice.vatExcluded,
|
||||
2,
|
||||
)
|
||||
}}
|
||||
|
|
@ -482,7 +488,11 @@ watch(
|
|||
<div class="q-pa-sm row surface-2 items-center text-weight-bold">
|
||||
{{ $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
|
||||
? formatNumberDecimal(Math.max(installmentAmount || 0, 0), 2) || 0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import DatePicker from 'src/components/shared/DatePicker.vue';
|
||||
import SelectUser from 'src/components/shared/select/SelectUser.vue';
|
||||
|
||||
defineProps<{
|
||||
readonly: boolean;
|
||||
|
|
@ -13,6 +14,7 @@ const contactor = defineModel<string>('contactor', { required: true });
|
|||
const telephone = defineModel<string>('telephone', { required: true });
|
||||
const dueDate = defineModel<Date | string>('dueDate', { required: true });
|
||||
const createdAt = defineModel<Date | string>('createdAt');
|
||||
const sellerId = defineModel<string>('sellerId', { required: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -95,5 +97,11 @@ const createdAt = defineModel<Date | string>('createdAt');
|
|||
dense
|
||||
outlined
|
||||
/>
|
||||
<SelectUser
|
||||
:label="$t('preview.seller')"
|
||||
v-model:value="sellerId"
|
||||
:readonly
|
||||
class="col-12 col-md-2"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ const emit = defineEmits<{
|
|||
const selectedProductGroup = defineModel<string>('selectedProductGroup', {
|
||||
default: '',
|
||||
});
|
||||
|
||||
const selectedProductGroupOption = ref<ProductGroup | undefined>();
|
||||
const model = defineModel<boolean>();
|
||||
const inputSearch = defineModel<string>('inputSearch');
|
||||
const productGroup = defineModel<ProductGroup[]>('productGroup', {
|
||||
|
|
@ -66,21 +68,21 @@ const serviceList = defineModel<Partial<Record<ProductGroupId, Service[]>>>(
|
|||
);
|
||||
|
||||
const priceDisplay = computed(() => ({
|
||||
price: !isRoleInclude(['sale_agent']),
|
||||
// price: !isRoleInclude(['sale_agent']),
|
||||
price: true,
|
||||
agentPrice: isRoleInclude([
|
||||
'admin',
|
||||
'head_of_admin',
|
||||
'head_of_sale',
|
||||
'system',
|
||||
'owner',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'executive',
|
||||
'accountant',
|
||||
'sale_agent',
|
||||
'head_of_sale',
|
||||
]),
|
||||
serviceCharge: isRoleInclude([
|
||||
'admin',
|
||||
'head_of_admin',
|
||||
'system',
|
||||
'owner',
|
||||
'head_of_admin',
|
||||
'admin',
|
||||
'executive',
|
||||
'accountant',
|
||||
]),
|
||||
}));
|
||||
|
|
@ -569,14 +571,18 @@ watch(
|
|||
{{
|
||||
productGroup.find(
|
||||
(g) => g.id === selectedProductGroup,
|
||||
)?.name || '-'
|
||||
)?.name ||
|
||||
selectedProductGroupOption?.name ||
|
||||
'-'
|
||||
}}
|
||||
</span>
|
||||
<span class="text-caption app-text-muted">
|
||||
{{
|
||||
productGroup.find(
|
||||
(g) => g.id === selectedProductGroup,
|
||||
)?.code || '-'
|
||||
)?.code ||
|
||||
selectedProductGroupOption?.code ||
|
||||
'-'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -862,13 +868,13 @@ watch(
|
|||
<span class="q-pr-sm">
|
||||
{{ $t('productService.group.title') }}
|
||||
</span>
|
||||
|
||||
<SelectProductGroup
|
||||
class="col-md-4 col-12"
|
||||
:class="{ 'q-mb-sm': $q.screen.lt.md }"
|
||||
id="product-group-select"
|
||||
clearable
|
||||
v-model:value="selectedProductGroup"
|
||||
v-model:value-option="selectedProductGroupOption"
|
||||
:placeholder="
|
||||
!selectedProductGroup
|
||||
? $t('general.select', {
|
||||
|
|
|
|||
|
|
@ -341,12 +341,13 @@ watch(() => state.search, getWorkerList);
|
|||
>
|
||||
<div
|
||||
style="display: inline-block; margin-inline: auto"
|
||||
v-if="workerList.length === 0"
|
||||
v-if="workerList.length === 0 && state.search"
|
||||
>
|
||||
<NoData :not-found="!!state.search" />
|
||||
</div>
|
||||
|
||||
<TableWorker
|
||||
v-else
|
||||
v-model:selected="workerSelected"
|
||||
:rows="workerList"
|
||||
:disabledWorkerId
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import useOcrStore from 'stores/ocr';
|
|||
|
||||
// NOTE: Import Components
|
||||
import {
|
||||
AddButton,
|
||||
SaveButton,
|
||||
EditButton,
|
||||
UndoButton,
|
||||
|
|
@ -53,6 +54,9 @@ import { SideMenu } from 'src/components';
|
|||
import BasicInformation from 'components/03_customer-management/employee/BasicInformation.vue';
|
||||
import { AddressForm } from 'src/components/form';
|
||||
import ExpirationDate from 'src/components/03_customer-management/ExpirationDate.vue';
|
||||
import FormEmployeeHealthCheck from 'src/components/03_customer-management/FormEmployeeHealthCheck.vue';
|
||||
import FormEmployeeWorkHistory from 'src/components/03_customer-management/FormEmployeeWorkHistory.vue';
|
||||
import FormEmployeeOther from 'src/components/03_customer-management/FormEmployeeOther.vue';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
|
|
@ -109,7 +113,7 @@ const props = withDefaults(
|
|||
defineProps<{
|
||||
customerBranchId?: string;
|
||||
disabledWorkerId?: string[];
|
||||
preselectWorker?: Employee[];
|
||||
preselectWorker?: (Employee & { workerNew: boolean })[];
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
|
|
@ -129,7 +133,7 @@ const optionStore = useOptionStore();
|
|||
const employeeStore = useEmployeeStore();
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
const newWorkerList = defineModel<
|
||||
const newWorkerList = ref<
|
||||
(EmployeeWorker & {
|
||||
attachment?: {
|
||||
name?: string;
|
||||
|
|
@ -139,7 +143,7 @@ const newWorkerList = defineModel<
|
|||
_meta?: Record<string, any>;
|
||||
}[];
|
||||
})[]
|
||||
>('newWorkerList', { default: [] });
|
||||
>([]);
|
||||
const workerSelected = ref<Employee[]>([]);
|
||||
const workerList = ref<Employee[]>([]);
|
||||
const importWorkerCriteria = ref<{
|
||||
|
|
@ -204,7 +208,13 @@ function getEmployeeImageUrl(item: Employee) {
|
|||
|
||||
function init() {
|
||||
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();
|
||||
}
|
||||
|
|
@ -604,11 +614,14 @@ watch(
|
|||
solid
|
||||
id="btn-success"
|
||||
@click="
|
||||
emits('success', {
|
||||
worker: workerSelected,
|
||||
newWorker: newWorkerList,
|
||||
}),
|
||||
(open = false)
|
||||
() => {
|
||||
$emit('success', {
|
||||
worker: workerSelected,
|
||||
newWorker: newWorkerList,
|
||||
});
|
||||
|
||||
open = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ $t('general.select', { msg: $t('quotation.employeeList') }) }}
|
||||
|
|
@ -630,9 +643,11 @@ watch(
|
|||
if (employeeFormState.currentTab === 'personalInfo') {
|
||||
const currentEmployeeId =
|
||||
await employeeFormStore.submitPersonal(onCreateImageList);
|
||||
quotationForm.injectNewEmployee({
|
||||
data: { ...currentFromDataEmployee, id: currentEmployeeId },
|
||||
});
|
||||
newWorkerList.push(
|
||||
quotationForm.injectNewEmployee({
|
||||
data: { ...currentFromDataEmployee, id: currentEmployeeId },
|
||||
}),
|
||||
);
|
||||
employeeFormState.isEmployeeEdit = false;
|
||||
employeeFormState.dialogType = 'info';
|
||||
}
|
||||
|
|
@ -663,6 +678,7 @@ watch(
|
|||
:show="
|
||||
() => {
|
||||
employeeFormStore.resetFormDataEmployee(true);
|
||||
setCurrentBranchId();
|
||||
}
|
||||
"
|
||||
:before-close="
|
||||
|
|
@ -1035,6 +1051,7 @@ watch(
|
|||
</div>
|
||||
|
||||
<BasicInformation
|
||||
disable-customer-select
|
||||
no-action
|
||||
id="form-information"
|
||||
prefix-id="form-employee"
|
||||
|
|
@ -1455,6 +1472,7 @@ watch(
|
|||
v-model:remark="meta.remark"
|
||||
v-model:worker-type="meta.workerType"
|
||||
v-model:number="meta.number"
|
||||
v-model:report-date="meta.reportDate"
|
||||
/>
|
||||
|
||||
<NoticeJobEmployment v-if="mode === 'noticeJobEmployment'" />
|
||||
|
|
@ -1681,6 +1699,7 @@ watch(
|
|||
v-model:remark="value.remark"
|
||||
v-model:worker-type="value.workerType"
|
||||
v-model:number="value.number"
|
||||
v-model:report-date="value.reportDate"
|
||||
>
|
||||
<template v-slot:expiryDate>
|
||||
{{ $t('general.expirationDate') }} :
|
||||
|
|
@ -1991,8 +2010,8 @@ watch(
|
|||
}
|
||||
|
||||
:deep(
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
||||
) {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
|
||||
|
|
@ -2009,9 +2028,9 @@ watch(
|
|||
}
|
||||
|
||||
:deep(
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
|
||||
.q-focus-helper
|
||||
) {
|
||||
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
|
||||
.q-focus-helper
|
||||
) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
QuotationPayload,
|
||||
QuotationFull,
|
||||
EmployeeWorker,
|
||||
PayCondition,
|
||||
} from 'src/stores/quotations/types';
|
||||
import { Employee } from 'src/stores/employee/types';
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ export const DEFAULT_DATA: QuotationPayload = {
|
|||
payBillDate: new Date(),
|
||||
paySplit: [],
|
||||
paySplitCount: 0,
|
||||
payCondition: 'Full',
|
||||
payCondition: PayCondition.Full,
|
||||
dueDate: new Date(Date.now() + 86400000),
|
||||
discount: 0,
|
||||
contactTel: '',
|
||||
|
|
@ -40,6 +41,7 @@ export const DEFAULT_DATA: QuotationPayload = {
|
|||
status: 'CREATED',
|
||||
remark: '#[quotation-labor]<br/><br/>#[quotation-payment]',
|
||||
agentPrice: false,
|
||||
sellerId: '',
|
||||
};
|
||||
|
||||
const DEFAULT_DATA_INVOICE: InvoicePayload = {
|
||||
|
|
@ -67,6 +69,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
|
|||
file?: File;
|
||||
_meta?: Record<string, any>;
|
||||
}[];
|
||||
workerNew: boolean;
|
||||
})[]
|
||||
>([]);
|
||||
|
||||
|
|
@ -218,7 +221,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
|
|||
},
|
||||
callback?: () => void,
|
||||
) {
|
||||
newWorkerList.value.push({
|
||||
const temp = {
|
||||
//passportNo: obj.data.passportNo,
|
||||
//documentExpireDate: obj.data.documentExpireDate,
|
||||
id: obj.data.id,
|
||||
|
|
@ -233,9 +236,12 @@ export const useQuotationForm = defineStore('form-quotation', () => {
|
|||
gender: obj.data.gender,
|
||||
dateOfBirth: obj.data.dateOfBirth,
|
||||
attachment: obj.data.attachment,
|
||||
});
|
||||
workerNew: true,
|
||||
};
|
||||
|
||||
callback?.();
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
function dialogDelete(callback: () => void) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
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 { useI18n } from 'vue-i18n';
|
||||
import ThaiBahtText from 'thai-baht-text';
|
||||
|
|
@ -175,6 +175,8 @@ enum View {
|
|||
const view = ref<View>(View.Quotation);
|
||||
|
||||
onMounted(async () => {
|
||||
await configStore.getConfig();
|
||||
|
||||
const currentDocumentType = new URL(window.location.href).searchParams.get(
|
||||
'type',
|
||||
);
|
||||
|
|
@ -259,18 +261,6 @@ onMounted(async () => {
|
|||
productList.value =
|
||||
(data?.value?.productServiceList ?? data.value?.productServiceList).map(
|
||||
(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 {
|
||||
id: v.product.id,
|
||||
code: v.product.code,
|
||||
|
|
@ -279,8 +269,8 @@ onMounted(async () => {
|
|||
pricePerUnit: v.pricePerUnit || 0,
|
||||
discount: v.discount || 0,
|
||||
vat: v.vat || 0,
|
||||
value: precisionRound(price + (v.product.calcVat ? vat : 0)),
|
||||
calcVat: v.product.calcVat,
|
||||
value: 0,
|
||||
calcVat: v.vat > 0,
|
||||
product: v.product,
|
||||
};
|
||||
},
|
||||
|
|
@ -292,23 +282,17 @@ onMounted(async () => {
|
|||
[]
|
||||
).reduce(
|
||||
(a, c) => {
|
||||
const originalPrice = c.pricePerUnit;
|
||||
const finalPriceWithVat = precisionRound(
|
||||
originalPrice * (1 + (config.value?.vat || 0.07)),
|
||||
);
|
||||
const finalPriceNoVat =
|
||||
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
||||
const calcVat = c.vat > 0;
|
||||
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
|
||||
const pricePerUnit =
|
||||
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
|
||||
const price =
|
||||
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
|
||||
(1 + vatFactor);
|
||||
|
||||
const price = finalPriceNoVat * c.amount;
|
||||
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.totalPrice = precisionRound(a.totalPrice + price + 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
|
||||
: precisionRound(a.vatExcluded + price);
|
||||
|
|
@ -334,16 +318,12 @@ onMounted(async () => {
|
|||
|
||||
function calcPrice(c: Product) {
|
||||
const originalPrice = c.pricePerUnit;
|
||||
const finalPriceWithVat = precisionRound(
|
||||
originalPrice + originalPrice * (config.value?.vat || 0.07),
|
||||
const finalPricePerUnit = precisionRound(
|
||||
originalPrice +
|
||||
(c.calcVat ? originalPrice * (config.value?.vat || 0.07) : 0),
|
||||
);
|
||||
const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
|
||||
|
||||
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);
|
||||
const price = finalPricePerUnit * c.amount - c.discount;
|
||||
return precisionRound(price);
|
||||
}
|
||||
|
||||
async function closeTab() {
|
||||
|
|
@ -427,31 +407,15 @@ function print() {
|
|||
<td>{{ v.detail }}</td>
|
||||
<td style="text-align: right">{{ v.amount }}</td>
|
||||
<td style="text-align: right">
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
v.pricePerUnit +
|
||||
(v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
||||
? v.pricePerUnit * (config?.vat || 0.07)
|
||||
: 0),
|
||||
2,
|
||||
)
|
||||
}}
|
||||
{{ formatNumberDecimal(v.pricePerUnit, 2) }}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ formatNumberDecimal(v.discount, 2) }}
|
||||
<template v-if="v.discount !== 0">
|
||||
{{ formatNumberDecimal(v.discount, 2) }} ฿
|
||||
</template>
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
|
||||
? precisionRound(
|
||||
(v.pricePerUnit * v.amount - v.discount) *
|
||||
(config?.vat || 0.07),
|
||||
)
|
||||
: 0,
|
||||
2,
|
||||
)
|
||||
}}
|
||||
{{ Math.round((v.vat > 0 ? config?.vat || 0.07 : 0) * 100) }}%
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ formatNumberDecimal(calcPrice(v), 2) }}
|
||||
|
|
@ -511,7 +475,9 @@ function print() {
|
|||
<td class="text-right">
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
summaryPrice.totalPrice - summaryPrice.totalDiscount,
|
||||
summaryPrice.totalPrice -
|
||||
summaryPrice.totalDiscount -
|
||||
summaryPrice.vatExcluded,
|
||||
2,
|
||||
)
|
||||
}}
|
||||
|
|
@ -600,7 +566,7 @@ function print() {
|
|||
details?.worker.map(
|
||||
(v, i) =>
|
||||
`${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(),
|
||||
) || [],
|
||||
},
|
||||
}) || '-'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { dateFormat } from 'src/utils/datetime';
|
|||
|
||||
// NOTE: Import stores
|
||||
import { formatAddress } from 'src/utils/address';
|
||||
import { getCustomerName } from 'src/stores/utils';
|
||||
|
||||
// NOTE Import Types
|
||||
import { Branch } from 'src/stores/branch/types';
|
||||
|
|
@ -78,12 +79,15 @@ function titleMode(mode: View): string {
|
|||
})
|
||||
}}
|
||||
</span>
|
||||
<span>เลขประจำตัวผู้เสียภาษี {{ branch.taxNo }}</span>
|
||||
<span>เบอร์โทร {{ branch.telephoneNo }}</span>
|
||||
<span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
|
||||
<span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
|
||||
<span>{{ branch.webUrl }}</span>
|
||||
</article>
|
||||
<article>
|
||||
<b>{{ $t('quotation.customer') }}</b>
|
||||
<div>
|
||||
{{ getCustomerName(customer, { noCode: true, locale: 'tha' }) }}
|
||||
</div>
|
||||
<span>
|
||||
{{
|
||||
formatAddress({
|
||||
|
|
@ -101,8 +105,18 @@ function titleMode(mode: View): string {
|
|||
})
|
||||
}}
|
||||
</span>
|
||||
<span>เลขประจำตัวผู้เสียภาษี {{ customer.citizenId }}</span>
|
||||
<span>เบอร์โทร {{ customer.telephoneNo }}</span>
|
||||
<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>
|
||||
</section>
|
||||
<section class="detail-quotation-info">
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ const props = withDefaults(
|
|||
defineProps<{
|
||||
readonly?: boolean;
|
||||
isEdit?: boolean;
|
||||
hideAction?: boolean;
|
||||
hideDelete?: boolean;
|
||||
|
||||
dataId?: string;
|
||||
}>(),
|
||||
|
|
@ -80,6 +82,7 @@ const emit = defineEmits<{
|
|||
(e: 'addImage'): void;
|
||||
(e: 'removeImage'): void;
|
||||
(e: 'submitImage', name: string): void;
|
||||
(e: 'deleteAttachment', name: string): void;
|
||||
}>();
|
||||
|
||||
const data = defineModel<InstitutionPayload>('data', {
|
||||
|
|
@ -119,6 +122,9 @@ const formBankBook = defineModel<BankBook[]>('formBankBook', {
|
|||
},
|
||||
],
|
||||
});
|
||||
const attachment = defineModel<File[]>('attachment');
|
||||
const attachmentList =
|
||||
defineModel<{ name: string; url: string }[]>('attachmentList');
|
||||
|
||||
function viewImage() {
|
||||
imageState.imageDialog = true;
|
||||
|
|
@ -346,6 +352,7 @@ watch(
|
|||
v-model:contact-name="data.contactName"
|
||||
v-model:email="data.contactEmail"
|
||||
v-model:contact-tel="data.contactTel"
|
||||
v-model:attachment="attachment"
|
||||
/>
|
||||
<AddressForm
|
||||
id="agencies-form-address-info"
|
||||
|
|
@ -411,6 +418,7 @@ watch(
|
|||
:prefix="data.name"
|
||||
hide-fade
|
||||
use-toggle
|
||||
:readonly="hideAction"
|
||||
:active="data.status !== 'INACTIVE'"
|
||||
:toggle-title="$t('status.title')"
|
||||
:icon="'ph-building-office'"
|
||||
|
|
@ -450,7 +458,7 @@ watch(
|
|||
style="position: absolute; z-index: 999; top: 0; right: 0"
|
||||
>
|
||||
<div
|
||||
v-if="data.status !== 'INACTIVE'"
|
||||
v-if="data.status !== 'INACTIVE' && !hideAction"
|
||||
class="surface-1 row rounded"
|
||||
>
|
||||
<UndoButton
|
||||
|
|
@ -484,7 +492,7 @@ watch(
|
|||
type="button"
|
||||
/>
|
||||
<DeleteButton
|
||||
v-if="!isEdit"
|
||||
v-if="!isEdit && !hideDelete"
|
||||
id="btn-info-basic-delete"
|
||||
icon-only
|
||||
@click="
|
||||
|
|
@ -547,6 +555,9 @@ watch(
|
|||
v-model:contact-name="data.contactName"
|
||||
v-model:email="data.contactEmail"
|
||||
v-model:contact-tel="data.contactTel"
|
||||
v-model:attachment="attachment"
|
||||
:attachment-list="attachmentList"
|
||||
@delete-attachment="(name) => $emit('deleteAttachment', name)"
|
||||
/>
|
||||
<AddressForm
|
||||
id="agencies-address-info"
|
||||
|
|
@ -597,6 +608,7 @@ watch(
|
|||
v-model:on-create-data-list="imageListOnCreate"
|
||||
v-model:image-url="imageState.imageUrl"
|
||||
v-model:data-list="imageList"
|
||||
:changeDisabled="hideAction"
|
||||
:on-create="model"
|
||||
:hiddenFooter="!imageState.isImageEdit"
|
||||
@add-image="addImage"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Icon } from '@iconify/vue/dist/iconify.js';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
import { baseUrl } from 'src/stores/utils';
|
||||
import { baseUrl, canAccess } from 'src/stores/utils';
|
||||
import { useNavigator } from 'src/stores/navigator';
|
||||
import { useInstitution } from 'src/stores/institution';
|
||||
import { Institution, InstitutionPayload } from 'src/stores/institution/types';
|
||||
|
|
@ -115,6 +115,8 @@ const blankFormData: InstitutionPayload = {
|
|||
],
|
||||
};
|
||||
|
||||
const attachment = ref<File[]>([]);
|
||||
const attachmentList = ref<{ name: string; url: string }[]>([]);
|
||||
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
|
||||
const refAgenciesDialog = ref();
|
||||
const formData = ref<InstitutionPayload>(structuredClone(blankFormData));
|
||||
|
|
@ -145,6 +147,8 @@ function resetForm() {
|
|||
pageState.addModal = false;
|
||||
pageState.viewDrawer = false;
|
||||
currAgenciesData.value = undefined;
|
||||
attachment.value = [];
|
||||
attachmentList.value = [];
|
||||
formData.value = structuredClone(blankFormData);
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +158,7 @@ function undo() {
|
|||
pageState.isDrawerEdit = false;
|
||||
}
|
||||
|
||||
function assignFormData(data: Institution) {
|
||||
async function assignFormData(data: Institution) {
|
||||
currAgenciesData.value = data;
|
||||
formData.value = {
|
||||
group: data.group,
|
||||
|
|
@ -174,9 +178,9 @@ function assignFormData(data: Institution) {
|
|||
provinceId: data.provinceId,
|
||||
selectedImage: data.selectedImage,
|
||||
status: data.status,
|
||||
contactEmail: data.contactEmail,
|
||||
contactName: data.contactName,
|
||||
contactTel: data.contactTel,
|
||||
contactEmail: data.contactEmail || '',
|
||||
contactName: data.contactName || '',
|
||||
contactTel: data.contactTel || '',
|
||||
bank: data.bank.map((v) => ({
|
||||
bankName: v.bankName,
|
||||
accountNumber: v.accountNumber,
|
||||
|
|
@ -187,6 +191,8 @@ function assignFormData(data: Institution) {
|
|||
bankUrl: `${baseUrl}/institution/${data.id}/bank-qr/${v.id}?ts=${Date.now()}`,
|
||||
})),
|
||||
};
|
||||
|
||||
await fetchAttachment();
|
||||
}
|
||||
|
||||
async function submit(opt?: { selectedImage: string }) {
|
||||
|
|
@ -214,7 +220,6 @@ async function submit(opt?: { selectedImage: string }) {
|
|||
...v,
|
||||
})),
|
||||
};
|
||||
console.log('payload', payload);
|
||||
if (
|
||||
(pageState.isDrawerEdit && currAgenciesData.value?.id) ||
|
||||
(opt?.selectedImage && currAgenciesData.value?.id)
|
||||
|
|
@ -229,18 +234,29 @@ async function submit(opt?: { selectedImage: string }) {
|
|||
);
|
||||
|
||||
if (ret) {
|
||||
attachment.value.forEach(async (file) => {
|
||||
await institutionStore.putAttachment({
|
||||
parentId: ret.id || '',
|
||||
name: file.name,
|
||||
file: file,
|
||||
});
|
||||
});
|
||||
pageState.isDrawerEdit = false;
|
||||
currAgenciesData.value = ret;
|
||||
formData.value.selectedImage = ret.selectedImage;
|
||||
await fetchData($q.screen.xs);
|
||||
attachment.value = [];
|
||||
|
||||
if (refAgenciesDialog.value && opt?.selectedImage) {
|
||||
refAgenciesDialog.value.clearImageState();
|
||||
}
|
||||
setTimeout(async () => {
|
||||
await fetchAttachment();
|
||||
}, 300);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await institutionStore.createInstitution(
|
||||
const res = await institutionStore.createInstitution(
|
||||
{
|
||||
...payload,
|
||||
code: formData.value.group || '',
|
||||
|
|
@ -248,6 +264,16 @@ async function submit(opt?: { selectedImage: string }) {
|
|||
imageListOnCreate.value,
|
||||
);
|
||||
|
||||
if (!res) return;
|
||||
|
||||
attachment.value.forEach(async (file) => {
|
||||
await institutionStore.putAttachment({
|
||||
parentId: res.id || '',
|
||||
name: file.name,
|
||||
file: file,
|
||||
});
|
||||
});
|
||||
|
||||
await fetchData($q.screen.xs);
|
||||
pageState.addModal = false;
|
||||
return;
|
||||
|
|
@ -347,6 +373,49 @@ async function changeStatus(id?: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function deleteAttachment(attachmentName: string) {
|
||||
dialog({
|
||||
color: 'negative',
|
||||
icon: 'mdi-trash-can-outline',
|
||||
title: t('dialog.title.confirmDelete', {
|
||||
msg: t('personnel.form.attachment'),
|
||||
}),
|
||||
actionText: t('general.delete'),
|
||||
persistent: true,
|
||||
message: t('dialog.message.confirmDelete'),
|
||||
action: async () => {
|
||||
if (!currAgenciesData.value?.id) return;
|
||||
institutionStore.delAttachment({
|
||||
parentId: currAgenciesData.value?.id,
|
||||
name: attachmentName,
|
||||
});
|
||||
setTimeout(async () => {
|
||||
await fetchAttachment();
|
||||
}, 300);
|
||||
},
|
||||
cancel: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchAttachment() {
|
||||
const resAttachment = await institutionStore.listAttachment({
|
||||
parentId: currAgenciesData.value.id,
|
||||
});
|
||||
|
||||
if (!resAttachment) return;
|
||||
|
||||
attachmentList.value = await Promise.all(
|
||||
resAttachment.map(async (f) => ({
|
||||
name: f,
|
||||
url: await institutionStore.getAttachment({
|
||||
parentId: currAgenciesData.value.id,
|
||||
name: f,
|
||||
download: false,
|
||||
}),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
navigatorStore.current.title = 'agencies.title';
|
||||
navigatorStore.current.path = [{ text: 'agencies.caption', i18n: true }];
|
||||
|
|
@ -366,6 +435,7 @@ watch(
|
|||
</script>
|
||||
<template>
|
||||
<FloatingActionButton
|
||||
v-if="canAccess('agencies', 'edit')"
|
||||
style="z-index: 999"
|
||||
hide-icon
|
||||
@click="triggerDialog('add')"
|
||||
|
|
@ -750,6 +820,7 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="canAccess('agencies', 'edit')"
|
||||
:id-name="props.row.name"
|
||||
:status="props.row.status"
|
||||
@view="
|
||||
|
|
@ -833,6 +904,7 @@ watch(
|
|||
"
|
||||
/>
|
||||
<KebabAction
|
||||
v-if="canAccess('agencies', 'edit')"
|
||||
:id-name="props.row.id"
|
||||
:status="props.row.status"
|
||||
@view="
|
||||
|
|
@ -920,7 +992,10 @@ watch(
|
|||
{{ $t('general.recordPerPage') }}
|
||||
</div>
|
||||
<div>
|
||||
<PaginationPageSize v-model="pageSize" />
|
||||
<PaginationPageSize
|
||||
v-model="pageSize"
|
||||
:fetch-data="() => fetchData()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -971,14 +1046,18 @@ watch(
|
|||
}
|
||||
"
|
||||
@change-status="triggerChangeStatus"
|
||||
@delete-attachment="deleteAttachment"
|
||||
:readonly="!pageState.isDrawerEdit"
|
||||
:isEdit="pageState.isDrawerEdit"
|
||||
:hide-action="!canAccess('agencies', 'edit')"
|
||||
v-model="pageState.addModal"
|
||||
v-model:drawer-model="pageState.viewDrawer"
|
||||
v-model:data="formData"
|
||||
v-model:form-bank-book="formData.bank"
|
||||
v-model:image-list-on-create="imageListOnCreate"
|
||||
v-model:deletes-status-qr-code-bank-imag="deletesStatusQrCodeBankImag"
|
||||
v-model:attachment="attachment"
|
||||
:attachment-list="attachmentList"
|
||||
/>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
|
|
|||