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

View file

@ -74,11 +74,6 @@ type Options = { label: string; value: string };
class="col"
:label="'Agencies Name'"
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>

View file

@ -54,8 +54,10 @@ onMounted(() => {
@click="$emit('click')"
>
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
<article class="col column q-pl-md ellipsis">
<span>{{ name }}</span>
<article class="col column q-pl-md">
<span class="ellipsis full-width">
{{ name }}
</span>
<span class="text-caption app-text-muted-2">
{{
uploading.loaded

View file

@ -11,6 +11,15 @@ import { dateFormat } from 'src/utils/datetime';
const isEdit = ref(false);
const allMeta = ref<Record<string, string>>();
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<
{
_meta?: Record<string, any>;

View file

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

View file

@ -731,6 +731,16 @@ export default {
},
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: {
code: 'Request List Code',
},
@ -928,6 +938,12 @@ export default {
receive: 'Job Receipt ',
caption: 'All Task Order',
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',
tasktobeDone: 'Task to be Done',
@ -961,7 +977,7 @@ export default {
Validate: 'Validate',
Complete: 'Complete',
Canceled: 'Canceled',
Restart: 'go proceed again',
Restart: 'Retry',
receive: {
Canceled: 'Canceled',
},
@ -1099,9 +1115,10 @@ export default {
authFailed: 'Authentication Failed. Please try again later. ',
installmentsValidateFailed:
'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.',
quotationNotFound: 'Quotation cannot be found.',
sameNameExists: 'Same name exists.',
},
},

View file

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

View file

@ -577,7 +577,7 @@ function employeeConfirmUnsave(close = true) {
color: 'warning',
icon: 'mdi-alert',
title: t('form.warning.title'),
actionText: t('ok'),
actionText: t('general.ok'),
persistent: true,
message: t('form.warning.unsave'),
action: () => {
@ -604,7 +604,7 @@ function customerConfirmUnsave(close = true) {
color: 'warning',
icon: 'mdi-alert',
title: t('form.warning.title'),
actionText: t('ok'),
actionText: t('general.ok'),
persistent: true,
message: t('form.warning.unsave'),
@ -2196,7 +2196,7 @@ const emptyCreateDialog = ref(false);
color: 'warning',
icon: 'mdi-alert',
title: t('dialog.title.incompleteDataEntry'),
actionText: t('ok'),
actionText: t('general.ok'),
persistent: true,
message: t('dialog.message.incompleteDataEntry', {
tap: `${tapIsUndefined.map((v: string) => t(`customerBranch.tab.${v}`)).join(', ')}`,
@ -2419,26 +2419,26 @@ const emptyCreateDialog = ref(false);
[
{
name: 'personalInfo',
label: 'customerEmployee.form.group.personalInfo',
label: $t('customerEmployee.form.group.personalInfo'),
},
{
name: 'passport',
label: 'customerEmployee.fileType.passport',
label: $t('customerEmployee.fileType.passport'),
},
{
name: 'visa',
label: 'customerEmployee.form.group.visa',
label: $t('customerEmployee.form.group.visa'),
},
{
name: 'healthCheck',
label: 'customerEmployee.form.group.healthCheck',
label: $t('customerEmployee.form.group.healthCheck'),
},
{
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) => {
if (!employeeFormState.statusSavePersonal) {
return v.name === 'personalInfo';
@ -4088,7 +4088,7 @@ const emptyCreateDialog = ref(false);
color: 'warning',
icon: 'mdi-alert',
title: t('dialog.title.incompleteDataEntry'),
actionText: t('ok'),
actionText: t('general.ok'),
persistent: true,
message: t('dialog.message.incompleteDataEntry', {
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 }} -->
</q-td>
<q-td v-if="fieldSelected.includes('name')">
<section class="row items-center">
<section class="row items-center no-wrap">
<q-avatar
class="q-mr-sm"
size="md"

View file

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

View file

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

View file

@ -442,7 +442,9 @@ function print() {
class="row text-right border-5 items-center"
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
class="border-5"
style="
@ -480,7 +482,7 @@ function print() {
border-bottom: 2px solid var(--main);
"
>
หมายเหต
{{ $t('general.remark') }}
</span>
<div
@ -528,7 +530,7 @@ function print() {
border-bottom: 2px solid var(--main);
"
>
องทางซำระเง
{{ $t('quotation.paymentChannels') }}
</span>
<article style="height: 5.8in">
<BankComponents

View file

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

View file

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

View file

@ -15,7 +15,12 @@ import DutyExpansion from './DutyExpansion.vue';
import MessengerExpansion from './MessengerExpansion.vue';
// 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 { useRequestList } from 'src/stores/request-list';
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() {
const current = route.params['requestListId'];
@ -611,8 +575,10 @@ function goToQuotation(
icon="mdi-account-settings-outline"
:label="$t('customer.employer')"
:value="
getCustomerName(data, { locale: locale, noCode: true }) ||
'-'
getCustomerName(data.quotation.customerBranch, {
locale: locale,
noCode: true,
}) || '-'
"
/>
<DataDisplay
@ -620,7 +586,9 @@ function goToQuotation(
icon="mdi-account-settings-outline"
:label="$t('customer.employee')"
:value="
getEmployeeName(data, { locale: $i18n.locale }) || '-'
getEmployeeName(data.employee, {
locale: $i18n.locale,
}) || '-'
"
/>
<DataDisplay
@ -631,6 +599,40 @@ function goToQuotation(
/>
<div v-if="$q.screen.gt.sm" class="col"></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>
</transition>
</article>

View file

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

View file

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

View file

@ -380,7 +380,9 @@ function print() {
class="row text-right border-5 items-center"
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
class="border-5"
style="

View file

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

View file

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

View file

@ -42,6 +42,7 @@ const contactTel = defineModel<string>('contactTel');
class="col-md-4 col-12"
:label="`${$t('taskOrder.issueBranch')}${$i18n.locale === 'tha' ? $t('taskOrder.title') : ''}`"
v-model:value="registeredBranchId"
auto-select-on-single
/>
<SelectInstitution
:readonly
@ -51,6 +52,7 @@ const contactTel = defineModel<string>('contactTel');
v-model:value="institutionId"
:disabled="taskListGroup"
:params="{ payload: { group: institutionGroup } }"
auto-select-on-single
/>
<DatePicker
: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 FailRemarkDialog from '../receive_view/FailRemarkDialog.vue';
import { taskStatusOpts } from '../constants';
import SelectReadyRequestWork from '../SelectReadyRequestWork.vue';
import { dialogWarningClose } from 'stores/utils';
import useOptionStore from 'src/stores/options';
@ -37,15 +35,12 @@ import {
UserTaskStatus,
} from 'src/stores/task-order/types';
import { RequestWork } from 'src/stores/request-list';
import { precisionRound } from 'src/utils/arithmetic';
import { getUserId } from 'src/services/keycloak';
const taskOrderFormStore = useTaskOrderForm();
const taskOrderStore = useTaskOrderStore();
const route = useRoute();
const router = useRouter();
const configStore = useConfigStore();
const optionStore = useOptionStore();
const { data: config } = storeToRefs(configStore);
const { t } = useI18n();
@ -59,6 +54,7 @@ const fileData = ref<
loaded: number;
total: number;
url?: string;
placeholder?: boolean;
}[]
>([]);
@ -397,6 +393,11 @@ async function submitForm() {
});
if (res && currentFormData.value.id) {
await taskOrderFormStore.assignFormData(currentFormData.value.id);
if (fileList.value) {
await uploadFile(currentFormData.value.id, fileList.value);
}
router.push({
name: 'TaskOrderView',
params: { id: currentFormData.value.id },
@ -445,25 +446,29 @@ async function getFileList(taskId: string) {
if (fileList)
fileData.value = await Promise.all(
fileList.map(async (v) => {
const rse = await taskOrderStore.headAttachment({
const res = await taskOrderStore.headAttachment({
parentId: taskId,
fileId: v,
});
let contentLength = 0;
if (rse) contentLength = Number(rse['content-length']);
if (res) contentLength = Number(res['content-length']);
return {
name: v,
progress: 1,
loaded: 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) {
dialogWarningClose(t, {
message: t('dialog.message.confirmDelete'),
@ -508,8 +513,18 @@ async function uploadFile(taskId: string, list: FileList) {
);
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() {
@ -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], () => {
fetchStatus();
});
@ -821,8 +863,12 @@ watch([currentFormData.value.taskStatus], () => {
v-model:file-data="fileData"
:transform-url="
async (url: string) => {
const result = await api.get<string>(url);
return result.data;
if (state.mode === 'create') {
return url;
} else {
const result = await api.get<string>(url);
return result.data;
}
}
"
@fetch-file-list="
@ -833,9 +879,21 @@ watch([currentFormData.value.taskStatus], () => {
"
@upload="
async (f) => {
if (!currentFormData.id) return;
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);
}
@ -973,7 +1031,7 @@ watch([currentFormData.value.taskStatus], () => {
(l) => l.userId === v.responsibleUser.id,
)?.userTaskStatus === UserTaskStatus.Submit
"
:rows="list"
:rows="sortList(list)"
@change-all-status="
(v) =>
handleChangeStatus(
@ -997,7 +1055,6 @@ watch([currentFormData.value.taskStatus], () => {
fullTaskOrder?.userTask.find(
(l) => l.userId === v.responsibleUser.id,
)?.userTaskStatus;
console.log(_userStatus);
return (
_userStatus !== UserTaskStatus.Submit &&
_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 () => {
initTheme();
initLang();
@ -607,7 +636,7 @@ watch([currentFormData.value.taskStatus], () => {
)
"
step-on
:rows="list"
:rows="sortList(list)"
@change-all-status="(v) => handleChangeStatus(v, i)"
v-model:selected-employee="selectedEmployee[i]"
>

View file

@ -53,6 +53,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
: pageState.statusFilter === PaymentDataStatus.Wait
? false
: undefined,
quotationOnly: true,
debitNoteOnly: false,
});
if (ret) {
data.value = ret.result;
@ -64,7 +66,10 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
}
async function fetchStats() {
const ret = await invoiceStore.getInvoiceStats();
const ret = await invoiceStore.getInvoiceStats({
quotationOnly: true,
debitNoteOnly: false,
});
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 () => {
initTheme();
initLang();
@ -653,8 +657,12 @@ onMounted(async () => {
v-model:file-data="attachmentData"
:transform-url="
async (url: string) => {
const result = await api.get<string>(url);
return result.data;
if (!creditNoteData?.id) {
return url;
} else {
const result = await api.get<string>(url);
return result.data;
}
}
"
@fetch-file-list="
@ -675,6 +683,7 @@ onMounted(async () => {
loaded: 0,
total: el.size,
placeholder: true,
url: fileToUrl(el),
});
});

View file

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

View file

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

View file

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

View file

@ -18,6 +18,7 @@ import useOptionStore from '../options';
import { CustomerBranch } from '../customer/types';
import { CustomerBranchRelation } from '../quotations';
import { Employee } from '../employee/types';
import { CreatedBy } from '../types';
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;
}
export function calculateDaysUntilExpire(expireDate: Date): number {
export function calculateDaysUntilExpire(expireDate: Date | string): number {
const today = new Date();
const expire = new Date(expireDate);
const diffInTime = expire.getTime() - today.getTime();
@ -596,7 +597,7 @@ export function getCustomerName(
}
export function getEmployeeName(
record: Employee,
record: Employee | CreatedBy,
opts?: {
locale?: string;
},
@ -604,7 +605,7 @@ export function getEmployeeName(
const employee = record;
return {
['eng']: `${useOptionStore().mapOption(employee.namePrefix)} ${employee.firstNameEN} ${employee.lastNameEN}`,
['tha']: `${useOptionStore().mapOption(employee.namePrefix)} ${employee.firstName} ${employee.lastName}`,
['eng']: `${typeof employee.namePrefix === 'string' ? useOptionStore().mapOption(employee.namePrefix) : ''} ${employee.firstNameEN} ${employee.lastNameEN}`,
['tha']: `${typeof employee.namePrefix === 'string' ? useOptionStore().mapOption(employee.namePrefix) : ''} ${employee.firstName} ${employee.lastName}`,
}[opts?.locale || 'eng'];
}