Compare commits

...

50 commits

Author SHA1 Message Date
HAM
65dcd138db fix: creditNote column number wrong running
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2026-01-12 15:25:32 +07:00
net
e6d06b39da refactor: id missing test
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-12-16 17:07:28 +07:00
net
3cab6cc0e5 refactor: handle btn save
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-12-16 10:28:24 +07:00
JakkrapartXD
9994366c74 feat: Add multi-language user name display to the profile menu.
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 9s
2025-12-04 16:44:09 +07:00
Aif
f4db5ad855 feat: required branch
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-11-12 11:34:21 +07:00
Aif
15a812b50e feat: Refresh quotation list on window focus
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-11-12 11:27:55 +07:00
Aif
f7a8416e7a feat: Refresh task order list on window focus
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-11-12 11:10:21 +07:00
Aif
79d6482caa Add unique id and for attributes to status elements
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
Introduces dynamic id and for attributes to readonly status, dropdown, menu items, and failed remark button for improved accessibility and testability in TaskStatusComponent.vue.
2025-11-11 15:02:36 +07:00
Aif
75d5c7dfe8 feat: unique id attributes to UI components
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-11-11 11:01:36 +07:00
Aif
637eeab3c2 feat: unique IDs to UI components for testing
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-11-11 10:02:13 +07:00
net
2b1e3b12a4 refacotr: add id
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-11-07 16:24:10 +07:00
Aif
a1ed625d32 feat: for
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-20 11:24:28 +07:00
Aif
59a3f964c4 fix: Adjust the badge count to reflect the current tab
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2025-10-20 10:11:24 +07:00
Aif
2afb5ea7e9 feat: fetch data
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-17 10:11:25 +07:00
net
aaf776639d refactor: hide btn
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-16 14:17:58 +07:00
net
04c463a717 refactor: handle i18n
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-15 10:24:07 +07:00
net
8e65a1c5a2 refactor: handle i18n #236
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2025-10-15 10:04:56 +07:00
net
21fc2d5d96 refacotr: bind value
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 18:03:59 +07:00
net
73f43c2a29 refactor: date en
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-14 17:57:05 +07:00
net
16ea66484d refactor: add i18n #229
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-14 17:51:05 +07:00
net
90f31a0c87 refactor: update stats #234
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 17:25:51 +07:00
net
d1785faed2 refactor: update stats
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 17:20:10 +07:00
net
f68e8cf675 refactor: add i18n error
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 17:16:35 +07:00
net
cd4b087fec refactor: hide filter
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-14 16:50:14 +07:00
Aif
8a0340f588 feat: i18n payment condition en
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-14 16:04:33 +07:00
net
2b9c8aa613 refacotr: clear value #230
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 15:47:53 +07:00
Aif
eebd585554 feat: i18n branch name en
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 15:28:17 +07:00
Aif
db5262da42 feat: i18n register name en
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 15:17:13 +07:00
Aif
72b0e89642 feat: i18n register name en
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 14:57:58 +07:00
Aif
2320883cb6 feat: i18n register name en
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 14:50:18 +07:00
Aif
0e6bee7b62 feat: i18n registerNameEN
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 14:46:12 +07:00
net
5c867a496d fix: #242
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-14 14:45:41 +07:00
Aif
d06c26c3c8 feat: i18n registerName
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 14:40:21 +07:00
Aif
c4f088c5cb feat: i18n
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-10-14 14:30:08 +07:00
net
6cf8cf28aa refactor: #245 handle i18n
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-10-14 13:32:30 +07:00
puriphatt
1249f67a0f Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-25 10:18:48 +07:00
puriphatt
05d38b1ab3 chore: remove duplicate 2025-09-25 10:18:31 +07:00
Thanaphon Frappet
d09484a52a refactor: get contact number and name 2025-09-25 10:15:12 +07:00
puriphatt
d8d02a679d fix: quotation payment date
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-25 10:14:22 +07:00
Methapon2001
11047e569d Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2025-09-22 08:56:46 +07:00
puriphatt
f3b5b25bf3 refactor: quotation payment method select bank from register branch
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-19 14:43:15 +07:00
Methapon2001
e817e8fd05 Merge branch 'develop' 2025-09-19 10:02:10 +07:00
puriphatt
18e5517325 fix: submit payment method
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-09-19 09:55:21 +07:00
Methapon2001
5e13864d4a Merge branch 'develop' 2025-09-19 09:13:06 +07:00
net
61dca12e5a fix: no data
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-18 17:34:23 +07:00
net
aa908f0c3d fix: show summary price
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-18 16:41:41 +07:00
net
80056f8e0b fix: search date product
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-18 15:39:25 +07:00
Methapon2001
02bb682150 Merge branch 'develop' 2025-09-18 14:27:56 +07:00
net
d2acd6ba4c refactor: export product
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-09-18 13:21:11 +07:00
net
d53eb15a88 fix: new worker
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-18 11:13:35 +07:00
60 changed files with 1022 additions and 394 deletions

View file

@ -293,15 +293,11 @@ watch(
:readonly="readonly" :readonly="readonly"
:label="$t('form.birthDate')" :label="$t('form.birthDate')"
:disabled-dates="disabledAfterToday" :disabled-dates="disabledAfterToday"
:rules=" :rules="[
employee
? []
: [
(val: string) => (val: string) =>
!!val || !!val ||
$t('form.error.selectField', { field: $t('form.birthDate') }), $t('form.error.selectField', { field: $t('form.birthDate') }),
] ]"
"
/> />
<q-input <q-input

View file

@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { CustomerBranch } from 'src/stores/customer';
import SelectCustomer from '../shared/select/SelectCustomer.vue'; import SelectCustomer from '../shared/select/SelectCustomer.vue';
import SelectBranch from '../shared/select/SelectBranch.vue'; import SelectBranch from '../shared/select/SelectBranch.vue';
import { CustomerBranch } from 'src/stores/customer';
import { ref } from 'vue';
const branchId = defineModel<string>('branchId'); const branchId = defineModel<string>('branchId');
const customerBranchId = defineModel<string>('customerBranchId'); const customerBranchId = defineModel<string>('customerBranchId');
const agentPrice = defineModel<boolean>('agentPrice'); const agentPrice = defineModel<boolean>('agentPrice');
@ -73,6 +74,7 @@ defineEmits<{
required required
:readonly :readonly
/> />
<SelectCustomer <SelectCustomer
id="about-select-customer-branch-id" id="about-select-customer-branch-id"
for="about-select-customer-branch-id" for="about-select-customer-branch-id"

View file

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { QTableProps } from 'quasar'; import { QTableProps } from 'quasar';
import { dateFormat } from 'src/utils/datetime'; import { dateFormat, dateFormatJS } from 'src/utils/datetime';
import { formatNumberDecimal } from 'stores/utils'; import { formatNumberDecimal } from 'stores/utils';
@ -86,11 +86,11 @@ defineEmits<{
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('createdAt')"> <q-td v-if="visibleColumns.includes('createdAt')">
{{ dateFormat(props.row.createdAt) }} {{ dateFormatJS({ date: props.row.createdAt }) }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('dueDate')"> <q-td v-if="visibleColumns.includes('dueDate')">
{{ dateFormat(props.row.dueDate) }} {{ dateFormatJS({ date: props.row.dueDate }) }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('contactName')"> <q-td v-if="visibleColumns.includes('contactName')">

View file

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

View file

@ -96,7 +96,9 @@ defineEmits<{
expandedTree[expandedTree.length - 1] === node.id, expandedTree[expandedTree.length - 1] === node.id,
}" }"
> >
{{ node.name }} {{
$i18n.locale === 'eng' ? node.nameEN || node.name : node.name
}}
</span> </span>
<span class="app-text-muted text-caption ellipsis"> <span class="app-text-muted text-caption ellipsis">
{{ node.code }} {{ node.code }}

View file

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

View file

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

View file

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

View file

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

View file

@ -26,6 +26,7 @@ defineEmits<{
type ExclusiveProps = { type ExclusiveProps = {
selectFirstValue?: boolean; selectFirstValue?: boolean;
prefix?: string;
}; };
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>(); const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -71,6 +72,7 @@ function setDefaultValue() {
<SelectInput <SelectInput
v-model="value" v-model="value"
incremental incremental
:id="`${prefix || 'nome'}-select-user`"
:label :label
:placeholder :placeholder
:readonly :readonly

View file

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

View file

@ -161,6 +161,7 @@ export default {
documentStatus: 'Document Status', documentStatus: 'Document Status',
advanceSearch: 'Advance Search', advanceSearch: 'Advance Search',
totalPeople: '{meg} people', totalPeople: '{meg} people',
price: 'Price {price} Baht',
}, },
menu: { menu: {
@ -1233,6 +1234,9 @@ export default {
taskListNotPending: 'One or more task is not pending.', taskListNotPending: 'One or more task is not pending.',
reqNotMet: 'Not Match', reqNotMet: 'Not Match',
systemError: 'A system error occurred.', systemError: 'A system error occurred.',
taskOrderInvalid: 'Please select the product and the organization.',
flowAccountProductIdNotFound: 'Product not found in flow account',
}, },
}, },

View file

@ -161,6 +161,7 @@ export default {
documentStatus: 'สถานะเอกสาร', documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง', advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน', totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
}, },
menu: { menu: {
@ -1219,6 +1220,8 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ', 'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน', reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด', systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
}, },
}, },

View file

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

View file

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

View file

@ -541,7 +541,8 @@ onMounted(async () => {
<div class="col"> <div class="col">
<div class="col"> <div class="col">
{{ {{
props.row.customerType === 'CORP' props.row.customerType === 'CORP' &&
locale === 'tha'
? props.row.branch[0]?.registerName || '-' ? props.row.branch[0]?.registerName || '-'
: optionStore.mapOption( : optionStore.mapOption(
props.row.branch[0].namePrefix, props.row.branch[0].namePrefix,
@ -551,10 +552,24 @@ onMounted(async () => {
' ' + ' ' +
props.row.branch[0]?.lastName || '-' props.row.branch[0]?.lastName || '-'
}} }}
{{
props.row.customerType === 'CORP' &&
locale === 'eng'
? props.row.branch[0]?.registerNameEN || '-'
: optionStore.mapOption(
props.row.branch[0].namePrefix,
) +
' ' +
props.row.branch[0]?.firstNameEN +
' ' +
props.row.branch[0]?.lastNameEN || '-'
}}
</div> </div>
<div class="col app-text-muted"> <div class="col app-text-muted">
{{ {{
props.row.customerType === 'CORP' props.row.customerType === 'CORP' &&
locale === 'tha'
? props.row.branch[0]?.registerNameEN || '-' ? props.row.branch[0]?.registerNameEN || '-'
: capitalizeFirstLetter( : capitalizeFirstLetter(
props.row.branch[0].namePrefix, props.row.branch[0].namePrefix,
@ -564,6 +579,19 @@ onMounted(async () => {
' ' + ' ' +
props.row.branch[0]?.lastNameEN || '-' props.row.branch[0]?.lastNameEN || '-'
}} }}
{{
props.row.customerType === 'CORP' &&
locale === 'eng'
? props.row.branch[0]?.registerName || '-'
: capitalizeFirstLetter(
props.row.branch[0].namePrefix,
) +
' ' +
props.row.branch[0]?.firstName +
' ' +
props.row.branch[0]?.lastName || '-'
}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -102,6 +102,8 @@ const {
deleteWork, deleteWork,
importProduct, importProduct,
productExport,
} = productServiceStore; } = productServiceStore;
const { workNameItems } = storeToRefs(productServiceStore); const { workNameItems } = storeToRefs(productServiceStore);
@ -1169,6 +1171,7 @@ function clearFormService() {
profileSubmit.value = false; profileSubmit.value = false;
imageProduct.value = undefined; imageProduct.value = undefined;
profileFileImg.value = null; profileFileImg.value = null;
serviceTab.value = 1;
} }
function sameFormService() { function sameFormService() {
@ -1896,6 +1899,25 @@ async function submitWorkName(
else await editWork(workId, data); 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( watch(
() => formService.value.attributes.workflowId, () => formService.value.attributes.workflowId,
async (a, b) => { async (a, b) => {
@ -2229,7 +2251,6 @@ watch(
</AdvanceSearch> </AdvanceSearch>
</template> </template>
</q-input> </q-input>
<div class="row col-md-6" style="white-space: nowrap"> <div class="row col-md-6" style="white-space: nowrap">
<q-select <q-select
v-show="$q.screen.gt.sm" v-show="$q.screen.gt.sm"
@ -2780,6 +2801,13 @@ watch(
} }
" "
/> />
<SaveButton
icon-only
:icon="'material-symbols:download'"
color="var(--info-bg)"
@click.stop="triggerExport()"
/>
</div> </div>
<div class="row col-md-6" style="white-space: nowrap"> <div class="row col-md-6" style="white-space: nowrap">
@ -4357,6 +4385,7 @@ watch(
<!-- add service --> <!-- add service -->
<DialogForm <DialogForm
v-if="dialogService"
hide-footer hide-footer
no-address no-address
no-app-box no-app-box
@ -4722,6 +4751,7 @@ watch(
<!-- edit service edit package--> <!-- edit service edit package-->
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" --> <!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
<DialogForm <DialogForm
v-if="dialogServiceEdit"
hide-footer hide-footer
no-address no-address
height="95vh" height="95vh"

View file

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, reactive, ref, watch, computed } from 'vue'; import { onMounted, onUnmounted, reactive, ref, watch, computed } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -275,6 +275,10 @@ const customerNameInfo = computed(() => {
return name || '-'; return name || '-';
}); });
function handleWindowFocus() {
fetchQuotationList();
}
onMounted(async () => { onMounted(async () => {
pageState.gridView = $q.screen.lt.md ? true : false; pageState.gridView = $q.screen.lt.md ? true : false;
navigatorStore.current.title = 'quotation.title'; navigatorStore.current.title = 'quotation.title';
@ -312,6 +316,12 @@ onMounted(async () => {
} }
flowStore.rotate(); flowStore.rotate();
window.addEventListener('focus', handleWindowFocus);
});
onUnmounted(() => {
window.removeEventListener('focus', handleWindowFocus);
}); });
async function fetchQuotationList(mobileFetch?: boolean) { async function fetchQuotationList(mobileFetch?: boolean) {
@ -767,8 +777,13 @@ async function filterBySellerId() {
:worker-count="item.row._count.worker" :worker-count="item.row._count.worker"
:worker-max="item.row.workerMax || item.row._count.worker" :worker-max="item.row.workerMax || item.row._count.worker"
:customer-name=" :customer-name="
item.row.customerBranch.registerName || item.row.customerBranch.customer.customerType === 'CORP'
`${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}` ? $i18n.locale === 'tha'
? item.row.customerBranch.registerName
: item.row.customerBranch.registerNameEN
: $i18n.locale === 'tha'
? `${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}`
: `${item.row.customerBranch.firstNameEN || '-'} ${item.row.customerBranch.lastNameEN || ''}`
" "
:reporter=" :reporter="
$i18n.locale === 'eng' $i18n.locale === 'eng'

View file

@ -1,16 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { baseUrl, dialog } from 'stores/utils'; import { onMounted, reactive, ref } from 'vue';
import { QFile, QMenu } from 'quasar';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useConfigStore } from 'stores/config';
import { formatNumberDecimal } from 'stores/utils';
import { SaveButton, EditButton, UndoButton } from 'components/button';
import SelectInput from 'src/components/shared/SelectInput.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import { reactive, ref } from 'vue';
import { useQuotationPayment } from 'src/stores/quotations'; import { useQuotationPayment } from 'src/stores/quotations';
import { import {
PaymentPayload, PaymentPayload,
@ -19,16 +12,25 @@ import {
QuotationPaymentData, QuotationPaymentData,
} from 'src/stores/quotations/types'; } from 'src/stores/quotations/types';
import { dateFormatJS } from 'src/utils/datetime'; import { dateFormatJS } from 'src/utils/datetime';
import { QFile, QMenu } from 'quasar';
import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue';
import { onMounted } from 'vue';
import { DebitNote } from 'src/stores/debit-note'; import { DebitNote } from 'src/stores/debit-note';
import { baseUrl, dialog, formatNumberDecimal } from 'stores/utils';
import { useConfigStore } from 'stores/config';
import useBranchStore from 'src/stores/branch';
import useOptionStore from 'src/stores/options';
import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue';
import SelectInput from 'src/components/shared/SelectInput.vue';
import { SaveButton, EditButton, UndoButton } from 'components/button';
const { t } = useI18n();
const { fetchListBankByBranch } = useBranchStore();
const { mapOption } = useOptionStore();
const configStore = useConfigStore(); const configStore = useConfigStore();
const quotationPayment = useQuotationPayment(); const quotationPayment = useQuotationPayment();
const { data: config } = storeToRefs(configStore); const { data: config } = storeToRefs(configStore);
const prop = defineProps<{ const prop = defineProps<{
branchId: string;
data?: Quotation | QuotationFull | DebitNote; data?: Quotation | QuotationFull | DebitNote;
readonly?: boolean; readonly?: boolean;
isDebitNote?: boolean; isDebitNote?: boolean;
@ -39,6 +41,7 @@ const firstCodePayment = defineModel<string>('firstCodePayment');
const refQFile = ref<InstanceType<typeof QFile>[]>([]); const refQFile = ref<InstanceType<typeof QFile>[]>([]);
const refQMenu = ref<InstanceType<typeof QMenu>[]>([]); const refQMenu = ref<InstanceType<typeof QMenu>[]>([]);
const paymentData = ref<QuotationPaymentData[]>([]); const paymentData = ref<QuotationPaymentData[]>([]);
const accountOpt = ref<{ label: string; value: string }[]>([]);
const formPaymentMethod = ref< const formPaymentMethod = ref<
{ {
id: string; id: string;
@ -180,7 +183,7 @@ async function selectStatus(
payment.paymentStatus = status; payment.paymentStatus = status;
const payload = { const payload = {
paymentStatus: payment.paymentStatus, paymentStatus: payment.paymentStatus,
date: new Date(payment.date), date: new Date(),
amount: payment.amount, amount: payment.amount,
}; };
const res = await quotationPayment.updateQuotationPayment( const res = await quotationPayment.updateQuotationPayment(
@ -215,6 +218,7 @@ async function triggerSubmit(id: string) {
formPaymentMethod.value[index].isEdit = false; formPaymentMethod.value[index].isEdit = false;
setTimeout(async () => { setTimeout(async () => {
await fetchData(); await fetchData();
await getSlipList(paymentData.value[index], index);
}, 300); }, 300);
} }
@ -247,8 +251,25 @@ async function fetchData() {
} }
} }
async function fetchBankOption() {
const bankOption = await fetchListBankByBranch(prop.branchId);
accountOpt.value = bankOption
.map((b) => {
const name =
`${b.accountName} ${mapOption(b.bankName)} ${mapOption(b.accountType)} ${b.accountNumber}`.trim();
if (!name) return;
return {
label: name,
value: name,
};
})
.filter((i) => !!i);
}
onMounted(async () => { onMounted(async () => {
await fetchData(); await fetchData();
await fetchBankOption();
}); });
</script> </script>
<template> <template>
@ -640,12 +661,19 @@ onMounted(async () => {
</template> </template>
<!-- การรบชำระ --> <!-- การรบชำระ -->
<template v-if="payment.paymentStatus === 'PaymentSuccess'">
<section <section
class="q-px-md q-py-xs text-weight-medium row items-center" class="q-px-md q-py-xs text-weight-medium row items-center"
style="background-color: hsla(var(--info-bg) / 0.1)" style="background-color: hsla(var(--info-bg) / 0.1)"
> >
{{ $t('quotation.receiptDialog.paymentMethod') }} {{ $t('quotation.receiptDialog.paymentMethod') }}
</section> </section>
<q-form
class="column full-height"
@submit.prevent
@submit="triggerSubmit(payment.id)"
>
<div <div
class="surface-2 q-px-md q-py-sm row q-col-gutter-sm items-center" class="surface-2 q-px-md q-py-sm row q-col-gutter-sm items-center"
> >
@ -658,6 +686,9 @@ onMounted(async () => {
" "
v-model="formPaymentMethod[i].channel" v-model="formPaymentMethod[i].channel"
class="col-md-2 col-6" class="col-md-2 col-6"
:rules="[
(val: string) => !!val || $t('form.error.required'),
]"
:option="[ :option="[
{ {
label: $t('creditNote.label.Cash'), label: $t('creditNote.label.Cash'),
@ -689,18 +720,26 @@ onMounted(async () => {
(!formPaymentMethod[i].isEdit && !!payment.channel) (!formPaymentMethod[i].isEdit && !!payment.channel)
" "
:label="$t('quotation.refNo')" :label="$t('quotation.refNo')"
:rules="[
(val: string) => !!val || $t('form.error.required'),
]"
hide-bottom-space
/> />
<q-input <SelectInput
v-if="formPaymentMethod[i].channel === 'BankTransfer'" v-if="formPaymentMethod[i].channel === 'BankTransfer'"
dense id="select-payment-account"
outlined for="select-payment-account"
class="col"
v-model="formPaymentMethod[i].account"
:readonly=" :readonly="
readonly || readonly ||
(!formPaymentMethod[i].isEdit && !!payment.channel) (!formPaymentMethod[i].isEdit && !!payment.channel)
" "
v-model="formPaymentMethod[i].account"
class="col"
:option="accountOpt"
:label="$t('quotation.bankAccount')" :label="$t('quotation.bankAccount')"
:rules="[
(val: string) => !!val || $t('form.error.required'),
]"
/> />
<div class="q-ml-auto"> <div class="q-ml-auto">
<UndoButton <UndoButton
@ -718,7 +757,7 @@ onMounted(async () => {
<SaveButton <SaveButton
v-if="!payment.channel || formPaymentMethod[i].isEdit" v-if="!payment.channel || formPaymentMethod[i].isEdit"
icon-only icon-only
@click="triggerSubmit(payment.id)" type="submit"
/> />
<EditButton <EditButton
v-if=" v-if="
@ -729,6 +768,8 @@ onMounted(async () => {
/> />
</div> </div>
</div> </div>
</q-form>
</template>
<!-- ปโหลดใบเสร --> <!-- ปโหลดใบเสร -->
<section <section

View file

@ -96,6 +96,8 @@ type ProductGroupId = string;
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const customerBranchOption = ref<CustomerBranch>();
const employeeStore = useEmployeeStore(); const employeeStore = useEmployeeStore();
const route = useRoute(); const route = useRoute();
const useReceiptStore = useReceipt(); const useReceiptStore = useReceipt();
@ -113,8 +115,6 @@ const $q = useQuasar();
const openQuotation = ref<boolean>(false); const openQuotation = ref<boolean>(false);
const formMetadata = ref(); const formMetadata = ref();
const customerBranchOption = ref<CustomerBranch>();
const rowsRequestList = ref<RequestData[]>([]); const rowsRequestList = ref<RequestData[]>([]);
const { const {
@ -161,49 +161,7 @@ const selectedWorker = ref<
}[]; }[];
})[] })[]
>([]); >([]);
const selectedWorkerItem = computed(() => { const selectedWorkerItem = ref([]);
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 firstCodePayment = ref('');
const selectedProductGroup = ref(''); const selectedProductGroup = ref('');
const selectedInstallmentNo = ref<number[]>([]); const selectedInstallmentNo = ref<number[]>([]);
@ -238,7 +196,7 @@ function getPrice(
) { ) {
if (filterHook) list = list.filter(filterHook); if (filterHook) list = list.filter(filterHook);
return list.reduce( const value = list.reduce(
(a, c) => { (a, c) => {
if ( if (
selectedInstallmentNo.value.length > 0 && selectedInstallmentNo.value.length > 0 &&
@ -278,6 +236,8 @@ function getPrice(
finalPrice: 0, finalPrice: 0,
}, },
); );
return value;
} }
const summaryPrice = computed(() => getPrice(productServiceList.value)); const summaryPrice = computed(() => getPrice(productServiceList.value));
@ -556,7 +516,7 @@ async function convertDataToFormSubmit() {
), ),
); );
selectedWorker.value.forEach((v, i) => { selectedWorkerItem.value.forEach((v, i) => {
if (v.attachment !== undefined) { if (v.attachment !== undefined) {
v.attachment.forEach((value) => { v.attachment.forEach((value) => {
fileItemNewWorker.value.push({ fileItemNewWorker.value.push({
@ -573,7 +533,7 @@ async function convertDataToFormSubmit() {
quotationFormData.value.worker = JSON.parse( quotationFormData.value.worker = JSON.parse(
JSON.stringify([ JSON.stringify([
...selectedWorker.value.map((v) => { ...selectedWorkerItem.value.map((v) => {
{ {
return v.id; return v.id;
} }
@ -806,7 +766,40 @@ function toggleDeleteProduct(index: number) {
} }
async function assignWorkerToSelectedWorker() { async function assignWorkerToSelectedWorker() {
selectedWorker.value = quotationFormData.value.worker; 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,
};
});
} }
function convertToTable(nodes: Node[]) { function convertToTable(nodes: Node[]) {
@ -865,21 +858,21 @@ function convertToTable(nodes: Node[]) {
function convertEmployeeToTable(selected: Employee[]) { function convertEmployeeToTable(selected: Employee[]) {
productServiceList.value.forEach((v) => { productServiceList.value.forEach((v) => {
if (selectedWorker.value.length === 0 && v.amount === 1) v.amount -= 1; if (selectedWorkerItem.value.length === 0 && v.amount === 1) v.amount -= 1;
v.amount = Math.max( v.amount = Math.max(
v.amount + selected.length - selectedWorker.value.length, v.amount + selected.length - selectedWorkerItem.value.length,
1, 1,
); );
const oldWorkerId: string[] = []; const oldWorkerId: string[] = [];
const newWorkerIndex: number[] = []; const newWorkerIndex: number[] = [];
selectedWorker.value.forEach((item, i) => { selectedWorkerItem.value.forEach((item, i) => {
if (v.workerIndex.includes(i)) oldWorkerId.push(item.id); if (v.workerIndex.includes(i)) oldWorkerId.push(item.id);
}); });
selected.forEach((item, i) => { selected.forEach((item, i) => {
if (selectedWorker.value.find((n) => item.id === n.id)) return; if (selectedWorkerItem.value.find((n) => item.id === n.id)) return;
newWorkerIndex.push(i); newWorkerIndex.push(i);
}); });
@ -892,7 +885,7 @@ function convertEmployeeToTable(selected: Employee[]) {
pageState.employeeModal = false; pageState.employeeModal = false;
quotationFormData.value.workerMax = Math.max( quotationFormData.value.workerMax = Math.max(
quotationFormData.value.workerMax || 1, quotationFormData.value.workerMax || 1,
selectedWorker.value.length, selectedWorkerItem.value.length,
); );
} }
@ -965,6 +958,71 @@ function viewProductFile(data: ProductRelation) {
pageState.imageDialogUrl = base64 ? base64[1] : ''; 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>>(); const sessionData = ref<Record<string, any>>();
onMounted(async () => { onMounted(async () => {
@ -1036,7 +1094,7 @@ watch(
() => quotationFormData.value.customerBranchId, () => quotationFormData.value.customerBranchId,
async (v) => { async (v) => {
if (!v) return; if (!v) return;
selectedWorker.value = []; selectedWorkerItem.value = [];
}, },
); );
@ -1052,6 +1110,15 @@ watch(
const productServiceNodes = ref<ProductTree>([]); const productServiceNodes = ref<ProductTree>([]);
watch(customerBranchOption, () => {
if (!customerBranchOption.value) return;
quotationFormData.value.contactName =
customerBranchOption.value.contactName || '';
quotationFormData.value.contactTel =
customerBranchOption.value.contactTel || '';
});
watch( watch(
() => productServiceList.value, () => productServiceList.value,
() => { () => {
@ -1087,7 +1154,19 @@ watch(customerBranchOption, () => {
// } // }
function storeDataLocal() { function storeDataLocal() {
quotationFormData.value.productServiceList = productService.value; 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,
};
});
localStorage.setItem( localStorage.setItem(
'quotation-preview', 'quotation-preview',
@ -1096,7 +1175,7 @@ function storeDataLocal() {
codeInvoice: code.value, codeInvoice: code.value,
codePayment: firstCodePayment.value, codePayment: firstCodePayment.value,
...quotationFormData.value, ...quotationFormData.value,
productServiceList: productService.value, productServiceList: tempProductService,
}, },
meta: { meta: {
source: { source: {
@ -1116,7 +1195,7 @@ function storeDataLocal() {
workName: quotationFormData.value.workName, workName: quotationFormData.value.workName,
dueDate: quotationFormData.value.dueDate, dueDate: quotationFormData.value.dueDate,
}, },
selectedWorker: selectedWorker.value, selectedWorker: selectedWorkerItem.value,
createdBy: quotationFormState.value.createdBy('tha'), createdBy: quotationFormState.value.createdBy('tha'),
agentPrice: agentPrice.value, agentPrice: agentPrice.value,
}, },
@ -1201,10 +1280,10 @@ async function getWorkerFromCriteria(
if (!ret) return false; // error, do not close dialog if (!ret) return false; // error, do not close dialog
const deduplicate = ret.result.filter( const deduplicate = ret.result.filter(
(a) => !selectedWorker.value.find((b) => a.id === b.id), (a) => !selectedWorkerItem.value.find((b) => a.id === b.id),
); );
convertEmployeeToTable([...deduplicate, ...selectedWorker.value]); convertEmployeeToTable([...deduplicate, ...selectedWorkerItem.value]);
return true; return true;
} }
@ -1594,15 +1673,15 @@ function covertToNode() {
(v) => (v) =>
(quotationFormData.workerMax = Math.max( (quotationFormData.workerMax = Math.max(
v, v,
selectedWorker.length, selectedWorkerItem.length,
)) ))
" "
:employee-amount=" :employee-amount="
quotationFormData.workerMax || selectedWorker.length quotationFormData.workerMax || selectedWorkerItem.length
" "
:readonly="readonly" :readonly="readonly"
:rows="selectedWorkerItem" :rows="selectedWorkerItem"
@delete="(i) => deleteItem(selectedWorker, i)" @delete="(i) => deleteItem(selectedWorkerItem, i)"
/> />
</div> </div>
</q-expansion-item> </q-expansion-item>
@ -1846,7 +1925,7 @@ function covertToNode() {
installments: quotationFormData.paySplit, installments: quotationFormData.paySplit,
}, },
'quotation-labor': { 'quotation-labor': {
name: selectedWorker.map( name: selectedWorkerItem.map(
(v, i) => (v, i) =>
`${i + 1}. ` + `${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(),
@ -1939,6 +2018,7 @@ function covertToNode() {
view !== View.Receipt && view !== View.Receipt &&
view !== View.Complete view !== View.Complete
" "
:branch-id="quotationFull.registeredBranchId"
:readonly=" :readonly="
isRoleInclude(['sale', 'head_of_sale']) || isRoleInclude(['sale', 'head_of_sale']) ||
!canAccess('quotation', 'edit') !canAccess('quotation', 'edit')
@ -2389,13 +2469,12 @@ function covertToNode() {
<!-- add employee quotation --> <!-- add employee quotation -->
<QuotationFormWorkerSelect <QuotationFormWorkerSelect
:preselect-worker="selectedWorker" :preselect-worker="selectedWorkerItem"
:customerBranchId="quotationFormData.customerBranchId" :customerBranchId="quotationFormData.customerBranchId"
v-model:open="pageState.employeeModal" v-model:open="pageState.employeeModal"
v-model:new-worker-list="newWorkerList"
@success=" @success="
(v) => { (v) => {
selectedWorker = v.worker; combineWorker(v.newWorker, v.worker);
} }
" "
/> />
@ -2438,7 +2517,7 @@ function covertToNode() {
<!-- add Worker --> <!-- add Worker -->
<QuotationFormWorkerAddDialog <QuotationFormWorkerAddDialog
v-if="quotationFormState.source" v-if="quotationFormState.source"
:disabled-worker-id="selectedWorker.map((v) => v.id)" :disabled-worker-id="selectedWorkerItem.map((v) => v.id)"
:product-service-list="quotationFormState.source.productServiceList" :product-service-list="quotationFormState.source.productServiceList"
:quotation-id="quotationFormState.source.id" :quotation-id="quotationFormState.source.id"
:customer-branch-id="quotationFormState.source.customerBranchId" :customer-branch-id="quotationFormState.source.customerBranchId"

View file

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

View file

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

View file

@ -113,7 +113,7 @@ const props = withDefaults(
defineProps<{ defineProps<{
customerBranchId?: string; customerBranchId?: string;
disabledWorkerId?: string[]; disabledWorkerId?: string[];
preselectWorker?: Employee[]; preselectWorker?: (Employee & { workerNew: boolean })[];
}>(), }>(),
{}, {},
); );
@ -133,7 +133,7 @@ const optionStore = useOptionStore();
const employeeStore = useEmployeeStore(); const employeeStore = useEmployeeStore();
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
const newWorkerList = defineModel< const newWorkerList = ref<
(EmployeeWorker & { (EmployeeWorker & {
attachment?: { attachment?: {
name?: string; name?: string;
@ -143,7 +143,7 @@ const newWorkerList = defineModel<
_meta?: Record<string, any>; _meta?: Record<string, any>;
}[]; }[];
})[] })[]
>('newWorkerList', { default: [] }); >([]);
const workerSelected = ref<Employee[]>([]); const workerSelected = ref<Employee[]>([]);
const workerList = ref<Employee[]>([]); const workerList = ref<Employee[]>([]);
const importWorkerCriteria = ref<{ const importWorkerCriteria = ref<{
@ -208,7 +208,13 @@ function getEmployeeImageUrl(item: Employee) {
function init() { function init() {
if (props.preselectWorker) { if (props.preselectWorker) {
workerSelected.value = JSON.parse(JSON.stringify(props.preselectWorker)); workerSelected.value = JSON.parse(
JSON.stringify(props.preselectWorker.filter((v) => !v.workerNew)),
);
newWorkerList.value = JSON.parse(
JSON.stringify(props.preselectWorker.filter((v) => v.workerNew)),
);
} }
getWorkerList(); getWorkerList();
} }
@ -608,11 +614,14 @@ watch(
solid solid
id="btn-success" id="btn-success"
@click=" @click="
(emits('success', { () => {
$emit('success', {
worker: workerSelected, worker: workerSelected,
newWorker: newWorkerList, newWorker: newWorkerList,
}), });
(open = false))
open = false;
}
" "
> >
{{ $t('general.select', { msg: $t('quotation.employeeList') }) }} {{ $t('general.select', { msg: $t('quotation.employeeList') }) }}
@ -634,9 +643,11 @@ watch(
if (employeeFormState.currentTab === 'personalInfo') { if (employeeFormState.currentTab === 'personalInfo') {
const currentEmployeeId = const currentEmployeeId =
await employeeFormStore.submitPersonal(onCreateImageList); await employeeFormStore.submitPersonal(onCreateImageList);
newWorkerList.push(
quotationForm.injectNewEmployee({ quotationForm.injectNewEmployee({
data: { ...currentFromDataEmployee, id: currentEmployeeId }, data: { ...currentFromDataEmployee, id: currentEmployeeId },
}); }),
);
employeeFormState.isEmployeeEdit = false; employeeFormState.isEmployeeEdit = false;
employeeFormState.dialogType = 'info'; employeeFormState.dialogType = 'info';
} }

View file

@ -69,6 +69,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
file?: File; file?: File;
_meta?: Record<string, any>; _meta?: Record<string, any>;
}[]; }[];
workerNew: boolean;
})[] })[]
>([]); >([]);
@ -220,7 +221,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
}, },
callback?: () => void, callback?: () => void,
) { ) {
newWorkerList.value.push({ const temp = {
//passportNo: obj.data.passportNo, //passportNo: obj.data.passportNo,
//documentExpireDate: obj.data.documentExpireDate, //documentExpireDate: obj.data.documentExpireDate,
id: obj.data.id, id: obj.data.id,
@ -235,9 +236,12 @@ export const useQuotationForm = defineStore('form-quotation', () => {
gender: obj.data.gender, gender: obj.data.gender,
dateOfBirth: obj.data.dateOfBirth, dateOfBirth: obj.data.dateOfBirth,
attachment: obj.data.attachment, attachment: obj.data.attachment,
}); workerNew: true,
};
callback?.(); callback?.();
return temp;
} }
function dialogDelete(callback: () => void) { function dialogDelete(callback: () => void) {

View file

@ -61,6 +61,7 @@ const props = withDefaults(
readonly?: boolean; readonly?: boolean;
listDocument: string[]; listDocument: string[];
currentId: { customer: string; employee: string }; currentId: { customer: string; employee: string };
prefix?: string;
}>(), }>(),
{ {
listDocument: () => [], listDocument: () => [],
@ -244,14 +245,14 @@ function changeCustomerTab(opts: { tab: 'customer' | 'employee' }) {
<nav class="q-ml-auto row" v-if="!readonly"> <nav class="q-ml-auto row" v-if="!readonly">
<CancelButton <CancelButton
v-if="state.isEdit" v-if="state.isEdit"
id="btn-info-basic-undo" :id="`btn-docs-${props.prefix || 'nome'}-info-basic-undo`"
icon-only icon-only
type="button" type="button"
@click.stop="triggerCancel" @click.stop="triggerCancel"
/> />
<EditButton <EditButton
v-if="!state.isEdit" v-if="!state.isEdit"
id="btn-info-basic-edit" :id="`btn-docs-${props.prefix || 'nome'}-info-basic-edit`"
icon-only icon-only
@click.stop="triggerEdit" @click.stop="triggerEdit"
type="button" type="button"

View file

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

View file

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

View file

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

View file

@ -95,6 +95,7 @@ function triggerCancel(id: string) {
const res = await requestListStore.cancelRequest(id); const res = await requestListStore.cancelRequest(id);
if (res) { if (res) {
fetchList(); fetchList();
fetchStats();
} }
}, },
}); });

View file

@ -14,6 +14,7 @@ const props = defineProps<{
step: Step; step: Step;
responsibleAreaDistrictId?: string; responsibleAreaDistrictId?: string;
defaultMessenger?: string; defaultMessenger?: string;
prefix?: string;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -116,21 +117,21 @@ function assignToForm() {
<nav class="q-ml-auto row" v-if="!readonly"> <nav class="q-ml-auto row" v-if="!readonly">
<UndoButton <UndoButton
v-if="state.isEdit" v-if="state.isEdit"
id="btn-info-basic-undo" :id="`btn-messenger-${props.prefix || 'nome'}-info-basic-undo`"
icon-only icon-only
type="button" type="button"
@click.stop="triggerUndo" @click.stop="triggerUndo"
/> />
<SaveButton <SaveButton
v-if="state.isEdit" v-if="state.isEdit"
id="btn-info-basic-save" :id="`btn-messenger-${props.prefix || 'nome'}-info-basic-save`"
icon-only icon-only
type="submit" type="submit"
@click.stop="(e) => refForm?.submit(e)" @click.stop="(e) => refForm?.submit(e)"
/> />
<EditButton <EditButton
v-if="!state.isEdit" v-if="!state.isEdit"
id="btn-info-basic-edit" :id="`btn-messenger-${props.prefix || 'nome'}-info-basic-edit`"
icon-only icon-only
@click.stop="triggerEdit" @click.stop="triggerEdit"
type="button" type="button"
@ -157,6 +158,7 @@ function assignToForm() {
v-model:responsible-user-local="formData.responsibleUserLocal" v-model:responsible-user-local="formData.responsibleUserLocal"
v-model:responsible-user-id="formData.responsibleUserId" v-model:responsible-user-id="formData.responsibleUserId"
:district-id="responsibleAreaDistrictId" :district-id="responsibleAreaDistrictId"
:prefix
/> />
</q-form> </q-form>
</section> </section>

View file

@ -83,6 +83,8 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
:id="`expansion-${product?.name || name}`"
:for="`expansion-${product?.name || name}`"
dense dense
:class="{ 'status-unpaid': !paySuccess }" :class="{ 'status-unpaid': !paySuccess }"
class="overflow-hidden" class="overflow-hidden"
@ -146,6 +148,8 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
<div class="q-ml-auto q-gutter-y-xs"> <div class="q-ml-auto q-gutter-y-xs">
<div class="justify-end flex"> <div class="justify-end flex">
<q-btn-dropdown <q-btn-dropdown
:id="`btn-dropdown-${product?.name || name}`"
:for="`btn-dropdown-${product?.name || name}`"
:disable=" :disable="
readonly || changeableStatus(status?.workStatus).length === 0 readonly || changeableStatus(status?.workStatus).length === 0
" "
@ -197,6 +201,8 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
<q-list dense> <q-list dense>
<q-item <q-item
v-for="(value, index) in changeableStatus(status?.workStatus)" v-for="(value, index) in changeableStatus(status?.workStatus)"
:id="`btn-dropdown-${product?.name || name}-${value}`"
:for="`btn-dropdown-${product?.name || name}-${value}`"
:key="index" :key="index"
clickable clickable
v-close-popup v-close-popup
@ -272,14 +278,14 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
:deep( :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); color: var(--brand-1);
} }
:deep( :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-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-focus-helper
) { ) {
visibility: hidden; visibility: hidden;
} }

View file

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

View file

@ -881,6 +881,7 @@ function toEmployee(employee: RequestData['employee']) {
:readonly="value._readonly" :readonly="value._readonly"
ref="refDocumentExpansion" ref="refDocumentExpansion"
:attributes="value.attributes" :attributes="value.attributes"
:prefix="value.productService.product.name"
@change-status=" @change-status="
(opt) => { (opt) => {
triggerChangeStatusFile({ triggerChangeStatusFile({
@ -928,6 +929,7 @@ function toEmployee(employee: RequestData['employee']) {
<MessengerExpansion <MessengerExpansion
v-if="value._messengerExpansion" v-if="value._messengerExpansion"
:readonly="value._readonly" :readonly="value._readonly"
:prefix="value.productService.product.name"
:default-messenger=" :default-messenger="
value.stepStatus[pageState.currentStep - 1] value.stepStatus[pageState.currentStep - 1]
? undefined ? undefined
@ -953,6 +955,7 @@ function toEmployee(employee: RequestData['employee']) {
<DutyExpansion <DutyExpansion
v-if="value._dutyExpansion" v-if="value._dutyExpansion"
:readonly="value._readonly" :readonly="value._readonly"
:prefix="value.productService.product.name"
:step="{ :step="{
step: pageState.currentStep, step: pageState.currentStep,
requestWorkId: value.id || '', requestWorkId: value.id || '',
@ -966,6 +969,7 @@ function toEmployee(employee: RequestData['employee']) {
v-if="value._formExpansion" v-if="value._formExpansion"
:request-work-id="value.id" :request-work-id="value.id"
:readonly="value._readonly" :readonly="value._readonly"
:prefix="value.productService.product.name"
:step="{ :step="{
step: pageState.currentStep, step: pageState.currentStep,
requestWorkId: value.id || '', requestWorkId: value.id || '',
@ -979,6 +983,7 @@ function toEmployee(employee: RequestData['employee']) {
:request-list-data="data" :request-list-data="data"
:id="value.id" :id="value.id"
:readonly="value._readonly" :readonly="value._readonly"
:prefix="value.productService.product.name"
:properties-to-show=" :properties-to-show="
value.productService.work?.attributes.workflowStep[ value.productService.work?.attributes.workflowStep[
pageState.currentStep - 1 pageState.currentStep - 1

View file

@ -190,6 +190,8 @@ function handleCheckAll() {
> >
<q-th v-if="checkable"> <q-th v-if="checkable">
<q-checkbox <q-checkbox
:for="`list-checkbox-all`"
:id="`list-checkbox-all`"
v-if="selected.length > 0" v-if="selected.length > 0"
:model-value=" :model-value="
selected.length === selected.length ===
@ -229,6 +231,8 @@ function handleCheckAll() {
> >
<q-td v-if="checkable"> <q-td v-if="checkable">
<q-checkbox <q-checkbox
:for="`list-checkbox-${props.rowIndex + 1}`"
:id="`list-checkbox-${props.rowIndex + 1}`"
:disable=" :disable="
selected.length > 0 && selected.length > 0 &&
!listSameArea.includes( !listSameArea.includes(

View file

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
// NOTE: Library // NOTE: Library
import { computed, onMounted, reactive, watch, ref } from 'vue'; import { computed, onMounted, onUnmounted, reactive, watch, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -86,6 +86,7 @@ async function fetchTaskOrderList(opts?: { page?: number; pageSize?: number }) {
}); });
} }
if (res) { if (res) {
taskOrderStore.getTaskOrderStats();
data.value = res.result; data.value = res.result;
pageState.total = res.total; pageState.total = res.total;
pageMax.value = Math.ceil(res.total / pageSize.value); pageMax.value = Math.ceil(res.total / pageSize.value);
@ -146,6 +147,10 @@ async function deleteTaskOrder(id: string) {
}); });
} }
function handleWindowFocus() {
fetchTaskOrderList();
}
onMounted(async () => { onMounted(async () => {
pageState.gridView = $q.screen.lt.md ? true : false; pageState.gridView = $q.screen.lt.md ? true : false;
navigatorStore.current.title = 'taskOrder.title'; navigatorStore.current.title = 'taskOrder.title';
@ -156,6 +161,12 @@ onMounted(async () => {
if (route.query['tab'] && typeof route.query['tab'] === 'string') { if (route.query['tab'] && typeof route.query['tab'] === 'string') {
pageState.currentTab = route.query['tab']; pageState.currentTab = route.query['tab'];
} }
window.addEventListener('focus', handleWindowFocus);
});
onUnmounted(() => {
window.removeEventListener('focus', handleWindowFocus);
}); });
watch( watch(
@ -218,7 +229,11 @@ watch(
color: hsl(var(--info-bg)); color: hsl(var(--info-bg));
" "
> >
{{ Object.values(stats).reduce((s, v) => s + v, 0) }} {{
pageState.isMessenger
? pageState.total
: stats[pageState.currentTab as TaskOrderStatus]
}}
</q-badge> </q-badge>
<q-btn <q-btn
class="q-ml-sm" class="q-ml-sm"

View file

@ -295,6 +295,7 @@ function assignTempGroup() {
class="bordered-b" class="bordered-b"
> >
<q-expansion-item <q-expansion-item
:id="`expansion-product-${product.code}`"
dense dense
class="overflow-hidden" class="overflow-hidden"
switch-toggle-side switch-toggle-side
@ -348,6 +349,7 @@ function assignTempGroup() {
</FormGroupHead> </FormGroupHead>
<div class="q-pa-md full-width"> <div class="q-pa-md full-width">
<TableEmployee <TableEmployee
:id="`table-employee-${product.code}`"
checkbox-on checkbox-on
check-all check-all
select-ready select-ready
@ -371,9 +373,15 @@ function assignTempGroup() {
</section> </section>
<template #footer> <template #footer>
<CancelButton class="q-ml-auto" outlined @click="close" /> <CancelButton
id="btn-dialog-cancel"
class="q-ml-auto"
outlined
@click="close"
/>
<SaveButton <SaveButton
:label="$t('general.select')" :label="$t('general.select')"
id="btn-dialog-select"
class="q-ml-sm" class="q-ml-sm"
icon="mdi-check" icon="mdi-check"
solid solid

View file

@ -225,6 +225,7 @@ function disableCheckAll() {
<template> <template>
<q-table <q-table
flat flat
id="table-employee"
bordered bordered
row-key="id" row-key="id"
v-bind="props" v-bind="props"
@ -273,6 +274,7 @@ function disableCheckAll() {
> >
<q-th v-if="checkboxOn" class="relative-position"> <q-th v-if="checkboxOn" class="relative-position">
<q-checkbox <q-checkbox
id="checkbox-check-all"
v-if="checkAll" v-if="checkAll"
:disable="disableCheckAll()" :disable="disableCheckAll()"
:model-value=" :model-value="
@ -305,6 +307,7 @@ function disableCheckAll() {
class="absolute-right row items-center" class="absolute-right row items-center"
> >
<q-btn <q-btn
id="btn-change-all-status"
flat flat
dense dense
rounded rounded
@ -339,6 +342,7 @@ function disableCheckAll() {
{{ $t(`taskOrder.status.Complete`) }} {{ $t(`taskOrder.status.Complete`) }}
</q-item> </q-item>
<q-item <q-item
id="menu-item-redo"
clickable clickable
v-close-popup v-close-popup
class="items-center" class="items-center"
@ -358,6 +362,7 @@ function disableCheckAll() {
{{ $t(`taskOrder.status.Redo`) }} {{ $t(`taskOrder.status.Redo`) }}
</q-item> </q-item>
<q-item <q-item
id="menu-item-restart"
clickable clickable
v-close-popup v-close-popup
class="items-center" class="items-center"
@ -379,6 +384,7 @@ function disableCheckAll() {
</q-list> </q-list>
<q-list v-if="!validate" dense> <q-list v-if="!validate" dense>
<q-item <q-item
id="menu-item-success"
clickable clickable
v-close-popup v-close-popup
class="items-center" class="items-center"
@ -398,6 +404,7 @@ function disableCheckAll() {
{{ $t(`taskOrder.status.Success`) }} {{ $t(`taskOrder.status.Success`) }}
</q-item> </q-item>
<q-item <q-item
id="menu-item-failed"
clickable clickable
v-close-popup v-close-popup
class="items-center" class="items-center"
@ -480,6 +487,7 @@ function disableCheckAll() {
<q-td> <q-td>
<span <span
class="cursor-pointer link" class="cursor-pointer link"
:id="`link-request-list-${props.row.request.code}`"
@click="goToRequestList(props.row.request.id)" @click="goToRequestList(props.row.request.id)"
> >
{{ props.row.request.code }} {{ props.row.request.code }}
@ -489,6 +497,7 @@ function disableCheckAll() {
<q-td> <q-td>
<span <span
class="cursor-pointer link" class="cursor-pointer link"
:id="`link-quotation-${props.row.request.quotation?.code}`"
@click="goToQuotation(props.row.request.quotation)" @click="goToQuotation(props.row.request.quotation)"
> >
{{ props.row.request.quotation?.code }} {{ props.row.request.quotation?.code }}
@ -496,7 +505,11 @@ function disableCheckAll() {
</q-td> </q-td>
<q-td v-if="stepOn" class="text-left"> <q-td v-if="stepOn" class="text-left">
<div v-if="props.row._template" class="column text-left"> <div
v-if="props.row._template"
class="column text-left"
:id="`template-step-${props.row.request.code}`"
>
<span>{{ props.row._template.templateName }}</span> <span>{{ props.row._template.templateName }}</span>
<span class="app-text-muted text-caption"> <span class="app-text-muted text-caption">
{{ $t('flow.stepNo', { msg: props.row._template.step }) }} {{ $t('flow.stepNo', { msg: props.row._template.step }) }}
@ -522,7 +535,10 @@ function disableCheckAll() {
</template> </template>
</q-img> </q-img>
</q-avatar> </q-avatar>
<div class="column text-left q-ml-sm"> <div
class="column text-left q-ml-sm"
:id="`employee-name-${props.row.request.employee?.code}`"
>
<div> <div>
{{ getEmployeeName(props.row, { locale: $i18n.locale }) }} {{ getEmployeeName(props.row, { locale: $i18n.locale }) }}
</div> </div>
@ -532,6 +548,7 @@ function disableCheckAll() {
</div> </div>
</div> </div>
<Icon <Icon
:id="`icon-gender-${props.row.request.employee?.code}`"
class="q-ml-md" class="q-ml-md"
:class="`app-text-${props.row.request.employee?.gender}`" :class="`app-text-${props.row.request.employee?.gender}`"
:icon="`material-symbols:${props.row.request.employee?.gender}`" :icon="`material-symbols:${props.row.request.employee?.gender}`"
@ -539,13 +556,18 @@ function disableCheckAll() {
/> />
</div> </div>
</q-td> </q-td>
<q-td>{{ calculateAge(props.row.request.employee?.dateOfBirth) }}</q-td> <q-td :id="`employee-age-${props.row.request.employee?.code}`">
<q-td> {{ calculateAge(props.row.request.employee?.dateOfBirth) }}
</q-td>
<q-td :id="`employee-nationality-${props.row.request.employee?.code}`">
{{ {{
useOptionStore().mapOption(props.row.request.employee?.nationality) useOptionStore().mapOption(props.row.request.employee?.nationality)
}} }}
</q-td> </q-td>
<q-td> <q-td
:id="`quotation-due-date-${props.row.request.quotation?.code}`"
v-if="!statusOn"
>
{{ {{
dateFormatJS({ dateFormatJS({
date: props.row.request.quotation?.dueDate, date: props.row.request.quotation?.dueDate,
@ -555,13 +577,16 @@ function disableCheckAll() {
}) })
}} }}
</q-td> </q-td>
<q-td> <q-td
:id="`expiration-date-${props.row.request.quotation?.code}`"
v-if="!statusOn"
>
<ExpirationDate <ExpirationDate
:expiration-date="new Date(props.row.request.quotation?.dueDate)" :expiration-date="new Date(props.row.request.quotation?.dueDate)"
/> />
</q-td> </q-td>
<q-td> <q-td v-if="!statusOn">
<BadgeComponent <BadgeComponent
v-if="props.row.request.quotation?.urgent" v-if="props.row.request.quotation?.urgent"
icon="mdi-fire" icon="mdi-fire"

View file

@ -114,6 +114,7 @@ const emit = defineEmits<{
</script> </script>
<template> <template>
<q-table <q-table
id="table-task-order"
v-bind="props" v-bind="props"
:columns="column" :columns="column"
bordered bordered
@ -133,7 +134,11 @@ const emit = defineEmits<{
:props="props" :props="props"
> >
<q-th v-if="selection !== 'none'"> <q-th v-if="selection !== 'none'">
<q-checkbox v-model="props.selected" size="sm" /> <q-checkbox
id="checkbox-select-all-task"
v-model="props.selected"
size="sm"
/>
</q-th> </q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props"> <q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label && $t(col.label) }} {{ col.label && $t(col.label) }}
@ -149,13 +154,21 @@ const emit = defineEmits<{
> >
<q-tr class="text-center" :class="{ urgent: props.row.urgent }"> <q-tr class="text-center" :class="{ urgent: props.row.urgent }">
<q-td v-if="selection !== 'none'"> <q-td v-if="selection !== 'none'">
<q-checkbox v-model="props.selected" size="sm" /> <q-checkbox
:id="`checkbox-task-${props.row.code}`"
v-model="props.selected"
size="sm"
/>
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('order')"> <q-td v-if="visibleColumns.includes('order')">
{{ props.rowIndex + 1 }} {{ props.rowIndex + 1 }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('taskName')" class="text-left"> <q-td
<div> v-if="visibleColumns.includes('taskName')"
class="text-left"
:id="`task-name-${props.row.code}`"
>
<div :id="`task-name-div-${props.row.code}`">
{{ props.row.taskName || '-' }} {{ props.row.taskName || '-' }}
<q-tooltip :delay="300"> <q-tooltip :delay="300">
{{ props.row.taskName || '-' }} {{ props.row.taskName || '-' }}
@ -170,31 +183,51 @@ const emit = defineEmits<{
}} }}
</div> </div>
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('issueBranch')"> <q-td
v-if="visibleColumns.includes('issueBranch')"
:id="`task-issue-branch-${props.row.code}`"
>
{{ {{
$i18n.locale === 'eng' $i18n.locale === 'eng'
? props.row.registeredBranch.nameEN || '-' ? props.row.registeredBranch.nameEN || '-'
: props.row.registeredBranch.name || '-' : props.row.registeredBranch.name || '-'
}} }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('institution')"> <q-td
v-if="visibleColumns.includes('institution')"
:id="`task-institution-${props.row.code}`"
>
{{ {{
$i18n.locale === 'eng' $i18n.locale === 'eng'
? props.row.institution.nameEN || '-' ? props.row.institution.nameEN || '-'
: props.row.institution.name || '-' : props.row.institution.name || '-'
}} }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('createdAt')"> <q-td
v-if="visibleColumns.includes('createdAt')"
:id="`task-created-at-${props.row.code}`"
>
{{ dateFormatJS({ date: props.row.createdAt }) || '-' }} {{ dateFormatJS({ date: props.row.createdAt }) || '-' }}
{{ dateFormatJS({ date: props.row.createdAt, timeOnly: true }) }} {{ dateFormatJS({ date: props.row.createdAt, timeOnly: true }) }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('createdBy')" class="text-left"> <q-td
v-if="visibleColumns.includes('createdBy')"
class="text-left"
:id="`task-created-by-${props.row.code}`"
>
{{ getCreatedByName(props.row, $i18n) }} {{ getCreatedByName(props.row, $i18n) }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('contactTel')"> <q-td
v-if="visibleColumns.includes('contactTel')"
:id="`task-contact-tel-${props.row.code}`"
>
{{ props.row.contactTel || '-' }} {{ props.row.contactTel || '-' }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('contactName')" class="text-left"> <q-td
v-if="visibleColumns.includes('contactName')"
class="text-left"
:id="`task-contact-name-${props.row.code}`"
>
{{ props.row.contactName || '-' }} {{ props.row.contactName || '-' }}
</q-td> </q-td>
<q-td v-if="visibleColumns.includes('taskStatus')"> <q-td v-if="visibleColumns.includes('taskStatus')">
@ -202,11 +235,12 @@ const emit = defineEmits<{
hide-icon hide-icon
:hsla-color="taskOrderStatus(props.row.taskOrderStatus, 'color')" :hsla-color="taskOrderStatus(props.row.taskOrderStatus, 'color')"
:title="$t(taskOrderStatus(props.row.taskOrderStatus, 'status'))" :title="$t(taskOrderStatus(props.row.taskOrderStatus, 'status'))"
:id="`badge-task-status-${props.row.code}`"
/> />
</q-td> </q-td>
<q-td v-if="selection === 'none'"> <q-td v-if="selection === 'none'">
<q-btn <q-btn
:id="`btn-eye-${props.row.taskName}`" :id="`btn-view-task-${props.row.code}`"
icon="mdi-eye-outline" icon="mdi-eye-outline"
size="sm" size="sm"
dense dense
@ -221,7 +255,7 @@ const emit = defineEmits<{
canAccess('taskOrder', 'edit') canAccess('taskOrder', 'edit')
" "
:hide-delete="!canAccess('taskOrder', 'create')" :hide-delete="!canAccess('taskOrder', 'create')"
:idName="`btn-kebab-${props.row.taskName}`" :idName="`btn-kebab-${props.row.code}`"
status="'ACTIVE'" status="'ACTIVE'"
hide-toggle hide-toggle
@view="$emit('view', props.row)" @view="$emit('view', props.row)"
@ -233,6 +267,7 @@ const emit = defineEmits<{
<q-btn <q-btn
dense dense
flat flat
:id="`btn-sub-row-${props.row.code}`"
class="rounded" class="rounded"
@click.stop=" @click.stop="
() => { () => {

View file

@ -60,16 +60,25 @@ function inactiveCheck() {
</script> </script>
<template> <template>
<div <div
:id="`readonly-status-${status}`"
:for="`readonly-status-${status}`"
v-if="readonly" v-if="readonly"
class="row rounded bordered surface-2 items-center justify-center q-pa-xs no-wrap" class="row rounded bordered surface-2 items-center justify-center q-pa-xs no-wrap"
:style="`color: hsl(var(--${currStatus?.color}-bg))`" :style="`color: hsl(var(--${currStatus?.color}-bg))`"
> >
<q-icon :name="currStatus?.icon" class="q-pr-xs" size="xs" /> <q-icon
{{ $t(`taskOrder.status.${status}`) }} :id="`readonly-status-icon-${status}`"
:name="currStatus?.icon"
class="q-pr-xs"
size="xs"
/>
<span :id="`readonly-status-label-${status}`">{{ $t(`taskOrder.status.${status}`) }}</span>
</div> </div>
<div v-else class="row items-center justify-center no-wrap"> <div v-else class="row items-center justify-center no-wrap">
<q-btn-dropdown <q-btn-dropdown
:id="`btn-dropdown-status-${status}`"
:for="`btn-dropdown-status-${status}`"
dense dense
unelevated unelevated
:label=" :label="
@ -110,6 +119,8 @@ function inactiveCheck() {
> >
<q-list v-if="!noAction" dense> <q-list v-if="!noAction" dense>
<q-item <q-item
:for="`menu-item-status-${v.value}`"
:id="`menu-item-status-${v.value}`"
v-for="(v, index) in type === 'order' v-for="(v, index) in type === 'order'
? { ? {
Success: taskStatusOrderToggle.filter( Success: taskStatusOrderToggle.filter(
@ -147,6 +158,8 @@ function inactiveCheck() {
</q-btn-dropdown> </q-btn-dropdown>
<q-btn <q-btn
:id="`btn-failed-remark-${status}`"
:for="`btn-failed-remark-${status}`"
v-if="currStatus?.value === TaskStatus.Failed" v-if="currStatus?.value === TaskStatus.Failed"
flat flat
dense dense
@ -188,7 +201,7 @@ function inactiveCheck() {
:deep( :deep(
.hide-icon .hide-icon
i.q-icon.mdi.mdi-chevron-down.q-btn-dropdown__arrow.q-btn-dropdown__arrow-container i.q-icon.mdi.mdi-chevron-down.q-btn-dropdown__arrow.q-btn-dropdown__arrow-container
) { ) {
display: none; display: none;
} }
</style> </style>

View file

@ -25,6 +25,7 @@ const fileData = defineModel<
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
id="expansion-additional-file"
dense dense
class="overflow-hidden bordered full-width" class="overflow-hidden bordered full-width"
switch-toggle-side switch-toggle-side
@ -41,6 +42,7 @@ const fileData = defineModel<
<main class="q-px-md q-py-sm surface-1"> <main class="q-px-md q-py-sm surface-1">
<UploadFileSection <UploadFileSection
id="upload-additional-file"
multiple multiple
:layout="$q.screen.gt.sm ? 'column' : 'row'" :layout="$q.screen.gt.sm ? 'column' : 'row'"
:readonly :readonly

View file

@ -22,6 +22,7 @@ const contactTel = defineModel<string>('contactTel');
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
id="expansion-document"
default-opened default-opened
dense dense
class="overflow-hidden bordered full-width" class="overflow-hidden bordered full-width"
@ -38,13 +39,16 @@ const contactTel = defineModel<string>('contactTel');
<main class="q-px-md q-py-sm surface-1 row q-col-gutter-sm"> <main class="q-px-md q-py-sm surface-1 row q-col-gutter-sm">
<SelectBranch <SelectBranch
id="select-issue-branch"
:readonly :readonly
required
class="col-md-4 col-12" class="col-md-4 col-12"
:label="`${$t('taskOrder.issueBranch')}${$i18n.locale === 'tha' ? $t('taskOrder.title') : ''}`" :label="`${$t('taskOrder.issueBranch')}${$i18n.locale === 'tha' ? $t('taskOrder.title') : ''}`"
v-model:value="registeredBranchId" v-model:value="registeredBranchId"
auto-select-on-single auto-select-on-single
/> />
<SelectInstitution <SelectInstitution
id="select-agencies"
:readonly :readonly
required required
class="col-md-4 col-12" class="col-md-4 col-12"
@ -55,6 +59,7 @@ const contactTel = defineModel<string>('contactTel');
auto-select-on-single auto-select-on-single
/> />
<DatePicker <DatePicker
id="datepicker-issue-date"
:label="$t('taskOrder.issueDate')" :label="$t('taskOrder.issueDate')"
class="col-md-2 col-6" class="col-md-2 col-6"
:model-value="issueDate || new Date(Date.now())" :model-value="issueDate || new Date(Date.now())"
@ -62,6 +67,7 @@ const contactTel = defineModel<string>('contactTel');
:disabled="!readonly" :disabled="!readonly"
/> />
<q-input <q-input
id="input-task-code"
:label="$t('taskOrder.code')" :label="$t('taskOrder.code')"
outlined outlined
dense dense
@ -72,6 +78,7 @@ const contactTel = defineModel<string>('contactTel');
/> />
<q-input <q-input
id="input-task-name"
:readonly :readonly
:label="$t('general.name', { msg: $t('taskOrder.title') })" :label="$t('general.name', { msg: $t('taskOrder.title') })"
outlined outlined
@ -80,6 +87,7 @@ const contactTel = defineModel<string>('contactTel');
v-model="taskName" v-model="taskName"
/> />
<q-input <q-input
id="input-contact-name"
:readonly :readonly
:label="$t('taskOrder.contactName')" :label="$t('taskOrder.contactName')"
outlined outlined
@ -88,6 +96,7 @@ const contactTel = defineModel<string>('contactTel');
v-model="contactName" v-model="contactName"
/> />
<q-input <q-input
id="input-contact-tel"
:readonly :readonly
:label="$t('general.telephone')" :label="$t('general.telephone')"
outlined outlined
@ -96,6 +105,7 @@ const contactTel = defineModel<string>('contactTel');
v-model="contactTel" v-model="contactTel"
/> />
<q-input <q-input
id="input-made-by"
:readonly :readonly
:label="$t('taskOrder.madeBy')" :label="$t('taskOrder.madeBy')"
outlined outlined

View file

@ -32,6 +32,7 @@ const summaryPrice = defineModel<{
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
id="expansion-payment"
dense dense
class="overflow-hidden bordered full-width" class="overflow-hidden bordered full-width"
switch-toggle-side switch-toggle-side
@ -47,6 +48,7 @@ const summaryPrice = defineModel<{
<main class="q-px-md q-py-sm surface-1"> <main class="q-px-md q-py-sm surface-1">
<QuotationFormInfo <QuotationFormInfo
id="form-info-payment"
task-order task-order
:task-order-complete="complete" :task-order-complete="complete"
v-model:pay-type="payType" v-model:pay-type="payType"

View file

@ -111,6 +111,7 @@ function taskOrderStatus(value: TaskStatus) {
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
id="expansion-product-list"
dense dense
class="overflow-hidden bordered full-width" class="overflow-hidden bordered full-width"
switch-toggle-side switch-toggle-side
@ -123,9 +124,11 @@ function taskOrderStatus(value: TaskStatus) {
<span <span
class="row items-center justify-between full-width" class="row items-center justify-between full-width"
style="min-height: 31.01px" style="min-height: 31.01px"
id="header-product-list"
> >
{{ $t('general.information', { msg: $t('taskOrder.productList') }) }} {{ $t('general.information', { msg: $t('taskOrder.productList') }) }}
<AddButton <AddButton
id="btn-add-product"
icon-only icon-only
@click.stop="$emit('addProduct')" @click.stop="$emit('addProduct')"
v-if="!readonly" v-if="!readonly"
@ -135,6 +138,7 @@ function taskOrderStatus(value: TaskStatus) {
<main class="q-px-md q-py-sm surface-1"> <main class="q-px-md q-py-sm surface-1">
<q-table <q-table
id="table-product-list"
:columns=" :columns="
creditNote creditNote
? productColumn.filter( ? productColumn.filter(
@ -180,7 +184,7 @@ function taskOrderStatus(value: TaskStatus) {
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>" } & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
> >
<q-tr class="text-center"> <q-tr class="text-center">
<q-td> <q-td :id="`product-order-${props.rowIndex}`">
{{ props.rowIndex + 1 }} {{ props.rowIndex + 1 }}
</q-td> </q-td>
<q-td> <q-td>
@ -206,16 +210,17 @@ function taskOrderStatus(value: TaskStatus) {
</q-td> </q-td>
<q-td class="text-left" v-if="!creditNote"> <q-td class="text-left" v-if="!creditNote">
<BadgeComponent <BadgeComponent
:id="`badge-status-${props.row.product.code}`"
hide-icon hide-icon
:hsla-color="taskOrderStatus(props.row.product.taskStatus)" :hsla-color="taskOrderStatus(props.row.product.taskStatus)"
:title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`" :title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`"
/> />
</q-td> </q-td>
<q-td> <q-td :id="`product-amount-${props.row.product.code}`">
{{ props.row.list.length }} {{ props.row.list.length }}
</q-td> </q-td>
<q-td class="text-right"> <q-td :id="`product-price-per-unit-${props.row.product.code}`" class="text-right">
{{ {{
formatNumberDecimal( formatNumberDecimal(
calcPricePerUnit(props.row.product) + calcPricePerUnit(props.row.product) +
@ -230,6 +235,7 @@ function taskOrderStatus(value: TaskStatus) {
<!-- TODO: display price detail --> <!-- TODO: display price detail -->
<q-td align="center" v-if="!creditNote"> <q-td align="center" v-if="!creditNote">
<q-input <q-input
:id="`input-discount-${props.row.product.code}`"
:readonly :readonly
:bg-color="readonly ? 'transparent' : ''" :bg-color="readonly ? 'transparent' : ''"
dense dense
@ -267,7 +273,11 @@ function taskOrderStatus(value: TaskStatus) {
/> />
</q-td> </q-td>
<!-- before vat --> <!-- before vat -->
<q-td class="text-right" v-if="!creditNote"> <q-td
:id="`product-price-before-vat-${props.row.product.code}`"
class="text-right"
v-if="!creditNote"
>
{{ {{
formatNumberDecimal( formatNumberDecimal(
props.row.product.serviceChargeCalcVat props.row.product.serviceChargeCalcVat
@ -284,7 +294,11 @@ function taskOrderStatus(value: TaskStatus) {
}} }}
</q-td> </q-td>
<!-- vat --> <!-- vat -->
<q-td class="text-right" v-if="!creditNote"> <q-td
:id="`product-vat-${props.row.product.code}`"
class="text-right"
v-if="!creditNote"
>
{{ {{
formatNumberDecimal( formatNumberDecimal(
props.row.product.serviceChargeCalcVat props.row.product.serviceChargeCalcVat
@ -301,7 +315,7 @@ function taskOrderStatus(value: TaskStatus) {
}} }}
</q-td> </q-td>
<!-- total --> <!-- total -->
<q-td class="text-right"> <q-td :id="`product-total-price-${props.row.product.code}`" class="text-right">
{{ {{
formatNumberDecimal( formatNumberDecimal(
calcPrice(props.row.product, props.row.list.length), calcPrice(props.row.product, props.row.list.length),
@ -311,6 +325,7 @@ function taskOrderStatus(value: TaskStatus) {
</q-td> </q-td>
<q-td> <q-td>
<q-btn <q-btn
:id="`btn-toggle-employee-${props.row.product.code}`"
dense dense
flat flat
class="rounded" class="rounded"
@ -334,6 +349,7 @@ function taskOrderStatus(value: TaskStatus) {
<q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props"> <q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props">
<q-td colspan="100%" style="padding: 16px"> <q-td colspan="100%" style="padding: 16px">
<TableEmployee <TableEmployee
:id="`table-employee-in-product-${props.row.product.code}`"
:step-on="!creditNote" :step-on="!creditNote"
:status-on="creditNote" :status-on="creditNote"
:rows="props.row.list" :rows="props.row.list"
@ -351,7 +367,9 @@ function taskOrderStatus(value: TaskStatus) {
}) })
}} }}
</span> </span>
<div class="surface-3 q-px-sm rounded">{{ taskList.length }}</div> <div class="surface-3 q-px-sm rounded" id="total-product-count">
{{ taskList.length }}
</div>
</div> </div>
</main> </main>
</q-expansion-item> </q-expansion-item>

View file

@ -33,6 +33,7 @@ const getToolbarConfig = computed(() => {
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
id="expansion-remark"
dense dense
class="overflow-hidden bordered full-width" class="overflow-hidden bordered full-width"
switch-toggle-side switch-toggle-side
@ -48,6 +49,7 @@ const getToolbarConfig = computed(() => {
<main class="surface-1 q-pa-md full-width"> <main class="surface-1 q-pa-md full-width">
<q-editor <q-editor
id="editor-remark"
dense dense
:readonly="readonly || !remarkWrite" :readonly="readonly || !remarkWrite"
:model-value=" :model-value="
@ -90,6 +92,7 @@ const getToolbarConfig = computed(() => {
<template v-if="!readonly" v-slot:toggle> <template v-if="!readonly" v-slot:toggle>
<div class="text-caption row no-wrap q-px-sm"> <div class="text-caption row no-wrap q-px-sm">
<MainButton <MainButton
id="btn-remark-view"
:solid="!remarkWrite" :solid="!remarkWrite"
icon="mdi-eye-outline" icon="mdi-eye-outline"
color="0 0% 40%" color="0 0% 40%"
@ -102,6 +105,7 @@ const getToolbarConfig = computed(() => {
{{ $t('general.view', { msg: $t('general.example') }) }} {{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton> </MainButton>
<MainButton <MainButton
id="btn-remark-edit"
:solid="remarkWrite" :solid="remarkWrite"
icon="mdi-pencil-outline" icon="mdi-pencil-outline"
color="0 0% 40%" color="0 0% 40%"

View file

@ -47,11 +47,17 @@ defineProps<{
<div class="row q-col-gutter-sm q-px-md q-py-sm"> <div class="row q-col-gutter-sm q-px-md q-py-sm">
<DataDisplay <DataDisplay
class="col-md col-6" class="col-md col-6"
id="dd-recipient"
:label="$t('taskOrder.recipientOrSender')" :label="$t('taskOrder.recipientOrSender')"
> >
<template #value> <template #value>
<q-avatar size="md" class="q-mr-xs"> <q-avatar size="md" class="q-mr-xs">
<q-img class="text-center" :ratio="1" :src="contactUrl"> <q-img
:id="`img-avatar-${contactName}`"
class="text-center"
:ratio="1"
:src="contactUrl"
>
<template #error> <template #error>
<div <div
class="no-padding full-width full-height flex items-center justify-center" class="no-padding full-width full-height flex items-center justify-center"
@ -59,6 +65,7 @@ defineProps<{
> >
<q-img <q-img
v-if="gender" v-if="gender"
:id="`img-gender-${contactName}`"
:src=" :src="
gender === 'male' gender === 'male'
? '/no-img-man.png' ? '/no-img-man.png'
@ -67,6 +74,7 @@ defineProps<{
/> />
<q-icon <q-icon
v-else v-else
:id="`icon-avatar-${contactName}`"
size="sm" size="sm"
name="mdi-account-outline" name="mdi-account-outline"
style="color: white" style="color: white"
@ -81,18 +89,21 @@ defineProps<{
<DataDisplay <DataDisplay
class="col-md col-6" class="col-md col-6"
id="dd-telephone"
:label="$t('general.telephone')" :label="$t('general.telephone')"
:value="contactTel || '-'" :value="contactTel || '-'"
/> />
<DataDisplay <DataDisplay
class="col-md col-6" class="col-md col-6"
id="dd-email"
:label="$t('form.email')" :label="$t('form.email')"
:value="email || '-'" :value="email || '-'"
/> />
<DataDisplay <DataDisplay
class="col-md col-6" class="col-md col-6"
id="dd-work-start-date"
:label="$t('taskOrder.workStartDate')" :label="$t('taskOrder.workStartDate')"
> >
<template #value> <template #value>
@ -106,6 +117,7 @@ defineProps<{
<DataDisplay <DataDisplay
class="col-md col-6" class="col-md col-6"
id="dd-work-submission-date"
:label="$t('taskOrder.workSubmissionDate')" :label="$t('taskOrder.workSubmissionDate')"
> >
<template #value> <template #value>
@ -117,9 +129,14 @@ defineProps<{
</template> </template>
</DataDisplay> </DataDisplay>
<DataDisplay class="col-md col-6" :label="$t('general.status')"> <DataDisplay
id="dd-status"
class="col-md col-6"
:label="$t('general.status')"
>
<template #value> <template #value>
<BadgeComponent <BadgeComponent
:id="`badge-status-${contactName}`"
v-if="status" v-if="status"
hide-icon hide-icon
:title=" :title="

View file

@ -814,7 +814,7 @@ watch(
> >
<section class="banner" :class="{ dark: $q.dark.isActive }"></section> <section class="banner" :class="{ dark: $q.dark.isActive }"></section>
<div style="flex: 1" class="row items-center"> <div style="flex: 1" class="row items-center">
<RouterLink to="/task-order"> <RouterLink to="/task-order" id="link-task-order">
<q-img src="/icons/favicon-512x512.png" width="3rem" /> <q-img src="/icons/favicon-512x512.png" width="3rem" />
</RouterLink> </RouterLink>
<span class="column text-h6 text-bold q-ml-md"> <span class="column text-h6 text-bold q-ml-md">
@ -863,6 +863,7 @@ watch(
<!-- TODO: replace step and status --> <!-- TODO: replace step and status -->
<StateButton <StateButton
v-for="i in statusTabForm" v-for="i in statusTabForm"
:id="`btn-status-${i.title}`"
:key="i.title" :key="i.title"
:label="$t(`taskOrder.${i.title}`)" :label="$t(`taskOrder.${i.title}`)"
:status-active="i.active?.()" :status-active="i.active?.()"
@ -916,6 +917,7 @@ watch(
" "
> >
<DocumentExpansion <DocumentExpansion
id="expansion-document"
:readonly="!['create', 'edit'].includes(state.mode || '')" :readonly="!['create', 'edit'].includes(state.mode || '')"
v-model:registered-branch-id="currentFormData.registeredBranchId" v-model:registered-branch-id="currentFormData.registeredBranchId"
v-model:institution-id="currentFormData.institutionId" v-model:institution-id="currentFormData.institutionId"
@ -935,6 +937,7 @@ watch(
/> />
</q-form> </q-form>
<ProductExpansion <ProductExpansion
id="expansion-product"
ref="refProductExpansion" ref="refProductExpansion"
v-if=" v-if="
view === TaskOrderStatus.Pending || view === TaskOrderStatus.Pending ||
@ -947,6 +950,7 @@ watch(
/> />
<PaymentExpansion <PaymentExpansion
id="expansion-payment"
v-model:summary-price="summaryPrice" v-model:summary-price="summaryPrice"
:complete="view === TaskOrderStatus.Complete" :complete="view === TaskOrderStatus.Complete"
v-if=" v-if="
@ -956,6 +960,7 @@ watch(
/> />
<AdditionalFileExpansion <AdditionalFileExpansion
id="expansion-additional-file"
:readonly="!['create', 'edit'].includes(state.mode || '')" :readonly="!['create', 'edit'].includes(state.mode || '')"
v-if=" v-if="
view === TaskOrderStatus.Pending || view === TaskOrderStatus.Pending ||
@ -1014,6 +1019,7 @@ watch(
/> />
<!-- TODO: blind remark, urgent --> <!-- TODO: blind remark, urgent -->
<RemarkExpansion <RemarkExpansion
id="expansion-remark"
v-if=" v-if="
view === TaskOrderStatus.Pending || view === TaskOrderStatus.Pending ||
view === TaskOrderStatus.Complete view === TaskOrderStatus.Complete
@ -1038,6 +1044,7 @@ watch(
" "
> >
<InfoMessengerExpansion <InfoMessengerExpansion
:id="`expansion-messenger-${messengerIndex}`"
v-for="(v, messengerIndex) in messengerListGroup" v-for="(v, messengerIndex) in messengerListGroup"
:key="messengerIndex" :key="messengerIndex"
:gender="getMessengerName(v.responsibleUser, { gender: true })" :gender="getMessengerName(v.responsibleUser, { gender: true })"
@ -1073,6 +1080,7 @@ watch(
class="bordered-b" class="bordered-b"
> >
<q-expansion-item <q-expansion-item
:id="`expansion-product-${productIndex}`"
dense dense
class="overflow-hidden" class="overflow-hidden"
switch-toggle-side switch-toggle-side
@ -1246,6 +1254,7 @@ watch(
<nav class="row justify-end"> <nav class="row justify-end">
<MainButton <MainButton
class="q-mr-auto" class="q-mr-auto"
id="btn-view-example"
v-if="currentFormData.id" v-if="currentFormData.id"
outlined outlined
icon="mdi-play-box-outline" icon="mdi-play-box-outline"
@ -1260,10 +1269,16 @@ watch(
{{ $t('general.view', { msg: $t('general.example') }) }} {{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton> </MainButton>
<div class="row" style="gap: var(--size-2)"> <div class="row" style="gap: var(--size-2)">
<UndoButton outlined @click="undo()" v-if="state.mode === 'edit'" /> <UndoButton
id="btn-undo"
outlined
@click="undo()"
v-if="state.mode === 'edit'"
/>
<CancelButton <CancelButton
v-if="state.mode !== 'edit'" v-if="state.mode !== 'edit'"
:label="$t('dialog.action.close')" :label="$t('dialog.action.close')"
id="btn-close"
outlined outlined
@click="closeTab()" @click="closeTab()"
/> />
@ -1275,12 +1290,14 @@ watch(
" "
> >
<SaveButton <SaveButton
id="btn-save"
v-if="state.mode && ['create', 'edit'].includes(state.mode)" v-if="state.mode && ['create', 'edit'].includes(state.mode)"
@click="() => formDocument.submit()" @click="() => formDocument.submit()"
solid solid
/> />
<EditButton <EditButton
v-else v-else
id="btn-edit"
class="no-print" class="no-print"
@click="state.mode = 'edit'" @click="state.mode = 'edit'"
solid solid
@ -1288,6 +1305,7 @@ watch(
</template> </template>
<SaveButton <SaveButton
v-if=" v-if="
canAccess('taskOrder', 'edit') &&
state.mode !== 'create' && state.mode !== 'create' &&
view === TaskOrderStatus.Validate && view === TaskOrderStatus.Validate &&
fullTaskOrder?.taskOrderStatus !== TaskOrderStatus.Pending && fullTaskOrder?.taskOrderStatus !== TaskOrderStatus.Pending &&
@ -1326,6 +1344,7 @@ watch(
" "
:label="$t('taskOrder.confirmEndWork')" :label="$t('taskOrder.confirmEndWork')"
icon="mdi-check" icon="mdi-check"
id="btn-confirm-end-work"
solid solid
></SaveButton> ></SaveButton>
</div> </div>
@ -1335,6 +1354,7 @@ watch(
<!-- SEC: Dialog --> <!-- SEC: Dialog -->
<SelectReadyRequestWork <SelectReadyRequestWork
id="dialog-select-request-work"
:task-list-group="taskListGroup" :task-list-group="taskListGroup"
:fetch-params="{ readyToTask: true }" :fetch-params="{ readyToTask: true }"
v-model:open="pageState.productDialog" v-model:open="pageState.productDialog"

View file

@ -98,6 +98,7 @@ function tooltip(id: string) {
<main class="q-py-sm q-px-md scroll"> <main class="q-py-sm q-px-md scroll">
<q-select <q-select
:readonly :readonly
id="select-fail-task"
dense dense
outlined outlined
multiple multiple
@ -131,6 +132,7 @@ function tooltip(id: string) {
> >
<template #selected-item="scope"> <template #selected-item="scope">
<q-chip <q-chip
:id="`chip-fail-task-${taskStatusList[scope.index].code}`"
dense dense
:removable="!readonly" :removable="!readonly"
@remove="scope.removeAtIndex(scope.index)" @remove="scope.removeAtIndex(scope.index)"
@ -170,6 +172,7 @@ function tooltip(id: string) {
<template #option="scope"> <template #option="scope">
<q-item <q-item
:id="`option-fail-task-${scope.opt.request.code}`"
clickable clickable
v-bind="scope.itemProps" v-bind="scope.itemProps"
class="row items-start col-12 no-padding" class="row items-start col-12 no-padding"
@ -230,6 +233,7 @@ function tooltip(id: string) {
<SelectInput <SelectInput
:readonly :readonly
id="select-fail-type"
:option="[ :option="[
{ {
label: $t('taskOrder.documentSubmitFailed'), label: $t('taskOrder.documentSubmitFailed'),
@ -260,6 +264,7 @@ function tooltip(id: string) {
<div v-if="failedType === 'other'" class="q-mt-sm rounded"> <div v-if="failedType === 'other'" class="q-mt-sm rounded">
<q-editor <q-editor
id="editor-fail-comment"
dense dense
flat flat
v-model="failedComment" v-model="failedComment"
@ -279,8 +284,18 @@ function tooltip(id: string) {
</main> </main>
<template #footer v-if="!readonly"> <template #footer v-if="!readonly">
<CancelButton class="q-ml-auto" outlined @click="$emit('close')" /> <CancelButton
<SaveButton class="q-ml-sm" solid @click="submit" /> id="btn-fail-remark-cancel"
class="q-ml-auto"
outlined
@click="$emit('close')"
/>
<SaveButton
id="btn-fail-remark-save"
class="q-ml-sm"
solid
@click="submit"
/>
</template> </template>
</DialogFormContainer> </DialogFormContainer>
</template> </template>

View file

@ -394,8 +394,14 @@ watch(
{ {
label: $t('general.customer'), label: $t('general.customer'),
value: value:
item.row.quotation.customerBranch.registerName || item.row.quotation.customerBranch.customer
`${item.row.quotation.customerBranch?.firstName || '-'} ${item.row.quotation.customerBranch?.lastName || ''}`, .customerType === 'CORP'
? $i18n.locale === 'tha'
? item.row.quotation.customerBranch.registerName
: item.row.quotation.customerBranch.registerNameEN
: $i18n.locale === 'tha'
? `${item.row.quotation.customerBranch?.firstName || '-'} ${item.row.quotation.customerBranch?.lastName || ''}`
: `${item.row.quotation.customerBranch?.firstNameEN || '-'} ${item.row.quotation.customerBranch?.lastNameEN || ''}`,
}, },
{ {
label: $t('taskOrder.issueDate'), label: $t('taskOrder.issueDate'),

View file

@ -83,11 +83,16 @@ defineEmits<{
<!-- NOTE: custom column will starts with # --> <!-- NOTE: custom column will starts with # -->
<template v-if="!col.name.startsWith('#')"> <template v-if="!col.name.startsWith('#')">
<span> <span>
<template v-if="typeof col.field === 'function'">
{{ {{
typeof col.field === 'string' typeof col.field(props.row) === 'object'
? props.row[col.field as keyof Invoice] ? col.field(props.row)[$i18n.locale]
: col.field(props.row) : col.field(props.row)
}} }}
</template>
<template v-else>
{{ props.row[col.field as keyof Invoice] }}
</template>
</span> </span>
</template> </template>
<template v-if="col.name === '#order'"> <template v-if="col.name === '#order'">

View file

@ -29,7 +29,16 @@ export const columns = [
name: 'customer', name: 'customer',
align: 'center', align: 'center',
label: 'general.customer', label: 'general.customer',
field: (v: Invoice) => v.quotation.customerBranch.registerName, field: (v: Invoice) =>
v.quotation.customerBranch.customer.customerType === 'CORP'
? {
tha: v.quotation.customerBranch.registerName,
eng: v.quotation.customerBranch.registerNameEN,
}
: {
tha: `${v.quotation.customerBranch.firstName || ''} ${v.quotation.customerBranch.lastName || ''}`,
eng: `${v.quotation.customerBranch.firstNameEN || ''} ${v.quotation.customerBranch.lastNameEN || ''}`,
},
}, },
{ {

View file

@ -134,6 +134,11 @@ onMounted(async () => {
creditNote.getCreditNoteStats().then((res) => res && (stats.value = res)); creditNote.getCreditNoteStats().then((res) => res && (stats.value = res));
getList(); getList();
window.addEventListener('focus', () => {
creditNote.getCreditNoteStats().then((res) => res && (stats.value = res));
getList();
});
}); });
watch( watch(
@ -406,10 +411,12 @@ watch(
value: value:
item.row.quotation.customerBranch.customer item.row.quotation.customerBranch.customer
.customerType === 'CORP' .customerType === 'CORP'
? $i18n.locale === 'tha'
? item.row.quotation.customerBranch.registerName ? item.row.quotation.customerBranch.registerName
: item.row.quotation.customerBranch.registerNameEN
: $i18n.locale === 'tha' : $i18n.locale === 'tha'
? `${item.row.quotation.customerBranch.firstName} ${item.row.quotation.customerBranch.lastName}` ? `${item.row.quotation.customerBranch.firstName || ''} ${item.row.quotation.customerBranch.lastName || ''}`
: `${item.row.quotation.customerBranch.firstNameEN} ${item.row.quotation.customerBranch.lastNameEN}`, : `${item.row.quotation.customerBranch.firstNameEN || ''} ${item.row.quotation.customerBranch.lastNameEN || ''}`,
}, },
{ {
label: $t('requestList.quotationCode'), label: $t('requestList.quotationCode'),

View file

@ -8,7 +8,7 @@ import { columns } from './constants';
import KebabAction from 'src/components/shared/KebabAction.vue'; import KebabAction from 'src/components/shared/KebabAction.vue';
const creditNote = useCreditNote(); const creditNote = useCreditNote();
const { data, page } = storeToRefs(creditNote); const { data, page, pageSize } = storeToRefs(creditNote);
const prop = defineProps<{ const prop = defineProps<{
grid: boolean; grid: boolean;
@ -26,7 +26,14 @@ const visible = computed(() =>
<template> <template>
<q-table <q-table
:rows-per-page-options="[0]" :rows-per-page-options="[0]"
:rows="data.map((item, i) => ({ ...item, _index: i, _page: page }))" :rows="
data.map((item, i) => ({
...item,
_index: i,
_page: page,
_pageSize: pageSize,
}))
"
:columns="visible" :columns="visible"
:grid :grid
hide-bottom hide-bottom
@ -52,7 +59,7 @@ const visible = computed(() =>
<template <template
v-slot:body="props: { v-slot:body="props: {
row: CreditNote & { _index: number; _page: number }; row: CreditNote & { _index: number; _page: number; _pageSize: number };
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>" } & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
> >
<q-tr :class="{ dark: $q.dark.isActive }" class="text-center"> <q-tr :class="{ dark: $q.dark.isActive }" class="text-center">

View file

@ -32,8 +32,9 @@ export const columns = [
name: 'order', name: 'order',
align: 'center', align: 'center',
label: 'general.order', label: 'general.order',
field: (data: CreditNote & { _index: number; _page: number }) => field: (
data._page * (data._index + 1), data: CreditNote & { _index: number; _page: number; _pageSize: number },
) => (data._page - 1) * data._pageSize + (data._index + 1),
}, },
{ {
name: 'code', name: 'code',

View file

@ -209,47 +209,7 @@ const selectedWorker = ref<
}[]; }[];
})[] })[]
>([]); >([]);
const selectedWorkerItem = computed(() => { const selectedWorkerItem = ref([]);
return [
...selectedWorker.value.map((e) => ({
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}`
: '',
status: e.status,
})),
...newWorkerList.value.map((v: any) => ({
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',
})),
];
});
const selectedInstallmentNo = ref<number[]>([]); const selectedInstallmentNo = ref<number[]>([]);
const installmentAmount = ref<number>(0); const installmentAmount = ref<number>(0);
@ -374,7 +334,39 @@ function assignProductServiceList() {
function assignSelectedWorker() { function assignSelectedWorker() {
if (debitNoteData.value) if (debitNoteData.value)
selectedWorker.value = debitNoteData.value.worker.map((v) => v.employee); selectedWorkerItem.value = debitNoteData.value.worker.map((e) => {
return {
id: e.employee.id,
foreignRefNo: e.employee.employeePassport
? e.employee.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.employee.firstNameEN} ${e.employee.lastNameEN}`
: `${e.employee.firstName || e.employee.firstNameEN} ${e.employee.lastName || e.employee.lastNameEN}`,
birthDate: dateFormatJS({ date: e.employee.dateOfBirth }),
gender: e.employee.gender,
age: calculateAge(e.employee.dateOfBirth),
nationality: optionStore.mapOption(e.employee.nationality),
documentExpireDate:
e.employee.employeePassport !== undefined &&
e.employee.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employee.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.employee.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.employee.selectedImage}`
: '',
status: e.employee.status,
workerNew: false,
lastNameEN: e.employee.lastNameEN,
lastName: e.employee.lastName,
middleNameEN: e.employee.middleNameEN,
middleName: e.employee.middleName,
firstNameEN: e.employee.firstNameEN,
firstName: e.employee.firstName,
namePrefix: e.employee.namePrefix,
};
});
} }
async function assignFormData(id: string) { async function assignFormData(id: string) {
@ -386,7 +378,7 @@ async function assignFormData(id: string) {
selectedProductGroup.value = selectedProductGroup.value =
data.productServiceList[0]?.product.productGroup?.id || ''; data.productServiceList[0]?.product.productGroup?.id || '';
((previousValue = { (previousValue = {
id: data.id || undefined, id: data.id || undefined,
debitNoteQuotationId: data.debitNoteQuotationId || undefined, debitNoteQuotationId: data.debitNoteQuotationId || undefined,
productServiceList: structuredClone( productServiceList: structuredClone(
@ -412,7 +404,7 @@ async function assignFormData(id: string) {
quotationId: data.debitNoteQuotationId, quotationId: data.debitNoteQuotationId,
remark: data.remark || undefined, remark: data.remark || undefined,
}), }),
(currentFormData.value = structuredClone(previousValue))); (currentFormData.value = structuredClone(previousValue));
assignProductServiceList(); assignProductServiceList();
assignSelectedWorker(); assignSelectedWorker();
@ -540,8 +532,6 @@ function getPrice(
) { ) {
if (filterHook) list = list.filter(filterHook); if (filterHook) list = list.filter(filterHook);
console.log(list);
return list.reduce( return list.reduce(
(a, c) => { (a, c) => {
if ( if (
@ -815,15 +805,11 @@ async function submit() {
worker: JSON.parse( worker: JSON.parse(
JSON.stringify([ JSON.stringify([
...selectedWorker.value.map((v) => { ...selectedWorkerItem.value.map((v) => {
{ {
return v.id; return v.id;
} }
}), }),
...newWorkerList.value.map((v) => {
const { attachment, ...payload } = v;
return pageState.mode === 'edit' ? payload.id : payload;
}),
]), ]),
), ),
dueDate: currentFormData.value.dueDate, dueDate: currentFormData.value.dueDate,
@ -902,7 +888,7 @@ function storeDataLocal() {
dueDate: currentFormData.value.dueDate, dueDate: currentFormData.value.dueDate,
}, },
productServicelist: productService.value, productServicelist: productService.value,
selectedWorker: selectedWorker.value, selectedWorker: selectedWorkerItem.value,
createdBy: getName(), createdBy: getName(),
}, },
}), }),
@ -927,6 +913,69 @@ function closeAble() {
return window.opener !== null; return window.opener !== null;
} }
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}`
: '',
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,
})),
];
}
watch( watch(
() => pageState.mode, () => pageState.mode,
() => toggleMode(pageState.mode), () => toggleMode(pageState.mode),
@ -1090,6 +1139,7 @@ async function submitAccepted() {
<PaymentForm <PaymentForm
v-if="view === QuotationStatus.PaymentPending" v-if="view === QuotationStatus.PaymentPending"
is-debit-note is-debit-note
:branch-id="quotationData?.registeredBranchId"
:readonly="isRoleInclude(['sale', 'head_of_sale'])" :readonly="isRoleInclude(['sale', 'head_of_sale'])"
:data="debitNoteData" :data="debitNoteData"
@fetch-status=" @fetch-status="
@ -1111,7 +1161,7 @@ async function submitAccepted() {
" "
:row-worker="selectedWorkerItem" :row-worker="selectedWorkerItem"
@add-worker="() => (pageState.employeeModal = true)" @add-worker="() => (pageState.employeeModal = true)"
@delete="(i) => deleteItem(selectedWorker, i)" @delete="(i) => deleteItem(selectedWorkerItem, i)"
/> />
<!-- #TODO add openProductDialog at @add-product--> <!-- #TODO add openProductDialog at @add-product-->
@ -1331,7 +1381,7 @@ async function submitAccepted() {
<SaveButton <SaveButton
v-if="pageState.mode !== 'info'" v-if="pageState.mode !== 'info'"
:disabled=" :disabled="
selectedWorkerItem.length === 0 && productService.length === 0 selectedWorkerItem.length === 0 || productService.length === 0
" "
@click="submit" @click="submit"
solid solid
@ -1376,13 +1426,12 @@ async function submitAccepted() {
<!-- add employee quotation --> <!-- add employee quotation -->
<QuotationFormWorkerSelect <QuotationFormWorkerSelect
:preselect-worker="selectedWorker" :preselect-worker="selectedWorkerItem"
:customerBranchId="quotationData?.customerBranchId" :customerBranchId="quotationData?.customerBranchId"
v-model:open="pageState.employeeModal" v-model:open="pageState.employeeModal"
v-model:new-worker-list="newWorkerList"
@success=" @success="
(v) => { (v) => {
selectedWorker = v.worker; combineWorker(v.newWorker, v.worker);
} }
" "
/> />

View file

@ -143,6 +143,15 @@ onMounted(async () => {
}); });
getList(); getList();
window.addEventListener('focus', () => {
debitNote.getDebitNoteStats().then((res) => {
if (res) {
stats.value = res;
}
});
getList();
});
}); });
watch( watch(
@ -425,10 +434,12 @@ watch(
label: $t('quotation.customer'), label: $t('quotation.customer'),
value: value:
item.row.customerBranch.customer.customerType === 'CORP' item.row.customerBranch.customer.customerType === 'CORP'
? $i18n.locale === 'tha'
? item.row.customerBranch.registerName ? item.row.customerBranch.registerName
: item.row.customerBranch.registerNameEN
: $i18n.locale === 'tha' : $i18n.locale === 'tha'
? `${item.row.customerBranch.firstName} ${item.row.customerBranch.lastName}` ? `${item.row.customerBranch.firstName || ''} ${item.row.customerBranch.lastName || ''}`
: `${item.row.customerBranch.firstNameEN} ${item.row.customerBranch.lastNameEN}`, : `${item.row.customerBranch.firstNameEN || ''} ${item.row.customerBranch.lastNameEN || ''}`,
}, },
{ {
label: $t('requestList.quotationCode'), label: $t('requestList.quotationCode'),

View file

@ -343,9 +343,16 @@ watch(
{ {
label: $t('general.customer'), label: $t('general.customer'),
value: value:
item.row.invoice.quotation.customerBranch item.row.invoice.quotation.customerBranch.customer
.registerName || .customerType === 'CORP'
`${item.row.invoice.quotation.customerBranch?.firstName || '-'} ${item.row.invoice.quotation.customerBranch?.lastName || ''}`, ? $i18n.locale === 'tha'
? item.row.invoice.quotation.customerBranch
.registerName
: item.row.invoice.quotation.customerBranch
.registerNameEN
: $i18n.locale === 'tha'
? `${item.row.invoice.quotation.customerBranch?.firstName || '-'} ${item.row.invoice.quotation.customerBranch?.lastName || ''}`
: `${item.row.invoice.quotation.customerBranch?.firstNameEN || '-'} ${item.row.invoice.quotation.customerBranch?.lastNameEN || ''}`,
}, },
{ {
label: $t('taskOrder.issueDate'), label: $t('taskOrder.issueDate'),

View file

@ -45,7 +45,7 @@ const fieldSelected = ref<('no' | 'name' | 'nameEN')[]>([
const fieldSelectedOption = ref<{ label: string; value: string }[]>([ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
{ {
label: 'general.order', label: 'general.order',
value: 'orderNumber', value: 'no',
}, },
{ {
@ -312,25 +312,6 @@ watch(
</q-input> </q-input>
<div class="row col-md-5 justify-end" style="white-space: nowrap"> <div class="row col-md-5 justify-end" style="white-space: nowrap">
<q-select
v-if="$q.screen.gt.sm"
v-model="statusFilter"
outlined
dense
option-value="value"
option-label="label"
class="col"
:class="{ 'offset-md-5': pageState.gridView }"
map-options
emit-value
:for="'field-select-status'"
:hide-dropdown-icon="$q.screen.lt.sm"
:options="[
{ label: $t('general.all'), value: 'all' },
{ label: $t('general.active'), value: 'statusACTIVE' },
{ label: $t('general.inactive'), value: 'statusINACTIVE' },
]"
/>
<q-select <q-select
v-if="!pageState.gridView" v-if="!pageState.gridView"
id="select-field" id="select-field"
@ -595,10 +576,18 @@ watch(
></q-badge> ></q-badge>
</q-avatar> </q-avatar>
<span class="text-weight-bold column q-pl-md"> <span class="text-weight-bold column q-pl-md">
{{ props.row.name }} {{
$i18n.locale === 'tha'
? props.row.name
: props.row.nameEN
}}
<span class="text-caption app-text-muted-2"> <span class="text-caption app-text-muted-2">
{{ props.row.nameEN }} {{
$i18n.locale === 'tha'
? props.row.nameEN
: props.row.name
}}
</span> </span>
</span> </span>
<nav <nav

View file

@ -447,6 +447,17 @@ const useBranchStore = defineStore('api-branch', () => {
return false; return false;
} }
async function fetchListBankByBranch(branchId: string) {
const res = await api.get(`/branch/${branchId}/bank`, {
headers: { 'X-Rtid': flowStore.rtid },
});
if (!res) return false;
if (res.status === 200) return res.data;
return false;
}
return { return {
data, data,
map, map,
@ -475,6 +486,8 @@ const useBranchStore = defineStore('api-branch', () => {
fetchByIdAttachment, fetchByIdAttachment,
putAttachment, putAttachment,
deleteByIdAttachment, deleteByIdAttachment,
fetchListBankByBranch,
}; };
}); });

View file

@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
import { api } from 'src/boot/axios'; import { api } from 'src/boot/axios';
import { Pagination } from 'stores/types'; import { Pagination } from 'stores/types';
import { getToken } from 'src/services/keycloak';
import { import {
ProductGroup, ProductGroup,
@ -19,6 +20,7 @@ import {
import { ref } from 'vue'; import { ref } from 'vue';
const useProductServiceStore = defineStore('api-product-service', () => { const useProductServiceStore = defineStore('api-product-service', () => {
const baseUrl = import.meta.env.VITE_API_BASE_URL;
const splitPay = ref<number>(0); const splitPay = ref<number>(0);
const workNameItems = ref< const workNameItems = ref<
{ {
@ -528,6 +530,53 @@ const useProductServiceStore = defineStore('api-product-service', () => {
if (!res) return false; if (!res) return false;
} }
async function productExport(params: {
status?: 'CREATED' | 'ACTIVE' | 'INACTIVE';
shared?: boolean;
productGroupId?: string;
query?: string;
page?: number;
pageSize?: number;
orderField?: string;
orderBy?: string;
activeOnly?: boolean;
startDate?: Date;
endDate?: Date;
}) {
let url = baseUrl + '/' + 'product-export';
const queryParams = new URLSearchParams(
Object.keys(params).reduce((acc: Record<string, string>, key) => {
const value = params[key as keyof typeof params];
if (value !== undefined && value !== null && value !== '') {
const stringValue =
typeof value === 'boolean' || typeof value === 'number'
? String(value)
: value instanceof Date
? value.toISOString()
: String(value);
acc[key] = stringValue;
}
return acc;
}, {}),
);
url += '?' + queryParams.toString();
const res = await fetch(url, {
headers: { ['Authorization']: 'Bearer ' + (await getToken()) },
});
const text = await res.json();
const blob = new Blob([text], { type: 'text/csv' });
if (res.ok && blob) {
const a = document.createElement('a');
a.download = 'customer-report' + '.csv';
a.href = window.URL.createObjectURL(blob);
a.click();
a.remove();
}
}
return { return {
workNameItems, workNameItems,
splitPay, splitPay,
@ -568,6 +617,8 @@ const useProductServiceStore = defineStore('api-product-service', () => {
deleteImageByName, deleteImageByName,
importProduct, importProduct,
productExport,
}; };
}); });

View file

@ -23,22 +23,23 @@ const templates = {
installments?: { installments?: {
no: number; no: number;
amount: number; amount: number;
name?: string;
}[]; }[];
}) => { }) => {
if (context?.paymentType === 'Full') { if (context?.paymentType === 'Full') {
return [ return [
'**** เงื่อนไขเพิ่มเติม', `**** ${i18n.global.t('general.additional')}`,
'- เงื่อนไขการชำระเงิน แบบเต็มจำนวน', `- ${i18n.global.t('quotation.paymentCondition')} ${i18n.global.t('quotation.type.Full')}`,
`&nbsp; จำนวน ${formatNumberDecimal(context?.amount || 0, 2)}`, `&nbsp; ${i18n.global.t('general.amount')} ${formatNumberDecimal(context?.amount || 0, 2)}`,
].join('<br/>'); ].join('<br/>');
} else { } else {
return [ return [
'**** เงื่อนไขเพิ่มเติม', `**** ${i18n.global.t('general.additional')}`,
`- เงื่อนไขการชำระเงิน แบบแบ่งจ่าย${context?.paymentType === 'SplitCustom' ? ' กำหนดเอง ' : ' '}${context?.installments?.length} งวด`, `- ${i18n.global.t('quotation.paymentCondition')} ${i18n.global.t('quotation.type.Split')}${context?.paymentType === 'SplitCustom' ? ` (${i18n.global.t('general.specify')}) ` : ' '}${context?.installments?.length} ${i18n.global.t('quotation.receiptDialog.installments')}`,
...(context?.installments?.map( ...(context?.installments?.map((v) => {
(v) => const installmentName = v.name ? ` (${v.name})` : '';
`&nbsp; งวดที่ ${v.no} จำนวน ${formatNumberDecimal(v.amount, 2)}`, return `&nbsp; ${i18n.global.t('quotation.periodNo')} ${v.no}${installmentName} ${i18n.global.t('general.amount')} ${formatNumberDecimal(v.amount, 2)}`;
) || []), }) || []),
].join('<br />'); ].join('<br />');
} }
}, },
@ -73,14 +74,14 @@ const templates = {
const employee = v.request.employee; const employee = v.request.employee;
const branch = v.request.quotation.customerBranch; const branch = v.request.quotation.customerBranch;
return ( return (
`${i + 1}. ` + ` ${i + 1}. ` +
` ${v.request.code}_${branch.customer.customerType === 'PERS' ? `${branch.namePrefix}. ${branch.firstNameEN} ${branch.lastNameEN} `.toUpperCase() : branch.registerName}_` + ` ${v.request.code}_${branch.customer.customerType === 'PERS' ? `${branch.namePrefix}. ${branch.firstNameEN} ${branch.lastNameEN} `.toUpperCase() : i18n.global.locale.value == 'tha' ? branch.registerName : branch.registerNameEN}_` +
`${employee.namePrefix}. ${employee.firstNameEN} ${employee.lastNameEN} `.toUpperCase() + `${employee.namePrefix}. ${employee.firstNameEN} ${employee.lastNameEN} `.toUpperCase() +
`${!!v.failedType && v.failedType !== 'other' ? `${i18n.global.t(`taskOrder.${v.failedType}`)}` : !!v.failedComment ? v.failedComment : ''}` `${!!v.failedType && v.failedType !== 'other' ? `${i18n.global.t(`taskOrder.${v.failedType}`)}` : !!v.failedComment ? v.failedComment : ''}`
); );
}); });
return [ return [
`- ${item.product.name} ราคา ${price} บาท`, `- ${item.product.name} ${i18n.global.t('price', { price: price })} `,
'', '',
...list, ...list,
'', '',