Compare commits

..

No commits in common. "develop" and "version-0.11.31" have entirely different histories.

159 changed files with 7526 additions and 11319 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

View file

@ -183,15 +183,15 @@
"prefix": [
{
"label": "MR",
"label": "Mr",
"value": "mr"
},
{
"label": "MRS",
"label": "Mrs",
"value": "mrs"
},
{
"label": "MISS",
"label": "Miss",
"value": "miss"
}
],

View file

@ -89,7 +89,15 @@ defineProps<{
</div>
</div>
</div>
<q-separator />
<div
style="
display: block;
width: 100%;
height: 1px;
background: hsla(0 0% 0% / 0.1);
margin-bottom: var(--size-2);
"
/>
<slot name="data"></slot>
<template v-if="!$slots.data">
<div

View file

@ -159,6 +159,42 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
]"
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 class="col-12 row q-col-gutter-sm">

View file

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

View file

@ -253,13 +253,16 @@ watch(
hide-bottom-space
:readonly="readonly"
:label="$t('form.email')"
:rules="[
(val) => (val && val.length > 0) || $t('form.error.required'),
(v: string) =>
!v ||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
$t('form.error.invalid'),
]"
:rules="
readonly
? undefined
: [
(v: string) =>
!v ||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
$t('form.error.invalid'),
]
"
class="col-md-3 col-6"
:model-value="readonly ? email || '-' : email"
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
@ -293,11 +296,15 @@ watch(
:readonly="readonly"
:label="$t('form.birthDate')"
:disabled-dates="disabledAfterToday"
:rules="[
(val: string) =>
!!val ||
$t('form.error.selectField', { field: $t('form.birthDate') }),
]"
:rules="
employee
? []
: [
(val: string) =>
!!val ||
$t('form.error.selectField', { field: $t('form.birthDate') }),
]
"
/>
<q-input

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -268,7 +268,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
<div class="col-md col-6">
<DatePicker
:label="$t('customerEmployee.formHealthCheck.coverageStartDate')"
v-model="checkup.coverageStartDate"
:id="`${prefixId}-input-coverage-start-date`"
:readonly="readonly || checkup.statusSave"

View file

@ -9,8 +9,6 @@ import useOptionStore from 'stores/options';
import DatePicker from '../shared/DatePicker.vue';
import { dateFormat } from 'src/utils/datetime';
const optionStore = useOptionStore();
const { locale } = useI18n();
@ -20,8 +18,8 @@ const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('issueDate');
const type = defineModel<string>('type');
const expireDate = defineModel<Date | string>('expireDate');
const birthDate = defineModel<Date | string>('birthDate');
const expireDate = defineModel<Date>('expireDate');
const birthDate = defineModel<Date>('birthDate');
const workerStatus = defineModel<string>('workerStatus');
const nationality = defineModel<string>('nationality');
const gender = defineModel<string>('gender');
@ -34,8 +32,6 @@ const firstName = defineModel<string>('firstName');
const namePrefix = defineModel<string>('namePrefix');
const passportNumber = defineModel<string>('passportNumber');
const file = defineModel<File>('file');
const passportValidator = /[a-zA-Z]{1}[a-zA-Z0-9]{1}[0-9]{5,7}$/;
const genderOptions = ref<Record<string, unknown>[]>([]);
@ -181,30 +177,6 @@ watch(
},
);
function browse() {
inputFile?.click();
}
const inputFile = (() => {
const _element = document.createElement('input');
_element.type = 'file';
_element.accept = 'image/jpeg,image/png';
_element.addEventListener('change', change);
return _element;
})();
async function change(e: Event) {
const _element = e.target as HTMLInputElement | null;
const _file = _element?.files?.[0];
if (_file) {
const newFileName = `passport-${dateFormat(new Date().toISOString())}-${_file.name}`;
const renamedFile = new File([_file], newFileName, { type: _file.type });
file.value = renamedFile;
}
}
watch(
() => namePrefix.value,
(v) => {
@ -249,14 +221,20 @@ watch(
</div>
<div class="q-col-gutter-sm" :class="{ row: $q.screen.gt.sm }">
<div v-if="!ocr">
<q-btn
flat
color="primary"
icon="mdi-upload-box-outline"
@click="() => browse()"
:disable="readonly"
></q-btn>
<div
class="col row justify-center q-col-gutter-sml"
style="max-height: 50%"
v-if="!ocr"
>
<q-avatar
style="border: 1px dashed; border-color: black"
square
size="100px"
font-size="50px"
color="grey-4"
text-color="grey"
icon="mdi-image-outline"
/>
</div>
<div
class="row q-col-gutter-sm"

View file

@ -28,22 +28,20 @@ const arrivalAt = defineModel<string>('arrivalAt');
const arrivalTMNo = defineModel<string>('arrivalTmNo');
const arrivalTM = defineModel<string>('arrivalTm');
const mrz = defineModel<string>('mrz');
const entryCount = defineModel<number | string>('entryCount');
const entryCount = defineModel<number>('entryCount');
const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('visaIssueDate');
const type = defineModel<string>('type');
const expireDate = defineModel<Date | string>('expireDate');
const expireDate = defineModel<Date>('expireDate');
const remark = defineModel<string>('remark');
const workerType = defineModel<string>('workerType');
const number = defineModel<string>('number');
const reportDate = defineModel<Date | null | string>('reportDate');
//
// const calculatedVisaDate = computed(() => {
// if (!issueDate.value) return undefined;
// return calculate90DayNext(issueDate.value);
// });
const calculatedVisaDate = computed(() => {
if (!issueDate.value) return undefined;
return calculate90DayNext(issueDate.value);
});
defineProps<{
title?: string;
@ -140,10 +138,6 @@ watch(
);
},
);
//
// watch([() => issueDate.value], () => {
// reportDate.value = calculate90DayNext(issueDate.value);
// });
</script>
<template>
@ -157,7 +151,7 @@ watch(
name="mdi-passport"
style="background-color: var(--surface-3)"
/>
{{ $t(title) }}
{{ title }}
</div>
<div
@ -377,12 +371,10 @@ watch(
<DatePicker
:id="`${prefixId}-date-picker-visa-issuance`"
:readonly
:disabled="!readonly"
:label="$t('customerEmployee.form.visa90Day')"
v-model="reportDate"
:model-value="calculatedVisaDate"
clearable
:rules="[
(val) => (val && val.length > 0) || $t('form.error.required'),
]"
/>
</div>
</div>

View file

@ -22,8 +22,6 @@ const prop = withDefaults(
inTable?: boolean;
addButton?: boolean;
prefixId?: string;
hideAction?: boolean;
hideDelete?: boolean;
}>(),
{
gridView: false,
@ -141,9 +139,8 @@ defineEmits<{
<q-avatar size="md">
<q-img
:src="
props.row.selectedImage
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
: `/images/employee-avatar-${props.row.gender}.png`
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
`/images/employee-avatar-${props.row.gender}.png`
"
class="text-center"
:ratio="1"
@ -268,10 +265,9 @@ defineEmits<{
@click.stop="$emit('view', props.row)"
/>
<KebabAction
v-if="!inTable && !hideAction"
v-if="!inTable"
:id-name="props.row.firstName"
:status="props.row.status"
:hide-delete="hideDelete"
@view="$emit('view', props.row)"
@edit="$emit('edit', props.row)"
@delete="$emit('delete', props.row)"
@ -284,11 +280,9 @@ defineEmits<{
<template v-slot:item="props">
<div class="col-12 col-md-3 col-sm-6">
<PersonCard
history
:hide-delete="hideDelete"
:hide-action="hideAction"
:id="`card-${props.row.firstNameEN}`"
:field-selected="fieldSelected"
history
:prefix-id="props.row.firstNameEN ?? props.rowIndex"
:data="{
code: props.row.code,
@ -296,9 +290,9 @@ defineEmits<{
$i18n.locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
: `${props.row.firstName} ${props.row.lastName} `.trim(),
img: props.row.selectedImage
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
: `/images/employee-avatar-${props.row.gender}.png`,
img:
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
`/images/employee-avatar-${props.row.gender}.png`,
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
male: props.row.gender === 'male',
female: props.row.gender === 'female',

View file

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

View file

@ -27,7 +27,6 @@ import { QField } from 'quasar';
defineProps<{
readonly?: boolean;
onDrawer?: boolean;
hideAction?: boolean;
}>();
const { t } = useI18n();
@ -202,7 +201,6 @@ onMounted(async () => {
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
>
<ToggleButton
:disable="hideAction"
class="q-mr-sm"
two-way
:model-value="flowData.status !== 'INACTIVE'"
@ -611,7 +609,7 @@ onMounted(async () => {
</span>
</q-item>
</template>
<template v-if="!readonly" #append>
<template #append>
<q-icon
name="mdi-menu-down"
:class="{ rotated: responsibleMenu }"

View file

@ -167,28 +167,26 @@ withDefaults(
<div class="col-12 full-width">
<q-table
:rows-per-page-options="[0]"
:rows="
[
priceDisplay.price && {
label: $t('productService.product.salePrice'),
pricePerUnit: price,
calcVat,
vatIncluded,
},
priceDisplay.agentPrice && {
label: $t('productService.product.agentPrice'),
calcVat: agentPriceCalcVat,
vatIncluded: agentPriceVatIncluded,
pricePerUnit: agentPrice,
},
priceDisplay.serviceCharge && {
label: $t('productService.product.processingPrice'),
calcVat: serviceChargeCalcVat,
vatIncluded: serviceChargeVatIncluded,
pricePerUnit: serviceCharge,
},
].filter(Boolean)
"
:rows="[
{
label: $t('productService.product.salePrice'),
pricePerUnit: price,
calcVat,
vatIncluded,
},
{
label: $t('productService.product.agentPrice'),
calcVat: agentPriceCalcVat,
vatIncluded: agentPriceVatIncluded,
pricePerUnit: agentPrice,
},
{
label: $t('productService.product.processingPrice'),
calcVat: serviceChargeCalcVat,
vatIncluded: serviceChargeVatIncluded,
pricePerUnit: serviceCharge,
},
]"
:columns
hide-bottom
bordered

View file

@ -48,7 +48,6 @@ defineProps<{
readonly?: boolean;
onDrawer?: boolean;
inputOnly?: boolean;
disableToggle?: boolean;
}>();
defineEmits<{
@ -77,7 +76,6 @@ defineEmits<{
<ToggleButton
class="q-mr-sm"
two-way
:disable="disableToggle"
:model-value="status !== 'INACTIVE'"
@click="
() => {
@ -197,8 +195,8 @@ defineEmits<{
}
:deep(
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
justify-content: start !important;
padding-right: 8px !important;
padding-top: 16px;
@ -210,15 +208,15 @@ defineEmits<{
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}
:deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper
) {
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper
) {
visibility: hidden;
}

View file

@ -2,18 +2,11 @@
import SelectCustomer from '../shared/select/SelectCustomer.vue';
import SelectBranch from '../shared/select/SelectBranch.vue';
import { CustomerBranch } from 'src/stores/customer';
import { ref } from 'vue';
const branchId = defineModel<string>('branchId');
const customerBranchId = defineModel<string>('customerBranchId');
const agentPrice = defineModel<boolean>('agentPrice');
const special = defineModel<boolean>('special');
const customerBranchOption = defineModel<CustomerBranch>(
'customerBranchOption',
);
defineProps<{
outlined?: boolean;
readonly?: boolean;
@ -74,12 +67,8 @@ defineEmits<{
required
:readonly
/>
<SelectCustomer
id="about-select-customer-branch-id"
for="about-select-customer-branch-id"
v-model:value="customerBranchId"
v-model:value-option="customerBranchOption"
:label="$t('quotation.customer')"
:creatable-disabled-text="`(${$t('form.error.selectField', {
field: $t('quotation.branchVirtual'),
@ -99,14 +88,14 @@ defineEmits<{
<style scoped>
: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;
}
: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;
}
</style>

View file

@ -58,15 +58,16 @@ const currentBtnOpen = ref<{ title: string; opened: boolean[] }[]>([
function calcPrice(c: (typeof rows.value)[number]) {
const originalPrice = c.pricePerUnit;
const finalPricePerUnit = precisionRound(
originalPrice +
(c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? originalPrice * (config.value?.vat || 0.07)
: 0),
const finalPriceWithVat = precisionRound(
originalPrice + originalPrice * (config.value?.vat || 0.07),
);
const price = finalPricePerUnit * c.amount - c.discount;
const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
return precisionRound(price);
const price = finalPriceNoVat * c.amount - c.discount;
const vat = c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? (finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07)
: 0;
return precisionRound(price + vat);
}
const discount4Show = ref<string[]>([]);
@ -434,20 +435,8 @@ watch(
<q-td align="right">
{{
formatNumberDecimal(
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,
),
props.row.pricePerUnit * props.row.amount -
props.row.discount,
2,
)
}}
@ -459,12 +448,9 @@ watch(
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
]
? precisionRound(
((props.row.pricePerUnit *
(1 + (config?.vat || 0.07)) *
props.row.amount -
props.row.discount) /
(1 + (config?.vat || 0.07))) *
0.07,
(props.row.pricePerUnit * props.row.amount -
props.row.discount) *
(config?.vat || 0.07),
)
: 0,
2,

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { QTableProps } from 'quasar';
import { dateFormat, dateFormatJS } from 'src/utils/datetime';
import { dateFormat } from 'src/utils/datetime';
import { formatNumberDecimal } from 'stores/utils';
@ -19,8 +19,6 @@ const props = withDefaults(
page?: number;
pageSize?: number;
hideBtnPreview?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
}>(),
{
row: () => [],
@ -86,11 +84,11 @@ defineEmits<{
</q-td>
<q-td v-if="visibleColumns.includes('createdAt')">
{{ dateFormatJS({ date: props.row.createdAt }) }}
{{ dateFormat(props.row.createdAt) }}
</q-td>
<q-td v-if="visibleColumns.includes('dueDate')">
{{ dateFormatJS({ date: props.row.dueDate }) }}
{{ dateFormat(props.row.dueDate) }}
</q-td>
<q-td v-if="visibleColumns.includes('contactName')">
@ -149,12 +147,12 @@ defineEmits<{
flat
@click.stop="$emit('view', props.row)"
/>
<KebabAction
v-if="!hideAction"
:idName="`btn-kebab-${props.row.workName}`"
status="'ACTIVE'"
hide-toggle
:hide-delete
hide-delete
:hide-edit="hideEdit"
@view="$emit('view', props.row)"
@edit="$emit('edit', props.row)"

View file

@ -1,10 +1,6 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SelectInput from '../shared/SelectInput.vue';
import useOptionStore from 'src/stores/options';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useInstitution } from 'src/stores/institution';
const optionStore = useOptionStore();
@ -14,11 +10,6 @@ defineProps<{
readonly?: boolean;
onDrawer?: boolean;
}>();
const emit = defineEmits<{
(e: 'deleteAttachment', name: string): void;
}>();
const attachmentRef = ref();
const group = defineModel('group', { default: '' });
const name = defineModel('name', { default: '' });
@ -26,19 +17,8 @@ const nameEn = defineModel('nameEn', { default: '' });
const contactName = defineModel('contactName', { default: '' });
const email = defineModel('email', { 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 };
function openNewTab(url: string) {
window.open(url, '_blank');
}
function deleteAttachment(name: string) {
emit('deleteAttachment', name);
}
</script>
<template>
<div class="row col-12">
@ -182,78 +162,6 @@ function deleteAttachment(name: string) {
/>
</template>
</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>
</template>

View file

@ -27,38 +27,26 @@ withDefaults(
class="app-text-muted q-pr-sm"
:width="iconSize || '2rem'"
/>
<span :id="`dd-wrapper-${label}`" class="row col">
<span
:id="`dd-label-${label}`"
class="col-12 app-text-muted-2"
style="font-size: 10px"
>
<span class="row col">
<span class="col-12 app-text-muted-2" style="font-size: 10px">
{{ label }}
</span>
<span :id="`dd-value-wrapper-${label}`" class="col-12 ellipsis">
<span class="col-12 ellipsis">
<slot name="value">
<span
:class="{ 'link cursor-pointer': clickable }"
v-if="typeof value === 'string'"
@click="clickable ? $emit('labelClick', value, null) : undefined"
:id="`link-${value}`"
:for="`link-${value}`"
@click="$emit('labelClick', value, null)"
>
{{ value }}
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
</span>
<span
:id="`dd-value-${label}`"
v-else
:class="{ 'link cursor-pointer': clickable }"
>
<span v-else :class="{ 'link cursor-pointer': clickable }">
<span
v-for="(item, index) in value"
:key="index"
@click="$emit('labelClick', item, index)"
class="link cursor-pointer"
:id="`link-${item}`"
:for="`link-${item}`"
>
{{ item }}
<span v-if="index < value.length - 1">,&nbsp;</span>

View file

@ -8,10 +8,6 @@ defineProps<{
const quotationId = defineModel<string>('quotationId', {
required: true,
});
const isDebitNote = defineModel<boolean>('isDebitNote', {
required: false,
default: false,
});
</script>
<template>
<div class="row col-12">
@ -41,7 +37,6 @@ const isDebitNote = defineModel<boolean>('isDebitNote', {
cancelIncludeDebitNote: true,
hasCancel: true,
}"
@selected="(v) => (isDebitNote = v.isDebitNote)"
/>
</section>
</div>

View file

@ -8,7 +8,7 @@ import {
UndoButton,
} from 'components/button';
const props = withDefaults(
withDefaults(
defineProps<{
title: string;
category?: string;
@ -42,11 +42,6 @@ const drawerOpen = defineModel<boolean>('drawerOpen', {
const myForm = ref();
function reset() {
if (props.beforeClose) {
drawerOpen.value = props.beforeClose
? props.beforeClose()
: !drawerOpen.value;
}
if (myForm.value) {
myForm.value.resetValidation();
}
@ -67,6 +62,7 @@ async function onValidationError(ref: any) {
@show="show"
@before-hide="reset"
@hide="close"
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
v-model="drawerOpen"
behavior="mobile"

View file

@ -46,7 +46,6 @@ defineProps<{
color="grey"
icon="mdi-close"
v-close-popup
@click="cancel"
/>
</div>

View file

@ -1,12 +1,5 @@
<script setup lang="ts">
const pageSize = defineModel<number>({ required: true });
withDefaults(
defineProps<{
fetchData?: (...args: unknown[]) => void;
}>(),
{},
);
</script>
<template>
@ -17,12 +10,7 @@ withDefaults(
:key="v"
clickable
v-close-popup
@click="
() => {
pageSize = v;
fetchData();
}
"
@click="pageSize = v"
>
<q-item-section>
<q-item-label>{{ v }}</q-item-label>

View file

@ -229,7 +229,6 @@ const smallBanner = ref(false);
<ToggleButton
v-if="useToggle"
:disable="readonly"
two-way
:model-value="toggleStatus !== 'INACTIVE'"
@click="$emit('update:toggleStatus', toggleStatus)"

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { BranchWithChildren } from 'stores/branch/types';
import KebabAction from './shared/KebabAction.vue';
import { isRoleInclude } from 'stores/utils';
const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', {
default: [],
@ -16,7 +17,6 @@ withDefaults(
labelKey?: string;
childrenKey: string;
action?: boolean;
hideCreate?: boolean;
}>(),
{
color: 'transparent',
@ -96,9 +96,7 @@ defineEmits<{
expandedTree[expandedTree.length - 1] === node.id,
}"
>
{{
$i18n.locale === 'eng' ? node.nameEN || node.name : node.name
}}
{{ node.name }}
</span>
<span class="app-text-muted text-caption ellipsis">
{{ node.code }}
@ -122,7 +120,11 @@ defineEmits<{
/>
<q-btn
v-if="node.isHeadOffice && typeTree === 'branch' && !hideCreate"
v-if="
node.isHeadOffice &&
typeTree === 'branch' &&
isRoleInclude(['head_of_admin', 'admin', 'system'])
"
:id="`create-sub-branch-btn-${node.name}`"
@click.stop="$emit('create', node)"
icon="mdi-file-plus-outline"

View file

@ -5,7 +5,6 @@ defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
id?: string;
icon?: string;
color: string;
iconOnly?: boolean;
@ -19,7 +18,6 @@ defineProps<{
<template>
<button
:id="id"
@click="(e) => $emit('click', e)"
class="main-btn"
:class="{

View file

@ -10,7 +10,6 @@ defineProps<{
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
color?: string;
label?: string;
icon?: string;
@ -24,7 +23,7 @@ defineProps<{
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-content-save-outline'"
:color="color || '207 96% 32%'"
color="207 96% 32%"
:title="iconOnly ? $t('general.save') : undefined"
>
{{ label || $t('general.save') }}

View file

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

View file

@ -16,4 +16,3 @@ export { default as SideMenu } from './SideMenu.vue';
export { default as StatCardComponent } from './StatCardComponent.vue';
export { default as TooltipComponent } from './TooltipComponent.vue';
export { default as TreeComponent } from './TreeComponent.vue';
export { default as PaginationPageSize } from './PaginationPageSize.vue';

View file

@ -106,7 +106,6 @@ watch(
:persistent="isDateSelect"
>
<div class="q-pa-sm">
<slot name="prepend"></slot>
<div class="text-weight-medium">
{{ $t('general.advanceSearch') }}
</div>

View file

@ -23,8 +23,6 @@ defineProps<{
history?: boolean;
prefixId?: string;
separateEnter?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
}>();
defineEmits<{
@ -78,10 +76,8 @@ defineEmits<{
/>
<KebabAction
v-if="!hideAction"
:id-name="prefixId"
:status="disabled ? 'INACTIVE' : 'ACTIVE'"
:hide-delete="hideDelete"
@view="
separateEnter
? $emit('viewCard', 'INFO')

View file

@ -13,7 +13,6 @@ let defaultFilter: (
const props = withDefaults(
defineProps<{
prefix?: string;
id?: string;
label?: string;
option: T[];
@ -72,7 +71,6 @@ watch(
</script>
<template>
<q-select
:id="id"
:placeholder="placeholder"
outlined
:clearable

View file

@ -75,9 +75,9 @@ function setDefaultValue() {
</script>
<template>
<SelectInput
for="select-hq-id"
v-model="value"
incremental
id="select-hq-id"
:label
:placeholder
:readonly

View file

@ -1,213 +0,0 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { createSelect, SelectProps } from './select';
import SelectInput from '../SelectInput.vue';
import { BusinessType } from 'src/stores/business-type/types';
import useStore from 'src/stores/business-type';
type SelectOption = BusinessType;
const value = defineModel<string | null | undefined>('value', {
required: true,
});
const valueOption = defineModel<SelectOption>('valueOption', {
required: false,
});
const selectOptions = ref<SelectOption[]>([]);
const { fetchList: getList, fetchById: getById } = useStore();
defineEmits<{
(e: 'create'): void;
}>();
type ExclusiveProps = {
lang?: string;
codeOnly?: boolean;
selectFirstValue?: boolean;
branchVirtual?: boolean;
checkRole?: string[];
};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
const { getOptions, setFirstValue, getSelectedOption, filter } =
createSelect<SelectOption>(
{
value,
valueOption,
selectOptions,
getList: async (query) => {
const ret = await getList({
query,
...props.params,
pageSize: 99999,
});
if (ret) return ret.result;
},
getByValue: async (id) => {
const ret = await getById(id);
if (ret) return ret;
},
},
{ valueField: 'id' },
);
onMounted(async () => {
await getOptions();
if (props.autoSelectOnSingle && selectOptions.value.length === 1) {
setFirstValue();
}
if (props.selectFirstValue) {
setDefaultValue();
} else await getSelectedOption();
});
function setDefaultValue() {
setFirstValue();
}
</script>
<template>
<SelectInput
v-model="value"
incremental
option-value="id"
:label="label || $t('menu.manage.businessType')"
:placeholder
:readonly
:disable="disabled"
:option="selectOptions"
:hide-selected="false"
:fill-input="false"
:rules="[
(v: string) => !props.required || !!v || $t('form.error.required'),
]"
@filter="filter"
>
<template #selected-item="{ opt }">
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
</template>
<template #no-option v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('businessType.title') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #before-options v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
for="select-biz-type-add-new"
id="select-biz-type-add-new"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('businessType.title') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #before-options v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
for="select-business-type-add-new"
id="select-business-type-add-new"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('menu.manage.businessType') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #option="{ opt, scope }">
<q-item v-bind="scope.itemProps">
<span class="row items-center">
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
</span>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #append v-if="clearable">
<q-icon
v-if="!readonly && value"
name="mdi-close-circle"
@click.stop="value = ''"
class="cursor-pointer clear-btn"
/>
</template>
</SelectInput>
</template>

View file

@ -30,7 +30,6 @@ defineEmits<{
type ExclusiveProps = {
simple?: boolean;
simpleBranchNo?: boolean;
selectFirstValue?: boolean;
};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -65,14 +64,10 @@ onMounted(async () => {
setFirstValue();
}
if (props.selectFirstValue) {
setDefaultValue();
} else await getSelectedOption();
});
await getSelectedOption();
function setDefaultValue() {
setFirstValue();
}
valueOption.value = selectOptions.value.find((v) => v.id === value.value);
});
</script>
<template>
<SelectInput
@ -165,9 +160,11 @@ function setDefaultValue() {
</template>
<template #option="{ opt, scope }">
<q-item v-bind="scope.itemProps" class="q-mx-sm bodrder">
<q-item @click="valueOption = opt" v-bind="scope.itemProps">
<SelectCustomerItem :data="opt" :simple :simple-branch-no />
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #append v-if="clearable">
@ -180,11 +177,3 @@ function setDefaultValue() {
</template>
</SelectInput>
</template>
<style scoped>
.bodrder {
border-bottom: solid;
border-bottom-width: 1px;
border-color: var(--border-color);
}
</style>

View file

@ -25,7 +25,6 @@ const { getQuotationList: getList, getQuotation: getById } = useStore();
defineEmits<{
(e: 'create'): void;
(e: 'selected', value: SelectOption): void;
}>();
type ExclusiveProps = {
@ -118,14 +117,6 @@ function setDefaultValue() {
(v: string) => !props.required || !!v || $t('form.error.required'),
]"
@filter="filter"
@update:model-value="
(v) => {
$emit(
'selected',
selectOptions.find((opt) => opt.id === v),
);
}
"
>
<template #append v-if="clearable">
<q-icon

View file

@ -26,7 +26,6 @@ defineEmits<{
type ExclusiveProps = {
selectFirstValue?: boolean;
prefix?: string;
};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -72,7 +71,6 @@ function setDefaultValue() {
<SelectInput
v-model="value"
incremental
:id="`${prefix || 'nome'}-select-user`"
:label
:placeholder
:readonly
@ -94,9 +92,7 @@ function setDefaultValue() {
:hide-selected="false"
:fill-input="false"
:rules="
required && !readonly
? [(v: string) => !!v || $t('form.error.required')]
: undefined
required ? [(v: string) => !!v || $t('form.error.required')] : undefined
"
@filter="filter"
>

View file

@ -35,13 +35,7 @@ export const createSelect = <T extends Record<string, any>>(
let previousSearch = '';
watch(value, (v) => {
if (!v) return;
if (cache && cache.find((opt) => opt[valueField] === v)) {
valueOption.value = cache.find((opt) => opt[valueField] === v);
return;
}
if (!v || (cache && cache.find((opt) => opt[valueField] === v))) return;
getSelectedOption();
});
@ -69,26 +63,15 @@ export const createSelect = <T extends Record<string, any>>(
const currentValue = value.value;
if (!currentValue) return;
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
if (valueOption.value && valueOption.value[valueField] === currentValue) {
selectOptions.value.unshift(valueOption.value);
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
return (
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
);
});
return;
return selectOptions.value.unshift(valueOption.value);
}
const ret = await getByValue(currentValue);
if (ret) {
selectOptions.value.unshift(ret);
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
return (
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
);
});
valueOption.value = ret;
}
}

View file

@ -251,10 +251,7 @@ function selectedIndex(item: any) {
>
<!-- NOTE: custom column will starts with # -->
<template v-if="!col.name.startsWith('#')">
<span v-if="col.name === 'serviceDetail'">
{{ props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') || '-' }}
</span>
<span v-else>
<span>
{{
typeof col.field === 'string'
? props.row[col.field as keyof (Product | Service)]

View file

@ -145,7 +145,6 @@ function selectedIndex(item: Employee) {
<template v-if="col.name === '#check'">
<q-checkbox
id="select-worker-all"
for="select-worker-all"
v-model="props.selected"
@update:model-value="(v) => handleUpdate()"
size="sm"
@ -201,7 +200,6 @@ function selectedIndex(item: Employee) {
v-model="props.selected"
size="sm"
:id="`select-worker-${props.row.firstName}`"
:for="`select-worker-${props.row.firstName}`"
/>
</template>
</q-td>

View file

@ -188,7 +188,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.citizenId')"
for="input-citizen-id"
v-model="citizenId"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<DatePicker
@ -222,7 +221,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.religion')"
for="input-religion"
v-model="religion"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-select
outlined
@ -307,7 +305,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.firstName')"
for="input-first-name"
v-model="firstName"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input
@ -319,7 +316,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.lastName')"
for="input-last-name"
v-model="lastName"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input

View file

@ -22,7 +22,6 @@ type Props = {
autoSave?: boolean;
data?: Data;
hideBtn?: boolean;
disabledSubmit?: boolean;
};
type HandleProps = {
@ -110,7 +109,6 @@ async function change(e: Event) {
hide-delete
hide-btn
edit
:disabledSubmit
:title
:is-edit
:readonly

View file

@ -31,7 +31,7 @@ const currentIndexDropdownList = ref(0);
const props = withDefaults(
defineProps<{
treeFile?: { label: string; file: { label: string }[] }[];
treeFile: { label: string; file: { label: string }[] }[];
readonly?: boolean;
dropdownList?: { label: string; value: string }[];
hideAction?: boolean;

View file

@ -54,11 +54,10 @@ onMounted(() => {
@click="$emit('click')"
>
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
<div class="col column q-pl-md">
<div class="ellipsis full-width" style="max-width: 65vw !important">
<article class="col column q-pl-md">
<span class="ellipsis full-width">
{{ name }}
</div>
</span>
<span class="text-caption app-text-muted-2">
{{
uploading.loaded
@ -80,7 +79,7 @@ onMounted(() => {
/>
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
</span>
</div>
</article>
<q-btn
v-if="closeable"
icon="mdi-close"

View file

@ -45,9 +45,9 @@ const props = withDefaults(
readonly?: boolean;
showTitle?: boolean;
ocr?: (
group: string,
group: any,
file: File,
) => Promise<{
) => void | Promise<{
status: boolean;
group: string;
meta: { name: string; value: string }[];
@ -123,7 +123,7 @@ async function change(e: Event) {
...obj.value,
{
_meta: structuredClone(toRaw(selectedMenu.value)._meta || {}),
group: selectedMenu.value?.group,
group: selectedMenu.value?.value,
file: renamedFile,
},
];
@ -168,8 +168,8 @@ async function change(e: Event) {
type: map['doc_type'],
number: map['doc_number'],
gender: map['sex'],
firstName: map['last_name'],
lastName: map['first_name'],
firstName: map['first_name'],
lastName: map['last_name'],
issueDate: map['issue_date'],
expireDate: map['expire_date'],
issuePlace: map['nationality'],
@ -327,7 +327,7 @@ defineEmits<{
:rows="
obj
.filter((v) => {
if (!autoSave && v.group !== selectedMenu?.group) {
if (!autoSave && v.group !== selectedMenu?.value) {
return false;
}
return true;

View file

@ -198,10 +198,3 @@ i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-i
.q-focus-helper {
visibility: hidden;
}
.clear-btn {
opacity: 0.6;
&:hover {
opacity: 1;
}
}

View file

@ -4,7 +4,7 @@ export default {
save: 'Save',
open: 'Open',
close: 'Close',
edit: 'Edit{text}',
edit: 'Edit',
cancel: 'Cancel',
back: 'Back',
undo: 'Undo',
@ -31,7 +31,6 @@ export default {
displayField: 'Display Fields',
order: 'Order',
name: '{msg} Name',
nameEN: 'Name (English)',
fullName: 'Full Name',
detail: '{msg} Detail',
remark: '{msg} Remark',
@ -161,7 +160,6 @@ export default {
documentStatus: 'Document Status',
advanceSearch: 'Advance Search',
totalPeople: '{meg} people',
price: 'Price {price} Baht',
},
menu: {
@ -204,14 +202,12 @@ export default {
title: 'Manage',
branch: 'Branch',
personnel: 'Personnel',
group: 'Group',
productService: 'Product and Service',
workflow: 'Workflow',
property: 'Property',
customer: 'Customer',
mainData: 'Main Data',
agencies: 'Agencies',
businessType: 'Business Type',
},
sales: {
@ -341,7 +337,7 @@ export default {
requireLength: 'Please enter {msg} character',
branchNameField: "Only letters, numbers, or the characters . , - ' &.",
branchNameENField:
"Only English letters, numbers, or the characters . , - ' &. ( )",
"Only English letters, numbers, or the characters . , - ' &.",
passportFormat: 'Please enter the passport number in the correct format.',
},
warning: {
@ -460,7 +456,7 @@ export default {
regisNo: 'Registration Number',
startDate: 'Start Date',
retireDate: 'Retire Date',
responsibleArea: 'Responsible Area',
responsibleArea: 'Responsibel Area',
discount: 'Discount Condition',
sourceNationality: 'Source Nationality',
importNationality: 'Import Nationality',
@ -477,7 +473,6 @@ export default {
blacklist: 'Black list',
contactName: 'Contact Person',
contactTel: 'Contact Number',
addressForeign: 'Use foreign address',
},
},
customer: {
@ -493,7 +488,7 @@ export default {
},
employer: 'Employer',
employerLegalEntity: 'Legal Entity',
employerNaturalPerson: 'Natural Person',
employerNaturalPerson: 'Natrual Person',
employerType: 'Employer Type',
employee: 'Employee',
form: {
@ -503,12 +498,11 @@ export default {
},
prefix: {
mr: 'MR.',
mrs: 'MRS.',
miss: 'MISS.',
mr: 'Mr.',
mrs: 'Mrs.',
miss: 'Miss.',
},
taxpayyerNo: 'Taxpayer Identification Number',
citizenId: 'Citizen ID',
religion: 'Religion',
issueDate: 'Issue Date',
@ -624,7 +618,7 @@ export default {
placeOfBirth: 'Place of Birth',
countryOfbirth: 'Country of Birth',
issueCountry: 'Issue Country',
entryCount: 'Number of Days in the Country',
entryCount: 'Entry Count',
employerSelect: {
branchName: 'Branch Name',
customerName: 'Employer Name',
@ -774,13 +768,10 @@ export default {
},
quotation: {
ownOnly: 'View Own Quotation Only',
quotationDate: 'Quotation Date',
seller: 'Seller',
paymentChannels: 'Payment Channels',
channelsThat: 'Channels That',
refNo: 'Reference Number',
bankAccount: 'Bank Account',
bankAccountNumber: 'Bank Account Number',
bankAccountName: 'Bank Account Name',
inTheNameOf: 'In The Name Of',
@ -932,7 +923,6 @@ export default {
contactName: 'Contact Person',
contactTel: 'Contact Number',
bankInfo: 'Bank Information',
attachment: 'Attachment',
},
requestList: {
@ -1127,7 +1117,7 @@ export default {
oneOrMoreBranchMissing:
'One or more branch cannot be delete and is missing.',
cantMakeHQAndBranchSameTime:
'Cannot make this as headquarters and branch at the same time.',
'Cannot make this as headquaters and branch at the same time.',
unknowHowToVerify: 'Unknown how to verify identity.',
noPermission:
'You do not have permission to access or perform with this resource.',
@ -1234,9 +1224,6 @@ export default {
taskListNotPending: 'One or more task is not pending.',
reqNotMet: 'Not Match',
systemError: 'A system error occurred.',
taskOrderInvalid: 'Please select the product and the organization.',
flowAccountProductIdNotFound: 'Product not found in flow account',
},
},
@ -1521,11 +1508,4 @@ export default {
last90Days: 'Last 90 Days',
customDateRange: 'Custom Date Range',
},
businessType: {
title: 'Business Type',
caption: 'Manage Business Type',
name: 'Business Type Name',
nameEn: 'Business Type Name (English)',
},
};

View file

@ -4,7 +4,7 @@ export default {
save: 'บันทึก',
open: 'เปิด',
close: 'ปิด',
edit: 'แก้ไข{text}',
edit: 'แก้ไข',
cancel: 'ยกเลิก',
back: 'ย้อนกลับ',
undo: 'ย้อนกลับ',
@ -31,7 +31,6 @@ export default {
displayField: 'ฟิลด์แสดงผล',
order: 'ลำดับ',
name: 'ชื่อ{msg}',
nameEN: 'ชื่อ (ภาษาอังกฤษ)',
fullName: 'ชื่อ-สกุล',
detail: 'รายละเอียด{msg}',
remark: 'หมายเหตุ{msg}',
@ -161,7 +160,6 @@ export default {
documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
},
menu: {
@ -204,14 +202,12 @@ export default {
title: 'จัดการ',
branch: 'สาขา',
personnel: 'บุคลากร',
group: 'กลุ่ม',
productService: 'สินค้าและบริการ',
workflow: 'ขั้นตอนการทำงาน',
property: 'คุณสมบัติ',
customer: 'ลูกค้า',
mainData: 'ข้อมูลหลัก',
agencies: 'หน่วยงาน',
businessType: 'ประเภทกิจการ',
},
sales: {
@ -338,7 +334,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -473,7 +469,6 @@ export default {
blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
},
},
customer: {
@ -500,16 +495,15 @@ export default {
},
prefix: {
mr: 'นาย',
mrs: 'นาง',
miss: 'นางสาว',
mr: 'Mr.',
mrs: 'Mrs.',
miss: 'Miss.',
},
citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ',
@ -626,7 +620,7 @@ export default {
placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก',
entryCount: 'จำนวนวันที่เข้าประเทศ',
entryCount: 'จำนวนที่เข้าประเทศ',
employerSelect: {
branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง',
@ -654,7 +648,7 @@ export default {
permitIssuedAt: 'สถานที่ออกใบอนุญาต',
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)',
identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
},
formFamily: {
citizenId:
@ -772,13 +766,10 @@ export default {
},
quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม',
@ -803,7 +794,7 @@ export default {
branch: 'สาขาที่ออกใบเสนอราคา',
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
customer: 'ลูกค้า',
newCustomer: 'แรงงานใหม่',
newCustomer: 'ลูกค้าใหม่',
employeeList: 'รายชื่อแรงงาน',
employee: 'แรงงาน',
employeeName: 'ชื่อ-นามสกุล แรงงาน',
@ -929,7 +920,6 @@ export default {
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
},
requestList: {
@ -1220,8 +1210,6 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
},
},
@ -1508,11 +1496,4 @@ export default {
last90Days: '90 วันที่ผ่านมา',
customDateRange: 'กำหนดช่วงวันที่เอง',
},
businessType: {
title: 'ประเภทกิจการ',
caption: 'จัดการประเภทกิจการ',
name: 'ชื่อประเภทกิจการ',
nameEn: 'ชื่อประเภทกิจการ (ภาษาอังกฤษ)',
},
};

View file

@ -7,7 +7,7 @@ import useMyBranch from 'stores/my-branch';
import { getUserId, getRole } from 'src/services/keycloak';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { canAccess } from 'src/stores/utils';
import { isRoleInclude } from 'src/stores/utils';
type Menu = {
label: string;
@ -71,50 +71,82 @@ function initMenu() {
{
label: 'branch',
route: '/branch-management',
hidden: !canAccess('branch'),
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
]),
},
{
label: 'personnel',
route: '/personnel-management',
hidden: !canAccess('personnel'),
},
{
label: 'group',
route: '/group-management',
hidden: !canAccess('personnel'),
hidden: !isRoleInclude([
'owner',
'system',
'head_of_admin',
'admin',
'branch_manager',
]),
},
{
label: 'workflow',
route: '/workflow',
hidden: !canAccess('workflow'),
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
},
{
label: 'property',
route: '/property',
hidden: !canAccess('workflow'),
},
{
label: 'businessType',
route: '/business-type',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
},
{
label: 'productService',
route: '/product-service',
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'head_of_sale',
'sale',
]),
},
{
label: 'customer',
route: '/customer-management',
hidden: !canAccess('customer'),
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
},
{
label: 'agencies',
route: '/agencies-management',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
},
],
},
{
label: 'menu.sales',
icon: 'mdi-store-settings-outline',
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
children: [
{ label: 'quotation', route: '/quotation' },
{ label: 'invoice', route: '/invoice' },
@ -137,6 +169,16 @@ function initMenu() {
label: 'menu.account',
icon: 'mdi-bank-outline',
disabled: false,
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
children: [
{ label: 'receipt', route: '/receipt' },
{ label: 'creditNote', route: '/credit-note' },
@ -158,13 +200,10 @@ function initMenu() {
{
label: 'menu.overall',
icon: 'mdi-monitor-dashboard',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin', 'executive']),
children: [
{ label: 'report', route: '/report' },
{
label: 'dashboard',
route: '/dash-board',
hidden: !canAccess('dashBoard'),
},
{ label: 'dashboard', route: '/dash-board' },
],
},

View file

@ -2,7 +2,7 @@
import { ref, onMounted, computed, reactive } from 'vue';
import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar';
import { getUserId, getUsername, getName, logout, getRole } from 'src/services/keycloak';
import { getUserId, getUsername, logout, getRole } from 'src/services/keycloak';
import { Icon } from '@iconify/vue';
import { useI18n } from 'vue-i18n';
import moment from 'moment';
@ -39,7 +39,7 @@ const configStore = useConfigStore();
const { data: notificationData } = storeToRefs(notificationStore);
const { visible } = storeToRefs(loaderStore);
const { t, locale } = useI18n({ useScope: 'global' });
const { t } = useI18n({ useScope: 'global' });
const userStore = useUserStore();
const canvasModal = ref(false);
@ -52,14 +52,8 @@ const unread = computed<number>(
);
const userImage = ref<string>();
const userGender = ref('');
const userName = ref({ th: '', en: '' });
const canvasRef = ref();
const displayName = computed(() => {
if (!userName.value.th && !userName.value.en) return getName() || 'Guest';
return locale.value === 'eng' ? userName.value.en : userName.value.th;
});
const language: {
value: Lang;
label: string;
@ -167,14 +161,9 @@ onMounted(async () => {
if (user === 'admin') return;
if (uid) {
const res = await userStore.fetchById(uid);
if (res) {
if (res.gender) {
userGender.value = res.gender;
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
}
//
userName.value.th = `${res.firstName || ''} ${res.lastName || ''}`.trim();
userName.value.en = `${res.firstNameEN || ''} ${res.lastNameEN || ''}`.trim();
if (res && res.gender) {
userGender.value = res.gender;
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
}
}
});
@ -495,7 +484,6 @@ onMounted(async () => {
<!-- User -->
<ProfileMenu
id="btn-profile-menu"
:user-name="displayName"
@logout="doLogout"
@edit-personal-info="console.log('edit')"
@signature="

View file

@ -12,7 +12,6 @@ const filterRole = ref<string[]>();
defineProps<{
userImage?: string;
gender?: string;
userName?: string;
}>();
const inputFile = document.createElement('input');
@ -148,9 +147,9 @@ onMounted(async () => {
class="text-weight-bold ellipsis"
style="max-width: 9vw"
>
{{ userName || getName() }}
{{ getName() }}
<q-tooltip>
{{ userName || getName() }}
{{ getName() }}
</q-tooltip>
</span>
<span
@ -235,12 +234,12 @@ onMounted(async () => {
style="margin-top: 58px"
>
<span v-if="isLoggedIn()">
{{ userName || getName() }}
{{ getName() }}
</span>
<span v-else>{{ 'Guest' }}</span>
<q-tooltip>
<span v-if="isLoggedIn()">
{{ userName || getName() }}
{{ getName() }}
</span>
<span v-else>{{ 'Guest' }}</span>
</q-tooltip>

View file

@ -10,7 +10,7 @@ import type { QTableProps, QTableSlots } from 'quasar';
import { resetScrollBar } from 'src/stores/utils';
import useBranchStore from 'stores/branch';
import useFlowStore from 'stores/flow';
import { isRoleInclude, canAccess } from 'stores/utils';
import { isRoleInclude } from 'stores/utils';
import {
BranchWithChildren,
BranchCreate,
@ -791,8 +791,6 @@ async function onSubmit(submitSelectedItem?: boolean) {
);
if (!res) return;
formType.value = 'view';
formData.value.codeHeadOffice = formData.value.code = res.code;
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
@ -867,9 +865,7 @@ async function onSubmit(submitSelectedItem?: boolean) {
actionText: t('dialog.action.ok'),
persistent: true,
title: t('form.warning.title'),
cancel: () => {
formType.value = 'create';
},
cancel: () => {},
action: async () => {
await createBranch();
},
@ -1050,7 +1046,7 @@ watch(currentHq, () => {
{{ $t('branch.allBranch') }}
</div>
<q-btn
v-if="isRoleInclude(['system'])"
v-if="isRoleInclude(['head_of_admin', 'admin', 'system'])"
round
flat
size="md"
@ -1074,7 +1070,6 @@ watch(currentHq, () => {
<div class="col full-width scroll">
<div class="q-pa-md">
<TreeComponent
:hide-create="!canAccess('branch', 'create')"
v-model:nodes="treeData"
v-model:expanded-tree="expandedTree"
node-key="id"
@ -1562,18 +1557,8 @@ watch(currentHq, () => {
</q-td>
<q-td>
<KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status"
:idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
@ -1713,18 +1698,8 @@ watch(currentHq, () => {
>
<template v-slot:action>
<KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status"
:idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
@ -2272,22 +2247,13 @@ watch(currentHq, () => {
@click="drawerEdit()"
type="button"
/>
<template
v-if="
formType !== 'edit' && formTypeBranch === 'headOffice'
? isRoleInclude(['system'])
: canAccess('branch', 'create')
"
>
<DeleteButton
v-if="formType !== 'edit'"
id="btn-info-basic-delete"
icon-only
@click="triggerDelete(currentEdit.id)"
type="button"
/>
</template>
<DeleteButton
v-if="formType !== 'edit'"
id="btn-info-basic-delete"
icon-only
@click="triggerDelete(currentEdit.id)"
type="button"
/>
</div>
</div>
<div

View file

@ -1,67 +0,0 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { getInstance } from 'src/services/keycloak';
import { useNavigator } from 'src/stores/navigator';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { initLang } from 'src/utils/ui';
const $q = useQuasar();
const { locale } = useI18n();
const navigatorStore = useNavigator();
const EDM_SERVICE = import.meta.env.VITE_EDM_MICRO_FRONTEND_URL;
const kc = getInstance();
const at = ref(kc.token);
const rt = ref(kc.refreshToken);
const iframe = ref<InstanceType<typeof HTMLIFrameElement>>();
function sendMessage() {
iframe?.value?.contentWindow?.postMessage(
{
i18n: locale.value,
darkMode: $q.dark.isActive,
},
'*',
);
}
onMounted(() => {
initLang();
currentLocale.value = locale.value;
navigatorStore.current.title = 'menu.dms';
navigatorStore.current.path = [
{
text: '',
i18n: true,
handler: () => {},
},
];
sendMessage();
});
watch(
() => kc.token,
() => {
at.value = kc.token;
rt.value = kc.refreshToken;
},
);
watch([locale, $q.dark], () => {
sendMessage();
});
const currentLocale = ref(locale.value);
</script>
<template>
<iframe
ref="iframe"
:src="`${EDM_SERVICE}/user?at=${at}&rt=${rt}&lang=${currentLocale}`"
frameborder="0"
class="full-width full-height rounded"
allowtransparency="true"
></iframe>
</template>
<style scoped></style>

View file

@ -157,14 +157,6 @@ const defaultFormData = {
contactTel: '',
remark: '',
agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
};
const formData = ref<UserCreate>({
@ -217,14 +209,6 @@ const formData = ref<UserCreate>({
contactTel: '',
remark: '',
agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
});
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
@ -447,28 +431,6 @@ async function onSubmit(excludeDialog?: boolean) {
...formData.value,
checkpointEN: formData.value.checkpoint,
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;
await userStore.editById(currentUser.value.id, formDataEdit);
@ -500,31 +462,7 @@ async function onSubmit(excludeDialog?: boolean) {
: '';
formData.value.checkpointEN = formData.value.checkpoint;
const result = await userStore.create(
{
...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,
},
formData.value,
onCreateImageList.value,
);
@ -622,20 +560,12 @@ async function assignFormData(idEdit: string) {
currentUser.value = foundUser;
formData.value = {
branchId: foundUser.branch[0]?.id,
provinceId: foundUser.addressForeign
? foundUser.provinceText
: foundUser.provinceId,
districtId: foundUser.addressForeign
? foundUser.districtText
: foundUser.districtId,
subDistrictId: foundUser.addressForeign
? foundUser.subDistrictText
: foundUser.subDistrictId,
provinceId: foundUser.provinceId,
districtId: foundUser.districtId,
subDistrictId: foundUser.subDistrictId,
telephoneNo: foundUser.telephoneNo,
email: foundUser.email,
zipCode: foundUser.addressForeign
? foundUser.zipCodeText
: foundUser.zipCode,
zipCode: foundUser.zipCode,
gender: foundUser.gender,
addressEN: foundUser.addressEN,
address: foundUser.address,
@ -689,10 +619,6 @@ async function assignFormData(idEdit: string) {
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null,
remark: foundUser.remark || '',
agencyStatus: foundUser.agencyStatus || '',
addressForeign: foundUser.addressForeign || false,
provinceTextEN: foundUser.provinceTextEN,
districtTextEN: foundUser.districtTextEN,
subDistrictTextEN: foundUser.subDistrictTextEN,
};
formData.value.status === 'ACTIVE' || 'CREATED'
@ -819,17 +745,7 @@ watch(
watch(
() => formData.value.userType,
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;
}
async () => {
if (!infoDrawerEdit.value) return;
formData.value.registrationNo = null;
formData.value.startDate = null;
@ -1362,7 +1278,7 @@ watch(
{{
locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
: `${props.row.firstName} ${props.row.lastName}`.trim()
}}
<q-tooltip
anchor="bottom left"
@ -1372,7 +1288,7 @@ watch(
{{
locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
: `${props.row.firstName} ${props.row.lastName}`.trim()
}}
</q-tooltip>
@ -1638,12 +1554,9 @@ watch(
hide-action
:is-edit="infoDrawerEdit"
:title="
(currentUser.namePrefix
? $t('customer.form.prefix.' + currentUser.namePrefix) + ' '
: '') +
(locale === 'eng'
locale === 'eng'
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
: `${currentUser.firstName || currentUser.firstNameEN} ${currentUser.lastName || currentUser.lastNameEN}`)
: `${currentUser.firstName} ${currentUser.lastName}`
"
v-model:drawerOpen="infoDrawer"
:submit="() => onSubmit()"
@ -1679,8 +1592,8 @@ watch(
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName || formData.firstNameEN,
lastName: formData.lastName || formData.lastNameEN,
firstName: formData.firstName,
lastName: formData.lastName,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
@ -1897,15 +1810,10 @@ watch(
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
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"
prefix-id="drawer-info-personnel"
:title="'personnel.form.addressInformation'"
dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
<FormByType
@ -2131,13 +2039,8 @@ watch(
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
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"
dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
<FormByType

View file

@ -9,7 +9,7 @@ import { baseUrl } from 'src/stores/utils';
import useCustomerStore from 'stores/customer';
import useFlowStore from 'stores/flow';
import useOptionStore from 'stores/options';
import { dialog, canAccess } from 'stores/utils';
import { dialog } from 'stores/utils';
import { Status } from 'stores/types';
import { Employee } from 'stores/employee/types';
@ -18,8 +18,6 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
import { columnsEmployee } from './constant';
import { useCustomerBranchForm, useEmployeeForm } from './form';
import DialogEmployee from 'src/components/03_customer-management/DialogEmployee.vue';
import DrawerEmployee from 'src/components/03_customer-management/DrawerEmployee.vue';
import EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue';
import FloatingActionButton from 'components/FloatingActionButton.vue';
import SideMenu from 'components/SideMenu.vue';
@ -91,11 +89,6 @@ const prop = withDefaults(
currentCitizenId?: string;
gender: string;
selectedImage: string;
fetchImageList: (
id: string,
selectedName: string,
type: 'customer' | 'employee',
) => Promise<void>;
}>(),
{
color: 'green',
@ -103,6 +96,7 @@ const prop = withDefaults(
);
const currentBranchEmployee = ref<string>('');
const listEmployee = ref<Employee[]>([]);
const customerId = defineModel<string>('customerId', { required: true });
defineEmits<{
@ -112,6 +106,16 @@ defineEmits<{
(e: 'dialog'): void;
}>();
onMounted(async () => {
customerBranchFormState.value.currentCustomerId = route.params
.customerId as string;
await fetchList();
branch.value?.forEach((v) => {
currentBtnOpen.value.push(false);
});
});
const columns = [
{
name: 'branchName',
@ -253,6 +257,10 @@ async function fetchEmployee(opts: { branchId: string; pageSize?: number }) {
}
}
onMounted(async () => {
await fetchList();
});
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
await fetchList();
});
@ -272,22 +280,12 @@ watch(
}
},
);
onMounted(async () => {
customerBranchFormState.value.currentCustomerId = route.params
.customerId as string;
await fetchList();
branch.value?.forEach((v) => {
currentBtnOpen.value.push(false);
});
});
</script>
<template>
<FloatingActionButton
style="z-index: 999"
v-if="$route.name !== 'CustomerManagement' && canAccess('customer', 'edit')"
v-if="$route.name !== 'CustomerManagement'"
@click="openEmployerBranchForm('create')"
hide-icon
></FloatingActionButton>
@ -475,6 +473,7 @@ onMounted(async () => {
<q-tr
:class="{
'app-text-muted': props.row.status === 'INACTIVE',
'cursor-pointer': props.row._count?.branch !== 0,
}"
:props="props"
@click="$emit('viewDetail', props.row, props.rowIndex)"
@ -548,13 +547,7 @@ onMounted(async () => {
v-if="branchFieldSelected.includes('businessTypePure')"
class="text-left"
>
{{
props.row.businessType
? props.row.businessType[
$i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-'
}}
{{ useOptionStore().mapOption(props.row.businessType) || '-' }}
</q-td>
<q-td
v-if="branchFieldSelected.includes('totalEmployee')"
@ -573,6 +566,8 @@ onMounted(async () => {
await fetchEmployee({
branchId: currentBranchEmployee,
pageSize: 999,
passport: true,
visa: true,
});
currentBtnOpen.map((v, i) => {
@ -620,7 +615,7 @@ onMounted(async () => {
<div class="text-center">
<TableEmpoloyee
:prefix-id="props.row.registerName || props.row.firstName"
:add-button="canAccess('customer', 'edit')"
add-button
in-table
:list-employee="listEmployee"
:columns-employee="columnsEmployee"
@ -643,15 +638,10 @@ onMounted(async () => {
"
@history="(item) => {}"
@view="
async (item) => {
(item) => {
employeeFormState.drawerModal = true;
//employeeFormState.isEmployeeEdit = true;
employeeFormStore.assignFormDataEmployee(item.id);
await fetchImageList(
item.id,
item.selectedImage || '',
'employee',
);
}
"
/>
@ -678,11 +668,9 @@ onMounted(async () => {
? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}`
: `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`,
telephone: props.row.telephoneNo,
businessTypePure: props.row.businessType
? props.row.businessType[
$i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-',
businessTypePure: useOptionStore().mapOption(
props.row.businessType,
),
totalEmployee: props.row._count?.employee,
}"
:visible-columns="branchFieldSelected"
@ -771,10 +759,7 @@ onMounted(async () => {
/>
<DeleteButton
icon-only
v-if="
customerBranchFormState.dialogType === 'info' &&
canAccess('customer', 'edit')
"
v-if="customerBranchFormState.dialogType === 'info'"
@click="
() => {
deleteBranchById(customerBranchFormData.id || '');
@ -868,6 +853,7 @@ onMounted(async () => {
v-model:last-name-en="customerBranchFormData.lastNameEN"
v-model:gender="customerBranchFormData.gender"
v-model:birth-date="customerBranchFormData.birthDate"
v-model:customer-name="customerBranchFormData.customerName"
v-model:legal-person-no="customerBranchFormData.legalPersonNo"
v-model:branch-code="customerBranchFormData.code"
v-model:register-name="customerBranchFormData.registerName"
@ -897,14 +883,15 @@ onMounted(async () => {
</div>
<EmployerFormBusiness
dense
class="q-mb-xl"
outlined
prefix-id="employer-branch"
:readonly="customerBranchFormState.dialogType === 'info'"
v-model:business-type-id="customerBranchFormData.businessTypeId"
v-model:bussiness-type="customerBranchFormData.businessType"
v-model:job-position="customerBranchFormData.jobPosition"
v-model:job-description="customerBranchFormData.jobDescription"
v-model:pay-date="customerBranchFormData.payDate"
v-model:pay-date-en="customerBranchFormData.payDateEN"
v-model:pay-date-e-n="customerBranchFormData.payDateEN"
v-model:wage-rate="customerBranchFormData.wageRate"
v-model:wage-rate-text="customerBranchFormData.wageRateText"
/>
@ -995,7 +982,7 @@ onMounted(async () => {
v-model:email="customerBranchFormData.email"
v-model:contact-tel="customerBranchFormData.contactTel"
v-model:office-tel="customerBranchFormData.officeTel"
v-model:agent-user-id="customerBranchFormData.agentUserId"
v-model:agent="customerBranchFormData.agent"
/>
</div>
</div>
@ -1011,18 +998,6 @@ onMounted(async () => {
/>
</template>
</DialogFormContainer>
<DialogEmployee
:fetch-list-employee="fetchEmployee"
:fetch-image-list="fetchImageList"
current-tab="employer"
/>
<DrawerEmployee
:fetch-list-employee="fetchEmployee"
:fetch-image-list="fetchImageList"
current-tab="employer"
/>
</template>
<style scoped>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,514 +0,0 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import {
PaginationComponent,
PaginationPageSize,
ImageUploadDialog,
DialogForm,
NoData,
} from 'src/components';
import DialogEmployee from 'src/components/03_customer-management/DialogEmployee.vue';
import HistoryEditComponent from 'src/components/03_customer-management/HistoryEditComponent.vue';
import TableEmpoloyee from 'src/components/03_customer-management/TableEmpoloyee.vue';
import DrawerEmployee from 'src/components/03_customer-management/DrawerEmployee.vue';
import useFlowStore from 'src/stores/flow';
import useEmployeeStore from 'src/stores/employee';
import { Status } from 'src/stores/types';
import { Employee, EmployeeHistory } from 'src/stores/employee/types';
import { baseUrl, dialog, canAccess } from 'src/stores/utils';
import { calculateAge, toISOStringWithTimezone } from 'src/utils/datetime';
import { useCustomerForm, useEmployeeForm } from './form';
import { columnsEmployee } from './constant';
const $q = useQuasar();
const flowStore = useFlowStore();
const employeeStore = useEmployeeStore();
const employeeFormStore = useEmployeeForm();
const customerFormStore = useCustomerForm();
const { t } = useI18n();
const { state: employeeFormState, currentFromDataEmployee } =
storeToRefs(employeeFormStore);
const { deleteEmployeeById } = employeeFormStore;
const { state: customerFormState } = storeToRefs(customerFormStore);
const employeeStats = defineModel('employeeStats', { default: 0 });
const props = defineProps<{
currentTab: 'employee' | 'employer';
currentStatus: Status | 'All';
gridView: boolean;
inputSearch: string;
searchDate: string[];
fieldSelected: string[];
fetchImageList: (
id: string,
selectedName: string,
type: 'customer' | 'employee',
) => Promise<void>;
triggerChangeStatus: (
id: string,
status: string,
employeeName?: string,
) => void;
}>();
defineExpose({
openSpecificEmployee,
fetchListEmployee,
toggleStatusEmployee,
});
const listEmployee = ref<Employee[]>([]);
const currentPageEmployee = ref<number>(1);
const maxPageEmployee = ref<number>(1);
const pageSize = ref<number>(30);
const employeeHistoryDialog = ref(false);
const employeeHistory = ref<EmployeeHistory[]>();
const splitPercent = computed(() => ($q.screen.lt.md ? 0 : 15));
async function fetchListEmployee(opt?: {
fetchStats?: boolean;
page?: number;
pageSize?: number;
customerId?: string;
mobileFetch?: boolean;
}) {
const resultListEmployee = await employeeStore.fetchList({
customerId: opt?.customerId,
page: opt
? opt.mobileFetch
? 1
: opt.page || currentPageEmployee.value
: currentPageEmployee.value,
pageSize: opt
? opt.mobileFetch
? listEmployee.value.length +
(employeeStats.value === listEmployee.value.length ? 1 : 0)
: opt.pageSize || pageSize.value
: pageSize.value,
status:
props.currentStatus === 'All'
? undefined
: props.currentStatus === 'ACTIVE'
? 'ACTIVE'
: 'INACTIVE',
query: props.inputSearch,
passport: true,
visa: true,
startDate: props.searchDate[0],
endDate: props.searchDate[1],
});
if (resultListEmployee) {
maxPageEmployee.value = Math.ceil(
resultListEmployee.total / pageSize.value,
);
$q.screen.xs && !(opt && opt.mobileFetch)
? listEmployee.value.push(...resultListEmployee.result)
: (listEmployee.value = resultListEmployee.result);
}
if (opt && opt.fetchStats)
employeeStats.value = await employeeStore.getStatsEmployee();
}
async function openHistory(id: string) {
const res = await employeeStore.getEditHistory(id);
employeeHistory.value = res.reverse();
employeeHistoryDialog.value = true;
}
async function editEmployeeFormPersonal(id: string) {
await employeeFormStore.assignFormDataEmployee(id);
await props.fetchImageList(
id,
currentFromDataEmployee.value.selectedImage || '',
'employee',
);
employeeFormState.value.isEmployeeEdit = true;
employeeFormState.value.dialogType = 'edit';
employeeFormState.value.drawerModal = true;
}
async function openSpecificEmployee(id: string) {
await employeeFormStore.assignFormDataEmployee(id);
await props.fetchImageList(
id,
currentFromDataEmployee.value.selectedImage || '',
'employee',
);
employeeFormState.value.dialogType = 'info';
employeeFormState.value.drawerModal = true;
}
async function toggleStatusEmployee(
id: string,
status: boolean,
employeeName: string,
) {
const res = await employeeStore.editById(id, {
status: !status ? 'ACTIVE' : 'INACTIVE',
firstNameEN: employeeName,
});
if (res && employeeFormState.value.drawerModal) {
currentFromDataEmployee.value.status = res.status;
}
await employeeFormStore.assignFormDataEmployee(id);
await fetchListEmployee({ mobileFetch: $q.screen.xs });
flowStore.rotate();
}
watch(
() => [
props.inputSearch,
props.searchDate,
props.currentStatus,
pageSize.value,
],
async () => {
currentPageEmployee.value = 1;
listEmployee.value = [];
await fetchListEmployee({ fetchStats: true });
customerFormState.value.currentCustomerId = undefined;
flowStore.rotate();
},
);
watch(
() => employeeFormState.value.currentCustomerBranch,
(e) => {
if (!e) return;
if (employeeFormState.value.formDataEmployeeSameAddr) {
currentFromDataEmployee.value.address = e.address;
currentFromDataEmployee.value.addressEN = e.addressEN;
currentFromDataEmployee.value.provinceId = e.provinceId;
currentFromDataEmployee.value.districtId = e.districtId;
currentFromDataEmployee.value.subDistrictId = e.subDistrictId;
}
currentFromDataEmployee.value.customerBranchId = e.id;
},
);
watch(
() => employeeFormState.value.formDataEmployeeSameAddr,
(isSame) => {
if (!employeeFormState.value.currentCustomerBranch) return;
if (isSame) {
currentFromDataEmployee.value.address =
employeeFormState.value.currentCustomerBranch.address;
currentFromDataEmployee.value.addressEN =
employeeFormState.value.currentCustomerBranch.addressEN;
currentFromDataEmployee.value.provinceId =
employeeFormState.value.currentCustomerBranch.provinceId;
currentFromDataEmployee.value.districtId =
employeeFormState.value.currentCustomerBranch.districtId;
currentFromDataEmployee.value.subDistrictId =
employeeFormState.value.currentCustomerBranch.subDistrictId;
}
currentFromDataEmployee.value.customerBranchId =
employeeFormState.value.currentCustomerBranch.id;
},
);
watch(
() => currentFromDataEmployee.value.dateOfBirth,
(v) => {
const isEdit =
employeeFormState.value.drawerModal &&
employeeFormState.value.isEmployeeEdit;
let currentFormDate = v && toISOStringWithTimezone(new Date(v));
let currentDate: string = '';
if (isEdit && employeeFormState.value.currentEmployee) {
currentDate = toISOStringWithTimezone(
new Date(employeeFormState.value.currentEmployee.dateOfBirth),
);
}
if (
employeeFormState.value.dialogModal ||
(isEdit && currentFormDate !== currentDate)
) {
const age = calculateAge(
currentFromDataEmployee.value.dateOfBirth,
'year',
);
if (currentFromDataEmployee.value.dateOfBirth && Number(age) < 15) {
dialog({
color: 'warning',
icon: 'mdi-alert',
title: t('dialog.title.youngWorker15'),
cancelText: t('general.edit'),
persistent: true,
message: t('dialog.message.youngWorker15'),
cancel: async () => {
currentFromDataEmployee.value.dateOfBirth = null;
return;
},
});
}
if (
currentFromDataEmployee.value.dateOfBirth &&
Number(age) > 15 &&
Number(age) <= 18
) {
dialog({
color: 'warning',
icon: 'mdi-alert',
title: t('dialog.title.youngWorker18'),
cancelText: t('general.cancel'),
actionText: t('general.confirm'),
persistent: true,
message: t('dialog.message.youngWorker18'),
action: () => {},
cancel: async () => {
currentFromDataEmployee.value.dateOfBirth = null;
return;
},
});
}
}
},
);
watch(
() => currentFromDataEmployee.value.image,
() => {
if (currentFromDataEmployee.value.image !== null)
employeeFormState.value.isImageEdit = true;
},
);
onMounted(async () => {
currentPageEmployee.value = 1;
listEmployee.value = [];
await fetchListEmployee({ fetchStats: true });
});
</script>
<template>
<q-splitter
v-model="splitPercent"
:limits="[0, 100]"
class="col full-width"
before-class="overflow-hidden"
after-class="overflow-hidden"
:disable="$q.screen.lt.sm"
>
<template v-slot:before>
<div
class="column q-pa-md surface-1 full-height full-width"
style="gap: var(--size-1)"
>
<q-item
active
dense
active-class="employer-active"
class="no-padding items-center rounded full-width"
v-close-popup
clickable
>
<span class="q-px-md ellipsis">
{{ $t('general.all') }}
</span>
</q-item>
</div>
</template>
<template v-slot:after>
<div class="column full-height no-wrap">
<!-- employee -->
<template
v-if="listEmployee && employeeStats > 0 && currentTab === 'employee'"
>
<div
v-if="listEmployee.length === 0"
class="row col full-width items-center justify-center"
style="min-height: 250px"
>
<NoData :not-found="!!inputSearch" />
</div>
<article
v-if="listEmployee.length !== 0"
class="column scroll q-pa-md col"
>
<q-infinite-scroll
:offset="10"
@load="
(_, done) => {
if (
$q.screen.gt.xs ||
currentPageEmployee === maxPageEmployee
)
return;
currentPageEmployee = currentPageEmployee + 1;
fetchListEmployee().then(() =>
done(currentPageEmployee >= maxPageEmployee),
);
}
"
>
<TableEmpoloyee
:hide-delete="!canAccess('customer', 'edit')"
v-model:page-size="pageSize"
v-model:current-page="currentPageEmployee"
:grid-view="gridView"
:list-employee="listEmployee"
:columns-employee="columnsEmployee"
:field-selected="fieldSelected"
@history="
(item: any) => {
openHistory(item.id);
}
"
@view="
async (item: any) => {
employeeFormState.drawerModal = true;
employeeFormState.isEmployeeEdit = false;
employeeFormStore.assignFormDataEmployee(item.id);
await fetchImageList(
item.id,
item.selectedImage || '',
'employee',
);
}
"
@edit="(item: any) => editEmployeeFormPersonal(item.id)"
@delete="
(item: any) => {
deleteEmployeeById({
id: item.id,
fetch: async () =>
await fetchListEmployee(
currentTab === 'employer'
? {
page: 1,
pageSize: 999,
customerId: customerFormState.currentCustomerId,
}
: {
fetchStats: true,
mobileFetch: $q.screen.xs,
},
),
});
}
"
@toggle-status="
async (item: any) => {
triggerChangeStatus(item.id, item.status, item.firstNameEN);
}
"
/>
<template v-slot:loading>
<div
v-if="
$q.screen.lt.sm && currentPageEmployee !== maxPageEmployee
"
class="row justify-center"
>
<q-spinner-dots color="primary" size="40px" />
</div>
</template>
</q-infinite-scroll>
</article>
<footer
v-if="listEmployee.length !== 0 && $q.screen.gt.xs"
class="row justify-between items-center q-px-md q-py-sm"
>
<div class="row col-4 items-center">
<div
class="app-text-muted"
style="width: 80px"
v-if="$q.screen.gt.sm"
>
{{ $t('general.recordPerPage') }}
</div>
<div><PaginationPageSize v-model="pageSize" /></div>
</div>
<div class="col-4 flex justify-center app-text-muted">
{{
$q.screen.gt.sm
? $t('general.recordsPage', {
resultcurrentPage: listEmployee.length,
total: employeeStats,
})
: $t('general.ofPage', {
current: listEmployee.length,
total: employeeStats,
})
}}
</div>
<div class="col-4 flex justify-end">
<PaginationComponent
v-model:current-page="currentPageEmployee"
v-model:max-page="maxPageEmployee"
:fetch-data="
async () => {
await fetchListEmployee();
flowStore.rotate();
}
"
/>
</div>
</footer>
</template>
</div>
</template>
</q-splitter>
<!-- add employee -->
<DialogEmployee
:fetch-list-employee="fetchListEmployee"
:fetch-image-list="fetchImageList"
:current-tab="currentTab"
/>
<!-- กจาง edit employee -->
<DrawerEmployee
:fetch-list-employee="fetchListEmployee"
:fetch-image-list="fetchImageList"
:current-tab="currentTab"
@change-status="
(s) =>
triggerChangeStatus(
currentFromDataEmployee.id,
s,
currentFromDataEmployee.firstNameEN,
)
"
/>
<DialogForm
:title="$t('general.historyEdit')"
hide-footer
v-model:modal="employeeHistoryDialog"
>
<div class="q-pa-md">
<HistoryEditComponent
v-if="employeeHistory"
v-model:history-list="employeeHistory"
/>
</div>
</DialogForm>
</template>
<style scoped>
.employer-active {
background-color: hsla(var(--info-bg) / 0.1);
color: hsl(var(--info-bg));
}
</style>

View file

@ -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'),
]"
/>
@ -167,6 +167,20 @@ watch(
</div>
<div class="col-12 row q-col-gutter-sm">
<q-input
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-12 col-md-5"
:label="$t('customer.form.employerName')"
for="input-legal-person-no"
:model-value="customerName"
@update:model-value="
(v) => (typeof v === 'string' ? (customerName = v.trim()) : '')
"
/>
<q-input
dense
outlined
@ -336,7 +350,6 @@ watch(
(v) => (typeof v === 'string' ? (prefixName = v) : '')
"
@clear="prefixName = ''"
:rules="[(val: string) => !!val || $t('form.error.required')]"
>
<template v-slot:no-option>
<q-item>
@ -388,8 +401,8 @@ watch(
label="Prefix"
:model-value="
readonly
? prefixName.toUpperCase() || '-'
: prefixName.toUpperCase() || ''
? capitalize(prefixName || '') || '-'
: capitalize(prefixName || '')
"
@update:model-value="
(v) => (typeof v === 'string' ? (prefixName = v) : '')

View file

@ -2,7 +2,6 @@
import useOptionStore from 'stores/options';
import SelectBranch from 'src/components/shared/select/SelectBranch.vue';
import { isRoleInclude } from 'src/stores/utils';
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
withDefaults(
defineProps<{
@ -103,13 +102,7 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
class="col-md-6"
:readonly
:disabled="
!isRoleInclude([
'admin',
'system',
'head_of_admin',
'executive',
'accountant',
]) && !readonly
!isRoleInclude(['admin', 'system', 'head_of_admin']) && !readonly
"
:label="$t('customer.form.registeredBranch')"
select-first-value
@ -143,10 +136,15 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
for="input-tax"
v-model="legalPersonNo"
/>
<SelectBusinessType
<q-input
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-6 col-md-3"
v-model:value="businessType"
:readonly
:label="$t('customer.table.businessTypePure')"
for="input-business-type"
:model-value="optionStore.mapOption(businessType)"
/>
</div>
@ -175,10 +173,15 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
:label="$t('personnel.form.citizenId')"
for="input-citizen-id"
/>
<SelectBusinessType
<q-input
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-6 col-md-3"
v-model:value="businessType"
:readonly
:label="$t('customer.table.businessTypePure')"
for="input-first-name-en"
:model-value="optionStore.mapOption(businessType)"
/>
</div>

View file

@ -7,8 +7,6 @@ import { CustomerCreate } from 'stores/customer/types';
import EmployerFormAbout from './EmployerFormAbout.vue';
import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
import { waitAll } from 'src/stores/utils';
import FormEmployeePassport from 'src/components/03_customer-management/FormEmployeePassport.vue';
import FormEmployeeVisa from 'src/components/03_customer-management/FormEmployeeVisa.vue';
import {
FormCitizen,
CorpFormBusinessRegistration,
@ -53,7 +51,6 @@ withDefaults(
actionDisabled?: boolean;
customerType?: 'CORP' | 'PERS';
hideAction?: boolean;
hideDelete?: boolean;
}>(),
{
hideAction: false,
@ -84,7 +81,7 @@ withDefaults(
/>
<DeleteButton
icon-only
v-if="readonly && !hideDelete"
v-if="readonly"
@click="$emit('delete')"
type="button"
:disabled="actionDisabled"
@ -144,6 +141,7 @@ withDefaults(
v-model:last-name-en="item.lastNameEN"
v-model:gender="item.gender"
v-model:birth-date="item.birthDate"
v-model:customer-name="item.customerName"
v-model:legal-person-no="item.legalPersonNo"
v-model:branch-code="item.code"
v-model:register-name="item.registerName"
@ -160,7 +158,7 @@ withDefaults(
outlined
:prefix-id="prefixId || 'employer'"
:readonly="readonly"
v-model:business-type-id="item.businessTypeId"
v-model:bussiness-type="item.businessType"
v-model:job-position="item.jobPosition"
v-model:job-description="item.jobDescription"
v-model:pay-date="item.payDate"
@ -222,6 +220,7 @@ withDefaults(
hide-action
:ocr="
async (group, file) => {
console.log(group);
if (group !== 'attachment') {
const res = await ocrStore.sendOcr({
file: file,
@ -246,20 +245,12 @@ withDefaults(
:auto-save="item.id !== ''"
:download="
(obj) => {
if (obj.group === 'citizen') {
customerStore.getFile({
parentId: item.id || '',
group: obj.group,
fileId: obj._meta.id,
download: true,
});
} else {
customerStore.getAttachment({
parentId: item.id || '',
name: obj._meta.id,
download: true,
});
}
customerStore.getFile({
parentId: item.id || '',
group: obj.group,
fileId: obj._meta.id,
download: true,
});
}
"
:delete-item="
@ -288,7 +279,7 @@ withDefaults(
_meta: any,
file: File | undefined,
) => {
if (group === 'citizen') {
if (group !== 'attachment') {
if (file !== undefined && item.id) {
const res = await customerStore.postMeta({
parentId: item.id || '',
@ -356,6 +347,7 @@ withDefaults(
});
const tempValue = resMeta.map(async (v: any) => {
console.log(v.expireDate);
return {
_meta: { ...v },
name: `${group}-${dateFormat(v.expireDate)}`,
@ -376,6 +368,7 @@ withDefaults(
});
const tempValue = (res as string[]).map(async (i: any) => {
console.log(i);
return {
_meta: { id: i, name: i },
name: i || '',

View file

@ -8,28 +8,12 @@ import { onMounted, watch } from 'vue';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import ThaiBahtText from 'thai-baht-text';
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
import BusinessTypeDialog from 'src/pages/16_business-type-management/BusinessTypeDialog.vue';
import useBusinessTypeStore from 'src/stores/business-type';
const { locale } = useI18n({ useScope: 'global' });
const rawOption = ref();
const businessTypeDialog = ref<boolean>(false);
const formDataBusinessType = ref<{
name: string;
nameEN: string;
}>({
name: '',
nameEN: '',
});
const useBusinessType = useBusinessTypeStore();
const businessTypeId = defineModel<string>('businessTypeId');
const bussinessType = defineModel<string>('bussinessType');
const jobPosition = defineModel<string>('jobPosition');
const jobDescription = defineModel<string>('jobDescription');
const payDate = defineModel<string>('payDate');
@ -44,8 +28,6 @@ const typeBusinessENOption = ref([]);
const jobPositionOption = ref([]);
const jobPositionENOption = ref([]);
const keySelect = ref<number>(0);
defineProps<{
title?: string;
dense?: boolean;
@ -55,33 +37,35 @@ defineProps<{
showTitle?: boolean;
}>();
function resetFormBusinessType() {
businessTypeDialog.value = false;
formDataBusinessType.value = { name: '', nameEN: '' };
}
async function submitBusinessType() {
const res = await useBusinessType.create(formDataBusinessType.value);
if (res) {
businessTypeId.value = res.id;
resetFormBusinessType();
keySelect.value++;
}
}
onMounted(async () => {
const resultOption = await fetch('/option/option.json');
rawOption.value = await resultOption.json();
typeBusinessENOption.value = rawOption.value.eng.businessType;
jobPositionENOption.value = rawOption.value.eng.position;
if (locale.value === 'eng') {
typeBusinessOption.value = rawOption.value.eng.businessType;
jobPositionOption.value = rawOption.value.eng.position;
}
if (locale.value === 'tha') {
typeBusinessOption.value = rawOption.value.tha.businessType;
jobPositionOption.value = rawOption.value.tha.position;
}
});
watch([typeBusinessOption, typeBusinessENOption], () => {
typeBusinessFilter = selectFilterOptionRefMod(
typeBusinessOption,
typeBusinessOptions,
'label',
);
typeBusinessENFilter = selectFilterOptionRefMod(
typeBusinessENOption,
typeBusinessENOptions,
'label',
);
});
watch(
() => rate.value,
(newValue) => {
@ -151,26 +135,69 @@ let jobPositionENFilter = selectFilterOptionRefMod(
<span>{{ $t('customerBranch.tab.business') }}</span>
</div>
<SelectBusinessType
:key="keySelect"
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
:hide-dropdown-icon="readonly"
input-debounce="0"
option-value="value"
option-label="label"
v-model="bussinessType"
class="col-md-6 col-12"
v-model:value="businessTypeId"
:readonly
creatable
lang="tha"
:dense="dense"
:readonly="readonly"
:label="$t('customer.form.businessType')"
:options="typeBusinessOptions"
:for="`${prefixId}-select-business-type`"
@filter="typeBusinessFilter"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@create="() => (businessTypeDialog = true)"
/>
<SelectBusinessType
:key="keySelect"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
:for="`${prefixId}-input-bussiness-type-en`"
:id="`${prefixId}-input-bussiness-type-en`"
:label="`${$t('customer.form.businessType')} (EN)`"
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
:hide-dropdown-icon="readonly"
input-debounce="0"
option-value="value"
option-label="label"
v-model="bussinessType"
class="col-md-6 col-12"
v-model:value="businessTypeId"
:readonly
creatable
lang="eng"
:dense="dense"
:readonly="readonly"
:options="typeBusinessENOptions"
@filter="typeBusinessENFilter"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@create="() => (businessTypeDialog = true)"
/>
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
outlined
@ -321,12 +348,4 @@ let jobPositionENFilter = selectFilterOptionRefMod(
"
/>
</div>
<BusinessTypeDialog
ref="refBusinessTypeDialog"
@close="resetFormBusinessType()"
@submit="submitBusinessType()"
v-model="businessTypeDialog"
v-model:data="formDataBusinessType"
/>
</template>

View file

@ -9,6 +9,8 @@ const contactName = defineModel<string>('contactName');
const email = defineModel<string>('email');
const contactTel = defineModel<string>('contactTel');
const officeTel = defineModel<string>('officeTel');
const agent = defineModel<string>('agent');
const agentUserId = defineModel<string>('agentUserId');
</script>
@ -107,6 +109,7 @@ const agentUserId = defineModel<string>('agentUserId');
/>
</template>
</q-input>
<SelectAgent
:label="$t('customer.form.agent')"
v-model:value="agentUserId"

View file

@ -69,19 +69,19 @@ export const uploadFileListCustomer: {
}[] = [
{
label: 'customer.typeFile.citizenId',
group: 'citizen',
value: 'citizen',
group: 'citizen',
},
{
label: 'customer.typeFile.registrationBook',
group: 'houseRegistration',
value: 'attachment',
group: 'houseRegistration',
},
{
label: 'customer.typeFile.houseMap',
group: 'vatRegistration',
value: 'attachment',
group: 'vatRegistration',
},
{
@ -92,7 +92,7 @@ export const uploadFileListCustomer: {
{
label: 'customer.typeFile.dbdCertificate',
group: 'dbdCertificate',
group: 'powerOfAttorney',
value: 'attachment',
},
@ -144,43 +144,43 @@ export const uploadFileListEmployee: {
},
{
label: 'customerEmployee.fileType.tm6',
group: 'tm6',
value: 'attachment',
group: 'tm6',
},
{
label: 'customerEmployee.fileType.workPermit',
group: 'workPermit',
value: 'attachment',
group: 'workPermit',
},
{
label: 'customerEmployee.fileType.noticeJobEmployment',
group: 'noticeJobEmployment',
value: 'attachment',
group: 'noticeJobEmployment',
},
{
label: 'customerEmployee.fileType.noticeJobEntry',
group: 'noticeJobEntry',
value: 'attachment',
group: 'noticeJobEntry',
},
{
label: 'customerEmployee.fileType.historyJob',
group: 'historyJob',
value: 'attachment',
group: 'historyJob',
},
{
label: 'customerEmployee.fileType.acceptJob',
group: 'acceptJob',
value: 'attachment',
group: 'acceptJob',
},
{
label: 'customerEmployee.fileType.receipt',
group: 'receipt',
value: 'attachment',
group: 'receipt',
},
{
label: 'customerEmployee.fileType.other',
group: 'other',
value: 'attachment',
group: 'other',
},
];

View file

@ -22,10 +22,6 @@ import { useRoute } from 'vue-router';
export const useCustomerForm = defineStore('form-customer', () => {
const customerStore = useCustomerStore();
const onCreateImageList = ref<{
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}>({ selectedImage: '', list: [] });
const { t } = useI18n();
const flowStore = useFlowStore();
@ -34,13 +30,11 @@ export const useCustomerForm = defineStore('form-customer', () => {
const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
const currentBranchRootId = ref<string>('');
const tabFieldRequired = ref<{
[key: string]: (keyof CustomerBranchCreate)[];
}>({
main: [],
business: ['businessTypeId', 'jobPosition'],
business: ['businessType', 'jobPosition'],
address: [
'address',
'addressEN',
@ -88,7 +82,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
formDataOcr: Record<string, any>;
isImageEdit: boolean;
currentCustomerId?: string;
imageList: { list: string[]; selectedImage: string };
}>({
dialogType: 'info',
dialogOpen: false,
@ -105,7 +98,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
treeFile: [],
formDataOcr: {},
isImageEdit: false,
imageList: { list: [], selectedImage: '' },
});
watch(
@ -168,7 +160,6 @@ 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;
@ -190,7 +181,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
payDate: v.payDate,
jobDescription: v.jobDescription,
jobPosition: v.jobPosition,
businessTypeId: v.businessTypeId,
businessType: v.businessType,
employmentOffice: v.employmentOffice,
employmentOfficeEN: v.employmentOfficeEN,
telephoneNo: v.telephoneNo,
@ -227,6 +218,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
contactTel: v.contactTel,
officeTel: v.officeTel,
agentUserId: v.agentUserId || undefined,
customerName: v.customerName,
authorizedName: v.authorizedName,
authorizedNameEN: v.authorizedNameEN,
@ -264,6 +256,7 @@ 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
@ -297,8 +290,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
birthDate:
currentFormData.value.customerBranch?.at(0)?.birthDate || undefined,
businessTypeId:
currentFormData.value.customerBranch?.at(0)?.businessTypeId || '',
businessType:
currentFormData.value.customerBranch?.at(0)?.businessType || '',
jobPosition:
currentFormData.value.customerBranch?.at(0)?.jobPosition || '',
jobDescription:
@ -335,6 +328,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
currentFormData.value.customerBranch?.at(0)?.agentUserId || undefined,
status: 'CREATED',
customerName:
currentFormData.value.customerBranch?.at(0)?.customerName || '',
registerName:
currentFormData.value.customerBranch?.at(0)?.registerName || '',
registerNameEN:
@ -503,14 +498,11 @@ export const useCustomerForm = defineStore('form-customer', () => {
}
return {
onCreateImageList,
tabFieldRequired,
registerAbleBranchOption,
state,
resetFormData,
currentFormData,
currentBranchRootId,
isFormDataDifferent,
resetForm,
assignFormData,
@ -549,7 +541,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
gender: '',
birthDate: undefined,
businessTypeId: '',
businessType: '',
jobPosition: '',
jobDescription: '',
payDate: '',
@ -579,6 +571,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
agentUserId: undefined,
status: 'CREATED',
customerName: '',
registerName: '',
registerNameEN: '',
registerDate: undefined,
@ -630,7 +623,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
payDateEN: _data.payDateEN,
jobDescription: _data.jobDescription,
jobPosition: _data.jobPosition,
businessTypeId: _data.businessTypeId,
businessType: _data.businessType,
employmentOffice: _data.employmentOffice,
employmentOfficeEN: _data.employmentOfficeEN,
telephoneNo: _data.telephoneNo,
@ -664,6 +657,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
officeTel: _data.officeTel,
agentUserId: _data.agentUserId || undefined,
codeCustomer: _data.codeCustomer,
customerName: _data.customerName,
homeCode: _data.homeCode,
authorizedName: _data.authorizedName,
authorizedNameEN: _data.authorizedNameEN,
@ -790,7 +784,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
}
| undefined;
ocr: boolean;
imageList: { list: string[]; selectedImage: string };
}>({
currentBranchId: '',
isImageEdit: false,
@ -815,10 +808,9 @@ export const useEmployeeForm = defineStore('form-employee', () => {
infoEmployeePersonCard: [],
formDataEmployeeOwner: undefined,
ocr: false,
imageList: { list: [], selectedImage: '' },
});
const defaultFormData: EmployeeCreate & { image?: File } = {
const defaultFormData: EmployeeCreate = {
id: '',
code: '',
customerBranchId: '',
@ -900,7 +892,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
expireDate: new Date(),
remark: undefined,
workerType: '',
reportDate: null,
number: '',
},
],
@ -946,7 +937,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
};
let resetEmployeeData = structuredClone(defaultFormData);
const currentFromDataEmployee = ref<EmployeeCreate & { image?: File }>(
const currentFromDataEmployee = ref<EmployeeCreate>(
structuredClone(defaultFormData),
);
@ -968,7 +959,6 @@ 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;
@ -996,16 +986,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport
].id === undefined
) {
const { id, employeeId, updatedAt, createdAt, file, ...payload } =
currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport
];
const res = await employeeStore.postMeta({
parentId: currentFromDataEmployee.value.id || '',
group: 'passport',
meta: payload,
file: file,
meta: currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport
],
});
if (res) {
@ -1019,7 +1005,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport
].id !== undefined
) {
const { id, employeeId, updatedAt, createdAt, file, ...payload } =
const { id, employeeId, updatedAt, createdAt, ...payload } =
currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport
];
@ -1032,7 +1018,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport
].id || '',
meta: payload,
file: file || undefined,
});
}
@ -1260,7 +1245,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
await assignFormDataEmployee(currentFromDataEmployee.value.id);
}
async function submitPersonal(imgList?: {
async function submitPersonal(imgList: {
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}) {
@ -1281,7 +1266,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
currentFromDataEmployee.value.lastNameEN.trim();
if (state.value.dialogType === 'create') {
delete currentFromDataEmployee.value.image;
const res = await employeeStore.create(
{
...currentFromDataEmployee.value,
@ -1398,10 +1382,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
statusSave: true,
})),
),
employeeOtherInfo: structuredClone({
...(payload.employeeOtherInfo ?? {}),
statusSave: true,
}),
employeeOtherInfo: structuredClone(
{
...payload.employeeOtherInfo,
statusSave: !!payload.employeeOtherInfo?.id ? true : false,
} || {},
),
employeeWork: structuredClone(
payload.employeeWork?.length === 0
? state.value.dialogModal
@ -1550,7 +1536,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
expireDate: new Date(),
remark: undefined,
workerType: '',
reportDate: null,
number: '',
});
@ -1675,7 +1660,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state,
currentFromDataEmployee,
resetEmployeeData,
addPassport,
addVisa,
addCheckup,

View file

@ -43,7 +43,6 @@ withDefaults(
defineProps<{
readonly?: boolean;
isEdit?: boolean;
hideAction?: boolean;
}>(),
{ readonly: false, isEdit: false },
);
@ -208,7 +207,7 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
style="position: absolute; z-index: 999; top: 0; right: 0"
>
<div
v-if="flowData.status !== 'INACTIVE' && !hideAction"
v-if="flowData.status !== 'INACTIVE'"
class="surface-1 row rounded"
>
<UndoButton
@ -288,7 +287,6 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
>
<template v-slot:btn-form-flow-step-drawer>
<q-btn
v-if="!hideAction"
dense
flat
icon="mdi-plus"
@ -317,7 +315,6 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
<FormFlow
:readonly
onDrawer
:hide-action="hideAction"
v-model:user-in-table="userInTable"
v-model:flow-data="flowData"
v-model:register-branch-id="registerBranchId"

View file

@ -11,7 +11,7 @@ import {
} from 'src/stores/workflow-template/types';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { useNavigator } from 'src/stores/navigator';
import { dialog, canAccess } from 'src/stores/utils';
import { dialog } from 'src/stores/utils';
import FloatingActionButton from 'components/FloatingActionButton.vue';
import StatCardComponent from 'src/components/StatCardComponent.vue';
@ -334,7 +334,6 @@ watch(
</script>
<template>
<FloatingActionButton
v-if="canAccess('workflow', 'edit')"
style="z-index: 999"
hide-icon
@click="triggerDialog('add')"
@ -683,7 +682,6 @@ watch(
"
/>
<KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.name"
:status="props.row.status"
@view="
@ -765,7 +763,6 @@ watch(
"
/>
<KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.name"
:status="props.row.status"
@view="
@ -849,7 +846,6 @@ watch(
@drawer-undo="undo"
@close="resetForm"
@submit="submit"
:hide-action="!canAccess('workflow', 'edit')"
:readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit"
v-model="pageState.addModal"

View file

@ -41,7 +41,7 @@ import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import useFlowStore from 'stores/flow';
import { dateFormat } from 'src/utils/datetime';
import { formatNumberDecimal, isRoleInclude, canAccess } from 'stores/utils';
import { formatNumberDecimal, isRoleInclude, notify } from 'stores/utils';
const { getWorkflowTemplate } = useWorkflowTemplate();
import { Status } from 'stores/types';
@ -102,8 +102,6 @@ const {
deleteWork,
importProduct,
productExport,
} = productServiceStore;
const { workNameItems } = storeToRefs(productServiceStore);
@ -145,25 +143,27 @@ const { t } = useI18n();
const baseUrl = ref<string>(import.meta.env.VITE_API_BASE_URL);
const priceDisplay = computed(() => ({
// price: !isRoleInclude(['sale_agent']),
price: true,
price: !isRoleInclude(['sale_agent']),
agentPrice: isRoleInclude([
'system',
'head_of_admin',
'admin',
'executive',
'accountant',
'head_of_admin',
'head_of_sale',
'system',
'owner',
'accountant',
'sale_agent',
]),
serviceCharge: isRoleInclude([
'system',
'head_of_admin',
'admin',
'executive',
'head_of_admin',
'system',
'owner',
'accountant',
]),
}));
const actionDisplay = computed(() => canAccess('product', 'edit'));
const actionDisplay = computed(() =>
isRoleInclude(['admin', 'head_of_admin', 'system', 'owner', 'accountant']),
);
const splitterModel = computed(() =>
$q.screen.lt.md ? (productMode.value !== 'group' ? 0 : 100) : 25,
);
@ -1171,7 +1171,6 @@ function clearFormService() {
profileSubmit.value = false;
imageProduct.value = undefined;
profileFileImg.value = null;
serviceTab.value = 1;
}
function sameFormService() {
@ -1398,7 +1397,6 @@ function submitAddWorkProduct() {
if (!s.hasOwnProperty('productsId')) {
s.productsId = [];
}
if (s.productsId.length === 0) return;
s.productsId.push(i.id);
},
);
@ -1899,25 +1897,6 @@ 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) => {
@ -2251,6 +2230,7 @@ watch(
</AdvanceSearch>
</template>
</q-input>
<div class="row col-md-6" style="white-space: nowrap">
<q-select
v-show="$q.screen.gt.sm"
@ -2485,10 +2465,7 @@ watch(
)
"
>
{{
props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') ||
'-'
}}
{{ props.row.detail || '-' }}
</q-td>
<q-td
@ -2681,15 +2658,7 @@ watch(
{{ $t('general.recordPerPage') }}
</div>
<div>
<PaginationPageSize
v-model="pageSizeGroup"
:fetch-data="
async () => {
await fetchListGroups();
flowStore.rotate();
}
"
/>
<PaginationPageSize v-model="pageSizeGroup" />
</div>
</div>
</div>
@ -2801,13 +2770,6 @@ watch(
}
"
/>
<SaveButton
icon-only
:icon="'material-symbols:download'"
color="var(--info-bg)"
@click.stop="triggerExport()"
/>
</div>
<div class="row col-md-6" style="white-space: nowrap">
@ -3455,15 +3417,7 @@ watch(
{{ $t('general.recordPerPage') }}
</div>
<div>
<PaginationPageSize
v-model="pageSizeServiceAndProduct"
:fetch-data="
async () => {
await alternativeFetch();
flowStore.rotate();
}
"
/>
<PaginationPageSize v-model="pageSizeServiceAndProduct" />
</div>
</div>
</div>
@ -3595,7 +3549,7 @@ watch(
</div>
<div
class="col-12 col-md-10"
id="group-create"
id="customer-form-content"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-py-md q-px-lg': !$q.screen.gt.sm,
@ -4114,7 +4068,7 @@ watch(
</div>
<div
class="col-12 col-md-10"
id="product-create"
id="customer-form-content"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm,
@ -4340,7 +4294,7 @@ watch(
'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm,
}"
id="product-info"
id="customer-form-content"
style="height: 100%; max-height: 100%; overflow-y: auto"
>
<BasicInfoProduct
@ -4385,7 +4339,6 @@ watch(
<!-- add service -->
<DialogForm
v-if="dialogService"
hide-footer
no-address
no-app-box
@ -4522,11 +4475,6 @@ watch(
sub: true,
}))
"
:active="{
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#service-create"
/>
</div>
<span
@ -4549,7 +4497,7 @@ watch(
</div>
<div
class="col-12 col-md-10"
id="service-create"
id="customer-form-content"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm,
@ -4748,10 +4696,9 @@ watch(
</div>
</DialogForm>
<!-- edit service edit package-->
<!-- edit service -->
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
<DialogForm
v-if="dialogServiceEdit"
hide-footer
no-address
height="95vh"
@ -5004,7 +4951,6 @@ watch(
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#service-info"
/>
</div>
<span
@ -5027,7 +4973,7 @@ watch(
</div>
<div
class="col-12 col-md-10"
id="service-info"
id="customer-form-content"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm,

View file

@ -14,7 +14,7 @@ import { FloatingActionButton, PaginationComponent } from 'src/components';
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import PropertyDialog from './PropertyDialog.vue';
import { Property } from 'src/stores/property/types';
import { dialog, toCamelCase, canAccess } from 'src/stores/utils';
import { dialog, toCamelCase } from 'src/stores/utils';
import CreateButton from 'src/components/AddButton.vue';
import useOptionStore from 'stores/options';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
@ -331,7 +331,6 @@ watch(
</script>
<template>
<FloatingActionButton
v-if="canAccess('workflow', 'edit')"
style="z-index: 999"
hide-icon
@click="triggerDialog('add')"
@ -537,19 +536,11 @@ watch(
class="col surface-2 flex items-center justify-center"
>
<NoData
v-if="
pageState.total !== 0 ||
pageState.searchDate.length > 0 ||
!canAccess('workflow', 'edit')
"
v-if="pageState.total !== 0 || pageState.searchDate.length > 0"
:not-found="!!pageState.inputSearch"
/>
<CreateButton
v-if="
pageState.total === 0 &&
pageState.searchDate.length === 0 &&
canAccess('workflow', 'edit')
"
v-if="pageState.total === 0 && pageState.searchDate.length === 0"
@click="triggerDialog('add')"
label="general.add"
:i18n-args="{ text: $t('flow.title') }"
@ -707,7 +698,6 @@ watch(
"
/>
<KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.name"
:status="props.row.status"
@view="
@ -825,7 +815,6 @@ watch(
"
/>
<KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.id"
:status="props.row.status"
@view="
@ -917,7 +906,6 @@ watch(
@drawer-undo="() => undo()"
@close="() => resetForm()"
@submit="() => submit()"
:hide-action="!canAccess('workflow', 'edit')"
:readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit"
v-model="pageState.addModal"

View file

@ -30,7 +30,6 @@ withDefaults(
defineProps<{
readonly?: boolean;
isEdit?: boolean;
hideAction?: boolean;
}>(),
{ readonly: false, isEdit: false },
);
@ -152,7 +151,7 @@ defineEmits<{
style="position: absolute; z-index: 999; top: 0; right: 0"
>
<div
v-if="propertyData.status !== 'INACTIVE' && !hideAction"
v-if="propertyData.status !== 'INACTIVE'"
class="surface-1 row rounded"
>
<UndoButton
@ -237,7 +236,6 @@ defineEmits<{
<FormProperty
onDrawer
:readonly="!isEdit"
:disable-toggle="hideAction"
v-model:name="formProperty.name"
v-model:name-en="formProperty.nameEN"
v-model:type="formProperty.type"

View file

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref, watch, computed } from 'vue';
import { onMounted, reactive, ref, watch, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
@ -14,8 +14,7 @@ import useMyBranch from 'stores/my-branch';
import { useQuotationForm } from './form';
import { hslaColors } from './constants';
import { pageTabs, columnQuotation } from './constants';
import { toCamelCase, canAccess } from 'stores/utils';
import { getUserId } from 'src/services/keycloak';
import { toCamelCase } from 'stores/utils';
// NOTE Import Types
import { CustomerBranchCreate, CustomerType } from 'stores/customer/types';
@ -60,6 +59,7 @@ const flowStore = useFlowStore();
const userBranch = useMyBranch();
const navigatorStore = useNavigator();
const customerStore = useCustomerStore();
const {
fetchListOfOptionBranch,
customerFormUndo,
@ -75,7 +75,6 @@ const {
const {
state: customerFormState,
currentFormData: customerFormData,
currentBranchRootId,
registerAbleBranchOption,
tabFieldRequired,
} = storeToRefs(customerFormStore);
@ -89,8 +88,6 @@ const fieldSelectedOption = computed(() => {
value: v.name,
}));
});
const keyAddDialog = ref<number>(0);
const special = ref(false);
const branchId = ref('');
const agentPrice = ref<boolean>(false);
@ -107,7 +104,6 @@ const pageState = reactive({
fieldSelected: [''],
gridView: false,
total: 0,
sellerId: '',
currentTab: 'Issued',
addModal: false,
@ -275,10 +271,6 @@ const customerNameInfo = computed(() => {
return name || '-';
});
function handleWindowFocus() {
fetchQuotationList();
}
onMounted(async () => {
pageState.gridView = $q.screen.lt.md ? true : false;
navigatorStore.current.title = 'quotation.title';
@ -305,7 +297,6 @@ onMounted(async () => {
pageSize: quotationPageSize.value,
status: 'Issued',
urgentFirst: true,
sellerId: pageState.sellerId || undefined,
});
if (ret) {
@ -316,12 +307,6 @@ onMounted(async () => {
}
flowStore.rotate();
window.addEventListener('focus', handleWindowFocus);
});
onUnmounted(() => {
window.removeEventListener('focus', handleWindowFocus);
});
async function fetchQuotationList(mobileFetch?: boolean) {
@ -346,7 +331,6 @@ async function fetchQuotationList(mobileFetch?: boolean) {
urgentFirst: true,
startDate: pageState.searchDate[0],
endDate: pageState.searchDate[1],
sellerId: pageState.sellerId || undefined,
});
if (ret) {
@ -422,11 +406,6 @@ async function storeDataLocal(id: string) {
window.open(url, '_blank');
}
async function filterBySellerId() {
pageState.sellerId = pageState.sellerId ? '' : getUserId();
await fetchQuotationList();
}
</script>
<template>
@ -434,7 +413,6 @@ async function filterBySellerId() {
hide-icon
style="z-index: 999"
@click.stop="triggerAddQuotationDialog"
v-if="canAccess('quotation', 'create')"
/>
<div class="column full-height no-wrap">
@ -550,18 +528,7 @@ async function filterBySellerId() {
</template>
<template v-slot:append>
<q-separator vertical inset class="q-mr-xs" />
<AdvanceSearch v-model="pageState.searchDate">
<template #prepend>
<div class="text-weight-medium q-mb-sm">
<q-checkbox
size="xs"
:model-value="!!pageState.sellerId"
@click="filterBySellerId"
/>
{{ $t('quotation.ownOnly') }}
</div>
</template>
</AdvanceSearch>
<AdvanceSearch v-model="pageState.searchDate" />
</template>
</q-input>
@ -680,20 +647,12 @@ async function filterBySellerId() {
class="col surface-2 flex items-center justify-center"
>
<NoData
v-if="
pageState.inputSearch ||
!canAccess('quotation', 'create') ||
pageState.currentTab !== 'Issued'
"
v-if="pageState.inputSearch || pageState.currentTab !== 'Issued'"
:not-found="!!pageState.inputSearch"
/>
<CreateButton
v-if="
!pageState.inputSearch &&
pageState.currentTab === 'Issued' &&
canAccess('quotation', 'create')
"
v-if="!pageState.inputSearch && pageState.currentTab === 'Issued'"
@click="triggerAddQuotationDialog"
label="general.add"
:i18n-args="{ text: $t('quotation.title') }"
@ -723,8 +682,6 @@ async function filterBySellerId() {
:visible-columns="pageState.fieldSelected"
:grid="pageState.gridView"
:hide-edit="pageState.currentTab !== 'Issued'"
:hide-action="!canAccess('quotation', 'edit')"
:hide-delete="!canAccess('quotation', 'delete')"
:hide-btn-preview="pageState.currentTab === 'PaymentSuccess'"
@preview="(id: any) => storeDataLocal(id)"
@view="
@ -750,8 +707,7 @@ async function filterBySellerId() {
<div class="col-md-4 col-sm-6 col-12 column">
<QuotationCard
class="col"
:hide-action="!canAccess('quotation', 'edit')"
:hide-kebab-delete="!canAccess('quotation', 'delete')"
hide-kebab-delete
:hide-kebab-edit="!(pageState.currentTab === 'Issued')"
:hide-preview="pageState.currentTab === 'PaymentSuccess'"
:urgent="item.row.urgent"
@ -777,13 +733,8 @@ async function filterBySellerId() {
:worker-count="item.row._count.worker"
:worker-max="item.row.workerMax || item.row._count.worker"
:customer-name="
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 || ''}`
item.row.customerBranch.registerName ||
`${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}`
"
:reporter="
$i18n.locale === 'eng'
@ -882,7 +833,6 @@ async function filterBySellerId() {
<!-- NOTE: START - Quotation Form, Add Quotation -->
<DialogForm
:key="keyAddDialog"
:title="$t('general.add', { text: $t('quotation.title') })"
v-model:modal="pageState.addModal"
:submit-label="$t('general.add', { text: $t('quotation.title') })"
@ -892,14 +842,13 @@ async function filterBySellerId() {
:submit="
() => {
quotationFormState.mode = 'create';
quotationFormData.customerBranchId = currentBranchRootId;
triggerQuotationDialog({ statusDialog: 'create' });
}
"
:close="
() => {
branchId = '';
currentBranchRootId = '';
quotationFormData.customerBranchId = '';
}
"
:beforeClose="
@ -949,7 +898,7 @@ async function filterBySellerId() {
v-model:agent-price="agentPrice"
v-model:branch-id="branchId"
v-model:special="special"
v-model:customer-branch-id="currentBranchRootId"
v-model:customer-branch-id="quotationFormData.customerBranchId"
@add-customer="triggerSelectTypeCustomerd()"
/>
</div>
@ -1018,7 +967,6 @@ async function filterBySellerId() {
() => {
customerFormState.dialogModal = false;
onCreateImageList = { selectedImage: '', list: [] };
keyAddDialog++;
}
"
>
@ -1210,7 +1158,7 @@ async function filterBySellerId() {
customerFormData.customerBranch[0].legalPersonNo
"
v-model:business-type="
customerFormData.customerBranch[0].businessTypeId
customerFormData.customerBranch[0].businessType
"
v-model:job-position="
customerFormData.customerBranch[0].jobPosition
@ -1291,6 +1239,7 @@ async function filterBySellerId() {
res = await customerStore.createBranch({
...customerFormData.customerBranch[idx],
customerId: customerFormState.editCustomerId || '',
id: undefined,
});
}
if (res) {

File diff suppressed because it is too large Load diff

View file

@ -2,15 +2,12 @@
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { QSelect, useQuasar } from 'quasar';
import { getUserId } from 'src/services/keycloak';
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
import {
baseUrl,
dialogCheckData,
dialogWarningClose,
formatNumberDecimal,
canAccess,
isRoleInclude,
} from 'stores/utils';
import { ProductTree, quotationProductTree } from './utils';
@ -79,8 +76,7 @@ import { api } from 'src/boot/axios';
import { RouterLink, useRoute } from 'vue-router';
import { initLang, initTheme, Lang } from 'src/utils/ui';
import { convertTemplate } from 'src/utils/string-template';
import { CustomerBranch } from 'src/stores/customer';
import { getRole } from 'src/services/keycloak';
type Node = {
[key: string]: any;
@ -96,8 +92,6 @@ type ProductGroupId = string;
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const customerBranchOption = ref<CustomerBranch>();
const employeeStore = useEmployeeStore();
const route = useRoute();
const useReceiptStore = useReceipt();
@ -161,7 +155,49 @@ const selectedWorker = ref<
}[];
})[]
>([]);
const selectedWorkerItem = ref([]);
const selectedWorkerItem = computed(() => {
return [
...selectedWorker.value.map((e) => ({
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: e.firstName
? `${e.firstName} ${e.lastName}`
: `${e.firstNameEN} ${e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
status: e.status,
})),
...newWorkerList.value.map((v: any) => ({
foreignRefNo: v.passportNo,
employeeName:
locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName} ${v.lastName}`,
birthDate: dateFormatJS({ date: v.dateOfBirth }),
gender: v.gender,
age: calculateAge(v.dateOfBirth),
nationality: optionStore.mapOption(v.nationality),
documentExpireDate: '-',
imgUrl: '',
status: 'CREATED',
})),
];
});
const firstCodePayment = ref('');
const selectedProductGroup = ref('');
const selectedInstallmentNo = ref<number[]>([]);
@ -177,6 +213,17 @@ const attachmentData = ref<
url?: string;
}[]
>([]);
const hideBtnApproveInvoice = computed(() => {
const role = getRole();
const allowedRoles = [
'system',
'head_of_admin',
'admin',
'head_of_accountant',
'accountant',
];
return !role || !role.some((r) => allowedRoles.includes(r));
});
const getToolbarConfig = computed(() => {
const toolbar = [['left', 'center', 'justify'], ['toggle'], ['clip']];
@ -196,7 +243,7 @@ function getPrice(
) {
if (filterHook) list = list.filter(filterHook);
const value = list.reduce(
return list.reduce(
(a, c) => {
if (
selectedInstallmentNo.value.length > 0 &&
@ -206,25 +253,32 @@ function getPrice(
return a;
}
const originalPrice = c.pricePerUnit;
const finalPriceWithVat = precisionRound(
originalPrice * (1 + (config.value?.vat || 0.07)),
);
const finalPriceNoVat =
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
const price = finalPriceNoVat * c.amount;
const vat =
(finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07);
const calcVat =
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
const pricePerUnit =
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
const price =
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
(1 + vatFactor);
const vat = price * vatFactor;
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
a.vat = precisionRound(a.vat + vat);
a.totalPrice = precisionRound(a.totalPrice + price);
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
a.vat = calcVat ? precisionRound(a.vat + vat) : a.vat;
a.vatExcluded = calcVat
? a.vatExcluded
: precisionRound(a.vatExcluded + price);
a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
a.finalPrice = precisionRound(
a.totalPrice -
a.totalDiscount +
a.vat -
Number(quotationFormData.value.discount || 0),
);
return a;
},
@ -236,8 +290,6 @@ function getPrice(
finalPrice: 0,
},
);
return value;
}
const summaryPrice = computed(() => getPrice(productServiceList.value));
@ -516,7 +568,7 @@ async function convertDataToFormSubmit() {
),
);
selectedWorkerItem.value.forEach((v, i) => {
selectedWorker.value.forEach((v, i) => {
if (v.attachment !== undefined) {
v.attachment.forEach((value) => {
fileItemNewWorker.value.push({
@ -533,7 +585,7 @@ async function convertDataToFormSubmit() {
quotationFormData.value.worker = JSON.parse(
JSON.stringify([
...selectedWorkerItem.value.map((v) => {
...selectedWorker.value.map((v) => {
{
return v.id;
}
@ -573,7 +625,6 @@ async function convertDataToFormSubmit() {
discount: quotationFormData.value.discount,
remark: quotationFormData.value.remark || '',
agentPrice: agentPrice.value,
sellerId: quotationFormData.value.sellerId,
};
newWorkerList.value = [];
@ -676,13 +727,19 @@ function handleUpdateProductTable(
// handleChangePayType(quotationFormData.value.payCondition);
// calc price
const calc = (c: QuotationPayload['productServiceList'][number]) => {
const calcVat =
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
const pricePerUnit =
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
const price = pricePerUnit * c.amount * (1 + vatFactor) - c.discount;
return precisionRound(price);
const originalPrice = c.pricePerUnit || 0;
const finalPriceWithVat = precisionRound(
originalPrice * (1 + (config.value?.vat || 0.07)),
);
const finalPriceNoVat =
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
const price = finalPriceNoVat * c.amount;
const vat = c.product.calcVat
? (finalPriceNoVat * c.amount - (c.discount || 0)) *
(config.value?.vat || 0.07)
: 0;
return precisionRound(price + vat);
};
// installment
@ -735,16 +792,21 @@ function toggleDeleteProduct(index: number) {
// cal curr amount
if (currPaySplit && currTempPaySplit) {
const calcVat =
currProduct.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
const price = agentPrice.value
? currProduct.product.agentPrice
: currProduct.product.price;
const pricePerUnit = currProduct.product.vatIncluded
? price / (1 + (config.value?.vat || 0.07))
: price;
const vat =
(pricePerUnit * currProduct.amount - currProduct.discount) *
(config.value?.vat || 0.07);
const finalPrice =
pricePerUnit * currProduct.amount +
vat -
Number(currProduct.discount || 0);
const price = precisionRound(
currProduct.pricePerUnit * currProduct.amount * (1 + vatFactor) -
currProduct.discount,
);
currTempPaySplit.amount = currPaySplit.amount - price;
currTempPaySplit.amount = currPaySplit.amount - finalPrice;
currPaySplit.amount = currTempPaySplit.amount;
}
@ -766,40 +828,7 @@ function toggleDeleteProduct(index: number) {
}
async function assignWorkerToSelectedWorker() {
selectedWorkerItem.value = quotationFormData.value.worker.map((e) => {
return {
id: e.id,
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
employeePassport: e.employeePassport,
status: e.status,
workerNew: false,
lastNameEN: e.lastNameEN,
lastName: e.lastName,
middleNameEN: e.middleNameEN,
middleName: e.middleName,
firstNameEN: e.firstNameEN,
firstName: e.firstName,
namePrefix: e.namePrefix,
};
});
selectedWorker.value = quotationFormData.value.worker;
}
function convertToTable(nodes: Node[]) {
@ -832,7 +861,8 @@ function convertToTable(nodes: Node[]) {
tempTableProduct.value = JSON.parse(JSON.stringify(list));
if (nodes.length > 0) {
quotationFormData.value.paySplit = Array.from(
quotationFormData.value.paySplit = Array.apply(
null,
new Array(quotationFormData.value.paySplitCount),
).map((_, i) => ({
no: i + 1,
@ -858,21 +888,21 @@ function convertToTable(nodes: Node[]) {
function convertEmployeeToTable(selected: Employee[]) {
productServiceList.value.forEach((v) => {
if (selectedWorkerItem.value.length === 0 && v.amount === 1) v.amount -= 1;
if (selectedWorker.value.length === 0 && v.amount === 1) v.amount -= 1;
v.amount = Math.max(
v.amount + selected.length - selectedWorkerItem.value.length,
v.amount + selected.length - selectedWorker.value.length,
1,
);
const oldWorkerId: string[] = [];
const newWorkerIndex: number[] = [];
selectedWorkerItem.value.forEach((item, i) => {
selectedWorker.value.forEach((item, i) => {
if (v.workerIndex.includes(i)) oldWorkerId.push(item.id);
});
selected.forEach((item, i) => {
if (selectedWorkerItem.value.find((n) => item.id === n.id)) return;
if (selectedWorker.value.find((n) => item.id === n.id)) return;
newWorkerIndex.push(i);
});
@ -885,7 +915,7 @@ function convertEmployeeToTable(selected: Employee[]) {
pageState.employeeModal = false;
quotationFormData.value.workerMax = Math.max(
quotationFormData.value.workerMax || 1,
selectedWorkerItem.value.length,
selectedWorker.value.length,
);
}
@ -958,71 +988,6 @@ function viewProductFile(data: ProductRelation) {
pageState.imageDialogUrl = base64 ? base64[1] : '';
}
function combineWorker(newWorker: any, oldWorker: any) {
selectedWorkerItem.value = [
...oldWorker.map((e) => ({
id: e.id,
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
employeePassport: e.employeePassport,
status: e.status,
workerNew: false,
lastNameEN: e.lastNameEN,
lastName: e.lastName,
middleNameEN: e.middleNameEN,
middleName: e.middleName,
firstNameEN: e.firstNameEN,
firstName: e.firstName,
namePrefix: e.namePrefix,
})),
...newWorker.map((v: any) => ({
id: v.id,
foreignRefNo: v.passportNo || '-',
employeeName:
locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
birthDate: dateFormatJS({ date: v.dateOfBirth }),
gender: v.gender,
age: calculateAge(v.dateOfBirth),
nationality: optionStore.mapOption(v.nationality),
documentExpireDate: '-',
imgUrl: '',
status: 'CREATED',
lastNameEN: v.lastNameEN,
lastName: v.lastName,
middleNameEN: v.middleNameEN,
middleName: v.middleName,
firstNameEN: v.firstNameEN,
firstName: v.firstName,
namePrefix: v.namePrefix,
dateOfBirth: v.dateOfBirth,
workerNew: true,
})),
];
}
const sessionData = ref<Record<string, any>>();
onMounted(async () => {
@ -1059,7 +1024,6 @@ onMounted(async () => {
quotationFormData.value.customerBranchId = parsed.customerBranchId;
currentQuotationId.value = parsed.quotationId;
agentPrice.value = parsed.agentPrice;
quotationFormData.value.sellerId = getUserId();
await fetchQuotation();
await assignWorkerToSelectedWorker();
sessionData.value = parsed;
@ -1094,7 +1058,7 @@ watch(
() => quotationFormData.value.customerBranchId,
async (v) => {
if (!v) return;
selectedWorkerItem.value = [];
selectedWorker.value = [];
},
);
@ -1110,15 +1074,6 @@ watch(
const productServiceNodes = ref<ProductTree>([]);
watch(customerBranchOption, () => {
if (!customerBranchOption.value) return;
quotationFormData.value.contactName =
customerBranchOption.value.contactName || '';
quotationFormData.value.contactTel =
customerBranchOption.value.contactTel || '';
});
watch(
() => productServiceList.value,
() => {
@ -1126,13 +1081,6 @@ watch(
},
);
watch(customerBranchOption, () => {
if (!customerBranchOption.value) return;
quotationFormData.value.contactName = customerBranchOption.value.contactName;
quotationFormData.value.contactTel = customerBranchOption.value.contactTel;
});
// async function searchEmployee(text: string) {
// let query: string | undefined = text;
// let pageSize = 50;
@ -1154,19 +1102,7 @@ watch(customerBranchOption, () => {
// }
function storeDataLocal() {
const tempProductService = productService.value.map((v) => {
return {
...v,
vat: v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? precisionRound(
((v.pricePerUnit * (1 + (config?.value.vat || 0.07)) * v.amount -
v.discount) /
(1 + (config?.value.vat || 0.07))) *
0.07,
)
: 0,
};
});
quotationFormData.value.productServiceList = productService.value;
localStorage.setItem(
'quotation-preview',
@ -1175,7 +1111,7 @@ function storeDataLocal() {
codeInvoice: code.value,
codePayment: firstCodePayment.value,
...quotationFormData.value,
productServiceList: tempProductService,
productServiceList: productService.value,
},
meta: {
source: {
@ -1195,7 +1131,7 @@ function storeDataLocal() {
workName: quotationFormData.value.workName,
dueDate: quotationFormData.value.dueDate,
},
selectedWorker: selectedWorkerItem.value,
selectedWorker: selectedWorker.value,
createdBy: quotationFormState.value.createdBy('tha'),
agentPrice: agentPrice.value,
},
@ -1280,10 +1216,10 @@ async function getWorkerFromCriteria(
if (!ret) return false; // error, do not close dialog
const deduplicate = ret.result.filter(
(a) => !selectedWorkerItem.value.find((b) => a.id === b.id),
(a) => !selectedWorker.value.find((b) => a.id === b.id),
);
convertEmployeeToTable([...deduplicate, ...selectedWorkerItem.value]);
convertEmployeeToTable([...deduplicate, ...selectedWorker.value]);
return true;
}
@ -1579,7 +1515,6 @@ function covertToNode() {
v-model:contactor="quotationFormData.contactName"
v-model:telephone="quotationFormData.contactTel"
v-model:due-date="quotationFormData.dueDate"
v-model:seller-id="quotationFormData.sellerId"
>
<template #issue-info>
<FormAbout
@ -1590,7 +1525,6 @@ function covertToNode() {
v-model:customer-branch-id="
quotationFormData.customerBranchId
"
v-model:customer-branch-option="customerBranchOption"
:readonly="readonly"
/>
</template>
@ -1635,7 +1569,7 @@ function covertToNode() {
}}
</template>
</div>
<nav v-if="canAccess('quotation', 'edit')" class="q-ml-auto">
<nav class="q-ml-auto">
<AddButton
id="btn-add-worker"
for="btn-add-worker"
@ -1673,15 +1607,15 @@ function covertToNode() {
(v) =>
(quotationFormData.workerMax = Math.max(
v,
selectedWorkerItem.length,
selectedWorker.length,
))
"
:employee-amount="
quotationFormData.workerMax || selectedWorkerItem.length
quotationFormData.workerMax || selectedWorker.length
"
:readonly="readonly"
:rows="selectedWorkerItem"
@delete="(i) => deleteItem(selectedWorkerItem, i)"
@delete="(i) => deleteItem(selectedWorker, i)"
/>
</div>
</q-expansion-item>
@ -1812,9 +1746,7 @@ function covertToNode() {
:readonly="
{
quotation: quotationFormState.mode !== 'edit',
invoice:
isRoleInclude(['sale', 'head_of_sale']) ||
!canAccess('quotation', 'edit'),
invoice: false,
accepted: true,
}[view]
"
@ -1925,10 +1857,10 @@ function covertToNode() {
installments: quotationFormData.paySplit,
},
'quotation-labor': {
name: selectedWorkerItem.map(
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(),
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''} ${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
),
},
})
@ -2018,11 +1950,6 @@ function covertToNode() {
view !== View.Receipt &&
view !== View.Complete
"
:branch-id="quotationFull.registeredBranchId"
:readonly="
isRoleInclude(['sale', 'head_of_sale']) ||
!canAccess('quotation', 'edit')
"
:data="quotationFormState.source"
v-model:first-code-payment="firstCodePayment"
@fetch-status="
@ -2161,14 +2088,7 @@ function covertToNode() {
:style="`background-color:hsla(var(--info-bg) / 0.07)`"
>
<q-th auto-width>
<q-checkbox
v-if="
!quotationFormData.paySplit.every(
(p) => p.invoiceId,
)
"
v-model="props.selected"
/>
<q-checkbox v-model="props.selected" />
</q-th>
<q-th
v-for="col in props.cols"
@ -2198,6 +2118,8 @@ function covertToNode() {
installmentAmount = props.row.amount;
view = View.Invoice;
console.log(code);
}
}
"
@ -2359,7 +2281,6 @@ function covertToNode() {
class="q-ml-sm"
v-if="
view === View.Accepted &&
canAccess('quotation', 'edit') &&
quotationFormData.quotationStatus === 'Issued'
"
>
@ -2378,15 +2299,9 @@ function covertToNode() {
</MainButton>
</div>
<template
v-if="
view === View.InvoicePre &&
!quotationFormData.paySplit.every((p) => p.invoiceId)
"
>
<template v-if="view === View.InvoicePre">
<MainButton
solid
:disabled="selectedInstallment.length === 0"
icon="mdi-account-multiple-check-outline"
class="q-ml-sm"
color="207 96% 32%"
@ -2405,7 +2320,6 @@ function covertToNode() {
class="q-ml-sm"
v-if="
view === View.Invoice &&
canAccess('quotation', 'edit') &&
((quotationFormData.quotationStatus !== 'PaymentPending' &&
quotationFormData.payCondition !== 'Full') ||
quotationFormData.quotationStatus === 'Accepted') &&
@ -2413,6 +2327,7 @@ function covertToNode() {
"
>
<MainButton
v-if="!hideBtnApproveInvoice"
solid
icon="mdi-account-multiple-check-outline"
color="207 96% 32%"
@ -2432,7 +2347,6 @@ function covertToNode() {
style="gap: var(--size-2)"
v-if="
(view === View.Quotation &&
canAccess('quotation', 'edit') &&
(quotationFormData.quotationStatus === 'Issued' ||
quotationFormData.quotationStatus === 'Expired')) ||
!quotationFormData.quotationStatus
@ -2469,12 +2383,13 @@ function covertToNode() {
<!-- add employee quotation -->
<QuotationFormWorkerSelect
:preselect-worker="selectedWorkerItem"
:preselect-worker="selectedWorker"
:customerBranchId="quotationFormData.customerBranchId"
v-model:open="pageState.employeeModal"
v-model:new-worker-list="newWorkerList"
@success="
(v) => {
combineWorker(v.newWorker, v.worker);
selectedWorker = v.worker;
}
"
/>
@ -2517,7 +2432,7 @@ function covertToNode() {
<!-- add Worker -->
<QuotationFormWorkerAddDialog
v-if="quotationFormState.source"
:disabled-worker-id="selectedWorkerItem.map((v) => v.id)"
:disabled-worker-id="selectedWorker.map((v) => v.id)"
:product-service-list="quotationFormState.source.productServiceList"
:quotation-id="quotationFormState.source.id"
:customer-branch-id="quotationFormState.source.customerBranchId"

View file

@ -234,14 +234,14 @@ watch(
<section class="row q-col-gutter-sm col-12 items-center">
<SelectInput
class="col-md-6 col-12"
id="select-pay-type"
:label="$t('quotation.payType')"
:option="
taskOrder
? payTypeOption.filter((v) => v.value === 'Full')
: payTypeOption
"
:readonly="readonly || debitNote"
:readonly
id="pay-type"
:model-value="payType"
@update:model-value="
(v) => {
@ -275,7 +275,6 @@ watch(
</div>
<q-input
v-model="paySplitCount"
id="input-pay-split-count"
:readonly="readonly || payType === 'Split'"
class="col-3"
type="number"
@ -312,7 +311,6 @@ watch(
<q-input
:readonly="readonly"
:label="$t('general.name')"
:id="`input-period-name-${i}`"
v-if="payType === 'SplitCustom'"
v-model="period.name"
class="col q-mx-sm"
@ -322,7 +320,6 @@ watch(
<q-input
:readonly="readonly || payType === 'Split'"
class="col q-mx-sm"
:id="`input-period-amount-${i}`"
:label="$t('quotation.amount')"
:model-value="
amount4Show[i] || commaInput(period.amount.toString())
@ -380,7 +377,6 @@ watch(
<DatePicker
v-if="payType === 'BillFull'"
id="datepicker-bill-date"
:readonly
class="col-12"
:label="$t('quotation.callDueDate')"
@ -450,9 +446,7 @@ watch(
<span class="q-ml-auto">
{{
formatNumberDecimal(
summaryPrice.totalPrice -
summaryPrice.totalDiscount -
summaryPrice.vatExcluded,
summaryPrice.totalPrice - summaryPrice.totalDiscount,
2,
)
}}
@ -488,11 +482,7 @@ watch(
<div class="q-pa-sm row surface-2 items-center text-weight-bold">
{{ $t('quotation.totalPriceBaht') }}
<span
class="q-ml-auto"
style="color: var(--brand-1)"
id="value-final-price"
>
<span class="q-ml-auto" style="color: var(--brand-1)">
{{
payType === 'SplitCustom' && view === View.Invoice
? formatNumberDecimal(Math.max(installmentAmount || 0, 0), 2) || 0

View file

@ -1,6 +1,5 @@
<script setup lang="ts">
import DatePicker from 'src/components/shared/DatePicker.vue';
import SelectUser from 'src/components/shared/select/SelectUser.vue';
defineProps<{
readonly: boolean;
@ -14,7 +13,6 @@ const contactor = defineModel<string>('contactor', { required: true });
const telephone = defineModel<string>('telephone', { required: true });
const dueDate = defineModel<Date | string>('dueDate', { required: true });
const createdAt = defineModel<Date | string>('createdAt');
const sellerId = defineModel<string>('sellerId', { required: true });
</script>
<template>
@ -97,11 +95,5 @@ const sellerId = defineModel<string>('sellerId', { required: true });
dense
outlined
/>
<SelectUser
:label="$t('preview.seller')"
v-model:value="sellerId"
:readonly
class="col-12 col-md-2"
/>
</div>
</template>

View file

@ -51,8 +51,6 @@ const emit = defineEmits<{
const selectedProductGroup = defineModel<string>('selectedProductGroup', {
default: '',
});
const selectedProductGroupOption = ref<ProductGroup | undefined>();
const model = defineModel<boolean>();
const inputSearch = defineModel<string>('inputSearch');
const productGroup = defineModel<ProductGroup[]>('productGroup', {
@ -68,21 +66,21 @@ const serviceList = defineModel<Partial<Record<ProductGroupId, Service[]>>>(
);
const priceDisplay = computed(() => ({
// price: !isRoleInclude(['sale_agent']),
price: true,
price: !isRoleInclude(['sale_agent']),
agentPrice: isRoleInclude([
'system',
'head_of_admin',
'admin',
'executive',
'accountant',
'head_of_admin',
'head_of_sale',
'system',
'owner',
'accountant',
'sale_agent',
]),
serviceCharge: isRoleInclude([
'system',
'head_of_admin',
'admin',
'executive',
'head_of_admin',
'system',
'owner',
'accountant',
]),
}));
@ -571,18 +569,14 @@ watch(
{{
productGroup.find(
(g) => g.id === selectedProductGroup,
)?.name ||
selectedProductGroupOption?.name ||
'-'
)?.name || '-'
}}
</span>
<span class="text-caption app-text-muted">
{{
productGroup.find(
(g) => g.id === selectedProductGroup,
)?.code ||
selectedProductGroupOption?.code ||
'-'
)?.code || '-'
}}
</span>
</div>
@ -868,13 +862,13 @@ watch(
<span class="q-pr-sm">
{{ $t('productService.group.title') }}
</span>
<SelectProductGroup
class="col-md-4 col-12"
:class="{ 'q-mb-sm': $q.screen.lt.md }"
id="product-group-select"
clearable
v-model:value="selectedProductGroup"
v-model:value-option="selectedProductGroupOption"
:placeholder="
!selectedProductGroup
? $t('general.select', {

View file

@ -341,13 +341,12 @@ watch(() => state.search, getWorkerList);
>
<div
style="display: inline-block; margin-inline: auto"
v-if="workerList.length === 0 && state.search"
v-if="workerList.length === 0"
>
<NoData :not-found="!!state.search" />
</div>
<TableWorker
v-else
v-model:selected="workerSelected"
:rows="workerList"
:disabledWorkerId

View file

@ -21,7 +21,6 @@ import useOcrStore from 'stores/ocr';
// NOTE: Import Components
import {
AddButton,
SaveButton,
EditButton,
UndoButton,
@ -54,9 +53,6 @@ import { SideMenu } from 'src/components';
import BasicInformation from 'components/03_customer-management/employee/BasicInformation.vue';
import { AddressForm } from 'src/components/form';
import ExpirationDate from 'src/components/03_customer-management/ExpirationDate.vue';
import FormEmployeeHealthCheck from 'src/components/03_customer-management/FormEmployeeHealthCheck.vue';
import FormEmployeeWorkHistory from 'src/components/03_customer-management/FormEmployeeWorkHistory.vue';
import FormEmployeeOther from 'src/components/03_customer-management/FormEmployeeOther.vue';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
@ -113,7 +109,7 @@ const props = withDefaults(
defineProps<{
customerBranchId?: string;
disabledWorkerId?: string[];
preselectWorker?: (Employee & { workerNew: boolean })[];
preselectWorker?: Employee[];
}>(),
{},
);
@ -133,7 +129,7 @@ const optionStore = useOptionStore();
const employeeStore = useEmployeeStore();
const open = defineModel<boolean>('open', { default: false });
const newWorkerList = ref<
const newWorkerList = defineModel<
(EmployeeWorker & {
attachment?: {
name?: string;
@ -143,7 +139,7 @@ const newWorkerList = ref<
_meta?: Record<string, any>;
}[];
})[]
>([]);
>('newWorkerList', { default: [] });
const workerSelected = ref<Employee[]>([]);
const workerList = ref<Employee[]>([]);
const importWorkerCriteria = ref<{
@ -208,13 +204,7 @@ function getEmployeeImageUrl(item: Employee) {
function init() {
if (props.preselectWorker) {
workerSelected.value = JSON.parse(
JSON.stringify(props.preselectWorker.filter((v) => !v.workerNew)),
);
newWorkerList.value = JSON.parse(
JSON.stringify(props.preselectWorker.filter((v) => v.workerNew)),
);
workerSelected.value = JSON.parse(JSON.stringify(props.preselectWorker));
}
getWorkerList();
}
@ -614,14 +604,11 @@ watch(
solid
id="btn-success"
@click="
() => {
$emit('success', {
worker: workerSelected,
newWorker: newWorkerList,
});
open = false;
}
emits('success', {
worker: workerSelected,
newWorker: newWorkerList,
}),
(open = false)
"
>
{{ $t('general.select', { msg: $t('quotation.employeeList') }) }}
@ -643,11 +630,9 @@ watch(
if (employeeFormState.currentTab === 'personalInfo') {
const currentEmployeeId =
await employeeFormStore.submitPersonal(onCreateImageList);
newWorkerList.push(
quotationForm.injectNewEmployee({
data: { ...currentFromDataEmployee, id: currentEmployeeId },
}),
);
quotationForm.injectNewEmployee({
data: { ...currentFromDataEmployee, id: currentEmployeeId },
});
employeeFormState.isEmployeeEdit = false;
employeeFormState.dialogType = 'info';
}
@ -678,7 +663,6 @@ watch(
:show="
() => {
employeeFormStore.resetFormDataEmployee(true);
setCurrentBranchId();
}
"
:before-close="
@ -1051,7 +1035,6 @@ watch(
</div>
<BasicInformation
disable-customer-select
no-action
id="form-information"
prefix-id="form-employee"
@ -1472,7 +1455,6 @@ watch(
v-model:remark="meta.remark"
v-model:worker-type="meta.workerType"
v-model:number="meta.number"
v-model:report-date="meta.reportDate"
/>
<NoticeJobEmployment v-if="mode === 'noticeJobEmployment'" />
@ -1699,7 +1681,6 @@ watch(
v-model:remark="value.remark"
v-model:worker-type="value.workerType"
v-model:number="value.number"
v-model:report-date="value.reportDate"
>
<template v-slot:expiryDate>
{{ $t('general.expirationDate') }} :
@ -2010,8 +1991,8 @@ watch(
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}
@ -2028,9 +2009,9 @@ watch(
}
:deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
.q-focus-helper
) {
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
.q-focus-helper
) {
visibility: hidden;
}

View file

@ -8,7 +8,6 @@ import {
QuotationPayload,
QuotationFull,
EmployeeWorker,
PayCondition,
} from 'src/stores/quotations/types';
import { Employee } from 'src/stores/employee/types';
@ -30,7 +29,7 @@ export const DEFAULT_DATA: QuotationPayload = {
payBillDate: new Date(),
paySplit: [],
paySplitCount: 0,
payCondition: PayCondition.Full,
payCondition: 'Full',
dueDate: new Date(Date.now() + 86400000),
discount: 0,
contactTel: '',
@ -41,7 +40,6 @@ export const DEFAULT_DATA: QuotationPayload = {
status: 'CREATED',
remark: '#[quotation-labor]<br/><br/>#[quotation-payment]',
agentPrice: false,
sellerId: '',
};
const DEFAULT_DATA_INVOICE: InvoicePayload = {
@ -69,7 +67,6 @@ export const useQuotationForm = defineStore('form-quotation', () => {
file?: File;
_meta?: Record<string, any>;
}[];
workerNew: boolean;
})[]
>([]);
@ -221,7 +218,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
},
callback?: () => void,
) {
const temp = {
newWorkerList.value.push({
//passportNo: obj.data.passportNo,
//documentExpireDate: obj.data.documentExpireDate,
id: obj.data.id,
@ -236,12 +233,9 @@ export const useQuotationForm = defineStore('form-quotation', () => {
gender: obj.data.gender,
dateOfBirth: obj.data.dateOfBirth,
attachment: obj.data.attachment,
workerNew: true,
};
});
callback?.();
return temp;
}
function dialogDelete(callback: () => void) {

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { onMounted, nextTick, ref, watch, toRaw } from 'vue';
import { onMounted, nextTick, ref, watch } from 'vue';
import { precisionRound } from 'src/utils/arithmetic';
import { useI18n } from 'vue-i18n';
import ThaiBahtText from 'thai-baht-text';
@ -175,8 +175,6 @@ enum View {
const view = ref<View>(View.Quotation);
onMounted(async () => {
await configStore.getConfig();
const currentDocumentType = new URL(window.location.href).searchParams.get(
'type',
);
@ -261,6 +259,18 @@ onMounted(async () => {
productList.value =
(data?.value?.productServiceList ?? data.value?.productServiceList).map(
(v) => {
const originalPrice = v.pricePerUnit;
const finalPriceWithVat = precisionRound(
originalPrice * (1 + (config.value?.vat || 0.07)),
);
const finalPriceNoVat =
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
const price = finalPriceNoVat * v.amount - v.discount;
const vat =
(finalPriceNoVat * v.amount - v.discount) *
(config.value?.vat || 0.07);
return {
id: v.product.id,
code: v.product.code,
@ -269,8 +279,8 @@ onMounted(async () => {
pricePerUnit: v.pricePerUnit || 0,
discount: v.discount || 0,
vat: v.vat || 0,
value: 0,
calcVat: v.vat > 0,
value: precisionRound(price + (v.product.calcVat ? vat : 0)),
calcVat: v.product.calcVat,
product: v.product,
};
},
@ -282,17 +292,23 @@ onMounted(async () => {
[]
).reduce(
(a, c) => {
const calcVat = c.vat > 0;
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
const pricePerUnit =
precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
const price =
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
(1 + vatFactor);
const originalPrice = c.pricePerUnit;
const finalPriceWithVat = precisionRound(
originalPrice * (1 + (config.value?.vat || 0.07)),
);
const finalPriceNoVat =
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
const price = finalPriceNoVat * c.amount;
const vat =
(finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07);
const calcVat =
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
a.totalPrice = precisionRound(a.totalPrice + price);
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
a.vat = calcVat ? precisionRound(a.vat + c.vat) : a.vat;
a.vat = calcVat ? precisionRound(a.vat + vat) : a.vat;
a.vatExcluded = calcVat
? a.vatExcluded
: precisionRound(a.vatExcluded + price);
@ -318,12 +334,16 @@ onMounted(async () => {
function calcPrice(c: Product) {
const originalPrice = c.pricePerUnit;
const finalPricePerUnit = precisionRound(
originalPrice +
(c.calcVat ? originalPrice * (config.value?.vat || 0.07) : 0),
const finalPriceWithVat = precisionRound(
originalPrice + originalPrice * (config.value?.vat || 0.07),
);
const price = finalPricePerUnit * c.amount - c.discount;
return precisionRound(price);
const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
const price = finalPriceNoVat * c.amount - c.discount;
const vat = c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat']
? (finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07)
: 0;
return precisionRound(price + vat);
}
async function closeTab() {
@ -407,15 +427,31 @@ function print() {
<td>{{ v.detail }}</td>
<td style="text-align: right">{{ v.amount }}</td>
<td style="text-align: right">
{{ formatNumberDecimal(v.pricePerUnit, 2) }}
{{
formatNumberDecimal(
v.pricePerUnit +
(v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? v.pricePerUnit * (config?.vat || 0.07)
: 0),
2,
)
}}
</td>
<td style="text-align: right">
<template v-if="v.discount !== 0">
{{ formatNumberDecimal(v.discount, 2) }} ฿
</template>
{{ formatNumberDecimal(v.discount, 2) }}
</td>
<td style="text-align: right">
{{ Math.round((v.vat > 0 ? config?.vat || 0.07 : 0) * 100) }}%
{{
formatNumberDecimal(
v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? precisionRound(
(v.pricePerUnit * v.amount - v.discount) *
(config?.vat || 0.07),
)
: 0,
2,
)
}}
</td>
<td style="text-align: right">
{{ formatNumberDecimal(calcPrice(v), 2) }}
@ -475,9 +511,7 @@ function print() {
<td class="text-right">
{{
formatNumberDecimal(
summaryPrice.totalPrice -
summaryPrice.totalDiscount -
summaryPrice.vatExcluded,
summaryPrice.totalPrice - summaryPrice.totalDiscount,
2,
)
}}
@ -566,7 +600,7 @@ function print() {
details?.worker.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(),
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
) || [],
},
}) || '-'

View file

@ -3,7 +3,6 @@ import { dateFormat } from 'src/utils/datetime';
// NOTE: Import stores
import { formatAddress } from 'src/utils/address';
import { getCustomerName } from 'src/stores/utils';
// NOTE Import Types
import { Branch } from 'src/stores/branch/types';
@ -79,15 +78,12 @@ function titleMode(mode: View): string {
})
}}
</span>
<span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
<span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
<span>เลขประจำตวผเสยภาษ {{ branch.taxNo }}</span>
<span>เบอรโทร {{ branch.telephoneNo }}</span>
<span>{{ branch.webUrl }}</span>
</article>
<article>
<b>{{ $t('quotation.customer') }}</b>
<div>
{{ getCustomerName(customer, { noCode: true, locale: 'tha' }) }}
</div>
<span>
{{
formatAddress({
@ -105,18 +101,8 @@ function titleMode(mode: View): string {
})
}}
</span>
<span>
{{
customer.customer.customerType === 'CORP'
? `${$t('customer.form.legalPersonNo')} `
: `${$t('customer.form.taxpayyerNo')} `
}}{{
customer.customer.customerType === 'CORP'
? customer.codeCustomer
: customer.citizenId
}}
</span>
<span>{{ $t('taskOrder.telephone') }} {{ customer.telephoneNo }}</span>
<span>เลขประจำตวผเสยภาษ {{ customer.citizenId }}</span>
<span>เบอรโทร {{ customer.telephoneNo }}</span>
</article>
</section>
<section class="detail-quotation-info">

View file

@ -62,8 +62,6 @@ const props = withDefaults(
defineProps<{
readonly?: boolean;
isEdit?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
dataId?: string;
}>(),
@ -82,7 +80,6 @@ const emit = defineEmits<{
(e: 'addImage'): void;
(e: 'removeImage'): void;
(e: 'submitImage', name: string): void;
(e: 'deleteAttachment', name: string): void;
}>();
const data = defineModel<InstitutionPayload>('data', {
@ -122,9 +119,6 @@ const formBankBook = defineModel<BankBook[]>('formBankBook', {
},
],
});
const attachment = defineModel<File[]>('attachment');
const attachmentList =
defineModel<{ name: string; url: string }[]>('attachmentList');
function viewImage() {
imageState.imageDialog = true;
@ -352,7 +346,6 @@ watch(
v-model:contact-name="data.contactName"
v-model:email="data.contactEmail"
v-model:contact-tel="data.contactTel"
v-model:attachment="attachment"
/>
<AddressForm
id="agencies-form-address-info"
@ -418,7 +411,6 @@ watch(
:prefix="data.name"
hide-fade
use-toggle
:readonly="hideAction"
:active="data.status !== 'INACTIVE'"
:toggle-title="$t('status.title')"
:icon="'ph-building-office'"
@ -458,7 +450,7 @@ watch(
style="position: absolute; z-index: 999; top: 0; right: 0"
>
<div
v-if="data.status !== 'INACTIVE' && !hideAction"
v-if="data.status !== 'INACTIVE'"
class="surface-1 row rounded"
>
<UndoButton
@ -492,7 +484,7 @@ watch(
type="button"
/>
<DeleteButton
v-if="!isEdit && !hideDelete"
v-if="!isEdit"
id="btn-info-basic-delete"
icon-only
@click="
@ -555,9 +547,6 @@ watch(
v-model:contact-name="data.contactName"
v-model:email="data.contactEmail"
v-model:contact-tel="data.contactTel"
v-model:attachment="attachment"
:attachment-list="attachmentList"
@delete-attachment="(name) => $emit('deleteAttachment', name)"
/>
<AddressForm
id="agencies-address-info"
@ -608,7 +597,6 @@ watch(
v-model:on-create-data-list="imageListOnCreate"
v-model:image-url="imageState.imageUrl"
v-model:data-list="imageList"
:changeDisabled="hideAction"
:on-create="model"
:hiddenFooter="!imageState.isImageEdit"
@add-image="addImage"

View file

@ -7,7 +7,7 @@ import { Icon } from '@iconify/vue/dist/iconify.js';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { baseUrl, canAccess } from 'src/stores/utils';
import { baseUrl } from 'src/stores/utils';
import { useNavigator } from 'src/stores/navigator';
import { useInstitution } from 'src/stores/institution';
import { Institution, InstitutionPayload } from 'src/stores/institution/types';
@ -115,8 +115,6 @@ const blankFormData: InstitutionPayload = {
],
};
const attachment = ref<File[]>([]);
const attachmentList = ref<{ name: string; url: string }[]>([]);
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
const refAgenciesDialog = ref();
const formData = ref<InstitutionPayload>(structuredClone(blankFormData));
@ -147,8 +145,6 @@ function resetForm() {
pageState.addModal = false;
pageState.viewDrawer = false;
currAgenciesData.value = undefined;
attachment.value = [];
attachmentList.value = [];
formData.value = structuredClone(blankFormData);
}
@ -158,7 +154,7 @@ function undo() {
pageState.isDrawerEdit = false;
}
async function assignFormData(data: Institution) {
function assignFormData(data: Institution) {
currAgenciesData.value = data;
formData.value = {
group: data.group,
@ -178,9 +174,9 @@ async function assignFormData(data: Institution) {
provinceId: data.provinceId,
selectedImage: data.selectedImage,
status: data.status,
contactEmail: data.contactEmail || '',
contactName: data.contactName || '',
contactTel: data.contactTel || '',
contactEmail: data.contactEmail,
contactName: data.contactName,
contactTel: data.contactTel,
bank: data.bank.map((v) => ({
bankName: v.bankName,
accountNumber: v.accountNumber,
@ -191,8 +187,6 @@ async function assignFormData(data: Institution) {
bankUrl: `${baseUrl}/institution/${data.id}/bank-qr/${v.id}?ts=${Date.now()}`,
})),
};
await fetchAttachment();
}
async function submit(opt?: { selectedImage: string }) {
@ -220,6 +214,7 @@ async function submit(opt?: { selectedImage: string }) {
...v,
})),
};
console.log('payload', payload);
if (
(pageState.isDrawerEdit && currAgenciesData.value?.id) ||
(opt?.selectedImage && currAgenciesData.value?.id)
@ -234,29 +229,18 @@ async function submit(opt?: { selectedImage: string }) {
);
if (ret) {
attachment.value.forEach(async (file) => {
await institutionStore.putAttachment({
parentId: ret.id || '',
name: file.name,
file: file,
});
});
pageState.isDrawerEdit = false;
currAgenciesData.value = ret;
formData.value.selectedImage = ret.selectedImage;
await fetchData($q.screen.xs);
attachment.value = [];
if (refAgenciesDialog.value && opt?.selectedImage) {
refAgenciesDialog.value.clearImageState();
}
setTimeout(async () => {
await fetchAttachment();
}, 300);
return;
}
} else {
const res = await institutionStore.createInstitution(
await institutionStore.createInstitution(
{
...payload,
code: formData.value.group || '',
@ -264,16 +248,6 @@ async function submit(opt?: { selectedImage: string }) {
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);
pageState.addModal = false;
return;
@ -373,49 +347,6 @@ 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 () => {
navigatorStore.current.title = 'agencies.title';
navigatorStore.current.path = [{ text: 'agencies.caption', i18n: true }];
@ -435,7 +366,6 @@ watch(
</script>
<template>
<FloatingActionButton
v-if="canAccess('agencies', 'edit')"
style="z-index: 999"
hide-icon
@click="triggerDialog('add')"
@ -820,7 +750,6 @@ watch(
"
/>
<KebabAction
v-if="canAccess('agencies', 'edit')"
:id-name="props.row.name"
:status="props.row.status"
@view="
@ -904,7 +833,6 @@ watch(
"
/>
<KebabAction
v-if="canAccess('agencies', 'edit')"
:id-name="props.row.id"
:status="props.row.status"
@view="
@ -992,10 +920,7 @@ watch(
{{ $t('general.recordPerPage') }}
</div>
<div>
<PaginationPageSize
v-model="pageSize"
:fetch-data="() => fetchData()"
/>
<PaginationPageSize v-model="pageSize" />
</div>
</div>
</div>
@ -1046,18 +971,14 @@ watch(
}
"
@change-status="triggerChangeStatus"
@delete-attachment="deleteAttachment"
:readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit"
:hide-action="!canAccess('agencies', 'edit')"
v-model="pageState.addModal"
v-model:drawer-model="pageState.viewDrawer"
v-model:data="formData"
v-model:form-bank-book="formData.bank"
v-model:image-list-on-create="imageListOnCreate"
v-model:deletes-status-qr-code-bank-imag="deletesStatusQrCodeBankImag"
v-model:attachment="attachment"
:attachment-list="attachmentList"
/>
</template>
<style scoped>

View file

@ -24,7 +24,6 @@ import {
EmployeePassportPayload,
EmployeeVisaPayload,
} from 'stores/employee/types';
import { canAccess } from 'src/stores/utils';
type Data = {
id: string;
@ -51,8 +50,6 @@ defineEmits<{
}>();
const group = ref('passport');
const refFormEmployeePassport = ref();
const refFormEmployeeVisa = ref();
const requestListStore = useRequestList();
@ -61,7 +58,6 @@ const props = withDefaults(
readonly?: boolean;
listDocument: string[];
currentId: { customer: string; employee: string };
prefix?: string;
}>(),
{
listDocument: () => [],
@ -245,14 +241,14 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
<nav class="q-ml-auto row" v-if="!readonly">
<CancelButton
v-if="state.isEdit"
:id="`btn-docs-${props.prefix || 'nome'}-info-basic-undo`"
id="btn-info-basic-undo"
icon-only
type="button"
@click.stop="triggerCancel"
/>
<EditButton
v-if="!state.isEdit"
:id="`btn-docs-${props.prefix || 'nome'}-info-basic-edit`"
id="btn-info-basic-edit"
icon-only
@click.stop="triggerEdit"
type="button"
@ -478,13 +474,6 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
<q-td>
<span class="row justify-end no-wrap">
<OcrDialog
:disabled-submit="
group === 'passport'
? !refFormEmployeePassport
: group === 'visa'
? !refFormEmployeeVisa
: undefined
"
@submit="
(file, meta) => {
$emit(
@ -511,13 +500,7 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
>
<template #trigger="{ browse }">
<MainButton
v-if="
!!state.isEdit &&
(props.row.documentType === 'passport' ||
props.row.documentType === 'visa'
? canAccess('customer', 'edit')
: true)
"
v-if="!!state.isEdit"
iconOnly
icon="mdi-tray-arrow-up"
color="var(--positive-bg)"
@ -530,7 +513,6 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
</template>
<template #body="{ metadata, isRunning }">
<FormEmployeePassport
ref="refFormEmployeePassport"
v-if="group === 'passport' && metadata"
:title="$t('customerEmployee.form.group.passport')"
dense
@ -563,7 +545,6 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
<FormEmployeeVisa
v-if="group === 'visa' && metadata"
ref="refFormEmployeeVisa"
:title="$t('customerEmployee.form.group.visa')"
ocr
dense
@ -583,7 +564,6 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
v-model:remark="metadata.remark"
v-model:worker-type="metadata.workerType"
v-model:visa-number="metadata.doc_number"
v-model:report-date="metadata.reportDate"
/>
<div
v-if="isRunning"

View file

@ -11,7 +11,6 @@ import { useRequestList } from 'src/stores/request-list';
const props = defineProps<{
readonly?: boolean;
step: Step;
prefix?: string;
}>();
const requestListStore = useRequestList();
@ -101,21 +100,21 @@ function assignToForm() {
<nav class="q-ml-auto row" v-if="!readonly">
<UndoButton
v-if="state.isEdit"
:id="`btn-duty-${props.prefix || 'nome'}-info-basic-undo`"
id="btn-info-basic-undo"
icon-only
type="button"
@click.stop="triggerUndo"
/>
<SaveButton
v-if="state.isEdit"
:id="`btn-duty-${props.prefix || 'nome'}-info-basic-save`"
id="btn-info-basic-save"
icon-only
type="submit"
@click.stop="triggerSubmit"
/>
<EditButton
v-if="!state.isEdit"
:id="`btn-duty-${props.prefix || 'nome'}-info-basic-edit`"
id="btn-info-basic-edit"
icon-only
@click.stop="triggerEdit"
type="button"

View file

@ -12,7 +12,6 @@ const props = defineProps<{
readonly?: boolean;
step: Step;
requestWorkId: string;
prefix?: string;
}>();
const requestListStore = useRequestList();
@ -91,21 +90,21 @@ function assignToForm() {
<nav class="q-ml-auto row" v-if="!readonly">
<UndoButton
v-if="state.isEdit"
:id="`btn-form-${props.prefix || 'nome'}-info-basic-undo`"
id="btn-info-basic-undo"
icon-only
type="button"
@click.stop="triggerUndo"
/>
<SaveButton
v-if="state.isEdit"
:id="`btn-form-${props.prefix || 'nome'}-info-basic-save`"
id="btn-info-basic-save"
icon-only
type="submit"
@click.stop="triggerSubmit"
/>
<EditButton
v-if="!state.isEdit"
:id="`btn-form-${props.prefix || 'nome'}-info-basic-edit`"
id="btn-info-basic-edit"
icon-only
@click.stop="triggerEdit"
type="button"

View file

@ -12,7 +12,6 @@ const responsibleUserId = defineModel<string>('responsibleUserId', {
defineProps<{
readonly?: boolean;
districtId?: string;
prefix?: string;
}>();
watch(responsibleUserLocal, (lhs, rhs) => {
@ -28,8 +27,6 @@ watch(responsibleUserLocal, (lhs, rhs) => {
:label="$t('requestList.localEmployee')"
:disable="readonly"
class="col"
:id="`${prefix || 'nome'}-radio-local-employee`"
:for="`${prefix || 'nome'}-radio-local-employee`"
/>
<q-radio
v-model="responsibleUserLocal"
@ -37,8 +34,6 @@ watch(responsibleUserLocal, (lhs, rhs) => {
:label="$t('requestList.nonLocalEmployee')"
:disable="readonly"
class="col"
:id="`${prefix || 'nome'}-radio-non-local-employee`"
:for="`${prefix || 'nome'}-radio-non-local-employee`"
/>
<div class="col" />
<div class="offset-md-7"></div>
@ -57,8 +52,6 @@ watch(responsibleUserLocal, (lhs, rhs) => {
}"
:readonly
:label="$t('general.select', { msg: $t('personnel.MESSENGER') })"
:id="`${prefix || 'nome'}-select-user`"
:for="`${prefix || 'nome'}-select-user`"
/>
</div>
</div>

View file

@ -21,7 +21,7 @@ import { column } from './constants';
import useFlowStore from 'src/stores/flow';
import { useRequestList } from 'src/stores/request-list';
import { RequestData, RequestDataStatus } from 'src/stores/request-list/types';
import { dialogWarningClose, canAccess } from 'src/stores/utils';
import { dialogWarningClose } from 'src/stores/utils';
import { CancelButton, SaveButton } from 'src/components/button';
import { getRole } from 'src/services/keycloak';
import FloatingActionButton from 'src/components/FloatingActionButton.vue';
@ -95,12 +95,24 @@ function triggerCancel(id: string) {
const res = await requestListStore.cancelRequest(id);
if (res) {
fetchList();
fetchStats();
}
},
});
}
const hideAction = computed(() => {
const role = getRole();
const allowedRoles = [
'head_of_admin',
'head_of_sale',
'admin',
'sale',
'system',
];
return !role || !role.some((r) => allowedRoles.includes(r));
});
function triggerView(opts: { requestData: RequestData }) {
const url = new URL(
`/request-list/${opts.requestData.id}`,
@ -461,12 +473,11 @@ watch(
"
>
<TableRequestList
:no-link="!canAccess('customer', 'view')"
:columns="column"
:rows="data"
:grid="pageState.gridView"
:visible-columns="pageState.fieldSelected"
:hide-action="!canAccess('requestList', 'edit')"
:hide-action
@view="(data) => triggerView({ requestData: data })"
@delete="(data) => triggerCancel(data.id)"
@reject-cancel="
@ -563,7 +574,6 @@ watch(
v-if="requestListActionData"
v-model="pageState.requestListActionDialog"
:request-list="requestListActionData"
:no-link="!canAccess('customer', 'view')"
@submit="submitRequestListAction"
/>
</div>

View file

@ -14,7 +14,6 @@ const props = defineProps<{
step: Step;
responsibleAreaDistrictId?: string;
defaultMessenger?: string;
prefix?: string;
}>();
const emit = defineEmits<{
@ -58,7 +57,6 @@ const formData = ref<AttributesForm>(defaultForm);
function triggerUndo() {
assignToForm();
state.isEdit = false;
refForm.value?.resetValidation();
}
async function triggerSubmit() {
@ -87,11 +85,7 @@ function assignToForm() {
customerDutyCost: attributesForm.value.customerDutyCost ?? 30,
companyDuty: attributesForm.value.companyDuty ?? false,
companyDutyCost: attributesForm.value.companyDutyCost ?? 30,
responsibleUserLocal: attributesForm.value.responsibleUserLocal
? attributesForm.value.responsibleUserLocal
: props.responsibleAreaDistrictId
? false
: true,
responsibleUserLocal: attributesForm.value.responsibleUserLocal ?? true,
responsibleUserId:
attributesForm.value.responsibleUserId || props.defaultMessenger,
individualDuty: attributesForm.value.individualDuty ?? false,
@ -117,21 +111,21 @@ function assignToForm() {
<nav class="q-ml-auto row" v-if="!readonly">
<UndoButton
v-if="state.isEdit"
:id="`btn-messenger-${props.prefix || 'nome'}-info-basic-undo`"
id="btn-info-basic-undo"
icon-only
type="button"
@click.stop="triggerUndo"
/>
<SaveButton
v-if="state.isEdit"
:id="`btn-messenger-${props.prefix || 'nome'}-info-basic-save`"
id="btn-info-basic-save"
icon-only
type="submit"
@click.stop="(e) => refForm?.submit(e)"
/>
<EditButton
v-if="!state.isEdit"
:id="`btn-messenger-${props.prefix || 'nome'}-info-basic-edit`"
id="btn-info-basic-edit"
icon-only
@click.stop="triggerEdit"
type="button"
@ -158,7 +152,6 @@ function assignToForm() {
v-model:responsible-user-local="formData.responsibleUserLocal"
v-model:responsible-user-id="formData.responsibleUserId"
:district-id="responsibleAreaDistrictId"
:prefix
/>
</q-form>
</section>

View file

@ -83,8 +83,6 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
</script>
<template>
<q-expansion-item
:id="`expansion-${product?.name || name}`"
:for="`expansion-${product?.name || name}`"
dense
:class="{ 'status-unpaid': !paySuccess }"
class="overflow-hidden"
@ -148,8 +146,6 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
<div class="q-ml-auto q-gutter-y-xs">
<div class="justify-end flex">
<q-btn-dropdown
:id="`btn-dropdown-${product?.name || name}`"
:for="`btn-dropdown-${product?.name || name}`"
:disable="
readonly || changeableStatus(status?.workStatus).length === 0
"
@ -201,8 +197,6 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
<q-list dense>
<q-item
v-for="(value, index) in changeableStatus(status?.workStatus)"
:id="`btn-dropdown-${product?.name || name}-${value}`"
:for="`btn-dropdown-${product?.name || name}-${value}`"
:key="index"
clickable
v-close-popup
@ -277,15 +271,15 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}
:deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
.q-focus-helper
) {
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
.q-focus-helper
) {
visibility: hidden;
}

View file

@ -28,7 +28,6 @@ const props = withDefaults(
readonly?: boolean;
propertiesToShow: (PropString | PropNumber | PropDate | PropOptions)[];
requestListData: RequestData;
prefix?: string;
}>(),
{
id: '',
@ -129,7 +128,7 @@ defineEmits<{
<nav class="q-ml-auto row" v-if="!readonly">
<UndoButton
v-if="state.isEdit"
:id="`btn-properties-${props.prefix || 'nome'}-info-basic-undo`"
id="btn-info-basic-undo"
icon-only
type="button"
@click.stop="triggerUndo"
@ -137,14 +136,13 @@ defineEmits<{
<SaveButton
v-if="state.isEdit"
id="btn-info-basic-save"
:id="`btn-properties-${props.prefix || 'nome'}-info-basic-save`"
icon-only
type="submit"
@click.stop="triggerSubmit"
/>
<EditButton
v-if="!state.isEdit"
:id="`btn-properties-${props.prefix || 'nome'}-info-basic-edit`"
id="btn-info-basic-edit"
icon-only
@click.stop="triggerEdit"
type="button"

Some files were not shown because too many files have changed in this diff Show more