Compare commits

...

110 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
Methapon2001
35e23aa291 Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s
2025-09-18 10:29:45 +07:00
puriphatt
00f9b5f4c4 fix: customer filter and export
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-18 10:25:23 +07:00
Methapon2001
7846950802 Merge branch 'develop' 2025-09-18 10:09:37 +07:00
puriphatt
ef8e294ae4 Merge remote-tracking branch 'forgejo/refactor/customer' into develop
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-09-18 09:59:59 +07:00
net
016a54e45e fix: label 2025-09-18 09:59:59 +07:00
puriphatt
4e887fdff8 refactor: customer, employee upload profile after created 2025-09-18 09:59:59 +07:00
net
ded56d103b refactor: handle show discount 2025-09-18 09:59:59 +07:00
puriphatt
0d708405f6 refactor: drawer employee name 2025-09-18 09:59:59 +07:00
net
2099031fa8 refactor: add icon 2025-09-18 09:59:59 +07:00
puriphatt
fd12f32ab0 fix: customer & employee image list 2025-09-18 09:59:59 +07:00
net
f10103b5d0 fix: label 2025-09-18 09:59:59 +07:00
puriphatt
d6e366f788 feat: add customer export by status 2025-09-18 09:59:59 +07:00
net
52c384f0fb fix: create sub branch 2025-09-18 09:59:59 +07:00
puriphatt
fcafaeebc0 fix: change employee status with drawer alert 2025-09-18 09:59:59 +07:00
Methapon2001
0e57a3daf6 fix: .01 error 2025-09-18 09:59:59 +07:00
puriphatt
0ad017309f fix: drawer before close and employee edit state 2025-09-18 09:59:59 +07:00
net
b9cfb6274b fix: label 2025-09-18 09:59:59 +07:00
puriphatt
5becbae369 feat: filter customer by business type, address 2025-09-18 09:59:59 +07:00
net
7c3a9818c2 fix: calc price order 2025-09-18 09:59:59 +07:00
net
56f0a86845 fix: create sub branch 2025-09-18 09:59:59 +07:00
net
492f341e68 fix: current branch missing 2025-09-18 09:59:59 +07:00
puriphatt
49897ff007 fix: image selection 2025-09-18 09:59:59 +07:00
net
c430b6082e fix: calc price 2025-09-18 09:59:59 +07:00
puriphatt
e6f8870cdf refactor: drawer employee 2025-09-18 09:59:59 +07:00
Methapon2001
67cde37e34 fix: price calc on update installments 2025-09-18 09:59:59 +07:00
puriphatt
763ac07be7 refactor: customer & employee 2025-09-18 09:59:59 +07:00
Methapon2001
c07efa7318 fix: type error 2025-09-18 09:59:59 +07:00
puriphatt
507141dca5 fix: component 2025-09-18 09:59:59 +07:00
net
73b2d52fb0 fix: price calc 2025-09-18 09:59:59 +07:00
puriphatt
e6ecd39d24 feat: resize img 2025-09-18 09:59:59 +07:00
Methapon2001
c0a2d3769d fix: margin error .01 2025-09-18 09:59:59 +07:00
Methapon2001
05f7c886d6 refactor: use switch case instead 2025-09-18 09:59:59 +07:00
Methapon2001
93c54c0dd1 fix: remark 2025-09-18 09:59:59 +07:00
Methapon2001
ef4c84341c fix: price calc 2025-09-18 09:58:29 +07:00
net
09b51d601e refactor: can create new type 2025-09-18 09:58:29 +07:00
net
c29e1d4ec5 fix: select customer 2025-09-18 09:58:29 +07:00
puriphatt
d89925dee9 feat: quotation payment method 2025-09-18 09:58:29 +07:00
Thanaphon Frappet
3e24a46f66 feat: add customer export 2025-09-18 09:58:29 +07:00
Thanaphon Frappet
764d9bab3f fix: get contact number and name 2025-09-18 09:58:29 +07:00
Thanaphon Frappet
e5b2114984 fix: show remark
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-22 11:45:25 +07:00
Thanaphon Frappet
37c9f5fcd5 fix: edit label
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-22 11:25:51 +07:00
Thanaphon Frappet
67a69b85e0 refactor: add ( ) ,
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-21 15:54:30 +07:00
Methapon2001
eb88cc4269 fix: duplicate value 2025-08-21 15:54:30 +07:00
Methapon2001
ec780f2018 chore: clean 2025-08-21 15:54:30 +07:00
Methapon2001
31b4daf42b feat(biz-type): add create button on empty 2025-08-21 15:54:30 +07:00
Methapon2001
5fad663a6e feat(biz-type): add response type and response data 2025-08-21 15:54:30 +07:00
Methapon2001
d4a9be9236 feat: add create slot for business type select 2025-08-21 15:54:30 +07:00
puriphatt
e33191dcd4 fix: business type display 2025-08-21 15:54:30 +07:00
Thanaphon Frappet
fd28f36876 Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-21 10:07:56 +07:00
Thanaphon Frappet
a05c1e7004 fix: i18n
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-21 10:07:33 +07:00
Thanaphon Frappet
db2a094471 fix: name
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-21 10:04:23 +07:00
puriphatt
9aba48401a Merge remote-tracking branch 'origin/develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-20 17:52:48 +07:00
puriphatt
a3c51f5f52 fix: request list hide btn manage messenger condition
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-20 17:48:32 +07:00
Thanaphon Frappet
d44850a9ae Merge branch 'develop'
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-20 16:28:33 +07:00
Thanaphon Frappet
b86891c8c2 refactor: remove btn
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-20 15:43:55 +07:00
Methapon2001
473e272328 fix: error undefined
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
2025-08-19 11:45:24 +07:00
Thanaphon Frappet
02d02cf3a1 refactor: handle btn
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-18 09:24:49 +07:00
Thanaphon Frappet
044a530b8d refactor: edit i18n 2025-08-18 09:24:11 +07:00
Thanaphon Frappet
b21949712b fix: handle show group name
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
2025-08-14 11:07:25 +07:00
Thanaphon Frappet
42e545dd66 fix: value option no set 2025-08-14 11:07:01 +07:00
109 changed files with 8752 additions and 6978 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before After
Before After

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -20,8 +20,8 @@ const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry'); const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('issueDate'); const issueDate = defineModel<Date | null | string>('issueDate');
const type = defineModel<string>('type'); const type = defineModel<string>('type');
const expireDate = defineModel<Date>('expireDate'); const expireDate = defineModel<Date | string>('expireDate');
const birthDate = defineModel<Date>('birthDate'); const birthDate = defineModel<Date | string>('birthDate');
const workerStatus = defineModel<string>('workerStatus'); const workerStatus = defineModel<string>('workerStatus');
const nationality = defineModel<string>('nationality'); const nationality = defineModel<string>('nationality');
const gender = defineModel<string>('gender'); const gender = defineModel<string>('gender');

View file

@ -28,12 +28,12 @@ const arrivalAt = defineModel<string>('arrivalAt');
const arrivalTMNo = defineModel<string>('arrivalTmNo'); const arrivalTMNo = defineModel<string>('arrivalTmNo');
const arrivalTM = defineModel<string>('arrivalTm'); const arrivalTM = defineModel<string>('arrivalTm');
const mrz = defineModel<string>('mrz'); const mrz = defineModel<string>('mrz');
const entryCount = defineModel<number>('entryCount'); const entryCount = defineModel<number | string>('entryCount');
const issuePlace = defineModel<string>('issuePlace'); const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry'); const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('visaIssueDate'); const issueDate = defineModel<Date | null | string>('visaIssueDate');
const type = defineModel<string>('type'); const type = defineModel<string>('type');
const expireDate = defineModel<Date>('expireDate'); const expireDate = defineModel<Date | string>('expireDate');
const remark = defineModel<string>('remark'); const remark = defineModel<string>('remark');
const workerType = defineModel<string>('workerType'); const workerType = defineModel<string>('workerType');
const number = defineModel<string>('number'); const number = defineModel<string>('number');
@ -157,7 +157,7 @@ watch(
name="mdi-passport" name="mdi-passport"
style="background-color: var(--surface-3)" style="background-color: var(--surface-3)"
/> />
{{ title }} {{ $t(title) }}
</div> </div>
<div <div

View file

@ -141,8 +141,9 @@ defineEmits<{
<q-avatar size="md"> <q-avatar size="md">
<q-img <q-img
:src=" :src="
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` || props.row.selectedImage
`/images/employee-avatar-${props.row.gender}.png` ? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
: `/images/employee-avatar-${props.row.gender}.png`
" "
class="text-center" class="text-center"
:ratio="1" :ratio="1"
@ -295,9 +296,9 @@ defineEmits<{
$i18n.locale === 'eng' $i18n.locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim() ? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
: `${props.row.firstName} ${props.row.lastName} `.trim(), : `${props.row.firstName} ${props.row.lastName} `.trim(),
img: img: props.row.selectedImage
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` || ? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
`/images/employee-avatar-${props.row.gender}.png`, : `/images/employee-avatar-${props.row.gender}.png`,
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`, fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
male: props.row.gender === 'male', male: props.row.gender === 'male',
female: props.row.gender === 'female', female: props.row.gender === 'female',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { getRole } from 'src/services/keycloak';
import { createSelect, SelectProps } from './select'; import { createSelect, SelectProps } from './select';
import SelectInput from '../SelectInput.vue'; import SelectInput from '../SelectInput.vue';
@ -95,6 +94,103 @@ function setDefaultValue() {
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }} {{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
</template> </template>
<template #no-option v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('businessType.title') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #before-options v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
for="select-biz-type-add-new"
id="select-biz-type-add-new"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('businessType.title') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #before-options v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
for="select-business-type-add-new"
id="select-business-type-add-new"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('menu.manage.businessType') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #option="{ opt, scope }"> <template #option="{ opt, scope }">
<q-item v-bind="scope.itemProps"> <q-item v-bind="scope.itemProps">
<span class="row items-center"> <span class="row items-center">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -45,9 +45,9 @@ const props = withDefaults(
readonly?: boolean; readonly?: boolean;
showTitle?: boolean; showTitle?: boolean;
ocr?: ( ocr?: (
group: any, group: string,
file: File, file: File,
) => void | Promise<{ ) => Promise<{
status: boolean; status: boolean;
group: string; group: string;
meta: { name: string; value: string }[]; meta: { name: string; value: string }[];
@ -168,8 +168,8 @@ async function change(e: Event) {
type: map['doc_type'], type: map['doc_type'],
number: map['doc_number'], number: map['doc_number'],
gender: map['sex'], gender: map['sex'],
firstName: map['first_name'], firstName: map['last_name'],
lastName: map['last_name'], lastName: map['first_name'],
issueDate: map['issue_date'], issueDate: map['issue_date'],
expireDate: map['expire_date'], expireDate: map['expire_date'],
issuePlace: map['nationality'], issuePlace: map['nationality'],

View file

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

View file

@ -161,6 +161,7 @@ export default {
documentStatus: 'Document Status', documentStatus: 'Document Status',
advanceSearch: 'Advance Search', advanceSearch: 'Advance Search',
totalPeople: '{meg} people', totalPeople: '{meg} people',
price: 'Price {price} Baht',
}, },
menu: { menu: {
@ -340,7 +341,7 @@ export default {
requireLength: 'Please enter {msg} character', requireLength: 'Please enter {msg} character',
branchNameField: "Only letters, numbers, or the characters . , - ' &.", branchNameField: "Only letters, numbers, or the characters . , - ' &.",
branchNameENField: branchNameENField:
"Only English letters, numbers, or the characters . , - ' &.", "Only English letters, numbers, or the characters . , - ' &. ( )",
passportFormat: 'Please enter the passport number in the correct format.', passportFormat: 'Please enter the passport number in the correct format.',
}, },
warning: { warning: {
@ -507,6 +508,7 @@ export default {
miss: 'MISS.', miss: 'MISS.',
}, },
taxpayyerNo: 'Taxpayer Identification Number',
citizenId: 'Citizen ID', citizenId: 'Citizen ID',
religion: 'Religion', religion: 'Religion',
issueDate: 'Issue Date', issueDate: 'Issue Date',
@ -777,6 +779,8 @@ export default {
seller: 'Seller', seller: 'Seller',
paymentChannels: 'Payment Channels', paymentChannels: 'Payment Channels',
channelsThat: 'Channels That', channelsThat: 'Channels That',
refNo: 'Reference Number',
bankAccount: 'Bank Account',
bankAccountNumber: 'Bank Account Number', bankAccountNumber: 'Bank Account Number',
bankAccountName: 'Bank Account Name', bankAccountName: 'Bank Account Name',
inTheNameOf: 'In The Name Of', inTheNameOf: 'In The Name Of',
@ -1230,6 +1234,9 @@ export default {
taskListNotPending: 'One or more task is not pending.', taskListNotPending: 'One or more task is not pending.',
reqNotMet: 'Not Match', reqNotMet: 'Not Match',
systemError: 'A system error occurred.', systemError: 'A system error occurred.',
taskOrderInvalid: 'Please select the product and the organization.',
flowAccountProductIdNotFound: 'Product not found in flow account',
}, },
}, },

View file

@ -161,6 +161,7 @@ export default {
documentStatus: 'สถานะเอกสาร', documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง', advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน', totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
}, },
menu: { menu: {
@ -337,7 +338,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น', letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น', numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก', requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น", branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
branchNameENField: branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น", "โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ', passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -508,6 +509,7 @@ export default {
religion: 'ศาสนา', religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ', issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง', passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง', ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ', firstName: 'ชื่อ ',
@ -775,6 +777,8 @@ export default {
seller: 'ผู้ขาย', seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน', paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่', channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร', bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี', bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม', inTheNameOf: 'ในนาม',
@ -799,7 +803,7 @@ export default {
branch: 'สาขาที่ออกใบเสนอราคา', branch: 'สาขาที่ออกใบเสนอราคา',
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา', branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
customer: 'ลูกค้า', customer: 'ลูกค้า',
newCustomer: 'ลูกค้าใหม่', newCustomer: 'แรงงานใหม่',
employeeList: 'รายชื่อแรงงาน', employeeList: 'รายชื่อแรงงาน',
employee: 'แรงงาน', employee: 'แรงงาน',
employeeName: 'ชื่อ-นามสกุล แรงงาน', employeeName: 'ชื่อ-นามสกุล แรงงาน',
@ -1216,6 +1220,8 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ', 'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน', reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด', systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
}, },
}, },

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -140,7 +140,7 @@ watch(
:rules="[ :rules="[
(val) => !!val || $t('form.error.required'), (val) => !!val || $t('form.error.required'),
(val) => (val) =>
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) || /^[A-Za-z0-9ก-๙\s&.,'()-]+$/.test(val) ||
$t('form.error.branchNameField'), $t('form.error.branchNameField'),
]" ]"
/> />
@ -336,6 +336,7 @@ watch(
(v) => (typeof v === 'string' ? (prefixName = v) : '') (v) => (typeof v === 'string' ? (prefixName = v) : '')
" "
@clear="prefixName = ''" @clear="prefixName = ''"
:rules="[(val: string) => !!val || $t('form.error.required')]"
> >
<template v-slot:no-option> <template v-slot:no-option>
<q-item> <q-item>

View file

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

View file

@ -7,6 +7,8 @@ import { CustomerCreate } from 'stores/customer/types';
import EmployerFormAbout from './EmployerFormAbout.vue'; import EmployerFormAbout from './EmployerFormAbout.vue';
import EmployerFormAuthorized from './EmployerFormAuthorized.vue'; import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
import { waitAll } from 'src/stores/utils'; import { waitAll } from 'src/stores/utils';
import FormEmployeePassport from 'src/components/03_customer-management/FormEmployeePassport.vue';
import FormEmployeeVisa from 'src/components/03_customer-management/FormEmployeeVisa.vue';
import { import {
FormCitizen, FormCitizen,
CorpFormBusinessRegistration, CorpFormBusinessRegistration,

View file

@ -10,10 +10,25 @@ import { useI18n } from 'vue-i18n';
import ThaiBahtText from 'thai-baht-text'; import ThaiBahtText from 'thai-baht-text';
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue'; import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
import BusinessTypeDialog from 'src/pages/16_business-type-management/BusinessTypeDialog.vue';
import useBusinessTypeStore from 'src/stores/business-type';
const { locale } = useI18n({ useScope: 'global' }); const { locale } = useI18n({ useScope: 'global' });
const rawOption = ref(); const rawOption = ref();
const businessTypeDialog = ref<boolean>(false);
const formDataBusinessType = ref<{
name: string;
nameEN: string;
}>({
name: '',
nameEN: '',
});
const useBusinessType = useBusinessTypeStore();
const businessTypeId = defineModel<string>('businessTypeId'); const businessTypeId = defineModel<string>('businessTypeId');
const jobPosition = defineModel<string>('jobPosition'); const jobPosition = defineModel<string>('jobPosition');
const jobDescription = defineModel<string>('jobDescription'); const jobDescription = defineModel<string>('jobDescription');
@ -29,6 +44,8 @@ const typeBusinessENOption = ref([]);
const jobPositionOption = ref([]); const jobPositionOption = ref([]);
const jobPositionENOption = ref([]); const jobPositionENOption = ref([]);
const keySelect = ref<number>(0);
defineProps<{ defineProps<{
title?: string; title?: string;
dense?: boolean; dense?: boolean;
@ -38,6 +55,20 @@ defineProps<{
showTitle?: boolean; showTitle?: boolean;
}>(); }>();
function resetFormBusinessType() {
businessTypeDialog.value = false;
formDataBusinessType.value = { name: '', nameEN: '' };
}
async function submitBusinessType() {
const res = await useBusinessType.create(formDataBusinessType.value);
if (res) {
businessTypeId.value = res.id;
resetFormBusinessType();
keySelect.value++;
}
}
onMounted(async () => { onMounted(async () => {
const resultOption = await fetch('/option/option.json'); const resultOption = await fetch('/option/option.json');
rawOption.value = await resultOption.json(); rawOption.value = await resultOption.json();
@ -121,16 +152,24 @@ let jobPositionENFilter = selectFilterOptionRefMod(
</div> </div>
<SelectBusinessType <SelectBusinessType
:key="keySelect"
class="col-md-6 col-12" class="col-md-6 col-12"
v-model:value="businessTypeId" v-model:value="businessTypeId"
:readonly :readonly
creatable
lang="tha" lang="tha"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@create="() => (businessTypeDialog = true)"
/> />
<SelectBusinessType <SelectBusinessType
:key="keySelect"
class="col-md-6 col-12" class="col-md-6 col-12"
v-model:value="businessTypeId" v-model:value="businessTypeId"
lang="eng"
:readonly :readonly
creatable
lang="eng"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@create="() => (businessTypeDialog = true)"
/> />
<q-select <q-select
@ -282,4 +321,12 @@ let jobPositionENFilter = selectFilterOptionRefMod(
" "
/> />
</div> </div>
<BusinessTypeDialog
ref="refBusinessTypeDialog"
@close="resetFormBusinessType()"
@submit="submitBusinessType()"
v-model="businessTypeDialog"
v-model:data="formDataBusinessType"
/>
</template> </template>

View file

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

View file

@ -22,6 +22,10 @@ import { useRoute } from 'vue-router';
export const useCustomerForm = defineStore('form-customer', () => { export const useCustomerForm = defineStore('form-customer', () => {
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
const onCreateImageList = ref<{
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}>({ selectedImage: '', list: [] });
const { t } = useI18n(); const { t } = useI18n();
const flowStore = useFlowStore(); const flowStore = useFlowStore();
@ -30,6 +34,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
const registerAbleBranchOption = ref<{ id: string; name: string }[]>(); const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
const currentBranchRootId = ref<string>('');
const tabFieldRequired = ref<{ const tabFieldRequired = ref<{
[key: string]: (keyof CustomerBranchCreate)[]; [key: string]: (keyof CustomerBranchCreate)[];
}>({ }>({
@ -82,6 +88,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
formDataOcr: Record<string, any>; formDataOcr: Record<string, any>;
isImageEdit: boolean; isImageEdit: boolean;
currentCustomerId?: string; currentCustomerId?: string;
imageList: { list: string[]; selectedImage: string };
}>({ }>({
dialogType: 'info', dialogType: 'info',
dialogOpen: false, dialogOpen: false,
@ -98,6 +105,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
treeFile: [], treeFile: [],
formDataOcr: {}, formDataOcr: {},
isImageEdit: false, isImageEdit: false,
imageList: { list: [], selectedImage: '' },
}); });
watch( watch(
@ -160,6 +168,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
state.value.editCustomerCode = data.code; state.value.editCustomerCode = data.code;
state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`; state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
state.value.defaultCustomerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`; state.value.defaultCustomerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
currentBranchRootId.value = data.branch[0].id || '';
resetFormData.registeredBranchId = data.registeredBranchId; resetFormData.registeredBranchId = data.registeredBranchId;
resetFormData.status = data.status; resetFormData.status = data.status;
@ -255,7 +264,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
async function addCurrentCustomerBranch() { async function addCurrentCustomerBranch() {
if (currentFormData.value.customerBranch?.some((v) => !v.id)) return; if (currentFormData.value.customerBranch?.some((v) => !v.id)) return;
currentFormData.value.customerBranch?.push({ currentFormData.value.customerBranch?.push({
id: '',
customerId: '', customerId: '',
branchCode: branchCode:
currentFormData.value.customerBranch.length !== 0 currentFormData.value.customerBranch.length !== 0
@ -495,11 +503,14 @@ export const useCustomerForm = defineStore('form-customer', () => {
} }
return { return {
onCreateImageList,
tabFieldRequired, tabFieldRequired,
registerAbleBranchOption, registerAbleBranchOption,
state, state,
resetFormData, resetFormData,
currentFormData, currentFormData,
currentBranchRootId,
isFormDataDifferent, isFormDataDifferent,
resetForm, resetForm,
assignFormData, assignFormData,
@ -779,6 +790,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
} }
| undefined; | undefined;
ocr: boolean; ocr: boolean;
imageList: { list: string[]; selectedImage: string };
}>({ }>({
currentBranchId: '', currentBranchId: '',
isImageEdit: false, isImageEdit: false,
@ -803,6 +815,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
infoEmployeePersonCard: [], infoEmployeePersonCard: [],
formDataEmployeeOwner: undefined, formDataEmployeeOwner: undefined,
ocr: false, ocr: false,
imageList: { list: [], selectedImage: '' },
}); });
const defaultFormData: EmployeeCreate & { image?: File } = { const defaultFormData: EmployeeCreate & { image?: File } = {
@ -955,6 +968,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexVisa = -1; state.value.currentIndexVisa = -1;
state.value.currentIndexCheckup = -1; state.value.currentIndexCheckup = -1;
state.value.currentIndexWorkHistory = -1; state.value.currentIndexWorkHistory = -1;
state.value.imageList = { list: [], selectedImage: '' };
// state.value.currentTab = 'personalInfo'; // state.value.currentTab = 'personalInfo';
if (clean) { if (clean) {
state.value.formDataEmployeeOwner = undefined; state.value.formDataEmployeeOwner = undefined;
@ -1384,12 +1398,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
statusSave: true, statusSave: true,
})), })),
), ),
employeeOtherInfo: structuredClone( employeeOtherInfo: structuredClone({
{ ...(payload.employeeOtherInfo ?? {}),
...payload.employeeOtherInfo, statusSave: true,
statusSave: !!payload.employeeOtherInfo?.id ? true : false, }),
} || {},
),
employeeWork: structuredClone( employeeWork: structuredClone(
payload.employeeWork?.length === 0 payload.employeeWork?.length === 0
? state.value.dialogModal ? state.value.dialogModal
@ -1663,6 +1675,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state, state,
currentFromDataEmployee, currentFromDataEmployee,
resetEmployeeData, resetEmployeeData,
addPassport, addPassport,
addVisa, addVisa,
addCheckup, addCheckup,

View file

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

View file

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, reactive, ref, watch, computed } from 'vue'; import { onMounted, onUnmounted, reactive, ref, watch, computed } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -60,7 +60,6 @@ const flowStore = useFlowStore();
const userBranch = useMyBranch(); const userBranch = useMyBranch();
const navigatorStore = useNavigator(); const navigatorStore = useNavigator();
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
const { const {
fetchListOfOptionBranch, fetchListOfOptionBranch,
customerFormUndo, customerFormUndo,
@ -76,6 +75,7 @@ const {
const { const {
state: customerFormState, state: customerFormState,
currentFormData: customerFormData, currentFormData: customerFormData,
currentBranchRootId,
registerAbleBranchOption, registerAbleBranchOption,
tabFieldRequired, tabFieldRequired,
} = storeToRefs(customerFormStore); } = storeToRefs(customerFormStore);
@ -89,6 +89,8 @@ const fieldSelectedOption = computed(() => {
value: v.name, value: v.name,
})); }));
}); });
const keyAddDialog = ref<number>(0);
const special = ref(false); const special = ref(false);
const branchId = ref(''); const branchId = ref('');
const agentPrice = ref<boolean>(false); const agentPrice = ref<boolean>(false);
@ -273,6 +275,10 @@ const customerNameInfo = computed(() => {
return name || '-'; return name || '-';
}); });
function handleWindowFocus() {
fetchQuotationList();
}
onMounted(async () => { onMounted(async () => {
pageState.gridView = $q.screen.lt.md ? true : false; pageState.gridView = $q.screen.lt.md ? true : false;
navigatorStore.current.title = 'quotation.title'; navigatorStore.current.title = 'quotation.title';
@ -310,6 +316,12 @@ onMounted(async () => {
} }
flowStore.rotate(); flowStore.rotate();
window.addEventListener('focus', handleWindowFocus);
});
onUnmounted(() => {
window.removeEventListener('focus', handleWindowFocus);
}); });
async function fetchQuotationList(mobileFetch?: boolean) { async function fetchQuotationList(mobileFetch?: boolean) {
@ -765,8 +777,13 @@ async function filterBySellerId() {
:worker-count="item.row._count.worker" :worker-count="item.row._count.worker"
:worker-max="item.row.workerMax || item.row._count.worker" :worker-max="item.row.workerMax || item.row._count.worker"
:customer-name=" :customer-name="
item.row.customerBranch.registerName || item.row.customerBranch.customer.customerType === 'CORP'
`${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}` ? $i18n.locale === 'tha'
? item.row.customerBranch.registerName
: item.row.customerBranch.registerNameEN
: $i18n.locale === 'tha'
? `${item.row.customerBranch.firstName || '-'} ${item.row.customerBranch.lastName || ''}`
: `${item.row.customerBranch.firstNameEN || '-'} ${item.row.customerBranch.lastNameEN || ''}`
" "
:reporter=" :reporter="
$i18n.locale === 'eng' $i18n.locale === 'eng'
@ -865,6 +882,7 @@ async function filterBySellerId() {
<!-- NOTE: START - Quotation Form, Add Quotation --> <!-- NOTE: START - Quotation Form, Add Quotation -->
<DialogForm <DialogForm
:key="keyAddDialog"
:title="$t('general.add', { text: $t('quotation.title') })" :title="$t('general.add', { text: $t('quotation.title') })"
v-model:modal="pageState.addModal" v-model:modal="pageState.addModal"
:submit-label="$t('general.add', { text: $t('quotation.title') })" :submit-label="$t('general.add', { text: $t('quotation.title') })"
@ -874,13 +892,14 @@ async function filterBySellerId() {
:submit=" :submit="
() => { () => {
quotationFormState.mode = 'create'; quotationFormState.mode = 'create';
quotationFormData.customerBranchId = currentBranchRootId;
triggerQuotationDialog({ statusDialog: 'create' }); triggerQuotationDialog({ statusDialog: 'create' });
} }
" "
:close=" :close="
() => { () => {
branchId = ''; branchId = '';
quotationFormData.customerBranchId = ''; currentBranchRootId = '';
} }
" "
:beforeClose=" :beforeClose="
@ -930,7 +949,7 @@ async function filterBySellerId() {
v-model:agent-price="agentPrice" v-model:agent-price="agentPrice"
v-model:branch-id="branchId" v-model:branch-id="branchId"
v-model:special="special" v-model:special="special"
v-model:customer-branch-id="quotationFormData.customerBranchId" v-model:customer-branch-id="currentBranchRootId"
@add-customer="triggerSelectTypeCustomerd()" @add-customer="triggerSelectTypeCustomerd()"
/> />
</div> </div>
@ -999,6 +1018,7 @@ async function filterBySellerId() {
() => { () => {
customerFormState.dialogModal = false; customerFormState.dialogModal = false;
onCreateImageList = { selectedImage: '', list: [] }; onCreateImageList = { selectedImage: '', list: [] };
keyAddDialog++;
} }
" "
> >
@ -1190,7 +1210,7 @@ async function filterBySellerId() {
customerFormData.customerBranch[0].legalPersonNo customerFormData.customerBranch[0].legalPersonNo
" "
v-model:business-type=" v-model:business-type="
customerFormData.customerBranch[0].businessType customerFormData.customerBranch[0].businessTypeId
" "
v-model:job-position=" v-model:job-position="
customerFormData.customerBranch[0].jobPosition customerFormData.customerBranch[0].jobPosition
@ -1271,7 +1291,6 @@ async function filterBySellerId() {
res = await customerStore.createBranch({ res = await customerStore.createBranch({
...customerFormData.customerBranch[idx], ...customerFormData.customerBranch[idx],
customerId: customerFormState.editCustomerId || '', customerId: customerFormState.editCustomerId || '',
id: undefined,
}); });
} }
if (res) { if (res) {

File diff suppressed because it is too large Load diff

View file

@ -80,6 +80,8 @@ import { RouterLink, useRoute } from 'vue-router';
import { initLang, initTheme, Lang } from 'src/utils/ui'; import { initLang, initTheme, Lang } from 'src/utils/ui';
import { convertTemplate } from 'src/utils/string-template'; import { convertTemplate } from 'src/utils/string-template';
import { CustomerBranch } from 'src/stores/customer';
type Node = { type Node = {
[key: string]: any; [key: string]: any;
opened?: boolean; opened?: boolean;
@ -94,6 +96,8 @@ type ProductGroupId = string;
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const customerBranchOption = ref<CustomerBranch>();
const employeeStore = useEmployeeStore(); const employeeStore = useEmployeeStore();
const route = useRoute(); const route = useRoute();
const useReceiptStore = useReceipt(); const useReceiptStore = useReceipt();
@ -157,49 +161,7 @@ const selectedWorker = ref<
}[]; }[];
})[] })[]
>([]); >([]);
const selectedWorkerItem = computed(() => { const selectedWorkerItem = ref([]);
return [
...selectedWorker.value.map((e) => ({
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: e.firstName
? `${e.firstName} ${e.lastName}`
: `${e.firstNameEN} ${e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
status: e.status,
})),
...newWorkerList.value.map((v: any) => ({
foreignRefNo: v.passportNo,
employeeName:
locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName} ${v.lastName}`,
birthDate: dateFormatJS({ date: v.dateOfBirth }),
gender: v.gender,
age: calculateAge(v.dateOfBirth),
nationality: optionStore.mapOption(v.nationality),
documentExpireDate: '-',
imgUrl: '',
status: 'CREATED',
})),
];
});
const firstCodePayment = ref(''); const firstCodePayment = ref('');
const selectedProductGroup = ref(''); const selectedProductGroup = ref('');
const selectedInstallmentNo = ref<number[]>([]); const selectedInstallmentNo = ref<number[]>([]);
@ -234,7 +196,7 @@ function getPrice(
) { ) {
if (filterHook) list = list.filter(filterHook); if (filterHook) list = list.filter(filterHook);
return list.reduce( const value = list.reduce(
(a, c) => { (a, c) => {
if ( if (
selectedInstallmentNo.value.length > 0 && selectedInstallmentNo.value.length > 0 &&
@ -244,32 +206,25 @@ function getPrice(
return a; return a;
} }
const originalPrice = c.pricePerUnit;
const finalPriceWithVat = precisionRound(
originalPrice * (1 + (config.value?.vat || 0.07)),
);
const finalPriceNoVat =
finalPriceWithVat / (1 + (config.value?.vat || 0.07));
const price = finalPriceNoVat * c.amount;
const vat =
(finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07);
const calcVat = const calcVat =
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat']; c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
a.totalPrice = precisionRound(a.totalPrice + price); const pricePerUnit =
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount)); precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
a.vat = calcVat ? precisionRound(a.vat + vat) : a.vat;
const price =
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
(1 + vatFactor);
const vat = price * vatFactor;
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
a.vat = precisionRound(a.vat + vat);
a.vatExcluded = calcVat a.vatExcluded = calcVat
? a.vatExcluded ? a.vatExcluded
: precisionRound(a.vatExcluded + price); : precisionRound(a.vatExcluded + price);
a.finalPrice = precisionRound( a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
a.totalPrice -
a.totalDiscount +
a.vat -
Number(quotationFormData.value.discount || 0),
);
return a; return a;
}, },
@ -281,6 +236,8 @@ function getPrice(
finalPrice: 0, finalPrice: 0,
}, },
); );
return value;
} }
const summaryPrice = computed(() => getPrice(productServiceList.value)); const summaryPrice = computed(() => getPrice(productServiceList.value));
@ -559,7 +516,7 @@ async function convertDataToFormSubmit() {
), ),
); );
selectedWorker.value.forEach((v, i) => { selectedWorkerItem.value.forEach((v, i) => {
if (v.attachment !== undefined) { if (v.attachment !== undefined) {
v.attachment.forEach((value) => { v.attachment.forEach((value) => {
fileItemNewWorker.value.push({ fileItemNewWorker.value.push({
@ -576,7 +533,7 @@ async function convertDataToFormSubmit() {
quotationFormData.value.worker = JSON.parse( quotationFormData.value.worker = JSON.parse(
JSON.stringify([ JSON.stringify([
...selectedWorker.value.map((v) => { ...selectedWorkerItem.value.map((v) => {
{ {
return v.id; return v.id;
} }
@ -719,19 +676,13 @@ function handleUpdateProductTable(
// handleChangePayType(quotationFormData.value.payCondition); // handleChangePayType(quotationFormData.value.payCondition);
// calc price // calc price
const calc = (c: QuotationPayload['productServiceList'][number]) => { const calc = (c: QuotationPayload['productServiceList'][number]) => {
const originalPrice = c.pricePerUnit || 0; const calcVat =
const finalPriceWithVat = precisionRound( c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
originalPrice * (1 + (config.value?.vat || 0.07)), const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
); const pricePerUnit =
const finalPriceNoVat = precisionRound(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor);
finalPriceWithVat / (1 + (config.value?.vat || 0.07)); const price = pricePerUnit * c.amount * (1 + vatFactor) - c.discount;
return precisionRound(price);
const price = finalPriceNoVat * c.amount;
const vat = c.product.calcVat
? (finalPriceNoVat * c.amount - (c.discount || 0)) *
(config.value?.vat || 0.07)
: 0;
return precisionRound(price + vat);
}; };
// installment // installment
@ -784,21 +735,16 @@ function toggleDeleteProduct(index: number) {
// cal curr amount // cal curr amount
if (currPaySplit && currTempPaySplit) { if (currPaySplit && currTempPaySplit) {
const price = agentPrice.value const calcVat =
? currProduct.product.agentPrice currProduct.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
: currProduct.product.price; const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
const pricePerUnit = currProduct.product.vatIncluded
? price / (1 + (config.value?.vat || 0.07))
: price;
const vat =
(pricePerUnit * currProduct.amount - currProduct.discount) *
(config.value?.vat || 0.07);
const finalPrice =
pricePerUnit * currProduct.amount +
vat -
Number(currProduct.discount || 0);
currTempPaySplit.amount = currPaySplit.amount - finalPrice; const price = precisionRound(
currProduct.pricePerUnit * currProduct.amount * (1 + vatFactor) -
currProduct.discount,
);
currTempPaySplit.amount = currPaySplit.amount - price;
currPaySplit.amount = currTempPaySplit.amount; currPaySplit.amount = currTempPaySplit.amount;
} }
@ -820,7 +766,40 @@ function toggleDeleteProduct(index: number) {
} }
async function assignWorkerToSelectedWorker() { async function assignWorkerToSelectedWorker() {
selectedWorker.value = quotationFormData.value.worker; selectedWorkerItem.value = quotationFormData.value.worker.map((e) => {
return {
id: e.id,
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
employeePassport: e.employeePassport,
status: e.status,
workerNew: false,
lastNameEN: e.lastNameEN,
lastName: e.lastName,
middleNameEN: e.middleNameEN,
middleName: e.middleName,
firstNameEN: e.firstNameEN,
firstName: e.firstName,
namePrefix: e.namePrefix,
};
});
} }
function convertToTable(nodes: Node[]) { function convertToTable(nodes: Node[]) {
@ -853,8 +832,7 @@ function convertToTable(nodes: Node[]) {
tempTableProduct.value = JSON.parse(JSON.stringify(list)); tempTableProduct.value = JSON.parse(JSON.stringify(list));
if (nodes.length > 0) { if (nodes.length > 0) {
quotationFormData.value.paySplit = Array.apply( quotationFormData.value.paySplit = Array.from(
null,
new Array(quotationFormData.value.paySplitCount), new Array(quotationFormData.value.paySplitCount),
).map((_, i) => ({ ).map((_, i) => ({
no: i + 1, no: i + 1,
@ -880,21 +858,21 @@ function convertToTable(nodes: Node[]) {
function convertEmployeeToTable(selected: Employee[]) { function convertEmployeeToTable(selected: Employee[]) {
productServiceList.value.forEach((v) => { productServiceList.value.forEach((v) => {
if (selectedWorker.value.length === 0 && v.amount === 1) v.amount -= 1; if (selectedWorkerItem.value.length === 0 && v.amount === 1) v.amount -= 1;
v.amount = Math.max( v.amount = Math.max(
v.amount + selected.length - selectedWorker.value.length, v.amount + selected.length - selectedWorkerItem.value.length,
1, 1,
); );
const oldWorkerId: string[] = []; const oldWorkerId: string[] = [];
const newWorkerIndex: number[] = []; const newWorkerIndex: number[] = [];
selectedWorker.value.forEach((item, i) => { selectedWorkerItem.value.forEach((item, i) => {
if (v.workerIndex.includes(i)) oldWorkerId.push(item.id); if (v.workerIndex.includes(i)) oldWorkerId.push(item.id);
}); });
selected.forEach((item, i) => { selected.forEach((item, i) => {
if (selectedWorker.value.find((n) => item.id === n.id)) return; if (selectedWorkerItem.value.find((n) => item.id === n.id)) return;
newWorkerIndex.push(i); newWorkerIndex.push(i);
}); });
@ -907,7 +885,7 @@ function convertEmployeeToTable(selected: Employee[]) {
pageState.employeeModal = false; pageState.employeeModal = false;
quotationFormData.value.workerMax = Math.max( quotationFormData.value.workerMax = Math.max(
quotationFormData.value.workerMax || 1, quotationFormData.value.workerMax || 1,
selectedWorker.value.length, selectedWorkerItem.value.length,
); );
} }
@ -980,6 +958,71 @@ function viewProductFile(data: ProductRelation) {
pageState.imageDialogUrl = base64 ? base64[1] : ''; pageState.imageDialogUrl = base64 ? base64[1] : '';
} }
function combineWorker(newWorker: any, oldWorker: any) {
selectedWorkerItem.value = [
...oldWorker.map((e) => ({
id: e.id,
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
employeePassport: e.employeePassport,
status: e.status,
workerNew: false,
lastNameEN: e.lastNameEN,
lastName: e.lastName,
middleNameEN: e.middleNameEN,
middleName: e.middleName,
firstNameEN: e.firstNameEN,
firstName: e.firstName,
namePrefix: e.namePrefix,
})),
...newWorker.map((v: any) => ({
id: v.id,
foreignRefNo: v.passportNo || '-',
employeeName:
locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
birthDate: dateFormatJS({ date: v.dateOfBirth }),
gender: v.gender,
age: calculateAge(v.dateOfBirth),
nationality: optionStore.mapOption(v.nationality),
documentExpireDate: '-',
imgUrl: '',
status: 'CREATED',
lastNameEN: v.lastNameEN,
lastName: v.lastName,
middleNameEN: v.middleNameEN,
middleName: v.middleName,
firstNameEN: v.firstNameEN,
firstName: v.firstName,
namePrefix: v.namePrefix,
dateOfBirth: v.dateOfBirth,
workerNew: true,
})),
];
}
const sessionData = ref<Record<string, any>>(); const sessionData = ref<Record<string, any>>();
onMounted(async () => { onMounted(async () => {
@ -1051,7 +1094,7 @@ watch(
() => quotationFormData.value.customerBranchId, () => quotationFormData.value.customerBranchId,
async (v) => { async (v) => {
if (!v) return; if (!v) return;
selectedWorker.value = []; selectedWorkerItem.value = [];
}, },
); );
@ -1067,6 +1110,15 @@ watch(
const productServiceNodes = ref<ProductTree>([]); const productServiceNodes = ref<ProductTree>([]);
watch(customerBranchOption, () => {
if (!customerBranchOption.value) return;
quotationFormData.value.contactName =
customerBranchOption.value.contactName || '';
quotationFormData.value.contactTel =
customerBranchOption.value.contactTel || '';
});
watch( watch(
() => productServiceList.value, () => productServiceList.value,
() => { () => {
@ -1074,6 +1126,13 @@ watch(
}, },
); );
watch(customerBranchOption, () => {
if (!customerBranchOption.value) return;
quotationFormData.value.contactName = customerBranchOption.value.contactName;
quotationFormData.value.contactTel = customerBranchOption.value.contactTel;
});
// async function searchEmployee(text: string) { // async function searchEmployee(text: string) {
// let query: string | undefined = text; // let query: string | undefined = text;
// let pageSize = 50; // let pageSize = 50;
@ -1095,7 +1154,19 @@ watch(
// } // }
function storeDataLocal() { function storeDataLocal() {
quotationFormData.value.productServiceList = productService.value; const tempProductService = productService.value.map((v) => {
return {
...v,
vat: v.product[agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? precisionRound(
((v.pricePerUnit * (1 + (config?.value.vat || 0.07)) * v.amount -
v.discount) /
(1 + (config?.value.vat || 0.07))) *
0.07,
)
: 0,
};
});
localStorage.setItem( localStorage.setItem(
'quotation-preview', 'quotation-preview',
@ -1104,7 +1175,7 @@ function storeDataLocal() {
codeInvoice: code.value, codeInvoice: code.value,
codePayment: firstCodePayment.value, codePayment: firstCodePayment.value,
...quotationFormData.value, ...quotationFormData.value,
productServiceList: productService.value, productServiceList: tempProductService,
}, },
meta: { meta: {
source: { source: {
@ -1124,7 +1195,7 @@ function storeDataLocal() {
workName: quotationFormData.value.workName, workName: quotationFormData.value.workName,
dueDate: quotationFormData.value.dueDate, dueDate: quotationFormData.value.dueDate,
}, },
selectedWorker: selectedWorker.value, selectedWorker: selectedWorkerItem.value,
createdBy: quotationFormState.value.createdBy('tha'), createdBy: quotationFormState.value.createdBy('tha'),
agentPrice: agentPrice.value, agentPrice: agentPrice.value,
}, },
@ -1209,10 +1280,10 @@ async function getWorkerFromCriteria(
if (!ret) return false; // error, do not close dialog if (!ret) return false; // error, do not close dialog
const deduplicate = ret.result.filter( const deduplicate = ret.result.filter(
(a) => !selectedWorker.value.find((b) => a.id === b.id), (a) => !selectedWorkerItem.value.find((b) => a.id === b.id),
); );
convertEmployeeToTable([...deduplicate, ...selectedWorker.value]); convertEmployeeToTable([...deduplicate, ...selectedWorkerItem.value]);
return true; return true;
} }
@ -1519,6 +1590,7 @@ function covertToNode() {
v-model:customer-branch-id=" v-model:customer-branch-id="
quotationFormData.customerBranchId quotationFormData.customerBranchId
" "
v-model:customer-branch-option="customerBranchOption"
:readonly="readonly" :readonly="readonly"
/> />
</template> </template>
@ -1601,15 +1673,15 @@ function covertToNode() {
(v) => (v) =>
(quotationFormData.workerMax = Math.max( (quotationFormData.workerMax = Math.max(
v, v,
selectedWorker.length, selectedWorkerItem.length,
)) ))
" "
:employee-amount=" :employee-amount="
quotationFormData.workerMax || selectedWorker.length quotationFormData.workerMax || selectedWorkerItem.length
" "
:readonly="readonly" :readonly="readonly"
:rows="selectedWorkerItem" :rows="selectedWorkerItem"
@delete="(i) => deleteItem(selectedWorker, i)" @delete="(i) => deleteItem(selectedWorkerItem, i)"
/> />
</div> </div>
</q-expansion-item> </q-expansion-item>
@ -1853,10 +1925,10 @@ function covertToNode() {
installments: quotationFormData.paySplit, installments: quotationFormData.paySplit,
}, },
'quotation-labor': { 'quotation-labor': {
name: selectedWorker.map( name: selectedWorkerItem.map(
(v, i) => (v, i) =>
`${i + 1}. ` + `${i + 1}. ` +
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''} ${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(), `${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''}${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
), ),
}, },
}) })
@ -1946,6 +2018,7 @@ function covertToNode() {
view !== View.Receipt && view !== View.Receipt &&
view !== View.Complete view !== View.Complete
" "
:branch-id="quotationFull.registeredBranchId"
:readonly=" :readonly="
isRoleInclude(['sale', 'head_of_sale']) || isRoleInclude(['sale', 'head_of_sale']) ||
!canAccess('quotation', 'edit') !canAccess('quotation', 'edit')
@ -2396,13 +2469,12 @@ function covertToNode() {
<!-- add employee quotation --> <!-- add employee quotation -->
<QuotationFormWorkerSelect <QuotationFormWorkerSelect
:preselect-worker="selectedWorker" :preselect-worker="selectedWorkerItem"
:customerBranchId="quotationFormData.customerBranchId" :customerBranchId="quotationFormData.customerBranchId"
v-model:open="pageState.employeeModal" v-model:open="pageState.employeeModal"
v-model:new-worker-list="newWorkerList"
@success=" @success="
(v) => { (v) => {
selectedWorker = v.worker; combineWorker(v.newWorker, v.worker);
} }
" "
/> />
@ -2445,7 +2517,7 @@ function covertToNode() {
<!-- add Worker --> <!-- add Worker -->
<QuotationFormWorkerAddDialog <QuotationFormWorkerAddDialog
v-if="quotationFormState.source" v-if="quotationFormState.source"
:disabled-worker-id="selectedWorker.map((v) => v.id)" :disabled-worker-id="selectedWorkerItem.map((v) => v.id)"
:product-service-list="quotationFormState.source.productServiceList" :product-service-list="quotationFormState.source.productServiceList"
:quotation-id="quotationFormState.source.id" :quotation-id="quotationFormState.source.id"
:customer-branch-id="quotationFormState.source.customerBranchId" :customer-branch-id="quotationFormState.source.customerBranchId"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -79,8 +79,8 @@ function titleMode(mode: View): string {
}) })
}} }}
</span> </span>
<span>เลขประจำตวผเสยภาษ {{ branch.taxNo }}</span> <span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
<span>เบอรโทร {{ branch.telephoneNo }}</span> <span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
<span>{{ branch.webUrl }}</span> <span>{{ branch.webUrl }}</span>
</article> </article>
<article> <article>
@ -105,8 +105,18 @@ function titleMode(mode: View): string {
}) })
}} }}
</span> </span>
<span>เลขประจำตวผเสยภาษ {{ customer.citizenId }}</span> <span>
<span>เบอรโทร {{ customer.telephoneNo }}</span> {{
customer.customer.customerType === 'CORP'
? `${$t('customer.form.legalPersonNo')} `
: `${$t('customer.form.taxpayyerNo')} `
}}{{
customer.customer.customerType === 'CORP'
? customer.codeCustomer
: customer.citizenId
}}
</span>
<span>{{ $t('taskOrder.telephone') }} {{ customer.telephoneNo }}</span>
</article> </article>
</section> </section>
<section class="detail-quotation-info"> <section class="detail-quotation-info">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -190,6 +190,8 @@ function handleCheckAll() {
> >
<q-th v-if="checkable"> <q-th v-if="checkable">
<q-checkbox <q-checkbox
:for="`list-checkbox-all`"
:id="`list-checkbox-all`"
v-if="selected.length > 0" v-if="selected.length > 0"
:model-value=" :model-value="
selected.length === selected.length ===
@ -229,6 +231,8 @@ function handleCheckAll() {
> >
<q-td v-if="checkable"> <q-td v-if="checkable">
<q-checkbox <q-checkbox
:for="`list-checkbox-${props.rowIndex + 1}`"
:id="`list-checkbox-${props.rowIndex + 1}`"
:disable=" :disable="
selected.length > 0 && selected.length > 0 &&
!listSameArea.includes( !listSameArea.includes(
@ -314,7 +318,7 @@ function handleCheckAll() {
/> --> /> -->
<AvatarGroup <AvatarGroup
:data="[ :data="[
...responsiblePerson(props.row.quotation).user.map((v) => ({ ...(responsiblePerson(props.row.quotation)?.user.map((v) => ({
name: name:
$i18n.locale === 'eng' $i18n.locale === 'eng'
? `${v.firstNameEN} ${v.lastNameEN}` ? `${v.firstNameEN} ${v.lastNameEN}`
@ -324,11 +328,11 @@ function handleCheckAll() {
? `/no-img-man.png` ? `/no-img-man.png`
: `/no-img-female.png` : `/no-img-female.png`
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`, : `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
})), })) || []),
...responsiblePerson(props.row.quotation).group.map((g) => ({ ...(responsiblePerson(props.row.quotation)?.group.map((g) => ({
name: `${$t('general.group')} ${g.group}`, name: `${$t('general.group')} ${g.group}`,
imgUrl: '/img-group.png', imgUrl: '/img-group.png',
})), })) || []),
]" ]"
></AvatarGroup> ></AvatarGroup>
</q-td> </q-td>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -202,40 +202,44 @@ onMounted(async () => {
}) })
.reduce( .reduce(
(a, c) => { (a, c) => {
const priceNoVat = c.product.serviceChargeVatIncluded const vatFactor = c.product.serviceChargeCalcVat
? c.pricePerUnit / (1 + (config.value?.vat || 0.07)) ? (config.value?.vat ?? 0.07)
: c.pricePerUnit; : 0;
const adjustedPriceWithVat = precisionRound(
priceNoVat * (1 + (config.value?.vat || 0.07)),
);
const adjustedPriceNoVat =
adjustedPriceWithVat / (1 + (config.value?.vat || 0.07));
const priceDiscountNoVat = adjustedPriceNoVat * c.amount - c.discount;
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07); const pricePerUnit =
const rawVat = rawVatTotal / c.amount; precisionRound(c.product.serviceCharge * (1 + vatFactor)) /
(1 + vatFactor);
const amount = c.amount;
const discount = c.discount || 0;
const price =
(pricePerUnit * amount * (1 + vatFactor) - discount) /
(1 + vatFactor);
const vat = price * vatFactor;
product.value.push({ product.value.push({
id: c.product.id, id: c.product.id,
code: c.product.code, code: c.product.code,
detail: c.product.name, detail: c.product.name,
priceUnit: precisionRound(priceNoVat), priceUnit: precisionRound(pricePerUnit),
amount: c.amount, amount: c.amount,
discount: c.discount, discount: c.discount,
vat: c.product.serviceChargeCalcVat ? precisionRound(rawVat) : 0, vat: c.product.serviceChargeCalcVat ? precisionRound(vat) : 0,
value: precisionRound( value: precisionRound(
priceNoVat * c.amount + pricePerUnit * c.amount +
(c.product.serviceChargeCalcVat ? rawVatTotal : 0), (c.product.serviceChargeCalcVat ? vat : 0),
), ),
}); });
a.totalPrice = a.totalPrice + priceDiscountNoVat; a.totalPrice = precisionRound(a.totalPrice + price + discount);
a.totalDiscount = a.totalDiscount + Number(c.discount); a.totalDiscount = precisionRound(a.totalDiscount + discount);
a.vat = c.product.calcVat ? a.vat + rawVatTotal : a.vat; a.vat = precisionRound(a.vat + vat);
a.vatExcluded = c.product.calcVat a.vatExcluded = c.product.serviceChargeCalcVat
? a.vatExcluded ? a.vatExcluded
: precisionRound(a.vatExcluded + priceDiscountNoVat); : precisionRound(a.vatExcluded + price);
a.finalPrice = a.totalPrice - a.totalDiscount + a.vat; a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
return a; return a;
}, },
{ {
@ -311,7 +315,7 @@ function closeAble() {
border-bottom: 2px solid var(--main); border-bottom: 2px solid var(--main);
" "
> >
{{ $t('taskOrder.goodReceipt') }} {{ $t('preview.productList') }}
</span> </span>
<table ref="elements" class="q-mb-sm" cellpadding="0" style="width: 100%"> <table ref="elements" class="q-mb-sm" cellpadding="0" style="width: 100%">
@ -335,7 +339,9 @@ function closeAble() {
{{ formatNumberDecimal(v.priceUnit, 2) }} {{ formatNumberDecimal(v.priceUnit, 2) }}
</td> </td>
<td style="text-align: right"> <td style="text-align: right">
{{ formatNumberDecimal(v.discount, 2) }} <template v-if="v.discount !== 0">
{{ formatNumberDecimal(v.discount, 2) }} ฿
</template>
</td> </td>
<td style="text-align: right"> <td style="text-align: right">
{{ formatNumberDecimal(v.vat, 2) }} {{ formatNumberDecimal(v.vat, 2) }}
@ -346,7 +352,6 @@ function closeAble() {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table
style="width: 40%; margin-left: auto" style="width: 40%; margin-left: auto"
class="q-mb-md" class="q-mb-md"
@ -398,7 +403,9 @@ function closeAble() {
<td class="text-right"> <td class="text-right">
{{ {{
formatNumberDecimal( formatNumberDecimal(
summaryPrice.totalPrice - summaryPrice.totalDiscount, summaryPrice.totalPrice -
summaryPrice.totalDiscount -
summaryPrice.vatExcluded,
2, 2,
) )
}} }}

View file

@ -64,8 +64,8 @@ defineProps<{
}) })
}} }}
</span> </span>
<span>เลขประจำตวผเสยภาษ {{ branch.taxNo }}</span> <span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
<span>เบอรโทร {{ branch.telephoneNo }}</span> <span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
<span>{{ branch.webUrl }}</span> <span>{{ branch.webUrl }}</span>
</article> </article>
<article> <article>

View file

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

View file

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

View file

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

View file

@ -14,6 +14,10 @@ import { storeToRefs } from 'pinia';
import BadgeComponent from 'src/components/BadgeComponent.vue'; import BadgeComponent from 'src/components/BadgeComponent.vue';
import { TaskStatus } from 'src/stores/task-order/types'; import { TaskStatus } from 'src/stores/task-order/types';
import { useTaskOrderForm } from '../form';
const taskOrderFormStore = useTaskOrderForm();
const currentBtnOpen = ref<boolean[]>([]); const currentBtnOpen = ref<boolean[]>([]);
const configStore = useConfigStore(); const configStore = useConfigStore();
const { data: config } = storeToRefs(configStore); const { data: config } = storeToRefs(configStore);
@ -61,60 +65,28 @@ function openList(index: number) {
} }
function calcPricePerUnit(product: RequestWork['productService']['product']) { function calcPricePerUnit(product: RequestWork['productService']['product']) {
const val = props.creditNote return product.serviceCharge;
? props.agentPrice
? product.agentPrice
: product.price
: product.serviceCharge;
if (
product[
props.creditNote
? props.agentPrice
? 'agentPriceCalcVat'
: 'calcVat'
: 'serviceChargeCalcVat'
]
) {
return val / (1 + (config.value?.vat || 0.07));
} else {
return val;
}
} }
function calcPrice( function calcPrice(
product: RequestWork['productService']['product'], product: RequestWork['productService']['product'],
amount: number, amount: number,
) { ) {
const pricePerUnit = props.creditNote const vatFactor = product.serviceChargeCalcVat
? props.agentPrice ? (config.value?.vat ?? 0.07)
? product.agentPrice
: product.price
: product.serviceCharge;
const discount =
taskProduct.value.find((v) => v.productId === product.id)?.discount || 0;
const priceNoVat = product[
props.creditNote
? props.agentPrice
? 'agentPriceVatIncluded'
: 'vatIncluded'
: 'serviceChargeVatIncluded'
]
? pricePerUnit / (1 + (config.value?.vat || 0.07))
: pricePerUnit;
const priceDiscountNoVat = priceNoVat * amount - discount;
const rawVatTotal = product[
props.creditNote
? props.agentPrice
? 'agentPriceCalcVat'
: 'calcVat'
: 'serviceChargeCalcVat'
]
? priceDiscountNoVat * (config.value?.vat || 0.07)
: 0; : 0;
return precisionRound(priceNoVat * amount + rawVatTotal); const discount =
taskProduct.value.find((v) => v.productId === product.id)?.discount || 0;
const pricePerUnit = precisionRound(product.serviceCharge);
const price =
(pricePerUnit * amount * (1 + vatFactor) - discount) / (1 + vatFactor);
const vat = price * vatFactor;
return price + vat;
} }
function taskOrderStatus(value: TaskStatus) { function taskOrderStatus(value: TaskStatus) {
@ -139,6 +111,7 @@ function taskOrderStatus(value: TaskStatus) {
</script> </script>
<template> <template>
<q-expansion-item <q-expansion-item
id="expansion-product-list"
dense dense
class="overflow-hidden bordered full-width" class="overflow-hidden bordered full-width"
switch-toggle-side switch-toggle-side
@ -151,9 +124,11 @@ function taskOrderStatus(value: TaskStatus) {
<span <span
class="row items-center justify-between full-width" class="row items-center justify-between full-width"
style="min-height: 31.01px" style="min-height: 31.01px"
id="header-product-list"
> >
{{ $t('general.information', { msg: $t('taskOrder.productList') }) }} {{ $t('general.information', { msg: $t('taskOrder.productList') }) }}
<AddButton <AddButton
id="btn-add-product"
icon-only icon-only
@click.stop="$emit('addProduct')" @click.stop="$emit('addProduct')"
v-if="!readonly" v-if="!readonly"
@ -163,6 +138,7 @@ function taskOrderStatus(value: TaskStatus) {
<main class="q-px-md q-py-sm surface-1"> <main class="q-px-md q-py-sm surface-1">
<q-table <q-table
id="table-product-list"
:columns=" :columns="
creditNote creditNote
? productColumn.filter( ? productColumn.filter(
@ -208,7 +184,7 @@ function taskOrderStatus(value: TaskStatus) {
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>" } & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
> >
<q-tr class="text-center"> <q-tr class="text-center">
<q-td> <q-td :id="`product-order-${props.rowIndex}`">
{{ props.rowIndex + 1 }} {{ props.rowIndex + 1 }}
</q-td> </q-td>
<q-td> <q-td>
@ -234,20 +210,21 @@ function taskOrderStatus(value: TaskStatus) {
</q-td> </q-td>
<q-td class="text-left" v-if="!creditNote"> <q-td class="text-left" v-if="!creditNote">
<BadgeComponent <BadgeComponent
:id="`badge-status-${props.row.product.code}`"
hide-icon hide-icon
:hsla-color="taskOrderStatus(props.row.product.taskStatus)" :hsla-color="taskOrderStatus(props.row.product.taskStatus)"
:title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`" :title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`"
/> />
</q-td> </q-td>
<q-td> <q-td :id="`product-amount-${props.row.product.code}`">
{{ props.row.list.length }} {{ props.row.list.length }}
</q-td> </q-td>
<q-td class="text-right"> <q-td :id="`product-price-per-unit-${props.row.product.code}`" class="text-right">
{{ {{
formatNumberDecimal( formatNumberDecimal(
calcPricePerUnit(props.row.product) + calcPricePerUnit(props.row.product) +
(props.row.product.calcVat (props.row.product.serviceChargeCalcVat
? calcPricePerUnit(props.row.product) * ? calcPricePerUnit(props.row.product) *
(config?.vat || 0.07) (config?.vat || 0.07)
: 0), : 0),
@ -258,6 +235,7 @@ function taskOrderStatus(value: TaskStatus) {
<!-- TODO: display price detail --> <!-- TODO: display price detail -->
<q-td align="center" v-if="!creditNote"> <q-td align="center" v-if="!creditNote">
<q-input <q-input
:id="`input-discount-${props.row.product.code}`"
:readonly :readonly
:bg-color="readonly ? 'transparent' : ''" :bg-color="readonly ? 'transparent' : ''"
dense dense
@ -295,37 +273,49 @@ function taskOrderStatus(value: TaskStatus) {
/> />
</q-td> </q-td>
<!-- before vat --> <!-- before vat -->
<q-td class="text-right" v-if="!creditNote"> <q-td
:id="`product-price-before-vat-${props.row.product.code}`"
class="text-right"
v-if="!creditNote"
>
{{ {{
formatNumberDecimal( formatNumberDecimal(
calcPricePerUnit(props.row.product) * props.row.list.length - props.row.product.serviceChargeCalcVat
(taskProduct.find( ? (calcPricePerUnit(props.row.product) *
(v) => v.productId === props.row.product.id, props.row.list.length *
)?.discount || 0), (1 + (config?.vat || 0.07))) /
2, (1 + (config?.vat || 0.07))
: calcPricePerUnit(props.row.product) *
props.row.list.length -
taskProduct.find(
(v) => v.productId === props.row.product.id,
)?.discount || 0,
) )
}} }}
</q-td> </q-td>
<!-- vat --> <!-- vat -->
<q-td class="text-right" v-if="!creditNote"> <q-td
:id="`product-vat-${props.row.product.code}`"
class="text-right"
v-if="!creditNote"
>
{{ {{
formatNumberDecimal( formatNumberDecimal(
props.row.product.calcVat props.row.product.serviceChargeCalcVat
? precisionRound( ? ((calcPricePerUnit(props.row.product) *
(calcPricePerUnit(props.row.product) * props.row.list.length *
props.row.list.length - (1 + (config?.vat || 0.07)) -
(taskProduct.find( taskProduct.find(
(v) => v.productId === props.row.product.id, (v) => v.productId === props.row.product.id,
)?.discount || 0)) * )?.discount || 0) /
(config?.vat || 0.07), (1 + (config?.vat || 0.07))) *
) 0.07
: 0, : 0,
2,
) )
}} }}
</q-td> </q-td>
<!-- total --> <!-- total -->
<q-td class="text-right"> <q-td :id="`product-total-price-${props.row.product.code}`" class="text-right">
{{ {{
formatNumberDecimal( formatNumberDecimal(
calcPrice(props.row.product, props.row.list.length), calcPrice(props.row.product, props.row.list.length),
@ -335,6 +325,7 @@ function taskOrderStatus(value: TaskStatus) {
</q-td> </q-td>
<q-td> <q-td>
<q-btn <q-btn
:id="`btn-toggle-employee-${props.row.product.code}`"
dense dense
flat flat
class="rounded" class="rounded"
@ -358,6 +349,7 @@ function taskOrderStatus(value: TaskStatus) {
<q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props"> <q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props">
<q-td colspan="100%" style="padding: 16px"> <q-td colspan="100%" style="padding: 16px">
<TableEmployee <TableEmployee
:id="`table-employee-in-product-${props.row.product.code}`"
:step-on="!creditNote" :step-on="!creditNote"
:status-on="creditNote" :status-on="creditNote"
:rows="props.row.list" :rows="props.row.list"
@ -375,7 +367,9 @@ function taskOrderStatus(value: TaskStatus) {
}) })
}} }}
</span> </span>
<div class="surface-3 q-px-sm rounded">{{ taskList.length }}</div> <div class="surface-3 q-px-sm rounded" id="total-product-count">
{{ taskList.length }}
</div>
</div> </div>
</main> </main>
</q-expansion-item> </q-expansion-item>

View file

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

View file

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

View file

@ -128,32 +128,33 @@ function getPrice(
})[]; })[];
}[], }[],
) { ) {
return list.reduce( const value = list.reduce(
(a, c) => { (a, c) => {
const pricePerUnit = c.product.serviceCharge; const vatFactor = c.product.serviceChargeCalcVat
? (config.value?.vat ?? 0.07)
: 0;
const pricePerUnit =
precisionRound(c.product.serviceCharge * (1 + vatFactor)) /
(1 + vatFactor);
const amount = c.list.length; const amount = c.list.length;
const discount = const discount =
taskProduct.value.find((v) => v.productId === c.product.id)?.discount || taskProduct.value.find((v) => v.productId === c.product.id)?.discount ||
0; 0;
const priceNoVat = c.product.serviceChargeVatIncluded
? pricePerUnit / (1 + (config.value?.vat || 0.07))
: pricePerUnit;
const adjustedPriceWithVat = precisionRound(
priceNoVat * (1 + (config.value?.vat || 0.07)),
);
const adjustedPriceNoVat =
adjustedPriceWithVat / (1 + (config.value?.vat || 0.07));
const priceDiscountNoVat = adjustedPriceNoVat * amount - discount;
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07); const price =
(pricePerUnit * amount * (1 + vatFactor) - discount) / (1 + vatFactor);
a.totalPrice = a.totalPrice + priceDiscountNoVat; const vat = price * vatFactor;
a.totalDiscount = a.totalDiscount + Number(discount);
a.vat = c.product.serviceChargeCalcVat ? a.vat + rawVatTotal : a.vat; a.totalPrice = precisionRound(a.totalPrice + price + discount);
a.totalDiscount = precisionRound(a.totalDiscount + discount);
a.vat = precisionRound(a.vat + vat);
a.vatExcluded = c.product.serviceChargeCalcVat a.vatExcluded = c.product.serviceChargeCalcVat
? a.vatExcluded ? a.vatExcluded
: precisionRound(a.vatExcluded + priceDiscountNoVat); : precisionRound(a.vatExcluded + price);
a.finalPrice = a.totalPrice - a.totalDiscount + a.vat; a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
return a; return a;
}, },
{ {
@ -164,6 +165,8 @@ function getPrice(
finalPrice: 0, finalPrice: 0,
}, },
); );
return value;
} }
function getTemplateData( function getTemplateData(
@ -811,7 +814,7 @@ watch(
> >
<section class="banner" :class="{ dark: $q.dark.isActive }"></section> <section class="banner" :class="{ dark: $q.dark.isActive }"></section>
<div style="flex: 1" class="row items-center"> <div style="flex: 1" class="row items-center">
<RouterLink to="/task-order"> <RouterLink to="/task-order" id="link-task-order">
<q-img src="/icons/favicon-512x512.png" width="3rem" /> <q-img src="/icons/favicon-512x512.png" width="3rem" />
</RouterLink> </RouterLink>
<span class="column text-h6 text-bold q-ml-md"> <span class="column text-h6 text-bold q-ml-md">
@ -860,6 +863,7 @@ watch(
<!-- TODO: replace step and status --> <!-- TODO: replace step and status -->
<StateButton <StateButton
v-for="i in statusTabForm" v-for="i in statusTabForm"
:id="`btn-status-${i.title}`"
:key="i.title" :key="i.title"
:label="$t(`taskOrder.${i.title}`)" :label="$t(`taskOrder.${i.title}`)"
:status-active="i.active?.()" :status-active="i.active?.()"
@ -913,6 +917,7 @@ watch(
" "
> >
<DocumentExpansion <DocumentExpansion
id="expansion-document"
:readonly="!['create', 'edit'].includes(state.mode || '')" :readonly="!['create', 'edit'].includes(state.mode || '')"
v-model:registered-branch-id="currentFormData.registeredBranchId" v-model:registered-branch-id="currentFormData.registeredBranchId"
v-model:institution-id="currentFormData.institutionId" v-model:institution-id="currentFormData.institutionId"
@ -932,6 +937,7 @@ watch(
/> />
</q-form> </q-form>
<ProductExpansion <ProductExpansion
id="expansion-product"
ref="refProductExpansion" ref="refProductExpansion"
v-if=" v-if="
view === TaskOrderStatus.Pending || view === TaskOrderStatus.Pending ||
@ -944,6 +950,7 @@ watch(
/> />
<PaymentExpansion <PaymentExpansion
id="expansion-payment"
v-model:summary-price="summaryPrice" v-model:summary-price="summaryPrice"
:complete="view === TaskOrderStatus.Complete" :complete="view === TaskOrderStatus.Complete"
v-if=" v-if="
@ -953,6 +960,7 @@ watch(
/> />
<AdditionalFileExpansion <AdditionalFileExpansion
id="expansion-additional-file"
:readonly="!['create', 'edit'].includes(state.mode || '')" :readonly="!['create', 'edit'].includes(state.mode || '')"
v-if=" v-if="
view === TaskOrderStatus.Pending || view === TaskOrderStatus.Pending ||
@ -1011,6 +1019,7 @@ watch(
/> />
<!-- TODO: blind remark, urgent --> <!-- TODO: blind remark, urgent -->
<RemarkExpansion <RemarkExpansion
id="expansion-remark"
v-if=" v-if="
view === TaskOrderStatus.Pending || view === TaskOrderStatus.Pending ||
view === TaskOrderStatus.Complete view === TaskOrderStatus.Complete
@ -1035,6 +1044,7 @@ watch(
" "
> >
<InfoMessengerExpansion <InfoMessengerExpansion
:id="`expansion-messenger-${messengerIndex}`"
v-for="(v, messengerIndex) in messengerListGroup" v-for="(v, messengerIndex) in messengerListGroup"
:key="messengerIndex" :key="messengerIndex"
:gender="getMessengerName(v.responsibleUser, { gender: true })" :gender="getMessengerName(v.responsibleUser, { gender: true })"
@ -1070,6 +1080,7 @@ watch(
class="bordered-b" class="bordered-b"
> >
<q-expansion-item <q-expansion-item
:id="`expansion-product-${productIndex}`"
dense dense
class="overflow-hidden" class="overflow-hidden"
switch-toggle-side switch-toggle-side
@ -1243,6 +1254,7 @@ watch(
<nav class="row justify-end"> <nav class="row justify-end">
<MainButton <MainButton
class="q-mr-auto" class="q-mr-auto"
id="btn-view-example"
v-if="currentFormData.id" v-if="currentFormData.id"
outlined outlined
icon="mdi-play-box-outline" icon="mdi-play-box-outline"
@ -1257,10 +1269,16 @@ watch(
{{ $t('general.view', { msg: $t('general.example') }) }} {{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton> </MainButton>
<div class="row" style="gap: var(--size-2)"> <div class="row" style="gap: var(--size-2)">
<UndoButton outlined @click="undo()" v-if="state.mode === 'edit'" /> <UndoButton
id="btn-undo"
outlined
@click="undo()"
v-if="state.mode === 'edit'"
/>
<CancelButton <CancelButton
v-if="state.mode !== 'edit'" v-if="state.mode !== 'edit'"
:label="$t('dialog.action.close')" :label="$t('dialog.action.close')"
id="btn-close"
outlined outlined
@click="closeTab()" @click="closeTab()"
/> />
@ -1272,12 +1290,14 @@ watch(
" "
> >
<SaveButton <SaveButton
id="btn-save"
v-if="state.mode && ['create', 'edit'].includes(state.mode)" v-if="state.mode && ['create', 'edit'].includes(state.mode)"
@click="() => formDocument.submit()" @click="() => formDocument.submit()"
solid solid
/> />
<EditButton <EditButton
v-else v-else
id="btn-edit"
class="no-print" class="no-print"
@click="state.mode = 'edit'" @click="state.mode = 'edit'"
solid solid
@ -1285,6 +1305,7 @@ watch(
</template> </template>
<SaveButton <SaveButton
v-if=" v-if="
canAccess('taskOrder', 'edit') &&
state.mode !== 'create' && state.mode !== 'create' &&
view === TaskOrderStatus.Validate && view === TaskOrderStatus.Validate &&
fullTaskOrder?.taskOrderStatus !== TaskOrderStatus.Pending && fullTaskOrder?.taskOrderStatus !== TaskOrderStatus.Pending &&
@ -1323,6 +1344,7 @@ watch(
" "
:label="$t('taskOrder.confirmEndWork')" :label="$t('taskOrder.confirmEndWork')"
icon="mdi-check" icon="mdi-check"
id="btn-confirm-end-work"
solid solid
></SaveButton> ></SaveButton>
</div> </div>
@ -1332,6 +1354,7 @@ watch(
<!-- SEC: Dialog --> <!-- SEC: Dialog -->
<SelectReadyRequestWork <SelectReadyRequestWork
id="dialog-select-request-work"
:task-list-group="taskListGroup" :task-list-group="taskListGroup"
:fetch-params="{ readyToTask: true }" :fetch-params="{ readyToTask: true }"
v-model:open="pageState.productDialog" v-model:open="pageState.productDialog"

View file

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

View file

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

View file

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

View file

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

View file

@ -31,6 +31,7 @@ import {
EditButton, EditButton,
UndoButton, UndoButton,
} from 'src/components/button'; } from 'src/components/button';
import { precisionRound } from 'src/utils/arithmetic';
import { DebitNote, useDebitNote } from 'src/stores/debit-note'; import { DebitNote, useDebitNote } from 'src/stores/debit-note';
import { RequestWork } from 'src/stores/request-list/types'; import { RequestWork } from 'src/stores/request-list/types';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@ -40,6 +41,8 @@ import { useI18n } from 'vue-i18n';
import { QForm } from 'quasar'; import { QForm } from 'quasar';
import { getName } from 'src/services/keycloak'; import { getName } from 'src/services/keycloak';
import { RequestWorkStatus } from 'src/stores/request-list/types';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const creditNote = useCreditNote(); const creditNote = useCreditNote();
@ -49,6 +52,7 @@ const configStore = useConfigStore();
const { data: config } = storeToRefs(configStore); const { data: config } = storeToRefs(configStore);
const { t } = useI18n(); const { t } = useI18n();
const agentPrice = ref<boolean>(false);
const refForm = ref<InstanceType<typeof QForm>>(); const refForm = ref<InstanceType<typeof QForm>>();
const creditNoteData = ref<CreditNote>(); const creditNoteData = ref<CreditNote>();
const quotationData = ref<DebitNote | QuotationFull>(); const quotationData = ref<DebitNote | QuotationFull>();
@ -206,22 +210,23 @@ function getPrice(
) { ) {
return list.reduce( return list.reduce(
(a, c) => { (a, c) => {
const pricePerUnit = const value = creditNote.getfinalPriceCredit(c.list || []);
c.product.pricePerUnit - c.product.discount / c.product.amount;
const amount = c.list.length;
const priceNoVat = pricePerUnit;
const priceDiscountNoVat = priceNoVat * amount;
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07); a.totalPrice = precisionRound(a.totalPrice + value.totalPrice);
a.totalDiscount = precisionRound(a.totalDiscount + value.totalDiscount);
a.vat = precisionRound(a.vat + value.vat);
a.vatIncluded = precisionRound(a.vatIncluded + value.vatIncluded);
a.vatExcluded = precisionRound(a.vatExcluded + value.vatExcluded);
a.finalPrice = precisionRound(a.finalPrice + value.finalPrice);
a.totalPrice = a.totalPrice + priceDiscountNoVat;
a.vat = c.product.vat !== 0 ? a.vat + rawVatTotal : a.vat;
a.finalPrice = a.totalPrice + a.vat;
return a; return a;
}, },
{ {
totalPrice: 0, totalPrice: 0,
totalDiscount: 0,
vat: 0, vat: 0,
vatIncluded: 0,
vatExcluded: 0,
finalPrice: 0, finalPrice: 0,
}, },
); );
@ -296,6 +301,8 @@ async function getQuotation() {
if (!ret) return; if (!ret) return;
quotationData.value = ret; quotationData.value = ret;
agentPrice.value = quotationData.value.agentPrice;
} }
async function submit() { async function submit() {

View file

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

View file

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

View file

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

View file

@ -237,30 +237,12 @@ onMounted(async () => {
}); });
function calcPricePerUnit(product: RequestWork['productService']['product']) { function calcPricePerUnit(product: RequestWork['productService']['product']) {
return product.vatIncluded return agentPrice.value ? product.agentPrice : product.price;
? (agentPrice.value ? product.agentPrice : product.price) /
(1 + (config.value?.vat || 0.07))
: agentPrice.value
? product.agentPrice
: product.price;
} }
function calcPrice( function calcPrice(c: RequestWork[]): number {
product: RequestWork['productService']['product'], const price = creditNoteStore.getfinalPriceCredit(c);
amount: number, return price.finalPrice;
vat: number = 0,
) {
const pricePerUnit = agentPrice.value ? product.agentPrice : product.price;
const priceNoVat = product.vatIncluded
? pricePerUnit / (1 + (config.value?.vat || 0.07))
: pricePerUnit;
const priceDiscountNoVat = priceNoVat * amount - 0;
const rawVatTotal =
vat === 0 ? 0 : priceDiscountNoVat * (config.value?.vat || 0.07);
return precisionRound(priceNoVat * amount + rawVatTotal);
} }
watch(elements, () => {}); watch(elements, () => {});
@ -327,31 +309,15 @@ function closeAble() {
<th>{{ $t('preview.pricePerUnit') }}</th> <th>{{ $t('preview.pricePerUnit') }}</th>
<th>{{ $t('preview.value') }}</th> <th>{{ $t('preview.value') }}</th>
</tr> </tr>
{{ console.log(chunks) }}
{{ console.log(chunk) }}
<tr v-for="(v, i) in chunk" :key="i"> <tr v-for="(v, i) in chunk" :key="i">
<td class="text-center">{{ i + 1 }}</td> <td class="text-center">{{ i + 1 }}</td>
<td>{{ v.product.product.code }}</td> <td>{{ v.product.product.code }}</td>
<td>{{ v.product.product.name }}</td> <td>{{ v.product.product.name }}</td>
<td style="text-align: center"> <td style="text-align: right">
{{ {{ formatNumberDecimal(calcPricePerUnit(v.product.product), 2) }}
formatNumberDecimal(
calcPricePerUnit(v.product.product) +
(v.product.product.calcVat
? calcPricePerUnit(v.product.product) *
(config?.vat || 0.07)
: 0),
2,
)
}}
</td> </td>
<td style="text-align: center"> <td style="text-align: right">
{{ {{ formatNumberDecimal(calcPrice(v.list), 2) }}
formatNumberDecimal(
calcPrice(v.product.product, v.list.length, v.product.vat),
2,
)
}}
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -409,8 +375,8 @@ function closeAble() {
{{ {{
formatNumberDecimal( formatNumberDecimal(
summaryPrice.totalPrice - summaryPrice.totalPrice -
summaryPrice.totalDiscount + summaryPrice.totalDiscount -
summaryPrice.vat, summaryPrice.vatExcluded,
2, 2,
) )
}} }}

View file

@ -79,8 +79,8 @@ function titleMode(mode: View): string {
}) })
}} }}
</span> </span>
<span>เลขประจำตวผเสยภาษ {{ branch.taxNo }}</span> <span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
<span>เบอรโทร {{ branch.telephoneNo }}</span> <span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
<span>{{ branch.webUrl }}</span> <span>{{ branch.webUrl }}</span>
</article> </article>
<article> <article>
@ -105,8 +105,18 @@ function titleMode(mode: View): string {
}) })
}} }}
</span> </span>
<span>เลขประจำตวผเสยภาษ {{ customer.citizenId }}</span> <span>
<span>เบอรโทร {{ customer.telephoneNo }}</span> {{
customer.customer.customerType === 'CORP'
? `${$t('customer.form.legalPersonNo')} `
: `${$t('customer.form.taxpayyerNo')} `
}}{{
customer.customer.customerType === 'CORP'
? customer.codeCustomer
: customer.citizenId
}}
</span>
<span>{{ $t('taskOrder.telephone') }} {{ customer.telephoneNo }}</span>
</article> </article>
</section> </section>
<section class="detail-quotation-info"> <section class="detail-quotation-info">

View file

@ -12,10 +12,13 @@ import { baseUrl, formatNumberDecimal } from 'src/stores/utils';
import { precisionRound } from 'src/utils/arithmetic'; import { precisionRound } from 'src/utils/arithmetic';
import { productColumn } from '../constants'; import { productColumn } from '../constants';
import { useCreditNote } from 'src/stores/credit-note';
const configStore = useConfigStore(); const configStore = useConfigStore();
const { data: config } = storeToRefs(configStore); const { data: config } = storeToRefs(configStore);
const currentExpanded = ref<boolean[]>([]); const currentExpanded = ref<boolean[]>([]);
const creditNote = useCreditNote();
defineProps<{ defineProps<{
readonly?: boolean; readonly?: boolean;
@ -44,15 +47,22 @@ function calcPricePerUnit(product: RequestWork['productService']) {
return product.pricePerUnit - product.discount / product.amount; return product.pricePerUnit - product.discount / product.amount;
} }
function calcPrice(c: RequestWork['productService'], amount: number) { function calcVat(c: RequestWork['productService']) {
const pricePerUnit = c.pricePerUnit - c.discount / c.amount; const vatFactor = c.product.serviceChargeCalcVat
const priceNoVat = pricePerUnit; ? (config.value?.vat ?? 0.07)
const priceDiscountNoVat = priceNoVat * amount; : 0;
const rawVatTotal = const price = precisionRound(
c.vat === 0 ? 0 : priceDiscountNoVat * (config.value?.vat || 0.07); c.pricePerUnit * (1 + vatFactor) - c.discount / (1 + vatFactor),
);
return precisionRound(priceNoVat * amount + rawVatTotal); const vat = (price / (1 + vatFactor)) * vatFactor;
return vat;
}
function calcPrice(c: RequestWork[]) {
const price = creditNote.getfinalPriceCredit(c);
return price.finalPrice;
} }
</script> </script>
<template> <template>
@ -162,12 +172,7 @@ function calcPrice(c: RequestWork['productService'], amount: number) {
<!-- total --> <!-- total -->
<q-td class="text-right"> <q-td class="text-right">
{{ {{ formatNumberDecimal(calcPrice(props.row.list), 2) }}
formatNumberDecimal(
calcPrice(props.row.product, props.row.list.length),
2,
)
}}
</q-td> </q-td>
<q-td> <q-td>
<q-btn <q-btn

View file

@ -209,47 +209,7 @@ const selectedWorker = ref<
}[]; }[];
})[] })[]
>([]); >([]);
const selectedWorkerItem = computed(() => { const selectedWorkerItem = ref([]);
return [
...selectedWorker.value.map((e) => ({
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
status: e.status,
})),
...newWorkerList.value.map((v: any) => ({
foreignRefNo: v.passportNo || '-',
employeeName:
locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
birthDate: dateFormatJS({ date: v.dateOfBirth }),
gender: v.gender,
age: calculateAge(v.dateOfBirth),
nationality: optionStore.mapOption(v.nationality),
documentExpireDate: '-',
imgUrl: '',
status: 'CREATED',
})),
];
});
const selectedInstallmentNo = ref<number[]>([]); const selectedInstallmentNo = ref<number[]>([]);
const installmentAmount = ref<number>(0); const installmentAmount = ref<number>(0);
@ -374,7 +334,39 @@ function assignProductServiceList() {
function assignSelectedWorker() { function assignSelectedWorker() {
if (debitNoteData.value) if (debitNoteData.value)
selectedWorker.value = debitNoteData.value.worker.map((v) => v.employee); selectedWorkerItem.value = debitNoteData.value.worker.map((e) => {
return {
id: e.employee.id,
foreignRefNo: e.employee.employeePassport
? e.employee.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.employee.firstNameEN} ${e.employee.lastNameEN}`
: `${e.employee.firstName || e.employee.firstNameEN} ${e.employee.lastName || e.employee.lastNameEN}`,
birthDate: dateFormatJS({ date: e.employee.dateOfBirth }),
gender: e.employee.gender,
age: calculateAge(e.employee.dateOfBirth),
nationality: optionStore.mapOption(e.employee.nationality),
documentExpireDate:
e.employee.employeePassport !== undefined &&
e.employee.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employee.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.employee.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.employee.selectedImage}`
: '',
status: e.employee.status,
workerNew: false,
lastNameEN: e.employee.lastNameEN,
lastName: e.employee.lastName,
middleNameEN: e.employee.middleNameEN,
middleName: e.employee.middleName,
firstNameEN: e.employee.firstNameEN,
firstName: e.employee.firstName,
namePrefix: e.employee.namePrefix,
};
});
} }
async function assignFormData(id: string) { async function assignFormData(id: string) {
@ -386,7 +378,7 @@ async function assignFormData(id: string) {
selectedProductGroup.value = selectedProductGroup.value =
data.productServiceList[0]?.product.productGroup?.id || ''; data.productServiceList[0]?.product.productGroup?.id || '';
((previousValue = { (previousValue = {
id: data.id || undefined, id: data.id || undefined,
debitNoteQuotationId: data.debitNoteQuotationId || undefined, debitNoteQuotationId: data.debitNoteQuotationId || undefined,
productServiceList: structuredClone( productServiceList: structuredClone(
@ -412,7 +404,7 @@ async function assignFormData(id: string) {
quotationId: data.debitNoteQuotationId, quotationId: data.debitNoteQuotationId,
remark: data.remark || undefined, remark: data.remark || undefined,
}), }),
(currentFormData.value = structuredClone(previousValue))); (currentFormData.value = structuredClone(previousValue));
assignProductServiceList(); assignProductServiceList();
assignSelectedWorker(); assignSelectedWorker();
@ -434,6 +426,7 @@ async function getQuotation(id?: string) {
const data = await quotationStore.getQuotation(quotationId); const data = await quotationStore.getQuotation(quotationId);
if (!!data) { if (!!data) {
quotationData.value = data; quotationData.value = data;
agentPrice.value = quotationData.value.agentPrice;
} }
} }
} }
@ -549,25 +542,28 @@ function getPrice(
return a; return a;
} }
const price = precisionRound(c.pricePerUnit * c.amount); const calcVat =
const vat = c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
precisionRound(
(c.pricePerUnit * (c.discount ? c.amount : 1) - c.discount) *
(config.value?.vat || 0.07),
) * (!c.discount ? c.amount : 1);
a.totalPrice = precisionRound(a.totalPrice + price); const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
a.vat = c.product.calcVat ? precisionRound(a.vat + vat) : a.vat; const pricePerUnit = precisionRound(
(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor),
);
const price = precisionRound(
(pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
(1 + vatFactor),
);
const vat = price * vatFactor;
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
a.vat = precisionRound(a.vat + vat);
a.vatExcluded = c.product.calcVat a.vatExcluded = c.product.calcVat
? a.vatExcluded ? a.vatExcluded
: precisionRound(a.vat + vat); : precisionRound(a.vatExcluded + price);
a.finalPrice = precisionRound( a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
a.totalPrice -
a.totalDiscount +
a.vat -
Number(currentFormData.value.discount || 0),
);
return a; return a;
}, },
@ -575,6 +571,7 @@ function getPrice(
totalPrice: 0, totalPrice: 0,
totalDiscount: 0, totalDiscount: 0,
vat: 0, vat: 0,
vatIncluded: 0,
vatExcluded: 0, vatExcluded: 0,
finalPrice: 0, finalPrice: 0,
}, },
@ -808,15 +805,11 @@ async function submit() {
worker: JSON.parse( worker: JSON.parse(
JSON.stringify([ JSON.stringify([
...selectedWorker.value.map((v) => { ...selectedWorkerItem.value.map((v) => {
{ {
return v.id; return v.id;
} }
}), }),
...newWorkerList.value.map((v) => {
const { attachment, ...payload } = v;
return pageState.mode === 'edit' ? payload.id : payload;
}),
]), ]),
), ),
dueDate: currentFormData.value.dueDate, dueDate: currentFormData.value.dueDate,
@ -869,6 +862,7 @@ async function exampleReceipt(id: string) {
function storeDataLocal() { function storeDataLocal() {
// quotationFormData.value.productServiceList = productServiceList.value; // quotationFormData.value.productServiceList = productServiceList.value;
//
localStorage.setItem( localStorage.setItem(
'debit-note-preview', 'debit-note-preview',
@ -877,6 +871,7 @@ function storeDataLocal() {
...currentFormData.value, ...currentFormData.value,
customerBranchId: quotationData.value?.customerBranchId, customerBranchId: quotationData.value?.customerBranchId,
registeredBranchId: quotationData.value?.registeredBranchId, registeredBranchId: quotationData.value?.registeredBranchId,
agentPrice: quotationData.value.agentPrice,
}, },
meta: { meta: {
source: { source: {
@ -893,7 +888,7 @@ function storeDataLocal() {
dueDate: currentFormData.value.dueDate, dueDate: currentFormData.value.dueDate,
}, },
productServicelist: productService.value, productServicelist: productService.value,
selectedWorker: selectedWorker.value, selectedWorker: selectedWorkerItem.value,
createdBy: getName(), createdBy: getName(),
}, },
}), }),
@ -918,6 +913,69 @@ function closeAble() {
return window.opener !== null; return window.opener !== null;
} }
function combineWorker(newWorker: any, oldWorker: any) {
selectedWorkerItem.value = [
...oldWorker.map((e) => ({
id: e.id,
foreignRefNo: e.employeePassport
? e.employeePassport[0]?.number || '-'
: '-',
employeeName:
locale.value === Lang.English
? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName || e.firstNameEN} ${e.lastName || e.lastNameEN}`,
birthDate: dateFormatJS({ date: e.dateOfBirth }),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate:
e.employeePassport !== undefined &&
e.employeePassport[0]?.expireDate !== undefined
? dateFormatJS({ date: e.employeePassport[0]?.expireDate })
: '-',
imgUrl: e.selectedImage
? `${API_BASE_URL}/employee/${e.id}/image/${e.selectedImage}`
: '',
status: e.status,
workerNew: false,
lastNameEN: e.lastNameEN,
lastName: e.lastName,
middleNameEN: e.middleNameEN,
middleName: e.middleName,
firstNameEN: e.firstNameEN,
firstName: e.firstName,
namePrefix: e.namePrefix,
})),
...newWorker.map((v: any) => ({
id: v.id,
foreignRefNo: v.passportNo || '-',
employeeName:
locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName || v.firstNameEN} ${v.lastName || v.lastNameEN}`,
birthDate: dateFormatJS({ date: v.dateOfBirth }),
gender: v.gender,
age: calculateAge(v.dateOfBirth),
nationality: optionStore.mapOption(v.nationality),
documentExpireDate: '-',
imgUrl: '',
status: 'CREATED',
lastNameEN: v.lastNameEN,
lastName: v.lastName,
middleNameEN: v.middleNameEN,
middleName: v.middleName,
firstNameEN: v.firstNameEN,
firstName: v.firstName,
namePrefix: v.namePrefix,
dateOfBirth: v.dateOfBirth,
workerNew: true,
})),
];
}
watch( watch(
() => pageState.mode, () => pageState.mode,
() => toggleMode(pageState.mode), () => toggleMode(pageState.mode),
@ -1046,7 +1104,11 @@ async function submitAccepted() {
:status-active="i.active?.()" :status-active="i.active?.()"
:status-done="i.status === 'done'" :status-done="i.status === 'done'"
:status-waiting="i.status === 'waiting'" :status-waiting="i.status === 'waiting'"
@click="i.handler()" @click="
() => {
if (pageState.mode !== 'create') i.handler();
}
"
/> />
</nav> </nav>
<!-- #TODO add goToQuotation as @goto-quotation--> <!-- #TODO add goToQuotation as @goto-quotation-->
@ -1077,6 +1139,7 @@ async function submitAccepted() {
<PaymentForm <PaymentForm
v-if="view === QuotationStatus.PaymentPending" v-if="view === QuotationStatus.PaymentPending"
is-debit-note is-debit-note
:branch-id="quotationData?.registeredBranchId"
:readonly="isRoleInclude(['sale', 'head_of_sale'])" :readonly="isRoleInclude(['sale', 'head_of_sale'])"
:data="debitNoteData" :data="debitNoteData"
@fetch-status=" @fetch-status="
@ -1098,7 +1161,7 @@ async function submitAccepted() {
" "
:row-worker="selectedWorkerItem" :row-worker="selectedWorkerItem"
@add-worker="() => (pageState.employeeModal = true)" @add-worker="() => (pageState.employeeModal = true)"
@delete="(i) => deleteItem(selectedWorker, i)" @delete="(i) => deleteItem(selectedWorkerItem, i)"
/> />
<!-- #TODO add openProductDialog at @add-product--> <!-- #TODO add openProductDialog at @add-product-->
@ -1318,7 +1381,7 @@ async function submitAccepted() {
<SaveButton <SaveButton
v-if="pageState.mode !== 'info'" v-if="pageState.mode !== 'info'"
:disabled=" :disabled="
selectedWorkerItem.length === 0 && productService.length === 0 selectedWorkerItem.length === 0 || productService.length === 0
" "
@click="submit" @click="submit"
solid solid
@ -1363,13 +1426,12 @@ async function submitAccepted() {
<!-- add employee quotation --> <!-- add employee quotation -->
<QuotationFormWorkerSelect <QuotationFormWorkerSelect
:preselect-worker="selectedWorker" :preselect-worker="selectedWorkerItem"
:customerBranchId="quotationData?.customerBranchId" :customerBranchId="quotationData?.customerBranchId"
v-model:open="pageState.employeeModal" v-model:open="pageState.employeeModal"
v-model:new-worker-list="newWorkerList"
@success=" @success="
(v) => { (v) => {
selectedWorker = v.worker; combineWorker(v.newWorker, v.worker);
} }
" "
/> />

View file

@ -143,6 +143,15 @@ onMounted(async () => {
}); });
getList(); getList();
window.addEventListener('focus', () => {
debitNote.getDebitNoteStats().then((res) => {
if (res) {
stats.value = res;
}
});
getList();
});
}); });
watch( watch(
@ -411,7 +420,7 @@ watch(
@delete="() => triggerDelete(item.row.id)" @delete="() => triggerDelete(item.row.id)"
:title="item.row.debitNoteQuotation?.workName" :title="item.row.debitNoteQuotation?.workName"
:code="item.row.code" :code="item.row.code"
:status="$t(`quotation.status.${item.row.quotationStatus}`)" :status="$t(`debitNote.stats.${item.row.quotationStatus}`)"
:badge-color="hslaColors[item.row.quotationStatus] || ''" :badge-color="hslaColors[item.row.quotationStatus] || ''"
:custom-data="[ :custom-data="[
{ {
@ -425,10 +434,12 @@ watch(
label: $t('quotation.customer'), label: $t('quotation.customer'),
value: value:
item.row.customerBranch.customer.customerType === 'CORP' item.row.customerBranch.customer.customerType === 'CORP'
? item.row.customerBranch.registerName ? $i18n.locale === 'tha'
? item.row.customerBranch.registerName
: item.row.customerBranch.registerNameEN
: $i18n.locale === 'tha' : $i18n.locale === 'tha'
? `${item.row.customerBranch.firstName} ${item.row.customerBranch.lastName}` ? `${item.row.customerBranch.firstName || ''} ${item.row.customerBranch.lastName || ''}`
: `${item.row.customerBranch.firstNameEN} ${item.row.customerBranch.lastNameEN}`, : `${item.row.customerBranch.firstNameEN || ''} ${item.row.customerBranch.lastNameEN || ''}`,
}, },
{ {
label: $t('requestList.quotationCode'), label: $t('requestList.quotationCode'),

View file

@ -82,6 +82,8 @@ const data = ref<
>(); >();
const productServiceList = ref<ProductServiceList[]>([]); const productServiceList = ref<ProductServiceList[]>([]);
const selectedInstallmentNo = ref<number[]>([]);
const agentPrice = ref<boolean>(false);
const summaryPrice = ref<SummaryPrice>({ const summaryPrice = ref<SummaryPrice>({
totalPrice: 0, totalPrice: 0,
@ -217,6 +219,8 @@ onMounted(async () => {
} }
productServiceList.value = parsed.meta.productServicelist; productServiceList.value = parsed.meta.productServicelist;
selectedInstallmentNo.value = parsed.meta.selectedInstallmentNo;
agentPrice.value = parsed.meta.agentPrice;
productList.value = productList.value =
productServiceList.value?.map((v) => ({ productServiceList.value?.map((v) => ({
@ -224,40 +228,56 @@ onMounted(async () => {
code: v.product.code, code: v.product.code,
detail: v.product.name, detail: v.product.name,
amount: v.amount || 0, amount: v.amount || 0,
priceUnit: v.pricePerUnit || 0, priceUnit:
v.pricePerUnit +
(v.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat']
? v.pricePerUnit * (config.value?.vat || 0.07)
: 0),
discount: v.discount || 0, discount: v.discount || 0,
vat: v.vat || 0, vat: v.vat || 0,
value: precisionRound( value: precisionRound(
(v.pricePerUnit || 0) * v.amount - (v.pricePerUnit +
(v.discount || 0) + (v.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat']
(v.product.calcVat ? v.pricePerUnit * (config.value?.vat || 0.07)
? ((v.pricePerUnit || 0) * v.amount - (v.discount || 0)) * : 0)) *
(config.value?.vat || 0.07) v.amount -
: 0), v.discount,
), ),
})) || []; })) || [];
} }
summaryPrice.value = (productServiceList.value || []).reduce( summaryPrice.value = (productServiceList.value || []).reduce(
(a, c) => { (a, c) => {
const price = precisionRound((c.pricePerUnit || 0) * c.amount); if (
const vat = precisionRound( selectedInstallmentNo.value?.length > 0 &&
((c.pricePerUnit || 0) * c.amount - (c.discount || 0)) * c.installmentNo &&
(config.value?.vat || 0.07), !selectedInstallmentNo.value?.includes(c.installmentNo)
) {
return a;
}
const calcVat =
c.product[agentPrice.value ? 'agentPriceCalcVat' : 'calcVat'];
const vatFactor = calcVat ? (config.value?.vat ?? 0.07) : 0;
const pricePerUnit = precisionRound(
(c.pricePerUnit * (1 + vatFactor)) / (1 + vatFactor),
); );
a.totalPrice = precisionRound(a.totalPrice + price); const price = precisionRound(
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount)); (pricePerUnit * c.amount * (1 + vatFactor) - c.discount) /
a.vat = c.product.calcVat ? precisionRound(a.vat + vat) : a.vat; (1 + vatFactor),
);
const vat = price * vatFactor;
a.totalPrice = precisionRound(a.totalPrice + price + c.discount);
a.totalDiscount = precisionRound(a.totalDiscount + c.discount);
a.vat = precisionRound(a.vat + vat);
a.vatExcluded = c.product.calcVat a.vatExcluded = c.product.calcVat
? a.vatExcluded ? a.vatExcluded
: precisionRound(a.vat + vat); : precisionRound(a.vatExcluded + price);
a.finalPrice = precisionRound( a.finalPrice = precisionRound(a.totalPrice - a.totalDiscount + a.vat);
a.totalPrice -
a.totalDiscount +
a.vat -
Number(data.value?.discount || 0),
);
return a; return a;
}, },
@ -349,7 +369,9 @@ function print() {
{{ formatNumberDecimal(v.priceUnit, 2) }} {{ formatNumberDecimal(v.priceUnit, 2) }}
</td> </td>
<td style="text-align: right"> <td style="text-align: right">
{{ formatNumberDecimal(v.discount, 2) }} <template v-if="v.discount !== 0">
{{ formatNumberDecimal(v.discount, 2) }} ฿
</template>
</td> </td>
<td style="text-align: right"> <td style="text-align: right">
{{ formatNumberDecimal(v.vat, 2) }} {{ formatNumberDecimal(v.vat, 2) }}
@ -413,8 +435,8 @@ function print() {
{{ {{
formatNumberDecimal( formatNumberDecimal(
summaryPrice.totalPrice - summaryPrice.totalPrice -
summaryPrice.totalDiscount + summaryPrice.totalDiscount -
summaryPrice.vat, summaryPrice.vatExcluded,
2, 2,
) )
}} }}

View file

@ -79,8 +79,8 @@ function titleMode(mode: View): string {
}) })
}} }}
</span> </span>
<span>เลขประจำตวผเสยภาษ {{ branch.taxNo }}</span> <span>{{ $t('branch.form.taxNo') }} {{ branch.taxNo }}</span>
<span>เบอรโทร {{ branch.telephoneNo }}</span> <span>{{ $t('taskOrder.telephone') }} {{ branch.telephoneNo }}</span>
<span>{{ branch.webUrl }}</span> <span>{{ branch.webUrl }}</span>
</article> </article>
<article> <article>
@ -105,8 +105,18 @@ function titleMode(mode: View): string {
}) })
}} }}
</span> </span>
<span>เลขประจำตวผเสยภาษ {{ customer.citizenId }}</span> <span>
<span>เบอรโทร {{ customer.telephoneNo }}</span> {{
customer.customer.customerType === 'CORP'
? `${$t('customer.form.legalPersonNo')} `
: `${$t('customer.form.taxpayyerNo')} `
}}{{
customer.customer.customerType === 'CORP'
? customer.codeCustomer
: customer.citizenId
}}
</span>
<span>{{ $t('taskOrder.telephone') }} {{ customer.telephoneNo }}</span>
</article> </article>
</section> </section>
<section class="detail-quotation-info"> <section class="detail-quotation-info">

View file

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

View file

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

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