1073 lines
35 KiB
Vue
1073 lines
35 KiB
Vue
<script setup lang="ts">
|
|
// NOTE: Library
|
|
import { onMounted, reactive, ref, watch, computed, nextTick } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
// NOTE: Components
|
|
import DataDisplay from 'components/08_request-list/DataDisplay.vue';
|
|
import DocumentExpansion from './DocumentExpansion.vue';
|
|
import FormExpansion from './FormExpansion.vue';
|
|
import PropertiesExpansion from './PropertiesExpansion.vue';
|
|
import FormGroupHead from './FormGroupHead.vue';
|
|
import AvatarGroup from 'src/components/shared/AvatarGroup.vue';
|
|
import { StateButton } from 'components/button';
|
|
import { CancelButton, MainButton, SaveButton } from 'components/button';
|
|
import DutyExpansion from './DutyExpansion.vue';
|
|
import MessengerExpansion from './MessengerExpansion.vue';
|
|
import RequestAction from './RequestAction.vue';
|
|
import DialogFormContainer from 'src/components/dialog/DialogFormContainer.vue';
|
|
import DialogHeader from 'src/components/dialog/DialogHeader.vue';
|
|
import FormCancel from './FormCancel.vue';
|
|
|
|
// NOTE: Store
|
|
import {
|
|
baseUrl,
|
|
dialog,
|
|
getEmployeeName,
|
|
getCustomerName,
|
|
dialogWarningClose,
|
|
canAccess,
|
|
} from 'src/stores/utils';
|
|
import { dateFormatJS } from 'src/utils/datetime';
|
|
import { useRequestList } from 'src/stores/request-list';
|
|
import {
|
|
RequestData,
|
|
RequestWork,
|
|
Attributes,
|
|
DocStatus,
|
|
Step,
|
|
RequestWorkStatus,
|
|
RequestDataStatus,
|
|
} from 'src/stores/request-list/types';
|
|
import useOptionStore from 'src/stores/options';
|
|
import ProductExpansion from './ProductExpansion.vue';
|
|
import { useRoute } from 'vue-router';
|
|
import { useWorkflowTemplate } from 'src/stores/workflow-template';
|
|
import { WorkflowTemplate } from 'src/stores/workflow-template/types';
|
|
import { initLang, initTheme } from 'src/utils/ui';
|
|
import {
|
|
EmployeePassportPayload,
|
|
EmployeeVisaPayload,
|
|
} from 'stores/employee/types';
|
|
import { PropVariant } from 'src/stores/product-service/types';
|
|
import { Invoice } from 'src/stores/payment/types';
|
|
|
|
import { CreatedBy } from 'src/stores/types';
|
|
import { getUserId } from 'src/services/keycloak';
|
|
import { QuotationFull } from 'src/stores/quotations/types';
|
|
import useUserStore from 'src/stores/user';
|
|
|
|
const { locale, t } = useI18n();
|
|
|
|
// NOTE: Variable
|
|
const route = useRoute();
|
|
const optionStore = useOptionStore();
|
|
const requestListStore = useRequestList();
|
|
const flowTemplateStore = useWorkflowTemplate();
|
|
const userStore = useUserStore();
|
|
|
|
const currentUserGroup = ref<string[]>([]);
|
|
const workList = ref<RequestWork[]>([]);
|
|
const statusFile = ref<Attributes>({
|
|
customer: {},
|
|
employee: {},
|
|
});
|
|
|
|
const refDocumentExpansion = ref<InstanceType<typeof DocumentExpansion>[]>([]);
|
|
const data = ref<RequestData>();
|
|
const flow = ref<WorkflowTemplate>();
|
|
const productsList = computed(() =>
|
|
workList.value
|
|
?.filter((v) =>
|
|
v.productService.work?.attributes.workflowStep?.[
|
|
pageState.currentStep - 1
|
|
]?.productsId.includes(v.productService.productId),
|
|
)
|
|
.map((v) => {
|
|
const _props =
|
|
v.productService.work?.attributes?.workflowStep[
|
|
pageState.currentStep - 1
|
|
]?.attributes?.properties;
|
|
return Object.assign(v, {
|
|
_documentExpansion: _props.some(
|
|
(v: PropVariant) => v.fieldName === 'documentCheck',
|
|
),
|
|
_formExpansion: _props.some(
|
|
(v: PropVariant) => v.fieldName === 'designForm',
|
|
),
|
|
_dutyExpansion: _props.some((v: PropVariant) => v.fieldName === 'duty'),
|
|
_messengerExpansion: _props.some(
|
|
(v: PropVariant) => v.fieldName === 'messenger',
|
|
),
|
|
});
|
|
})
|
|
.sort(
|
|
(lhs, rhs) =>
|
|
lhs.productService.installmentNo - rhs.productService.installmentNo,
|
|
),
|
|
);
|
|
|
|
const pageState = reactive({
|
|
hideMetaData: false,
|
|
currentStep: 1,
|
|
requestActionDialog: false,
|
|
rejectCancelDialog: false,
|
|
rejectCancelReason: '',
|
|
requestWorkId: '',
|
|
});
|
|
|
|
// NOTE: Function
|
|
|
|
async function fetchRequestWorkList(opts: { requestDataId: string }) {
|
|
const res = await requestListStore.getRequestWorkList({
|
|
requestDataId: opts.requestDataId,
|
|
pageSize: 9999,
|
|
});
|
|
|
|
if (res) {
|
|
workList.value = res.result;
|
|
}
|
|
}
|
|
|
|
async function getData() {
|
|
const current = route.params['requestListId'];
|
|
|
|
if (typeof current === 'string') {
|
|
const res = await requestListStore.getRequestData(current);
|
|
|
|
if (res) {
|
|
data.value = res;
|
|
await fetchRequestWorkList({ requestDataId: current });
|
|
await getFlow();
|
|
}
|
|
}
|
|
}
|
|
|
|
async function getFlow() {
|
|
if (!workList.value) return;
|
|
|
|
const attr = workList.value.find((v) => !!v.productService.work?.attributes)
|
|
?.productService.work?.attributes;
|
|
|
|
if (attr && Object.hasOwn(attr, 'workflowId')) {
|
|
const workflowId = attr['workflowId'];
|
|
|
|
const res = await flowTemplateStore.getWorkflowTemplate(workflowId);
|
|
|
|
if (res) flow.value = res;
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
initTheme();
|
|
initLang();
|
|
|
|
const result = await userStore.fetchUserGroup();
|
|
|
|
currentUserGroup.value = result.map((v) => v.name);
|
|
|
|
// get data
|
|
await getData();
|
|
});
|
|
|
|
watch(() => route.params['requestListId'], getData);
|
|
|
|
async function triggerChangeStatusWork(step: Step, responsibleUserId?: string) {
|
|
if (step.workStatus === 'Ready' && !responsibleUserId) {
|
|
dialog({
|
|
color: 'warning',
|
|
icon: 'mdi-alert',
|
|
title: t('form.warning.title'),
|
|
actionText: t('dialog.action.ok'),
|
|
message: t('dialog.message.warningSelectDeliveryStaff'),
|
|
action: async () => {
|
|
await nextTick();
|
|
return;
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (step.workStatus === 'RejectCancel') {
|
|
pageState.rejectCancelDialog = true;
|
|
pageState.rejectCancelReason = '';
|
|
pageState.requestWorkId = step.requestWorkId;
|
|
return;
|
|
}
|
|
|
|
const res = await requestListStore.editStatusRequestWork(step);
|
|
if (res) {
|
|
const indexWork = workList.value?.findIndex(
|
|
(v) => v.id === step.requestWorkId,
|
|
);
|
|
if (indexWork === -1 || indexWork === undefined) return;
|
|
if (workList.value === undefined) return;
|
|
|
|
const indexStep = workList.value[indexWork].stepStatus.findIndex(
|
|
(v) => v.step === step.step,
|
|
);
|
|
|
|
if (indexStep === -1) {
|
|
workList.value[indexWork].stepStatus.push(res);
|
|
}
|
|
if (indexStep !== -1) {
|
|
workList.value[indexWork].stepStatus[indexStep].workStatus =
|
|
res.workStatus;
|
|
}
|
|
}
|
|
await nextTick();
|
|
}
|
|
|
|
async function triggerChangeStatusFile(opt: {
|
|
index: number;
|
|
id: string;
|
|
documentType: string;
|
|
status: DocStatus;
|
|
type: 'customer' | 'employee';
|
|
}) {
|
|
if (!workList.value) return;
|
|
|
|
const workItem = workList.value[opt.index];
|
|
if (!workItem || !workItem.attributes) {
|
|
if (workItem) {
|
|
workItem.attributes = { customer: {}, employee: {} };
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const attributes = workItem.attributes;
|
|
|
|
statusFile.value = attributes;
|
|
if (!statusFile.value[opt.type]) {
|
|
statusFile.value[opt.type] = {};
|
|
}
|
|
|
|
statusFile.value[opt.type]![opt.documentType] = opt.status;
|
|
|
|
const res = await requestListStore.editRequestWork({
|
|
id: opt.id,
|
|
attributes: statusFile.value,
|
|
});
|
|
|
|
if (res) {
|
|
workList.value[opt.index].attributes = res.attributes;
|
|
}
|
|
}
|
|
|
|
async function triggerUpload(opt: {
|
|
id: string;
|
|
type: 'customer' | 'employee';
|
|
group: string;
|
|
file: File;
|
|
form?: EmployeePassportPayload | EmployeeVisaPayload;
|
|
}) {
|
|
const newName = `${opt.group}-${Date.now()}-${opt.file.name}`;
|
|
|
|
const res = await requestListStore.uploadAttachmentRequest({
|
|
...opt,
|
|
name: newName,
|
|
});
|
|
|
|
return !!res;
|
|
}
|
|
|
|
async function triggerViewFile(opt: {
|
|
id: string;
|
|
fileName: string;
|
|
type: 'customer' | 'employee';
|
|
group: string;
|
|
download?: boolean;
|
|
}) {
|
|
let url;
|
|
url = await requestListStore.viewAttachmentRequest({
|
|
id: opt.id,
|
|
name: opt.fileName,
|
|
type: opt.type,
|
|
group: opt.group,
|
|
download: opt.download,
|
|
});
|
|
|
|
if (!opt.download) window.open(url, '_blank');
|
|
}
|
|
|
|
const responsibleList = computed(() => {
|
|
const temp = workList.value?.reduce<
|
|
Record<string, { user: CreatedBy[]; group: string[] }>
|
|
>((acc, curr: RequestWork) => {
|
|
curr.productService.service?.workflow?.step.forEach((v) => {
|
|
const key = v.order.toString();
|
|
const responsibleGroup = (
|
|
v.responsibleGroup as unknown as { group: string }[]
|
|
).map((v) => v.group);
|
|
|
|
if (!acc[key]) {
|
|
acc[key] = {
|
|
user: v.responsiblePerson.map((v) => v.user),
|
|
group: responsibleGroup,
|
|
};
|
|
}
|
|
|
|
const current = acc[key];
|
|
|
|
v.responsiblePerson.forEach((lhs) => {
|
|
if (current.user.find((rhs) => rhs.id === lhs.userId)) return;
|
|
current.user.push(lhs.user);
|
|
});
|
|
|
|
responsibleGroup.forEach((lhs) => {
|
|
if (current.group.find((rhs) => rhs === lhs)) return;
|
|
current.group.push(lhs);
|
|
});
|
|
});
|
|
|
|
return acc;
|
|
}, {});
|
|
|
|
return temp || {};
|
|
});
|
|
|
|
function getInstallmentInfo() {
|
|
if (
|
|
data.value?.quotation.payCondition === 'Full' ||
|
|
data.value?.quotation.payCondition === 'BillFull'
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const total = data.value?.quotation.paySplitCount || 0;
|
|
const paid = data.value?.quotation.invoice?.reduce((a, c) => {
|
|
if (c.payment?.paymentStatus === 'PaymentSuccess') {
|
|
a += c.installments.length || 0;
|
|
}
|
|
return a;
|
|
}, 0);
|
|
|
|
return { total, paid };
|
|
}
|
|
|
|
function isInstallmentPaySuccess(installmentNo: number) {
|
|
if (
|
|
data.value?.quotation.payCondition === 'Full' ||
|
|
data.value?.quotation.payCondition === 'BillFull'
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
const invoice = data.value?.quotation.invoice?.find((lhs) => {
|
|
return lhs.installments?.some((rhs) => rhs.no === installmentNo);
|
|
});
|
|
|
|
return !!(invoice?.payment?.paymentStatus === 'PaymentSuccess');
|
|
}
|
|
|
|
function goToQuotation(
|
|
quotation: QuotationFull,
|
|
opt?: { tab?: string; id?: string; amount?: number },
|
|
) {
|
|
const { tab, id, amount } = opt || {};
|
|
const url = new URL('/quotation/view', window.location.origin);
|
|
if (tab) {
|
|
url.searchParams.append('tab', tab);
|
|
}
|
|
if (id) {
|
|
url.searchParams.append('id', id);
|
|
}
|
|
if (amount) {
|
|
url.searchParams.append('amount', amount.toString());
|
|
}
|
|
|
|
localStorage.setItem(
|
|
'new-quotation',
|
|
JSON.stringify({
|
|
customerBranchId: quotation.customerBranchId,
|
|
agentPrice: quotation.agentPrice,
|
|
statusDialog: 'info',
|
|
quotationId: opt && opt.id ? opt.id : quotation.id,
|
|
}),
|
|
);
|
|
|
|
window.open(url.toString(), '_blank');
|
|
}
|
|
|
|
function goToDebitNote(opt?: { tab?: string; id?: string }) {
|
|
const { tab, id } = opt || {};
|
|
const url = new URL(
|
|
`/debit-note/${id}?mode=info&tab=${tab}`,
|
|
window.location.origin,
|
|
);
|
|
|
|
window.open(url.toString(), '_blank');
|
|
}
|
|
|
|
function closeTab() {
|
|
dialogWarningClose(t, {
|
|
message: t('dialog.message.close'),
|
|
action: () => {
|
|
window.close();
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
|
|
function openRequestAction() {
|
|
pageState.requestActionDialog = true;
|
|
}
|
|
|
|
async function submitRequestAction(data: {
|
|
form: { responsibleUserLocal: boolean; responsibleUserId: string };
|
|
selected: { _work: RequestWork }[];
|
|
}) {
|
|
const requestWorksId = data.selected.map((s) => s._work.id);
|
|
|
|
const res = await requestListStore.actionRequestWork(
|
|
route.params['requestListId'] as string,
|
|
pageState.currentStep,
|
|
requestWorksId.map((v) => ({
|
|
responsibleUserId: data.form.responsibleUserId,
|
|
responsibleUserLocal: data.form.responsibleUserLocal,
|
|
requestWorkId: v!,
|
|
})),
|
|
);
|
|
if (res) {
|
|
await getData();
|
|
pageState.requestActionDialog = false;
|
|
}
|
|
}
|
|
|
|
async function submitRejectCancel() {
|
|
const current = route.params['requestListId'];
|
|
if (typeof current !== 'string') return;
|
|
|
|
const res = await requestListStore.rejectRequestWork(
|
|
current,
|
|
pageState.requestWorkId,
|
|
{
|
|
reason: pageState.rejectCancelReason,
|
|
},
|
|
);
|
|
|
|
if (res) {
|
|
const indexWork = workList.value?.findIndex(
|
|
(v) => v.id === pageState.requestWorkId,
|
|
);
|
|
|
|
workList.value[indexWork].rejectRequestCancel = true;
|
|
workList.value[indexWork].rejectRequestCancelReason =
|
|
pageState.rejectCancelReason;
|
|
pageState.rejectCancelDialog = false;
|
|
}
|
|
}
|
|
|
|
function toCustomer(customer: RequestData['quotation']['customerBranch']) {
|
|
if (!canAccess('customer', 'view')) return;
|
|
const url = new URL(
|
|
`/customer-management?tab=customer&id=${customer.customerId}`,
|
|
window.location.origin,
|
|
);
|
|
|
|
window.open(url.toString(), '_blank');
|
|
}
|
|
|
|
function toEmployee(employee: RequestData['employee']) {
|
|
if (!canAccess('customer', 'view')) return;
|
|
const url = new URL(
|
|
`/customer-management?tab=employee&id=${employee.id}`,
|
|
window.location.origin,
|
|
);
|
|
|
|
window.open(url.toString(), '_blank');
|
|
}
|
|
</script>
|
|
<template>
|
|
<div class="column surface-0 fullscreen" v-if="data">
|
|
<!-- SEC: Header -->
|
|
<header class="row q-px-md q-py-sm items-center full justify-between">
|
|
<div style="flex: 1" class="row items-center">
|
|
<RouterLink to="/request-list">
|
|
<q-img src="/icons/favicon-512x512.png" width="3rem" />
|
|
</RouterLink>
|
|
<span class="column text-h6 text-bold q-ml-md">
|
|
{{ $t('requestList.title') }}
|
|
{{ data.code || '' }}
|
|
<span class="text-caption text-regular app-text-muted">
|
|
{{
|
|
$t('quotation.processOn', {
|
|
msg: dateFormatJS({ date: data.createdAt }),
|
|
})
|
|
}}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div class="row q-gutter-x-xl q-mr-xl">
|
|
<div class="column">
|
|
<span class="app-text-muted">
|
|
{{ $t('requestList.salesRepresentative') }}
|
|
</span>
|
|
<span>
|
|
{{ optionStore.mapOption(data.quotation.createdBy.namePrefix) }}
|
|
|
|
{{
|
|
$i18n.locale === 'eng'
|
|
? `${data.quotation.createdBy.firstNameEN} ${data.quotation.createdBy.lastNameEN}`
|
|
: `${data.quotation.createdBy.firstName} ${data.quotation.createdBy.lastName}`
|
|
}}
|
|
</span>
|
|
</div>
|
|
<div class="column">
|
|
<span class="app-text-muted">{{ $t('flow.responsiblePerson') }}</span>
|
|
<span>
|
|
<template
|
|
v-if="responsibleList[pageState.currentStep]?.user.length"
|
|
>
|
|
<AvatarGroup
|
|
:data="[
|
|
...(responsibleList[pageState.currentStep].user || []).map(
|
|
(v) => ({
|
|
name:
|
|
$i18n.locale === 'eng'
|
|
? `${v.firstNameEN} ${v.lastNameEN}`
|
|
: `${v.firstName} ${v.lastName}`,
|
|
imgUrl: !v.selectedImage
|
|
? v.gender === 'male'
|
|
? `/no-img-man.png`
|
|
: `/no-img-female.png`
|
|
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
|
}),
|
|
),
|
|
...responsibleList[pageState.currentStep].group.map((g) => ({
|
|
name: `${$t('general.group')} ${g}`,
|
|
imgUrl: '/img-group.png',
|
|
})),
|
|
]"
|
|
/>
|
|
</template>
|
|
<template v-else>-</template>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- SEC: Body -->
|
|
<main class="col full-width q-pa-md" style="flex-grow: 1; overflow-y: auto">
|
|
<section class="col-sm col-12">
|
|
<div class="col q-gutter-y-md" :key="pageState.currentStep">
|
|
<!-- step -->
|
|
<nav
|
|
v-if="flow"
|
|
class="surface-1 q-pa-sm row no-wrap full-width scroll rounded"
|
|
style="gap: 10px"
|
|
>
|
|
<span
|
|
v-if="
|
|
workList?.every((v) =>
|
|
v.productService.work?.attributes?.workflowStep?.every(
|
|
(s: any) => {
|
|
s.attributes?.properties.length === 0;
|
|
},
|
|
),
|
|
)
|
|
"
|
|
class="app-text-muted q-py-sm"
|
|
>
|
|
{{ $t('requestList.noWorkflowTemplate') }}
|
|
</span>
|
|
<template v-for="(value, i) in flow.step" :key="value.id">
|
|
<StateButton
|
|
@click="() => (pageState.currentStep = value.order)"
|
|
:status-waiting="
|
|
!workList
|
|
?.filter((v) => {
|
|
return v.productService.work?.attributes.workflowStep?.[
|
|
i - 1
|
|
]?.productsId.includes(v.productService.productId);
|
|
})
|
|
.every((v) => {
|
|
const status = v.stepStatus.find(
|
|
({ step }) => step === i,
|
|
)?.workStatus;
|
|
return (
|
|
status === RequestWorkStatus.Completed ||
|
|
status === RequestWorkStatus.Ended ||
|
|
status === RequestWorkStatus.Canceled
|
|
);
|
|
})
|
|
"
|
|
:status-done="
|
|
workList
|
|
?.filter((v) => {
|
|
return v.productService.work?.attributes.workflowStep?.[
|
|
i
|
|
]?.productsId.includes(v.productService.productId);
|
|
})
|
|
.every((v) => {
|
|
const status = v.stepStatus.find(
|
|
({ step }) => step === i + 1,
|
|
)?.workStatus;
|
|
return (
|
|
status === RequestWorkStatus.Completed ||
|
|
status === RequestWorkStatus.Ended ||
|
|
status === RequestWorkStatus.Canceled ||
|
|
(data?.requestDataStatus === 'Canceled' && !status)
|
|
);
|
|
})
|
|
"
|
|
:status-active="pageState.currentStep === value.order"
|
|
:label="value.name"
|
|
/>
|
|
<!-- 'quotation-status-active': value.active?.(), -->
|
|
<!-- @click="'waiting' !== 'waiting' && value.handler()" -->
|
|
</template>
|
|
</nav>
|
|
|
|
<!-- meta data -->
|
|
<article class="surface-1 rounded">
|
|
<div
|
|
class="text-weight-bold row items-center no-wrap q-pa-sm"
|
|
style="gap: 16px"
|
|
>
|
|
<q-img src="/images/quotation-avatar.png" width="42px" />
|
|
<span class="ellipsis" style="font-size: 18px">
|
|
{{ data.quotation.workName || '-' }}
|
|
</span>
|
|
<q-btn
|
|
class="q-ml-sm"
|
|
icon="mdi-pin-outline"
|
|
color="primary"
|
|
size="sm"
|
|
flat
|
|
dense
|
|
rounded
|
|
@click="pageState.hideMetaData = !pageState.hideMetaData"
|
|
:style="pageState.hideMetaData ? 'rotate: 90deg' : ''"
|
|
style="transition: 0.1s ease-in-out"
|
|
/>
|
|
</div>
|
|
<transition name="slide">
|
|
<section
|
|
v-if="!pageState.hideMetaData"
|
|
class=""
|
|
:class="{ row: $q.screen.gt.sm, column: $q.screen.lt.md }"
|
|
>
|
|
<FormGroupHead class="col-12">
|
|
{{ $t('requestList.ref') }}
|
|
</FormGroupHead>
|
|
<div
|
|
class="col-12 q-pa-sm"
|
|
:class="{
|
|
row: $q.screen.gt.sm,
|
|
'column q-gutter-y-sm': $q.screen.lt.md,
|
|
}"
|
|
>
|
|
<DataDisplay
|
|
clickable
|
|
class="col"
|
|
icon="mdi-file-document-outline"
|
|
:label="$t('requestList.quotationCode')"
|
|
:value="data.quotation.code || '-'"
|
|
@label-click="
|
|
data.quotation.isDebitNote
|
|
? goToDebitNote({ id: data.quotation.id, tab: 'title' })
|
|
: goToQuotation(data.quotation)
|
|
"
|
|
/>
|
|
<DataDisplay
|
|
clickable
|
|
class="col"
|
|
icon="mdi-file-document-outline"
|
|
tooltip
|
|
:label="$t('requestList.invoiceCode')"
|
|
:value="
|
|
data.quotation?.invoice?.map((i: Invoice) => i.code)
|
|
"
|
|
@label-click="
|
|
(value: string, index: number | null) => {
|
|
if (!data || index === null) return;
|
|
|
|
data.quotation.isDebitNote
|
|
? goToDebitNote({
|
|
id: data.quotation.id,
|
|
tab: 'payment',
|
|
})
|
|
: goToQuotation(data.quotation, {
|
|
tab: 'invoice',
|
|
id: data.quotation.id,
|
|
amount: data.quotation.invoice?.[index]?.amount,
|
|
});
|
|
}
|
|
"
|
|
/>
|
|
<DataDisplay
|
|
clickable
|
|
class="col"
|
|
icon="mdi-file-document-outline"
|
|
tooltip
|
|
:label="$t('requestList.receiptCode')"
|
|
:value="
|
|
data.quotation?.invoice?.flatMap(
|
|
(i: Invoice) => i.payment?.code || [],
|
|
)
|
|
"
|
|
@click="
|
|
data.quotation.isDebitNote
|
|
? goToDebitNote({
|
|
id: data.quotation.id,
|
|
tab: 'receipt',
|
|
})
|
|
: goToQuotation(data.quotation, { tab: 'receipt' })
|
|
"
|
|
/>
|
|
|
|
<DataDisplay
|
|
v-if="data.quotation.isDebitNote"
|
|
clickable
|
|
class="col"
|
|
icon="mdi-file-document-outline"
|
|
:label="$t('requestList.referenceNo')"
|
|
:value="data.quotation.debitNoteQuotation.code || '-'"
|
|
@label-click="
|
|
goToQuotation(data.quotation, {
|
|
id: data.quotation.debitNoteQuotationId,
|
|
})
|
|
"
|
|
/>
|
|
|
|
<div v-if="$q.screen.gt.sm" class="col"></div>
|
|
</div>
|
|
<FormGroupHead class="col-12">
|
|
{{ $t('quotation.employee') }}
|
|
</FormGroupHead>
|
|
<div
|
|
class="col-12 q-pa-sm"
|
|
:class="{
|
|
row: $q.screen.gt.sm,
|
|
'column q-gutter-y-sm': $q.screen.lt.md,
|
|
}"
|
|
>
|
|
<DataDisplay
|
|
:clickable="canAccess('customer', 'view')"
|
|
class="col"
|
|
icon="mdi-account-settings-outline"
|
|
:label="$t('customer.employer')"
|
|
:value="
|
|
getCustomerName(data.quotation.customerBranch, {
|
|
locale: locale,
|
|
noCode: true,
|
|
}) || '-'
|
|
"
|
|
@label-click="toCustomer(data.quotation.customerBranch)"
|
|
/>
|
|
<DataDisplay
|
|
:clickable="canAccess('customer', 'view')"
|
|
class="col"
|
|
icon="mdi-account-settings-outline"
|
|
:label="$t('customer.employee')"
|
|
:value="
|
|
getEmployeeName(data.employee, {
|
|
locale: $i18n.locale,
|
|
}) || '-'
|
|
"
|
|
@label-click="toEmployee(data.employee)"
|
|
/>
|
|
<DataDisplay
|
|
class="col"
|
|
icon="mdi-passport"
|
|
:label="$t('customerEmployee.form.passportNo')"
|
|
:value="data.employee.employeePassport?.[0]?.number || '-'"
|
|
/>
|
|
<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>
|
|
<!-- product -->
|
|
<template
|
|
v-for="(value, index) in productsList.map((v) =>
|
|
Object.assign(v, {
|
|
_readonly:
|
|
data.requestDataStatus === RequestDataStatus.Canceled ||
|
|
(responsibleList &&
|
|
(!!responsibleList[pageState.currentStep]?.user?.length ||
|
|
!!responsibleList[pageState.currentStep]?.group
|
|
?.length) &&
|
|
!responsibleList[pageState.currentStep]?.user.find(
|
|
(v) => v.id === getUserId(),
|
|
) &&
|
|
!responsibleList[pageState.currentStep]?.group.some((v) =>
|
|
currentUserGroup.includes(v),
|
|
)),
|
|
}),
|
|
)"
|
|
:key="value"
|
|
>
|
|
<ProductExpansion
|
|
:request-cancel="value.customerRequestCancel"
|
|
:request-cancel-reason="value.customerRequestCancelReason"
|
|
:reject-request-cancel="value.rejectRequestCancel"
|
|
:reject-request-cancel-reason="value.rejectRequestCancelReason"
|
|
:cancel="data.requestDataStatus === RequestDataStatus.Canceled"
|
|
:readonly="value._readonly"
|
|
:order-able="value._messengerExpansion"
|
|
:installment-info="getInstallmentInfo()"
|
|
:pay-success="
|
|
isInstallmentPaySuccess(value.productService.installmentNo)
|
|
"
|
|
:status="
|
|
value.stepStatus?.find((v) => v.step === pageState.currentStep)
|
|
"
|
|
:installment-no="value.productService.installmentNo"
|
|
:pay-condition="data?.quotation.payCondition"
|
|
:img-url="`/product/${value.productService.productId}/image/${value.productService.product.selectedImage}`"
|
|
:name="value.productService.product.name"
|
|
:code="value.productService.product.code"
|
|
:product="value.productService.product"
|
|
@change-status="
|
|
(v) => {
|
|
triggerChangeStatusWork(
|
|
{
|
|
workStatus: v.requestWorkStatus,
|
|
step:
|
|
v.step === undefined
|
|
? pageState.currentStep
|
|
: v.step.step,
|
|
requestWorkId: value.id || '',
|
|
},
|
|
value.stepStatus?.[pageState.currentStep - 1]
|
|
?.responsibleUserId ?? data.defaultMessengerId,
|
|
);
|
|
}
|
|
"
|
|
>
|
|
<template v-slot="{ product }">
|
|
<section
|
|
class="column surface-1 q-px-sm bordered-t q-pb-sm q-gutter-y-sm"
|
|
>
|
|
<DocumentExpansion
|
|
v-if="value._documentExpansion"
|
|
:readonly="value._readonly"
|
|
ref="refDocumentExpansion"
|
|
:attributes="value.attributes"
|
|
@change-status="
|
|
(opt) => {
|
|
triggerChangeStatusFile({
|
|
index,
|
|
id: value.id || '',
|
|
documentType: opt.key || '',
|
|
status: opt.status,
|
|
type: opt.type || 'customer',
|
|
});
|
|
}
|
|
"
|
|
@view-doc="
|
|
(opt) => {
|
|
triggerViewFile({
|
|
id: opt.id,
|
|
fileName: opt.data.fileName,
|
|
type: opt.type,
|
|
group: opt.data.documentType,
|
|
});
|
|
}
|
|
"
|
|
@upload="
|
|
async (opt, done) => {
|
|
await triggerUpload({ ...opt });
|
|
await done(opt.type || 'customer');
|
|
}
|
|
"
|
|
@download="
|
|
(opt) => {
|
|
triggerViewFile({
|
|
id: opt.id,
|
|
fileName: opt.data.fileName,
|
|
type: opt.type,
|
|
group: opt.data.documentType,
|
|
download: true,
|
|
});
|
|
}
|
|
"
|
|
:current-id="{
|
|
customer: value.request.quotation.customerBranchId,
|
|
employee: value.request.employeeId,
|
|
}"
|
|
:listDocument="product?.document"
|
|
/>
|
|
<MessengerExpansion
|
|
v-if="value._messengerExpansion"
|
|
:readonly="value._readonly"
|
|
:default-messenger="
|
|
value.stepStatus[pageState.currentStep - 1]
|
|
? undefined
|
|
: data.defaultMessengerId
|
|
"
|
|
:step="{
|
|
step: pageState.currentStep,
|
|
requestWorkId: value.id || '',
|
|
}"
|
|
:id="value.id"
|
|
:attributes-form="
|
|
value.stepStatus[pageState.currentStep - 1]
|
|
"
|
|
:responsible-area-district-id="
|
|
data.quotation.customerBranch.districtId
|
|
"
|
|
@update-attributes="
|
|
(opt) => {
|
|
value.stepStatus[pageState.currentStep - 1] = opt;
|
|
}
|
|
"
|
|
/>
|
|
<DutyExpansion
|
|
v-if="value._dutyExpansion"
|
|
:readonly="value._readonly"
|
|
:step="{
|
|
step: pageState.currentStep,
|
|
requestWorkId: value.id || '',
|
|
}"
|
|
:id="value.id"
|
|
:attributes-form="
|
|
value.stepStatus?.[pageState.currentStep - 1]
|
|
"
|
|
/>
|
|
<FormExpansion
|
|
v-if="value._formExpansion"
|
|
:request-work-id="value.id"
|
|
:readonly="value._readonly"
|
|
:step="{
|
|
step: pageState.currentStep,
|
|
requestWorkId: value.id || '',
|
|
}"
|
|
:id="value.id"
|
|
:attributes-form="
|
|
value.stepStatus?.[pageState.currentStep - 1]
|
|
"
|
|
/>
|
|
<PropertiesExpansion
|
|
:request-list-data="data"
|
|
:id="value.id"
|
|
:readonly="value._readonly"
|
|
:properties-to-show="
|
|
value.productService.work?.attributes.workflowStep[
|
|
pageState.currentStep - 1
|
|
].attributes.properties
|
|
"
|
|
:attributes="value.attributes"
|
|
/>
|
|
</section>
|
|
</template>
|
|
</ProductExpansion>
|
|
</template>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- SEC: Footer -->
|
|
<footer
|
|
class="surface-1 q-pa-md full-width row justify-end"
|
|
style="gap: var(--size-2)"
|
|
>
|
|
<CancelButton
|
|
outlined
|
|
@click="closeTab()"
|
|
:label="$t('dialog.action.close')"
|
|
/>
|
|
<MainButton
|
|
v-if="
|
|
productsList.some((v) => v._messengerExpansion) &&
|
|
!(
|
|
data.requestDataStatus === RequestDataStatus.Completed ||
|
|
data.requestDataStatus === RequestDataStatus.Canceled ||
|
|
(responsibleList &&
|
|
!responsibleList[pageState.currentStep]?.user.find(
|
|
(v) => v.id === getUserId(),
|
|
) &&
|
|
!responsibleList[pageState.currentStep]?.group.some((v) =>
|
|
currentUserGroup.includes(v),
|
|
)) ||
|
|
(!!responsibleList[pageState.currentStep]?.user?.length &&
|
|
!!responsibleList[pageState.currentStep]?.user?.length)
|
|
)
|
|
"
|
|
solid
|
|
icon="mdi-account-outline"
|
|
color="207 96% 32%"
|
|
@click="openRequestAction"
|
|
>
|
|
{{
|
|
$t('general.manage', { text: $t('requestList.employeeMessenger') })
|
|
}}
|
|
</MainButton>
|
|
</footer>
|
|
</div>
|
|
|
|
<RequestAction
|
|
v-model="pageState.requestActionDialog"
|
|
:work="workList"
|
|
:responsible-district-id="data?.quotation.customerBranch.districtId || ''"
|
|
@submit="submitRequestAction"
|
|
/>
|
|
|
|
<DialogFormContainer
|
|
width="60vw"
|
|
height="40vh"
|
|
v-model="pageState.rejectCancelDialog"
|
|
@submit="() => submitRejectCancel()"
|
|
>
|
|
<template #header>
|
|
<DialogHeader :title="$t('requestList.status.work.RejectCancel')" />
|
|
</template>
|
|
|
|
<section class="col q-pa-md scroll">
|
|
<FormCancel v-model:reason="pageState.rejectCancelReason" />
|
|
</section>
|
|
|
|
<template #footer>
|
|
<CancelButton
|
|
class="q-ml-auto"
|
|
outlined
|
|
@click="() => (pageState.rejectCancelDialog = false)"
|
|
/>
|
|
<SaveButton
|
|
label="ยืนยัน"
|
|
class="q-ml-sm"
|
|
icon="mdi-check"
|
|
solid
|
|
type="submit"
|
|
/>
|
|
</template>
|
|
</DialogFormContainer>
|
|
</template>
|
|
<style scoped></style>
|