2024-06-19 15:43:58 +07:00
|
|
|
<script lang="ts" setup>
|
2024-10-03 15:33:20 +07:00
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
|
import { storeToRefs } from 'pinia';
|
2024-10-04 14:51:31 +07:00
|
|
|
import { useQuasar } from 'quasar';
|
2024-10-11 11:54:22 +07:00
|
|
|
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
|
|
|
|
import { dialogCheckData } from 'stores/utils';
|
2024-10-15 09:42:16 +07:00
|
|
|
import { ProductTree, quotationProductTree } from './utils';
|
2024-10-03 15:33:20 +07:00
|
|
|
|
2024-10-04 14:51:31 +07:00
|
|
|
// NOTE: Import stores
|
2024-10-03 18:10:40 +07:00
|
|
|
import { setLocale, dateFormat, calculateAge } from 'src/utils/datetime';
|
2024-10-04 17:01:24 +07:00
|
|
|
import { useEmployeeForm } from 'src/pages/03_customer-management/form';
|
2024-10-04 14:51:31 +07:00
|
|
|
import useProductServiceStore from 'stores/product-service';
|
2024-10-04 17:01:24 +07:00
|
|
|
import { baseUrl, waitAll } from 'src/stores/utils';
|
2024-10-04 14:51:31 +07:00
|
|
|
import useCustomerStore from 'stores/customer';
|
2024-10-03 15:33:20 +07:00
|
|
|
import useOptionStore from 'stores/options';
|
|
|
|
|
import { useQuotationForm } from './form';
|
2024-10-04 17:01:24 +07:00
|
|
|
import useOcrStore from 'stores/ocr';
|
2024-10-07 10:20:29 +07:00
|
|
|
import { deleteItem } from 'stores/utils';
|
2024-10-08 13:45:06 +07:00
|
|
|
import { runOcr, parseResultMRZ } from 'src/utils/ocr';
|
2024-10-04 14:51:31 +07:00
|
|
|
|
|
|
|
|
// NOTE Import Types
|
|
|
|
|
import { QuotationPayload } from 'src/stores/quotations/types';
|
2024-10-04 17:01:24 +07:00
|
|
|
import { EmployeeWorker } from 'src/stores/quotations/types';
|
2024-10-03 15:33:20 +07:00
|
|
|
import { Employee } from 'src/stores/employee/types';
|
|
|
|
|
import {
|
|
|
|
|
ProductGroup,
|
2024-10-04 14:57:08 +07:00
|
|
|
Product,
|
2024-10-03 15:33:20 +07:00
|
|
|
Service,
|
|
|
|
|
} from 'src/stores/product-service/types';
|
2024-06-19 15:43:58 +07:00
|
|
|
|
2024-10-04 14:51:31 +07:00
|
|
|
// NOTE: Import Components
|
2024-10-04 17:01:24 +07:00
|
|
|
import FormEmployeePassport from 'components/03_customer-management/FormEmployeePassport.vue';
|
|
|
|
|
import FormEmployeeVisa from 'components/03_customer-management/FormEmployeeVisa.vue';
|
|
|
|
|
import FormReferDocument from 'src/components/05_quotation/FormReferDocument.vue';
|
|
|
|
|
import { UploadFileGroup, noticeJobEmployment } from 'components/upload-file';
|
|
|
|
|
import FormPerson from 'components/02_personnel-management/FormPerson.vue';
|
2024-10-04 14:51:31 +07:00
|
|
|
import ProductItem from 'components/05_quotation/ProductItem.vue';
|
2024-07-25 09:27:58 +07:00
|
|
|
import WorkerItem from 'components/05_quotation/WorkerItem.vue';
|
2024-10-04 14:51:31 +07:00
|
|
|
import ToggleButton from 'components/button/ToggleButton.vue';
|
|
|
|
|
import FormAbout from 'components/05_quotation/FormAbout.vue';
|
|
|
|
|
import SelectZone from 'components/shared/SelectZone.vue';
|
|
|
|
|
import PersonCard from 'components/shared/PersonCard.vue';
|
2024-10-10 17:49:28 +07:00
|
|
|
import {
|
|
|
|
|
AddButton,
|
|
|
|
|
SaveButton,
|
|
|
|
|
EditButton,
|
|
|
|
|
UndoButton,
|
|
|
|
|
DeleteButton,
|
2024-10-18 09:10:33 +07:00
|
|
|
CancelButton,
|
2024-10-18 09:37:34 +07:00
|
|
|
MainButton,
|
2024-10-10 17:49:28 +07:00
|
|
|
} from 'components/button';
|
2024-10-04 14:51:31 +07:00
|
|
|
import ProductServiceForm from './ProductServiceForm.vue';
|
|
|
|
|
import QuotationFormInfo from './QuotationFormInfo.vue';
|
2024-10-04 17:01:24 +07:00
|
|
|
import ProfileBanner from 'components/ProfileBanner.vue';
|
|
|
|
|
import DialogForm from 'components/DialogForm.vue';
|
|
|
|
|
import {
|
|
|
|
|
uploadFileListEmployee,
|
|
|
|
|
columnsAttachment,
|
|
|
|
|
} from 'src/pages/03_customer-management/constant';
|
2024-10-10 09:24:02 +07:00
|
|
|
import { precisionRound } from 'src/utils/arithmetic';
|
|
|
|
|
import { useConfigStore } from 'src/stores/config';
|
2024-10-04 14:51:31 +07:00
|
|
|
|
2024-10-11 11:28:06 +07:00
|
|
|
// defineProps<{
|
|
|
|
|
// readonly?: boolean;
|
|
|
|
|
// }>();
|
2024-10-03 11:14:12 +07:00
|
|
|
|
|
|
|
|
type Node = {
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
opened?: boolean;
|
|
|
|
|
checked?: boolean;
|
|
|
|
|
bg?: string;
|
|
|
|
|
fg?: string;
|
|
|
|
|
icon?: string;
|
|
|
|
|
children?: Node[];
|
|
|
|
|
};
|
2024-09-18 15:38:50 +07:00
|
|
|
|
2024-10-04 14:51:31 +07:00
|
|
|
type ProductGroupId = string;
|
2024-06-19 15:43:58 +07:00
|
|
|
|
2024-10-10 09:24:02 +07:00
|
|
|
const configStore = useConfigStore();
|
2024-10-03 11:14:12 +07:00
|
|
|
const productServiceStore = useProductServiceStore();
|
2024-10-04 17:01:24 +07:00
|
|
|
const employeeFormStore = useEmployeeForm();
|
2024-10-04 14:51:31 +07:00
|
|
|
const customerStore = useCustomerStore();
|
2024-10-03 11:14:12 +07:00
|
|
|
const quotationForm = useQuotationForm();
|
2024-10-04 14:51:31 +07:00
|
|
|
const optionStore = useOptionStore();
|
2024-10-11 11:54:22 +07:00
|
|
|
const { locale } = useI18n();
|
2024-10-10 13:35:15 +07:00
|
|
|
const ocrStore = useOcrStore();
|
2024-09-30 16:36:18 +07:00
|
|
|
const $q = useQuasar();
|
|
|
|
|
|
2024-10-07 16:57:44 +07:00
|
|
|
const {
|
|
|
|
|
currentFormData: quotationFormData,
|
|
|
|
|
currentFormState: quotationFormState,
|
2024-10-08 13:45:06 +07:00
|
|
|
newWorkerList,
|
|
|
|
|
fileItemNewWorker,
|
2024-10-08 16:42:17 +07:00
|
|
|
quotationFull,
|
2024-10-07 16:57:44 +07:00
|
|
|
} = storeToRefs(quotationForm);
|
2024-10-04 14:51:31 +07:00
|
|
|
|
2024-10-10 09:24:02 +07:00
|
|
|
const { data: config } = storeToRefs(configStore);
|
|
|
|
|
|
2024-10-03 18:10:40 +07:00
|
|
|
const refSelectZoneEmployee = ref<InstanceType<typeof SelectZone>>();
|
2024-10-08 13:45:06 +07:00
|
|
|
const mrz = ref<Awaited<ReturnType<typeof parseResultMRZ>>>();
|
2024-06-19 15:43:58 +07:00
|
|
|
const toggleWorker = ref(true);
|
2024-10-08 16:42:17 +07:00
|
|
|
const currentQuotationId = ref<string | undefined>(undefined);
|
2024-10-04 14:51:31 +07:00
|
|
|
const date = ref();
|
2024-10-03 18:10:40 +07:00
|
|
|
const preSelectedWorker = ref<Employee[]>([]);
|
2024-10-10 13:31:46 +07:00
|
|
|
const readonly = computed(() => {
|
|
|
|
|
return !(
|
|
|
|
|
quotationFormState.value.mode === 'create' ||
|
|
|
|
|
quotationFormState.value.mode === 'edit'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-08 13:45:06 +07:00
|
|
|
const selectedWorker = ref<
|
|
|
|
|
(Employee & {
|
|
|
|
|
attachment?: {
|
|
|
|
|
name?: string;
|
|
|
|
|
group?: string;
|
|
|
|
|
url?: string;
|
|
|
|
|
file?: File;
|
|
|
|
|
_meta?: Record<string, any>;
|
|
|
|
|
}[];
|
|
|
|
|
})[]
|
|
|
|
|
>([]);
|
2024-10-07 12:55:39 +07:00
|
|
|
const workerList = ref<Employee[]>([]);
|
2024-10-03 18:10:40 +07:00
|
|
|
|
2024-10-08 16:38:15 +07:00
|
|
|
const selectedProductGroup = ref('');
|
2024-10-04 15:16:28 +07:00
|
|
|
const agentPrice = ref(false);
|
2024-10-10 09:24:02 +07:00
|
|
|
const summaryPrice = computed(() =>
|
|
|
|
|
productServiceList.value.reduce(
|
|
|
|
|
(a, c) => {
|
|
|
|
|
const price = precisionRound(c.pricePerUnit * c.amount);
|
|
|
|
|
const vat = precisionRound(
|
|
|
|
|
(c.pricePerUnit * c.amount - c.discount) * (config.value?.vat || 0.07),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
a.totalPrice = precisionRound(a.totalPrice + price);
|
|
|
|
|
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
|
2024-10-17 15:50:26 +07:00
|
|
|
a.vat = c.product.calcVat ? precisionRound(a.vat + vat) : a.vat;
|
|
|
|
|
a.vatExcluded = c.product.calcVat
|
|
|
|
|
? a.vatExcluded
|
|
|
|
|
: precisionRound(a.vat + vat);
|
2024-10-10 09:24:02 +07:00
|
|
|
a.finalPrice = precisionRound(
|
2024-10-10 15:05:15 +07:00
|
|
|
a.totalPrice -
|
|
|
|
|
a.totalDiscount +
|
|
|
|
|
a.vat -
|
|
|
|
|
Number(quotationFormData.value.discount || 0),
|
2024-10-10 09:24:02 +07:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return a;
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
totalPrice: 0,
|
|
|
|
|
totalDiscount: 0,
|
|
|
|
|
vat: 0,
|
2024-10-17 15:50:26 +07:00
|
|
|
vatExcluded: 0,
|
2024-10-10 09:24:02 +07:00
|
|
|
finalPrice: 0,
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
2024-10-04 15:16:28 +07:00
|
|
|
|
2024-09-18 15:38:50 +07:00
|
|
|
const payBank = ref('');
|
2024-09-30 16:36:18 +07:00
|
|
|
|
2024-10-03 11:14:12 +07:00
|
|
|
const pageState = reactive({
|
|
|
|
|
hideStat: false,
|
|
|
|
|
inputSearch: '',
|
|
|
|
|
statusFilter: 'all',
|
|
|
|
|
fieldSelected: [],
|
|
|
|
|
gridView: false,
|
2024-10-07 10:20:29 +07:00
|
|
|
isLoaded: false,
|
2024-10-03 11:14:12 +07:00
|
|
|
|
|
|
|
|
currentTab: 'all',
|
|
|
|
|
addModal: false,
|
|
|
|
|
quotationModal: false,
|
|
|
|
|
employeeModal: false,
|
|
|
|
|
productServiceModal: false,
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-04 14:57:08 +07:00
|
|
|
const productList = ref<Partial<Record<ProductGroupId, Product[]>>>({});
|
2024-10-03 11:14:12 +07:00
|
|
|
const serviceList = ref<Partial<Record<ProductGroupId, Service[]>>>({});
|
2024-10-04 14:51:31 +07:00
|
|
|
const productGroup = ref<ProductGroup[]>([]);
|
2024-10-03 11:14:12 +07:00
|
|
|
|
2024-10-04 17:01:24 +07:00
|
|
|
const { state: employeeFormState, currentFromDataEmployee } =
|
|
|
|
|
storeToRefs(employeeFormStore);
|
|
|
|
|
|
2024-10-03 11:14:12 +07:00
|
|
|
const selectedGroupSub = ref<'product' | 'service' | null>(null);
|
2024-10-08 13:45:06 +07:00
|
|
|
const formDataEmployee = ref<
|
|
|
|
|
EmployeeWorker & {
|
|
|
|
|
attachment?: {
|
|
|
|
|
name?: string;
|
|
|
|
|
group?: string;
|
|
|
|
|
url?: string;
|
|
|
|
|
file?: File;
|
|
|
|
|
_meta?: Record<string, any>;
|
|
|
|
|
}[];
|
|
|
|
|
}
|
|
|
|
|
>({
|
|
|
|
|
passportNo: '',
|
2024-10-04 17:01:24 +07:00
|
|
|
documentExpireDate: new Date(),
|
|
|
|
|
lastNameEN: '',
|
|
|
|
|
lastName: '',
|
|
|
|
|
middleNameEN: '',
|
|
|
|
|
middleName: '',
|
|
|
|
|
firstNameEN: '',
|
|
|
|
|
firstName: '',
|
|
|
|
|
namePrefix: '',
|
|
|
|
|
nationality: '',
|
|
|
|
|
gender: '',
|
|
|
|
|
dateOfBirth: new Date(),
|
2024-10-08 13:45:06 +07:00
|
|
|
attachment: [],
|
2024-10-04 17:01:24 +07:00
|
|
|
});
|
|
|
|
|
|
2024-10-03 17:17:15 +07:00
|
|
|
const productServiceList = ref<
|
|
|
|
|
Required<QuotationPayload['productServiceList'][number]>[]
|
|
|
|
|
>([]);
|
|
|
|
|
|
2024-10-18 09:10:33 +07:00
|
|
|
function closeTab() {
|
|
|
|
|
window.close();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 13:35:15 +07:00
|
|
|
async function assignToProductServiceList() {
|
2024-10-15 16:13:00 +07:00
|
|
|
const ret = await productServiceStore.fetchProductGroup({
|
2024-10-10 13:35:15 +07:00
|
|
|
page: 1,
|
|
|
|
|
pageSize: 9999,
|
|
|
|
|
});
|
2024-10-15 17:16:36 +07:00
|
|
|
|
2024-10-10 13:35:15 +07:00
|
|
|
if (ret) {
|
|
|
|
|
productGroup.value = ret.result;
|
|
|
|
|
|
|
|
|
|
productServiceList.value = quotationFormData.value.productServiceList.map(
|
|
|
|
|
(v) => ({
|
|
|
|
|
workerIndex: v.workerIndex || [0],
|
|
|
|
|
vat: v.vat || 0,
|
|
|
|
|
pricePerUnit: v.pricePerUnit || 0,
|
|
|
|
|
discount: v.discount || 0,
|
2024-10-11 11:54:22 +07:00
|
|
|
amount: v.amount || 0,
|
2024-10-10 13:35:15 +07:00
|
|
|
product: v.product,
|
|
|
|
|
work: v.work || null,
|
|
|
|
|
service: v.service || null,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
selectedProductGroup.value =
|
2024-10-10 17:49:28 +07:00
|
|
|
quotationFormData.value.productServiceList[0]?.product.productGroup?.id ||
|
2024-10-10 13:35:15 +07:00
|
|
|
'';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function convertDataToFormSubmit() {
|
2024-10-07 16:57:44 +07:00
|
|
|
quotationFormData.value.productServiceList = JSON.parse(
|
|
|
|
|
JSON.stringify(
|
2024-10-08 13:45:06 +07:00
|
|
|
productServiceList.value.map((v) => ({
|
2024-10-10 18:16:04 +07:00
|
|
|
workerIndex: v.workerIndex,
|
|
|
|
|
discount: v.discount,
|
|
|
|
|
amount: v.amount,
|
2024-10-07 16:57:44 +07:00
|
|
|
product: v.product,
|
|
|
|
|
work: v.work,
|
|
|
|
|
service: v.service,
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
2024-10-08 13:45:06 +07:00
|
|
|
selectedWorker.value.forEach((v, i) => {
|
|
|
|
|
if (v.attachment !== undefined) {
|
|
|
|
|
v.attachment.forEach((value) => {
|
|
|
|
|
fileItemNewWorker.value.push({
|
|
|
|
|
employeeIndex: i,
|
|
|
|
|
name: value.name,
|
|
|
|
|
group: value.group as 'passport' | 'visa' | 'in-country-notice',
|
|
|
|
|
url: value.url,
|
|
|
|
|
file: value.file,
|
|
|
|
|
_meta: value._meta,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-07 16:57:44 +07:00
|
|
|
quotationFormData.value.worker = JSON.parse(
|
|
|
|
|
JSON.stringify(
|
2024-10-08 13:45:06 +07:00
|
|
|
selectedWorker.value.map((v) => {
|
2024-10-07 16:57:44 +07:00
|
|
|
if (v.id === undefined) {
|
2024-10-08 13:45:06 +07:00
|
|
|
const { attachment, ...payload } = v;
|
|
|
|
|
return payload;
|
2024-10-07 16:57:44 +07:00
|
|
|
} else {
|
|
|
|
|
return v.id;
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
2024-10-17 14:51:42 +07:00
|
|
|
quotationFormData.value.paySplit = JSON.parse(
|
|
|
|
|
JSON.stringify(
|
|
|
|
|
quotationFormData.value.paySplit.map((p) => ({ ...p, no: undefined })),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
2024-10-10 13:35:15 +07:00
|
|
|
quotationFormData.value = {
|
|
|
|
|
id: quotationFormData.value.id,
|
|
|
|
|
productServiceList: quotationFormData.value.productServiceList,
|
|
|
|
|
urgent: quotationFormData.value.urgent,
|
|
|
|
|
customerBranchId: quotationFormData.value.customerBranchId,
|
2024-10-15 17:16:36 +07:00
|
|
|
registeredBranchId: quotationFormData.value.registeredBranchId,
|
2024-10-10 13:35:15 +07:00
|
|
|
worker: quotationFormData.value.worker,
|
|
|
|
|
payBillDate: quotationFormData.value.payBillDate,
|
|
|
|
|
paySplit: quotationFormData.value.paySplit,
|
|
|
|
|
paySplitCount: quotationFormData.value.paySplitCount,
|
|
|
|
|
payCondition: quotationFormData.value.payCondition,
|
|
|
|
|
dueDate: quotationFormData.value.dueDate,
|
|
|
|
|
documentReceivePoint: quotationFormData.value.documentReceivePoint,
|
|
|
|
|
contactTel: quotationFormData.value.contactTel,
|
|
|
|
|
contactName: quotationFormData.value.contactName,
|
|
|
|
|
workName: quotationFormData.value.workName,
|
|
|
|
|
_count: quotationFormData.value._count,
|
|
|
|
|
status: quotationFormData.value.status,
|
2024-10-18 10:40:34 +07:00
|
|
|
discount: quotationFormData.value.discount,
|
2024-10-18 11:12:04 +07:00
|
|
|
remark: quotationFormData.value.remark,
|
2024-10-10 13:35:15 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const res = await quotationForm.submitQuotation();
|
|
|
|
|
|
|
|
|
|
if (res === true) {
|
|
|
|
|
quotationFormState.value.mode = 'info';
|
|
|
|
|
}
|
2024-10-07 16:57:44 +07:00
|
|
|
}
|
|
|
|
|
|
2024-10-03 11:14:12 +07:00
|
|
|
async function getAllProduct(
|
|
|
|
|
groupId: string,
|
2024-10-15 18:01:11 +07:00
|
|
|
opts?: { force?: boolean; page?: number; pageSize?: number; query?: string },
|
2024-10-03 11:14:12 +07:00
|
|
|
) {
|
|
|
|
|
selectedGroupSub.value = 'product';
|
|
|
|
|
if (!opts?.force && productList.value[groupId] !== undefined) return;
|
|
|
|
|
const ret = await productServiceStore.fetchListProduct({
|
|
|
|
|
page: opts?.page ?? 1,
|
|
|
|
|
pageSize: opts?.pageSize ?? 9999,
|
2024-10-15 17:16:36 +07:00
|
|
|
query: opts?.query,
|
2024-10-03 11:14:12 +07:00
|
|
|
productGroupId: groupId,
|
|
|
|
|
});
|
|
|
|
|
if (ret) productList.value[groupId] = ret.result;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 17:57:33 +07:00
|
|
|
function setDefaultFormEmployee() {
|
|
|
|
|
formDataEmployee.value = {
|
2024-10-08 13:45:06 +07:00
|
|
|
passportNo: '',
|
2024-10-04 17:57:33 +07:00
|
|
|
documentExpireDate: new Date(),
|
|
|
|
|
lastNameEN: '',
|
|
|
|
|
lastName: '',
|
|
|
|
|
middleNameEN: '',
|
|
|
|
|
middleName: '',
|
|
|
|
|
firstNameEN: '',
|
|
|
|
|
firstName: '',
|
|
|
|
|
namePrefix: '',
|
|
|
|
|
nationality: '',
|
|
|
|
|
gender: '',
|
|
|
|
|
dateOfBirth: new Date(),
|
2024-10-08 13:45:06 +07:00
|
|
|
attachment: [],
|
2024-10-04 17:57:33 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
employeeFormState.value.dialogModal = false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 11:14:12 +07:00
|
|
|
async function getAllService(
|
|
|
|
|
groupId: string,
|
2024-10-15 18:01:11 +07:00
|
|
|
opts?: { force?: boolean; page?: number; pageSize?: number; query?: string },
|
2024-10-03 11:14:12 +07:00
|
|
|
) {
|
|
|
|
|
selectedGroupSub.value = 'service';
|
2024-10-15 18:01:11 +07:00
|
|
|
|
2024-10-03 11:14:12 +07:00
|
|
|
if (!opts?.force && serviceList.value[groupId] !== undefined) return;
|
2024-10-15 18:01:11 +07:00
|
|
|
|
2024-10-03 11:14:12 +07:00
|
|
|
const ret = await productServiceStore.fetchListService({
|
|
|
|
|
page: opts?.page ?? 1,
|
|
|
|
|
pageSize: opts?.pageSize ?? 9999,
|
|
|
|
|
productGroupId: groupId,
|
2024-10-15 17:16:36 +07:00
|
|
|
query: opts?.query,
|
|
|
|
|
|
2024-10-03 11:14:12 +07:00
|
|
|
fullDetail: true,
|
|
|
|
|
});
|
|
|
|
|
if (ret) serviceList.value[groupId] = ret.result;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 17:01:24 +07:00
|
|
|
function triggerCreateEmployee() {
|
|
|
|
|
employeeFormStore.resetFormDataEmployee(true);
|
2024-10-10 17:40:54 +07:00
|
|
|
setDefaultFormEmployee();
|
2024-10-04 17:01:24 +07:00
|
|
|
employeeFormState.value.dialogType = 'create';
|
|
|
|
|
employeeFormState.value.dialogModal = true;
|
|
|
|
|
employeeFormState.value.isEmployeeEdit = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 18:10:40 +07:00
|
|
|
async function triggerSelectEmployeeDialog() {
|
2024-10-03 11:14:12 +07:00
|
|
|
pageState.employeeModal = true;
|
2024-10-03 18:10:40 +07:00
|
|
|
await nextTick();
|
|
|
|
|
refSelectZoneEmployee.value?.assignSelect(
|
|
|
|
|
preSelectedWorker.value,
|
|
|
|
|
selectedWorker.value,
|
|
|
|
|
);
|
2024-10-03 11:14:12 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function triggerProductServiceDialog() {
|
|
|
|
|
pageState.productServiceModal = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 15:33:20 +07:00
|
|
|
function toggleDeleteProduct(index: number) {
|
2024-10-11 11:11:16 +07:00
|
|
|
productServiceList.value.splice(index, 1);
|
2024-10-08 16:42:17 +07:00
|
|
|
}
|
|
|
|
|
async function assignWorkerToSelectedWorker() {
|
2024-10-09 09:08:19 +07:00
|
|
|
if (quotationFormData.value.customerBranchId) {
|
|
|
|
|
const retEmp = await customerStore.fetchBranchEmployee(
|
|
|
|
|
quotationFormData.value.customerBranchId,
|
2024-10-17 17:39:20 +07:00
|
|
|
{ passport: true },
|
2024-10-08 16:42:17 +07:00
|
|
|
);
|
2024-10-09 09:08:19 +07:00
|
|
|
if (retEmp) {
|
|
|
|
|
workerList.value = retEmp.data.result;
|
|
|
|
|
quotationFormData.value.worker.forEach((value) => {
|
2024-10-09 09:20:10 +07:00
|
|
|
const tempValue: Employee | undefined = retEmp.data.result.find(
|
2024-10-09 09:08:19 +07:00
|
|
|
(v) => v.id === value.id,
|
|
|
|
|
);
|
|
|
|
|
if (tempValue !== undefined) selectedWorker.value.push(tempValue);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-03 15:33:20 +07:00
|
|
|
}
|
2024-10-03 11:14:12 +07:00
|
|
|
|
2024-10-03 15:33:20 +07:00
|
|
|
function convertToTable(nodes: Node[]) {
|
2024-10-03 17:17:15 +07:00
|
|
|
const _recursive = (v: Node): Node | Node[] => {
|
|
|
|
|
if (v.checked && v.children) return v.children.flatMap(_recursive);
|
|
|
|
|
if (v.checked) return v;
|
|
|
|
|
return [];
|
|
|
|
|
};
|
2024-10-08 10:06:11 +07:00
|
|
|
const list = nodes.flatMap(_recursive).map((v) => v.value);
|
|
|
|
|
|
|
|
|
|
list.forEach((v) => {
|
|
|
|
|
v.amount = Math.max(selectedWorker.value.length, 1);
|
2024-10-10 17:49:28 +07:00
|
|
|
for (let i = 0; i < selectedWorker.value.length; i++) {
|
|
|
|
|
v.workerIndex.push(i);
|
|
|
|
|
}
|
2024-10-08 10:06:11 +07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
productServiceList.value = list;
|
2024-10-04 09:16:40 +07:00
|
|
|
pageState.productServiceModal = false;
|
2024-10-03 11:14:12 +07:00
|
|
|
}
|
|
|
|
|
|
2024-10-03 18:10:40 +07:00
|
|
|
function convertEmployeeToTable() {
|
2024-10-11 11:11:16 +07:00
|
|
|
productServiceList.value.forEach((v) => {
|
2024-10-08 10:06:11 +07:00
|
|
|
if (selectedWorker.value.length === 0 && v.amount === 1) v.amount -= 1;
|
2024-10-11 11:11:16 +07:00
|
|
|
|
2024-10-08 10:06:11 +07:00
|
|
|
v.amount = Math.max(
|
|
|
|
|
v.amount + preSelectedWorker.value.length - selectedWorker.value.length,
|
|
|
|
|
1,
|
|
|
|
|
);
|
2024-10-10 17:49:28 +07:00
|
|
|
|
2024-10-11 11:11:16 +07:00
|
|
|
const oldWorkerId: string[] = [];
|
|
|
|
|
const newWorkerIndex: number[] = [];
|
|
|
|
|
|
|
|
|
|
selectedWorker.value.forEach((item, i) => {
|
|
|
|
|
if (v.workerIndex.includes(i)) oldWorkerId.push(item.id);
|
|
|
|
|
});
|
|
|
|
|
preSelectedWorker.value.forEach((item, i) => {
|
|
|
|
|
if (selectedWorker.value.find((n) => item.id === n.id)) return;
|
|
|
|
|
newWorkerIndex.push(i);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
v.workerIndex = oldWorkerId
|
|
|
|
|
.map((id) => preSelectedWorker.value.findIndex((item) => item.id === id))
|
|
|
|
|
.filter((idx) => idx !== -1)
|
|
|
|
|
.concat(newWorkerIndex);
|
2024-10-08 10:06:11 +07:00
|
|
|
});
|
2024-10-11 11:11:16 +07:00
|
|
|
|
2024-10-03 18:10:40 +07:00
|
|
|
refSelectZoneEmployee.value?.assignSelect(
|
|
|
|
|
selectedWorker.value,
|
|
|
|
|
preSelectedWorker.value,
|
|
|
|
|
);
|
|
|
|
|
pageState.employeeModal = false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 16:36:18 +07:00
|
|
|
function changeMode(mode: string) {
|
|
|
|
|
if (mode === 'light') {
|
|
|
|
|
localStorage.setItem('currentTheme', 'light');
|
|
|
|
|
$q.dark.set(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode === 'dark') {
|
|
|
|
|
localStorage.setItem('currentTheme', 'dark');
|
|
|
|
|
$q.dark.set(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode === 'baseOnDevice') {
|
|
|
|
|
localStorage.setItem('currentTheme', 'baseOnDevice');
|
|
|
|
|
if (
|
|
|
|
|
window.matchMedia &&
|
|
|
|
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
|
|
|
) {
|
|
|
|
|
$q.dark.set(true);
|
|
|
|
|
} else {
|
|
|
|
|
$q.dark.set(false);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
2024-10-10 09:24:02 +07:00
|
|
|
await configStore.getConfig();
|
2024-10-04 15:16:28 +07:00
|
|
|
// get language
|
2024-09-30 16:36:18 +07:00
|
|
|
const getCurLang = localStorage.getItem('currentLanguage');
|
|
|
|
|
if (getCurLang === 'English') {
|
|
|
|
|
locale.value = 'eng';
|
|
|
|
|
setLocale('en-gb');
|
|
|
|
|
}
|
|
|
|
|
if (getCurLang === 'ไทย') {
|
|
|
|
|
locale.value = 'tha';
|
|
|
|
|
setLocale('th');
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 15:16:28 +07:00
|
|
|
// get theme
|
2024-09-30 16:36:18 +07:00
|
|
|
const getCurTheme = localStorage.getItem('currentTheme');
|
|
|
|
|
if (
|
|
|
|
|
getCurTheme === 'light' ||
|
|
|
|
|
getCurTheme === 'dark' ||
|
|
|
|
|
getCurTheme === 'baseOnDevice'
|
|
|
|
|
) {
|
|
|
|
|
changeMode(getCurTheme);
|
|
|
|
|
} else {
|
|
|
|
|
changeMode('light');
|
|
|
|
|
}
|
2024-10-04 15:16:28 +07:00
|
|
|
|
|
|
|
|
// get query string
|
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
|
|
|
|
|
|
const price = urlParams.get('agentPrice');
|
|
|
|
|
agentPrice.value = price === 'true' ? true : false;
|
|
|
|
|
|
|
|
|
|
date.value = Date.now();
|
|
|
|
|
|
2024-10-08 16:42:17 +07:00
|
|
|
currentQuotationId.value = urlParams.get('quotationId') || undefined;
|
|
|
|
|
|
2024-10-15 17:16:36 +07:00
|
|
|
quotationFormData.value.registeredBranchId = urlParams.get('branchId') || '';
|
|
|
|
|
|
2024-10-04 15:16:28 +07:00
|
|
|
quotationFormData.value.customerBranchId =
|
|
|
|
|
urlParams.get('customerBranchId') || '';
|
|
|
|
|
|
2024-10-07 16:57:44 +07:00
|
|
|
quotationFormState.value.mode = urlParams.get('statusDialog') as
|
|
|
|
|
| 'info'
|
|
|
|
|
| 'edit'
|
|
|
|
|
| 'create';
|
|
|
|
|
|
2024-10-04 15:16:28 +07:00
|
|
|
// fetch option
|
|
|
|
|
const resultOption = await fetch('/option/option.json');
|
|
|
|
|
const rawOption = await resultOption.json();
|
|
|
|
|
if (locale.value === 'eng') optionStore.globalOption = rawOption.eng;
|
|
|
|
|
if (locale.value === 'tha') optionStore.globalOption = rawOption.tha;
|
|
|
|
|
|
2024-10-08 16:42:17 +07:00
|
|
|
if (
|
|
|
|
|
currentQuotationId.value !== undefined &&
|
|
|
|
|
quotationFormState.value.mode !== 'create'
|
|
|
|
|
) {
|
|
|
|
|
await quotationForm.assignFormData(
|
|
|
|
|
currentQuotationId.value,
|
|
|
|
|
quotationFormState.value.mode,
|
|
|
|
|
);
|
2024-10-09 09:20:10 +07:00
|
|
|
await assignWorkerToSelectedWorker();
|
2024-10-07 10:20:29 +07:00
|
|
|
}
|
2024-10-10 13:35:15 +07:00
|
|
|
await assignToProductServiceList();
|
2024-10-07 10:20:29 +07:00
|
|
|
|
|
|
|
|
pageState.isLoaded = true;
|
2024-09-30 16:36:18 +07:00
|
|
|
});
|
2024-10-07 10:20:29 +07:00
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => quotationFormData.value.customerBranchId,
|
|
|
|
|
async (v) => {
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
url.searchParams.set('customerBranchId', v);
|
|
|
|
|
history.pushState({}, '', url.toString());
|
|
|
|
|
|
2024-10-15 09:42:16 +07:00
|
|
|
if (!v) return;
|
2024-10-17 17:39:20 +07:00
|
|
|
const retEmp = await customerStore.fetchBranchEmployee(v, {
|
|
|
|
|
passport: true,
|
|
|
|
|
});
|
2024-10-15 09:42:16 +07:00
|
|
|
if (retEmp) {
|
|
|
|
|
workerList.value = retEmp.data.result;
|
|
|
|
|
}
|
2024-10-07 10:20:29 +07:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
watch(
|
2024-10-15 17:16:36 +07:00
|
|
|
() => quotationFormData.value.registeredBranchId,
|
2024-10-07 10:20:29 +07:00
|
|
|
async (v) => {
|
|
|
|
|
if (!pageState.isLoaded) return;
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
url.searchParams.set('branchId', v);
|
|
|
|
|
history.pushState({}, '', url.toString());
|
|
|
|
|
},
|
|
|
|
|
);
|
2024-10-15 09:42:16 +07:00
|
|
|
|
2024-10-15 17:16:36 +07:00
|
|
|
const productServiceNodes = ref<ProductTree>([]);
|
2024-10-15 09:42:16 +07:00
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => productServiceList.value,
|
|
|
|
|
() => {
|
|
|
|
|
productServiceNodes.value = quotationProductTree(productServiceList.value);
|
|
|
|
|
},
|
|
|
|
|
);
|
2024-10-15 17:16:36 +07:00
|
|
|
|
|
|
|
|
async function searchEmployee(text: string) {
|
|
|
|
|
let query: string | undefined = text;
|
|
|
|
|
let pageSize = 50;
|
|
|
|
|
|
|
|
|
|
if (!text) {
|
|
|
|
|
query = undefined;
|
|
|
|
|
pageSize = 9999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const retEmp = await customerStore.fetchBranchEmployee(
|
|
|
|
|
quotationFormData.value.customerBranchId,
|
|
|
|
|
{
|
|
|
|
|
query: query,
|
|
|
|
|
pageSize: pageSize,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
if (retEmp) workerList.value = retEmp.data.result;
|
|
|
|
|
}
|
2024-10-18 09:27:56 +07:00
|
|
|
|
|
|
|
|
function storeDataLocal() {
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
'quotation-preview',
|
2024-10-18 12:32:55 +07:00
|
|
|
JSON.stringify({
|
|
|
|
|
data: quotationFormData.value,
|
|
|
|
|
meta: {
|
|
|
|
|
createdBy: quotationFormState.value.createdBy('tha'),
|
|
|
|
|
},
|
|
|
|
|
}),
|
2024-10-18 09:27:56 +07:00
|
|
|
);
|
2024-10-18 10:42:35 +07:00
|
|
|
|
2024-10-18 10:56:10 +07:00
|
|
|
window.open('/quotation/document-view', '_blank');
|
2024-10-18 09:27:56 +07:00
|
|
|
}
|
2024-06-19 15:43:58 +07:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2024-10-16 14:24:54 +07:00
|
|
|
<div class="column surface-0 fullscreen">
|
2024-09-27 15:45:24 +07:00
|
|
|
<div class="color-bar">
|
|
|
|
|
<div class="orange-segment"></div>
|
2024-10-16 11:13:34 +07:00
|
|
|
<div class="yellow-segment"></div>
|
2024-09-27 15:45:24 +07:00
|
|
|
<div class="gray-segment"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2024-10-10 10:35:20 +07:00
|
|
|
<header class="row q-px-md q-py-sm items-center full-width">
|
2024-09-27 15:45:24 +07:00
|
|
|
<div style="flex: 1" class="row items-center">
|
2024-10-10 10:35:20 +07:00
|
|
|
<q-img src="/icons/favicon-512x512.png" width="3rem" />
|
2024-09-27 15:45:24 +07:00
|
|
|
<span class="column text-h6 text-bold q-ml-md">
|
|
|
|
|
{{ $t('quotation.title') }}
|
|
|
|
|
<span class="text-caption text-regular app-text-muted">
|
2024-10-03 11:14:12 +07:00
|
|
|
{{
|
|
|
|
|
$t('quotation.processOn', {
|
2024-10-11 11:28:06 +07:00
|
|
|
msg:
|
|
|
|
|
quotationFormState.mode === 'create'
|
|
|
|
|
? `${dateFormat(date, true)} ${dateFormat(date, true, true)}`
|
|
|
|
|
: `${dateFormat(quotationFull?.createdAt, true)} ${dateFormat(quotationFull?.createdAt, true, true)}`,
|
2024-10-03 11:14:12 +07:00
|
|
|
})
|
|
|
|
|
}}
|
2024-09-27 15:45:24 +07:00
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2024-10-03 11:14:12 +07:00
|
|
|
<FormAbout
|
|
|
|
|
class="col-4"
|
|
|
|
|
input-only
|
2024-10-15 17:16:36 +07:00
|
|
|
v-model:branch-id="quotationFormData.registeredBranchId"
|
2024-10-03 11:14:12 +07:00
|
|
|
v-model:customer-branch-id="quotationFormData.customerBranchId"
|
2024-10-10 13:31:46 +07:00
|
|
|
:readonly="readonly"
|
2024-10-11 11:54:22 +07:00
|
|
|
@add-customer=""
|
2024-06-19 15:43:58 +07:00
|
|
|
/>
|
2024-09-27 15:45:24 +07:00
|
|
|
</header>
|
2024-06-19 15:43:58 +07:00
|
|
|
|
2024-09-30 13:13:08 +07:00
|
|
|
<article
|
2024-10-10 09:48:36 +07:00
|
|
|
class="row col full-width q-pa-md"
|
2024-09-18 15:38:50 +07:00
|
|
|
style="flex-grow: 1; overflow-y: hidden"
|
|
|
|
|
:style="{
|
2024-10-10 10:35:20 +07:00
|
|
|
overflowY: $q.screen.gt.xs ? 'hidden' : 'auto',
|
2024-09-18 15:38:50 +07:00
|
|
|
}"
|
2024-06-19 15:43:58 +07:00
|
|
|
>
|
2024-09-27 15:45:24 +07:00
|
|
|
<section
|
2024-10-10 09:48:36 +07:00
|
|
|
class="col-sm col-12 row scroll"
|
|
|
|
|
:class="{
|
|
|
|
|
'full-height q-mr-md': $q.screen.gt.xs,
|
|
|
|
|
'q-mb-md': $q.screen.lt.md,
|
2024-09-18 15:38:50 +07:00
|
|
|
}"
|
2024-06-19 15:43:58 +07:00
|
|
|
>
|
2024-09-27 15:45:24 +07:00
|
|
|
<div class="col q-gutter-y-md">
|
|
|
|
|
<q-expansion-item
|
|
|
|
|
for="item-up"
|
|
|
|
|
id="item-up"
|
|
|
|
|
dense
|
|
|
|
|
class="overflow-hidden"
|
|
|
|
|
switch-toggle-side
|
|
|
|
|
default-opened
|
|
|
|
|
style="border-radius: var(--radius-2)"
|
|
|
|
|
expand-icon="mdi-chevron-down-circle"
|
|
|
|
|
header-class="surface-1"
|
|
|
|
|
>
|
|
|
|
|
<template v-slot:header>
|
2024-10-03 15:33:20 +07:00
|
|
|
<section class="row items-center full-width">
|
|
|
|
|
<div class="row items-center q-pr-md q-py-sm">
|
|
|
|
|
<span
|
|
|
|
|
class="text-weight-medium q-mr-md"
|
|
|
|
|
style="font-size: 18px"
|
|
|
|
|
>
|
|
|
|
|
{{ $t('quotation.employeeList') }}
|
|
|
|
|
</span>
|
2024-10-10 13:31:46 +07:00
|
|
|
<template v-if="!readonly">
|
|
|
|
|
<ToggleButton class="q-mr-sm" v-model="toggleWorker" />
|
|
|
|
|
{{
|
|
|
|
|
toggleWorker
|
|
|
|
|
? $t('general.specify')
|
|
|
|
|
: $t('general.noSpecify')
|
|
|
|
|
}}
|
|
|
|
|
</template>
|
2024-10-03 15:33:20 +07:00
|
|
|
</div>
|
|
|
|
|
<nav class="q-ml-auto">
|
|
|
|
|
<AddButton
|
2024-10-10 13:31:46 +07:00
|
|
|
v-if="!readonly"
|
2024-10-03 15:33:20 +07:00
|
|
|
icon-only
|
|
|
|
|
@click.stop="triggerSelectEmployeeDialog"
|
|
|
|
|
/>
|
|
|
|
|
</nav>
|
|
|
|
|
</section>
|
2024-09-27 15:45:24 +07:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div class="surface-1 q-pa-md full-width">
|
|
|
|
|
<WorkerItem
|
2024-10-03 18:10:40 +07:00
|
|
|
:employee-amount="selectedWorker.length"
|
2024-10-10 13:31:46 +07:00
|
|
|
:readonly="readonly"
|
2024-10-03 18:10:40 +07:00
|
|
|
fallback-img="/images/employee-avatar.png"
|
|
|
|
|
:rows="
|
|
|
|
|
selectedWorker.map((e: Employee) => ({
|
2024-10-17 17:39:20 +07:00
|
|
|
foreignRefNo: e.employeePassport
|
|
|
|
|
? e.employeePassport[0]?.number || '-'
|
|
|
|
|
: '-',
|
2024-10-03 18:10:40 +07:00
|
|
|
employeeName:
|
|
|
|
|
$i18n.locale === 'eng'
|
|
|
|
|
? `${e.firstNameEN} ${e.lastNameEN}`
|
|
|
|
|
: `${e.firstName} ${e.lastName}`,
|
|
|
|
|
birthDate: dateFormat(e.dateOfBirth),
|
|
|
|
|
gender: e.gender,
|
|
|
|
|
age: calculateAge(e.dateOfBirth),
|
|
|
|
|
nationality: optionStore.mapOption(e.nationality),
|
2024-10-17 17:39:20 +07:00
|
|
|
documentExpireDate: e.employeePassport
|
|
|
|
|
? dateFormat(e.employeePassport[0]?.expireDate) || '-'
|
|
|
|
|
: '-',
|
2024-10-03 18:10:40 +07:00
|
|
|
imgUrl: `${baseUrl}/customer/${e.id}/image/${e.selectedImage}`,
|
|
|
|
|
status: e.status,
|
|
|
|
|
}))
|
|
|
|
|
"
|
2024-10-07 10:20:29 +07:00
|
|
|
@delete="(i) => deleteItem(selectedWorker, i)"
|
2024-09-27 15:45:24 +07:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</q-expansion-item>
|
|
|
|
|
|
|
|
|
|
<q-expansion-item
|
|
|
|
|
for="item-up"
|
|
|
|
|
id="item-up"
|
|
|
|
|
dense
|
|
|
|
|
class="overflow-hidden"
|
|
|
|
|
switch-toggle-side
|
|
|
|
|
default-opened
|
|
|
|
|
style="border-radius: var(--radius-2)"
|
|
|
|
|
expand-icon="mdi-chevron-down-circle"
|
|
|
|
|
header-class="surface-1"
|
|
|
|
|
>
|
|
|
|
|
<template v-slot:header>
|
2024-10-03 15:33:20 +07:00
|
|
|
<section class="row items-center full-width">
|
|
|
|
|
<div class="row items-center q-pr-md q-py-sm">
|
|
|
|
|
<span class="text-weight-medium" style="font-size: 18px">
|
|
|
|
|
{{ $t('quotation.productList') }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<nav class="q-ml-auto">
|
|
|
|
|
<AddButton
|
2024-10-10 13:31:46 +07:00
|
|
|
v-if="!readonly"
|
2024-10-03 15:33:20 +07:00
|
|
|
icon-only
|
|
|
|
|
class="q-ml-auto"
|
|
|
|
|
@click.stop="triggerProductServiceDialog"
|
|
|
|
|
/>
|
|
|
|
|
</nav>
|
|
|
|
|
</section>
|
2024-09-27 15:45:24 +07:00
|
|
|
</template>
|
2024-10-07 12:55:39 +07:00
|
|
|
<div class="surface-1 q-pa-md full-width">
|
2024-10-08 16:38:15 +07:00
|
|
|
<span
|
|
|
|
|
v-if="productServiceList.length > 0"
|
|
|
|
|
class="text-weight-bold row items-center q-pb-md"
|
|
|
|
|
>
|
|
|
|
|
{{
|
|
|
|
|
productGroup.find((g) => g.id === selectedProductGroup)
|
|
|
|
|
?.name || '-'
|
|
|
|
|
}}
|
|
|
|
|
</span>
|
2024-10-03 17:17:15 +07:00
|
|
|
<ProductItem
|
2024-10-10 13:31:46 +07:00
|
|
|
:readonly="readonly"
|
2024-10-04 15:16:28 +07:00
|
|
|
:agent-price="agentPrice"
|
2024-10-17 15:50:26 +07:00
|
|
|
:employee-rows="
|
2024-10-09 18:09:01 +07:00
|
|
|
selectedWorker.map((e: Employee) => ({
|
|
|
|
|
foreignRefNo: '123456789',
|
|
|
|
|
employeeName:
|
|
|
|
|
$i18n.locale === 'eng'
|
|
|
|
|
? `${e.firstNameEN} ${e.lastNameEN}`
|
|
|
|
|
: `${e.firstName} ${e.lastName}`,
|
|
|
|
|
birthDate: dateFormat(e.dateOfBirth),
|
|
|
|
|
gender: e.gender,
|
|
|
|
|
age: calculateAge(e.dateOfBirth),
|
|
|
|
|
nationality: optionStore.mapOption(e.nationality),
|
|
|
|
|
documentExpireDate: '1234',
|
|
|
|
|
imgUrl: `${baseUrl}/customer/${e.id}/image/${e.selectedImage}`,
|
|
|
|
|
status: e.status,
|
|
|
|
|
}))
|
|
|
|
|
"
|
2024-10-03 17:17:15 +07:00
|
|
|
@delete="toggleDeleteProduct"
|
|
|
|
|
v-model:rows="productServiceList"
|
|
|
|
|
/>
|
2024-09-27 15:45:24 +07:00
|
|
|
</div>
|
|
|
|
|
</q-expansion-item>
|
|
|
|
|
|
|
|
|
|
<q-expansion-item
|
|
|
|
|
for="item-up"
|
|
|
|
|
id="item-up"
|
|
|
|
|
dense
|
|
|
|
|
class="overflow-hidden"
|
|
|
|
|
switch-toggle-side
|
|
|
|
|
default-opened
|
|
|
|
|
style="border-radius: var(--radius-2)"
|
|
|
|
|
expand-icon="mdi-chevron-down-circle"
|
|
|
|
|
header-class="surface-1"
|
|
|
|
|
>
|
|
|
|
|
<template v-slot:header>
|
|
|
|
|
<div class="row full-width items-center q-pr-md q-py-sm">
|
|
|
|
|
<span class="text-weight-medium" style="font-size: 18px">
|
2024-10-03 15:33:20 +07:00
|
|
|
{{ $t('general.remark') }}
|
2024-09-27 15:45:24 +07:00
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<div class="surface-1 q-pa-md full-width">
|
|
|
|
|
<q-editor
|
|
|
|
|
dense
|
2024-10-18 11:12:04 +07:00
|
|
|
:model-value="quotationFormData.remark || ''"
|
2024-09-27 15:45:24 +07:00
|
|
|
min-height="5rem"
|
|
|
|
|
class="full-width"
|
|
|
|
|
toolbar-bg="input-border"
|
|
|
|
|
style="cursor: auto; color: var(--foreground)"
|
|
|
|
|
:flat="!readonly"
|
|
|
|
|
:style="`width: ${$q.screen.gt.xs ? '100%' : '63vw'}`"
|
|
|
|
|
:toolbar="[['left', 'center', 'justify'], ['clip']]"
|
|
|
|
|
:toolbar-toggle-color="readonly ? 'disabled' : 'primary'"
|
|
|
|
|
:toolbar-color="
|
|
|
|
|
readonly ? 'disabled' : $q.dark.isActive ? 'white' : ''
|
|
|
|
|
"
|
|
|
|
|
:definitions="{
|
|
|
|
|
clip: {
|
|
|
|
|
icon: 'mdi-paperclip',
|
|
|
|
|
tip: 'Upload',
|
|
|
|
|
handler: () => console.log('upload'),
|
|
|
|
|
},
|
|
|
|
|
}"
|
2024-10-18 11:12:04 +07:00
|
|
|
@update:model-value="
|
|
|
|
|
(v) => {
|
|
|
|
|
quotationFormData.remark = v;
|
|
|
|
|
}
|
|
|
|
|
"
|
2024-09-27 15:45:24 +07:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</q-expansion-item>
|
2024-09-18 15:38:50 +07:00
|
|
|
</div>
|
2024-09-27 15:45:24 +07:00
|
|
|
</section>
|
|
|
|
|
|
2024-06-19 15:43:58 +07:00
|
|
|
<div
|
2024-10-10 09:48:36 +07:00
|
|
|
class="col-12 col-md-3 col-sm-4 scroll"
|
|
|
|
|
:class="{ 'full-height': $q.screen.gt.xs }"
|
2024-06-19 15:43:58 +07:00
|
|
|
>
|
2024-09-18 15:38:50 +07:00
|
|
|
<QuotationFormInfo
|
2024-10-11 11:54:22 +07:00
|
|
|
:quotation-no="(quotationFull && quotationFull.code) || ''"
|
2024-10-04 15:16:28 +07:00
|
|
|
v-model:urgent="quotationFormData.urgent"
|
2024-10-16 13:10:58 +07:00
|
|
|
:actor="quotationFormState.createdBy?.($i18n.locale) || ''"
|
2024-10-04 15:16:28 +07:00
|
|
|
v-model:work-name="quotationFormData.workName"
|
|
|
|
|
v-model:contactor="quotationFormData.contactName"
|
|
|
|
|
v-model:telephone="quotationFormData.contactTel"
|
|
|
|
|
v-model:document-receive-point="
|
|
|
|
|
quotationFormData.documentReceivePoint
|
|
|
|
|
"
|
|
|
|
|
v-model:due-date="quotationFormData.dueDate"
|
|
|
|
|
v-model:pay-type="quotationFormData.payCondition"
|
2024-09-18 15:38:50 +07:00
|
|
|
v-model:pay-bank="payBank"
|
2024-10-04 15:16:28 +07:00
|
|
|
v-model:pay-split-count="quotationFormData.paySplitCount"
|
|
|
|
|
v-model:pay-split="quotationFormData.paySplit"
|
2024-09-18 15:38:50 +07:00
|
|
|
:readonly
|
2024-10-10 15:05:15 +07:00
|
|
|
v-model:final-discount="quotationFormData.discount"
|
2024-10-16 14:24:54 +07:00
|
|
|
v-model:pay-bill-date="quotationFormData.payBillDate"
|
2024-10-04 15:16:28 +07:00
|
|
|
v-model:summary-price="summaryPrice"
|
2024-09-18 15:38:50 +07:00
|
|
|
/>
|
2024-06-19 15:43:58 +07:00
|
|
|
</div>
|
2024-09-30 13:13:08 +07:00
|
|
|
</article>
|
2024-06-19 15:43:58 +07:00
|
|
|
|
2024-10-09 09:56:15 +07:00
|
|
|
<footer class="surface-1 q-pa-md full-width">
|
2024-09-27 15:45:24 +07:00
|
|
|
<div class="row full-width justify-between">
|
2024-10-18 09:37:34 +07:00
|
|
|
<MainButton
|
|
|
|
|
outlined
|
|
|
|
|
icon="mdi-play-box-outline"
|
|
|
|
|
color="207 96% 32%"
|
2024-10-18 09:27:56 +07:00
|
|
|
@click="storeDataLocal"
|
|
|
|
|
>
|
2024-09-27 15:45:24 +07:00
|
|
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
2024-10-18 09:37:34 +07:00
|
|
|
</MainButton>
|
2024-10-18 09:10:33 +07:00
|
|
|
<div class="row" style="gap: var(--size-2)">
|
|
|
|
|
<CancelButton solid @click="closeTab()" />
|
|
|
|
|
<SaveButton
|
|
|
|
|
v-if="
|
|
|
|
|
quotationFormState.mode === 'create' ||
|
|
|
|
|
quotationFormState.mode === 'edit'
|
|
|
|
|
"
|
|
|
|
|
@click="
|
|
|
|
|
() => {
|
|
|
|
|
convertDataToFormSubmit();
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
solid
|
|
|
|
|
/>
|
|
|
|
|
<EditButton
|
|
|
|
|
v-else
|
|
|
|
|
class="no-print"
|
|
|
|
|
@click="quotationFormState.mode = 'edit'"
|
|
|
|
|
solid
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2024-07-26 10:55:30 +07:00
|
|
|
</div>
|
2024-09-27 15:45:24 +07:00
|
|
|
</footer>
|
2024-10-03 11:14:12 +07:00
|
|
|
|
|
|
|
|
<!-- SEC: Dialog -->
|
|
|
|
|
<!-- add employee quotation -->
|
|
|
|
|
<DialogForm
|
|
|
|
|
:title="$t('general.select', { msg: $t('quotation.employeeList') })"
|
|
|
|
|
v-model:modal="pageState.employeeModal"
|
|
|
|
|
:submit-label="$t('general.select', { msg: $t('quotation.employee') })"
|
|
|
|
|
submit-icon="mdi-check"
|
|
|
|
|
height="75vh"
|
2024-10-03 18:10:40 +07:00
|
|
|
:submit="() => convertEmployeeToTable()"
|
|
|
|
|
:close="
|
|
|
|
|
() => {
|
|
|
|
|
(preSelectedWorker = []), (pageState.employeeModal = false);
|
|
|
|
|
}
|
|
|
|
|
"
|
2024-10-03 11:14:12 +07:00
|
|
|
>
|
|
|
|
|
<section class="col row scroll">
|
|
|
|
|
<SelectZone
|
2024-10-03 18:10:40 +07:00
|
|
|
ref="refSelectZoneEmployee"
|
|
|
|
|
v-model:selected-item="preSelectedWorker"
|
2024-10-15 17:16:36 +07:00
|
|
|
@search="
|
|
|
|
|
(v) => {
|
|
|
|
|
searchEmployee(v);
|
|
|
|
|
}
|
|
|
|
|
"
|
2024-10-03 18:10:40 +07:00
|
|
|
:items="workerList"
|
2024-10-07 12:55:39 +07:00
|
|
|
:new-items="newWorkerList"
|
2024-10-03 11:14:12 +07:00
|
|
|
>
|
|
|
|
|
<template #top>
|
|
|
|
|
<AddButton
|
|
|
|
|
icon-only
|
|
|
|
|
@click="
|
|
|
|
|
() => {
|
|
|
|
|
triggerCreateEmployee();
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
<template #data="{ item }">
|
|
|
|
|
<PersonCard
|
|
|
|
|
noAction
|
|
|
|
|
prefixId="asda"
|
|
|
|
|
class="full-width"
|
|
|
|
|
:data="{
|
2024-10-03 18:10:40 +07:00
|
|
|
name:
|
|
|
|
|
$i18n.locale === 'eng'
|
|
|
|
|
? `${item.firstNameEN} ${item.lastNameEN}`
|
|
|
|
|
: `${item.firstName} ${item.lastName}`,
|
2024-10-03 11:14:12 +07:00
|
|
|
code: item.code,
|
|
|
|
|
female: item.gender === 'female',
|
|
|
|
|
male: item.gender === 'male',
|
2024-10-03 18:10:40 +07:00
|
|
|
img: `${baseUrl}/customer/${item.id}/image/${item.selectedImage}`,
|
|
|
|
|
fallbackImg: '/images/employee-avatar.png',
|
2024-10-03 11:14:12 +07:00
|
|
|
detail: [
|
2024-10-03 18:10:40 +07:00
|
|
|
{
|
|
|
|
|
icon: 'mdi-passport',
|
|
|
|
|
value: optionStore.mapOption(item.nationality),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: 'mdi-clock-outline',
|
|
|
|
|
value: calculateAge(item.dateOfBirth),
|
|
|
|
|
},
|
2024-10-03 11:14:12 +07:00
|
|
|
],
|
|
|
|
|
}"
|
|
|
|
|
></PersonCard>
|
|
|
|
|
</template>
|
2024-10-07 12:55:39 +07:00
|
|
|
|
|
|
|
|
<template #newData="{ item }">
|
|
|
|
|
<PersonCard
|
|
|
|
|
noAction
|
|
|
|
|
prefixId="asda"
|
|
|
|
|
class="full-width"
|
|
|
|
|
:data="{
|
|
|
|
|
name:
|
|
|
|
|
$i18n.locale === 'eng'
|
|
|
|
|
? `${item.firstNameEN} ${item.lastNameEN}`
|
|
|
|
|
: `${item.firstName} ${item.lastName}`,
|
|
|
|
|
code: item.code,
|
|
|
|
|
female: item.gender === 'female',
|
|
|
|
|
male: item.gender === 'male',
|
|
|
|
|
img: `${baseUrl}/customer/${item.id}/image/${item.selectedImage}`,
|
|
|
|
|
fallbackImg: '/images/employee-avatar.png',
|
|
|
|
|
detail: [
|
|
|
|
|
{
|
|
|
|
|
icon: 'mdi-passport',
|
|
|
|
|
value: optionStore.mapOption(item.nationality),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: 'mdi-clock-outline',
|
|
|
|
|
value: calculateAge(item.dateOfBirth),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}"
|
|
|
|
|
></PersonCard>
|
|
|
|
|
</template>
|
2024-10-03 11:14:12 +07:00
|
|
|
</SelectZone>
|
|
|
|
|
</section>
|
|
|
|
|
</DialogForm>
|
|
|
|
|
|
|
|
|
|
<!-- add product service -->
|
|
|
|
|
<ProductServiceForm
|
|
|
|
|
v-model="pageState.productServiceModal"
|
2024-10-15 09:42:16 +07:00
|
|
|
v-model:nodes="productServiceNodes"
|
2024-10-03 11:14:12 +07:00
|
|
|
v-model:product-group="productGroup"
|
|
|
|
|
v-model:product-list="productList"
|
|
|
|
|
v-model:service-list="serviceList"
|
2024-10-08 16:38:15 +07:00
|
|
|
v-model:selected-product-group="selectedProductGroup"
|
2024-10-07 15:14:33 +07:00
|
|
|
:agent-price="agentPrice"
|
2024-10-03 17:17:15 +07:00
|
|
|
@submit="convertToTable"
|
2024-10-03 11:14:12 +07:00
|
|
|
@select-group="
|
|
|
|
|
async (id) => {
|
|
|
|
|
await getAllService(id);
|
|
|
|
|
await getAllProduct(id);
|
|
|
|
|
}
|
2024-10-15 17:16:36 +07:00
|
|
|
"
|
|
|
|
|
@search="
|
|
|
|
|
(id, text, mode) => {
|
|
|
|
|
if (mode === 'service') {
|
2024-10-15 18:01:11 +07:00
|
|
|
getAllService(id, { force: true, query: text, pageSize: 50 });
|
2024-10-15 17:16:36 +07:00
|
|
|
}
|
|
|
|
|
if (mode === 'product') {
|
2024-10-15 18:01:11 +07:00
|
|
|
getAllProduct(id, { force: true, query: text, pageSize: 50 });
|
2024-10-15 17:16:36 +07:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-03 11:14:12 +07:00
|
|
|
"
|
|
|
|
|
></ProductServiceForm>
|
2024-09-27 15:45:24 +07:00
|
|
|
</div>
|
2024-10-04 17:01:24 +07:00
|
|
|
|
|
|
|
|
<!-- NOTE: START - Employee Add Form -->
|
|
|
|
|
|
|
|
|
|
<DialogForm
|
|
|
|
|
hide-footer
|
|
|
|
|
ref="formDialogRef"
|
|
|
|
|
v-model:modal="employeeFormState.dialogModal"
|
|
|
|
|
:title="$t('form.title.create', { name: $t('customer.employee') })"
|
|
|
|
|
:submit="
|
|
|
|
|
() => {
|
2024-10-08 13:45:06 +07:00
|
|
|
quotationForm.injectNewEmployee({ data: formDataEmployee }, () =>
|
2024-10-04 17:57:33 +07:00
|
|
|
setDefaultFormEmployee(),
|
|
|
|
|
);
|
2024-10-04 17:01:24 +07:00
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
:close="
|
|
|
|
|
() => {
|
|
|
|
|
employeeFormState.dialogModal = false;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
:class="{
|
|
|
|
|
'q-mx-lg q-my-md': $q.screen.gt.sm,
|
|
|
|
|
'q-mx-md q-my-sm': !$q.screen.gt.sm,
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<ProfileBanner
|
|
|
|
|
active
|
|
|
|
|
useToggle
|
|
|
|
|
color="white"
|
|
|
|
|
icon="mdi-account-plus-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"
|
|
|
|
|
fallbackCover="/images/employee-banner.png"
|
|
|
|
|
:img="employeeFormState.profileUrl || `/images/employee-avatar.png`"
|
|
|
|
|
:toggleTitle="$t('status.title')"
|
|
|
|
|
hideFade
|
|
|
|
|
@view="
|
|
|
|
|
() => {
|
|
|
|
|
employeeFormState.imageDialog = true;
|
|
|
|
|
employeeFormState.isImageEdit = false;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
@edit="
|
|
|
|
|
employeeFormState.imageDialog = employeeFormState.isImageEdit = true
|
|
|
|
|
"
|
|
|
|
|
@update:toggle-status="
|
|
|
|
|
() => {
|
|
|
|
|
currentFromDataEmployee.status =
|
|
|
|
|
currentFromDataEmployee.status === 'CREATED'
|
|
|
|
|
? 'INACTIVE'
|
|
|
|
|
: 'CREATED';
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
class="col"
|
|
|
|
|
:class="{
|
|
|
|
|
'q-px-lg q-pb-lg': $q.screen.gt.sm,
|
|
|
|
|
'q-px-md q-pb-sm': !$q.screen.gt.sm,
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style="overflow-y: auto"
|
|
|
|
|
class="row full-width full-height surface-1 rounded bordered relative-position"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
:class="{
|
|
|
|
|
'q-py-md q-px-lg': $q.screen.gt.sm,
|
|
|
|
|
'q-py-sm q-px-lg': !$q.screen.gt.sm,
|
|
|
|
|
}"
|
|
|
|
|
style="position: absolute; z-index: 99999; top: 0; right: 0"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
v-if="currentFromDataEmployee.status !== 'INACTIVE'"
|
|
|
|
|
class="surface-1 row rounded"
|
|
|
|
|
>
|
|
|
|
|
<UndoButton
|
|
|
|
|
v-if="
|
|
|
|
|
employeeFormState.isEmployeeEdit &&
|
|
|
|
|
employeeFormState.dialogType !== 'create'
|
|
|
|
|
"
|
|
|
|
|
id="btn-info-basic-undo"
|
|
|
|
|
icon-only
|
|
|
|
|
@click="
|
|
|
|
|
() => {
|
|
|
|
|
employeeFormStore.resetFormDataEmployee();
|
|
|
|
|
employeeFormState.isEmployeeEdit = false;
|
|
|
|
|
employeeFormState.dialogType = 'info';
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
type="button"
|
|
|
|
|
/>
|
|
|
|
|
<SaveButton
|
|
|
|
|
v-if="employeeFormState.isEmployeeEdit"
|
|
|
|
|
id="btn-info-basic-save"
|
|
|
|
|
icon-only
|
|
|
|
|
type="submit"
|
|
|
|
|
/>
|
|
|
|
|
<EditButton
|
|
|
|
|
v-if="!employeeFormState.isEmployeeEdit"
|
|
|
|
|
id="btn-info-basic-edit"
|
|
|
|
|
icon-only
|
|
|
|
|
@click="
|
|
|
|
|
() => {
|
|
|
|
|
employeeFormState.isEmployeeEdit = true;
|
|
|
|
|
employeeFormState.dialogType = 'edit';
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
type="button"
|
|
|
|
|
/>
|
|
|
|
|
<DeleteButton
|
|
|
|
|
v-if="!employeeFormState.isEmployeeEdit"
|
|
|
|
|
id="btn-info-basic-delete"
|
|
|
|
|
icon-only
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
2024-10-07 10:18:35 +07:00
|
|
|
class="col-12 full-height q-col-gutter-sm q-py-md q-pl-md q-pr-sm"
|
2024-10-04 17:01:24 +07:00
|
|
|
:class="{
|
|
|
|
|
'q-py-md q-pr-md ': $q.screen.gt.sm,
|
|
|
|
|
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
|
|
|
|
}"
|
|
|
|
|
id="branch-form"
|
|
|
|
|
style="overflow-y: auto"
|
|
|
|
|
>
|
|
|
|
|
<FormReferDocument
|
|
|
|
|
title="form.field.basicInformation"
|
|
|
|
|
prefixId="dialog"
|
|
|
|
|
dense
|
2024-10-08 13:45:06 +07:00
|
|
|
v-model:passport-no="formDataEmployee.passportNo"
|
2024-10-04 17:01:24 +07:00
|
|
|
v-model:document-expire-date="formDataEmployee.documentExpireDate"
|
2024-10-07 10:18:35 +07:00
|
|
|
class="q-mb-md"
|
2024-10-04 17:01:24 +07:00
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<FormPerson
|
|
|
|
|
id="form-personal"
|
|
|
|
|
prefix-id="form-employee"
|
|
|
|
|
dense
|
|
|
|
|
outlined
|
|
|
|
|
employee
|
|
|
|
|
separator
|
|
|
|
|
hideNameEn
|
|
|
|
|
title="personnel.form.personalInformation"
|
2024-10-07 10:18:35 +07:00
|
|
|
class="q-mb-md"
|
2024-10-04 17:01:24 +07:00
|
|
|
v-model:prefix-name="formDataEmployee.namePrefix"
|
|
|
|
|
v-model:first-name="formDataEmployee.firstName"
|
|
|
|
|
v-model:mid-name="formDataEmployee.middleName"
|
|
|
|
|
v-model:last-name="formDataEmployee.lastName"
|
|
|
|
|
v-model:birth-date="formDataEmployee.dateOfBirth"
|
|
|
|
|
v-model:gender="formDataEmployee.gender"
|
|
|
|
|
v-model:nationality="formDataEmployee.nationality"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<UploadFileGroup
|
2024-10-07 10:18:35 +07:00
|
|
|
show-title
|
2024-10-08 13:45:06 +07:00
|
|
|
v-model="formDataEmployee.attachment"
|
2024-10-04 17:01:24 +07:00
|
|
|
hide-action
|
2024-10-09 15:23:11 +07:00
|
|
|
@submit="
|
|
|
|
|
async (group, allMeta) => {
|
|
|
|
|
if (allMeta === undefined) return;
|
|
|
|
|
|
|
|
|
|
if (group === 'passport') {
|
|
|
|
|
const fullName = allMeta['full_name'].split(' ');
|
2024-10-10 17:22:52 +07:00
|
|
|
let tempValue: {
|
|
|
|
|
oldData: { filName: string; value: string }[];
|
|
|
|
|
newData: { filName: string; value: string }[];
|
|
|
|
|
} = { oldData: [], newData: [] };
|
|
|
|
|
|
2024-10-15 15:00:25 +07:00
|
|
|
if (
|
|
|
|
|
formDataEmployee.gender !== '' &&
|
|
|
|
|
formDataEmployee.gender !== allMeta['sex']
|
|
|
|
|
) {
|
|
|
|
|
tempValue.oldData.push({
|
|
|
|
|
filName: $t('form.gender'),
|
|
|
|
|
value: $t(`general.${formDataEmployee.gender}`),
|
|
|
|
|
});
|
|
|
|
|
tempValue.newData.push({
|
|
|
|
|
filName: $t('form.gender'),
|
|
|
|
|
value: $t(`general.${allMeta['sex']}`),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 17:22:52 +07:00
|
|
|
if (formDataEmployee.firstName !== '') {
|
|
|
|
|
tempValue.oldData.push({
|
|
|
|
|
filName: $t('personnel.form.firstName'),
|
|
|
|
|
value: formDataEmployee.firstName,
|
|
|
|
|
});
|
|
|
|
|
tempValue.newData.push({
|
|
|
|
|
filName: $t('personnel.form.firstName'),
|
|
|
|
|
value: fullName[0],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (formDataEmployee.lastName !== '') {
|
|
|
|
|
tempValue.oldData.push({
|
|
|
|
|
filName: $t('personnel.form.lastName'),
|
|
|
|
|
value: formDataEmployee.lastName,
|
|
|
|
|
});
|
|
|
|
|
tempValue.newData.push({
|
|
|
|
|
filName: $t('personnel.form.lastName'),
|
|
|
|
|
value: fullName[1],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (formDataEmployee.passportNo !== '') {
|
|
|
|
|
tempValue.oldData.push({
|
|
|
|
|
filName: $t('customerEmployee.form.passportNo'),
|
|
|
|
|
value: formDataEmployee.passportNo || '',
|
|
|
|
|
});
|
|
|
|
|
tempValue.newData.push({
|
|
|
|
|
filName: $t('customerEmployee.form.passportNo'),
|
|
|
|
|
value: allMeta['doc_number'],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 15:00:25 +07:00
|
|
|
if (formDataEmployee.nationality !== '') {
|
|
|
|
|
tempValue.oldData.push({
|
|
|
|
|
filName: $t('general.nationality'),
|
|
|
|
|
value: formDataEmployee.nationality || '',
|
|
|
|
|
});
|
|
|
|
|
tempValue.newData.push({
|
|
|
|
|
filName: $t('general.nationality'),
|
|
|
|
|
value: allMeta['nationality'],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 17:22:52 +07:00
|
|
|
dialogCheckData({
|
|
|
|
|
action: async () => {
|
2024-10-15 15:00:25 +07:00
|
|
|
formDataEmployee.gender = allMeta['sex'];
|
2024-10-09 15:23:11 +07:00
|
|
|
formDataEmployee.firstName = fullName[0];
|
|
|
|
|
formDataEmployee.lastName = fullName[1];
|
|
|
|
|
formDataEmployee.passportNo = allMeta['doc_number'];
|
2024-10-15 15:00:25 +07:00
|
|
|
formDataEmployee.nationality = allMeta['nationality'];
|
2024-10-09 15:23:11 +07:00
|
|
|
},
|
2024-10-10 17:22:52 +07:00
|
|
|
checkData: () => {
|
|
|
|
|
return tempValue;
|
2024-10-09 15:23:11 +07:00
|
|
|
},
|
2024-10-10 17:40:54 +07:00
|
|
|
cancel: () => {
|
2024-10-15 15:00:25 +07:00
|
|
|
if (!formDataEmployee.firstName) {
|
|
|
|
|
formDataEmployee.gender = allMeta['gender'];
|
|
|
|
|
}
|
|
|
|
|
if (!formDataEmployee.firstName) {
|
|
|
|
|
formDataEmployee.firstName = fullName[0];
|
|
|
|
|
}
|
2024-10-10 17:40:54 +07:00
|
|
|
if (!formDataEmployee.firstName) {
|
|
|
|
|
formDataEmployee.firstName = fullName[0];
|
|
|
|
|
}
|
|
|
|
|
if (!formDataEmployee.lastName) {
|
|
|
|
|
formDataEmployee.lastName = fullName[1];
|
|
|
|
|
}
|
|
|
|
|
if (!formDataEmployee.passportNo) {
|
|
|
|
|
formDataEmployee.passportNo = allMeta['doc_number'];
|
|
|
|
|
}
|
2024-10-15 15:00:25 +07:00
|
|
|
if (!formDataEmployee.nationality) {
|
|
|
|
|
formDataEmployee.nationality = allMeta['nationality'];
|
|
|
|
|
}
|
2024-10-10 17:40:54 +07:00
|
|
|
},
|
2024-10-10 17:22:52 +07:00
|
|
|
});
|
2024-10-09 15:23:11 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"
|
2024-10-04 17:01:24 +07:00
|
|
|
:menu="uploadFileListEmployee"
|
|
|
|
|
:columns="columnsAttachment"
|
|
|
|
|
:ocr="
|
|
|
|
|
async (group, file) => {
|
2024-10-08 13:45:06 +07:00
|
|
|
if (group === 'passport') {
|
|
|
|
|
mrz = await runOcr(file, parseResultMRZ);
|
|
|
|
|
|
|
|
|
|
if (mrz !== null) {
|
2024-10-10 09:24:02 +07:00
|
|
|
const mapMrz = Object.entries(mrz.result || {}).map(
|
2024-10-08 13:45:06 +07:00
|
|
|
([key, value]) => ({
|
|
|
|
|
name: key,
|
|
|
|
|
value: value,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
const tempValue = {
|
|
|
|
|
status: true,
|
|
|
|
|
group,
|
|
|
|
|
meta: mapMrz,
|
|
|
|
|
};
|
2024-10-04 17:01:24 +07:00
|
|
|
|
2024-10-08 13:45:06 +07:00
|
|
|
return tempValue;
|
2024-10-04 17:01:24 +07:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-10-08 13:45:06 +07:00
|
|
|
const res = await ocrStore.sendOcr({
|
|
|
|
|
file: file,
|
|
|
|
|
category: group,
|
2024-10-04 17:01:24 +07:00
|
|
|
});
|
2024-10-08 13:45:06 +07:00
|
|
|
|
2024-10-04 17:01:24 +07:00
|
|
|
if (res) {
|
2024-10-08 13:45:06 +07:00
|
|
|
const tempValue = {
|
|
|
|
|
status: true,
|
|
|
|
|
group,
|
|
|
|
|
meta: res.fields,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return tempValue;
|
2024-10-04 17:01:24 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-08 13:45:06 +07:00
|
|
|
return { status: true, group, meta: [] };
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
:delete-item="
|
|
|
|
|
async () => {
|
|
|
|
|
return true;
|
2024-10-04 17:01:24 +07:00
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
:get-file-list="
|
|
|
|
|
async (group: 'passport' | 'visa') => {
|
2024-10-08 13:45:06 +07:00
|
|
|
if (formDataEmployee.attachment !== undefined) {
|
|
|
|
|
const resMeta = formDataEmployee.attachment.filter(
|
|
|
|
|
(v) => v.group === group,
|
|
|
|
|
);
|
2024-10-04 17:01:24 +07:00
|
|
|
|
|
|
|
|
const tempValue = resMeta.map(async (i: any) => {
|
|
|
|
|
return {
|
2024-10-08 13:45:06 +07:00
|
|
|
_meta: { ...i._meta },
|
2024-10-04 17:01:24 +07:00
|
|
|
name: i.id || '',
|
|
|
|
|
group: group,
|
2024-10-08 13:45:06 +07:00
|
|
|
url: i.url,
|
2024-10-04 17:01:24 +07:00
|
|
|
file: undefined,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return await waitAll(tempValue);
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
>
|
|
|
|
|
<template #form="{ mode, meta, isEdit }">
|
|
|
|
|
<FormEmployeePassport
|
|
|
|
|
v-if="mode === 'passport' && meta"
|
|
|
|
|
prefix-id="drawer-info-employee"
|
|
|
|
|
id="form-passport"
|
|
|
|
|
dense
|
|
|
|
|
outlined
|
|
|
|
|
separator
|
|
|
|
|
ocr
|
|
|
|
|
:title="$t('customerEmployee.form.group.passport')"
|
|
|
|
|
:readonly="!isEdit"
|
|
|
|
|
v-model:passport-type="meta.type"
|
|
|
|
|
v-model:passport-number="meta.number"
|
2024-10-15 15:00:25 +07:00
|
|
|
v-model:gender="meta.gender"
|
|
|
|
|
v-model:first-name="meta.firstName"
|
|
|
|
|
v-model:last-name="meta.lastName"
|
2024-10-04 17:01:24 +07:00
|
|
|
v-model:passport-issue-date="meta.issueDate"
|
|
|
|
|
v-model:passport-expiry-date="meta.expireDate"
|
|
|
|
|
v-model:passport-issuing-place="meta.issuePlace"
|
|
|
|
|
v-model:passport-issuing-country="meta.issueCountry"
|
|
|
|
|
/>
|
|
|
|
|
<FormEmployeeVisa
|
|
|
|
|
v-if="mode === 'visa' && meta"
|
|
|
|
|
prefix-id="drawer-info-employee"
|
|
|
|
|
id="form-visa"
|
|
|
|
|
ocr
|
|
|
|
|
dense
|
|
|
|
|
outlined
|
|
|
|
|
title="customerEmployee.form.group.visa"
|
|
|
|
|
:readonly="!isEdit"
|
|
|
|
|
v-model:visa-type="meta.type"
|
|
|
|
|
v-model:visa-number="meta.number"
|
|
|
|
|
v-model:visa-issue-date="meta.issueDate"
|
|
|
|
|
v-model:visa-expiry-date="meta.expireDate"
|
|
|
|
|
v-model:visa-issuing-place="meta.issuePlace"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<noticeJobEmployment v-if="mode === 'noticeJobEmployment'" />
|
|
|
|
|
</template>
|
|
|
|
|
</UploadFileGroup>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</DialogForm>
|
|
|
|
|
|
|
|
|
|
<!-- NOTE: END - Employee Add Form -->
|
2024-06-19 15:43:58 +07:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.worker-list > :deep(*:not(:last-child)) {
|
|
|
|
|
margin-bottom: var(--size-2);
|
|
|
|
|
}
|
2024-09-18 15:38:50 +07:00
|
|
|
|
|
|
|
|
.icon-wrapper {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
aspect-ratio: 1/1;
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
padding: var(--size-1);
|
|
|
|
|
border-radius: var(--radius-2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bg-color-orange {
|
|
|
|
|
--_color: var(--yellow-7-hsl);
|
|
|
|
|
color: white;
|
|
|
|
|
background: hsla(var(--_color));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .bg-color-orange {
|
|
|
|
|
--_color: var(--orange-6-hsl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bg-color-orange-light {
|
|
|
|
|
--_color: var(--yellow-7-hsl);
|
|
|
|
|
background: hsla(var(--_color) / 0.2);
|
|
|
|
|
}
|
|
|
|
|
.dark .bg-color-orange {
|
|
|
|
|
--_color: var(--orange-6-hsl / 0.2);
|
|
|
|
|
}
|
2024-09-27 15:45:24 +07:00
|
|
|
|
|
|
|
|
.color-bar {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 1vh;
|
|
|
|
|
background: linear-gradient(
|
|
|
|
|
90deg,
|
|
|
|
|
rgba(245, 159, 0, 1) 0%,
|
|
|
|
|
rgba(255, 255, 255, 1) 77%,
|
|
|
|
|
rgba(204, 204, 204, 1) 100%
|
|
|
|
|
);
|
|
|
|
|
display: flex;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 11:13:34 +07:00
|
|
|
.orange-segment {
|
2024-09-27 15:45:24 +07:00
|
|
|
background-color: var(--yellow-7);
|
|
|
|
|
flex-grow: 4;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 11:13:34 +07:00
|
|
|
.yellow-segment {
|
2024-09-27 15:45:24 +07:00
|
|
|
background-color: hsla(var(--yellow-7-hsl) / 0.2);
|
|
|
|
|
flex-grow: 0.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.gray-segment {
|
|
|
|
|
background-color: #ccc;
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.orange-segment,
|
2024-10-16 11:13:34 +07:00
|
|
|
.yellow-segment,
|
2024-09-27 15:45:24 +07:00
|
|
|
.gray-segment {
|
|
|
|
|
transform: skewX(-60deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
|
2024-10-11 10:01:29 +07:00
|
|
|
color: hsl(var(--text-mute));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(
|
|
|
|
|
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
|
|
|
|
|
) {
|
2024-09-27 15:45:24 +07:00
|
|
|
color: var(--brand-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.q-editor__toolbar-group):nth-child(2) {
|
|
|
|
|
margin-left: auto !important;
|
|
|
|
|
}
|
2024-09-30 16:36:18 +07:00
|
|
|
|
|
|
|
|
:deep(.q-editor__toolbar.row.no-wrap.scroll-x) {
|
|
|
|
|
background-color: var(--surface-2) !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.q-editor__toolbar) {
|
|
|
|
|
border-color: var(--surface-3) !important;
|
|
|
|
|
}
|
2024-10-03 15:33:20 +07:00
|
|
|
|
|
|
|
|
: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
|
|
|
|
|
) {
|
|
|
|
|
visibility: hidden;
|
|
|
|
|
}
|
2024-06-19 15:43:58 +07:00
|
|
|
</style>
|