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/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/src/components/02_personnel-management/FormPerson.vue b/src/components/02_personnel-management/FormPerson.vue index 1aee5b95..cb0463af 100644 --- a/src/components/02_personnel-management/FormPerson.vue +++ b/src/components/02_personnel-management/FormPerson.vue @@ -293,15 +293,11 @@ watch( :readonly="readonly" :label="$t('form.birthDate')" :disabled-dates="disabledAfterToday" - :rules=" - employee - ? [] - : [ - (val: string) => - !!val || - $t('form.error.selectField', { field: $t('form.birthDate') }), - ] - " + :rules="[ + (val: string) => + !!val || + $t('form.error.selectField', { field: $t('form.birthDate') }), + ]" /> +import { storeToRefs } from 'pinia'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import FormPerson from '../02_personnel-management/FormPerson.vue'; +import { ProfileBanner, DialogForm, SideMenu } from 'src/components'; +import { + AddButton, + EditButton, + DeleteButton, + SaveButton, + UndoButton, +} from 'components/button'; +import AddressForm from '../form/AddressForm.vue'; +import NoticeJobEmployment from '../upload-file/NoticeJobEmployment.vue'; +import UploadFileGroup from '../upload-file/UploadFileGroup.vue'; +import BasicInformation from './employee/BasicInformation.vue'; +import FormEmployeeHealthCheck from './FormEmployeeHealthCheck.vue'; +import FormEmployeeOther from './FormEmployeeOther.vue'; +import FormEmployeePassport from './FormEmployeePassport.vue'; +import FormEmployeeVisa from './FormEmployeeVisa.vue'; +import FormEmployeeWorkHistory from './FormEmployeeWorkHistory.vue'; +import ExpirationDate from 'components/03_customer-management/ExpirationDate.vue'; +import ImageUploadDialog from '../ImageUploadDialog.vue'; + +import { + uploadFileListEmployee, + columnsAttachment, +} from 'src/pages/03_customer-management/constant'; +import { + baseUrl, + setPrefixName, + waitAll, + notify, + dialogCheckData, +} from 'src/stores/utils'; +import { + useCustomerForm, + useEmployeeForm, +} from 'src/pages/03_customer-management/form'; +import useFlowStore from 'src/stores/flow'; +import useEmployeeStore from 'src/stores/employee'; +import useOcrStore from 'stores/ocr'; +import { dateFormat } from 'src/utils/datetime'; +import { runOcr, parseResultMRZ } from 'src/utils/ocr'; +import useOptionStore from 'src/stores/options'; + +const route = useRoute(); +const ocrStore = useOcrStore(); +const flowStore = useFlowStore(); +const { t, locale } = useI18n(); +const optionStore = useOptionStore(); +const employeeFormStore = useEmployeeForm(); +const employeeStore = useEmployeeStore(); +const customerFormStore = useCustomerForm(); +const { state: customerFormState, currentFormData: customerFormData } = + storeToRefs(customerFormStore); +const { employeeFormUndo, employeeConfirmUnsave, deleteEmployeeById } = + employeeFormStore; +const { + state: employeeFormState, + currentFromDataEmployee, + onCreateImageList, + statusEmployeeCreate, + refreshImageState, +} = storeToRefs(employeeFormStore); + +const props = defineProps<{ + currentTab: string; + fetchListEmployee: (opt?: { + fetchStats?: boolean; + page?: number; + pageSize?: number; + customerId?: string; + mobileFetch?: boolean; + }) => Promise; + fetchImageList: ( + id: string, + selectedName: string, + type: 'customer' | 'employee', + ) => Promise; +}>(); + +const mrz = defineModel>>('mrz'); + + 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 @@ + + + diff --git a/src/components/03_customer-management/FormEmployeePassport.vue b/src/components/03_customer-management/FormEmployeePassport.vue index 040c9676..06373538 100644 --- a/src/components/03_customer-management/FormEmployeePassport.vue +++ b/src/components/03_customer-management/FormEmployeePassport.vue @@ -20,8 +20,8 @@ const issuePlace = defineModel('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'); diff --git a/src/components/03_customer-management/FormEmployeeVisa.vue b/src/components/03_customer-management/FormEmployeeVisa.vue index 8b912492..7c9085c4 100644 --- a/src/components/03_customer-management/FormEmployeeVisa.vue +++ b/src/components/03_customer-management/FormEmployeeVisa.vue @@ -28,12 +28,12 @@ const arrivalAt = defineModel('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('type'); -const expireDate = defineModel('expireDate'); +const expireDate = defineModel('expireDate'); const remark = defineModel('remark'); const workerType = defineModel('workerType'); const number = defineModel('number'); @@ -157,7 +157,7 @@ watch( name="mdi-passport" style="background-color: var(--surface-3)" /> - {{ title }} + {{ $t(title) }}
('branchId'); const customerBranchId = defineModel('customerBranchId'); const agentPrice = defineModel('agentPrice'); const special = defineModel('special'); +const customerBranchOption = defineModel( + 'customerBranchOption', +); + defineProps<{ outlined?: boolean; readonly?: boolean; @@ -67,10 +74,12 @@ defineEmits<{ required :readonly /> + {{ formatNumberDecimal( - props.row.pricePerUnit * props.row.amount - - props.row.discount, + props.row.product[ + agentPrice ? 'agentPriceCalcVat' : 'calcVat' + ] + ? precisionRound( + (props.row.pricePerUnit * + (1 + (config?.vat || 0.07)) * + props.row.amount - + props.row.discount) / + (1 + (config?.vat || 0.07)), + ) + : precisionRound( + props.row.pricePerUnit * props.row.amount - + props.row.discount, + ), 2, ) }} @@ -448,9 +459,12 @@ watch( agentPrice ? 'agentPriceCalcVat' : 'calcVat' ] ? precisionRound( - (props.row.pricePerUnit * props.row.amount - - props.row.discount) * - (config?.vat || 0.07), + ((props.row.pricePerUnit * + (1 + (config?.vat || 0.07)) * + props.row.amount - + props.row.discount) / + (1 + (config?.vat || 0.07))) * + 0.07, ) : 0, 2, diff --git a/src/components/05_quotation/TableQuotation.vue b/src/components/05_quotation/TableQuotation.vue index 414eedf9..76c9e38c 100644 --- a/src/components/05_quotation/TableQuotation.vue +++ b/src/components/05_quotation/TableQuotation.vue @@ -1,6 +1,6 @@ diff --git a/src/pages/03_customer-management/TabEmployee.vue b/src/pages/03_customer-management/TabEmployee.vue new file mode 100644 index 00000000..81348622 --- /dev/null +++ b/src/pages/03_customer-management/TabEmployee.vue @@ -0,0 +1,514 @@ + + + + diff --git a/src/pages/03_customer-management/components/employer/EmployerFormAbout.vue b/src/pages/03_customer-management/components/employer/EmployerFormAbout.vue index f98df3da..7353db3c 100644 --- a/src/pages/03_customer-management/components/employer/EmployerFormAbout.vue +++ b/src/pages/03_customer-management/components/employer/EmployerFormAbout.vue @@ -140,7 +140,7 @@ watch( :rules="[ (val) => !!val || $t('form.error.required'), (val) => - /^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) || + /^[A-Za-z0-9ก-๙\s&.,'()-]+$/.test(val) || $t('form.error.branchNameField'), ]" /> @@ -336,6 +336,7 @@ watch( (v) => (typeof v === 'string' ? (prefixName = v) : '') " @clear="prefixName = ''" + :rules="[(val: string) => !!val || $t('form.error.required')]" > 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,6 +34,8 @@ export const useCustomerForm = defineStore('form-customer', () => { const registerAbleBranchOption = ref<{ id: string; name: string }[]>(); + const currentBranchRootId = ref(''); + const tabFieldRequired = ref<{ [key: string]: (keyof CustomerBranchCreate)[]; }>({ @@ -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; @@ -255,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 @@ -495,11 +503,14 @@ export const useCustomerForm = defineStore('form-customer', () => { } return { + onCreateImageList, tabFieldRequired, registerAbleBranchOption, state, resetFormData, currentFormData, + currentBranchRootId, + isFormDataDifferent, resetForm, assignFormData, @@ -779,6 +790,7 @@ export const useEmployeeForm = defineStore('form-employee', () => { } | undefined; ocr: boolean; + imageList: { list: string[]; selectedImage: string }; }>({ currentBranchId: '', isImageEdit: false, @@ -803,6 +815,7 @@ export const useEmployeeForm = defineStore('form-employee', () => { infoEmployeePersonCard: [], formDataEmployeeOwner: undefined, ocr: false, + imageList: { list: [], selectedImage: '' }, }); const defaultFormData: EmployeeCreate & { image?: File } = { @@ -955,6 +968,7 @@ export const useEmployeeForm = defineStore('form-employee', () => { state.value.currentIndexVisa = -1; state.value.currentIndexCheckup = -1; state.value.currentIndexWorkHistory = -1; + state.value.imageList = { list: [], selectedImage: '' }; // state.value.currentTab = 'personalInfo'; if (clean) { state.value.formDataEmployeeOwner = undefined; @@ -1384,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 @@ -1663,6 +1675,7 @@ export const useEmployeeForm = defineStore('form-employee', () => { state, currentFromDataEmployee, resetEmployeeData, + addPassport, addVisa, addCheckup, diff --git a/src/pages/04_product-service/MainPage.vue b/src/pages/04_product-service/MainPage.vue index 442401f1..17cd4040 100644 --- a/src/pages/04_product-service/MainPage.vue +++ b/src/pages/04_product-service/MainPage.vue @@ -102,6 +102,8 @@ const { deleteWork, importProduct, + + productExport, } = productServiceStore; const { workNameItems } = storeToRefs(productServiceStore); @@ -1169,6 +1171,7 @@ function clearFormService() { profileSubmit.value = false; imageProduct.value = undefined; profileFileImg.value = null; + serviceTab.value = 1; } function sameFormService() { @@ -1896,6 +1899,25 @@ async function submitWorkName( 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) => { @@ -2229,7 +2251,6 @@ watch( -
+ +
@@ -4357,6 +4385,7 @@ watch( -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'; @@ -60,7 +60,6 @@ const flowStore = useFlowStore(); const userBranch = useMyBranch(); const navigatorStore = useNavigator(); const customerStore = useCustomerStore(); - const { fetchListOfOptionBranch, customerFormUndo, @@ -76,6 +75,7 @@ const { const { state: customerFormState, currentFormData: customerFormData, + currentBranchRootId, registerAbleBranchOption, tabFieldRequired, } = storeToRefs(customerFormStore); @@ -89,6 +89,8 @@ const fieldSelectedOption = computed(() => { value: v.name, })); }); + +const keyAddDialog = ref(0); const special = ref(false); const branchId = ref(''); const agentPrice = ref(false); @@ -273,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'; @@ -310,6 +316,12 @@ onMounted(async () => { } flowStore.rotate(); + + window.addEventListener('focus', handleWindowFocus); +}); + +onUnmounted(() => { + window.removeEventListener('focus', handleWindowFocus); }); async function fetchQuotationList(mobileFetch?: boolean) { @@ -765,8 +777,13 @@ async function filterBySellerId() { :worker-count="item.row._count.worker" :worker-max="item.row.workerMax || item.row._count.worker" :customer-name=" - item.row.customerBranch.registerName || - `${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}` + item.row.customerBranch.customer.customerType === 'CORP' + ? $i18n.locale === 'tha' + ? item.row.customerBranch.registerName + : item.row.customerBranch.registerNameEN + : $i18n.locale === 'tha' + ? `${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}` + : `${item.row.customerBranch.firstNameEN || '-'} ${item.row.customerBranch.lastNameEN || ''}` " :reporter=" $i18n.locale === 'eng' @@ -865,6 +882,7 @@ async function filterBySellerId() {
@@ -999,6 +1018,7 @@ async function filterBySellerId() { () => { customerFormState.dialogModal = false; onCreateImageList = { selectedImage: '', list: [] }; + keyAddDialog++; } " > @@ -1190,7 +1210,7 @@ async function filterBySellerId() { customerFormData.customerBranch[0].legalPersonNo " v-model:business-type=" - customerFormData.customerBranch[0].businessType + customerFormData.customerBranch[0].businessTypeId " v-model:job-position=" customerFormData.customerBranch[0].jobPosition @@ -1271,7 +1291,6 @@ async function filterBySellerId() { 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 64f57560..cf3d7c63 100644 --- a/src/pages/05_quotation/PaymentForm.vue +++ b/src/pages/05_quotation/PaymentForm.vue @@ -1,15 +1,9 @@ diff --git a/src/pages/09_task-order/document_view/MainPage.vue b/src/pages/09_task-order/document_view/MainPage.vue index b631dbb1..fc936db4 100644 --- a/src/pages/09_task-order/document_view/MainPage.vue +++ b/src/pages/09_task-order/document_view/MainPage.vue @@ -202,40 +202,44 @@ onMounted(async () => { }) .reduce( (a, c) => { - const priceNoVat = c.product.serviceChargeVatIncluded - ? c.pricePerUnit / (1 + (config.value?.vat || 0.07)) - : c.pricePerUnit; - const adjustedPriceWithVat = precisionRound( - priceNoVat * (1 + (config.value?.vat || 0.07)), - ); - const adjustedPriceNoVat = - adjustedPriceWithVat / (1 + (config.value?.vat || 0.07)); - const priceDiscountNoVat = adjustedPriceNoVat * c.amount - c.discount; + const vatFactor = c.product.serviceChargeCalcVat + ? (config.value?.vat ?? 0.07) + : 0; - const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07); - const rawVat = rawVatTotal / c.amount; + const pricePerUnit = + precisionRound(c.product.serviceCharge * (1 + vatFactor)) / + (1 + vatFactor); + + const amount = c.amount; + const discount = c.discount || 0; + + const price = + (pricePerUnit * amount * (1 + vatFactor) - discount) / + (1 + vatFactor); + + const vat = price * vatFactor; product.value.push({ id: c.product.id, code: c.product.code, detail: c.product.name, - priceUnit: precisionRound(priceNoVat), + priceUnit: precisionRound(pricePerUnit), amount: c.amount, discount: c.discount, - vat: c.product.serviceChargeCalcVat ? precisionRound(rawVat) : 0, + vat: c.product.serviceChargeCalcVat ? precisionRound(vat) : 0, value: precisionRound( - priceNoVat * c.amount + - (c.product.serviceChargeCalcVat ? rawVatTotal : 0), + pricePerUnit * c.amount + + (c.product.serviceChargeCalcVat ? vat : 0), ), }); - a.totalPrice = a.totalPrice + priceDiscountNoVat; - a.totalDiscount = a.totalDiscount + Number(c.discount); - a.vat = c.product.calcVat ? a.vat + rawVatTotal : a.vat; - a.vatExcluded = c.product.calcVat + a.totalPrice = precisionRound(a.totalPrice + price + discount); + a.totalDiscount = precisionRound(a.totalDiscount + discount); + a.vat = precisionRound(a.vat + vat); + a.vatExcluded = c.product.serviceChargeCalcVat ? a.vatExcluded - : precisionRound(a.vatExcluded + priceDiscountNoVat); - a.finalPrice = a.totalPrice - a.totalDiscount + a.vat; + : precisionRound(a.vatExcluded + price); + a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat); return a; }, { @@ -311,7 +315,7 @@ function closeAble() { border-bottom: 2px solid var(--main); " > - {{ $t('taskOrder.goodReceipt') }} + {{ $t('preview.productList') }} @@ -335,7 +339,9 @@ function closeAble() { {{ formatNumberDecimal(v.priceUnit, 2) }}
- {{ formatNumberDecimal(v.discount, 2) }} + {{ formatNumberDecimal(v.vat, 2) }} @@ -346,7 +352,6 @@ function closeAble() {
- {{ formatNumberDecimal( - summaryPrice.totalPrice - summaryPrice.totalDiscount, + summaryPrice.totalPrice - + summaryPrice.totalDiscount - + summaryPrice.vatExcluded, 2, ) }} diff --git a/src/pages/09_task-order/document_view/ViewHeader.vue b/src/pages/09_task-order/document_view/ViewHeader.vue index 0d1a89c3..9097cc89 100644 --- a/src/pages/09_task-order/document_view/ViewHeader.vue +++ b/src/pages/09_task-order/document_view/ViewHeader.vue @@ -64,8 +64,8 @@ defineProps<{ }) }} - เลขประจำตัวผู้เสียภาษี {{ branch.taxNo }} - เบอร์โทร {{ branch.telephoneNo }} + {{ $t('branch.form.taxNo') }} {{ branch.taxNo }} + {{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}{{ branch.webUrl }}
diff --git a/src/pages/09_task-order/expansion/AdditionalFileExpansion.vue b/src/pages/09_task-order/expansion/AdditionalFileExpansion.vue index b14ee28a..2d3ebdea 100644 --- a/src/pages/09_task-order/expansion/AdditionalFileExpansion.vue +++ b/src/pages/09_task-order/expansion/AdditionalFileExpansion.vue @@ -25,6 +25,7 @@ const fileData = defineModel<