diff --git a/package.json b/package.json
index be62c38a..9638bb29 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"apexcharts": "^4.5.0",
"axios": "^1.8.4",
"cropperjs": "^1.6.2",
+ "dayjs": "^1.11.13",
"highlight.js": "^11.11.1",
"keycloak-js": "^25.0.6",
"markdown-it": "^14.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 47c2eb49..7cb78cc6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -29,6 +29,9 @@ importers:
cropperjs:
specifier: ^1.6.2
version: 1.6.2
+ dayjs:
+ specifier: ^1.11.13
+ version: 1.11.13
highlight.js:
specifier: ^11.11.1
version: 11.11.1
@@ -1473,6 +1476,9 @@ packages:
date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+ dayjs@1.11.13:
+ resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
+
de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -5272,6 +5278,8 @@ snapshots:
date-fns@3.6.0: {}
+ dayjs@1.11.13: {}
+
de-indent@1.0.2: {}
debug@2.6.9:
diff --git a/public/images/customer-CORP-avartar-female.png b/public/images/customer-CORP-avartar-female.png
index 446ef866..4cbb894f 100644
Binary files a/public/images/customer-CORP-avartar-female.png and b/public/images/customer-CORP-avartar-female.png differ
diff --git a/public/images/customer-CORP-avartar-male.png b/public/images/customer-CORP-avartar-male.png
index 6ad95d7d..ae177f60 100644
Binary files a/public/images/customer-CORP-avartar-male.png and b/public/images/customer-CORP-avartar-male.png differ
diff --git a/public/images/customer-PERS-avartar-female.png b/public/images/customer-PERS-avartar-female.png
index ca0a2bf1..c3ba574e 100644
Binary files a/public/images/customer-PERS-avartar-female.png and b/public/images/customer-PERS-avartar-female.png differ
diff --git a/public/images/customer-PERS-avartar-male.png b/public/images/customer-PERS-avartar-male.png
index e9fd15fe..ce0ab20c 100644
Binary files a/public/images/customer-PERS-avartar-male.png and b/public/images/customer-PERS-avartar-male.png differ
diff --git a/public/images/employee-avatar-female.png b/public/images/employee-avatar-female.png
index 66ace3a0..ce9370f1 100644
Binary files a/public/images/employee-avatar-female.png and b/public/images/employee-avatar-female.png differ
diff --git a/public/images/employee-avatar-male.png b/public/images/employee-avatar-male.png
index a8daa8ff..aaf5fb1f 100644
Binary files a/public/images/employee-avatar-male.png and b/public/images/employee-avatar-male.png differ
diff --git a/public/img-group.png b/public/img-group.png
new file mode 100644
index 00000000..0493168b
Binary files /dev/null and b/public/img-group.png differ
diff --git a/public/no-img-female.png b/public/no-img-female.png
index 4e177dca..95f959ff 100644
Binary files a/public/no-img-female.png and b/public/no-img-female.png differ
diff --git a/public/no-img-man.png b/public/no-img-man.png
index 861f356a..f0ccba15 100644
Binary files a/public/no-img-man.png and b/public/no-img-man.png differ
diff --git a/public/option/option.json b/public/option/option.json
index c5ea33cd..963c4ad2 100644
--- a/public/option/option.json
+++ b/public/option/option.json
@@ -183,15 +183,15 @@
"prefix": [
{
- "label": "Mr",
+ "label": "MR",
"value": "mr"
},
{
- "label": "Mrs",
+ "label": "MRS",
"value": "mrs"
},
{
- "label": "Miss",
+ "label": "MISS",
"value": "miss"
}
],
diff --git a/quasar.config.ts b/quasar.config.ts
index cc0715ad..38bb94c1 100644
--- a/quasar.config.ts
+++ b/quasar.config.ts
@@ -31,7 +31,7 @@ export default defineConfig((ctx) => {
devServer: {
host: '0.0.0.0',
open: false,
- port: 5173,
+ port: 5174,
},
framework: {
config: {},
diff --git a/src/components/01_branch-management/BranchCard.vue b/src/components/01_branch-management/BranchCard.vue
index 83c268ba..c10d63fc 100644
--- a/src/components/01_branch-management/BranchCard.vue
+++ b/src/components/01_branch-management/BranchCard.vue
@@ -89,15 +89,7 @@ defineProps<{
-
+
-
- (virtual = v === 'Virtual')"
- :rules="[(val) => val && val.length > 0]"
- :error-message="$t('form.error.required')"
- >
-
-
-
- {{ $t('general.noData') }}
-
-
-
-
diff --git a/src/components/02_personnel-management/FormByType.vue b/src/components/02_personnel-management/FormByType.vue
index 1537dc07..d1709858 100644
--- a/src/components/02_personnel-management/FormByType.vue
+++ b/src/components/02_personnel-management/FormByType.vue
@@ -29,19 +29,18 @@ const discountCondition = defineModel
(
const sourceNationality = defineModel(
'sourceNationality',
);
-const importNationality = defineModel(
+const importNationality = defineModel(
'importNationality',
);
const trainingPlace = defineModel('trainingPlace');
const checkpoint = defineModel('checkpoint');
-const agencyFile = defineModel('agencyFile');
-const agencyFileList =
- defineModel<{ name: string; url: string }[]>('agencyFileList');
+const userFile = defineModel('userFile');
+const userFileList =
+ defineModel<{ name: string; url: string }[]>('userFileList');
const remark = defineModel('remark');
const agencyStatus = defineModel('agencyStatus');
const attachmentRef = ref();
-const checkpointENOption = ref([]);
defineProps<{
dense?: boolean;
@@ -71,18 +70,12 @@ function deleteFile(name: string) {
userStore.deleteAttachment(userId.value, payload);
const result = await userStore.fetchAttachment(userId.value);
if (result) {
- agencyFileList.value = result;
+ userFileList.value = result;
}
},
cancel: () => {},
});
}
-
-onMounted(async () => {
- const resultOption = await fetch('/option/option.json');
- const rawOption = await resultOption.json();
- checkpointENOption.value = rawOption.eng.border;
-});
@@ -140,11 +133,12 @@ onMounted(async () => {
/>
{
(v) => (typeof v === 'string' ? (sourceNationality = v) : '')
"
/>
+
(typeof v === 'string' ? (importNationality = v) : '')
- "
- />
- (typeof v === 'string' ? (trainingPlace = v) : '')
- "
/>
+
{
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
"
/>
+
(typeof v === 'string' ? (checkpoint = v) : '')
+ (v) => (typeof v === 'string' ? (trainingPlace = v) : '')
"
/>
@@ -253,7 +237,7 @@ onMounted(async () => {
value: AgencyStatus.Blacklist,
},
]"
- class="col-md-6 col-12"
+ class="col-md-4 col-12"
:readonly
:label="$t('personnel.form.agencyStatus')"
clearable
@@ -275,76 +259,78 @@ onMounted(async () => {
"
@clear="remark = ''"
/>
-
-
-
-
-
-
-
- {{ file.file.name }}
-
-
-
-
-
+
-
-
- openNewTab(item.url)"
- >
-
-
-
- {{ item.name }}
-
-
+
+
+
+
+
+
+
+ {{ file.file.name }}
+
+
+
+
+
+
+
+
+ openNewTab(item.url)"
+ >
+
+
+
+ {{ item.name }}
-
-
-
-
+
+
+
+
+
diff --git a/src/components/02_personnel-management/FormPerson.vue b/src/components/02_personnel-management/FormPerson.vue
index 0204bb47..cb0463af 100644
--- a/src/components/02_personnel-management/FormPerson.vue
+++ b/src/components/02_personnel-management/FormPerson.vue
@@ -1,10 +1,8 @@
@@ -171,41 +96,19 @@ watch(
for="input-citizen-id"
/>
- (typeof v === 'string' ? (prefixName = v) : '')
+ :rules="
+ agency ? [] : [(val: string) => !!val || $t('form.error.required')]
"
- @clear="prefixName = ''"
- :rules="[(val: string) => !!val || $t('form.error.required')]"
- >
-
-
-
- {{ $t('general.noData') }}
-
-
-
-
+ :label="$t('personnel.form.prefixName')"
+ class="col-md-1 col-6"
+ v-model="prefixName"
+ />
- (typeof v === 'string' ? (prefixName = v) : '')
- "
- @clear="prefixName = ''"
:rules="[(val: string) => !!val || $t('form.error.required')]"
- >
-
-
-
- {{ $t('general.noData') }}
-
-
-
-
+ label="Prefix"
+ class="col-md-1 col-6"
+ v-model="prefixName"
+ />
(typeof v === 'string' ? (email = v) : '')"
@@ -405,39 +275,16 @@ watch(
- (typeof v === 'string' ? (gender = v) : '')"
- @clear="gender = ''"
- >
-
-
-
- {{ $t('general.noData') }}
-
-
-
-
+ class="col-md-2 col-6"
+ v-model="gender"
+ />
-
-
-
-
- {{ $t('general.noData') }}
-
-
-
-
-
+
+
+ (typeof v === 'string' ? (contactName = v) : '')
+ "
+ />
+
+ (typeof v === 'string' ? (contactTel = v) : '')
+ "
>
-
-
-
- {{ $t('general.noData') }}
-
-
+
+
-
+
diff --git a/src/components/03_customer-management/DialogEmployee.vue b/src/components/03_customer-management/DialogEmployee.vue
new file mode 100644
index 00000000..db428f83
--- /dev/null
+++ b/src/components/03_customer-management/DialogEmployee.vue
@@ -0,0 +1,1656 @@
+
+
+
+
+
{
+ employeeFormState.imageDialog = true;
+ employeeFormState.isImageEdit = false;
+ }
+ "
+ @edit="
+ () => {
+ if (currentFromDataEmployee.id) {
+ fetchImageList(
+ currentFromDataEmployee.id,
+ currentFromDataEmployee.selectedImage || '',
+ 'employee',
+ );
+ }
+ employeeFormState.imageDialog =
+ employeeFormState.isImageEdit = true;
+ }
+ "
+ @update:toggle-status="
+ () => {
+ currentFromDataEmployee.status =
+ currentFromDataEmployee.status === 'CREATED'
+ ? 'INACTIVE'
+ : 'CREATED';
+ }
+ "
+ />
+
+
+
+
+
+ {
+ if (!v) return;
+ if (!currentFromDataEmployee.id) return;
+ await employeeStore.addImageList(
+ v,
+ currentFromDataEmployee.id,
+ Date.now().toString(),
+ );
+ await fetchImageList(
+ currentFromDataEmployee.id,
+ currentFromDataEmployee.selectedImage || '',
+ 'employee',
+ );
+ }
+ "
+ @remove-image="
+ async (v) => {
+ if (!v) return;
+ if (!currentFromDataEmployee.id) return;
+ const name = v.split('/').pop() || '';
+ await employeeStore.deleteImageByName(currentFromDataEmployee.id, name);
+ await fetchImageList(
+ currentFromDataEmployee.id,
+ currentFromDataEmployee.selectedImage || '',
+ 'employee',
+ );
+ }
+ "
+ @submit="
+ async (v) => {
+ if (employeeFormState.dialogModal && !currentFromDataEmployee.id) {
+ employeeFormState.profileUrl = v;
+ employeeFormState.imageDialog = false;
+ } else {
+ refreshImageState = true;
+ employeeFormState.dialogType = 'edit';
+ currentFromDataEmployee.selectedImage = v;
+ employeeFormState.imageList
+ ? (employeeFormState.imageList.selectedImage = v)
+ : '';
+ employeeFormState.profileUrl = `${baseUrl}/employee/${currentFromDataEmployee.id && currentFromDataEmployee.id}/image/${v}`;
+ employeeFormStore.resetFormDataEmployee();
+ await employeeFormStore.submitPersonal(onCreateImageList);
+ employeeFormState.imageDialog = false;
+ refreshImageState = false;
+ employeeFormState.isEmployeeEdit = false;
+ employeeFormState.dialogType = 'info';
+ await fetchListEmployee();
+ }
+ }
+ "
+ >
+
+
+ {{ $t('general.image') }}
+ {{
+ $i18n.locale === 'eng'
+ ? `${currentFromDataEmployee.firstNameEN || currentFromDataEmployee.firstName} ${currentFromDataEmployee.lastNameEN || currentFromDataEmployee.lastName}`
+ : `${currentFromDataEmployee.firstName} ${currentFromDataEmployee.lastName}`
+ }}
+
+
+
+
+
+
+
diff --git a/src/components/03_customer-management/DrawerEmployee.vue b/src/components/03_customer-management/DrawerEmployee.vue
new file mode 100644
index 00000000..1c1d39bf
--- /dev/null
+++ b/src/components/03_customer-management/DrawerEmployee.vue
@@ -0,0 +1,1772 @@
+
+
+
+
+
+
+
{
+ employeeFormState.isImageEdit = false;
+ employeeFormStore.resetFormDataEmployee();
+ employeeFormState.isEmployeeEdit = false;
+ employeeFormState.dialogType = 'info';
+ employeeFormState.currentIndexPassport = -1;
+ }
+ "
+ @view="
+ () => {
+ employeeFormState.imageDialog = true;
+ employeeFormState.isImageEdit = false;
+ }
+ "
+ @edit="
+ employeeFormState.imageDialog = employeeFormState.isImageEdit = true
+ "
+ @update:toggle-status="
+ (v) => {
+ if (currentFromDataEmployee.id !== undefined)
+ $emit('changeStatus', v);
+ }
+ "
+ :active="currentFromDataEmployee.status !== 'INACTIVE'"
+ use-toggle
+ color="white"
+ icon="mdi-account-outline"
+ :bg-color="
+ employeeFormState.profileUrl
+ ? 'white'
+ : 'linear-gradient(135deg, rgba(43,137,223,1) 0%, rgba(230,51,81,1) 100%)'
+ "
+ v-model:current-tab="employeeFormState.currentTab"
+ v-model:toggle-status="currentFromDataEmployee.status"
+ fallback-cover="/images/employee-banner.png"
+ :title="
+ employeeFormState.currentEmployee
+ ? setPrefixName(
+ {
+ namePrefix: employeeFormState.currentEmployee.namePrefix,
+ firstName:
+ employeeFormState.currentEmployee.firstName ||
+ employeeFormState.currentEmployee.firstNameEN,
+ lastName:
+ employeeFormState.currentEmployee.lastName ||
+ employeeFormState.currentEmployee.lastNameEN,
+ firstNameEN: employeeFormState.currentEmployee.firstNameEN,
+ lastNameEN: employeeFormState.currentEmployee.lastNameEN,
+ },
+ { locale },
+ )
+ : '-'
+ "
+ :caption="currentFromDataEmployee.code"
+ :img="
+ `${baseUrl}/employee/${currentFromDataEmployee.id}/image/${currentFromDataEmployee.selectedImage}`.concat(
+ refreshImageState ? `?ts=${Date.now()}` : '',
+ ) || null
+ "
+ :fallbackImg="`/images/employee-avatar-${currentFromDataEmployee.gender === 'male' ? 'male' : 'female'}.png`"
+ :tabs-list="[
+ {
+ name: 'personalInfo',
+ label: $t('customerEmployee.form.group.personalInfo'),
+ },
+ {
+ name: 'passport',
+ label: $t('customerEmployee.fileType.passport'),
+ },
+ {
+ name: 'visa',
+ label: $t('customerEmployee.form.group.visa'),
+ },
+ {
+ name: 'healthCheck',
+ label: $t('customerEmployee.form.group.healthCheck'),
+ },
+ {
+ name: 'workHistory',
+ label: $t('customerEmployee.form.group.workHistory'),
+ },
+ { name: 'other', label: $t('customerEmployee.form.group.other') },
+ ]"
+ :toggle-title="$t('status.title')"
+ />
+
+
+
+
+
+ {
+ if (!v) return;
+ if (!currentFromDataEmployee.id) return;
+ await employeeStore.addImageList(
+ v,
+ currentFromDataEmployee.id,
+ Date.now().toString(),
+ );
+ await fetchImageList(
+ currentFromDataEmployee.id,
+ currentFromDataEmployee.selectedImage || '',
+ 'employee',
+ );
+ }
+ "
+ @remove-image="
+ async (v) => {
+ if (!v) return;
+ if (!currentFromDataEmployee.id) return;
+ const name = v.split('/').pop() || '';
+ await employeeStore.deleteImageByName(currentFromDataEmployee.id, name);
+ await fetchImageList(
+ currentFromDataEmployee.id,
+ currentFromDataEmployee.selectedImage || '',
+ 'employee',
+ );
+ }
+ "
+ @submit="
+ async (v) => {
+ if (employeeFormState.dialogModal && !currentFromDataEmployee.id) {
+ employeeFormState.profileUrl = v;
+ employeeFormState.imageDialog = false;
+ } else {
+ refreshImageState = true;
+ employeeFormState.dialogType = 'edit';
+ currentFromDataEmployee.selectedImage = v;
+ employeeFormState.imageList
+ ? (employeeFormState.imageList.selectedImage = v)
+ : '';
+ employeeFormState.profileUrl = `${baseUrl}/employee/${currentFromDataEmployee.id && currentFromDataEmployee.id}/image/${v}`;
+ employeeFormStore.resetFormDataEmployee();
+ await employeeFormStore.submitPersonal(onCreateImageList);
+ employeeFormState.imageDialog = false;
+ refreshImageState = false;
+ employeeFormState.isEmployeeEdit = false;
+ employeeFormState.dialogType = 'info';
+ await fetchListEmployee();
+ }
+ }
+ "
+ >
+
+
+ {{ $t('general.image') }}
+ {{
+ $i18n.locale === 'eng'
+ ? `${currentFromDataEmployee.firstNameEN || currentFromDataEmployee.firstName} ${currentFromDataEmployee.lastNameEN || currentFromDataEmployee.lastName}`
+ : `${currentFromDataEmployee.firstName} ${currentFromDataEmployee.lastName}`
+ }}
+
+
+
+
+
+
+
diff --git a/src/components/03_customer-management/FormEmployeeHealthCheck.vue b/src/components/03_customer-management/FormEmployeeHealthCheck.vue
index b08e7a70..f8690dbe 100644
--- a/src/components/03_customer-management/FormEmployeeHealthCheck.vue
+++ b/src/components/03_customer-management/FormEmployeeHealthCheck.vue
@@ -268,6 +268,7 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
('issuePlace');
const issueCountry = defineModel('issueCountry');
const issueDate = defineModel('issueDate');
const type = defineModel('type');
-const expireDate = defineModel('expireDate');
-const birthDate = defineModel('birthDate');
+const expireDate = defineModel('expireDate');
+const birthDate = defineModel('birthDate');
const workerStatus = defineModel('workerStatus');
const nationality = defineModel('nationality');
const gender = defineModel('gender');
@@ -32,6 +34,8 @@ const firstName = defineModel('firstName');
const namePrefix = defineModel('namePrefix');
const passportNumber = defineModel('passportNumber');
+const file = defineModel('file');
+
const passportValidator = /[a-zA-Z]{1}[a-zA-Z0-9]{1}[0-9]{5,7}$/;
const genderOptions = ref[]>([]);
@@ -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(
-
-
+
+ browse()"
+ :disable="readonly"
+ >
('arrivalAt');
const arrivalTMNo = defineModel('arrivalTmNo');
const arrivalTM = defineModel('arrivalTm');
const mrz = defineModel('mrz');
-const entryCount = defineModel('entryCount');
+const entryCount = defineModel('entryCount');
const issuePlace = defineModel('issuePlace');
const issueCountry = defineModel('issueCountry');
const issueDate = defineModel('visaIssueDate');
-const type = defineModel('visaType');
-const expireDate = defineModel('expireDate');
+const type = defineModel('type');
+const expireDate = defineModel('expireDate');
const remark = defineModel('remark');
const workerType = defineModel('workerType');
-const number = defineModel('visaNumber');
+const number = defineModel('number');
+const reportDate = defineModel('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;
@@ -78,6 +80,12 @@ onMounted(async () => {
await fetchProvince();
});
+const visaIssueCountryOptions = ref[]>([]);
+let visaIssueCountryFilter: (
+ value: string,
+ update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
+) => void;
+
const visaTypeOptions = ref[]>([]);
let visaTypeFilter: (
value: string,
@@ -97,6 +105,12 @@ onMounted(() => {
'label',
);
+ visaIssueCountryFilter = selectFilterOptionRefMod(
+ ref(optionStore.globalOption?.nationality),
+ visaIssueCountryOptions,
+ 'label',
+ );
+
workerTypeFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.workerType),
workerTypeOptions,
@@ -107,8 +121,14 @@ onMounted(() => {
watch(
() => optionStore.globalOption,
() => {
+ visaIssueCountryFilter = selectFilterOptionRefMod(
+ ref(optionStore.globalOption?.nationality),
+ visaIssueCountryOptions,
+ 'label',
+ );
+
visaTypeFilter = selectFilterOptionRefMod(
- optionStore.globalOption.nationality,
+ optionStore.globalOption.visaType,
visaTypeOptions,
'label',
);
@@ -120,6 +140,10 @@ watch(
);
},
);
+//
+// watch([() => issueDate.value], () => {
+// reportDate.value = calculate90DayNext(issueDate.value);
+// });
@@ -133,7 +157,7 @@ watch(
name="mdi-passport"
style="background-color: var(--surface-3)"
/>
- {{ title }}
+ {{ $t(title) }}
@@ -422,11 +448,11 @@ watch(
class="col-md-4 col-6"
:dense="dense"
:readonly="readonly"
- :options="visaTypeOptions"
+ :options="visaIssueCountryOptions"
:hide-dropdown-icon="readonly"
:for="`${prefixId}-select-issue-country`"
:label="$t('customerEmployee.form.issueCountry')"
- @filter="visaTypeFilter"
+ @filter="visaIssueCountryFilter"
:model-value="readonly ? issueCountry || '-' : issueCountry"
@update:model-value="
(v) => (typeof v === 'string' ? (issueCountry = v) : '')
diff --git a/src/components/03_customer-management/TableEmpoloyee.vue b/src/components/03_customer-management/TableEmpoloyee.vue
index e5e49b51..000c701a 100644
--- a/src/components/03_customer-management/TableEmpoloyee.vue
+++ b/src/components/03_customer-management/TableEmpoloyee.vue
@@ -22,6 +22,8 @@ const prop = withDefaults(
inTable?: boolean;
addButton?: boolean;
prefixId?: string;
+ hideAction?: boolean;
+ hideDelete?: boolean;
}>(),
{
gridView: false,
@@ -139,8 +141,9 @@ defineEmits<{
();
const { t } = useI18n();
const userStore = useUserStore();
const optionStore = useOptionStore();
+const workflowStore = useWorkflowTemplate();
const userInTable = defineModel('userInTable', {
default: [],
@@ -51,7 +56,9 @@ let objectOptions = [
const options = ref(objectOptions);
const role = ref([]);
const userList = ref([]);
+const groupList = ref([]);
const responsiblePersonSearch = ref('');
+const responsibleMenu = ref(false);
async function getUserList(opts?: { query: string }) {
const resUser = await userStore.fetchList({
@@ -60,10 +67,10 @@ async function getUserList(opts?: { query: string }) {
if (resUser) userList.value = resUser.result;
}
-// async function getUserById(responsiblePersonId: string) {
-// const resUser = await userStore.fetchById(responsiblePersonId);
-// if (resUser) userInTable.value.push(resUser);
-// }
+async function getGroupList() {
+ const resGroup = await workflowStore.getGroupList();
+ if (resGroup) groupList.value = resGroup;
+}
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
const currStep = flowData.value.step[stepIndex];
@@ -78,6 +85,7 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
userInTable.value[stepIndex] = {
name: flowData.value.step[stepIndex].name,
responsiblePerson: [],
+ responsibleGroup: [],
};
}
@@ -101,6 +109,33 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
}
}
+function selectResponsibleGroup(stepIndex: number, responsibleGroup: string) {
+ const currStep = flowData.value.step[stepIndex];
+ const existGroupIndex = currStep.responsibleGroup?.findIndex(
+ (p) => p === responsibleGroup,
+ );
+
+ if (existGroupIndex === -1) {
+ currStep.responsibleGroup?.push(responsibleGroup);
+
+ if (!userInTable.value[stepIndex]) {
+ userInTable.value[stepIndex] = {
+ name: flowData.value.step[stepIndex].name,
+ responsiblePerson: [],
+ responsibleGroup: [],
+ };
+ }
+
+ userInTable.value[stepIndex]?.responsibleGroup.push(responsibleGroup);
+ } else {
+ currStep.responsibleGroup?.splice(Number(existGroupIndex), 1);
+ userInTable.value[stepIndex]?.responsibleGroup.splice(
+ Number(existGroupIndex),
+ 1,
+ );
+ }
+}
+
function selectItem(
val: Record,
responsibleInstitution?: string[],
@@ -142,6 +177,7 @@ watch(
onMounted(async () => {
role.value = getRole() || [];
await getUserList();
+ await getGroupList();
await userStore.fetchHqOption();
});
@@ -166,6 +202,7 @@ onMounted(async () => {
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
>
{
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
- {{
- `${optionStore.mapOption(person.namePrefix || '')} ${
- $i18n.locale === 'eng'
- ? person.firstNameEN
- : person.firstName
- } ${
- $i18n.locale === 'eng'
- ? person.lastNameEN
- : person.lastName
- }`
- }}
-
-
- {{ person.code }}
-
-
+
+
+
+
+
+
+
+
+
+
+ {{
+ `${optionStore.mapOption(person.namePrefix || '')} ${
+ $i18n.locale === 'eng'
+ ? person.firstNameEN
+ : person.firstName
+ } ${
+ $i18n.locale === 'eng'
+ ? person.lastNameEN
+ : person.lastName
+ }`
+ }}
+
+
+ {{ person.code }}
+
-
-
+
-
-
+
+ {{ $t('general.group') }}
+
+
+
+
+
+
+ {{ group }}
+
+
+
+
+
+
+ (responsibleMenu = true)"
+ @before-hide="() => (responsibleMenu = false)"
+ >
{
{{ $t('general.noData') }}
{
{{ $t('personnel.MESSENGER') }}
{
+
+
+ {{ $t('general.group') }}
+
+
+ {{ $t('general.noData') }}
+
+
+
+
+
+
+
+
+ {{ group.name }}
+
+
+
-
+
{
:deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) {
visibility: hidden;
}
+
+.transition-rotate {
+ transition: transform 0.3s ease;
+}
+.rotated {
+ transform: rotate(180deg);
+}
diff --git a/src/components/04_product-service/PriceDataComponent.vue b/src/components/04_product-service/PriceDataComponent.vue
index 59043144..d005c043 100644
--- a/src/components/04_product-service/PriceDataComponent.vue
+++ b/src/components/04_product-service/PriceDataComponent.vue
@@ -167,26 +167,28 @@ withDefaults(
@@ -271,6 +276,8 @@ withDefaults(
flat
outlined
dense
+ :readonly
+ :hide-dropdown-icon="readonly"
v-model="vatIncluded"
>
diff --git a/src/components/04_product-service/WorkManagementComponent.vue b/src/components/04_product-service/WorkManagementComponent.vue
index e79872d9..86e79951 100644
--- a/src/components/04_product-service/WorkManagementComponent.vue
+++ b/src/components/04_product-service/WorkManagementComponent.vue
@@ -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);
}
diff --git a/src/components/04_product-service/WorkNameManagement.vue b/src/components/04_product-service/WorkNameManagement.vue
index 2bde41ce..81854b9e 100644
--- a/src/components/04_product-service/WorkNameManagement.vue
+++ b/src/components/04_product-service/WorkNameManagement.vue
@@ -1,7 +1,6 @@
@@ -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'),
+ ]"
/>
(typeof v === 'string' ? (contactTel = v) : '')
@@ -148,6 +182,78 @@ type Options = { label: string; value: string };
/>
+
+
+
+
+
+
+
+
+ {{ file.file.name }}
+
+
+
+
+
+
+
+
+ openNewTab(item.url)"
+ >
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
diff --git a/src/components/08_request-list/DataDisplay.vue b/src/components/08_request-list/DataDisplay.vue
index 30a8e52b..f36acb4f 100644
--- a/src/components/08_request-list/DataDisplay.vue
+++ b/src/components/08_request-list/DataDisplay.vue
@@ -27,26 +27,38 @@ withDefaults(
class="app-text-muted q-pr-sm"
:width="iconSize || '2rem'"
/>
-
-
+
+
{{ label }}
-
+
{{ value }}
{{ value }}
-
+
{{ item }}
,
diff --git a/src/components/11_credit-note/FormCredit.vue b/src/components/11_credit-note/FormCredit.vue
index b6e2bf1f..5e4ee55b 100644
--- a/src/components/11_credit-note/FormCredit.vue
+++ b/src/components/11_credit-note/FormCredit.vue
@@ -8,6 +8,10 @@ defineProps<{
const quotationId = defineModel('quotationId', {
required: true,
});
+const isDebitNote = defineModel('isDebitNote', {
+ required: false,
+ default: false,
+});
@@ -37,6 +41,7 @@ const quotationId = defineModel('quotationId', {
cancelIncludeDebitNote: true,
hasCancel: true,
}"
+ @selected="(v) => (isDebitNote = v.isDebitNote)"
/>
diff --git a/src/components/DialogForm.vue b/src/components/DialogForm.vue
index 84319bcf..fb29a4ca 100644
--- a/src/components/DialogForm.vue
+++ b/src/components/DialogForm.vue
@@ -42,6 +42,15 @@ defineProps<{
const modal = defineModel('modal', { default: false });
const currentTab = defineModel('currentTab');
+
+async function onValidationError(ref: any) {
+ const el = ref.$el as Element;
+ el.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ inline: 'nearest',
+ });
+}
('currentTab');
@submit.prevent
@validation-success="submit"
class="column full-height"
+ @validation-error="onValidationError"
>
('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',
+ });
+}
(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"
>
+import { Icon } from '@iconify/vue/dist/iconify.js';
+
defineProps<{
hideIcon?: boolean;
+ icon?: string;
}>();
@@ -10,10 +13,13 @@ defineProps<{
v-if="!hideIcon"
id="btn-add"
padding="sm"
- icon="mdi-plus"
+ :icon="icon ? undefined : 'mdi-plus'"
direction="up"
class="color-btn"
>
+
+
+
+ >
+
+
diff --git a/src/components/GlobalDialog.vue b/src/components/GlobalDialog.vue
index 059f7755..f9df77d4 100644
--- a/src/components/GlobalDialog.vue
+++ b/src/components/GlobalDialog.vue
@@ -46,6 +46,7 @@ defineProps<{
color="grey"
icon="mdi-close"
v-close-popup
+ @click="cancel"
/>
diff --git a/src/components/PaginationPageSize.vue b/src/components/PaginationPageSize.vue
index 4eb86e0c..e8ece111 100644
--- a/src/components/PaginationPageSize.vue
+++ b/src/components/PaginationPageSize.vue
@@ -1,5 +1,12 @@
@@ -10,7 +17,12 @@ const pageSize = defineModel({ required: true });
:key="v"
clickable
v-close-popup
- @click="pageSize = v"
+ @click="
+ () => {
+ pageSize = v;
+ fetchData();
+ }
+ "
>
{{ v }}
diff --git a/src/components/ProfileBanner.vue b/src/components/ProfileBanner.vue
index 9c72041c..2d4b3975 100644
--- a/src/components/ProfileBanner.vue
+++ b/src/components/ProfileBanner.vue
@@ -229,6 +229,7 @@ const smallBanner = ref(false);
$emit('update:currentTab', v)"
>
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
+ }}
{{ node.code }}
@@ -120,11 +122,7 @@ defineEmits<{
/>
+import { ref } from 'vue';
import MainButton from './MainButton.vue';
-defineEmits<{
+const emit = defineEmits<{
(e: 'click', v: MouseEvent): void;
+ (e: 'fileSelected', v: File[]): void;
}>();
defineProps<{
iconOnly?: boolean;
@@ -10,15 +12,29 @@ defineProps<{
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
+ importFile?: boolean;
label?: string;
icon?: string;
}>();
+
+const inputRef = ref(null);
+
+function triggerFileInput() {
+ inputRef.value?.click();
+}
+
+function handleFileChange(event: Event) {
+ const files = (event.target as HTMLInputElement).files;
+ if (files && files.length > 0) {
+ emit('fileSelected', Array.from(files));
+ }
+}
$emit('click', e)"
+ @click="(e) => (importFile ? triggerFileInput() : $emit('click', e))"
v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-import'"
color="var(--info-bg)"
@@ -26,4 +42,13 @@ defineProps<{
>
{{ label || $t('general.import') }}
+
+ handleFileChange(e)"
+ hidden
+ accept=".xls, .xlsx , .csv"
+ multiple
+ />
diff --git a/src/components/button/MainButton.vue b/src/components/button/MainButton.vue
index 836abe32..08233063 100644
--- a/src/components/button/MainButton.vue
+++ b/src/components/button/MainButton.vue
@@ -5,6 +5,7 @@ defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
+ id?: string;
icon?: string;
color: string;
iconOnly?: boolean;
@@ -18,6 +19,7 @@ defineProps<{
+
+
+
+ {{ $t('personnel.form.addressForeign') }}
+
@@ -449,7 +499,24 @@ watchEffect(async () => {
(v) => (typeof v === 'string' ? (street = v) : '')
"
/>
+
{
+
{
+
+
{
(zipCode = v.toString())"
+ :rules="
+ !addressForeign
+ ? []
+ : [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
{
(v) => (typeof v === 'string' ? (streetEN = v) : '')
"
/>
+
{
+
+
{
+
+
{
(zipCode = v.toString())"
+ :rules="
+ !addressForeign
+ ? []
+ : [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
+import { ref, watch } from 'vue';
+import { dateFormatJS } from 'src/utils/datetime';
+import SelectInput from './SelectInput.vue';
+import VueDatePicker from '@vuepic/vue-datepicker';
+import dayjs from 'dayjs';
+
+defineProps<{
+ active?: boolean;
+}>();
+
+const date = defineModel();
+
+const dateRange = ref('');
+const isDateSelect = ref(false);
+
+function mapDateRange(val: string) {
+ const today = dayjs();
+ let start: dayjs.Dayjs, end: dayjs.Dayjs;
+
+ switch (val) {
+ case 'toDay':
+ start = today.startOf('day');
+ end = today.endOf('day');
+ break;
+ case 'yesterday':
+ start = today.subtract(1, 'day').startOf('day');
+ end = today.subtract(1, 'day').endOf('day');
+ break;
+ case 'thisWeek':
+ start = today.startOf('week');
+ end = today.endOf('week');
+ break;
+ case 'lastWeek':
+ start = today.subtract(1, 'week').startOf('week');
+ end = today.subtract(1, 'week').endOf('week');
+ break;
+ case 'thisMonth':
+ start = today.startOf('month');
+ end = today.endOf('month');
+ break;
+ case 'lastMonth':
+ start = today.subtract(1, 'month').startOf('month');
+ end = today.subtract(1, 'month').endOf('month');
+ break;
+ case 'thisYear':
+ start = today.startOf('year');
+ end = today.endOf('year');
+ break;
+ case 'lastYear':
+ start = today.subtract(1, 'year').startOf('year');
+ end = today.subtract(1, 'year').endOf('year');
+ break;
+ case 'last7Days':
+ start = today.subtract(6, 'day').startOf('day');
+ end = today.endOf('day');
+ break;
+ case 'last30Days':
+ start = today.subtract(29, 'day').startOf('day');
+ end = today.endOf('day');
+ break;
+ case 'last90Days':
+ start = today.subtract(89, 'day').startOf('day');
+ end = today.endOf('day');
+ break;
+ case 'customDateRange':
+ start = today.startOf('day');
+ end = today.endOf('day');
+ break;
+ default:
+ return;
+ }
+
+ return [start.toDate().toISOString(), end.toDate().toISOString()];
+}
+
+watch(
+ () => dateRange.value,
+ () => {
+ if (!dateRange.value) return;
+ date.value = mapDateRange(dateRange.value);
+ },
+);
+
+watch(
+ () => date.value,
+ () => {
+ if (date.value && date.value.length === 0) dateRange.value = '';
+ },
+);
+
+
+
+
+
+
+
+ {{ $t('general.advanceSearch') }}
+
+
(date = [])"
+ />
+
+ (isDateSelect = true)"
+ @closed="() => (isDateSelect = false)"
+ >
+
+
+
+
+
+
+ {{
+ date
+ ? dateFormatJS({ date: date[0] }) +
+ ' - ' +
+ dateFormatJS({ date: date[1] })
+ : ''
+ }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t('general.advanceSearch') }}
+
+
+
diff --git a/src/components/shared/AvatarGroup.vue b/src/components/shared/AvatarGroup.vue
index a3f3d85e..ed8f63ef 100644
--- a/src/components/shared/AvatarGroup.vue
+++ b/src/components/shared/AvatarGroup.vue
@@ -25,7 +25,11 @@ withDefaults(
alt="Image"
/>
-
+
{{ person.name }}
diff --git a/src/components/shared/KebabAction.vue b/src/components/shared/KebabAction.vue
index 4dd095db..7bea6b36 100644
--- a/src/components/shared/KebabAction.vue
+++ b/src/components/shared/KebabAction.vue
@@ -16,6 +16,7 @@ const props = withDefaults(
useUpload?: boolean;
useCancel?: boolean;
useRejectCancel?: boolean;
+ useCopy?: boolean;
disableCancel?: boolean;
disableDelete?: boolean;
}>(),
@@ -31,6 +32,7 @@ defineEmits<{
(e: 'link'): void;
(e: 'upload'): void;
(e: 'delete'): void;
+ (e: 'copy'): void;
(e: 'cancel'): void;
(e: 'rejectCancel'): void;
(e: 'changeStatus'): void;
@@ -172,6 +174,27 @@ watch(
+
$emit('copy')"
+ >
+
+
+ {{ $t('general.copy') }}
+
+
+
();
defineEmits<{
@@ -76,8 +78,10 @@ defineEmits<{
/>
string | true)[];
}>(),
@@ -70,6 +72,7 @@ watch(
{
+ multiple ? (model = []) : (model = '');
+ }
+ "
>
diff --git a/src/components/shared/select-muliple/SelectOffice.vue b/src/components/shared/select-muliple/SelectOffice.vue
index 24437cd1..22a0a327 100644
--- a/src/components/shared/select-muliple/SelectOffice.vue
+++ b/src/components/shared/select-muliple/SelectOffice.vue
@@ -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"
>
diff --git a/src/components/shared/select/SelectBranch.vue b/src/components/shared/select/SelectBranch.vue
index b18a1e51..6fa6aa8c 100644
--- a/src/components/shared/select/SelectBranch.vue
+++ b/src/components/shared/select/SelectBranch.vue
@@ -75,9 +75,9 @@ function setDefaultValue() {
+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('value', {
+ required: true,
+});
+const valueOption = defineModel('valueOption', {
+ required: false,
+});
+
+const selectOptions = ref([]);
+
+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 & ExclusiveProps>();
+
+const { getOptions, setFirstValue, getSelectedOption, filter } =
+ createSelect(
+ {
+ 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();
+}
+
+
+
+
+ {{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
+
+
+
+
+
+
+
+
+ {{ $t('general.add', { text: $t('businessType.title') }) }}
+
+
+ {{ creatableDisabledText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('general.add', { text: $t('businessType.title') }) }}
+
+
+ {{ creatableDisabledText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('general.add', { text: $t('menu.manage.businessType') }) }}
+
+
+ {{ creatableDisabledText }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/shared/select/SelectCustomer.vue b/src/components/shared/select/SelectCustomer.vue
index f6fbf9cd..2e62eba8 100644
--- a/src/components/shared/select/SelectCustomer.vue
+++ b/src/components/shared/select/SelectCustomer.vue
@@ -30,6 +30,7 @@ defineEmits<{
type ExclusiveProps = {
simple?: boolean;
simpleBranchNo?: boolean;
+ selectFirstValue?: boolean;
};
const props = defineProps & ExclusiveProps>();
@@ -64,8 +65,14 @@ onMounted(async () => {
setFirstValue();
}
- await getSelectedOption();
+ if (props.selectFirstValue) {
+ setDefaultValue();
+ } else await getSelectedOption();
});
+
+function setDefaultValue() {
+ setFirstValue();
+}
{
-
+
-
-
@@ -175,3 +180,11 @@ onMounted(async () => {
+
+
diff --git a/src/components/shared/select/SelectInstitution.vue b/src/components/shared/select/SelectInstitution.vue
index bf5877c0..4b4ea912 100644
--- a/src/components/shared/select/SelectInstitution.vue
+++ b/src/components/shared/select/SelectInstitution.vue
@@ -43,6 +43,7 @@ const { getOptions, setFirstValue, getSelectedOption, filter } =
const ret = await getList({
query: query === '' ? undefined : query,
...props.params,
+ activeOnly: true,
});
if (ret) return ret.result;
},
diff --git a/src/components/shared/select/SelectQuotation.vue b/src/components/shared/select/SelectQuotation.vue
index ae9f7863..344e500b 100644
--- a/src/components/shared/select/SelectQuotation.vue
+++ b/src/components/shared/select/SelectQuotation.vue
@@ -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),
+ );
+ }
+ "
>
& ExclusiveProps>();
@@ -71,6 +72,7 @@ function setDefaultValue() {
diff --git a/src/components/shared/select/select.ts b/src/components/shared/select/select.ts
index 08fd88b2..11ec46d4 100644
--- a/src/components/shared/select/select.ts
+++ b/src/components/shared/select/select.ts
@@ -35,7 +35,13 @@ export const createSelect = >(
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 = >(
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;
}
}
diff --git a/src/components/shared/table/TableProductAndService.vue b/src/components/shared/table/TableProductAndService.vue
index e1801167..d8e0c683 100644
--- a/src/components/shared/table/TableProductAndService.vue
+++ b/src/components/shared/table/TableProductAndService.vue
@@ -251,7 +251,10 @@ function selectedIndex(item: any) {
>
-
+
+ {{ props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') || '-' }}
+
+
{{
typeof col.field === 'string'
? props.row[col.field as keyof (Product | Service)]
diff --git a/src/components/shared/table/TableWorker.vue b/src/components/shared/table/TableWorker.vue
index 8594b0e7..e8cfa214 100644
--- a/src/components/shared/table/TableWorker.vue
+++ b/src/components/shared/table/TableWorker.vue
@@ -145,6 +145,7 @@ function selectedIndex(item: Employee) {
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}`"
/>
diff --git a/src/components/upload-file/FormCitizen.vue b/src/components/upload-file/FormCitizen.vue
index 5d35209e..06c97e6d 100644
--- a/src/components/upload-file/FormCitizen.vue
+++ b/src/components/upload-file/FormCitizen.vue
@@ -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')]"
/>
{
@click="$emit('click')"
>
-
-
+
+
{{ name }}
-
+
+
{{
uploading.loaded
@@ -79,7 +80,7 @@ onMounted(() => {
/>
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
-
+
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;
diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss
index 7bd226ac..eb2b877b 100644
--- a/src/css/quasar.variables.scss
+++ b/src/css/quasar.variables.scss
@@ -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;
+ }
+}
diff --git a/src/i18n/eng.ts b/src/i18n/eng.ts
index 241621bb..3379ba60 100644
--- a/src/i18n/eng.ts
+++ b/src/i18n/eng.ts
@@ -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',
@@ -60,7 +61,7 @@ export default {
branchStatus: 'Branch Status',
success: 'Success',
taxNo: 'Legal Person',
- contactName: 'Contact Name',
+ contactName: 'Contact Person',
image: 'Image of ',
apply: 'Apply',
licenseNumber: 'License number',
@@ -154,6 +155,13 @@ export default {
draw: 'Draw',
newUpload: 'New Upload',
nativeLanguage: '{msg} Native Language',
+ copy: 'Copy',
+ paste: 'Paste',
+ period: 'Period',
+ documentStatus: 'Document Status',
+ advanceSearch: 'Advance Search',
+ totalPeople: '{meg} people',
+ price: 'Price {price} Baht',
},
menu: {
@@ -196,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: {
@@ -248,7 +258,8 @@ export default {
manual: {
title: 'Manual',
- usage: 'การใช้งาน',
+ usage: 'Usage',
+ troubleshooting: 'Troubleshooting',
},
},
@@ -330,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: {
@@ -377,7 +388,7 @@ export default {
branchLabel: 'Branch',
branchHQLabel: 'Headoffice',
taxNo: 'Legal Person',
- contactName: 'Contact Name',
+ contactName: 'Contact Person',
},
page: {
captionManage: 'Manage',
@@ -398,8 +409,8 @@ export default {
code: 'Headoffice Code',
codeBranch: 'Branch Code',
taxNo: 'Tax Identification Number',
- contactName: 'Contact Name',
- contactTelephone: 'Contact Telephone',
+ contactName: 'Contact Person',
+ contactTelephone: 'Contact Number',
branchName: 'Branch Name',
branchNameEN: 'Branch Name (EN)',
servicePointName: 'Service Point Name',
@@ -449,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',
@@ -464,6 +475,9 @@ export default {
normal: 'Normal',
canceled: 'Canceled',
blacklist: 'Black list',
+ contactName: 'Contact Person',
+ contactTel: 'Contact Number',
+ addressForeign: 'Use foreign address',
},
},
customer: {
@@ -477,10 +491,9 @@ export default {
powerOfAttorney: 'Power of Attorney',
others: 'Others',
},
-
employer: 'Employer',
employerLegalEntity: 'Legal Entity',
- employerNaturalPerson: 'Natrual Person',
+ employerNaturalPerson: 'Natural Person',
employerType: 'Employer Type',
employee: 'Employee',
form: {
@@ -490,24 +503,22 @@ 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',
passportExpiryDate: 'Passport Expiry Date',
-
ownerName: 'Customer Name',
firstName: 'First Name ',
lastName: 'Last Name ',
firstNameEN: 'First Name in English',
lastNameEN: 'Last Name in English',
-
cardNumber: 'ID Card Number',
-
prefixName: 'Prefix',
legalPersonNo: 'Legal Entity Registration Number',
registerName: 'Company Name',
@@ -515,7 +526,6 @@ export default {
registerDate: 'Registered On',
registerCompanyName: 'Registered Name',
authorizedCapital: 'Authorized Capital',
-
workplace: 'Workplace',
workplaceEN: 'Workplace (EN)',
address: 'Address',
@@ -523,7 +533,6 @@ export default {
branchCode: 'Branch Code',
customerCode: 'Employer Code',
legalPersonCode: 'Legal Entity Code',
-
codeAbbrev: 'Company Abbreviation',
codeNumber: 'Company Number',
registeredBranch: 'Registered Branch',
@@ -561,7 +570,7 @@ export default {
jobPosition: 'Job Position',
address: 'Address',
workPlace: 'Workplace',
- contactName: 'Contact Name',
+ contactName: 'Contact Person',
contactPhone: 'Contact Phone',
totalEmployee: 'Total Employee',
officeTel: 'Headoffice Telephone',
@@ -615,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',
@@ -765,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',
@@ -799,7 +811,7 @@ export default {
employee: 'Employee',
employeeName: 'Full Name',
workName: 'Work Name',
- contactName: 'Contact Name',
+ contactName: 'Contact Person',
documentReceivePoint: 'Document Drop-Off Point"',
dueDate: 'Quotation Due Date',
specialCondition: 'Special Conditions',
@@ -917,9 +929,10 @@ export default {
code: 'Agencies Code',
group: 'Agencies Group',
name: 'Agencies Name',
- contactName: 'Contact Name',
- contactTel: 'Contact Telephone',
+ contactName: 'Contact Person',
+ contactTel: 'Contact Number',
bankInfo: 'Bank Information',
+ attachment: 'Attachment',
},
requestList: {
@@ -941,8 +954,9 @@ export default {
localEmployee: 'Local Employee',
nonLocalEmployee: 'Non Local Employee',
noWorkflowTemplate: 'A workflow template has not been selected.',
-
salesRepresentative: 'Sales Representative',
+
+ dataOffice: 'Employment Office District',
ref: 'Reference',
action: {
title: 'Action',
@@ -1006,7 +1020,7 @@ export default {
issueBranch: 'Issue Branch',
issueDate: 'Issue Date',
madeBy: 'Made By',
- contactName: 'Contact Name',
+ contactName: 'Contact Person',
workOrderCode: 'Work Order Code',
workOrderName: 'Work Order Name',
telephone: 'Telephone',
@@ -1065,6 +1079,10 @@ export default {
confirmDebitNoteAccept: 'Confirm acceptance of the debit note.',
},
message: {
+ copy: 'Copy',
+ warningPaste:
+ 'Do you want to replace the data with the newly copied information?',
+ warningCopyEmpty: 'You have not copied any data yet',
quotationAccept: 'Once accepted, no further modifications can be made',
beingUse: '"{msg}" is being used.',
incompleteDataEntry: 'Incomplete data entry on {tap} page',
@@ -1109,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.',
@@ -1216,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',
},
},
@@ -1485,4 +1506,26 @@ export default {
type: 'Type',
},
},
+
+ dateRange: {
+ today: 'Today',
+ yesterday: 'Yesterday',
+ thisWeek: 'This Week',
+ lastWeek: 'Last Week',
+ thisMonth: 'This Month',
+ lastMonth: 'Last Month',
+ thisYear: 'This Year',
+ lastYear: 'Last Year',
+ last7Days: 'Last 7 Days',
+ last30Days: 'Last 30 Days',
+ 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)',
+ },
};
diff --git a/src/i18n/tha.ts b/src/i18n/tha.ts
index 19915334..921e25fd 100644
--- a/src/i18n/tha.ts
+++ b/src/i18n/tha.ts
@@ -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}',
@@ -154,6 +155,13 @@ export default {
draw: 'วาด',
newUpload: 'อัปโหลดใหม่',
nativeLanguage: '{msg} ภาษาต้นทาง',
+ copy: 'คัดลอก',
+ paste: 'วาง',
+ period: 'ช่วงเวลา',
+ documentStatus: 'สถานะเอกสาร',
+ advanceSearch: 'ค้นหาขั้นสูง',
+ totalPeople: '{meg} คน',
+ price: 'ราคา {price} บาท',
},
menu: {
@@ -196,12 +204,14 @@ export default {
title: 'จัดการ',
branch: 'สาขา',
personnel: 'บุคลากร',
+ group: 'กลุ่ม',
productService: 'สินค้าและบริการ',
workflow: 'ขั้นตอนการทำงาน',
property: 'คุณสมบัติ',
customer: 'ลูกค้า',
mainData: 'ข้อมูลหลัก',
agencies: 'หน่วยงาน',
+ businessType: 'ประเภทกิจการ',
},
sales: {
@@ -249,6 +259,7 @@ export default {
manual: {
title: 'คู่มือ',
usage: 'การใช้งาน',
+ troubleshooting: 'การแก้ปัญหา',
},
},
@@ -327,7 +338,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
- branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
+ branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@@ -456,10 +467,13 @@ export default {
citizenId: 'เลขที่บัตรประชาชน',
citizenIssue: 'วันที่ออกบัตร',
citizenExpire: 'วันที่หมดอายุ',
- agencyStatus: 'สถานะการเป็นเอเจนซี่',
+ agencyStatus: 'สถานะเอเจนซี่',
normal: 'ปกติ',
canceled: 'ยกเลิก',
blacklist: 'แบล็คลิสต์',
+ contactName: 'ชื่อผู้ติดต่อ',
+ contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
+ addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
},
},
customer: {
@@ -486,15 +500,16 @@ export default {
},
prefix: {
- mr: 'Mr.',
- mrs: 'Mrs.',
- miss: 'Miss.',
+ mr: 'นาย',
+ mrs: 'นาง',
+ miss: 'นางสาว',
},
citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
+ taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ',
@@ -578,7 +593,7 @@ export default {
family: 'ข้อมูลครอบครัว',
},
workerStatus: 'สถานะคนงาน',
- previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
+ previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า',
employerBranch: 'สาขานายจ้าง',
employeeCode: 'รหัสลูกจ้าง',
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
@@ -611,7 +626,7 @@ export default {
placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก',
- entryCount: 'จำนวนที่เข้าประเทศ',
+ entryCount: 'จำนวนวันที่เข้าประเทศ',
employerSelect: {
branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง',
@@ -639,7 +654,7 @@ export default {
permitIssuedAt: 'สถานที่ออกใบอนุญาต',
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
- identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
+ identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)',
},
formFamily: {
citizenId:
@@ -757,10 +772,13 @@ export default {
},
quotation: {
+ ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่',
+ refNo: 'เลขที่อ้างอิง',
+ bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม',
@@ -785,7 +803,7 @@ export default {
branch: 'สาขาที่ออกใบเสนอราคา',
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
customer: 'ลูกค้า',
- newCustomer: 'ลูกค้าใหม่',
+ newCustomer: 'แรงงานใหม่',
employeeList: 'รายชื่อแรงงาน',
employee: 'แรงงาน',
employeeName: 'ชื่อ-นามสกุล แรงงาน',
@@ -911,6 +929,7 @@ export default {
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
+ attachment: 'เอกสารเพิ่มเติม',
},
requestList: {
@@ -932,6 +951,7 @@ export default {
nonLocalEmployee: 'พนักงานนอกพื้นที่',
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
salesRepresentative: 'พนักงานขาย',
+ dataOffice: 'สำนักงานเขตจัดหางาน',
ref: 'อ้างอิง',
action: {
title: 'จัดการ',
@@ -1050,6 +1070,9 @@ export default {
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
},
message: {
+ copy: 'คัดลอก',
+ warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
+ warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
beingUse: '"{msg}" มีการใช้งานอยู่',
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
@@ -1197,6 +1220,8 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด',
+ taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
+ flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
},
},
@@ -1468,4 +1493,26 @@ export default {
type: 'ประเภท',
},
},
+
+ dateRange: {
+ today: 'วันนี้',
+ yesterday: 'เมื่อวานนี้',
+ thisWeek: 'สัปดาห์นี้',
+ lastWeek: 'สัปดาห์ที่แล้ว',
+ thisMonth: 'เดือนนี้',
+ lastMonth: 'เดือนที่แล้ว',
+ thisYear: 'ปีนี้',
+ lastYear: 'ปีที่แล้ว',
+ last7Days: '7 วันที่ผ่านมา',
+ last30Days: '30 วันที่ผ่านมา',
+ last90Days: '90 วันที่ผ่านมา',
+ customDateRange: 'กำหนดช่วงวันที่เอง',
+ },
+
+ businessType: {
+ title: 'ประเภทกิจการ',
+ caption: 'จัดการประเภทกิจการ',
+ name: 'ชื่อประเภทกิจการ',
+ nameEn: 'ชื่อประเภทกิจการ (ภาษาอังกฤษ)',
+ },
};
diff --git a/src/layouts/DrawerComponent.vue b/src/layouts/DrawerComponent.vue
index c1b01436..d7a70d33 100644
--- a/src/layouts/DrawerComponent.vue
+++ b/src/layouts/DrawerComponent.vue
@@ -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'),
+ },
],
},
@@ -215,6 +176,10 @@ function initMenu() {
label: 'usage',
route: '/manual',
},
+ {
+ label: 'troubleshooting',
+ route: '/troubleshooting',
+ },
],
},
];
diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue
index c09effed..29e106bc 100644
--- a/src/layouts/MainLayout.vue
+++ b/src/layouts/MainLayout.vue
@@ -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(
);
const userImage = ref();
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 () => {
{
style="margin-top: 58px"
>
- {{ getName() }}
+ {{ userName || getName() }}
{{ 'Guest' }}
- {{ getName() }}
+ {{ userName || getName() }}
{{ 'Guest' }}
diff --git a/src/pages/00_manual/MainPage.vue b/src/pages/00_manual/MainPage.vue
index adcb668f..33a19ebe 100644
--- a/src/pages/00_manual/MainPage.vue
+++ b/src/pages/00_manual/MainPage.vue
@@ -1,7 +1,7 @@
@@ -34,7 +56,7 @@ onMounted(async () => {
>
diff --git a/src/pages/03_customer-management/components/employer/EmployerFormBranch.vue b/src/pages/03_customer-management/components/employer/EmployerFormBranch.vue
index 1b70b651..14f7aa98 100644
--- a/src/pages/03_customer-management/components/employer/EmployerFormBranch.vue
+++ b/src/pages/03_customer-management/components/employer/EmployerFormBranch.vue
@@ -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(
/>
-
-
-
- {{ $t('general.noData') }}
-
-
-
-
- (businessTypeDialog = true)"
+ />
+
-
-
-
- {{ $t('general.noData') }}
-
-
-
-
+ @create="() => (businessTypeDialog = true)"
+ />
+
+
diff --git a/src/pages/03_customer-management/components/employer/EmployerFormContact.vue b/src/pages/03_customer-management/components/employer/EmployerFormContact.vue
index 4a58439c..7f56742c 100644
--- a/src/pages/03_customer-management/components/employer/EmployerFormContact.vue
+++ b/src/pages/03_customer-management/components/employer/EmployerFormContact.vue
@@ -9,8 +9,6 @@ const contactName = defineModel
('contactName');
const email = defineModel('email');
const contactTel = defineModel('contactTel');
const officeTel = defineModel('officeTel');
-const agent = defineModel('agent');
-
const agentUserId = defineModel('agentUserId');
@@ -109,7 +107,6 @@ const agentUserId = defineModel('agentUserId');
/>
-
{
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('');
+
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;
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(
+ const currentFromDataEmployee = ref(
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,
diff --git a/src/pages/04_flow-managment/FlowDialog.vue b/src/pages/04_flow-managment/FlowDialog.vue
index 77d8bd12..42c429ba 100644
--- a/src/pages/04_flow-managment/FlowDialog.vue
+++ b/src/pages/04_flow-managment/FlowDialog.vue
@@ -43,6 +43,7 @@ withDefaults(
defineProps<{
readonly?: boolean;
isEdit?: boolean;
+ hideAction?: boolean;
}>(),
{ readonly: false, isEdit: false },
);
@@ -60,6 +61,7 @@ async function addStep() {
flowData.value.step.push({
responsibleInstitution: [],
responsiblePersonId: [],
+ responsibleGroup: [],
value: [],
detail: '',
name: '',
@@ -166,6 +168,7 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
id="flow-form-dialog"
>
import { onMounted, reactive, ref, watch } from 'vue';
-import { QSelect, QTableProps } from 'quasar';
+import { QTableProps } from 'quasar';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@@ -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';
@@ -22,6 +22,7 @@ import NoData from 'src/components/NoData.vue';
import KebabAction from 'src/components/shared/KebabAction.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import { useQuasar } from 'quasar';
+import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { t } = useI18n();
const workflowStore = useWorkflowTemplate();
@@ -45,6 +46,7 @@ const pageState = reactive({
addModal: false,
viewDrawer: false,
isDrawerEdit: true,
+ searchDate: [],
});
const fieldSelected = ref<('order' | 'name' | 'step')[]>([
@@ -68,7 +70,6 @@ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
},
]);
-const refFilter = ref>();
const currWorkflowData = ref();
const formDataWorkflow = ref({
status: 'CREATED',
@@ -102,6 +103,7 @@ const columns = [
function triggerDialog(type: 'add' | 'edit' | 'view') {
if (type === 'add') {
registeredBranchId.value = '';
+ userInTable.value = [];
formDataWorkflow.value = {
status: 'CREATED',
name: '',
@@ -206,7 +208,7 @@ async function submit() {
...formDataWorkflow.value,
});
} else {
- await workflowStore.creatWorkflowTemplate({
+ await workflowStore.createWorkflowTemplate({
registeredBranchId: registeredBranchId.value,
...formDataWorkflow.value,
});
@@ -222,7 +224,11 @@ function assignFormData(workflowData: WorkflowTemplate) {
status: workflowData.status,
name: workflowData.name,
step: workflowData.step.map((s, i) => {
- userInTable.value[i] = { name: s.name, responsiblePerson: [] };
+ userInTable.value[i] = {
+ name: s.name,
+ responsiblePerson: [],
+ responsibleGroup: [],
+ };
s.responsiblePerson.forEach((p) => {
userInTable.value[i].responsiblePerson.push({
id: p.user.id,
@@ -236,12 +242,16 @@ function assignFormData(workflowData: WorkflowTemplate) {
code: p.user.code,
});
});
+ s.responsibleGroup.forEach((g) => {
+ userInTable.value[i].responsibleGroup.push(g);
+ });
return {
id: s.id,
name: s.name,
detail: s.detail,
messengerByArea: s.messengerByArea || false,
value: s.value.length > 0 ? JSON.parse(JSON.stringify(s.value)) : [],
+ responsibleGroup: s.responsibleGroup.map((g) => g),
responsiblePersonId: s.responsiblePerson.map((p) => p.userId),
responsibleInstitution: JSON.parse(
JSON.stringify(s.responsibleInstitution),
@@ -282,6 +292,8 @@ async function fetchWorkflowList(mobileFetch?: boolean) {
: statusFilter.value === 'statusACTIVE'
? 'ACTIVE'
: 'INACTIVE',
+ startDate: pageState.searchDate[0],
+ endDate: pageState.searchDate[1],
});
if (res) {
workflowData.value =
@@ -311,14 +323,18 @@ watch(
fetchWorkflowList();
},
);
-watch([() => pageState.inputSearch, workflowPageSize], () => {
- workflowData.value = [];
- workflowPage.value = 1;
- fetchWorkflowList();
-});
+watch(
+ [() => pageState.inputSearch, workflowPageSize, () => pageState.searchDate],
+ () => {
+ workflowData.value = [];
+ workflowPage.value = 1;
+ fetchWorkflowList();
+ },
+);
pageState.inputSearch, workflowPageSize], () => {
-
-
-
-
+
+
+
+ {{ $t('general.status') }}
+
+
-
+
pageState.inputSearch, workflowPageSize], () => {
class="col surface-2 flex items-center justify-center"
>
pageState.inputSearch, workflowPageSize], () => {
"
/>
pageState.inputSearch, workflowPageSize], () => {
"
/>
{
@@ -813,6 +849,7 @@ watch([() => pageState.inputSearch, workflowPageSize], () => {
@drawer-undo="undo"
@close="resetForm"
@submit="submit"
+ :hide-action="!canAccess('workflow', 'edit')"
:readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit"
v-model="pageState.addModal"
diff --git a/src/pages/04_product-service/MainPage.vue b/src/pages/04_product-service/MainPage.vue
index 462e7476..17cd4040 100644
--- a/src/pages/04_product-service/MainPage.vue
+++ b/src/pages/04_product-service/MainPage.vue
@@ -3,7 +3,7 @@ import { nextTick, ref, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue';
import { storeToRefs } from 'pinia';
-import { QSelect, useQuasar, type QTableProps } from 'quasar';
+import { useQuasar, type QTableProps } from 'quasar';
import DialogProperties from 'src/components/dialog/DialogProperties.vue';
import ProductCardComponent from 'components/04_product-service/ProductCardComponent.vue';
@@ -33,6 +33,7 @@ import {
SaveButton,
UndoButton,
ToggleButton,
+ ImportButton,
} from 'components/button';
import TableProduct from 'src/components/04_product-service/TableProduct.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
@@ -40,7 +41,7 @@ import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import useFlowStore from 'stores/flow';
import { dateFormat } from 'src/utils/datetime';
-import { formatNumberDecimal, isRoleInclude } from 'stores/utils';
+import { formatNumberDecimal, isRoleInclude, canAccess } from 'stores/utils';
const { getWorkflowTemplate } = useWorkflowTemplate();
import { Status } from 'stores/types';
@@ -59,6 +60,7 @@ import {
ServiceById,
WorkItems,
Attributes,
+ WorkCreate,
} from 'stores/product-service/types';
import { computed } from 'vue';
import {
@@ -67,6 +69,8 @@ import {
} from 'src/stores/workflow-template/types';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { deepEquals } from 'src/utils/arr';
+import { toRaw } from 'vue';
+import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const flowStore = useFlowStore();
const navigatorStore = useNavigator();
@@ -96,10 +100,15 @@ const {
createWork,
editWork,
deleteWork,
+
+ importProduct,
+
+ productExport,
} = productServiceStore;
const { workNameItems } = storeToRefs(productServiceStore);
const allStat = ref<{ mode: string; count: number }[]>([]);
+
const stat = ref<
{
icon: string;
@@ -136,33 +145,29 @@ const { t } = useI18n();
const baseUrl = ref(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,
);
-const refFilterGroup = ref>();
-const refFilterProductService = ref>();
const holdDialog = ref(false);
const imageDialog = ref(false);
const currentNode = ref();
@@ -520,6 +525,7 @@ const currentStatusGroupType = ref('CREATED');
const currentIdGroupType = ref('');
const currentStatus = ref('All');
+const searchDate = ref([]);
// img
const isImageEdit = ref(false);
@@ -615,6 +621,8 @@ async function fetchListGroups(mobileFetch?: boolean) {
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: 'INACTIVE',
+ startDate: searchDate.value[0],
+ endDate: searchDate.value[1],
});
if (res) {
@@ -675,6 +683,8 @@ async function fetchListOfProduct(mobileFetch?: boolean) {
? 'ACTIVE'
: undefined,
productGroupId: currentIdGroup.value,
+ startDate: searchDate.value[0],
+ endDate: searchDate.value[1],
});
if (res) {
@@ -720,6 +730,8 @@ async function fetchListOfService(mobileFetch?: boolean) {
? 'ACTIVE'
: undefined,
productGroupId: currentIdGroup.value,
+ startDate: searchDate.value[0],
+ endDate: searchDate.value[1],
});
if (res) {
@@ -1159,6 +1171,7 @@ function clearFormService() {
profileSubmit.value = false;
imageProduct.value = undefined;
profileFileImg.value = null;
+ serviceTab.value = 1;
}
function sameFormService() {
@@ -1385,6 +1398,7 @@ function submitAddWorkProduct() {
if (!s.hasOwnProperty('productsId')) {
s.productsId = [];
}
+ if (s.productsId.length === 0) return;
s.productsId.push(i.id);
},
);
@@ -1437,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: () => {},
});
@@ -1590,6 +1598,7 @@ async function enterNext(type: 'service' | 'product') {
inputSearchProductAndService.value = '';
currentStatus.value = 'All';
filterStat.value = [];
+ searchDate.value = [];
if (
expandedTree.value.length > 1 &&
@@ -1745,7 +1754,7 @@ watch(currentStatus, async () => {
flowStore.rotate();
});
-watch(inputSearch, async () => {
+watch([inputSearch, () => searchDate.value], async () => {
if (productMode.value === 'group') {
productGroup.value = [];
currentPageGroup.value = 1;
@@ -1754,7 +1763,7 @@ watch(inputSearch, async () => {
}
});
-watch(inputSearchProductAndService, async () => {
+watch([inputSearchProductAndService, () => searchDate.value], async () => {
product.value = [];
service.value = [];
currentPageServiceAndProduct.value = 1;
@@ -1831,6 +1840,84 @@ function handleSubmitSameWorkflow() {
);
}
+async function copy(id: string) {
+ {
+ const res = await fetchListServiceById(id);
+ if (res) {
+ formService.value = {
+ code: res.code.slice(0, -3),
+ name: res.name,
+ detail: res.detail,
+ attributes: res.attributes,
+ work: res.work.map((v) => ({
+ id: v.id,
+ name: v.name,
+ attributes: v.attributes,
+ product: v.productOnWork.map((productOnWorkItem) => ({
+ id: productOnWorkItem.product.id,
+ installmentNo: productOnWorkItem.installmentNo,
+ stepCount: productOnWorkItem.stepCount,
+ })),
+ })),
+ status: res.status,
+ productGroupId: res.productGroupId,
+ selectedImage: res.selectedImage,
+ installments: res.installments,
+ };
+
+ workItems.value = res.work.map((item) => {
+ return {
+ id: item.id,
+ name: item.name,
+ attributes: item.attributes,
+ product: item.productOnWork.map((productOnWorkItem) => {
+ return {
+ ...productOnWorkItem.product,
+ nameEn: productOnWorkItem.product.name,
+ installmentNo: productOnWorkItem.installmentNo,
+ };
+ }),
+ };
+ });
+ }
+ }
+
+ 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,
+) {
+ 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) => {
@@ -1948,19 +2035,34 @@ watch(
-
-
-
-
+
+
+
+ {{ $t('general.status') }}
+
+
-
+
@@ -2115,26 +2217,43 @@ watch(
-
-
-
-
+
+
+
+ {{ $t('general.status') }}
+
+
-
+
-
-
-
- {{ props.row.detail || '-' }}
+ {{
+ props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') ||
+ '-'
+ }}
@@ -2618,26 +2747,72 @@ watch(
-
-
-
-
+
+
+
+ {{ $t('general.status') }}
+
+
-
+
+
+
+ {
+ importProduct(
+ currentIdGroup,
+ file,
+ async () => await fetchListOfProduct(),
+ );
+ }
+ "
+ />
+
+
+
-
-
{
+ copy(props.row.id);
+ }
+ "
@view="
async () => {
if (props.row.type === 'product') {
@@ -3275,7 +3455,15 @@ watch(
{{ $t('general.recordPerPage') }}
@@ -3407,7 +3595,7 @@ watch(
+
@@ -4535,7 +4730,7 @@ watch(
? workNameRef.isWorkNameEdit()
: false;
if (isWorkNameEdit) {
- triggerConfirmCloseWork();
+ triggerConfirmCloseWorkName();
return true;
}
return false;
@@ -4547,15 +4742,16 @@ watch(
ref="workNameRef"
v-model:name-list="workNameItems"
@delete="confirmDeleteWork"
- @edit="editWork"
- @add="createWork"
+ @edit="submitWorkName"
+ @add="addWorkName"
/>
-
+
pageState.inputSearch, propertyPageSize], () => {
-
-
-
-
+
+
+
+ {{ $t('general.status') }}
+
+
-
+
pageState.inputSearch, propertyPageSize], () => {
class="col surface-2 flex items-center justify-center"
>
pageState.inputSearch, propertyPageSize], () => {
"
/>
pageState.inputSearch, propertyPageSize], () => {
"
/>
pageState.inputSearch, propertyPageSize], () => {
@drawer-undo="() => undo()"
@close="() => resetForm()"
@submit="() => submit()"
+ :hide-action="!canAccess('workflow', 'edit')"
:readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit"
v-model="pageState.addModal"
diff --git a/src/pages/04_property-managment/PropertyDialog.vue b/src/pages/04_property-managment/PropertyDialog.vue
index 333f0ec7..66161d57 100644
--- a/src/pages/04_property-managment/PropertyDialog.vue
+++ b/src/pages/04_property-managment/PropertyDialog.vue
@@ -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"
>
-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';
@@ -7,14 +7,15 @@ import { useI18n } from 'vue-i18n';
// NOTE: Import stores
import useCustomerStore from 'stores/customer';
import { useQuotationStore } from 'src/stores/quotations';
-import { dialog, isRoleInclude, notify } from 'stores/utils';
+import { dialog, isRoleInclude, notify, setPrefixName } from 'stores/utils';
import { useNavigator } from 'src/stores/navigator';
import useFlowStore from 'src/stores/flow';
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';
@@ -49,6 +50,7 @@ import { Quotation } from 'src/stores/quotations/types';
import TableQuotation from 'src/components/05_quotation/TableQuotation.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import { DialogContainer, DialogHeader } from 'src/components/dialog';
+import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { t, locale } = useI18n();
const $q = useQuasar();
@@ -58,7 +60,6 @@ const flowStore = useFlowStore();
const userBranch = useMyBranch();
const navigatorStore = useNavigator();
const customerStore = useCustomerStore();
-
const {
fetchListOfOptionBranch,
customerFormUndo,
@@ -74,6 +75,7 @@ const {
const {
state: customerFormState,
currentFormData: customerFormData,
+ currentBranchRootId,
registerAbleBranchOption,
tabFieldRequired,
} = storeToRefs(customerFormStore);
@@ -87,6 +89,8 @@ const fieldSelectedOption = computed(() => {
value: v.name,
}));
});
+
+const keyAddDialog = ref(0);
const special = ref(false);
const branchId = ref('');
const agentPrice = ref(false);
@@ -103,12 +107,14 @@ const pageState = reactive({
fieldSelected: [''],
gridView: false,
total: 0,
+ sellerId: '',
currentTab: 'Issued',
addModal: false,
quotationModal: false,
employeeModal: false,
receiptModal: false,
+ searchDate: [],
});
pageState.fieldSelected = [...columnQuotation.map((v) => v.name)];
@@ -269,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';
@@ -295,6 +305,7 @@ onMounted(async () => {
pageSize: quotationPageSize.value,
status: 'Issued',
urgentFirst: true,
+ sellerId: pageState.sellerId || undefined,
});
if (ret) {
@@ -305,6 +316,12 @@ onMounted(async () => {
}
flowStore.rotate();
+
+ window.addEventListener('focus', handleWindowFocus);
+});
+
+onUnmounted(() => {
+ window.removeEventListener('focus', handleWindowFocus);
});
async function fetchQuotationList(mobileFetch?: boolean) {
@@ -327,6 +344,9 @@ async function fetchQuotationList(mobileFetch?: boolean) {
: 'Issued',
query: pageState.inputSearch,
urgentFirst: true,
+ startDate: pageState.searchDate[0],
+ endDate: pageState.searchDate[1],
+ sellerId: pageState.sellerId || undefined,
});
if (ret) {
@@ -350,7 +370,12 @@ async function fetchQuotationList(mobileFetch?: boolean) {
}
watch(
- [() => pageState.currentTab, () => pageState.inputSearch, quotationPageSize],
+ [
+ () => pageState.currentTab,
+ () => pageState.inputSearch,
+ () => pageState.searchDate,
+ quotationPageSize,
+ ],
() => {
quotationPage.value = 1;
quotationData.value = [];
@@ -397,6 +422,11 @@ async function storeDataLocal(id: string) {
window.open(url, '_blank');
}
+
+async function filterBySellerId() {
+ pageState.sellerId = pageState.sellerId ? '' : getUserId();
+ await fetchQuotationList();
+}
@@ -404,6 +434,7 @@ async function storeDataLocal(id: string) {
hide-icon
style="z-index: 999"
@click.stop="triggerAddQuotationDialog"
+ v-if="canAccess('quotation', 'create')"
/>
@@ -517,6 +548,21 @@ async function storeDataLocal(id: string) {
+
+
+
+
+
+
+ {{ $t('quotation.ownOnly') }}
+
+
+
+
@@ -634,12 +680,20 @@ async function storeDataLocal(id: string) {
class="col surface-2 flex items-center justify-center"
>
storeDataLocal(id)"
@view="
@@ -694,7 +750,8 @@ async function storeDataLocal(id: string) {
@@ -954,6 +1018,7 @@ async function storeDataLocal(id: string) {
() => {
customerFormState.dialogModal = false;
onCreateImageList = { selectedImage: '', list: [] };
+ keyAddDialog++;
}
"
>
@@ -1008,7 +1073,16 @@ async function storeDataLocal(id: string) {
"
:title="
customerFormData.customerType === 'PERS'
- ? `${customerFormData.customerBranch[0]?.firstName} ${customerFormData.customerBranch[0]?.lastName}`
+ ? setPrefixName(
+ {
+ namePrefix: customerFormData.customerBranch[0]?.namePrefix,
+ firstName: customerFormData.customerBranch[0]?.firstName,
+ lastName: customerFormData.customerBranch[0]?.lastName,
+ firstNameEN: customerFormData.customerBranch[0]?.firstNameEN,
+ lastNameEN: customerFormData.customerBranch[0]?.lastNameEN,
+ },
+ { locale },
+ )
: customerFormData.customerBranch[0]?.registerName
"
:caption="
@@ -1117,13 +1191,13 @@ async function storeDataLocal(id: string) {
id="form-basic-info-customer"
:onCreate="customerFormState.dialogType === 'create'"
@edit="
- (customerFormState.dialogType = 'edit'),
- (customerFormState.readonly = false)
+ ((customerFormState.dialogType = 'edit'),
+ (customerFormState.readonly = false))
"
@cancel="() => customerFormUndo(false)"
@delete="
customerFormState.editCustomerId &&
- deleteCustomerById(customerFormState.editCustomerId)
+ deleteCustomerById(customerFormState.editCustomerId)
"
:customer-type="customerFormData.customerType"
v-model:registered-branch-id="customerFormData.registeredBranchId"
@@ -1136,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
@@ -1217,7 +1291,6 @@ async function storeDataLocal(id: string) {
res = await customerStore.createBranch({
...customerFormData.customerBranch[idx],
customerId: customerFormState.editCustomerId || '',
- id: undefined,
});
}
if (res) {
diff --git a/src/pages/05_quotation/PaymentForm.vue b/src/pages/05_quotation/PaymentForm.vue
index 5bf0e43e..cf3d7c63 100644
--- a/src/pages/05_quotation/PaymentForm.vue
+++ b/src/pages/05_quotation/PaymentForm.vue
@@ -1,15 +1,9 @@
-
-
-
-
-
-
-
-
-
- {{ $t('quotation.receiptDialog.paymentWait') }}
-
-
-
-
-
- {{ data.workName }}
-
-
- {{ data.code }}
-
-
- ฿ {{ formatNumberDecimal(data.finalPrice, 2) || '0.00' }}
-
-
-
-
-
-
-
-
-
- {{ $t('general.total') }}
-
- ฿
- {{ formatNumberDecimal(data.totalPrice, 2) || '0.00' }}
-
-
-
- {{ $t('quotation.discountList') }}
-
- ฿
- {{ formatNumberDecimal(data.totalDiscount, 2) || '0.00' }}
-
-
-
- {{ $t('general.totalAfterDiscount') }}
-
- ฿
- {{
- formatNumberDecimal(
- data.totalPrice - data.totalDiscount,
- 2,
- ) || '0.00'
- }}
-
-
-
- {{ $t('general.totalVatExcluded') }}
-
- ฿ {{ formatNumberDecimal(data.vatExcluded, 2) || '0.00' }}
-
-
-
- {{
- $t('general.vat', {
- msg: `${config && Math.round(config.vat * 100)}%`,
- })
- }}
-
- ฿ {{ formatNumberDecimal(data.vat, 2) || '0.00' }}
-
-
-
- {{ $t('general.totalVatIncluded') }}
-
- ฿
- {{
- formatNumberDecimal(
- data.totalPrice - data.totalDiscount + data.vat,
- 2,
- ) || '0.00'
- }}
-
-
-
- {{ $t('general.discountAfterVat') }}
-
- ฿ {{ formatNumberDecimal(data.discount, 2) || '0.00' }}
-
-
-
-
-
-
- {{ $t('quotation.totalPriceBaht') }}:
-
- {{ formatNumberDecimal(data.finalPrice, 2) || '0.00' }}
-
-
-
-
-
-
-
-
@@ -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.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.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)`"
>
-
+
@@ -2304,9 +2378,15 @@ function covertToNode() {
-
+
{
- selectedWorker = v.worker;
+ combineWorker(v.newWorker, v.worker);
}
"
/>
@@ -2437,7 +2517,7 @@ function covertToNode() {
{
@@ -275,6 +275,7 @@ watch(
{{
formatNumberDecimal(
- summaryPrice.totalPrice - summaryPrice.totalDiscount,
+ summaryPrice.totalPrice -
+ summaryPrice.totalDiscount -
+ summaryPrice.vatExcluded,
2,
)
}}
@@ -482,7 +488,11 @@ watch(
{{ $t('quotation.totalPriceBaht') }}
-
+
{{
payType === 'SplitCustom' && view === View.Invoice
? formatNumberDecimal(Math.max(installmentAmount || 0, 0), 2) || 0
diff --git a/src/pages/05_quotation/QuotationFormMetadata.vue b/src/pages/05_quotation/QuotationFormMetadata.vue
index 071bc84b..161b0420 100644
--- a/src/pages/05_quotation/QuotationFormMetadata.vue
+++ b/src/pages/05_quotation/QuotationFormMetadata.vue
@@ -1,5 +1,6 @@
@@ -95,5 +97,11 @@ const createdAt = defineModel('createdAt');
dense
outlined
/>
+
diff --git a/src/pages/05_quotation/QuotationFormProductSelect.vue b/src/pages/05_quotation/QuotationFormProductSelect.vue
index eb6ffef8..b2e92761 100644
--- a/src/pages/05_quotation/QuotationFormProductSelect.vue
+++ b/src/pages/05_quotation/QuotationFormProductSelect.vue
@@ -51,6 +51,8 @@ const emit = defineEmits<{
const selectedProductGroup = defineModel('selectedProductGroup', {
default: '',
});
+
+const selectedProductGroupOption = ref();
const model = defineModel();
const inputSearch = defineModel('inputSearch');
const productGroup = defineModel('productGroup', {
@@ -66,21 +68,21 @@ const serviceList = defineModel>>(
);
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 ||
+ '-'
}}
{{
productGroup.find(
(g) => g.id === selectedProductGroup,
- )?.code || '-'
+ )?.code ||
+ selectedProductGroupOption?.code ||
+ '-'
}}
@@ -862,13 +868,13 @@ watch(
{{ $t('productService.group.title') }}
-
import { useI18n } from 'vue-i18n';
import { reactive, ref, watch, onMounted } from 'vue';
-import { baseUrl, notify } from 'src/stores/utils';
+import { baseUrl, notify, setPrefixName } from 'src/stores/utils';
// NOTE: Import stores
import { dialog } from 'stores/utils';
@@ -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('open', { default: false });
-const newWorkerList = defineModel<
+const newWorkerList = ref<
(EmployeeWorker & {
attachment?: {
name?: string;
@@ -139,7 +143,7 @@ const newWorkerList = defineModel<
_meta?: Record;
}[];
})[]
->('newWorkerList', { default: [] });
+>([]);
const workerSelected = ref([]);
const workerList = ref([]);
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();
}
@@ -296,6 +306,42 @@ watch(
function setCurrentBranchId() {
employeeFormState.value.currentBranchId = props.customerBranchId;
}
+
+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;
+ },
+);
@@ -568,11 +614,14 @@ function setCurrentBranchId() {
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') }) }}
@@ -594,9 +643,11 @@ function setCurrentBranchId() {
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';
}
@@ -627,6 +678,7 @@ function setCurrentBranchId() {
:show="
() => {
employeeFormStore.resetFormDataEmployee(true);
+ setCurrentBranchId();
}
"
:before-close="
@@ -700,6 +752,20 @@ function setCurrentBranchId() {
"
:toggleTitle="$t('status.title')"
hideFade
+ :title="
+ currentFromDataEmployee
+ ? setPrefixName(
+ {
+ namePrefix: currentFromDataEmployee.namePrefix,
+ firstName: currentFromDataEmployee.firstName,
+ lastName: currentFromDataEmployee.lastName,
+ firstNameEN: currentFromDataEmployee.firstNameEN,
+ lastNameEN: currentFromDataEmployee.lastNameEN,
+ },
+ { locale },
+ )
+ : '-'
+ "
@view="
() => {
employeeFormState.imageDialog = true;
@@ -985,6 +1051,7 @@ function setCurrentBranchId() {
@@ -1628,6 +1699,7 @@ function setCurrentBranchId() {
v-model:remark="value.remark"
v-model:worker-type="value.workerType"
v-model:number="value.number"
+ v-model:report-date="value.reportDate"
>
{{ $t('general.expirationDate') }} :
@@ -1938,8 +2010,8 @@ function setCurrentBranchId() {
}
: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);
}
@@ -1956,9 +2028,9 @@ function setCurrentBranchId() {
}
: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;
}
diff --git a/src/pages/05_quotation/form.ts b/src/pages/05_quotation/form.ts
index 45801a5a..5b16785a 100644
--- a/src/pages/05_quotation/form.ts
+++ b/src/pages/05_quotation/form.ts
@@ -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]
#[quotation-payment]',
agentPrice: false,
+ sellerId: '',
};
const DEFAULT_DATA_INVOICE: InvoicePayload = {
@@ -67,6 +69,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
file?: File;
_meta?: Record;
}[];
+ 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) {
diff --git a/src/pages/05_quotation/preview/ViewForm.vue b/src/pages/05_quotation/preview/ViewForm.vue
index 0a12408f..3a7fd081 100644
--- a/src/pages/05_quotation/preview/ViewForm.vue
+++ b/src/pages/05_quotation/preview/ViewForm.vue
@@ -1,6 +1,6 @@
-
-
-
-
+
+
+
+ {{ $t('general.status') }}
+
+
-
+
@@ -954,13 +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:on-create-image-list="onCreateImageList"
+ v-model:image-list-on-create="imageListOnCreate"
+ v-model:deletes-status-qr-code-bank-imag="deletesStatusQrCodeBankImag"
+ v-model:attachment="attachment"
+ :attachment-list="attachmentList"
/>
diff --git a/src/pages/08_request-list/MessengerExpansion.vue b/src/pages/08_request-list/MessengerExpansion.vue
index 24434d89..456aefe4 100644
--- a/src/pages/08_request-list/MessengerExpansion.vue
+++ b/src/pages/08_request-list/MessengerExpansion.vue
@@ -13,6 +13,8 @@ const props = defineProps<{
readonly?: boolean;
step: Step;
responsibleAreaDistrictId?: string;
+ defaultMessenger?: string;
+ prefix?: string;
}>();
const emit = defineEmits<{
@@ -56,6 +58,7 @@ const formData = ref(defaultForm);
function triggerUndo() {
assignToForm();
state.isEdit = false;
+ refForm.value?.resetValidation();
}
async function triggerSubmit() {
@@ -84,8 +87,13 @@ function assignToForm() {
customerDutyCost: attributesForm.value.customerDutyCost ?? 30,
companyDuty: attributesForm.value.companyDuty ?? false,
companyDutyCost: attributesForm.value.companyDutyCost ?? 30,
- responsibleUserLocal: attributesForm.value.responsibleUserLocal ?? true,
- responsibleUserId: attributesForm.value.responsibleUserId ?? '',
+ responsibleUserLocal: attributesForm.value.responsibleUserLocal
+ ? attributesForm.value.responsibleUserLocal
+ : props.responsibleAreaDistrictId
+ ? false
+ : true,
+ responsibleUserId:
+ attributesForm.value.responsibleUserId || props.defaultMessenger,
individualDuty: attributesForm.value.individualDuty ?? false,
individualDutyCost: attributesForm.value.individualDutyCost ?? 10,
}),
@@ -109,21 +117,21 @@ function assignToForm() {
refForm?.submit(e)"
/>
diff --git a/src/pages/08_request-list/ProductExpansion.vue b/src/pages/08_request-list/ProductExpansion.vue
index d2214d03..ed5f5784 100644
--- a/src/pages/08_request-list/ProductExpansion.vue
+++ b/src/pages/08_request-list/ProductExpansion.vue
@@ -83,6 +83,8 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
(),
{
id: '',
@@ -128,7 +129,7 @@ defineEmits<{
+import { reactive, ref, watch } from 'vue';
+import { RequestData } from 'src/stores/request-list';
+import { DialogContainer, DialogHeader } from 'src/components/dialog';
+import {
+ BackButton,
+ CancelButton,
+ MainButton,
+ SaveButton,
+} from 'src/components/button';
+import FormResponsibleUser from './FormResponsibleUser.vue';
+import FormGroupHead from './FormGroupHead.vue';
+import TableRequestList from './TableRequestList.vue';
+import { column } from './constants';
+import useAddressStore from 'src/stores/address';
+
+defineProps<{
+ requestList: RequestData[];
+ noLink?: boolean;
+}>();
+
+defineEmits<{
+ (
+ e: 'submit',
+ data: {
+ form: { responsibleUserLocal: boolean; responsibleUserId: string };
+ selected: RequestData[];
+ },
+ ): void;
+}>();
+
+enum Step {
+ RequestList = 1,
+ Configure = 2,
+}
+
+const open = defineModel({ default: false });
+const step = ref(Step.RequestList);
+const selected = ref([]);
+const listSameArea = ref([]);
+const form = reactive({
+ responsibleUserLocal: false,
+ responsibleUserId: '',
+});
+
+function reset() {
+ step.value = Step.RequestList;
+ selected.value = [];
+ form.responsibleUserLocal = false;
+ form.responsibleUserId = '';
+}
+
+function prev() {
+ step.value = Step.RequestList;
+}
+
+watch(
+ () => selected.value,
+ async () => {
+ if (selected.value.length === 1) {
+ const districtId = selected.value[0].quotation.customerBranch.districtId;
+ const ret = await useAddressStore().listSameOfficeArea(districtId);
+ if (ret) listSameArea.value = ret;
+ }
+ },
+);
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('general.next') }}
+
+
+
+
+
+
+
+
diff --git a/src/pages/08_request-list/RequestListView.vue b/src/pages/08_request-list/RequestListView.vue
index a908e4d5..afb649b8 100644
--- a/src/pages/08_request-list/RequestListView.vue
+++ b/src/pages/08_request-list/RequestListView.vue
@@ -26,6 +26,7 @@ import {
getEmployeeName,
getCustomerName,
dialogWarningClose,
+ canAccess,
} from 'src/stores/utils';
import { dateFormatJS } from 'src/utils/datetime';
import { useRequestList } from 'src/stores/request-list';
@@ -54,6 +55,7 @@ import { Invoice } from 'src/stores/payment/types';
import { CreatedBy } from 'src/stores/types';
import { getUserId } from 'src/services/keycloak';
import { QuotationFull } from 'src/stores/quotations/types';
+import useUserStore from 'src/stores/user';
const { locale, t } = useI18n();
@@ -62,7 +64,9 @@ const route = useRoute();
const optionStore = useOptionStore();
const requestListStore = useRequestList();
const flowTemplateStore = useWorkflowTemplate();
+const userStore = useUserStore();
+const currentUserGroup = ref([]);
const workList = ref([]);
const statusFile = ref({
customer: {},
@@ -158,6 +162,10 @@ onMounted(async () => {
initTheme();
initLang();
+ const result = await userStore.fetchUserGroup();
+
+ currentUserGroup.value = result.map((v) => v.name);
+
// get data
await getData();
});
@@ -283,26 +291,38 @@ async function triggerViewFile(opt: {
if (!opt.download) window.open(url, '_blank');
}
-const responsiblePersonList = computed(() => {
- const temp = workList.value?.reduce>(
- (acc, curr: RequestWork) => {
- curr.productService.service?.workflow?.step.forEach((v) => {
- const key = v.order.toString();
+const responsibleList = computed(() => {
+ const temp = workList.value?.reduce<
+ Record
+ >((acc, curr: RequestWork) => {
+ curr.productService.service?.workflow?.step.forEach((v) => {
+ const key = v.order.toString();
+ const responsibleGroup = (
+ v.responsibleGroup as unknown as { group: string }[]
+ ).map((v) => v.group);
- if (!acc[key]) acc[key] = v.responsiblePerson.map((v) => v.user);
+ if (!acc[key]) {
+ acc[key] = {
+ user: v.responsiblePerson.map((v) => v.user),
+ group: responsibleGroup,
+ };
+ }
- const current = acc[key];
+ const current = acc[key];
- v.responsiblePerson.forEach((lhs) => {
- if (current.find((rhs) => rhs.id === lhs.userId)) return;
- current.push(lhs.user);
- });
+ v.responsiblePerson.forEach((lhs) => {
+ if (current.user.find((rhs) => rhs.id === lhs.userId)) return;
+ current.user.push(lhs.user);
});
- return acc;
- },
- {},
- );
+ responsibleGroup.forEach((lhs) => {
+ if (current.group.find((rhs) => rhs === lhs)) return;
+ current.group.push(lhs);
+ });
+ });
+
+ return acc;
+ }, {});
return temp || {};
});
@@ -438,6 +458,26 @@ async function submitRejectCancel() {
pageState.rejectCancelDialog = false;
}
}
+
+function toCustomer(customer: RequestData['quotation']['customerBranch']) {
+ if (!canAccess('customer', 'view')) return;
+ const url = new URL(
+ `/customer-management?tab=customer&id=${customer.customerId}`,
+ window.location.origin,
+ );
+
+ window.open(url.toString(), '_blank');
+}
+
+function toEmployee(employee: RequestData['employee']) {
+ if (!canAccess('customer', 'view')) return;
+ const url = new URL(
+ `/customer-management?tab=employee&id=${employee.id}`,
+ window.location.origin,
+ );
+
+ window.open(url.toString(), '_blank');
+}
@@ -478,11 +518,11 @@ async function submitRejectCancel() {
{{ $t('flow.responsiblePerson') }}
({
name:
$i18n.locale === 'eng'
@@ -494,8 +534,12 @@ async function submitRejectCancel() {
: `/no-img-female.png`
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
}),
- )
- "
+ ),
+ ...responsibleList[pageState.currentStep].group.map((g) => ({
+ name: `${$t('general.group')} ${g}`,
+ imgUrl: '/img-group.png',
+ })),
+ ]"
/>
-
@@ -701,6 +745,7 @@ async function submitRejectCancel() {
}"
>
-
+
{
triggerChangeStatusFile({
@@ -870,8 +928,12 @@ async function submitRejectCancel() {
/>
(),
{
row: () => [],
@@ -36,9 +41,19 @@ defineEmits<{
(e: 'rejectCancel', data: RequestData): void;
}>();
-function responsiblePerson(quotation: QuotationFull): CreatedBy[] | undefined {
+const selected = defineModel('selected');
+
+function responsiblePerson(quotation: QuotationFull) {
const productServiceList = quotation.productServiceList;
const tempPerson: CreatedBy[] = [];
+ const tempGroup: {
+ group: string;
+ id: string;
+ workflowTemplateStepId: string;
+ }[] = [];
+
+ const userIds = new Set();
+ const groupKeys = new Set();
for (const v of productServiceList) {
const tempStep = v.service?.workflow?.step;
@@ -46,10 +61,30 @@ function responsiblePerson(quotation: QuotationFull): CreatedBy[] | undefined {
if (tempStep) {
tempStep.forEach((lhs) => {
for (const rhs of lhs.responsiblePerson) {
- tempPerson.push(rhs.user);
+ if (!userIds.has(rhs.user.id)) {
+ userIds.add(rhs.user.id);
+ tempPerson.push(rhs.user);
+ }
}
});
- return tempPerson;
+
+ tempStep.forEach((lhs) => {
+ const newGroup = lhs.responsibleGroup as unknown as {
+ group: string;
+ id: string;
+ workflowTemplateStepId: string;
+ }[];
+
+ for (const rhs of newGroup) {
+ const key = `${rhs.group}-${rhs.id}-${rhs.workflowTemplateStepId}`;
+ if (!groupKeys.has(key)) {
+ groupKeys.add(key);
+ tempGroup.push(rhs);
+ }
+ }
+ });
+
+ return { user: tempPerson, group: tempGroup };
}
}
@@ -92,10 +127,41 @@ function getEmployeeName(
return (
{
['eng']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstNameEN} ${employee?.lastNameEN}`,
- ['tha']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstName} ${employee?.lastName}`,
+ ['tha']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstName || employee?.firstNameEN} ${employee?.lastName || employee?.lastNameEN}`,
}[opts?.locale || 'eng'] || '-'
);
}
+
+function toCustomer(customer: RequestData['quotation']['customerBranch']) {
+ if (props.noLink) return;
+ const url = new URL(
+ `/customer-management?tab=customer&id=${customer.customerId}`,
+ window.location.origin,
+ );
+
+ window.open(url.toString(), '_blank');
+}
+
+function toEmployee(employee: RequestData['employee']) {
+ if (props.noLink) return;
+ const url = new URL(
+ `/customer-management?tab=employee&id=${employee.id}`,
+ window.location.origin,
+ );
+
+ window.open(url.toString(), '_blank');
+}
+
+function handleCheckAll() {
+ const filteredRows = props.rows.filter((row) =>
+ props.listSameArea?.includes(row.quotation.customerBranch.districtId),
+ );
+ if (selected.value.length === filteredRows.length) {
+ selected.value = [];
+ } else {
+ selected.value = filteredRows;
+ }
+}
+
+
+
+
{{ col.label && $t(col.label) }}
@@ -125,9 +217,32 @@ function getEmployeeName(
} & Omit[0], 'row'>"
>
+
+
+
{{ props.rowIndex + 1 }}
@@ -138,21 +253,53 @@ function getEmployeeName(
- {{
- getCustomerName(props.row, {
- noCode: true,
- locale: $i18n.locale,
- }) || '-'
- }}
+
+ {{
+ getCustomerName(props.row, {
+ noCode: true,
+ locale: $i18n.locale,
+ }) || '-'
+ }}
+
- {{ getEmployeeName(props.row, { locale: $i18n.locale }) || '-' }}
+
+ {{ getEmployeeName(props.row, { locale: $i18n.locale }) || '-' }}
+
+
+
+ {{
+ props.row.employee.employeePassport.length !== 0
+ ? props.row.employee.employeePassport[0].number
+ : '-'
+ }}
+
+
+ {{
+ $i18n.locale === 'eng'
+ ? props.row.dataOffice.nameEN
+ : props.row.dataOffice.name
+ }}
+
+
+ {{ dateFormatJS({ date: props.row.createdAt }) }}
+
+
{{ props.row.quotation.code || '-' }}
-
+ /> -->
+
+ 0 ||
+ (responsiblePerson(props.row.quotation).group ?? []).length >
+ 0
"
+ :data="[
+ ...responsiblePerson(props.row.quotation).user.map((v) => ({
+ name:
+ $i18n.locale === 'eng'
+ ? `${v.firstNameEN} ${v.lastNameEN}`
+ : `${v.firstName} ${v.lastName}`,
+ imgUrl: !v.selectedImage
+ ? v.gender === 'male'
+ ? `/no-img-man.png`
+ : `/no-img-female.png`
+ : `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
+ })),
+ ...responsiblePerson(props.row.quotation).group.map((g) => ({
+ name: `${$t('general.group')} ${g.group}`,
+ imgUrl: '/img-group.png',
+ })),
+ ]"
/>
-
@@ -406,4 +581,15 @@ function getEmployeeName(
background: var(--red-8);
}
}
+
+.link {
+ color: hsl(var(--info-bg));
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.disabled-row {
+ opacity: 0.3;
+ filter: grayscale(1);
+}
diff --git a/src/pages/08_request-list/constants.ts b/src/pages/08_request-list/constants.ts
index 2534aea5..c71aac2e 100644
--- a/src/pages/08_request-list/constants.ts
+++ b/src/pages/08_request-list/constants.ts
@@ -28,6 +28,24 @@ export const column = [
label: 'customer.employee',
field: 'employee',
},
+ {
+ name: 'employeePassport',
+ align: 'center',
+ label: 'customerEmployee.form.passportNo',
+ field: 'employeePassport',
+ },
+ {
+ name: 'dataOffice',
+ align: 'center',
+ label: 'requestList.dataOffice',
+ field: 'dataOffice',
+ },
+ {
+ name: 'createdAt',
+ align: 'center',
+ label: 'general.createdAt',
+ field: 'createdAt',
+ },
{
name: 'quotationCode',
diff --git a/src/pages/09_task-order/MainPage.vue b/src/pages/09_task-order/MainPage.vue
index a842b431..2a9f22bd 100644
--- a/src/pages/09_task-order/MainPage.vue
+++ b/src/pages/09_task-order/MainPage.vue
@@ -1,10 +1,11 @@
- {{ Object.values(stats).reduce((s, v) => s + v, 0) }}
+ {{
+ pageState.isMessenger
+ ? pageState.total
+ : stats[pageState.currentTab as TaskOrderStatus]
+ }}
+
+
+
+
diff --git a/src/pages/09_task-order/SelectReadyRequestWork.vue b/src/pages/09_task-order/SelectReadyRequestWork.vue
index 34d9dd22..5276a1a5 100644
--- a/src/pages/09_task-order/SelectReadyRequestWork.vue
+++ b/src/pages/09_task-order/SelectReadyRequestWork.vue
@@ -4,6 +4,7 @@ import {
useRequestList,
RequestWork,
RequestWorkStatus,
+ RequestDataStatus,
} from 'src/stores/request-list';
import DialogHeader from 'src/components/dialog/DialogHeader.vue';
import CancelButton from 'src/components/button/CancelButton.vue';
@@ -192,7 +193,8 @@ function submit() {
s.workStatus ===
(props.creditNote
? RequestWorkStatus.Canceled
- : RequestWorkStatus.InProgress),
+ : RequestWorkStatus.InProgress) ||
+ v.request.requestDataStatus === RequestDataStatus.Canceled,
);
if (curr) {
const task: Task = {
@@ -293,6 +295,7 @@ function assignTempGroup() {
class="bordered-b"
>