Compare commits

...

24 commits

Author SHA1 Message Date
Thanaphon Frappet
d44850a9ae Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-20 16:28:33 +07:00
Thanaphon Frappet
b86891c8c2 refactor: remove btn
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-20 15:43:55 +07:00
Methapon2001
473e272328 fix: error undefined
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2025-08-19 11:45:24 +07:00
Thanaphon Frappet
02d02cf3a1 refactor: handle btn
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-18 09:24:49 +07:00
Thanaphon Frappet
044a530b8d refactor: edit i18n 2025-08-18 09:24:11 +07:00
Thanaphon Frappet
b21949712b fix: handle show group name
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-14 11:07:25 +07:00
Thanaphon Frappet
42e545dd66 fix: value option no set 2025-08-14 11:07:01 +07:00
Methapon2001
263c703e69 Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 8s
2025-08-04 09:01:06 +07:00
Methapon2001
968aa04aa9 fix: remark handling
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-25 16:19:36 +07:00
puriphatt
8ca3f784f1 fix: handle customer business type in table
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-21 09:53:31 +07:00
puriphatt
d60f858582 fix: quotation disable selectInstallment invoice btn when not selected
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-17 11:12:27 +07:00
Methapon2001
4ec3506e62 Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-17 08:58:25 +07:00
puriphatt
f3342dfbda fix: i18n
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2025-07-16 15:29:11 +07:00
puriphatt
9b56896695 fix: q-file visibility
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-07-16 15:25:47 +07:00
puriphatt
7d4b38369c fix: agency zipcode rules
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-07-16 15:00:36 +07:00
puriphatt
b977f86de9 feat: institution attachment
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-16 14:39:41 +07:00
Methapon2001
da52bfbcbd feat: remove employer prefix from string remark
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-16 13:36:12 +07:00
puriphatt
cdb38e301e feat: agency personnel foreign address
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-16 12:55:51 +07:00
puriphatt
7f56a6219a fix: edit debit note with new employee
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-15 14:31:53 +07:00
puriphatt
642dec8de9 refactor: debit note pay type
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-15 13:57:26 +07:00
Thanaphon Frappet
1360aca7e9 refactor: set label
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-07-15 13:53:45 +07:00
Thanaphon Frappet
dbca22f639 fix: show name anmd name en
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-07-15 13:44:30 +07:00
Thanaphon Frappet
b5abf693c2 fix: name
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2025-07-15 13:42:29 +07:00
puriphatt
8ef2ca2e96 fix: fix quotation customer branch id when create new employee
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-07-15 11:57:15 +07:00
28 changed files with 646 additions and 132 deletions

View file

@ -159,42 +159,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
]" ]"
for="input-name-en" for="input-name-en"
/> />
<q-select
v-if="
typeBranch !== 'headOffice' &&
isRoleInclude(['head_of_admin', 'head_of_account'])
"
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
class="col-2"
dense
for="input-branch-status"
:readonly="readonly || isRoleInclude(['head_of_account'])"
:options="['Virtual', 'Branch']"
:hide-dropdown-icon="readonly"
:label="$t('general.branchStatus')"
:model-value="virtual ? 'Virtual' : 'Branch'"
@update:model-value="(v) => (virtual = v === 'Virtual')"
:rules="[(val) => val && val.length > 0]"
:error-message="$t('form.error.required')"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
</div> </div>
<div class="col-12 row q-col-gutter-sm"> <div class="col-12 row q-col-gutter-sm">

View file

@ -262,7 +262,7 @@ function deleteFile(name: string) {
</div> </div>
<q-file <q-file
v-if="userType" v-if="userType && !readonly"
ref="attachmentRef" ref="attachmentRef"
for="input-attachment" for="input-attachment"
:dense="dense" :dense="dense"

View file

@ -54,6 +54,7 @@ defineProps<{
employeeOwnerOption?: CustomerBranch[]; employeeOwnerOption?: CustomerBranch[];
prefixId: string; prefixId: string;
showBtnSave?: boolean; showBtnSave?: boolean;
disableCustomerSelect?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -117,12 +118,16 @@ defineEmits<{
<div class="col-12 row" style="gap: var(--size-2)"> <div class="col-12 row" style="gap: var(--size-2)">
<SelectCustomer <SelectCustomer
id="form-select-customer-branch-id"
for="form-select-customer-branch-id"
v-model:value="customerBranchId" v-model:value="customerBranchId"
v-model:value-option="currentCustomerBranch" v-model:value-option="currentCustomerBranch"
:label="$t('customer.form.branchCode')" :label="$t('customer.form.branchCode')"
class="col-12 field-two" class="col-12 field-two"
simple
required required
:readonly :readonly
:disabled="disableCustomerSelect && !readonly"
/> />
<q-input <q-input

View file

@ -68,6 +68,8 @@ defineEmits<{
:readonly :readonly
/> />
<SelectCustomer <SelectCustomer
id="about-select-customer-branch-id"
for="about-select-customer-branch-id"
v-model:value="customerBranchId" v-model:value="customerBranchId"
:label="$t('quotation.customer')" :label="$t('quotation.customer')"
:creatable-disabled-text="`(${$t('form.error.selectField', { :creatable-disabled-text="`(${$t('form.error.selectField', {
@ -88,14 +90,14 @@ defineEmits<{
<style scoped> <style scoped>
:deep( :deep(
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one
) { ) {
padding-right: 4px; padding-right: 4px;
} }
:deep( :deep(
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two
) { ) {
padding-left: 4px; padding-left: 4px;
} }
</style> </style>

View file

@ -1,6 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SelectInput from '../shared/SelectInput.vue'; import SelectInput from '../shared/SelectInput.vue';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useInstitution } from 'src/stores/institution';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
@ -10,6 +14,11 @@ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
}>(); }>();
const emit = defineEmits<{
(e: 'deleteAttachment', name: string): void;
}>();
const attachmentRef = ref();
const group = defineModel('group', { default: '' }); const group = defineModel('group', { default: '' });
const name = defineModel('name', { default: '' }); const name = defineModel('name', { default: '' });
@ -17,8 +26,19 @@ const nameEn = defineModel('nameEn', { default: '' });
const contactName = defineModel('contactName', { default: '' }); const contactName = defineModel('contactName', { default: '' });
const email = defineModel('email', { default: '' }); const email = defineModel('email', { default: '' });
const contactTel = defineModel('contactTel', { default: '' }); const contactTel = defineModel('contactTel', { default: '' });
const attachment = defineModel<File[]>('attachment');
const attachmentList =
defineModel<{ name: string; url: string }[]>('attachmentList');
type Options = { label: string; value: string }; type Options = { label: string; value: string };
function openNewTab(url: string) {
window.open(url, '_blank');
}
function deleteAttachment(name: string) {
emit('deleteAttachment', name);
}
</script> </script>
<template> <template>
<div class="row col-12"> <div class="row col-12">
@ -162,6 +182,78 @@ type Options = { label: string; value: string };
/> />
</template> </template>
</q-input> </q-input>
<q-file
v-if="!readonly"
ref="attachmentRef"
for="input-attachment"
dense
outlined
multiple
append
:readonly
:label="$t('personnel.form.attachment')"
class="col"
v-model="attachment"
>
<template v-slot:prepend>
<Icon
icon="material-symbols:attach-file"
width="20px"
style="color: var(--brand-1)"
/>
</template>
<template v-slot:file="file">
<div class="row full-width items-center">
<span class="col ellipsis">
{{ file.file.name }}
</span>
<q-btn
dense
rounded
flat
padding="2 2"
class="app-text-muted"
icon="mdi-close-circle"
@click.stop="attachmentRef.removeAtIndex(file.index)"
/>
</div>
</template>
</q-file>
<div v-if="attachmentList && attachmentList?.length > 0" class="col-12">
<q-list bordered separator class="rounded" style="padding: 0">
<q-item
id="attachment-file"
for="attachment-file"
v-for="item in attachmentList"
clickable
:key="item.url"
class="items-center row"
@click="() => openNewTab(item.url)"
>
<q-item-section>
<div class="row items-center justify-between">
<div class="col">
{{ item.name }}
</div>
<q-btn
v-if="!readonly"
id="delete-file"
rounded
flat
dense
unelevated
size="md"
icon="mdi-trash-can-outline"
class="app-text-negative"
@click.stop="deleteAttachment(item.name)"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</div>
</div> </div>
</div> </div>
</template> </template>

View file

@ -12,7 +12,7 @@ import { formatAddress } from 'src/utils/address';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
defineProps<{ const props = defineProps<{
title?: string; title?: string;
addressTitle?: string; addressTitle?: string;
addressTitleEN?: string; addressTitleEN?: string;
@ -30,6 +30,7 @@ defineProps<{
useEmployment?: boolean; useEmployment?: boolean;
useWorkPlace?: boolean; useWorkPlace?: boolean;
useForeignAddress?: boolean;
}>(); }>();
const addressStore = useAddressStore(); const addressStore = useAddressStore();
@ -57,6 +58,25 @@ const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
const zipCode = defineModel<string | null | undefined>('zipCode'); const zipCode = defineModel<string | null | undefined>('zipCode');
const sameWithEmployer = defineModel<boolean>('sameWithEmployer'); const sameWithEmployer = defineModel<boolean>('sameWithEmployer');
const provinceTextEN = defineModel<string | null | undefined>(
'provinceTextEn',
{
default: '',
},
);
const districtTextEN = defineModel<string | null | undefined>(
'districtTextEn',
{
default: '',
},
);
const subDistrictTextEN = defineModel<string | null | undefined>(
'subDistrictTextEn',
{
default: '',
},
);
const homeCode = defineModel<string | null | undefined>('homeCode'); const homeCode = defineModel<string | null | undefined>('homeCode');
const employmentOffice = defineModel<string | null | undefined>( const employmentOffice = defineModel<string | null | undefined>(
'employmentOffice', 'employmentOffice',
@ -64,6 +84,7 @@ const employmentOffice = defineModel<string | null | undefined>(
const employmentOfficeEN = defineModel<string | null | undefined>( const employmentOfficeEN = defineModel<string | null | undefined>(
'employmentOfficeEn', 'employmentOfficeEn',
); );
const addressForeign = defineModel<boolean>('addressForeign');
const addrOptions = reactive<{ const addrOptions = reactive<{
provinceOps: Province[]; provinceOps: Province[];
@ -78,14 +99,18 @@ const addrOptions = reactive<{
const area = ref<Office[]>([]); const area = ref<Office[]>([]);
const fullAddress = computed(() => { const fullAddress = computed(() => {
const province = provinceOptions.value.find((v) => v.id === provinceId.value); const province = addressForeign.value
const district = districtOptions.value.find((v) => v.id === districtId.value); ? { id: '1', name: provinceId.value }
const sDistrict = subDistrictOptions.value.find( : provinceOptions.value.find((v) => v.id === provinceId.value);
(v) => v.id === subDistrictId.value, const district = addressForeign.value
); ? { id: '1', name: districtId.value }
: districtOptions.value.find((v) => v.id === districtId.value);
const sDistrict = addressForeign.value
? { id: '1', name: subDistrictId.value }
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
if (province && district && sDistrict) { if (province?.name && district?.name && sDistrict?.name) {
const fullAddress = formatAddress({ const fullAddressText = formatAddress({
address: address.value, address: address.value,
addressEN: addressEN.value, addressEN: addressEN.value,
moo: moo.value ? moo.value : '', moo: moo.value ? moo.value : '',
@ -97,21 +122,26 @@ const fullAddress = computed(() => {
province: province as unknown as Province, province: province as unknown as Province,
district: district as unknown as District, district: district as unknown as District,
subDistrict: sDistrict as unknown as SubDistrict, subDistrict: sDistrict as unknown as SubDistrict,
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
}); });
return fullAddress; return fullAddressText;
} }
return '-'; return '-';
}); });
const fullAddressEN = computed(() => { const fullAddressEN = computed(() => {
const province = provinceOptions.value.find((v) => v.id === provinceId.value); const province = addressForeign.value
const district = districtOptions.value.find((v) => v.id === districtId.value); ? { nameEN: provinceTextEN.value }
const sDistrict = subDistrictOptions.value.find( : provinceOptions.value.find((v) => v.id === provinceId.value);
(v) => v.id === subDistrictId.value, const district = addressForeign.value
); ? { nameEN: districtTextEN.value }
: districtOptions.value.find((v) => v.id === districtId.value);
const sDistrict = addressForeign.value
? { nameEN: subDistrictTextEN.value }
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
if (province && district && sDistrict) { if (province?.nameEN && district?.nameEN && sDistrict?.nameEN) {
const fullAddress = formatAddress({ const fullAddressText = formatAddress({
address: address.value, address: address.value,
addressEN: addressEN.value, addressEN: addressEN.value,
moo: moo.value ? moo.value : '', moo: moo.value ? moo.value : '',
@ -124,8 +154,9 @@ const fullAddressEN = computed(() => {
district: district as unknown as District, district: district as unknown as District,
subDistrict: sDistrict as unknown as SubDistrict, subDistrict: sDistrict as unknown as SubDistrict,
en: true, en: true,
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
}); });
return fullAddress; return fullAddressText;
} }
return '-'; return '-';
}); });
@ -149,7 +180,7 @@ async function fetchProvince() {
} }
async function fetchDistrict() { async function fetchDistrict() {
if (!provinceId.value) return; if (!provinceId.value || addressForeign.value) return;
const result = await addressStore.fetchDistrictByProvinceId(provinceId.value); const result = await addressStore.fetchDistrictByProvinceId(provinceId.value);
if (result) addrOptions.districtOps = result; if (result) addrOptions.districtOps = result;
@ -168,7 +199,7 @@ async function fetchDistrict() {
} }
async function fetchSubDistrict() { async function fetchSubDistrict() {
if (!districtId.value) return; if (!districtId.value || addressForeign.value) return;
const result = await addressStore.fetchSubDistrictByProvinceId( const result = await addressStore.fetchSubDistrictByProvinceId(
districtId.value, districtId.value,
); );
@ -255,6 +286,16 @@ onMounted(async () => {
await fetchSubDistrict(); await fetchSubDistrict();
}); });
function clearAddress() {
provinceId.value = null;
districtId.value = null;
subDistrictId.value = null;
provinceTextEN.value = null;
districtTextEN.value = null;
subDistrictTextEN.value = null;
zipCode.value = null;
}
watch(provinceId, fetchDistrict); watch(provinceId, fetchDistrict);
watch(districtId, fetchSubDistrict); watch(districtId, fetchSubDistrict);
@ -313,6 +354,15 @@ watchEffect(async () => {
{{ $t('customerEmployee.form.addressCustom') }} {{ $t('customerEmployee.form.addressCustom') }}
</span> </span>
</div> </div>
<div v-if="useForeignAddress" class="text-caption q-ml-md app-text-muted">
<q-checkbox
size="xs"
v-model="addressForeign"
@update:model-value="clearAddress"
/>
{{ $t('personnel.form.addressForeign') }}
</div>
</div> </div>
<div class="col-12 row q-col-gutter-y-md"> <div class="col-12 row q-col-gutter-y-md">
@ -449,7 +499,24 @@ watchEffect(async () => {
(v) => (typeof v === 'string' ? (street = v) : '') (v) => (typeof v === 'string' ? (street = v) : '')
" "
/> />
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="provinceId"
:dense="dense"
:label="$t('form.province')"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-province-${indexId}` : 'input-province'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -493,7 +560,24 @@ watchEffect(async () => {
</template> </template>
</q-select> </q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="districtId"
:dense="dense"
:label="$t('form.district')"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-district-${indexId}` : 'input-district'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -536,7 +620,25 @@ watchEffect(async () => {
</q-item> </q-item>
</template> </template>
</q-select> </q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="subDistrictId"
:dense="dense"
:label="$t('form.district')"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-${indexId}` : 'input-sub-district'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -580,17 +682,27 @@ watchEffect(async () => {
</template> </template>
</q-select> </q-select>
<q-input <q-input
:key="Number(addressForeign)"
hide-bottom-space
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`" :for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
:dense="dense" :dense="dense"
outlined outlined
:disable="!readonly && !sameWithEmployer" :disable="!addressForeign && !readonly && !sameWithEmployer"
readonly :readonly="!addressForeign || readonly"
:label="$t('form.zipCode')" :label="$t('form.zipCode')"
class="col-md-3 col-6" class="col-md-3 col-6"
:model-value=" :model-value="
addrOptions.subDistrictOps !addressForeign
?.filter((x) => x.id === subDistrictId) ? (addrOptions.subDistrictOps
.map((x) => x.zipCode)[0] ?? '' ?.filter((x) => x.id === subDistrictId)
.map((x) => x.zipCode)[0] ?? '')
: zipCode
"
@update:model-value="(v) => (zipCode = v.toString())"
:rules="
!addressForeign
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
" "
/> />
<q-input <q-input
@ -689,7 +801,24 @@ watchEffect(async () => {
(v) => (typeof v === 'string' ? (streetEN = v) : '') (v) => (typeof v === 'string' ? (streetEN = v) : '')
" "
/> />
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="provinceTextEN"
:dense="dense"
label="Province"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-province-en-${indexId}` : 'input-province-en'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -732,7 +861,25 @@ watchEffect(async () => {
</q-item> </q-item>
</template> </template>
</q-select> </q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="districtTextEN"
:dense="dense"
label="District"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-district-en-${indexId}` : 'input-district-en'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -775,7 +922,25 @@ watchEffect(async () => {
</q-item> </q-item>
</template> </template>
</q-select> </q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="subDistrictTextEN"
:dense="dense"
label="Sub-District"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-en-${indexId}` : 'input-sub-district-en'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select <q-select
v-else
autocomplete="off" autocomplete="off"
outlined outlined
clearable clearable
@ -819,19 +984,28 @@ watchEffect(async () => {
</template> </template>
</q-select> </q-select>
<q-input <q-input
:key="Number(addressForeign)"
hide-bottom-space hide-bottom-space
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`" :for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
:dense="dense" :dense="dense"
outlined outlined
readonly :readonly="!addressForeign || readonly"
:disable="!readonly && !sameWithEmployer" :disable="!addressForeign && !readonly && !sameWithEmployer"
zip="zip-en" zip="zip-en"
label="Zip Code" label="Zip Code"
class="col-md-3 col-6" class="col-md-3 col-6"
:model-value=" :model-value="
addrOptions.subDistrictOps !addressForeign
?.filter((x) => x.id === subDistrictId) ? (addrOptions.subDistrictOps
.map((x) => x.zipCode)[0] ?? '' ?.filter((x) => x.id === subDistrictId)
.map((x) => x.zipCode)[0] ?? '')
: zipCode
"
@update:model-value="(v) => (zipCode = v.toString())"
:rules="
!addressForeign
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
" "
/> />
<q-input <q-input

View file

@ -79,7 +79,7 @@ function setDefaultValue() {
v-model="value" v-model="value"
incremental incremental
option-value="id" option-value="id"
:label :label="label || $t('menu.manage.businessType')"
:placeholder :placeholder
:readonly :readonly
:disable="disabled" :disable="disabled"
@ -92,21 +92,13 @@ function setDefaultValue() {
@filter="filter" @filter="filter"
> >
<template #selected-item="{ opt }"> <template #selected-item="{ opt }">
{{ {{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
(lang ?? $i18n.locale) !== 'eng'
? opt.name + ' ' + opt.name
: opt.nameEN + ' ' + opt.nameEN
}}
</template> </template>
<template #option="{ opt, scope }"> <template #option="{ opt, scope }">
<q-item v-bind="scope.itemProps"> <q-item v-bind="scope.itemProps">
<span class="row items-center"> <span class="row items-center">
{{ {{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
(lang ?? $i18n.locale) !== 'eng'
? opt.name + ' ' + opt.name
: opt.nameEN + ' ' + opt.nameEN
}}
</span> </span>
</q-item> </q-item>

View file

@ -35,7 +35,13 @@ export const createSelect = <T extends Record<string, any>>(
let previousSearch = ''; let previousSearch = '';
watch(value, (v) => { 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(); getSelectedOption();
}); });
@ -63,7 +69,15 @@ export const createSelect = <T extends Record<string, any>>(
const currentValue = value.value; const currentValue = value.value;
if (!currentValue) return; if (!currentValue) return;
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
const option = selectOptions.value.find(
(v) => v[valueField] === currentValue,
);
if (option) {
valueOption.value = option;
return;
}
if (valueOption.value && valueOption.value[valueField] === currentValue) { if (valueOption.value && valueOption.value[valueField] === currentValue) {
return selectOptions.value.unshift(valueOption.value); return selectOptions.value.unshift(valueOption.value);
} }

View file

@ -476,6 +476,7 @@ export default {
blacklist: 'Black list', blacklist: 'Black list',
contactName: 'Contact Person', contactName: 'Contact Person',
contactTel: 'Contact Number', contactTel: 'Contact Number',
addressForeign: 'Use foreign address',
}, },
}, },
customer: { customer: {
@ -927,6 +928,7 @@ export default {
contactName: 'Contact Person', contactName: 'Contact Person',
contactTel: 'Contact Number', contactTel: 'Contact Number',
bankInfo: 'Bank Information', bankInfo: 'Bank Information',
attachment: 'Attachment',
}, },
requestList: { requestList: {

View file

@ -472,6 +472,7 @@ export default {
blacklist: 'แบล็คลิสต์', blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ', contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ', contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
}, },
}, },
customer: { customer: {
@ -798,7 +799,7 @@ export default {
branch: 'สาขาที่ออกใบเสนอราคา', branch: 'สาขาที่ออกใบเสนอราคา',
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา', branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
customer: 'ลูกค้า', customer: 'ลูกค้า',
newCustomer: 'ลูกค้าใหม่', newCustomer: 'แรงงานใหม่',
employeeList: 'รายชื่อแรงงาน', employeeList: 'รายชื่อแรงงาน',
employee: 'แรงงาน', employee: 'แรงงาน',
employeeName: 'ชื่อ-นามสกุล แรงงาน', employeeName: 'ชื่อ-นามสกุล แรงงาน',
@ -924,6 +925,7 @@ export default {
contactName: 'ชื่อผู้ติดต่อ', contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ', contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร', bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
}, },
requestList: { requestList: {

View file

@ -157,6 +157,14 @@ const defaultFormData = {
contactTel: '', contactTel: '',
remark: '', remark: '',
agencyStatus: '', agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
}; };
const formData = ref<UserCreate>({ const formData = ref<UserCreate>({
@ -209,6 +217,14 @@ const formData = ref<UserCreate>({
contactTel: '', contactTel: '',
remark: '', remark: '',
agencyStatus: '', agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
}); });
const fieldSelectedOption = ref<{ label: string; value: string }[]>([ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
@ -431,6 +447,28 @@ async function onSubmit(excludeDialog?: boolean) {
...formData.value, ...formData.value,
checkpointEN: formData.value.checkpoint, checkpointEN: formData.value.checkpoint,
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE', status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE',
provinceId: formData.value.addressForeign
? null
: formData.value.provinceId,
districtId: formData.value.addressForeign
? null
: formData.value.districtId,
subDistrictId: formData.value.addressForeign
? null
: formData.value.subDistrictId,
provinceText: formData.value.addressForeign
? formData.value.provinceId
: null,
districtText: formData.value.addressForeign
? formData.value.districtId
: null,
subDistrictText: formData.value.addressForeign
? formData.value.subDistrictId
: null,
zipCodeText: formData.value.addressForeign
? formData.value.zipCode
: null,
} as const; } as const;
await userStore.editById(currentUser.value.id, formDataEdit); await userStore.editById(currentUser.value.id, formDataEdit);
@ -462,7 +500,31 @@ async function onSubmit(excludeDialog?: boolean) {
: ''; : '';
formData.value.checkpointEN = formData.value.checkpoint; formData.value.checkpointEN = formData.value.checkpoint;
const result = await userStore.create( const result = await userStore.create(
formData.value, {
...formData.value,
provinceId: formData.value.addressForeign
? null
: formData.value.provinceId,
districtId: formData.value.addressForeign
? null
: formData.value.districtId,
subDistrictId: formData.value.addressForeign
? null
: formData.value.subDistrictId,
provinceText: formData.value.addressForeign
? formData.value.provinceId
: null,
districtText: formData.value.addressForeign
? formData.value.districtId
: null,
subDistrictText: formData.value.addressForeign
? formData.value.subDistrictId
: null,
zipCodeText: formData.value.addressForeign
? formData.value.zipCode
: null,
},
onCreateImageList.value, onCreateImageList.value,
); );
@ -560,12 +622,20 @@ async function assignFormData(idEdit: string) {
currentUser.value = foundUser; currentUser.value = foundUser;
formData.value = { formData.value = {
branchId: foundUser.branch[0]?.id, branchId: foundUser.branch[0]?.id,
provinceId: foundUser.provinceId, provinceId: foundUser.addressForeign
districtId: foundUser.districtId, ? foundUser.provinceText
subDistrictId: foundUser.subDistrictId, : foundUser.provinceId,
districtId: foundUser.addressForeign
? foundUser.districtText
: foundUser.districtId,
subDistrictId: foundUser.addressForeign
? foundUser.subDistrictText
: foundUser.subDistrictId,
telephoneNo: foundUser.telephoneNo, telephoneNo: foundUser.telephoneNo,
email: foundUser.email, email: foundUser.email,
zipCode: foundUser.zipCode, zipCode: foundUser.addressForeign
? foundUser.zipCodeText
: foundUser.zipCode,
gender: foundUser.gender, gender: foundUser.gender,
addressEN: foundUser.addressEN, addressEN: foundUser.addressEN,
address: foundUser.address, address: foundUser.address,
@ -619,6 +689,10 @@ async function assignFormData(idEdit: string) {
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null, (foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null,
remark: foundUser.remark || '', remark: foundUser.remark || '',
agencyStatus: foundUser.agencyStatus || '', agencyStatus: foundUser.agencyStatus || '',
addressForeign: foundUser.addressForeign || false,
provinceTextEN: foundUser.provinceTextEN,
districtTextEN: foundUser.districtTextEN,
subDistrictTextEN: foundUser.subDistrictTextEN,
}; };
formData.value.status === 'ACTIVE' || 'CREATED' formData.value.status === 'ACTIVE' || 'CREATED'
@ -745,7 +819,17 @@ watch(
watch( watch(
() => formData.value.userType, () => formData.value.userType,
async () => { async (type) => {
if (type !== 'AGENCY') {
formData.value.addressForeign = false;
formData.value.provinceId = null;
formData.value.districtId = null;
formData.value.subDistrictId = null;
formData.value.provinceTextEN = null;
formData.value.districtTextEN = null;
formData.value.subDistrictTextEN = null;
formData.value.zipCodeText = null;
}
if (!infoDrawerEdit.value) return; if (!infoDrawerEdit.value) return;
formData.value.registrationNo = null; formData.value.registrationNo = null;
formData.value.startDate = null; formData.value.startDate = null;
@ -1813,10 +1897,15 @@ watch(
v-model:district-id="formData.districtId" v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId" v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode" v-model:zip-code="formData.zipCode"
v-model:address-foreign="formData.addressForeign"
v-model:province-text-en="formData.provinceTextEN"
v-model:district-text-en="formData.districtTextEN"
v-model:sub-district-text-en="formData.subDistrictTextEN"
:readonly="!infoDrawerEdit" :readonly="!infoDrawerEdit"
prefix-id="drawer-info-personnel" prefix-id="drawer-info-personnel"
:title="'personnel.form.addressInformation'" :title="'personnel.form.addressInformation'"
dense dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
<FormByType <FormByType
@ -2042,8 +2131,13 @@ watch(
v-model:district-id="formData.districtId" v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId" v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode" v-model:zip-code="formData.zipCode"
v-model:address-foreign="formData.addressForeign"
v-model:province-text-en="formData.provinceTextEN"
v-model:district-text-en="formData.districtTextEN"
v-model:sub-district-text-en="formData.subDistrictTextEN"
prefix-id="drawer-info-personnel" prefix-id="drawer-info-personnel"
dense dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
<FormByType <FormByType

View file

@ -1266,15 +1266,19 @@ const emptyCreateDialog = ref(false);
class="ellipsis" class="ellipsis"
> >
{{ {{
props.row.branch[0].businessType[ props.row.branch[0].businessType
$i18n.locale === 'eng' ? 'nameEN' : 'name' ? props.row.branch[0].businessType[
] $i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-'
}} }}
<q-tooltip> <q-tooltip>
{{ {{
props.row.branch[0].businessType[ props.row.branch[0].businessType
$i18n.locale === 'eng' ? 'nameEN' : 'name' ? props.row.branch[0].businessType[
] $i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-'
}} }}
</q-tooltip> </q-tooltip>
</q-td> </q-td>

View file

@ -2313,6 +2313,7 @@ function covertToNode() {
> >
<MainButton <MainButton
solid solid
:disabled="selectedInstallment.length === 0"
icon="mdi-account-multiple-check-outline" icon="mdi-account-multiple-check-outline"
class="q-ml-sm" class="q-ml-sm"
color="207 96% 32%" color="207 96% 32%"

View file

@ -240,7 +240,7 @@ watch(
? payTypeOption.filter((v) => v.value === 'Full') ? payTypeOption.filter((v) => v.value === 'Full')
: payTypeOption : payTypeOption
" "
:readonly :readonly="readonly || debitNote"
id="pay-type" id="pay-type"
:model-value="payType" :model-value="payType"
@update:model-value=" @update:model-value="

View file

@ -51,6 +51,8 @@ const emit = defineEmits<{
const selectedProductGroup = defineModel<string>('selectedProductGroup', { const selectedProductGroup = defineModel<string>('selectedProductGroup', {
default: '', default: '',
}); });
const selectedProductGroupOption = ref<ProductGroup | undefined>();
const model = defineModel<boolean>(); const model = defineModel<boolean>();
const inputSearch = defineModel<string>('inputSearch'); const inputSearch = defineModel<string>('inputSearch');
const productGroup = defineModel<ProductGroup[]>('productGroup', { const productGroup = defineModel<ProductGroup[]>('productGroup', {
@ -569,14 +571,18 @@ watch(
{{ {{
productGroup.find( productGroup.find(
(g) => g.id === selectedProductGroup, (g) => g.id === selectedProductGroup,
)?.name || '-' )?.name ||
selectedProductGroupOption?.name ||
'-'
}} }}
</span> </span>
<span class="text-caption app-text-muted"> <span class="text-caption app-text-muted">
{{ {{
productGroup.find( productGroup.find(
(g) => g.id === selectedProductGroup, (g) => g.id === selectedProductGroup,
)?.code || '-' )?.code ||
selectedProductGroupOption?.code ||
'-'
}} }}
</span> </span>
</div> </div>
@ -862,13 +868,13 @@ watch(
<span class="q-pr-sm"> <span class="q-pr-sm">
{{ $t('productService.group.title') }} {{ $t('productService.group.title') }}
</span> </span>
<SelectProductGroup <SelectProductGroup
class="col-md-4 col-12" class="col-md-4 col-12"
:class="{ 'q-mb-sm': $q.screen.lt.md }" :class="{ 'q-mb-sm': $q.screen.lt.md }"
id="product-group-select" id="product-group-select"
clearable clearable
v-model:value="selectedProductGroup" v-model:value="selectedProductGroup"
v-model:value-option="selectedProductGroupOption"
:placeholder=" :placeholder="
!selectedProductGroup !selectedProductGroup
? $t('general.select', { ? $t('general.select', {

View file

@ -21,6 +21,7 @@ import useOcrStore from 'stores/ocr';
// NOTE: Import Components // NOTE: Import Components
import { import {
AddButton,
SaveButton, SaveButton,
EditButton, EditButton,
UndoButton, UndoButton,
@ -53,6 +54,9 @@ import { SideMenu } from 'src/components';
import BasicInformation from 'components/03_customer-management/employee/BasicInformation.vue'; import BasicInformation from 'components/03_customer-management/employee/BasicInformation.vue';
import { AddressForm } from 'src/components/form'; import { AddressForm } from 'src/components/form';
import ExpirationDate from 'src/components/03_customer-management/ExpirationDate.vue'; 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; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
@ -663,6 +667,7 @@ watch(
:show=" :show="
() => { () => {
employeeFormStore.resetFormDataEmployee(true); employeeFormStore.resetFormDataEmployee(true);
setCurrentBranchId();
} }
" "
:before-close=" :before-close="
@ -1035,6 +1040,7 @@ watch(
</div> </div>
<BasicInformation <BasicInformation
disable-customer-select
no-action no-action
id="form-information" id="form-information"
prefix-id="form-employee" prefix-id="form-employee"

View file

@ -82,6 +82,7 @@ const emit = defineEmits<{
(e: 'addImage'): void; (e: 'addImage'): void;
(e: 'removeImage'): void; (e: 'removeImage'): void;
(e: 'submitImage', name: string): void; (e: 'submitImage', name: string): void;
(e: 'deleteAttachment', name: string): void;
}>(); }>();
const data = defineModel<InstitutionPayload>('data', { const data = defineModel<InstitutionPayload>('data', {
@ -121,6 +122,9 @@ const formBankBook = defineModel<BankBook[]>('formBankBook', {
}, },
], ],
}); });
const attachment = defineModel<File[]>('attachment');
const attachmentList =
defineModel<{ name: string; url: string }[]>('attachmentList');
function viewImage() { function viewImage() {
imageState.imageDialog = true; imageState.imageDialog = true;
@ -348,6 +352,7 @@ watch(
v-model:contact-name="data.contactName" v-model:contact-name="data.contactName"
v-model:email="data.contactEmail" v-model:email="data.contactEmail"
v-model:contact-tel="data.contactTel" v-model:contact-tel="data.contactTel"
v-model:attachment="attachment"
/> />
<AddressForm <AddressForm
id="agencies-form-address-info" id="agencies-form-address-info"
@ -550,6 +555,9 @@ watch(
v-model:contact-name="data.contactName" v-model:contact-name="data.contactName"
v-model:email="data.contactEmail" v-model:email="data.contactEmail"
v-model:contact-tel="data.contactTel" v-model:contact-tel="data.contactTel"
v-model:attachment="attachment"
:attachment-list="attachmentList"
@delete-attachment="(name) => $emit('deleteAttachment', name)"
/> />
<AddressForm <AddressForm
id="agencies-address-info" id="agencies-address-info"

View file

@ -115,6 +115,8 @@ const blankFormData: InstitutionPayload = {
], ],
}; };
const attachment = ref<File[]>([]);
const attachmentList = ref<{ name: string; url: string }[]>([]);
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all'); const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
const refAgenciesDialog = ref(); const refAgenciesDialog = ref();
const formData = ref<InstitutionPayload>(structuredClone(blankFormData)); const formData = ref<InstitutionPayload>(structuredClone(blankFormData));
@ -145,6 +147,8 @@ function resetForm() {
pageState.addModal = false; pageState.addModal = false;
pageState.viewDrawer = false; pageState.viewDrawer = false;
currAgenciesData.value = undefined; currAgenciesData.value = undefined;
attachment.value = [];
attachmentList.value = [];
formData.value = structuredClone(blankFormData); formData.value = structuredClone(blankFormData);
} }
@ -154,7 +158,7 @@ function undo() {
pageState.isDrawerEdit = false; pageState.isDrawerEdit = false;
} }
function assignFormData(data: Institution) { async function assignFormData(data: Institution) {
currAgenciesData.value = data; currAgenciesData.value = data;
formData.value = { formData.value = {
group: data.group, group: data.group,
@ -187,6 +191,8 @@ function assignFormData(data: Institution) {
bankUrl: `${baseUrl}/institution/${data.id}/bank-qr/${v.id}?ts=${Date.now()}`, bankUrl: `${baseUrl}/institution/${data.id}/bank-qr/${v.id}?ts=${Date.now()}`,
})), })),
}; };
await fetchAttachment();
} }
async function submit(opt?: { selectedImage: string }) { async function submit(opt?: { selectedImage: string }) {
@ -228,18 +234,29 @@ async function submit(opt?: { selectedImage: string }) {
); );
if (ret) { if (ret) {
attachment.value.forEach(async (file) => {
await institutionStore.putAttachment({
parentId: ret.id || '',
name: file.name,
file: file,
});
});
pageState.isDrawerEdit = false; pageState.isDrawerEdit = false;
currAgenciesData.value = ret; currAgenciesData.value = ret;
formData.value.selectedImage = ret.selectedImage; formData.value.selectedImage = ret.selectedImage;
await fetchData($q.screen.xs); await fetchData($q.screen.xs);
attachment.value = [];
if (refAgenciesDialog.value && opt?.selectedImage) { if (refAgenciesDialog.value && opt?.selectedImage) {
refAgenciesDialog.value.clearImageState(); refAgenciesDialog.value.clearImageState();
} }
setTimeout(async () => {
await fetchAttachment();
}, 300);
return; return;
} }
} else { } else {
await institutionStore.createInstitution( const res = await institutionStore.createInstitution(
{ {
...payload, ...payload,
code: formData.value.group || '', code: formData.value.group || '',
@ -247,6 +264,16 @@ async function submit(opt?: { selectedImage: string }) {
imageListOnCreate.value, imageListOnCreate.value,
); );
if (!res) return;
attachment.value.forEach(async (file) => {
await institutionStore.putAttachment({
parentId: res.id || '',
name: file.name,
file: file,
});
});
await fetchData($q.screen.xs); await fetchData($q.screen.xs);
pageState.addModal = false; pageState.addModal = false;
return; return;
@ -346,6 +373,49 @@ async function changeStatus(id?: string) {
} }
} }
async function deleteAttachment(attachmentName: string) {
dialog({
color: 'negative',
icon: 'mdi-trash-can-outline',
title: t('dialog.title.confirmDelete', {
msg: t('personnel.form.attachment'),
}),
actionText: t('general.delete'),
persistent: true,
message: t('dialog.message.confirmDelete'),
action: async () => {
if (!currAgenciesData.value?.id) return;
institutionStore.delAttachment({
parentId: currAgenciesData.value?.id,
name: attachmentName,
});
setTimeout(async () => {
await fetchAttachment();
}, 300);
},
cancel: () => {},
});
}
async function fetchAttachment() {
const resAttachment = await institutionStore.listAttachment({
parentId: currAgenciesData.value.id,
});
if (!resAttachment) return;
attachmentList.value = await Promise.all(
resAttachment.map(async (f) => ({
name: f,
url: await institutionStore.getAttachment({
parentId: currAgenciesData.value.id,
name: f,
download: false,
}),
})),
);
}
onMounted(async () => { onMounted(async () => {
navigatorStore.current.title = 'agencies.title'; navigatorStore.current.title = 'agencies.title';
navigatorStore.current.path = [{ text: 'agencies.caption', i18n: true }]; navigatorStore.current.path = [{ text: 'agencies.caption', i18n: true }];
@ -976,6 +1046,7 @@ watch(
} }
" "
@change-status="triggerChangeStatus" @change-status="triggerChangeStatus"
@delete-attachment="deleteAttachment"
:readonly="!pageState.isDrawerEdit" :readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit" :isEdit="pageState.isDrawerEdit"
:hide-action="!canAccess('agencies', 'edit')" :hide-action="!canAccess('agencies', 'edit')"
@ -985,6 +1056,8 @@ watch(
v-model:form-bank-book="formData.bank" v-model:form-bank-book="formData.bank"
v-model:image-list-on-create="imageListOnCreate" v-model:image-list-on-create="imageListOnCreate"
v-model:deletes-status-qr-code-bank-imag="deletesStatusQrCodeBankImag" v-model:deletes-status-qr-code-bank-imag="deletesStatusQrCodeBankImag"
v-model:attachment="attachment"
:attachment-list="attachmentList"
/> />
</template> </template>
<style scoped> <style scoped>

View file

@ -314,7 +314,7 @@ function handleCheckAll() {
/> --> /> -->
<AvatarGroup <AvatarGroup
:data="[ :data="[
...responsiblePerson(props.row.quotation).user.map((v) => ({ ...(responsiblePerson(props.row.quotation)?.user.map((v) => ({
name: name:
$i18n.locale === 'eng' $i18n.locale === 'eng'
? `${v.firstNameEN} ${v.lastNameEN}` ? `${v.firstNameEN} ${v.lastNameEN}`
@ -324,11 +324,11 @@ function handleCheckAll() {
? `/no-img-man.png` ? `/no-img-man.png`
: `/no-img-female.png` : `/no-img-female.png`
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`, : `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
})), })) || []),
...responsiblePerson(props.row.quotation).group.map((g) => ({ ...(responsiblePerson(props.row.quotation)?.group.map((g) => ({
name: `${$t('general.group')} ${g.group}`, name: `${$t('general.group')} ${g.group}`,
imgUrl: '/img-group.png', imgUrl: '/img-group.png',
})), })) || []),
]" ]"
></AvatarGroup> ></AvatarGroup>
</q-td> </q-td>

View file

@ -96,7 +96,7 @@ const pageState = reactive({
mode: 'view' as 'view' | 'edit' | 'info', mode: 'view' as 'view' | 'edit' | 'info',
}); });
const defaultRemark = '#[quotation-labor]<br/><br/>#[quotation-payment]'; const defaultRemark = '';
const formData = ref<CreditNotePayload>({ const formData = ref<CreditNotePayload>({
quotationId: '', quotationId: '',
@ -798,16 +798,7 @@ onMounted(async () => {
:default-remark="defaultRemark" :default-remark="defaultRemark"
:items="[]" :items="[]"
:readonly="pageState.mode === 'info'" :readonly="pageState.mode === 'info'"
> ></RemarkExpansion>
<template #hint>
{{ $t('general.hintRemark') }}
<code>#[quotation-labor]</code>
{{ $t('general.quotationLabor') }}
{{ $t('general.or') }}
<code>#[quotation-payment]</code>
{{ $t('general.quotationPayment') }}
</template>
</RemarkExpansion>
<QuotationFormReceipt <QuotationFormReceipt
v-if="creditNoteData && view === CreditNoteStatus.Success" v-if="creditNoteData && view === CreditNoteStatus.Success"

View file

@ -218,7 +218,7 @@ const selectedWorkerItem = computed(() => {
employeeName: employeeName:
locale.value === Lang.English locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}` ? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName} ${e.lastName}`, : `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }), birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender, gender: e.gender,
age: calculateAge(e.dateOfBirth), age: calculateAge(e.dateOfBirth),
@ -235,11 +235,11 @@ const selectedWorkerItem = computed(() => {
})), })),
...newWorkerList.value.map((v: any) => ({ ...newWorkerList.value.map((v: any) => ({
foreignRefNo: v.passportNo, foreignRefNo: v.passportNo || '-',
employeeName: employeeName:
locale.value === Lang.English locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}` ? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName} ${v.lastName}`, : `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
birthDate: dateFormatJS({ date: v.dateOfBirth }), birthDate: dateFormatJS({ date: v.dateOfBirth }),
gender: v.gender, gender: v.gender,
age: calculateAge(v.dateOfBirth), age: calculateAge(v.dateOfBirth),
@ -815,7 +815,7 @@ async function submit() {
}), }),
...newWorkerList.value.map((v) => { ...newWorkerList.value.map((v) => {
const { attachment, ...payload } = v; const { attachment, ...payload } = v;
return payload; return pageState.mode === 'edit' ? payload.id : payload;
}), }),
]), ]),
), ),
@ -1046,7 +1046,11 @@ async function submitAccepted() {
:status-active="i.active?.()" :status-active="i.active?.()"
:status-done="i.status === 'done'" :status-done="i.status === 'done'"
:status-waiting="i.status === 'waiting'" :status-waiting="i.status === 'waiting'"
@click="i.handler()" @click="
() => {
if (pageState.mode !== 'create') i.handler();
}
"
/> />
</nav> </nav>
<!-- #TODO add goToQuotation as @goto-quotation--> <!-- #TODO add goToQuotation as @goto-quotation-->
@ -1134,10 +1138,10 @@ async function submitAccepted() {
view === QuotationStatus.Accepted view === QuotationStatus.Accepted
" "
:total-price="summaryPrice.finalPrice" :total-price="summaryPrice.finalPrice"
:pay-split="[]"
class="q-mb-md" class="q-mb-md"
v-model:pay-type="currentFormData.payCondition" v-model:pay-type="currentFormData.payCondition"
v-model:pay-split-count="currentFormData.paySplitCount" v-model:pay-split-count="currentFormData.paySplitCount"
v-model:pay-split="currentFormData.paySplit"
v-model:final-discount="currentFormData.discount" v-model:final-discount="currentFormData.discount"
v-model:pay-bill-date="currentFormData.payBillDate" v-model:pay-bill-date="currentFormData.payBillDate"
v-model:summary-price="summaryPrice" v-model:summary-price="summaryPrice"
@ -1192,7 +1196,6 @@ async function submitAccepted() {
" "
/> />
<!-- TODO: bind remark -->
<RemarkExpansion <RemarkExpansion
v-if=" v-if="
view === QuotationStatus.Issued || view === QuotationStatus.Issued ||
@ -1200,6 +1203,8 @@ async function submitAccepted() {
view === QuotationStatus.PaymentPending view === QuotationStatus.PaymentPending
" "
:readonly="readonly" :readonly="readonly"
:final-price="summaryPrice.finalPrice"
:selected-worker
v-model:remark="currentFormData.remark" v-model:remark="currentFormData.remark"
/> />

View file

@ -492,9 +492,8 @@ function print() {
v-html=" v-html="
convertTemplate(data?.remark || '', { convertTemplate(data?.remark || '', {
'quotation-payment': { 'quotation-payment': {
paymentType: data?.payCondition || 'Full', paymentType: 'Full',
amount: summaryPrice.finalPrice, amount: summaryPrice.finalPrice,
installments: data?.paySplit,
}, },
'quotation-labor': { 'quotation-labor': {
name: name:

View file

@ -1,9 +1,21 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import { convertTemplate } from 'src/utils/string-template';
import { RequestWork } from 'src/stores/request-list';
import { Employee } from 'src/stores/employee/types';
defineProps<{ defineProps<{
readonly?: boolean; readonly?: boolean;
items?: {
product: RequestWork['productService']['product'];
list: RequestWork[];
}[];
finalPrice?: number;
selectedWorker?: Employee[];
}>(); }>();
const remark = defineModel<string>('remark', { default: '' }); const remark = defineModel<string>('remark', { default: '' });
const remarkWrite = ref<boolean>(false);
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
@ -24,7 +36,23 @@ const remark = defineModel<string>('remark', { default: '' });
<q-editor <q-editor
dense dense
:readonly="readonly" :readonly="readonly"
:model-value="remark" :model-value="
!remarkWrite || readonly
? convertTemplate(remark || '', {
'quotation-payment': {
paymentType: 'Full',
amount: finalPrice,
},
'quotation-labor': {
name: selectedWorker.map(
(v, i) =>
`${i + 1}. ` +
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''} ${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
),
},
})
: remark || ''
"
min-height="5rem" min-height="5rem"
class="full-width" class="full-width"
toolbar-bg="input-border" toolbar-bg="input-border"
@ -48,7 +76,36 @@ const remark = defineModel<string>('remark', { default: '' });
remark = v; remark = v;
} }
" "
/> >
<template v-if="!readonly" v-slot:toggle>
<div class="text-caption row no-wrap q-px-sm">
<MainButton
:solid="!remarkWrite"
icon="mdi-eye-outline"
color="0 0% 40%"
@click="remarkWrite = false"
style="padding: 0 var(--size-2); cursor: pointer"
:style="{
color: remarkWrite ? 'hsl(0 0% 40%)' : undefined,
}"
>
{{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton>
<MainButton
:solid="remarkWrite"
icon="mdi-pencil-outline"
color="0 0% 40%"
@click="remarkWrite = true"
style="padding: 0 var(--size-2); cursor: pointer"
:style="{
color: !remarkWrite ? 'hsl(0 0% 40%)' : undefined,
}"
>
{{ $t('general.edit') }}
</MainButton>
</div>
</template>
</q-editor>
</main> </main>
</q-expansion-item> </q-expansion-item>
</template> </template>

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { AddButton } from 'components/button'; import { AddButton } from 'components/button';
import ToggleButton from 'src/components/button/ToggleButton.vue';
import WorkerItem from 'src/components/05_quotation/WorkerItem.vue'; import WorkerItem from 'src/components/05_quotation/WorkerItem.vue';
defineProps<{ defineProps<{
@ -48,8 +49,8 @@ const toggleWorker = defineModel<boolean>('toggleWorker');
{{ $t('quotation.employeeList') }} {{ $t('quotation.employeeList') }}
</span> </span>
<template v-if="!readonly"> <template v-if="!readonly">
<ToggleButton class="q-mr-sm" v-model="toggleWorker" /> <!-- <ToggleButton class="q-mr-sm" v-model="toggleWorker" />
{{ toggleWorker ? $t('general.specify') : $t('general.noSpecify') }} {{ toggleWorker ? $t('general.specify') : $t('general.noSpecify') }} -->
</template> </template>
</div> </div>
<nav class="q-ml-auto"> <nav class="q-ml-auto">

View file

@ -5,9 +5,11 @@ import { api } from 'src/boot/axios';
import { PaginationResult } from 'src/types'; import { PaginationResult } from 'src/types';
import useFlowStore from '../flow'; import useFlowStore from '../flow';
import { Status } from '../types'; import { Status } from '../types';
import { baseUrl, manageAttachment, manageFile, manageMeta } from '../utils';
export const useInstitution = defineStore('institution-store', () => { export const useInstitution = defineStore('institution-store', () => {
const flowStore = useFlowStore(); const flowStore = useFlowStore();
const attachmentManager = manageAttachment(api, 'institution');
const data = ref<Institution[]>([]); const data = ref<Institution[]>([]);
const page = ref<number>(1); const page = ref<number>(1);
@ -200,6 +202,7 @@ export const useInstitution = defineStore('institution-store', () => {
} }
return { return {
...attachmentManager,
data, data,
page, page,
pageMax, pageMax,

View file

@ -61,6 +61,14 @@ export type User = {
contactTel?: string; contactTel?: string;
remark?: string; remark?: string;
agencyStatus?: AgencyStatus; agencyStatus?: AgencyStatus;
addressForeign?: boolean;
provinceText?: string | null;
districtText?: string | null;
subDistrictText?: string | null;
provinceTextEN?: string | null;
districtTextEN?: string | null;
subDistrictTextEN?: string | null;
zipCodeText?: string | null;
}; };
export type UserCreate = { export type UserCreate = {
@ -113,6 +121,14 @@ export type UserCreate = {
contactTel?: string; contactTel?: string;
remark?: string; remark?: string;
agencyStatus?: AgencyStatus | string; agencyStatus?: AgencyStatus | string;
addressForeign?: boolean;
provinceText?: string | null;
districtText?: string | null;
subDistrictText?: string | null;
provinceTextEN?: string | null;
districtTextEN?: string | null;
subDistrictTextEN?: string | null;
zipCodeText?: string | null;
}; };
export enum AgencyStatus { export enum AgencyStatus {

View file

@ -14,6 +14,7 @@ export function formatAddress(opt: {
district?: District | null; district?: District | null;
subDistrict?: SubDistrict | null; subDistrict?: SubDistrict | null;
en?: boolean; en?: boolean;
zipCode?: string;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
let addressParts: string[]; let addressParts: string[];
@ -58,7 +59,9 @@ export function formatAddress(opt: {
// ); // );
} }
if (opt.subDistrict) addressParts.push(`${opt.subDistrict.zipCode}`); if (opt.subDistrict && !opt.zipCode)
addressParts.push(`${opt.subDistrict.zipCode}`);
if (opt.zipCode) addressParts.push(`${opt.zipCode}`);
return addressParts.join(' '); return addressParts.join(' ');
} }

View file

@ -74,7 +74,7 @@ const templates = {
const branch = v.request.quotation.customerBranch; const branch = v.request.quotation.customerBranch;
return ( return (
`${i + 1}. ` + `${i + 1}. ` +
` ${v.request.code}_${branch.customer.customerType === 'PERS' ? `นายจ้าง ${branch.namePrefix}. ${branch.firstNameEN} ${branch.lastNameEN} `.toUpperCase() : branch.registerName}_` + ` ${v.request.code}_${branch.customer.customerType === 'PERS' ? `${branch.namePrefix}. ${branch.firstNameEN} ${branch.lastNameEN} `.toUpperCase() : branch.registerName}_` +
`${employee.namePrefix}. ${employee.firstNameEN} ${employee.lastNameEN} `.toUpperCase() + `${employee.namePrefix}. ${employee.firstNameEN} ${employee.lastNameEN} `.toUpperCase() +
`${!!v.failedType && v.failedType !== 'other' ? `${i18n.global.t(`taskOrder.${v.failedType}`)}` : !!v.failedComment ? v.failedComment : ''}` `${!!v.failedType && v.failedType !== 'other' ? `${i18n.global.t(`taskOrder.${v.failedType}`)}` : !!v.failedComment ? v.failedComment : ''}`
); );