Merge branch 'develop'

This commit is contained in:
Methapon2001 2025-01-22 11:46:41 +07:00
commit 16e9393315
29 changed files with 299 additions and 136 deletions

View file

@ -2,7 +2,7 @@
import { calculateDaysUntilExpire } from 'stores/utils'; import { calculateDaysUntilExpire } from 'stores/utils';
defineProps<{ defineProps<{
expirationDate: Date; expirationDate?: Date | string;
showAllDay?: boolean; showAllDay?: boolean;
}>(); }>();
</script> </script>
@ -10,7 +10,7 @@ defineProps<{
<template> <template>
<template <template
v-if=" v-if="
calculateDaysUntilExpire(expirationDate) <= 90 || (expirationDate && calculateDaysUntilExpire(expirationDate) <= 90) ||
(expirationDate !== undefined && !!showAllDay) (expirationDate !== undefined && !!showAllDay)
" "
> >

View file

@ -74,11 +74,6 @@ type Options = { label: string; value: string };
class="col" class="col"
:label="'Agencies Name'" :label="'Agencies Name'"
v-model="nameEn" v-model="nameEn"
:rules="[
(val: string) => !!val || $t('form.error.required'),
(val: string) =>
/^[A-Za-z0-9.,' -]+$/.test(val) || $t('form.error.letterOnly'),
]"
/> />
</div> </div>
</div> </div>

View file

@ -54,8 +54,10 @@ 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 ellipsis"> <article class="col column q-pl-md">
<span>{{ name }}</span> <span class="ellipsis full-width">
{{ name }}
</span>
<span class="text-caption app-text-muted-2"> <span class="text-caption app-text-muted-2">
{{ {{
uploading.loaded uploading.loaded

View file

@ -11,6 +11,15 @@ import { dateFormat } from 'src/utils/datetime';
const isEdit = ref(false); const isEdit = ref(false);
const allMeta = ref<Record<string, string>>(); const allMeta = ref<Record<string, string>>();
const { t } = useI18n(); const { t } = useI18n();
const currentId = defineModel<string>('currentId');
const groupList = defineModel<
{
label: string;
group: string;
value: string;
_meta?: Record<string, any>;
}[]
>('groupList');
const obj = defineModel< const obj = defineModel<
{ {
_meta?: Record<string, any>; _meta?: Record<string, any>;

View file

@ -82,6 +82,7 @@ function pickFile() {
<div <div
v-for="(d, j) in fileData" v-for="(d, j) in fileData"
:key="j" :key="j"
class="full-width"
:class="{ :class="{
'q-pt-md': layout === 'row' && j === 0, 'q-pt-md': layout === 'row' && j === 0,
'q-pt-sm': j > 0, 'q-pt-sm': j > 0,

View file

@ -731,6 +731,16 @@ export default {
}, },
quotation: { quotation: {
quotationDate: 'Quotation Date',
seller: 'Seller',
paymentChannels: 'Payment Channels',
channelsThat: 'Channels That',
bankAccountNumber: 'Bank Account Number',
bankAccountName: 'Bank Account Name',
inTheNameOf: 'In The Name Of',
productOrderer: 'Product Orderer',
approver: 'Approver',
date: 'Date',
tableColumnsRequest: { tableColumnsRequest: {
code: 'Request List Code', code: 'Request List Code',
}, },
@ -928,6 +938,12 @@ export default {
receive: 'Job Receipt ', receive: 'Job Receipt ',
caption: 'All Task Order', caption: 'All Task Order',
code: 'Task Order Code', code: 'Task Order Code',
workName: 'Work Name',
contacts: 'Contacts',
inTheNameOf: 'In The Name Of',
productOrderer: 'Product Orderer',
approver: 'Approver',
date: 'Date',
receiveTaskOrder: 'Receive Task Order', receiveTaskOrder: 'Receive Task Order',
tasktobeDone: 'Task to be Done', tasktobeDone: 'Task to be Done',
@ -961,7 +977,7 @@ export default {
Validate: 'Validate', Validate: 'Validate',
Complete: 'Complete', Complete: 'Complete',
Canceled: 'Canceled', Canceled: 'Canceled',
Restart: 'go proceed again', Restart: 'Retry',
receive: { receive: {
Canceled: 'Canceled', Canceled: 'Canceled',
}, },
@ -1099,9 +1115,10 @@ export default {
authFailed: 'Authentication Failed. Please try again later. ', authFailed: 'Authentication Failed. Please try again later. ',
installmentsValidateFailed: installmentsValidateFailed:
'Validation failed. Each installment must include at least one product. Please review and update the installments accordingly.', 'Validation failed. Each installment must include at least one product. Please review and update the installments accordingly.',
flowTemplateNotFound: 'Workflow template cannot be found', flowTemplateNotFound: 'Workflow template cannot be found.',
taskOrderNotFound: 'Task order cannot be found.', taskOrderNotFound: 'Task order cannot be found.',
quotationNotFound: 'Quotation cannot be found.', quotationNotFound: 'Quotation cannot be found.',
sameNameExists: 'Same name exists.',
}, },
}, },

View file

@ -725,6 +725,16 @@ export default {
}, },
quotation: { quotation: {
quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่',
bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม',
productOrderer: 'ผู้สั่งซื้อสินค้า',
approver: 'ผู้อนุมัติ',
date: 'วันที่',
tableColumnsRequest: { tableColumnsRequest: {
code: 'รหัสใบรายการคำขอ', code: 'รหัสใบรายการคำขอ',
}, },
@ -736,7 +746,7 @@ export default {
title: 'ใบเสนอราคา', title: 'ใบเสนอราคา',
caption: 'ใบเสนอราคาทั้งหมด', caption: 'ใบเสนอราคาทั้งหมด',
customerName: 'ชื่อลูกค้า', customerName: 'ชื่อลูกค้า',
actor: 'ผู้ที่ทำรายงาน', actor: 'ผู้ที่ทำรายการ',
totalPrice: 'ยอดรวมสุทธิ', totalPrice: 'ยอดรวมสุทธิ',
totalPriceBaht: 'ยอดรวมสุทธิ (บาท)', totalPriceBaht: 'ยอดรวมสุทธิ (บาท)',
receipt: 'ใบเสร็จ/กำกับภาษี', receipt: 'ใบเสร็จ/กำกับภาษี',
@ -919,6 +929,12 @@ export default {
receive: 'ใบรับงาน', receive: 'ใบรับงาน',
caption: 'ใบสั่งงานทั้งหมด', caption: 'ใบสั่งงานทั้งหมด',
code: 'เลขใบสั่งงาน', code: 'เลขใบสั่งงาน',
workName: 'ชื่อใบงาน',
contacts: 'ผู้ติดต่อ',
inTheNameOf: 'ในนาม',
productOrderer: 'ผู้สั่งซื้อสินค้า',
approver: 'ผู้อนุมัติ',
date: 'วันที่',
receiveTaskOrder: 'รับใบสั่งงาน', receiveTaskOrder: 'รับใบสั่งงาน',
tasktobeDone: 'งานที่ต้องทำ', tasktobeDone: 'งานที่ต้องทำ',
@ -946,11 +962,11 @@ export default {
InProgress: 'กำลังดำเนินการ ', InProgress: 'กำลังดำเนินการ ',
Success: 'ดำเนินการสำเร็จ', Success: 'ดำเนินการสำเร็จ',
Failed: 'ดำเนินการไม่สำเร็จ', Failed: 'ดำเนินการไม่สำเร็จ',
Redo: 'ยกเลิกรอดำเนินการใหม่', Redo: 'ยกเลิกเพื่อสร้างใบใหม่',
Validate: 'ตรวจสอบความถูกต้อง', Validate: 'ตรวจสอบความถูกต้อง',
Complete: 'ดำเนินการเสร็จสิ้น', Complete: 'ดำเนินการเสร็จสิ้น',
Canceled: 'รายการคำขอถูกยกเลิก', Canceled: 'รายการคำขอถูกยกเลิก',
Restart: 'ไปดำเนินการใหม่', Restart: 'ดำเนินการใหม่',
receive: { receive: {
Canceled: 'ยกเลิก', Canceled: 'ยกเลิก',
}, },
@ -1083,6 +1099,7 @@ export default {
flowTemplateNotFound: 'ไม่พบขั้นตอนการทำงาน', flowTemplateNotFound: 'ไม่พบขั้นตอนการทำงาน',
taskOrderNotFound: 'ไม่พบใบสั่งงาน', taskOrderNotFound: 'ไม่พบใบสั่งงาน',
quotationNotFound: 'ไม่พบใบเสนอราคา', quotationNotFound: 'ไม่พบใบเสนอราคา',
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
}, },
}, },

View file

@ -577,7 +577,7 @@ function employeeConfirmUnsave(close = true) {
color: 'warning', color: 'warning',
icon: 'mdi-alert', icon: 'mdi-alert',
title: t('form.warning.title'), title: t('form.warning.title'),
actionText: t('ok'), actionText: t('general.ok'),
persistent: true, persistent: true,
message: t('form.warning.unsave'), message: t('form.warning.unsave'),
action: () => { action: () => {
@ -604,7 +604,7 @@ function customerConfirmUnsave(close = true) {
color: 'warning', color: 'warning',
icon: 'mdi-alert', icon: 'mdi-alert',
title: t('form.warning.title'), title: t('form.warning.title'),
actionText: t('ok'), actionText: t('general.ok'),
persistent: true, persistent: true,
message: t('form.warning.unsave'), message: t('form.warning.unsave'),
@ -2196,7 +2196,7 @@ const emptyCreateDialog = ref(false);
color: 'warning', color: 'warning',
icon: 'mdi-alert', icon: 'mdi-alert',
title: t('dialog.title.incompleteDataEntry'), title: t('dialog.title.incompleteDataEntry'),
actionText: t('ok'), actionText: t('general.ok'),
persistent: true, persistent: true,
message: t('dialog.message.incompleteDataEntry', { message: t('dialog.message.incompleteDataEntry', {
tap: `${tapIsUndefined.map((v: string) => t(`customerBranch.tab.${v}`)).join(', ')}`, tap: `${tapIsUndefined.map((v: string) => t(`customerBranch.tab.${v}`)).join(', ')}`,
@ -2419,26 +2419,26 @@ const emptyCreateDialog = ref(false);
[ [
{ {
name: 'personalInfo', name: 'personalInfo',
label: 'customerEmployee.form.group.personalInfo', label: $t('customerEmployee.form.group.personalInfo'),
}, },
{ {
name: 'passport', name: 'passport',
label: 'customerEmployee.fileType.passport', label: $t('customerEmployee.fileType.passport'),
}, },
{ {
name: 'visa', name: 'visa',
label: 'customerEmployee.form.group.visa', label: $t('customerEmployee.form.group.visa'),
}, },
{ {
name: 'healthCheck', name: 'healthCheck',
label: 'customerEmployee.form.group.healthCheck', label: $t('customerEmployee.form.group.healthCheck'),
}, },
{ {
name: 'workHistory', name: 'workHistory',
label: 'customerEmployee.form.group.workHistory', label: $t('customerEmployee.form.group.workHistory'),
}, },
{ name: 'other', label: 'customerEmployee.form.group.other' }, { name: 'other', label: $t('customerEmployee.form.group.other') },
].filter((v) => { ].filter((v) => {
if (!employeeFormState.statusSavePersonal) { if (!employeeFormState.statusSavePersonal) {
return v.name === 'personalInfo'; return v.name === 'personalInfo';
@ -4088,7 +4088,7 @@ const emptyCreateDialog = ref(false);
color: 'warning', color: 'warning',
icon: 'mdi-alert', icon: 'mdi-alert',
title: t('dialog.title.incompleteDataEntry'), title: t('dialog.title.incompleteDataEntry'),
actionText: t('ok'), actionText: t('general.ok'),
persistent: true, persistent: true,
message: t('dialog.message.incompleteDataEntry', { message: t('dialog.message.incompleteDataEntry', {
tap: `${tapIsUndefined.map((v: string) => t(`customerBranch.tab.${v}`)).join(', ')}`, tap: `${tapIsUndefined.map((v: string) => t(`customerBranch.tab.${v}`)).join(', ')}`,

View file

@ -548,7 +548,7 @@ watch([() => pageState.inputSearch, workflowPageSize], fetchWorkflowList);
<!-- {{ (currentPage - 1) * pageSize + props.rowIndex + 1 }} --> <!-- {{ (currentPage - 1) * pageSize + props.rowIndex + 1 }} -->
</q-td> </q-td>
<q-td v-if="fieldSelected.includes('name')"> <q-td v-if="fieldSelected.includes('name')">
<section class="row items-center"> <section class="row items-center no-wrap">
<q-avatar <q-avatar
class="q-mr-sm" class="q-mr-sm"
size="md" size="md"

View file

@ -38,7 +38,7 @@ watch(
<template> <template>
<fieldset class="rounded" style="border: 1px solid var(--gray-4)"> <fieldset class="rounded" style="border: 1px solid var(--gray-4)">
<legend>องทางท {{ index + 1 }}</legend> <legend>{{ $t('quotation.channelsThat') }} {{ index + 1 }}</legend>
<div class="border-5 full-width row" style="gap: var(--size-4)"> <div class="border-5 full-width row" style="gap: var(--size-4)">
<div class="column q-pa-sm" style="width: fit-content"> <div class="column q-pa-sm" style="width: fit-content">
@ -56,7 +56,7 @@ watch(
<div class="column col" style="gap: var(--size-3)"> <div class="column col" style="gap: var(--size-3)">
<div class="row" style="justify-content: space-between"> <div class="row" style="justify-content: space-between">
<div class="text-with-label"> <div class="text-with-label">
<div>เลขบญชธนาคาร</div> <div>{{ $t('quotation.bankAccountNumber') }}</div>
<div class="row items-start"> <div class="row items-start">
<img <img
width="25px" width="25px"
@ -68,17 +68,17 @@ watch(
</div> </div>
</div> </div>
<div class="text-with-label"> <div class="text-with-label">
<div>เลขบญชธนาคาร</div> <div>{{ $t('quotation.bankAccountNumber') }}</div>
<div>{{ bankBook.accountNumber }}</div> <div>{{ bankBook.accountNumber }}</div>
</div> </div>
<div class="text-with-label"> <div class="text-with-label">
<div>อบญช</div> <div>{{ $t('quotation.bankAccountName') }}</div>
<div>{{ bankBook.accountName }}</div> <div>{{ bankBook.accountName }}</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="text-with-label"> <div class="text-with-label">
<div>สาขา</div> <div>{{ $t('customerEmployee.branch') }}</div>
<div>{{ bankBook.bankBranch }}</div> <div>{{ bankBook.bankBranch }}</div>
</div> </div>
</div> </div>

View file

@ -14,8 +14,8 @@ defineProps<{
<template> <template>
<div class="footer-container"> <div class="footer-container">
<div class="footer-top"> <div class="footer-top">
<div>ในนาม {{ data?.name || '-' }}</div> <div>{{ $t('quotation.inTheNameOf') }} {{ data?.name || '-' }}</div>
<div>ในนาม {{ data?.company || '-' }}</div> <div>{{ $t('quotation.inTheNameOf') }} {{ data?.company || '-' }}</div>
</div> </div>
<img src="/images/jws-stamp.png" alt="${0}" /> <img src="/images/jws-stamp.png" alt="${0}" />
@ -24,21 +24,21 @@ defineProps<{
<section> <section>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>งซอสนค</span> <span>{{ $t('quotation.productOrderer') }}</span>
</div> </div>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>นท</span> <span>{{ $t('quotation.date') }}</span>
</div> </div>
</section> </section>
<section> <section>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>อน</span> <span>{{ $t('quotation.approver') }}</span>
</div> </div>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>นท</span> <span>{{ $t('quotation.date') }}</span>
</div> </div>
</section> </section>
</div> </div>

View file

@ -442,7 +442,9 @@ function print() {
class="row text-right border-5 items-center" class="row text-right border-5 items-center"
style="width: 40%; background: var(--main); padding: 8px" style="width: 40%; background: var(--main); padding: 8px"
> >
<span style="color: white; font-weight: 600">ยอดรวมสทธ</span> <span style="color: white; font-weight: 600">
{{ $t('quotation.totalPrice') }}
</span>
<span <span
class="border-5" class="border-5"
style=" style="
@ -480,7 +482,7 @@ function print() {
border-bottom: 2px solid var(--main); border-bottom: 2px solid var(--main);
" "
> >
หมายเหต {{ $t('general.remark') }}
</span> </span>
<div <div
@ -528,7 +530,7 @@ function print() {
border-bottom: 2px solid var(--main); border-bottom: 2px solid var(--main);
" "
> >
องทางซำระเง {{ $t('quotation.paymentChannels') }}
</span> </span>
<article style="height: 5.8in"> <article style="height: 5.8in">
<BankComponents <BankComponents

View file

@ -83,7 +83,7 @@ function titleMode(mode: View): string {
<span>{{ branch.webUrl }}</span> <span>{{ branch.webUrl }}</span>
</article> </article>
<article> <article>
<b>กค</b> <b>{{ $t('quotation.customer') }}</b>
<span> <span>
{{ {{
formatAddress({ formatAddress({
@ -111,29 +111,29 @@ function titleMode(mode: View): string {
<div>{{ details.code }}</div> <div>{{ details.code }}</div>
</div> </div>
<div> <div>
<div>นทใบเสนอราคา</div> <div>{{ $t('quotation.quotationDate') }}</div>
<div>{{ dateFormat(details.createdAt, true, false, true) }}</div> <div>{{ dateFormat(details.createdAt, true, false, true) }}</div>
</div> </div>
<div> <div>
<div>ขาย</div> <div>{{ $t('quotation.seller') }}</div>
<div>{{ details.createdBy }}</div> <div>{{ details.createdBy }}</div>
</div> </div>
<div> <div>
<div>เงอนไขการชำระ</div> <div>{{ $t('quotation.paymentCondition') }}</div>
<div> <div>
{{ $t(`quotation.type.${details.payCondition}`) }} {{ $t(`quotation.type.${details.payCondition}`) }}
</div> </div>
</div> </div>
<div> <div>
<div>องาน</div> <div>{{ $t('quotation.workName') }}</div>
<div>{{ details.workName }}</div> <div>{{ details.workName }}</div>
</div> </div>
<div> <div>
<div>ดต</div> <div>{{ $t('quotation.contactName') }}</div>
<div>{{ details.contactName }}</div> <div>{{ details.contactName }}</div>
</div> </div>
<div> <div>
<div>นครบกำหนดชำระ</div> <div>{{ $t('quotation.receiptDialog.paymentDueDate') }}</div>
<div>{{ dateFormat(details.dueDate, true, false, true) }}</div> <div>{{ dateFormat(details.dueDate, true, false, true) }}</div>
</div> </div>
</section> </section>

View file

@ -19,7 +19,6 @@ import FloatingActionButton from 'src/components/FloatingActionButton.vue';
import CreateButton from 'src/components/AddButton.vue'; import CreateButton from 'src/components/AddButton.vue';
import NoData from 'src/components/NoData.vue'; import NoData from 'src/components/NoData.vue';
import AgenciesDialog from './AgenciesDialog.vue'; import AgenciesDialog from './AgenciesDialog.vue';
import { computed } from 'vue';
import { watch } from 'vue'; import { watch } from 'vue';
const { t } = useI18n(); const { t } = useI18n();
@ -28,7 +27,6 @@ const institutionStore = useInstitution();
const { data, page, pageMax, pageSize } = storeToRefs(institutionStore); const { data, page, pageMax, pageSize } = storeToRefs(institutionStore);
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
const fieldSelected = ref<('orderNumber' | 'name' | 'address')[]>([ const fieldSelected = ref<('orderNumber' | 'name' | 'address')[]>([
'orderNumber', 'orderNumber',
'name', 'name',

View file

@ -15,7 +15,12 @@ import DutyExpansion from './DutyExpansion.vue';
import MessengerExpansion from './MessengerExpansion.vue'; import MessengerExpansion from './MessengerExpansion.vue';
// NOTE: Store // NOTE: Store
import { baseUrl, dialog } from 'src/stores/utils'; import {
baseUrl,
dialog,
getEmployeeName,
getCustomerName,
} from 'src/stores/utils';
import { dateFormatJS } from 'src/utils/datetime'; import { dateFormatJS } from 'src/utils/datetime';
import { useRequestList } from 'src/stores/request-list'; import { useRequestList } from 'src/stores/request-list';
import { import {
@ -79,47 +84,6 @@ async function fetchRequestWorkList(opts: { requestDataId: string }) {
} }
} }
function getCustomerName(
record: RequestData,
opts?: {
locale?: string;
noCode?: boolean;
},
) {
const customer = record.quotation.customerBranch;
return (
{
['CORP']: {
[Lang.English]: customer.registerNameEN,
[Lang.Thai]: customer.registerName,
}[opts?.locale || 'eng'],
['PERS']:
{
[Lang.English]: `${optionStore.mapOption(customer.namePrefix)} ${customer.firstNameEN} ${customer.lastNameEN}`,
[Lang.Thai]: `${optionStore.mapOption(customer.namePrefix)} ${customer.firstName} ${customer.lastName}`,
}[opts?.locale || Lang.English] || '-',
}[customer.customer.customerType] +
(opts?.noCode ? '' : ' ' + `(${customer.code})`)
);
}
function getEmployeeName(
record: RequestData,
opts?: {
locale?: string;
},
) {
const employee = record.employee;
return (
{
[Lang.English]: `${optionStore.mapOption(employee.namePrefix)} ${employee.firstNameEN} ${employee.lastNameEN}`,
[Lang.Thai]: `${optionStore.mapOption(employee.namePrefix)} ${employee.firstName} ${employee.lastName}`,
}[opts?.locale || Lang.English] || '-'
);
}
async function getData() { async function getData() {
const current = route.params['requestListId']; const current = route.params['requestListId'];
@ -611,8 +575,10 @@ function goToQuotation(
icon="mdi-account-settings-outline" icon="mdi-account-settings-outline"
:label="$t('customer.employer')" :label="$t('customer.employer')"
:value=" :value="
getCustomerName(data, { locale: locale, noCode: true }) || getCustomerName(data.quotation.customerBranch, {
'-' locale: locale,
noCode: true,
}) || '-'
" "
/> />
<DataDisplay <DataDisplay
@ -620,7 +586,9 @@ function goToQuotation(
icon="mdi-account-settings-outline" icon="mdi-account-settings-outline"
:label="$t('customer.employee')" :label="$t('customer.employee')"
:value=" :value="
getEmployeeName(data, { locale: $i18n.locale }) || '-' getEmployeeName(data.employee, {
locale: $i18n.locale,
}) || '-'
" "
/> />
<DataDisplay <DataDisplay
@ -631,6 +599,40 @@ function goToQuotation(
/> />
<div v-if="$q.screen.gt.sm" class="col"></div> <div v-if="$q.screen.gt.sm" class="col"></div>
</div> </div>
<FormGroupHead class="col-12">
{{ $t('general.contactName') }}
</FormGroupHead>
<div
class="col-12 q-pa-sm"
:class="{
row: $q.screen.gt.sm,
'column q-gutter-y-sm': $q.screen.lt.md,
}"
>
<DataDisplay
class="col"
icon="mdi-account-settings-outline"
:label="$t('quotation.actor')"
:value="
getEmployeeName(data.quotation?.createdBy, {
locale: $i18n.locale,
}) || '-'
"
/>
<DataDisplay
class="col"
icon="mdi-account-settings-outline"
:label="$t('quotation.contactName')"
:value="data.quotation?.contactName || '-'"
/>
<DataDisplay
class="col"
icon="mdi-telephone-outline"
:label="$t('general.telephone')"
:value="data.quotation.contactTel || '-'"
/>
<div v-if="$q.screen.gt.sm" class="col"></div>
</div>
</section> </section>
</transition> </transition>
</article> </article>

View file

@ -34,8 +34,7 @@ const currStatus = computed(() =>
statusList.find((v) => v.value === props.status), statusList.find((v) => v.value === props.status),
); );
function hideIcon() { function inactiveCheck() {
if (props.noAction) return props.noAction;
if (props.type === 'order') { if (props.type === 'order') {
return ( return (
currStatus.value?.value === TaskStatus.InProgress || currStatus.value?.value === TaskStatus.InProgress ||
@ -81,7 +80,8 @@ function hideIcon() {
" "
class="text-capitalize text-weight-regular product-status rounded" class="text-capitalize text-weight-regular product-status rounded"
:class="{ :class="{
'hide-icon q-pr-md': hideIcon(), 'inactive hide-icon q-pr-md': inactiveCheck(),
'hide-icon q-pr-md': noAction,
warning: warning:
currStatus?.value === TaskStatus.Pending || currStatus?.value === TaskStatus.Pending ||
currStatus?.value === TaskStatus.InProgress, currStatus?.value === TaskStatus.InProgress,
@ -95,7 +95,9 @@ function hideIcon() {
currStatus?.value === TaskStatus.Canceled || currStatus?.value === TaskStatus.Canceled ||
currStatus?.value === TaskStatus.Restart, currStatus?.value === TaskStatus.Restart,
'pointer-events-none': { 'pointer-events-none': {
order: !['Success', 'Failed', 'Validate'].includes(status || ''), order: !['Success', 'Failed', 'Validate', 'Redo'].includes(
status || '',
),
receive: status !== TaskStatus.InProgress, receive: status !== TaskStatus.InProgress,
}[type ?? 'order'], }[type ?? 'order'],
}" }"
@ -171,6 +173,11 @@ function hideIcon() {
&.negative { &.negative {
--_color: var(--negative-bg); --_color: var(--negative-bg);
} }
&.inactive {
--_color: var(--stone-7-hsl);
opacity: 0.8;
cursor: default;
}
} }
.pointer-events-none { .pointer-events-none {

View file

@ -81,7 +81,7 @@ export const taskStatusOrderToggle = [
{ {
value: TaskStatus.InProgress, value: TaskStatus.InProgress,
icon: 'mdi-file-move-outline', icon: 'mdi-file-move-outline',
color: 'positive', color: 'warning',
}, },
{ {
value: TaskStatus.Complete, value: TaskStatus.Complete,

View file

@ -380,7 +380,9 @@ function print() {
class="row text-right border-5 items-center" class="row text-right border-5 items-center"
style="width: 40%; background: var(--main); padding: 8px" style="width: 40%; background: var(--main); padding: 8px"
> >
<span style="color: white; font-weight: 600">ยอดรวมสทธ</span> <span style="color: white; font-weight: 600">
{{ $t('quotation.totalPrice') }}
</span>
<span <span
class="border-5" class="border-5"
style=" style="

View file

@ -14,8 +14,8 @@ defineProps<{
<template> <template>
<div class="footer-container"> <div class="footer-container">
<div class="footer-top"> <div class="footer-top">
<div>ในนาม {{ data?.name || '-' }}</div> <div>{{ $t('taskOrder.inTheNameOf') }} {{ data?.name || '-' }}</div>
<div>ในนาม {{ data?.company || '-' }}</div> <div>{{ $t('taskOrder.inTheNameOf') }} {{ data?.company || '-' }}</div>
</div> </div>
<img src="/images/jws-stamp.png" alt="${0}" /> <img src="/images/jws-stamp.png" alt="${0}" />
@ -24,21 +24,21 @@ defineProps<{
<section> <section>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>งซอสนค</span> <span>{{ $t('taskOrder.productOrderer') }}</span>
</div> </div>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>นท</span> <span>{{ $t('taskOrder.date') }}</span>
</div> </div>
</section> </section>
<section> <section>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>อน</span> <span>{{ $t('taskOrder.approver') }}</span>
</div> </div>
<div> <div>
<span class="data-placeholder"></span> <span class="data-placeholder"></span>
<span>นท</span> <span>{{ $t('taskOrder.date') }}</span>
</div> </div>
</section> </section>
</div> </div>

View file

@ -101,11 +101,11 @@ defineProps<{
<div>{{ details.code }}</div> <div>{{ details.code }}</div>
</div> </div>
<div> <div>
<div>องาน</div> <div>{{ $t('taskOrder.workName') }}</div>
<div>{{ details.name }}</div> <div>{{ details.name }}</div>
</div> </div>
<div> <div>
<div>ดต</div> <div>{{ $t('taskOrder.contacts') }}</div>
<div>{{ details.contactName }}</div> <div>{{ details.contactName }}</div>
</div> </div>
</section> </section>

View file

@ -42,6 +42,7 @@ const contactTel = defineModel<string>('contactTel');
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
/> />
<SelectInstitution <SelectInstitution
:readonly :readonly
@ -51,6 +52,7 @@ const contactTel = defineModel<string>('contactTel');
v-model:value="institutionId" v-model:value="institutionId"
:disabled="taskListGroup" :disabled="taskListGroup"
:params="{ payload: { group: institutionGroup } }" :params="{ payload: { group: institutionGroup } }"
auto-select-on-single
/> />
<DatePicker <DatePicker
:label="$t('taskOrder.issueDate')" :label="$t('taskOrder.issueDate')"

View file

@ -19,8 +19,6 @@ import { SaveButton, MainButton, EditButton } from 'src/components/button';
import FormGroupHead from 'src/pages/08_request-list/FormGroupHead.vue'; import FormGroupHead from 'src/pages/08_request-list/FormGroupHead.vue';
import FailRemarkDialog from '../receive_view/FailRemarkDialog.vue'; import FailRemarkDialog from '../receive_view/FailRemarkDialog.vue';
import { taskStatusOpts } from '../constants';
import SelectReadyRequestWork from '../SelectReadyRequestWork.vue'; import SelectReadyRequestWork from '../SelectReadyRequestWork.vue';
import { dialogWarningClose } from 'stores/utils'; import { dialogWarningClose } from 'stores/utils';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
@ -37,15 +35,12 @@ import {
UserTaskStatus, UserTaskStatus,
} from 'src/stores/task-order/types'; } from 'src/stores/task-order/types';
import { RequestWork } from 'src/stores/request-list'; import { RequestWork } from 'src/stores/request-list';
import { precisionRound } from 'src/utils/arithmetic';
import { getUserId } from 'src/services/keycloak';
const taskOrderFormStore = useTaskOrderForm(); const taskOrderFormStore = useTaskOrderForm();
const taskOrderStore = useTaskOrderStore(); const taskOrderStore = useTaskOrderStore();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const configStore = useConfigStore(); const configStore = useConfigStore();
const optionStore = useOptionStore();
const { data: config } = storeToRefs(configStore); const { data: config } = storeToRefs(configStore);
const { t } = useI18n(); const { t } = useI18n();
@ -59,6 +54,7 @@ const fileData = ref<
loaded: number; loaded: number;
total: number; total: number;
url?: string; url?: string;
placeholder?: boolean;
}[] }[]
>([]); >([]);
@ -397,6 +393,11 @@ async function submitForm() {
}); });
if (res && currentFormData.value.id) { if (res && currentFormData.value.id) {
await taskOrderFormStore.assignFormData(currentFormData.value.id); await taskOrderFormStore.assignFormData(currentFormData.value.id);
if (fileList.value) {
await uploadFile(currentFormData.value.id, fileList.value);
}
router.push({ router.push({
name: 'TaskOrderView', name: 'TaskOrderView',
params: { id: currentFormData.value.id }, params: { id: currentFormData.value.id },
@ -445,25 +446,29 @@ async function getFileList(taskId: string) {
if (fileList) if (fileList)
fileData.value = await Promise.all( fileData.value = await Promise.all(
fileList.map(async (v) => { fileList.map(async (v) => {
const rse = await taskOrderStore.headAttachment({ const res = await taskOrderStore.headAttachment({
parentId: taskId, parentId: taskId,
fileId: v, fileId: v,
}); });
let contentLength = 0; let contentLength = 0;
if (rse) contentLength = Number(rse['content-length']); if (res) contentLength = Number(res['content-length']);
return { return {
name: v, name: v,
progress: 1, progress: 1,
loaded: contentLength, loaded: contentLength,
total: contentLength, total: contentLength,
url: `/task/${taskId}/attachment/${v}`, url: `/task-order/${taskId}/attachment/${v}`,
}; };
}), }),
); );
} }
function fileToUrl(file: File) {
return URL.createObjectURL(file);
}
async function remove(taskId: string, n: string) { async function remove(taskId: string, n: string) {
dialogWarningClose(t, { dialogWarningClose(t, {
message: t('dialog.message.confirmDelete'), message: t('dialog.message.confirmDelete'),
@ -508,8 +513,18 @@ async function uploadFile(taskId: string, list: FileList) {
); );
fileData?.value.push(data); fileData?.value.push(data);
} }
fileList.value = undefined;
return await Promise.all(promises); const beforeUnloadHandler = (e: Event) => {
e.preventDefault();
};
window.addEventListener('beforeunload', beforeUnloadHandler);
return await Promise.all(promises).then((v) => {
window.removeEventListener('beforeunload', beforeUnloadHandler);
return v;
});
} }
async function completeValidate() { async function completeValidate() {
@ -642,6 +657,33 @@ function handleChangeStatus(
}); });
} }
function sortList(
list: (RequestWork & {
_template?: {
id: string;
templateName: string;
templateStepName: string;
step: number;
responsibleInstitution: (string | { group: string })[];
} | null;
taskStatus: TaskStatus;
})[],
) {
const prioritizedStatuses = new Set([
TaskStatus.InProgress,
TaskStatus.Redo,
TaskStatus.Success,
TaskStatus.Complete,
TaskStatus.Canceled,
]);
return list.sort((a, b) => {
const aPriority = prioritizedStatuses.has(a.taskStatus) ? 1 : 0;
const bPriority = prioritizedStatuses.has(b.taskStatus) ? 1 : 0;
return aPriority - bPriority;
});
}
watch([currentFormData.value.taskStatus], () => { watch([currentFormData.value.taskStatus], () => {
fetchStatus(); fetchStatus();
}); });
@ -821,9 +863,13 @@ watch([currentFormData.value.taskStatus], () => {
v-model:file-data="fileData" v-model:file-data="fileData"
:transform-url=" :transform-url="
async (url: string) => { async (url: string) => {
if (state.mode === 'create') {
return url;
} else {
const result = await api.get<string>(url); const result = await api.get<string>(url);
return result.data; return result.data;
} }
}
" "
@fetch-file-list=" @fetch-file-list="
() => { () => {
@ -833,9 +879,21 @@ watch([currentFormData.value.taskStatus], () => {
" "
@upload=" @upload="
async (f) => { async (f) => {
if (!currentFormData.id) return;
fileList = f; fileList = f;
fileData = [];
Array.from(f).forEach((el) => {
fileData.push({
name: el.name,
progress: 1,
loaded: 0,
total: el.size,
placeholder: true,
url: fileToUrl(el),
});
});
if (!currentFormData.id) return;
await uploadFile(currentFormData.id, f); await uploadFile(currentFormData.id, f);
} }
@ -973,7 +1031,7 @@ watch([currentFormData.value.taskStatus], () => {
(l) => l.userId === v.responsibleUser.id, (l) => l.userId === v.responsibleUser.id,
)?.userTaskStatus === UserTaskStatus.Submit )?.userTaskStatus === UserTaskStatus.Submit
" "
:rows="list" :rows="sortList(list)"
@change-all-status=" @change-all-status="
(v) => (v) =>
handleChangeStatus( handleChangeStatus(
@ -997,7 +1055,6 @@ watch([currentFormData.value.taskStatus], () => {
fullTaskOrder?.userTask.find( fullTaskOrder?.userTask.find(
(l) => l.userId === v.responsibleUser.id, (l) => l.userId === v.responsibleUser.id,
)?.userTaskStatus; )?.userTaskStatus;
console.log(_userStatus);
return ( return (
_userStatus !== UserTaskStatus.Submit && _userStatus !== UserTaskStatus.Submit &&
_userStatus !== UserTaskStatus.Restart _userStatus !== UserTaskStatus.Restart

View file

@ -313,6 +313,35 @@ function taskStatusCount(index: number, id: string) {
} }
} }
function sortList(
list: (RequestWork & {
_template?: {
id: string;
templateName: string;
templateStepName: string;
step: number;
} | null;
taskStatus?: TaskStatus;
})[],
) {
const prioritizedStatuses = new Set([
TaskStatus.Failed,
TaskStatus.Success,
TaskStatus.Complete,
TaskStatus.Redo,
TaskStatus.Validate,
TaskStatus.Canceled,
]);
return list.sort((a, b) => {
const aPriority =
a.taskStatus && prioritizedStatuses.has(a.taskStatus) ? 1 : 0;
const bPriority =
b.taskStatus && prioritizedStatuses.has(b.taskStatus) ? 1 : 0;
return aPriority - bPriority;
});
}
onMounted(async () => { onMounted(async () => {
initTheme(); initTheme();
initLang(); initLang();
@ -607,7 +636,7 @@ watch([currentFormData.value.taskStatus], () => {
) )
" "
step-on step-on
:rows="list" :rows="sortList(list)"
@change-all-status="(v) => handleChangeStatus(v, i)" @change-all-status="(v) => handleChangeStatus(v, i)"
v-model:selected-employee="selectedEmployee[i]" v-model:selected-employee="selectedEmployee[i]"
> >

View file

@ -53,6 +53,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
: pageState.statusFilter === PaymentDataStatus.Wait : pageState.statusFilter === PaymentDataStatus.Wait
? false ? false
: undefined, : undefined,
quotationOnly: true,
debitNoteOnly: false,
}); });
if (ret) { if (ret) {
data.value = ret.result; data.value = ret.result;
@ -64,7 +66,10 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
} }
async function fetchStats() { async function fetchStats() {
const ret = await invoiceStore.getInvoiceStats(); const ret = await invoiceStore.getInvoiceStats({
quotationOnly: true,
debitNoteOnly: false,
});
if (ret) stats.value = ret; if (ret) stats.value = ret;
} }

View file

@ -490,6 +490,10 @@ async function getFileList(creditNoteId: string, slip?: boolean) {
)); ));
} }
function fileToUrl(file: File) {
return URL.createObjectURL(file);
}
onMounted(async () => { onMounted(async () => {
initTheme(); initTheme();
initLang(); initLang();
@ -653,9 +657,13 @@ onMounted(async () => {
v-model:file-data="attachmentData" v-model:file-data="attachmentData"
:transform-url=" :transform-url="
async (url: string) => { async (url: string) => {
if (!creditNoteData?.id) {
return url;
} else {
const result = await api.get<string>(url); const result = await api.get<string>(url);
return result.data; return result.data;
} }
}
" "
@fetch-file-list=" @fetch-file-list="
() => { () => {
@ -675,6 +683,7 @@ onMounted(async () => {
loaded: 0, loaded: 0,
total: el.size, total: el.size,
placeholder: true, placeholder: true,
url: fileToUrl(el),
}); });
}); });

View file

@ -179,13 +179,13 @@ watch(
icon: 'material-symbols-light:receipt-long', icon: 'material-symbols-light:receipt-long',
count: stats[CreditNoteStatus.Pending], count: stats[CreditNoteStatus.Pending],
label: `creditNote.stats.${CreditNoteStatus.Pending}`, label: `creditNote.stats.${CreditNoteStatus.Pending}`,
color: 'blue', color: 'orange',
}, },
{ {
icon: 'mdi-check-decagram-outline', icon: 'mdi-check-decagram-outline',
count: stats[CreditNoteStatus.Success], count: stats[CreditNoteStatus.Success],
label: `creditNote.stats.${CreditNoteStatus.Success}`, label: `creditNote.stats.${CreditNoteStatus.Success}`,
color: 'orange', color: 'blue',
}, },
]" ]"
:dark="$q.dark.isActive" :dark="$q.dark.isActive"

View file

@ -73,6 +73,6 @@ export const columns = [
] as const satisfies QTableProps['columns']; ] as const satisfies QTableProps['columns'];
export const hslaColors: Record<string, string> = { export const hslaColors: Record<string, string> = {
Pending: '--blue-6-hsl', Pending: '--orange-5-hsl',
Success: '--red-6-hsl', Success: '--blue-6-hsl',
}; };

View file

@ -128,7 +128,12 @@ export const useInvoice = defineStore('invoice-store', () => {
[PaymentDataStatus.Wait]: 0, [PaymentDataStatus.Wait]: 0,
}); });
async function getInvoiceStats(params?: { quotationId?: string }) { async function getInvoiceStats(params?: {
quotationId?: string;
quotationOnly?: boolean;
debitNoteId?: string;
debitNoteOnly?: boolean;
}) {
const res = await api.get<Record<PaymentDataStatus, number>>( const res = await api.get<Record<PaymentDataStatus, number>>(
'/invoice/stats', '/invoice/stats',
{ params }, { params },
@ -149,8 +154,11 @@ export const useInvoice = defineStore('invoice-store', () => {
page?: number; page?: number;
pageSize?: number; pageSize?: number;
query?: string; query?: string;
quotationId?: string;
pay?: boolean; pay?: boolean;
quotationOnly?: boolean;
debitNoteOnly?: boolean;
quotationId?: string;
debitNoteId?: string;
}) { }) {
const res = await api.get<PaginationResult<Invoice>>('/invoice', { const res = await api.get<PaginationResult<Invoice>>('/invoice', {
params, params,

View file

@ -18,6 +18,7 @@ import useOptionStore from '../options';
import { CustomerBranch } from '../customer/types'; import { CustomerBranch } from '../customer/types';
import { CustomerBranchRelation } from '../quotations'; import { CustomerBranchRelation } from '../quotations';
import { Employee } from '../employee/types'; import { Employee } from '../employee/types';
import { CreatedBy } from '../types';
export const baseUrl = import.meta.env.VITE_API_BASE_URL; export const baseUrl = import.meta.env.VITE_API_BASE_URL;
@ -510,7 +511,7 @@ export async function getAttachmentHead(api: AxiosInstance, url: string) {
if (res) return res.headers; if (res) return res.headers;
} }
export function calculateDaysUntilExpire(expireDate: Date): number { export function calculateDaysUntilExpire(expireDate: Date | string): number {
const today = new Date(); const today = new Date();
const expire = new Date(expireDate); const expire = new Date(expireDate);
const diffInTime = expire.getTime() - today.getTime(); const diffInTime = expire.getTime() - today.getTime();
@ -596,7 +597,7 @@ export function getCustomerName(
} }
export function getEmployeeName( export function getEmployeeName(
record: Employee, record: Employee | CreatedBy,
opts?: { opts?: {
locale?: string; locale?: string;
}, },
@ -604,7 +605,7 @@ export function getEmployeeName(
const employee = record; const employee = record;
return { return {
['eng']: `${useOptionStore().mapOption(employee.namePrefix)} ${employee.firstNameEN} ${employee.lastNameEN}`, ['eng']: `${typeof employee.namePrefix === 'string' ? useOptionStore().mapOption(employee.namePrefix) : ''} ${employee.firstNameEN} ${employee.lastNameEN}`,
['tha']: `${useOptionStore().mapOption(employee.namePrefix)} ${employee.firstName} ${employee.lastName}`, ['tha']: `${typeof employee.namePrefix === 'string' ? useOptionStore().mapOption(employee.namePrefix) : ''} ${employee.firstName} ${employee.lastName}`,
}[opts?.locale || 'eng']; }[opts?.locale || 'eng'];
} }