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"
:label="$t('form.birthDate')"
:disabled-dates="disabledAfterToday"
:rules="
employee
? []
: [
(val: string) =>
!!val ||
$t('form.error.selectField', { field: $t('form.birthDate') }),
]
"
:rules="[
(val: string) =>
!!val ||
$t('form.error.selectField', { field: $t('form.birthDate') }),
]"
/>
<q-input

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -102,6 +102,8 @@ const {
deleteWork,
importProduct,
productExport,
} = productServiceStore;
const { workNameItems } = storeToRefs(productServiceStore);
@ -1169,6 +1171,7 @@ function clearFormService() {
profileSubmit.value = false;
imageProduct.value = undefined;
profileFileImg.value = null;
serviceTab.value = 1;
}
function sameFormService() {
@ -1896,6 +1899,25 @@ async function submitWorkName(
else await editWork(workId, data);
}
async function triggerExport() {
productExport({
pageSize: 100_000,
productGroupId: currentIdGroup.value,
query: !!inputSearchProductAndService.value
? inputSearchProductAndService.value
: undefined,
status:
currentStatus.value === 'INACTIVE'
? 'INACTIVE'
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: undefined,
startDate: searchDate.value[0] ? new Date(searchDate.value[0]) : undefined,
endDate: searchDate.value[1] ? new Date(searchDate.value[1]) : undefined,
});
}
watch(
() => formService.value.attributes.workflowId,
async (a, b) => {
@ -2229,7 +2251,6 @@ watch(
</AdvanceSearch>
</template>
</q-input>
<div class="row col-md-6" style="white-space: nowrap">
<q-select
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 class="row col-md-6" style="white-space: nowrap">
@ -4357,6 +4385,7 @@ watch(
<!-- add service -->
<DialogForm
v-if="dialogService"
hide-footer
no-address
no-app-box
@ -4722,6 +4751,7 @@ watch(
<!-- edit service edit package-->
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
<DialogForm
v-if="dialogServiceEdit"
hide-footer
no-address
height="95vh"

View file

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

View file

@ -1,16 +1,9 @@
<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 { 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';
const { t } = useI18n();
import { reactive, ref } from 'vue';
import { useQuotationPayment } from 'src/stores/quotations';
import {
PaymentPayload,
@ -19,16 +12,25 @@ import {
QuotationPaymentData,
} from 'src/stores/quotations/types';
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 { 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 quotationPayment = useQuotationPayment();
const { data: config } = storeToRefs(configStore);
const prop = defineProps<{
branchId: string;
data?: Quotation | QuotationFull | DebitNote;
readonly?: boolean;
isDebitNote?: boolean;
@ -39,6 +41,7 @@ const firstCodePayment = defineModel<string>('firstCodePayment');
const refQFile = ref<InstanceType<typeof QFile>[]>([]);
const refQMenu = ref<InstanceType<typeof QMenu>[]>([]);
const paymentData = ref<QuotationPaymentData[]>([]);
const accountOpt = ref<{ label: string; value: string }[]>([]);
const formPaymentMethod = ref<
{
id: string;
@ -180,7 +183,7 @@ async function selectStatus(
payment.paymentStatus = status;
const payload = {
paymentStatus: payment.paymentStatus,
date: new Date(payment.date),
date: new Date(),
amount: payment.amount,
};
const res = await quotationPayment.updateQuotationPayment(
@ -215,6 +218,7 @@ async function triggerSubmit(id: string) {
formPaymentMethod.value[index].isEdit = false;
setTimeout(async () => {
await fetchData();
await getSlipList(paymentData.value[index], index);
}, 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 () => {
await fetchData();
await fetchBankOption();
});
</script>
<template>
@ -640,95 +661,115 @@ onMounted(async () => {
</template>
<!-- การรบชำระ -->
<section
class="q-px-md q-py-xs text-weight-medium row items-center"
style="background-color: hsla(var(--info-bg) / 0.1)"
>
{{ $t('quotation.receiptDialog.paymentMethod') }}
</section>
<div
class="surface-2 q-px-md q-py-sm row q-col-gutter-sm items-center"
>
<SelectInput
id="input-payment-channel"
for="input-payment-channel"
:readonly="
readonly ||
(!formPaymentMethod[i].isEdit && !!payment.channel)
"
v-model="formPaymentMethod[i].channel"
class="col-md-2 col-6"
:option="[
{
label: $t('creditNote.label.Cash'),
value: 'Cash',
},
{
label: $t('creditNote.label.BankTransfer'),
value: 'BankTransfer',
},
]"
:label="$t('quotation.receiptDialog.paymentMethod')"
@update:model-value="
() => {
if (formPaymentMethod[i].channel === 'Cash') {
formPaymentMethod[i].reference = null;
formPaymentMethod[i].account = null;
}
}
"
/>
<q-input
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
dense
outlined
class="col-md-3 col-6"
v-model="formPaymentMethod[i].reference"
:readonly="
readonly ||
(!formPaymentMethod[i].isEdit && !!payment.channel)
"
:label="$t('quotation.refNo')"
/>
<q-input
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
dense
outlined
class="col"
v-model="formPaymentMethod[i].account"
:readonly="
readonly ||
(!formPaymentMethod[i].isEdit && !!payment.channel)
"
:label="$t('quotation.bankAccount')"
/>
<div class="q-ml-auto">
<UndoButton
v-if="formPaymentMethod[i].isEdit"
icon-only
@click="
() => {
formPaymentMethod[i].isEdit = false;
formPaymentMethod[i].channel = payment.channel;
formPaymentMethod[i].reference = payment.reference;
formPaymentMethod[i].account = payment.account;
}
"
/>
<SaveButton
v-if="!payment.channel || formPaymentMethod[i].isEdit"
icon-only
@click="triggerSubmit(payment.id)"
/>
<EditButton
v-if="
payment.channel && formPaymentMethod[i].isEdit === false
"
icon-only
@click="formPaymentMethod[i].isEdit = true"
/>
</div>
</div>
<template v-if="payment.paymentStatus === 'PaymentSuccess'">
<section
class="q-px-md q-py-xs text-weight-medium row items-center"
style="background-color: hsla(var(--info-bg) / 0.1)"
>
{{ $t('quotation.receiptDialog.paymentMethod') }}
</section>
<q-form
class="column full-height"
@submit.prevent
@submit="triggerSubmit(payment.id)"
>
<div
class="surface-2 q-px-md q-py-sm row q-col-gutter-sm items-center"
>
<SelectInput
id="input-payment-channel"
for="input-payment-channel"
:readonly="
readonly ||
(!formPaymentMethod[i].isEdit && !!payment.channel)
"
v-model="formPaymentMethod[i].channel"
class="col-md-2 col-6"
:rules="[
(val: string) => !!val || $t('form.error.required'),
]"
:option="[
{
label: $t('creditNote.label.Cash'),
value: 'Cash',
},
{
label: $t('creditNote.label.BankTransfer'),
value: 'BankTransfer',
},
]"
:label="$t('quotation.receiptDialog.paymentMethod')"
@update:model-value="
() => {
if (formPaymentMethod[i].channel === 'Cash') {
formPaymentMethod[i].reference = null;
formPaymentMethod[i].account = null;
}
}
"
/>
<q-input
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
dense
outlined
class="col-md-3 col-6"
v-model="formPaymentMethod[i].reference"
:readonly="
readonly ||
(!formPaymentMethod[i].isEdit && !!payment.channel)
"
:label="$t('quotation.refNo')"
:rules="[
(val: string) => !!val || $t('form.error.required'),
]"
hide-bottom-space
/>
<SelectInput
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
id="select-payment-account"
for="select-payment-account"
:readonly="
readonly ||
(!formPaymentMethod[i].isEdit && !!payment.channel)
"
v-model="formPaymentMethod[i].account"
class="col"
:option="accountOpt"
:label="$t('quotation.bankAccount')"
:rules="[
(val: string) => !!val || $t('form.error.required'),
]"
/>
<div class="q-ml-auto">
<UndoButton
v-if="formPaymentMethod[i].isEdit"
icon-only
@click="
() => {
formPaymentMethod[i].isEdit = false;
formPaymentMethod[i].channel = payment.channel;
formPaymentMethod[i].reference = payment.reference;
formPaymentMethod[i].account = payment.account;
}
"
/>
<SaveButton
v-if="!payment.channel || formPaymentMethod[i].isEdit"
icon-only
type="submit"
/>
<EditButton
v-if="
payment.channel && formPaymentMethod[i].isEdit === false
"
icon-only
@click="formPaymentMethod[i].isEdit = true"
/>
</div>
</div>
</q-form>
</template>
<!-- ปโหลดใบเสร -->
<section

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -394,8 +394,14 @@ watch(
{
label: $t('general.customer'),
value:
item.row.quotation.customerBranch.registerName ||
`${item.row.quotation.customerBranch?.firstName || '-'} ${item.row.quotation.customerBranch?.lastName || ''}`,
item.row.quotation.customerBranch.customer
.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'),

View file

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

View file

@ -29,7 +29,16 @@ export const columns = [
name: 'customer',
align: 'center',
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));
getList();
window.addEventListener('focus', () => {
creditNote.getCreditNoteStats().then((res) => res && (stats.value = res));
getList();
});
});
watch(
@ -406,10 +411,12 @@ watch(
value:
item.row.quotation.customerBranch.customer
.customerType === 'CORP'
? item.row.quotation.customerBranch.registerName
? $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}`,
? `${item.row.quotation.customerBranch.firstName || ''} ${item.row.quotation.customerBranch.lastName || ''}`
: `${item.row.quotation.customerBranch.firstNameEN || ''} ${item.row.quotation.customerBranch.lastNameEN || ''}`,
},
{
label: $t('requestList.quotationCode'),

View file

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

View file

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

View file

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

View file

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

View file

@ -343,9 +343,16 @@ watch(
{
label: $t('general.customer'),
value:
item.row.invoice.quotation.customerBranch
.registerName ||
`${item.row.invoice.quotation.customerBranch?.firstName || '-'} ${item.row.invoice.quotation.customerBranch?.lastName || ''}`,
item.row.invoice.quotation.customerBranch.customer
.customerType === 'CORP'
? $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'),

View file

@ -45,7 +45,7 @@ const fieldSelected = ref<('no' | 'name' | 'nameEN')[]>([
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
{
label: 'general.order',
value: 'orderNumber',
value: 'no',
},
{
@ -312,25 +312,6 @@ watch(
</q-input>
<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
v-if="!pageState.gridView"
id="select-field"
@ -595,10 +576,18 @@ watch(
></q-badge>
</q-avatar>
<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">
{{ props.row.nameEN }}
{{
$i18n.locale === 'tha'
? props.row.nameEN
: props.row.name
}}
</span>
</span>
<nav

View file

@ -447,6 +447,17 @@ const useBranchStore = defineStore('api-branch', () => {
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 {
data,
map,
@ -475,6 +486,8 @@ const useBranchStore = defineStore('api-branch', () => {
fetchByIdAttachment,
putAttachment,
deleteByIdAttachment,
fetchListBankByBranch,
};
});

View file

@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
import { api } from 'src/boot/axios';
import { Pagination } from 'stores/types';
import { getToken } from 'src/services/keycloak';
import {
ProductGroup,
@ -19,6 +20,7 @@ import {
import { ref } from 'vue';
const useProductServiceStore = defineStore('api-product-service', () => {
const baseUrl = import.meta.env.VITE_API_BASE_URL;
const splitPay = ref<number>(0);
const workNameItems = ref<
{
@ -528,6 +530,53 @@ const useProductServiceStore = defineStore('api-product-service', () => {
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 {
workNameItems,
splitPay,
@ -568,6 +617,8 @@ const useProductServiceStore = defineStore('api-product-service', () => {
deleteImageByName,
importProduct,
productExport,
};
});

View file

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