Merge branch 'feat/ocr' into develop

This commit is contained in:
Net 2024-08-28 17:47:59 +07:00
commit 7a56f205ec
19 changed files with 1452 additions and 495 deletions

View file

@ -32,6 +32,7 @@ defineProps<{
outlined?: boolean; outlined?: boolean;
readonly?: boolean; readonly?: boolean;
separator?: boolean; separator?: boolean;
ocr?: boolean;
prefixId: string; prefixId: string;
}>(); }>();
@ -84,7 +85,35 @@ watch(
</script> </script>
<template> <template>
<div class="row col-12"> <div class="row col-12" v-if="ocr">
<div class="col-12 row">
<div class="col-6 flex flex-center">
<q-img
src="/images/customer-CORP-avartar.png "
style="height: 100px; max-width: 50%"
/>
</div>
<div class="col-6 column">
<q-input
lazy-rules="ondemand"
:for="`${prefixId}-input-passport-no`"
:dense="dense"
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-3 col-6"
:label="$t('customerEmployee.form.passportNo')"
v-model="passportNumber"
:rules="[
(val: string) =>
!!val || $t('inputValidate') + $t('formDialogInputPassportNo'),
]"
/>
</div>
</div>
</div>
<div class="row col-12" v-else>
<div class="col-12 q-pb-sm text-weight-bold text-body1"> <div class="col-12 q-pb-sm text-weight-bold text-body1">
<q-icon <q-icon
flat flat

View file

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import AppBox from './app/AppBox.vue'; import AppBox from './app/AppBox.vue';
import { CancelButton, SaveButton } from './button'; import { CancelButton, ClearButton, SaveButton } from './button';
defineExpose({ browse }); defineExpose({ browse });
defineProps<{ defineProps<{
@ -131,20 +131,15 @@ async function downloadImage(url: string) {
class="row items-center justify-end q-py-sm q-px-md bordered-t" class="row items-center justify-end q-py-sm q-px-md bordered-t"
v-if="!hiddenFooter" v-if="!hiddenFooter"
> >
<q-btn <ClearButton
dense outlined
unelevated
flat
:color="$q.dark.isActive ? 'grey-9' : 'grey-4'"
:text-color="$q.dark.isActive ? 'grey-1' : 'grey-10'"
:label="$t('general.clear')"
@click=" @click="
inputFile && (inputFile.value = ''), inputFile && (inputFile.value = ''),
(imageUrl = defaultUrl || fallbackUrl || ''), (imageUrl = defaultUrl || fallbackUrl || ''),
(file = null) (file = null)
" "
class="q-px-md q-mr-auto" class="q-px-md q-mr-auto"
:disable=" :disabled="
clearButtonDisabled || clearButtonDisabled ||
imageUrl === fallbackUrl || imageUrl === fallbackUrl ||
imageUrl === defaultUrl || imageUrl === defaultUrl ||

View file

@ -0,0 +1,26 @@
<script lang="ts" setup>
import MainButton from './MainButton.vue';
defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
iconOnly?: boolean;
solid?: boolean;
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
}>();
</script>
<template>
<MainButton
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
icon="mdi-broom"
color="var(--gray-7-hsl)"
:title="iconOnly ? $t('general.clear') : undefined"
>
{{ $t('general.clear') }}
</MainButton>
</template>

View file

@ -0,0 +1,26 @@
<script lang="ts" setup>
import MainButton from './MainButton.vue';
defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
iconOnly?: boolean;
solid?: boolean;
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
}>();
</script>
<template>
<MainButton
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
icon="mdi-close"
color="var(--gray-7-hsl)"
:title="iconOnly ? $t('general.clear') : undefined"
>
{{ $t('general.clear') }}
</MainButton>
</template>

View file

@ -6,3 +6,5 @@ export { default as DeleteButton } from './DeleteButton.vue';
export { default as BackButton } from './BackButton.vue'; export { default as BackButton } from './BackButton.vue';
export { default as UndoButton } from './UndoButton.vue'; export { default as UndoButton } from './UndoButton.vue';
export { default as ToggleButton } from './ToggleButton.vue'; export { default as ToggleButton } from './ToggleButton.vue';
export { default as ClearButton } from './ClearButton.vue';
export { default as CloseButton } from './CloseButton.vue';

View file

@ -0,0 +1,367 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { QSelect } from 'quasar';
import { selectFilterOptionRefMod } from 'stores/utils';
import { getRole } from 'src/services/keycloak';
import useOptionStore from 'stores/options';
import { onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import DatePicker from '../shared/DatePicker.vue';
const { locale } = useI18n();
import {
dateFormat,
calculateAge,
parseAndFormatDate,
disabledAfterToday,
} from 'src/utils/datetime';
import {
SaveButton,
EditButton,
DeleteButton,
UndoButton,
} from 'components/button';
defineProps<{
prefixId?: string;
outlined?: boolean;
readonly?: boolean;
create?: boolean;
actionDisabled?: boolean;
customerType?: 'CORP' | 'PERS';
orc?: boolean;
}>();
defineEmits<{
(e: 'save'): void;
(e: 'edit'): void;
(e: 'delete'): void;
(e: 'cancel'): void;
}>();
const optionStore = useOptionStore();
const namePrefix = defineModel<string | null>('namePrefix');
const birthDate = defineModel<Date | string | null>('birthDate');
const gender = defineModel<string>('gender');
const address = defineModel<string>('address');
const firstName = defineModel<string>('firstName', { required: true });
const lastName = defineModel<string>('lastName', { required: true });
const firstNameEN = defineModel<string>('firstNameEn', { required: true });
const lastNameEN = defineModel<string>('lastNameEn', { required: true });
const citizenId = defineModel<string | undefined>('citizenId', {
required: true,
});
const nationality = defineModel<string>('nationality');
const religion = defineModel<string>('religion');
const branchOptions = defineModel<{ id: string; name: string }[]>(
'branchOptions',
{ default: [] },
);
const filteredBranchOptions = ref<Record<string, unknown>[]>([]);
let branchFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
onMounted(() => {
branchFilter = selectFilterOptionRefMod(
branchOptions,
filteredBranchOptions,
'name',
);
});
watch(
() => branchOptions.value,
() => {
branchFilter = selectFilterOptionRefMod(
branchOptions,
filteredBranchOptions,
'name',
);
},
);
const prefixNameOptions = ref<Record<string, unknown>[]>([]);
let prefixNameFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const prefixNameEnOptions = ref<Record<string, unknown>[]>([]);
let prefixNameEnFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const genderOptions = ref<Record<string, unknown>[]>([]);
let genderFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
onMounted(() => {
prefixNameFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.prefix),
prefixNameOptions,
'label',
);
genderFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.gender),
genderOptions,
'label',
);
});
watch(
() => optionStore.globalOption,
() => {
prefixNameFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.prefix),
prefixNameOptions,
'label',
);
genderFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.gender),
genderOptions,
'label',
);
},
);
function formatCode(input: string | undefined, type: 'code' | 'number') {
if (!input) return;
return input.slice(...(type === 'code' ? [0, -6] : [-6]));
}
</script>
<template>
<div class="row q-mb-sm">
<template v-if="orc">
<div class="row" style="gap: 10px">
<div class="col-4 flex flex-center">
<q-img
src="/images/customer-CORP-avartar.png"
width="80px"
height="80px"
></q-img>
</div>
<div class="col row" style="gap: 10px">
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-12"
:label="$t('customer.form.citizenId')"
for="input-citizen-id"
v-model="citizenId"
/>
<DatePicker
:label="$t('form.birthDate')"
v-model="birthDate"
class="col-12"
:id="`${prefixId}-input-birth-date`"
:readonly="readonly"
clearable
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col"
:label="$t('general.nationality')"
for="input-nationality"
v-model="nationality"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col"
:label="$t('customer.form.religion')"
for="input-religion"
v-model="religion"
/>
<q-select
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
lazy-rules="ondemand"
class="col"
dense
:readonly="readonly"
:options="genderOptions"
:hide-dropdown-icon="readonly"
:for="`${prefixId}-select-gender`"
:label="$t('form.gender')"
@filter="genderFilter"
:model-value="readonly ? gender || '-' : gender"
@update:model-value="
(v) => (typeof v === 'string' ? (gender = v) : '')
"
@clear="gender = ''"
>
<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="row q-col-gutter-sm">
<q-select
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
lazy-rules="ondemand"
hide-dropdown-icon
class="col-2"
dense
:readonly="readonly"
:options="prefixNameOptions"
:for="`${prefixId}-select-prefix-name`"
:label="$t('form.prefixName')"
@filter="prefixNameFilter"
:model-value="readonly ? namePrefix || '-' : namePrefix"
@update:model-value="
(v) => {
typeof v === 'string' ? (namePrefix = v) : '';
}
"
@clear="namePrefix = ''"
>
<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-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-5"
:label="$t('customer.form.firstName')"
for="input-first-name"
v-model="firstName"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-5"
:label="$t('customer.form.lastName')"
for="input-last-name"
v-model="lastName"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:disable="!readonly"
:readonly="readonly"
hide-bottom-space
class="col-2"
:label="$t('customer.form.prefixName')"
for="input-prefix-name"
:model-value="
namePrefix ? $t(`customer.form.prefix.${namePrefix}`) : '-'
"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-5"
:label="$t('customer.form.firstNameEN')"
for="input-first-name-en"
v-model="firstNameEN"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-5"
:label="$t('customer.form.lastNameEN')"
for="input-last-name-en"
v-model="lastNameEN"
/>
<q-input
lazy-rules="ondemand"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-12"
:label="$t('general.address')"
for="input-address"
v-model="address"
/>
<DatePicker
:label="$t('customer.form.issueDate')"
v-model="firstName"
class="col-6"
:id="`${prefixId}-input-issue-date`"
:readonly="readonly"
clearable
/>
<DatePicker
:label="$t('customer.form.passportExpiryDate')"
v-model="firstName"
class="col-6"
:id="`${prefixId}-input-passport-expiry-date`"
:readonly="readonly"
clearable
/>
</div>
</div>
</template>
</div>
</template>

View file

@ -1,29 +1,40 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { computed, ref, watch } from 'vue';
import { SaveButton, UndoButton } from 'components/button'; import { SaveButton, UndoButton, CloseButton } from 'components/button';
import { dialog } from 'stores/utils';
import { VuePDF, usePDF } from '@tato30/vue-pdf'; import { VuePDF, usePDF } from '@tato30/vue-pdf';
const currentFileUrl = defineModel<string>('currentFileUrl'); const currentFileSelected = ref<string>('');
const { pdf, pages } = usePDF(currentFileUrl); const file = defineModel<
{
group?: string;
url?: string;
file?: File;
}[]
>('file', {
default: [],
});
const selected = defineModel<string>('selected'); const currentFile = computed(() => file.value.at(currentIndex.value));
const file = defineModel<File | null>('file'); const statusOcr = defineModel<boolean>('statusOcr', { default: false });
const currentMode = ref<string>('');
const currentIndex = ref(0);
const scale = ref(1); const scale = ref(1);
const page = ref(1); const page = ref(1);
const currentTab = ref<string>('information');
const currentIndexDropdownList = ref(0); const currentIndexDropdownList = ref(0);
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
tree?: { label: string; children: { label: string }[] }[]; treeFile: { label: string; file: { label: string }[] }[];
dropdownList?: string[]; readonly?: boolean;
dropdownList?: { label: string; value: string }[];
hideAction?: boolean;
}>(), }>(),
{ {
tree: () => [], treeFile: () => [],
}, },
); );
@ -47,73 +58,114 @@ function change(e: Event) {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(_file); reader.readAsDataURL(_file);
reader.onload = () => { reader.onload = () => {
currentFileUrl.value = reader.result as string; if (file.value[currentIndex.value]) {
file.value[currentIndex.value].url = reader.result as string;
}
}; };
if (_file) file.value = _file; if (_file && file.value[currentIndex.value]) {
file.value[currentIndex.value].file = _file;
file.value[currentIndex.value].group =
props.dropdownList?.[currentIndexDropdownList.value].value;
} else {
file.value.push({
group: props.dropdownList?.[currentIndexDropdownList.value].value,
file: _file,
});
}
statusOcr.value = true;
emit( emit(
'sendOcr', 'sendOcr',
props.dropdownList?.[currentIndexDropdownList.value] || '', props.dropdownList?.[currentIndexDropdownList.value].value || '',
inputFile?.files?.[0], inputFile?.files?.[0],
); );
} }
} }
const tabsList = [ watch(currentFileSelected, () => {
{ file.value.findIndex((v, i) => {
label: 'information', if (v.url?.includes(currentFileSelected.value)) {
name: 'information', currentIndex.value = i;
}, currentMode.value =
{ props.dropdownList?.[currentIndexDropdownList.value].label || 'other';
label: 'document', }
name: 'document', });
}, });
];
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'sendOcr', dropdown: string, file?: File): void; (e: 'sendOcr', dropdown: string, file?: File): void;
(e: 'save', เgroup: string, file?: File): void;
(e: 'deleteFile', filename: string): void;
}>(); }>();
const { pdf, pages } = usePDF(computed(() => currentFile.value?.url));
</script> </script>
<template> <template>
<div class="full-height full-width row"> <div class="full-width row no-wrap wrapper">
<div> <div class="col column no-wrap">
<div class="q-pa-sm text-center bordered" style="height: 50px"> <div class="q-pa-sm text-center bordered" style="height: 50px">
<q-btn-dropdown icon="mdi-upload" color="info" label="อัปโหลดเอกสาร"> <q-btn-dropdown
<q-list v-for="(v, i) in dropdownList" :key="v"> :disable="readonly"
icon="mdi-upload"
color="info"
label="อัปโหลดเอกสาร"
>
<q-list v-for="(v, i) in dropdownList" :key="v.value">
<q-item <q-item
clickable clickable
v-close-popup v-close-popup
@click=" @click="
() => { () => {
currentIndexDropdownList = i; currentIndexDropdownList = i;
const _idx = file.findIndex(
(v) => v.group === dropdownList?.[i].value,
);
if (_idx !== -1) {
currentIndex = _idx;
} else {
file.push({
group: dropdownList?.[i].value || 'other',
});
currentIndex = file.length - 1;
}
browse(); browse();
} }
" "
> >
<q-item-section> <q-item-section>
<q-item-label>{{ v }}</q-item-label> <q-item-label>{{ $t(v.label) }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-btn-dropdown> </q-btn-dropdown>
</div> </div>
<div class="full-height bordered-l bordered-b q-pa-sm"> <div class="bordered-l bordered-b q-pa-sm col full-height scroll">
<q-tree <q-tree
:nodes="tree || []" :nodes="treeFile || []"
node-key="label" node-key="label"
label-key="label"
children-key="file"
selected-color="primary" selected-color="primary"
v-model:selected="selected" v-model:selected="currentFileSelected"
default-expand-all default-expand-all
/> >
<template v-slot:default-header="prop">
<div class="ellipsis">
<q-tooltip>{{ prop.node.label }}</q-tooltip>
{{ prop.node.label }}
</div>
</template>
</q-tree>
</div> </div>
</div> </div>
<div class="col"> <div class="col column no-wrap">
<div <div
class="bordered row items-center justify-evenly q-pa-sm" class="bordered row items-center justify-evenly q-pa-sm no-wrap"
style="height: 50px" style="height: 50px"
> >
<q-btn <q-btn
@ -125,7 +177,7 @@ const emit = defineEmits<{
id="btn-prev-page-top" id="btn-prev-page-top"
/> />
<div>Page {{ page }} of {{ pages }}</div> <div class="ellipsis">Page {{ page }} of {{ pages }}</div>
<q-btn <q-btn
@click="scale = scale > 0.25 ? scale - 0.25 : scale" @click="scale = scale > 0.25 ? scale - 0.25 : scale"
@ -149,7 +201,7 @@ const emit = defineEmits<{
icon="mdi-magnify-plus-outline" icon="mdi-magnify-plus-outline"
@click="scale = scale < 2 ? scale + 0.25 : scale" @click="scale = scale < 2 ? scale + 0.25 : scale"
> >
<q-tooltip>{{ $t('zoomIn') }}</q-tooltip> <q-tooltip>{{ $t('general.zoomIn') }}</q-tooltip>
</q-btn> </q-btn>
<q-btn <q-btn
@ -165,52 +217,97 @@ const emit = defineEmits<{
<div <div
class="flex flex-center surface-2 bordered-l bordered-r bordered-b full-height scroll" class="flex flex-center surface-2 bordered-l bordered-r bordered-b full-height scroll"
> >
<VuePDF <template v-if="statusOcr">
v-if="file?.type === 'application/pdf'" <q-spinner color="primary" size="3em" :thickness="2" />
class="q-py-md" </template>
:pdf="pdf"
:page="page" <template v-else>
:scale="scale" <VuePDF
/> v-if="
<q-img v-else class="q-py-md full-width" :src="currentFileUrl" /> currentFile?.url?.split('?').at(0)?.endsWith('.pdf') ||
currentFile?.file?.type === 'application/pdf'
"
class="q-py-md"
:pdf="pdf"
:page="page"
:scale="scale"
/>
<q-img v-else class="q-py-md full-width" :src="currentFile?.url" />
</template>
</div> </div>
</div> </div>
<div class="col-4"> <div class="col-5 column no-wrap">
<div <div
class="bordered row items-center justify-between q-pa-sm" class="bordered row items-center justify-between q-pa-sm"
style="height: 50px" style="height: 50px"
> >
อมลหนงสอเดนทาง {{ $t(currentMode) }}
<div class="row" v-if="!hideAction">
<div class="row">
<UndoButton icon-only type="button" /> <UndoButton icon-only type="button" />
<SaveButton icon-only type="button" /> <SaveButton
icon-only
type="button"
@click="
$emit(
'save',
dropdownList?.[currentIndexDropdownList].value || '',
inputFile?.files?.[0],
)
"
/>
</div> </div>
</div> </div>
<div class="bordered-r bordered-b full-height"> <div class="q-pa-sm bordered-r bordered-b full-height col scroll">
<q-tabs <slot name="form" :mode="currentMode" />
dense
inline-label <div class="row items-center">
mobile-arrows {{ currentFileSelected }}
v-model="currentTab" <CloseButton
active-class="active-tab text-weight-bold" icon-only
class="app-text-muted full-width" v-if="!readonly"
align="left" type="button"
> class="q-ml-sm"
<q-tab @click="
:id="`tab-${tab.label}`" () => {
v-for="tab in tabsList" const tempValue = treeFile.find(
v-bind:key="tab.name" (v) => v.label === $t(`customer.typeFile.${currentMode}`),
class="content-tab text-capitalize" );
:name="tab.name"
:label="$t(tab.label)" if (!tempValue) return;
const idx = tempValue.file?.findIndex(
(v) => v.label === currentFileSelected,
);
dialog({
color: 'negative',
icon: 'mdi-alert',
title: $t('dialog.title.confirmDelete'),
actionText: $t('general.delete'),
persistent: true,
message: $t('dialog.message.confirmDelete'),
action: async () => {
$emit('deleteFile', currentFileSelected);
currentFileSelected = tempValue.file?.[idx - 1].label || '';
},
cancel: () => {},
});
}
"
/> />
</q-tabs> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss"></style> <style lang="scss">
.wrapper > * {
height: 300px;
}
</style>

View file

@ -1 +1,2 @@
export { default as UploadFile } from './UploadFile.vue'; export { default as UploadFile } from './UploadFile.vue';
export { default as FormCitizen } from './FormCitizen.vue';

View file

@ -54,6 +54,7 @@ export default {
age: 'Age', age: 'Age',
nationality: 'Nationalality', nationality: 'Nationalality',
times: 'No. {number}', times: 'No. {number}',
uploadFile: 'Upload File',
}, },
menu: { menu: {
dashboard: 'Dashboard', dashboard: 'Dashboard',
@ -238,6 +239,17 @@ export default {
}, },
}, },
customer: { customer: {
typeFile: {
citizenId: 'National ID card',
registrationBook: 'Household registration',
houseMap: 'House map',
businessRegistration: 'Commercial registration',
dbdCertificate: 'DBD Certificate',
VatRegistrationCertificate: 'VAT Registration Certificate',
powerOfAttorney: 'Power of Attorney',
others: 'Others',
},
employer: 'Employer', employer: 'Employer',
employerLegalEntity: 'Legal Entity', employerLegalEntity: 'Legal Entity',
employerNaturalPerson: 'Natrual Person', employerNaturalPerson: 'Natrual Person',
@ -255,6 +267,11 @@ export default {
miss: 'Miss.', miss: 'Miss.',
}, },
citizenId: 'Citizen ID',
religion: 'Religion',
issueDate: 'Issue Date',
passportExpiryDate: 'Passport Expiry Date',
firstName: 'First Name in Thai', firstName: 'First Name in Thai',
lastName: 'Last Name in Thai', lastName: 'Last Name in Thai',
firstNameEN: 'First Name in English', firstNameEN: 'First Name in English',
@ -385,6 +402,20 @@ export default {
mother: 'Mother', mother: 'Mother',
motherBirthPlace: 'Mother Birth Place', motherBirthPlace: 'Mother Birth Place',
}, },
fileType: {
passport: 'Passport',
visa: 'VISA',
tm6: 'TM.6',
workPermit: 'Work Permit',
noticeJobEmployment: 'Foreign Worker Employment Notification Form',
noticeJobEntry:
'Foreign Worker Employment Commencement Notification Form',
historyJob: 'Employment History Form',
acceptJob:
'Acknowledgement of Foreign Worker Employment Notification Form',
receipt: 'Receipt',
other: 'Other',
},
}, },
customerBranch: { customerBranch: {
tab: { tab: {
@ -392,7 +423,7 @@ export default {
address: 'Address', address: 'Address',
business: 'Business', business: 'Business',
contact: 'Contact', contact: 'Contact',
attachment: 'Attachment', attachment: 'Upload Document',
}, },
form: { form: {
title: 'Branch', title: 'Branch',

View file

@ -54,6 +54,7 @@ export default {
age: 'อายุ', age: 'อายุ',
nationality: 'สัญชาติ', nationality: 'สัญชาติ',
times: 'ครั้งที่ {number}', times: 'ครั้งที่ {number}',
uploadFile: 'อัปโหลดไฟล์',
}, },
menu: { menu: {
dashboard: 'แดชบอร์ด', dashboard: 'แดชบอร์ด',
@ -239,6 +240,16 @@ export default {
}, },
}, },
customer: { customer: {
typeFile: {
citizenId: 'บัตรประจำตัวประชาชน',
registrationBook: 'ทะเบียนบ้าน',
houseMap: 'แผนที่ (บ้าน)',
businessRegistration: 'ทะเบียนพาณิชย์',
dbdCertificate: 'หนังสือรับรอง (DBD)',
VatRegistrationCertificate: 'ภ.พ.20',
powerOfAttorney: 'หนังสือมอบอำนาจ',
others: 'อื่นๆ',
},
employer: 'นายจ้าง', employer: 'นายจ้าง',
employee: 'ลูกจ้าง', employee: 'ลูกจ้าง',
employerLegalEntity: 'นิติบุคคล', employerLegalEntity: 'นิติบุคคล',
@ -256,6 +267,11 @@ export default {
miss: 'Miss.', miss: 'Miss.',
}, },
citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
firstName: 'ชื่อ ภาษาไทย', firstName: 'ชื่อ ภาษาไทย',
lastName: 'นามสกุล ภาษาไทย', lastName: 'นามสกุล ภาษาไทย',
firstNameEN: 'ชื่อ ภาษาอังกฤษ', firstNameEN: 'ชื่อ ภาษาอังกฤษ',
@ -385,6 +401,18 @@ export default {
mother: 'มารดา', mother: 'มารดา',
motherBirthPlace: 'สถานที่เกิดของมารดา', motherBirthPlace: 'สถานที่เกิดของมารดา',
}, },
fileType: {
passport: 'ข้อมูลหนังสือการเดินทาง',
visa: 'ข้อมูลการตรวจลงตรา',
tm6: 'ตม.6',
workPermit: 'ใบอนุญาตทำงาน',
noticeJobEmployment: 'แบบแจ้งการจ้างคนต่างด้าวทำงาน',
noticeJobEntry: 'แบบแจ้งเข้าทำงานของคนต่างด้าว',
historyJob: 'ใบคัดประวัติระบบจัดหางาน',
acceptJob: 'ใบตอบรับการแจ้งเกี่ยวกับการทำงานของคนต่างด้าว',
receipt: 'ใบเสร็จรับเงิน',
other: 'อื่นๆ',
},
}, },
customerBranch: { customerBranch: {
tab: { tab: {
@ -392,7 +420,7 @@ export default {
address: 'ที่อยู่', address: 'ที่อยู่',
business: 'ธุรกิจ', business: 'ธุรกิจ',
contact: 'ติดต่อ', contact: 'ติดต่อ',
attachment: 'เอกสารเพิ่มเติม', attachment: 'อัปโหลดเอกสาร',
}, },
form: { form: {
title: 'สาขา', title: 'สาขา',

View file

@ -54,7 +54,8 @@ import {
columnsCustomer, columnsCustomer,
columnsEmployee, columnsEmployee,
formMenuIconEmployee, formMenuIconEmployee,
uploadFileList, uploadFileListEmployee,
uploadFileListCustomer,
} from './constant'; } from './constant';
import { useCustomerForm, useEmployeeForm } from './form'; import { useCustomerForm, useEmployeeForm } from './form';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@ -1871,6 +1872,7 @@ const emptyCreateDialog = ref(false);
<EmployerFormBranch <EmployerFormBranch
v-if="!!customerFormState.editCustomerId" v-if="!!customerFormState.editCustomerId"
:index="idx" :index="idx"
v-model:customer="customerFormData"
v-model:customer-branch="customerFormData.customerBranch[idx]" v-model:customer-branch="customerFormData.customerBranch[idx]"
:customer-type="customerFormData.customerType" :customer-type="customerFormData.customerType"
:customer-name="`${customerFormData.firstName} ${customerFormData.lastName}`" :customer-name="`${customerFormData.firstName} ${customerFormData.lastName}`"
@ -2049,6 +2051,11 @@ const emptyCreateDialog = ref(false);
anchor: 'form-visa', anchor: 'form-visa',
tab: 'personalInfo', tab: 'personalInfo',
}, },
{
name: $t('general.uploadFile'),
anchor: 'drawer-info-file-upload',
tab: 'personalInfo',
},
...(currentFromDataEmployee.employeeCheckup?.map((v, i) => ({ ...(currentFromDataEmployee.employeeCheckup?.map((v, i) => ({
name: $t('general.times', { number: i + 1 }), name: $t('general.times', { number: i + 1 }),
anchor: `form-employee-checkup-${i}`, anchor: `form-employee-checkup-${i}`,
@ -2160,6 +2167,7 @@ const emptyCreateDialog = ref(false);
employee employee
separator separator
title="customerEmployee.form.group.personalInfo" title="customerEmployee.form.group.personalInfo"
:readonly="!employeeFormState.isEmployeeEdit"
v-model:open="employeeFormState.dialogModal" v-model:open="employeeFormState.dialogModal"
v-model:prefixName="currentFromDataEmployee.namePrefix" v-model:prefixName="currentFromDataEmployee.namePrefix"
v-model:firstName="currentFromDataEmployee.firstName" v-model:firstName="currentFromDataEmployee.firstName"
@ -2176,6 +2184,7 @@ const emptyCreateDialog = ref(false);
<AddressForm <AddressForm
id="form-personal-address" id="form-personal-address"
prefix-id="form-employee" prefix-id="form-employee"
:readonly="!employeeFormState.isEmployeeEdit"
v-model:same-with-employer=" v-model:same-with-employer="
employeeFormState.formDataEmployeeSameAddr employeeFormState.formDataEmployeeSameAddr
" "
@ -2197,6 +2206,7 @@ const emptyCreateDialog = ref(false);
outlined outlined
separator separator
:title="$t('customerEmployee.form.group.passport')" :title="$t('customerEmployee.form.group.passport')"
:readonly="!employeeFormState.isEmployeeEdit"
v-model:passport-type="currentFromDataEmployee.passportType" v-model:passport-type="currentFromDataEmployee.passportType"
v-model:passport-number="currentFromDataEmployee.passportNumber" v-model:passport-number="currentFromDataEmployee.passportNumber"
v-model:passport-issue-date=" v-model:passport-issue-date="
@ -2223,6 +2233,7 @@ const emptyCreateDialog = ref(false);
dense dense
outlined outlined
title="customerEmployee.form.group.visa" title="customerEmployee.form.group.visa"
:readonly="!employeeFormState.isEmployeeEdit"
v-model:visa-type="currentFromDataEmployee.visaType" v-model:visa-type="currentFromDataEmployee.visaType"
v-model:visa-number="currentFromDataEmployee.visaNumber" v-model:visa-number="currentFromDataEmployee.visaNumber"
v-model:visa-issue-date="currentFromDataEmployee.visaIssueDate" v-model:visa-issue-date="currentFromDataEmployee.visaIssueDate"
@ -2237,6 +2248,84 @@ const emptyCreateDialog = ref(false);
v-model:entry-date="currentFromDataEmployee.entryDate" v-model:entry-date="currentFromDataEmployee.entryDate"
class="q-mb-xl" class="q-mb-xl"
/> />
<div class="row q-mb-md" id="drawer-info-file-upload">
<div class="col-12 q-pb-sm text-weight-bold text-body1">
<q-icon
flat
size="xs"
class="q-pa-sm rounded q-mr-xs"
color="info"
name="mdi-upload"
style="background-color: var(--surface-3)"
/>
{{ $t(`general.uploadFile`) }}
</div>
<UploadFile
:tree-file="
Object.values(
currentFromDataEmployee.file?.reduce<
Record<
string,
{ label: string; file: { label: string }[] }
>
>((a, c) => {
const _group = c.group || 'other';
if (!a[_group]) {
a[_group] = {
label: $t(
uploadFileListEmployee.find(
(v) => v.value === _group,
)?.label || _group,
),
file: [
{
label:
c.name ||
`${c.group}-${c.file?.name || Date.now()}`,
},
],
};
} else {
a[_group].file.push({
label:
c.name ||
`${c.group}-${c.file?.name || Date.now()}`,
});
}
return a;
}, {}) || {},
)
"
v-model:file="currentFromDataEmployee.file"
hide-action
v-model:status-ocr="employeeFormState.ocr"
:readonly="!employeeFormState.isEmployeeEdit"
:dropdown-list="uploadFileListEmployee"
@delete-file="
async (filename) => {
if (currentFromDataEmployee.id) {
const result = await employeeStore.deleteAttachment(
currentFromDataEmployee.id,
filename,
);
if (result) {
currentFromDataEmployee.file =
currentFromDataEmployee.file?.filter(
(v) => v.name !== filename,
);
}
}
}
"
@send-ocr="
async (_, file) => {
if (file) await ocrStore.sendOcr({ file });
employeeFormState.ocr = false;
}
"
/>
</div>
</template> </template>
<template v-if="employeeFormState.currentTab === 'healthCheck'"> <template v-if="employeeFormState.currentTab === 'healthCheck'">
@ -2338,6 +2427,7 @@ const emptyCreateDialog = ref(false);
} }
" "
/> />
a
</template> </template>
<template v-if="employeeFormState.currentTab === 'other'"> <template v-if="employeeFormState.currentTab === 'other'">
@ -2466,8 +2556,8 @@ const emptyCreateDialog = ref(false);
" "
:show=" :show="
async () => { async () => {
await fetchListOfOptionBranch(); // await fetchListOfOptionBranch();
customerFormStore.resetForm(customerFormState.dialogType === 'create'); // customerFormStore.resetForm(true);
} }
" "
:before-close=" :before-close="
@ -2477,6 +2567,7 @@ const emptyCreateDialog = ref(false);
return false; return false;
} else { } else {
fetchListCustomer(); fetchListCustomer();
customerFormStore.resetForm(true);
customerFormState.branchIndex = -1; customerFormState.branchIndex = -1;
} }
return false; return false;
@ -2515,10 +2606,15 @@ const emptyCreateDialog = ref(false);
name: $t('form.field.basicInformation'), name: $t('form.field.basicInformation'),
anchor: 'form-basic-info-customer', anchor: 'form-basic-info-customer',
}, },
{
name: $t('customerBranch.tab.attachment'),
anchor: 'form-upload-file-customer',
},
{ {
name: $t('customer.form.group.branch'), name: $t('customer.form.group.branch'),
anchor: 'form-branch-customer-branch', anchor: 'form-branch-customer-branch',
}, },
...(customerFormData.customerBranch?.map((v, i) => ({ ...(customerFormData.customerBranch?.map((v, i) => ({
name: name:
i === 0 i === 0
@ -2546,7 +2642,6 @@ const emptyCreateDialog = ref(false);
style="height: 100%; max-height: 100%; overflow-y: auto" style="height: 100%; max-height: 100%; overflow-y: auto"
> >
<EmployerFormBasicInfo <EmployerFormBasicInfo
class="q-mb-xl"
:readonly=" :readonly="
(customerFormState.dialogType === 'edit' && (customerFormState.dialogType === 'edit' &&
customerFormState.readonly === true) || customerFormState.readonly === true) ||
@ -2577,6 +2672,19 @@ const emptyCreateDialog = ref(false);
v-model:birth-date="customerFormData.birthDate" v-model:birth-date="customerFormData.birthDate"
/> />
<!-- <div class="row q-col-gutter-sm q-mb-xl">
<UploadFile
id="form-upload-file-customer"
class="q-mb-xl"
hide-action
:dropdown-list="uploadFileList"
v-model:file="customerFormState.file"
@send-ocr="
(group: any, file: any) => ocrStore.sendOcr({ file })
"
/>
</div> -->
<div class="row q-col-gutter-sm" id="form-branch-customer-branch"> <div class="row q-col-gutter-sm" id="form-branch-customer-branch">
<div <div
class="col-12 text-weight-bold text-body1 row items-center" class="col-12 text-weight-bold text-body1 row items-center"
@ -2663,9 +2771,33 @@ const emptyCreateDialog = ref(false);
); );
} }
customerFormData.customerBranch[idx].file?.forEach(
async (v) => {
if (!v.file) return;
const ext = v.file.name.split('.').at(-1);
let filename = v.group + '-' + new Date().getTime();
if (ext) filename += `.${ext}`;
const res = await customerStore.putAttachment({
branchId:
customerFormData.customerBranch?.[idx].id || '',
file: v.file,
filename,
});
if (res) {
await customerFormStore.assignFormData(
customerFormState.editCustomerId,
);
}
},
);
await customerFormStore.assignFormData( await customerFormStore.assignFormData(
customerFormState.editCustomerId, customerFormState.editCustomerId,
); );
customerFormStore.resetForm(); customerFormStore.resetForm();
} }
} }
@ -2674,9 +2806,11 @@ const emptyCreateDialog = ref(false);
<EmployerFormBranch <EmployerFormBranch
v-if="!!customerFormState.editCustomerId" v-if="!!customerFormState.editCustomerId"
:index="idx" :index="idx"
v-model:customer="customerFormData"
v-model:customer-branch=" v-model:customer-branch="
customerFormData.customerBranch[idx] customerFormData.customerBranch[idx]
" "
:tree-file="customerFormState.treeFile"
:customer-type="customerFormData.customerType" :customer-type="customerFormData.customerType"
:customer-name="`${customerFormData.firstName} ${customerFormData.lastName}`" :customer-name="`${customerFormData.firstName} ${customerFormData.lastName}`"
:action-disabled=" :action-disabled="
@ -2869,7 +3003,7 @@ const emptyCreateDialog = ref(false);
}, },
{ {
name: $t('อัปโหลดไฟล์'), name: $t('general.uploadFile'),
anchor: 'drawer-upload-file', anchor: 'drawer-upload-file',
tab: 'personalInfo', tab: 'personalInfo',
}, },
@ -3062,17 +3196,83 @@ const emptyCreateDialog = ref(false);
class="q-mb-xl" class="q-mb-xl"
/> />
<UploadFile <div class="row" id="drawer-upload-file">
:tree="[]" <div class="col-12 q-pb-sm text-weight-bold text-body1">
:dropdown-list="uploadFileList" <q-icon
@send-ocr=" flat
async (v: any, f: any) => { size="xs"
console.log(v, f); class="q-pa-sm rounded q-mr-xs"
color="info"
await ocrStore.sendOcr({ file: f }); name="mdi-upload"
} style="background-color: var(--surface-3)"
" />
/> {{ $t(`general.uploadFile`) }}
</div>
<UploadFile
:tree-file="
Object.values(
currentFromDataEmployee.file?.reduce<
Record<
string,
{ label: string; file: { label: string }[] }
>
>((a, c) => {
const _group = c.group || 'other';
if (!a[_group]) {
a[_group] = {
label: $t(
uploadFileListEmployee.find(
(v) => v.value === _group,
)?.label || _group,
),
file: [
{
label:
c.name ||
`${c.group}-${c.file?.name || Date.now()}`,
},
],
};
} else {
a[_group].file.push({
label:
c.name ||
`${c.group}-${c.file?.name || Date.now()}`,
});
}
return a;
}, {}) || {},
)
"
v-model:file="currentFromDataEmployee.file"
hide-action
v-model:status-ocr="employeeFormState.ocr"
:readonly="!employeeFormState.isEmployeeEdit"
:dropdown-list="uploadFileListEmployee"
@delete-file="
async (filename) => {
if (currentFromDataEmployee.id) {
const result = await employeeStore.deleteAttachment(
currentFromDataEmployee.id,
filename,
);
if (result) {
currentFromDataEmployee.file =
currentFromDataEmployee.file?.filter(
(v) => v.name !== filename,
);
}
}
}
"
@send-ocr="
async (_, file) => {
if (file) await ocrStore.sendOcr({ file });
employeeFormState.ocr = false;
}
"
/>
</div>
</template> </template>
<template v-if="employeeFormState.currentTab === 'healthCheck'"> <template v-if="employeeFormState.currentTab === 'healthCheck'">
<FormEmployeeHealthCheck <FormEmployeeHealthCheck

View file

@ -5,18 +5,32 @@ import EmployerFormBusiness from './EmployerFormBusiness.vue';
import EmployerFormContact from './EmployerFormContact.vue'; import EmployerFormContact from './EmployerFormContact.vue';
import { CustomerCreate } from 'stores/customer/types'; import { CustomerCreate } from 'stores/customer/types';
import EmployerFormAbout from './EmployerFormAbout.vue'; import EmployerFormAbout from './EmployerFormAbout.vue';
import EmployerFormAttachment from './EmployerFormAttachment.vue'; import { useCustomerForm } from 'src/pages/03_customer-management/form';
const customerFormStore = useCustomerForm();
import { FormCitizen } from 'components/upload-file/';
import useOcrStore from 'stores/ocr';
const ocrStore = useOcrStore();
import { import {
SaveButton, SaveButton,
EditButton, EditButton,
DeleteButton, DeleteButton,
UndoButton, UndoButton,
} from 'components/button'; } from 'components/button';
import UploadFile from 'src/components/upload-file/UploadFile.vue';
import { uploadFileListCustomer } from '../../constant';
const statusOcr = ref(false);
const customer = defineModel<CustomerCreate>('customer', { required: true });
const item = defineModel<NonNullable<CustomerCreate['customerBranch']>[number]>( const item = defineModel<NonNullable<CustomerCreate['customerBranch']>[number]>(
'customerBranch', 'customerBranch',
{ required: true }, { required: true },
); );
const tab = ref('main'); const tab = ref('main');
defineEmits<{ defineEmits<{
@ -33,6 +47,7 @@ defineProps<{
prefixId?: string; prefixId?: string;
actionDisabled?: boolean; actionDisabled?: boolean;
customerType?: 'CORP' | 'PERS'; customerType?: 'CORP' | 'PERS';
treeFile?: { label: string; file: { label: string }[] }[];
}>(); }>();
</script> </script>
@ -160,10 +175,103 @@ defineProps<{
/> />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="attachment"> <q-tab-panel name="attachment">
<EmployerFormAttachment <UploadFile
hide-action
:readonly="readonly"
:dropdown-list="uploadFileListCustomer"
v-model:status-ocr="statusOcr"
v-model:file="item.file"
:tree-file="
Object.values(
item.file?.reduce<
Record<string, { label: string; file: { label: string }[] }>
>((a, c) => {
const _group = c.group || 'other';
console.log(c);
if (!a[_group]) {
a[_group] = {
label: $t(
uploadFileListCustomer.find((v) => v.value === _group)
?.label || _group,
),
file: [
{
label:
c.name ||
`${c.group}-${c.file?.name || Date.now()}`,
},
],
};
} else {
a[_group].file.push({
label:
c.name || `${c.group}-${c.file?.name || Date.now()}`,
});
}
return a;
}, {}) || {},
)
"
@send-ocr="
async (v: any, f: any) => {
const res = await ocrStore.sendOcr({ file: f });
if (res) {
const map = res.fields.reduce<Record<string, string>>(
(a, c) => {
a[c.name] = c.value;
return a;
},
{},
);
if (!item.citizenId) item.citizenId = map['citizen_id'] || '';
if (!item.address) item.address = map['address'] || '';
if (!customer.firstName)
customer.firstName = map['firstname'] || '';
if (!customer.lastName)
customer.lastName = map['lastname'] || '';
if (!customer.firstNameEN)
customer.firstNameEN = map['firstname_en'] || '';
if (!customer.lastNameEN)
customer.lastNameEN = map['lastname_en'] || '';
if (!customer.birthDate)
customer.birthDate = new Date(map['birth_date'] || '');
}
statusOcr = false;
}
"
@delete-file="
(filename) => {
if (!item.id) return;
customerFormStore.deleteAttachment(
{ branchId: item.id, customerId: item.customerId },
filename,
);
}
"
>
<template #form="{ mode }">
<FormCitizen
v-if="mode === 'citizenId'"
orc
v-model:citizen-id="item.citizenId"
v-model:birth-date="customer.birthDate"
v-model:first-name="customer.firstName"
v-model:first-name-en="customer.firstNameEN"
v-model:last-name="customer.lastName"
v-model:last-name-en="customer.lastNameEN"
v-model:address="item.address"
/>
</template>
</UploadFile>
<!-- <EmployerFormAttachment
:readonly="readonly" :readonly="readonly"
v-model:attachment="item.file" v-model:attachment="item.file"
/> /> -->
</q-tab-panel> </q-tab-panel>
</q-tab-panels> </q-tab-panels>
</div> </div>

View file

@ -1,16 +1,93 @@
import { QTableProps } from 'quasar'; import { QTableProps } from 'quasar';
export const uploadFileList: string[] = [ export const uploadFileListCustomer: {
'ข้อมูลหนังสือการเดินทาง', label: string;
'ข้อมูลการตรวจลงตรา', value: string;
'ตม.6', }[] = [
'ใบอนุญาตทำงาน', {
'แบบแจ้งการจ้างคนต่างด้าวทำงาน', label: 'customer.typeFile.citizenId',
'แบบแจ้งเข้าทำงานของคนต่างด้าว', value: 'citizenId',
'ใบคัดประวัติระบบจัดหางาน', },
'ใบตอบรับการแจ้งเกี่ยวกับการทำงานของคนต่างด้าว', {
'ใบเสร็จรับเงิน', label: 'customer.typeFile.registrationBook',
'อื่นๆ', value: 'registrationBook',
},
{
label: 'customer.typeFile.houseMap',
value: 'houseMap',
},
{
label: 'customer.typeFile.businessRegistration',
value: 'businessRegistration',
},
{
label: 'customer.typeFile.dbdCertificate',
value: 'dbdCertificate',
},
{
label: 'customer.typeFile.vatRegistrationCertificate',
value: 'vatRegistrationCertificate',
},
{
label: 'customer.typeFile.powerOfAttorney',
value: 'powerOfAttorney',
},
{
label: 'customer.typeFile.others',
value: 'others',
},
];
export const uploadFileListEmployee: {
label: string;
value: string;
}[] = [
{
label: 'customerEmployee.fileType.passport',
value: 'passport',
},
{
label: 'customerEmployee.fileType.visa',
value: 'visa',
},
{
label: 'customerEmployee.fileType.tm6',
value: 'tm6',
},
{
label: 'customerEmployee.fileType.workPermit',
value: 'workPermit',
},
{
label: 'customerEmployee.fileType.noticeJobEmployment',
value: 'noticeJobEmployment',
},
{
label: 'customerEmployee.fileType.noticeJobEntry',
value: 'noticeJobEntry',
},
{
label: 'customerEmployee.fileType.historyJob',
value: 'historyJob',
},
{
label: 'customerEmployee.fileType.acceptJob',
value: 'acceptJob',
},
{
label: 'customerEmployee.fileType.receipt',
value: 'receipt',
},
{
label: 'customerEmployee.fileType.other',
value: 'other',
},
]; ];
export const formMenuIconEmployee = [ export const formMenuIconEmployee = [

View file

@ -2,6 +2,7 @@ import { ref, toRaw, watch } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { CustomerBranchCreate, CustomerCreate } from 'stores/customer/types'; import { CustomerBranchCreate, CustomerCreate } from 'stores/customer/types';
import { Employee, EmployeeCreate } from 'stores/employee/types'; import { Employee, EmployeeCreate } from 'stores/employee/types';
import { useI18n } from 'vue-i18n';
import useMyBranch from 'stores/my-branch'; import useMyBranch from 'stores/my-branch';
import useCustomerStore from 'stores/customer'; import useCustomerStore from 'stores/customer';
@ -9,6 +10,7 @@ import useEmployeeStore from 'stores/employee';
import useFlowStore from 'stores/flow'; import useFlowStore from 'stores/flow';
export const useCustomerForm = defineStore('form-customer', () => { export const useCustomerForm = defineStore('form-customer', () => {
const { t } = useI18n();
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL; const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
@ -46,6 +48,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
editCustomerId?: string; editCustomerId?: string;
editCustomerCode?: string; editCustomerCode?: string;
editCustomerBranchId?: string; editCustomerBranchId?: string;
treeFile: { label: string; file: { label: string }[] }[];
formDataOcr: Record<string, any>;
}>({ }>({
dialogType: 'info', dialogType: 'info',
dialogOpen: false, dialogOpen: false,
@ -59,6 +63,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
editCustomerId: '', editCustomerId: '',
editCustomerBranchId: '', editCustomerBranchId: '',
defaultCustomerImageUrl: '', defaultCustomerImageUrl: '',
treeFile: [],
formDataOcr: {},
}); });
watch( watch(
@ -66,6 +72,16 @@ export const useCustomerForm = defineStore('form-customer', () => {
(v) => (defaultFormData.customerType = v.customerType), (v) => (defaultFormData.customerType = v.customerType),
); );
async function deleteAttachment(
id: { branchId: string; customerId: string },
filename: string,
) {
const res = await customerStore.deleteAttachment(id.branchId, filename);
if (res) {
assignFormData(id.customerId);
}
}
function isFormDataDifferent() { function isFormDataDifferent() {
return ( return (
JSON.stringify(resetFormData) !== JSON.stringify(currentFormData.value) JSON.stringify(resetFormData) !== JSON.stringify(currentFormData.value)
@ -82,6 +98,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
resetFormData = structuredClone(defaultFormData); resetFormData = structuredClone(defaultFormData);
resetFormData.registeredBranchId = branchStore.currentMyBranch?.id || ''; resetFormData.registeredBranchId = branchStore.currentMyBranch?.id || '';
state.value.editCustomerId = ''; state.value.editCustomerId = '';
state.value.treeFile = [];
return; return;
} }
@ -127,41 +145,59 @@ export const useCustomerForm = defineStore('form-customer', () => {
resetFormData.birthDate = new Date(data.birthDate); resetFormData.birthDate = new Date(data.birthDate);
resetFormData.image = null; resetFormData.image = null;
resetFormData.customerBranch = data.branch.map((v) => ({ resetFormData.customerBranch = await Promise.all(
id: v.id, data.branch.map(async (v) => ({
code: v.code || '', id: v.id,
customerCode: '', code: v.code || '',
provinceId: v.provinceId, customerCode: '',
districtId: v.districtId, provinceId: v.provinceId,
subDistrictId: v.subDistrictId, districtId: v.districtId,
wageRate: v.wageRate, subDistrictId: v.subDistrictId,
payDate: new Date(v.payDate), // Convert the string to a Date object wageRate: v.wageRate,
saleEmployee: v.saleEmployee, payDate: new Date(v.payDate), // Convert the string to a Date object
jobDescription: v.jobDescription, saleEmployee: v.saleEmployee,
jobPositionEN: v.jobPositionEN, jobDescription: v.jobDescription,
jobPosition: v.jobPosition, jobPositionEN: v.jobPositionEN,
businessTypeEN: v.businessTypeEN, jobPosition: v.jobPosition,
businessType: v.businessType, businessTypeEN: v.businessTypeEN,
employmentOffice: v.employmentOffice, businessType: v.businessType,
telephoneNo: v.telephoneNo, employmentOffice: v.employmentOffice,
email: v.email, telephoneNo: v.telephoneNo,
addressEN: v.addressEN, email: v.email,
address: v.address, addressEN: v.addressEN,
workplaceEN: v.workplaceEN, address: v.address,
workplace: v.workplace, workplaceEN: v.workplaceEN,
status: v.status, workplace: v.workplace,
customerId: v.customerId, status: v.status,
citizenId: v.citizenId || '', customerId: v.customerId,
authorizedCapital: v.authorizedCapital || '', citizenId: v.citizenId || '',
registerDate: new Date(v.registerDate), // Convert the string to a Date object authorizedCapital: v.authorizedCapital || '',
registerNameEN: v.registerNameEN || '', registerDate: new Date(v.registerDate), // Convert the string to a Date object
registerName: v.registerName || '', registerNameEN: v.registerNameEN || '',
legalPersonNo: v.legalPersonNo || '', registerName: v.registerName || '',
registerCompanyName: '', legalPersonNo: v.legalPersonNo || '',
statusSave: true, registerCompanyName: '',
contactName: v.contactName || '', statusSave: true,
file: undefined, contactName: v.contactName || '',
})); file: await customerStore.listAttachment(v.id).then(async (r) => {
if (r) {
return await Promise.all(
r.map(async (item) => {
const fragment = item.split('-');
const group = fragment.length === 1 ? 'other' : fragment.at(0);
return {
url: await customerStore.getAttachment(v.id, item),
name: item,
group: group,
};
}),
);
}
return [];
}),
})),
);
currentFormData.value = structuredClone(resetFormData); currentFormData.value = structuredClone(resetFormData);
} }
@ -269,11 +305,13 @@ export const useCustomerForm = defineStore('form-customer', () => {
assignFormData, assignFormData,
submitFormCustomer, submitFormCustomer,
addCurrentCustomerBranch, addCurrentCustomerBranch,
deleteAttachment,
}; };
}); });
export const useCustomerBranchForm = defineStore('form-customer-branch', () => { export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
const customerFormStore = useCustomerForm();
const defaultFormData: CustomerBranchCreate & { id?: string } = { const defaultFormData: CustomerBranchCreate & { id?: string } = {
code: '', code: '',
@ -404,6 +442,10 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
if (!currentFormData.value.id) { if (!currentFormData.value.id) {
const res = await customerStore.createBranch({ const res = await customerStore.createBranch({
...currentFormData.value, ...currentFormData.value,
citizenId:
customerFormStore.currentFormData.customerType === 'CORP'
? undefined
: currentFormData.value.citizenId,
customerId: state.value.currentCustomerId, customerId: state.value.currentCustomerId,
}); });
if (res) return res; if (res) return res;
@ -418,8 +460,8 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
return { return {
state, state,
initForm,
currentFormData, currentFormData,
initForm,
isFormDataDifferent, isFormDataDifferent,
submitForm, submitForm,
}; };
@ -467,6 +509,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
zipCode: string; zipCode: string;
} }
| undefined; | undefined;
ocr: boolean;
}>({ }>({
currentIndex: -1, currentIndex: -1,
statusSavePersonal: false, statusSavePersonal: false,
@ -484,6 +527,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
editReadonly: false, editReadonly: false,
infoEmployeePersonCard: [], infoEmployeePersonCard: [],
formDataEmployeeOwner: undefined, formDataEmployeeOwner: undefined,
ocr: false,
}); });
const defaultFormData: EmployeeCreate = { const defaultFormData: EmployeeCreate = {
@ -783,10 +827,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
return; return;
} }
const res = await employeeStore.fetchById(id); const _data = await employeeStore.fetchById(id);
if (res) { if (_data) {
state.value.currentEmployee = res; const _attach = await employeeStore.listAttachment(_data.id);
state.value.currentEmployee = _data;
const { const {
createdAt, createdAt,
@ -800,47 +846,61 @@ export const useEmployeeForm = defineStore('form-employee', () => {
createdBy, createdBy,
updatedBy, updatedBy,
profileImageUrl, profileImageUrl,
...playlond ...payload
} = res; } = _data;
resetEmployeeData = { resetEmployeeData = {
...playlond, ...payload,
provinceId: province?.id, provinceId: province?.id,
districtId: district?.id, districtId: district?.id,
subDistrictId: subDistrict?.id, subDistrictId: subDistrict?.id,
employeeCheckup: structuredClone( employeeCheckup: structuredClone(
playlond.employeeCheckup?.length === 0 payload.employeeCheckup?.length === 0
? defaultFormData.employeeCheckup ? defaultFormData.employeeCheckup
: playlond.employeeCheckup?.map((item) => ({ : payload.employeeCheckup?.map((item) => ({
...item, ...item,
statusSave: true, statusSave: true,
})), })),
), ),
employeeOtherInfo: structuredClone( employeeOtherInfo: structuredClone(
{ {
...playlond.employeeOtherInfo, ...payload.employeeOtherInfo,
statusSave: !!playlond.employeeOtherInfo?.id ? true : false, statusSave: !!payload.employeeOtherInfo?.id ? true : false,
} || {}, } || {},
), ),
employeeWork: structuredClone( employeeWork: structuredClone(
playlond.employeeWork?.length === 0 payload.employeeWork?.length === 0
? defaultFormData.employeeWork ? defaultFormData.employeeWork
: playlond.employeeWork?.map((item) => ({ : payload.employeeWork?.map((item) => ({
...item, ...item,
statusSave: true, statusSave: true,
})), })),
), ),
file: _attach
? await Promise.all(
_attach.map(async (name) => {
const fragment = name.split('-');
const group = fragment.length === 1 ? 'other' : fragment.at(0);
return {
url: await employeeStore.getAttachment(_data.id, name),
name,
group,
};
}),
)
: [],
image: null, image: null,
}; };
currentFromDataEmployee.value = structuredClone(resetEmployeeData); currentFromDataEmployee.value = structuredClone(resetEmployeeData);
const foundBranch = await customerStore.fetchListCustomeBranchById( const foundBranch = await customerStore.fetchListCustomeBranchById(
playlond.customerBranchId, payload.customerBranchId,
); );
state.value.currentEmployeeCode = playlond.code; state.value.currentEmployeeCode = payload.code;
state.value.profileUrl = profileImageUrl || ' '; state.value.profileUrl = profileImageUrl || '';
profileImageUrl profileImageUrl
? (state.value.profileSubmit = true) ? (state.value.profileSubmit = true)
@ -849,8 +909,8 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.formDataEmployeeOwner = { ...foundBranch }; state.value.formDataEmployeeOwner = { ...foundBranch };
if ( if (
foundBranch.address === playlond.address && foundBranch.address === payload.address &&
foundBranch.zipCode === playlond.zipCode foundBranch.zipCode === payload.zipCode
) { ) {
state.value.formDataEmployeeSameAddr = true; state.value.formDataEmployeeSameAddr = true;
} else { } else {

View file

@ -16,6 +16,7 @@ import {
import axios from 'axios'; import axios from 'axios';
import useFlowStore from '../flow'; import useFlowStore from '../flow';
import { Employee } from '../employee/types'; import { Employee } from '../employee/types';
import { baseUrl } from '../utils';
const useCustomerStore = defineStore('api-customer', () => { const useCustomerStore = defineStore('api-customer', () => {
const flowStore = useFlowStore(); const flowStore = useFlowStore();
@ -208,6 +209,64 @@ const useCustomerStore = defineStore('api-customer', () => {
return res.data; return res.data;
} }
async function putAttachment(opts: {
branchId: string;
file: File;
filename?: string;
}) {
const res = await api.put(
`/customer-branch/${opts.branchId}/attachment/${opts.filename || opts.file.name}`,
opts.file,
{
headers: { 'X-Rtid': flowStore.rtid, 'Content-Type': opts.file.type },
onUploadProgress: (e) => console.log(e),
},
);
if (res.status >= 400) return false;
return true;
}
async function deleteAttachment(id: string, filename: string) {
const res = await api.delete(
`/customer-branch/${id}/attachment/${filename}`,
);
if (res.status >= 400) return false;
return true;
}
async function listAttachment(id: string) {
const res = await api.get<string[]>(`/customer-branch/${id}/attachment`, {
headers: { 'X-Rtid': flowStore.rtid },
});
if (res.status >= 400) return false;
return res.data;
}
async function getAttachment(id: string, filename: string, download = false) {
const url = `${baseUrl}/customer-branch/${id}/attachment/${filename}`;
const res = await api.get<string>(url);
if (download) {
fetch(res.data)
.then(async (res) => await res.blob())
.then((blob) => {
let a = document.createElement('a');
a.download = filename;
a.href = window.URL.createObjectURL(blob);
a.click();
a.remove();
});
}
return res.data;
}
async function create( async function create(
data: CustomerCreate, data: CustomerCreate,
flow?: { flow?: {
@ -395,7 +454,7 @@ const useCustomerStore = defineStore('api-customer', () => {
}); });
if (!res) return false; if (!res) return false;
if (file) await addBranchAttachment(res.data.id, { file }); // if (file) await addBranchAttachment(res.data.id, { file });
return res.data; return res.data;
} }
@ -443,7 +502,7 @@ const useCustomerStore = defineStore('api-customer', () => {
}, },
); );
if (file) await addBranchAttachment(id, { file }); // if (file) await addBranchAttachment(id, { file });
if (!res) return false; if (!res) return false;
@ -472,68 +531,6 @@ const useCustomerStore = defineStore('api-customer', () => {
return false; return false;
} }
async function addBranchAttachment(
branchId: string,
payload: BranchAttachmentCreate,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const list: { name: string; file: File }[] = [];
payload.file.forEach((v) => {
let filename = v.name;
if (list.some((v) => v.name === filename)) {
const dotIndex = filename.lastIndexOf('.');
const originalName =
dotIndex !== -1 && !filename.startsWith('.')
? filename.slice(0, dotIndex)
: filename;
const extension =
dotIndex !== -1 && !filename.startsWith('.')
? filename.slice(dotIndex)
: '';
let i = 0;
while (list.some((v) => v.name === filename)) {
filename = `${originalName} (${++i})`;
if (dotIndex !== -1) filename += extension;
}
}
list.push({ name: filename, file: v });
});
const res = await api.post<(BranchAttachment & { uploadUrl: string })[]>(
`/customer-branch/${branchId}/attachment`,
{ file: list.map((v) => v.name) },
{
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
);
await Promise.all(
res.data.map(async (a) => {
const found = list.find((b) => b.name === a.name)!;
await axios
.put(a.uploadUrl, found.file, {
headers: { 'Content-Type': found.file.type },
onUploadProgress: (e) => console.log(e),
})
.catch((e) => console.error(e));
}),
);
}
async function fetchListCustomeBranchById( async function fetchListCustomeBranchById(
branchId: string, branchId: string,
flow?: { flow?: {
@ -576,6 +573,11 @@ const useCustomerStore = defineStore('api-customer', () => {
fetchListCustomeBranchById, fetchListCustomeBranchById,
fetchBranchEmployee, fetchBranchEmployee,
listAttachment,
getAttachment,
putAttachment,
deleteAttachment,
}; };
}); });

View file

@ -106,7 +106,12 @@ export type CustomerBranchCreate = {
registerCompanyName: string; registerCompanyName: string;
statusSave?: boolean; statusSave?: boolean;
file?: File[]; file?: {
name?: string;
group?: string;
url?: string;
file?: File;
}[];
// id?: string; // id?: string;
// code?: string; // code?: string;

View file

@ -16,6 +16,7 @@ import {
import { CustomerBranch } from '../customer/types'; import { CustomerBranch } from '../customer/types';
import axios from 'axios'; import axios from 'axios';
import useFlowStore from '../flow'; import useFlowStore from '../flow';
import { baseUrl } from '../utils';
const useEmployeeStore = defineStore('api-employee', () => { const useEmployeeStore = defineStore('api-employee', () => {
const flowStore = useFlowStore(); const flowStore = useFlowStore();
@ -31,22 +32,15 @@ const useEmployeeStore = defineStore('api-employee', () => {
return false; return false;
} }
async function fetchList( async function fetchList(opts?: {
opts?: { page?: number;
page?: number; pageSize?: number;
pageSize?: number; query?: string;
query?: string; gender?: string;
gender?: string; status?: Status;
status?: Status; zipCode?: string;
zipCode?: string; customerId?: string;
customerId?: string; }) {
},
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const params = new URLSearchParams(); const params = new URLSearchParams();
for (const [k, v] of Object.entries(opts || {})) { for (const [k, v] of Object.entries(opts || {})) {
@ -58,11 +52,7 @@ const useEmployeeStore = defineStore('api-employee', () => {
const res = await api.get<Pagination<Employee[]>>( const res = await api.get<Pagination<Employee[]>>(
`/employee${(params && '?'.concat(query)) || ''}`, `/employee${(params && '?'.concat(query)) || ''}`,
{ {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}, },
); );
@ -73,58 +63,54 @@ const useEmployeeStore = defineStore('api-employee', () => {
return false; return false;
} }
async function create( async function create(data: EmployeeCreate) {
data: EmployeeCreate, const { id, code, image, file, ...payload } = data;
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const { id, code, image, ...payload } = data;
const res = await api.post< const res = await api.post<
Employee & { profileImageUrl: string; profileImageUploadUrl: string } Employee & { profileImageUrl: string; profileImageUploadUrl: string }
>('/employee', payload, { >('/employee', payload, {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
image &&
(await axios
.put(res.data.profileImageUploadUrl, image, {
headers: { 'Content-Type': image?.type },
onUploadProgress: (e) => console.log(e),
})
.catch((e) => console.error(e)));
if (!res) return false; if (!res) return false;
if (res.data.id) {
if (image) {
await api
.put(`/employee/${res.data.id}/image`, image, {
headers: { 'Content-Type': image?.type },
onUploadProgress: (e) => console.log(e),
})
.catch((e) => console.error(e));
}
if (file) {
const attachmentUpload = file.map(async ({ group, file }) => {
if (file) {
const _name = file.name;
const _ext = _name.split('.').at(-1);
let filename = (group || 'other') + '-' + Date.now();
if (_ext) filename = filename + '.' + _ext;
await uploadAttachment(res.data.id, file, filename);
}
});
await Promise.all(attachmentUpload);
}
}
return res.data; return res.data;
} }
async function createEmployeeCheckup( async function createEmployeeCheckup(
employeeId: string, employeeId: string,
data: EmployeeCheckupCreate, data: EmployeeCheckupCreate,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { id, statusSave, ...payload } = data; const { id, statusSave, ...payload } = data;
const res = await api.post<EmployeeCheckupCreate>( const res = await api.post<EmployeeCheckupCreate>(
`/employee/${employeeId}/checkup`, `/employee/${employeeId}/checkup`,
{ ...payload }, { ...payload },
{ { headers: { 'X-Rtid': flowStore.rtid } },
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
); );
if (!res) return false; if (!res) return false;
@ -135,23 +121,12 @@ const useEmployeeStore = defineStore('api-employee', () => {
async function createEmployeeWork( async function createEmployeeWork(
employeeId: string, employeeId: string,
data: EmployeeWorkCreate, data: EmployeeWorkCreate,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { id, ...payload } = data; const { id, ...payload } = data;
const res = await api.post<EmployeeWorkCreate>( const res = await api.post<EmployeeWorkCreate>(
`/employee/${employeeId}/work`, `/employee/${employeeId}/work`,
{ ...payload }, { ...payload },
{ { headers: { 'X-Rtid': flowStore.rtid } },
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
); );
if (!res) return false; if (!res) return false;
@ -162,22 +137,13 @@ const useEmployeeStore = defineStore('api-employee', () => {
async function createEmployeeOtherInfo( async function createEmployeeOtherInfo(
employeeId: string, employeeId: string,
data: EmployeeOtherCreate, data: EmployeeOtherCreate,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { id, statusSave, ...payload } = data; const { id, statusSave, ...payload } = data;
const res = await api.post<EmployeeOtherCreate>( const res = await api.post<EmployeeOtherCreate>(
`/employee/${employeeId}/other-info`, `/employee/${employeeId}/other-info`,
{ ...payload }, { ...payload },
{ {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}, },
); );
@ -189,11 +155,6 @@ const useEmployeeStore = defineStore('api-employee', () => {
async function editByIdEmployeeCheckup( async function editByIdEmployeeCheckup(
employeeOfId: string, employeeOfId: string,
data: Partial<EmployeeCheckupCreate>, data: Partial<EmployeeCheckupCreate>,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { const {
id, id,
@ -208,13 +169,7 @@ const useEmployeeStore = defineStore('api-employee', () => {
const res = await api.put<EmployeeCheckupCreate>( const res = await api.put<EmployeeCheckupCreate>(
`/employee/${employeeOfId}/checkup/${id}`, `/employee/${employeeOfId}/checkup/${id}`,
{ ...payload }, { ...payload },
{ { headers: { 'X-Rtid': flowStore.rtid } },
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
); );
if (!res) return false; if (!res) return false;
@ -225,11 +180,6 @@ const useEmployeeStore = defineStore('api-employee', () => {
async function editByIdEmployeeWork( async function editByIdEmployeeWork(
employeeOfId: string, employeeOfId: string,
data: Partial<EmployeeWorkCreate>, data: Partial<EmployeeWorkCreate>,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { const {
id, id,
@ -244,13 +194,7 @@ const useEmployeeStore = defineStore('api-employee', () => {
const res = await api.put<EmployeeWorkCreate>( const res = await api.put<EmployeeWorkCreate>(
`/employee/${employeeOfId}/work/${id}`, `/employee/${employeeOfId}/work/${id}`,
{ ...payload }, { ...payload },
{ { headers: { 'X-Rtid': flowStore.rtid } },
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
); );
if (!res) return false; if (!res) return false;
@ -261,11 +205,6 @@ const useEmployeeStore = defineStore('api-employee', () => {
async function editByIdEmployeeOtherInfo( async function editByIdEmployeeOtherInfo(
employeeOfId: string, employeeOfId: string,
data: Partial<EmployeeOtherCreate>, data: Partial<EmployeeOtherCreate>,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) { ) {
const { const {
id, id,
@ -280,13 +219,7 @@ const useEmployeeStore = defineStore('api-employee', () => {
const res = await api.put<EmployeeOtherCreate>( const res = await api.put<EmployeeOtherCreate>(
`/employee/${employeeOfId}/other-info/${id}`, `/employee/${employeeOfId}/other-info/${id}`,
{ ...payload }, { ...payload },
{ { headers: { 'X-Rtid': flowStore.rtid } },
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
); );
if (!res) return false; if (!res) return false;
@ -294,59 +227,50 @@ const useEmployeeStore = defineStore('api-employee', () => {
return res.data; return res.data;
} }
async function editById( async function editById(employeeId: string, data: Partial<EmployeeCreate>) {
employeeId: string, const { id, code, image, file, ...payload } = data;
data: Partial<EmployeeCreate>,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const { id, code, image, ...payload } = data;
const res = await api.put< const res = await api.put<
Employee & { imageUrl: string; profileImageUploadUrl: string } Employee & { imageUrl: string; profileImageUploadUrl: string }
>(`/employee/${employeeId}`, payload, { >(`/employee/${employeeId}`, payload, {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
image && if (!res) return false;
(await axios
.put(res.data.profileImageUploadUrl, image, { if (image) {
headers: { 'Content-Type': image.type }, await api
.put(`/employee/${employeeId}/image`, image, {
headers: { 'Content-Type': image?.type },
onUploadProgress: (e) => console.log(e), onUploadProgress: (e) => console.log(e),
}) })
.catch((e) => console.error(e))); .catch((e) => console.error(e));
}
if (file) {
const attachmentUpload = file.map(async ({ group, file }) => {
if (file) {
const _name = file.name;
const _ext = _name.split('.').at(-1);
if (!res) return false; let filename = (group || 'other') + '-' + Date.now();
if (_ext) filename = filename + '.' + _ext;
await uploadAttachment(employeeId, file, filename);
}
});
await Promise.all(attachmentUpload);
}
return res.data; return res.data;
} }
async function deleteByIdCheckUp( async function deleteByIdCheckUp(opts: {
opts: { employeeId: string;
employeeId: string; checkUpId?: string;
checkUpId?: string; }) {
},
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.delete<Employee>( const res = await api.delete<Employee>(
`/employee/${opts.employeeId}/checkup/${opts.checkUpId}`, `/employee/${opts.employeeId}/checkup/${opts.checkUpId}`,
{ { headers: { 'X-Rtid': flowStore.rtid } },
headers: {
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
},
); );
if (!res) return false; if (!res) return false;
@ -355,25 +279,11 @@ const useEmployeeStore = defineStore('api-employee', () => {
return false; return false;
} }
async function deleteByIdWork( async function deleteByIdWork(opts: { employeeId: string; workId?: string }) {
opts: {
employeeId: string;
workId?: string;
},
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.delete<Employee>( const res = await api.delete<Employee>(
`/employee/${opts.employeeId}/work/${opts.workId}`, `/employee/${opts.employeeId}/work/${opts.workId}`,
{ {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}, },
); );
@ -383,20 +293,9 @@ const useEmployeeStore = defineStore('api-employee', () => {
return false; return false;
} }
async function deleteById( async function deleteById(id: string) {
id: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.delete<Employee>(`/employee/${id}`, { const res = await api.delete<Employee>(`/employee/${id}`, {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
if (!res) return false; if (!res) return false;
@ -405,82 +304,38 @@ const useEmployeeStore = defineStore('api-employee', () => {
return false; return false;
} }
async function fetchCheckup( async function fetchCheckup(id: string) {
id: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get<EmployeeCheckup[]>(`/employee/${id}/checkup`, { const res = await api.get<EmployeeCheckup[]>(`/employee/${id}/checkup`, {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
if (res && res.status === 200) { if (res && res.status === 200) {
return res.data; return res.data;
} }
} }
async function fetchWork( async function fetchWork(id: string) {
id: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get<EmployeeWork[]>(`/employee/${id}/work`, { const res = await api.get<EmployeeWork[]>(`/employee/${id}/work`, {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
if (res && res.status === 200) { if (res && res.status === 200) {
return res.data; return res.data;
} }
} }
async function fetchOther( async function fetchOther(id: string) {
id: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get<EmployeeOther>(`/employee/${id}/other-info`, { const res = await api.get<EmployeeOther>(`/employee/${id}/other-info`, {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
if (res && res.status === 200) { if (res && res.status === 200) {
return res.data; return res.data;
} }
} }
async function getStatsEmployee( async function getStatsEmployee(customerBranchId?: string) {
customerBranchId?: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get( const res = await api.get(
`/employee/stats${customerBranchId ? '?customerBranchId=' + customerBranchId : ''}/`, `/employee/stats${customerBranchId ? '?customerBranchId=' + customerBranchId : ''}/`,
{ {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}, },
); );
if (res && res.status === 200) { if (res && res.status === 200) {
@ -488,19 +343,11 @@ const useEmployeeStore = defineStore('api-employee', () => {
} }
} }
async function getStatsEmployeeGender( async function getStatsEmployeeGender(opts?: {
opts?: { customerBranchId?: string;
customerBranchId?: string; status?: 'CREATED' | 'ACTIVE' | 'INACTIVE';
status?: 'CREATED' | 'ACTIVE' | 'INACTIVE'; query?: string;
query?: string; }) {
},
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const params = new URLSearchParams(); const params = new URLSearchParams();
for (const [k, v] of Object.entries(opts || {})) { for (const [k, v] of Object.entries(opts || {})) {
@ -512,11 +359,7 @@ const useEmployeeStore = defineStore('api-employee', () => {
const res = await api.get( const res = await api.get(
`/employee/stats/gender${(params && '?'.concat(query)) || ''}`, `/employee/stats/gender${(params && '?'.concat(query)) || ''}`,
{ {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}, },
); );
if (res && res.status === 200) { if (res && res.status === 200) {
@ -524,26 +367,66 @@ const useEmployeeStore = defineStore('api-employee', () => {
} }
} }
async function getEditHistory( async function getEditHistory(employeeId: string) {
employeeId: string,
flow?: {
sessionId?: string;
refTransactionId?: string;
transactionId?: string;
},
) {
const res = await api.get(`/employee/${employeeId}/edit-history`, { const res = await api.get(`/employee/${employeeId}/edit-history`, {
headers: { headers: { 'X-Rtid': flowStore.rtid },
'X-Session-Id': flow?.sessionId,
'X-Rtid': flow?.refTransactionId || flowStore.rtid,
'X-Tid': flow?.transactionId,
},
}); });
if (res && res.status === 200) { if (res && res.status === 200) {
return res.data; return res.data;
} }
} }
async function listAttachment(employeeId: string) {
const res = await api.get<string[]>(`/employee/${employeeId}/attachment`);
return !res || res.status >= 400 ? false : res.data;
}
async function getAttachment(
employeeId: string,
filename: string,
download = false,
) {
const url = `${baseUrl}/employee/${employeeId}/attachment/${filename}`;
const res = await api.get<string>(url);
if (download) {
fetch(res.data)
.then(async (res) => await res.blob())
.then((blob) => {
let a = document.createElement('a');
a.download = filename;
a.href = window.URL.createObjectURL(blob);
a.click();
a.remove();
});
}
return res.data;
}
async function uploadAttachment(
employeeId: string,
file: File,
filename?: string,
) {
const res = await api.put(
`/employee/${employeeId}/attachment/${filename || file.name}`,
file,
{
headers: {
'X-Rtid': flowStore.rtid,
'Content-Type': file.type,
},
},
);
return !(!res || res.status >= 400);
}
async function deleteAttachment(employeeId: string, filename: string) {
const res = await api.delete(
`/employee/${employeeId}/attachment/${filename}`,
{ headers: { 'X-Rtid': flowStore.rtid } },
);
return !(!res || res.status >= 400);
}
return { return {
data, data,
globalOption, globalOption,
@ -571,6 +454,11 @@ const useEmployeeStore = defineStore('api-employee', () => {
createEmployeeOtherInfo, createEmployeeOtherInfo,
editByIdEmployeeOtherInfo, editByIdEmployeeOtherInfo,
listAttachment,
getAttachment,
uploadAttachment,
deleteAttachment,
}; };
}); });

View file

@ -11,6 +11,7 @@ export type Employee = {
createdByUserId: string; createdByUserId: string;
updatedByUserId: string; updatedByUserId: string;
statusOrder: string; statusOrder: string;
namePrefix: string;
firstName: string; firstName: string;
firstNameEN: string; firstNameEN: string;
lastName: string; lastName: string;
@ -107,6 +108,13 @@ export type EmployeeCreate = {
employeeCheckup?: EmployeeCheckupCreate[]; employeeCheckup?: EmployeeCheckupCreate[];
employeeOtherInfo?: EmployeeOtherCreate; employeeOtherInfo?: EmployeeOtherCreate;
file?: {
name?: string;
group?: string;
url?: string;
file?: File;
}[];
}; };
export type EmployeeUpdate = { export type EmployeeUpdate = {

View file

@ -18,12 +18,19 @@ const useOcr = defineStore('useOcrStore', () => {
}, },
) { ) {
const res = await axios const res = await axios
.post(`${baseUrl}/`, payload.file, { .post<{
document: string;
fields: { name: string; value: string }[];
result: string;
}>(`${baseUrl}/`, payload.file, {
headers: { 'Content-Type': payload.file?.type }, headers: { 'Content-Type': payload.file?.type },
onUploadProgress: (e) => console.log(e), onUploadProgress: (e) => console.log(e),
}) })
.catch((e) => console.error(e)); .catch((e) => console.error(e));
// ]);
if (!res || res.status >= 400) return false;
return res.data;
} }
return { return {