978 lines
27 KiB
Vue
978 lines
27 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, reactive, computed } from 'vue';
|
|
import { api } from 'src/boot/axios';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { dateFormatJS } from 'src/utils/datetime';
|
|
import { initLang, initTheme } from 'src/utils/ui';
|
|
import { QuotationFull, useQuotationStore } from 'src/stores/quotations';
|
|
import {
|
|
CreditNote,
|
|
CreditNotePaybackStatus,
|
|
CreditNotePayload,
|
|
CreditNoteStatus,
|
|
useCreditNote,
|
|
} from 'src/stores/credit-note';
|
|
import { useConfigStore } from 'src/stores/config';
|
|
import DocumentExpansion from './expansion/DocumentExpansion.vue';
|
|
import RemarkExpansion from '../09_task-order/expansion/RemarkExpansion.vue';
|
|
import AdditionalFileExpansion from '../09_task-order/expansion/AdditionalFileExpansion.vue';
|
|
import PaymentExpansion from './expansion/PaymentExpansion.vue';
|
|
import CreditNoteExpansion from './expansion/CreditNoteExpansion.vue';
|
|
import StateButton from 'src/components/button/StateButton.vue';
|
|
import ProductExpansion from './expansion/ProductExpansion.vue';
|
|
import SelectReadyRequestWork from '../09_task-order/SelectReadyRequestWork.vue';
|
|
import RefundInformation from './RefundInformation.vue';
|
|
import QuotationFormReceipt from '../05_quotation/QuotationFormReceipt.vue';
|
|
import DialogViewFile from 'src/components/dialog/DialogViewFile.vue';
|
|
import {
|
|
MainButton,
|
|
SaveButton,
|
|
CancelButton,
|
|
EditButton,
|
|
UndoButton,
|
|
} from 'src/components/button';
|
|
import { precisionRound } from 'src/utils/arithmetic';
|
|
import { DebitNote, useDebitNote } from 'src/stores/debit-note';
|
|
import { RequestWork } from 'src/stores/request-list/types';
|
|
import { storeToRefs } from 'pinia';
|
|
import useOptionStore from 'src/stores/options';
|
|
import { dialogWarningClose, canAccess, isRoleInclude } from 'src/stores/utils';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { QForm } from 'quasar';
|
|
import { getName } from 'src/services/keycloak';
|
|
|
|
import { RequestWorkStatus } from 'src/stores/request-list/types';
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const creditNote = useCreditNote();
|
|
const debitNote = useDebitNote();
|
|
const quotation = useQuotationStore();
|
|
const configStore = useConfigStore();
|
|
const { data: config } = storeToRefs(configStore);
|
|
const { t } = useI18n();
|
|
|
|
const agentPrice = ref<boolean>(false);
|
|
const refForm = ref<InstanceType<typeof QForm>>();
|
|
const creditNoteData = ref<CreditNote>();
|
|
const quotationData = ref<DebitNote | QuotationFull>();
|
|
const view = ref<CreditNoteStatus | null>(null);
|
|
const fileList = ref<FileList>();
|
|
const attachmentList = ref<FileList>();
|
|
const fileData = ref<
|
|
{
|
|
name: string;
|
|
progress: number;
|
|
loaded: number;
|
|
total: number;
|
|
url?: string;
|
|
}[]
|
|
>([]);
|
|
const attachmentData = ref<
|
|
{
|
|
name: string;
|
|
progress: number;
|
|
loaded: number;
|
|
total: number;
|
|
url?: string;
|
|
placeholder?: boolean;
|
|
}[]
|
|
>([]);
|
|
|
|
const statusTabForm = ref<
|
|
{
|
|
title: string;
|
|
status: 'done' | 'doing' | 'waiting';
|
|
handler: () => void;
|
|
active?: () => boolean;
|
|
}[]
|
|
>([]);
|
|
|
|
// const readonly = computed(
|
|
// () =>
|
|
// creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Pending ||
|
|
// creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Success,
|
|
// );
|
|
|
|
const pageState = reactive({
|
|
productDialog: false,
|
|
fileDialog: false,
|
|
mode: 'view' as 'view' | 'edit' | 'info',
|
|
});
|
|
|
|
const defaultRemark = '';
|
|
|
|
const formData = ref<CreditNotePayload>({
|
|
quotationId: '',
|
|
requestWorkId: [],
|
|
reason: '',
|
|
detail: '',
|
|
paybackType: 'Cash',
|
|
paybackBank: '',
|
|
paybackAccount: '',
|
|
paybackAccountName: '',
|
|
remark: defaultRemark,
|
|
});
|
|
|
|
const formTaskList = ref<
|
|
{
|
|
requestWorkId: string;
|
|
requestWorkStep?: { requestWork: RequestWork };
|
|
}[]
|
|
>([]);
|
|
|
|
let taskListGroup = computed(() => {
|
|
const cacheData = formTaskList.value.reduce<
|
|
{
|
|
product: RequestWork['productService'];
|
|
list: RequestWork[];
|
|
}[]
|
|
>((acc, curr) => {
|
|
const task = curr.requestWorkStep;
|
|
|
|
if (!task) return acc;
|
|
|
|
if (task.requestWork) {
|
|
let exist = acc.find(
|
|
(item) => task.requestWork.productService.productId == item.product.id,
|
|
);
|
|
const record = Object.assign(task.requestWork);
|
|
|
|
if (exist) {
|
|
exist.list.push(task.requestWork);
|
|
} else {
|
|
acc.push({
|
|
product: task.requestWork.productService,
|
|
list: [record],
|
|
});
|
|
}
|
|
}
|
|
|
|
return acc;
|
|
}, []);
|
|
|
|
return cacheData;
|
|
});
|
|
|
|
const summaryPrice = computed(() => getPrice(taskListGroup.value));
|
|
|
|
async function initStatus() {
|
|
if (creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Pending)
|
|
view.value = CreditNoteStatus.Pending;
|
|
if (creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Success)
|
|
view.value = CreditNoteStatus.Success;
|
|
|
|
statusTabForm.value = [
|
|
{
|
|
title: 'title',
|
|
status: creditNoteData.value?.id !== undefined ? 'done' : 'doing',
|
|
active: () => view.value === null,
|
|
handler: () => {
|
|
view.value = null;
|
|
},
|
|
},
|
|
{
|
|
title: 'Pending',
|
|
status: creditNoteData.value?.id
|
|
? creditNoteData.value.creditNoteStatus === CreditNoteStatus.Waiting
|
|
? 'waiting'
|
|
: creditNoteData.value.creditNoteStatus === CreditNoteStatus.Success
|
|
? 'done'
|
|
: 'doing'
|
|
: 'waiting',
|
|
active: () => view.value === CreditNoteStatus.Pending,
|
|
handler: async () => {
|
|
view.value = CreditNoteStatus.Pending;
|
|
creditNoteData.value &&
|
|
(await getFileList(creditNoteData.value.id, true));
|
|
},
|
|
},
|
|
{
|
|
title: 'Success',
|
|
status:
|
|
creditNoteData.value?.id &&
|
|
creditNoteData.value.creditNoteStatus === CreditNoteStatus.Success
|
|
? 'done'
|
|
: 'waiting',
|
|
active: () => view.value === CreditNoteStatus.Success,
|
|
handler: () => {
|
|
view.value = CreditNoteStatus.Success;
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
function getPrice(
|
|
list: {
|
|
product: RequestWork['productService'];
|
|
list: RequestWork[];
|
|
}[],
|
|
) {
|
|
return list.reduce(
|
|
(a, c) => {
|
|
const value = creditNote.getfinalPriceCredit(c.list || []);
|
|
|
|
a.totalPrice = precisionRound(a.totalPrice + value.totalPrice);
|
|
a.totalDiscount = precisionRound(a.totalDiscount + value.totalDiscount);
|
|
a.vat = precisionRound(a.vat + value.vat);
|
|
a.vatIncluded = precisionRound(a.vatIncluded + value.vatIncluded);
|
|
a.vatExcluded = precisionRound(a.vatExcluded + value.vatExcluded);
|
|
a.finalPrice = precisionRound(a.finalPrice + value.finalPrice);
|
|
|
|
return a;
|
|
},
|
|
{
|
|
totalPrice: 0,
|
|
totalDiscount: 0,
|
|
vat: 0,
|
|
vatIncluded: 0,
|
|
vatExcluded: 0,
|
|
finalPrice: 0,
|
|
},
|
|
);
|
|
}
|
|
|
|
function openProductDialog() {
|
|
pageState.productDialog = true;
|
|
}
|
|
|
|
async function getCreditNote() {
|
|
if (typeof route.params['id'] !== 'string') return;
|
|
|
|
const ret = await creditNote.getCreditNote(route.params['id']);
|
|
|
|
if (!ret) return;
|
|
|
|
creditNoteData.value = ret;
|
|
assignFormData();
|
|
}
|
|
|
|
function assignFormData() {
|
|
if (!creditNoteData.value) return;
|
|
|
|
const current = creditNoteData.value;
|
|
|
|
formData.value = {
|
|
quotationId: creditNoteData.value.quotationId,
|
|
requestWorkId: creditNoteData.value.requestWork.map((v) => v.id || ''),
|
|
reason: creditNoteData.value.reason,
|
|
remark: creditNoteData.value.remark,
|
|
detail: creditNoteData.value.detail,
|
|
paybackType: creditNoteData.value.paybackType,
|
|
paybackBank: creditNoteData.value.paybackBank,
|
|
paybackAccount: creditNoteData.value.paybackAccount,
|
|
paybackAccountName: creditNoteData.value.paybackAccountName,
|
|
};
|
|
|
|
formTaskList.value = creditNoteData.value.requestWork.map((v) => ({
|
|
requestWorkId: v.id || '',
|
|
requestWorkStep: {
|
|
requestWork: {
|
|
...v,
|
|
stepStatus: v.stepStatus || [],
|
|
request: { ...v.request, quotation: current.quotation },
|
|
} as RequestWork,
|
|
},
|
|
}));
|
|
}
|
|
|
|
function openSlipDialog() {
|
|
pageState.fileDialog = true;
|
|
}
|
|
|
|
async function getQuotation() {
|
|
if (creditNoteData.value) {
|
|
quotationData.value = creditNoteData.value.quotation;
|
|
return;
|
|
}
|
|
|
|
if (
|
|
route.name !== 'CreditNoteNew' ||
|
|
typeof route.query['quotationId'] !== 'string'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const isDebitNote = route.query['isDebitNote'] === 'true';
|
|
const ret = isDebitNote
|
|
? await debitNote.getDebitNote(route.query['quotationId'])
|
|
: await quotation.getQuotation(route.query['quotationId']);
|
|
|
|
if (!ret) return;
|
|
|
|
quotationData.value = ret;
|
|
|
|
agentPrice.value = quotationData.value.agentPrice;
|
|
}
|
|
|
|
async function submit() {
|
|
const payload = formData.value;
|
|
|
|
payload.requestWorkId = formTaskList.value.map((v) => v.requestWorkId);
|
|
payload.quotationId =
|
|
(pageState.mode === 'edit'
|
|
? creditNoteData.value?.quotationId
|
|
: typeof route.query['quotationId'] === 'string'
|
|
? route.query['quotationId']
|
|
: '') || '';
|
|
|
|
const res =
|
|
pageState.mode === 'edit'
|
|
? await creditNote.updateCreditNote(
|
|
creditNoteData.value?.id || '',
|
|
payload,
|
|
)
|
|
: creditNoteData.value
|
|
? await creditNote.acceptCreditNote(creditNoteData.value.id)
|
|
: await creditNote.createCreditNote(payload);
|
|
|
|
if (res) {
|
|
await router.push(`/credit-note/${res.id}`);
|
|
|
|
await getCreditNote();
|
|
|
|
if (attachmentList.value) {
|
|
await uploadFile(res.id, attachmentList.value, false);
|
|
}
|
|
|
|
initStatus();
|
|
pageState.mode = 'info';
|
|
}
|
|
}
|
|
|
|
function goToQuotation() {
|
|
if (!quotationData.value) return;
|
|
|
|
const quotation = quotationData.value;
|
|
const url = new URL('/quotation/view', window.location.origin);
|
|
|
|
localStorage.setItem(
|
|
'new-quotation',
|
|
JSON.stringify({
|
|
customerBranchId: quotation.customerBranchId,
|
|
agentPrice: quotation.agentPrice,
|
|
statusDialog: 'info',
|
|
quotationId: quotation.id,
|
|
}),
|
|
);
|
|
|
|
window.open(url.toString(), '_blank');
|
|
}
|
|
|
|
async function changePaybackStatus(status: CreditNotePaybackStatus) {
|
|
if (!creditNoteData.value) return;
|
|
const res = await creditNote.action.updatePaybackStatus(
|
|
creditNoteData.value.id,
|
|
status,
|
|
);
|
|
if (res) {
|
|
creditNoteData.value.paybackStatus = status;
|
|
|
|
if (status === CreditNotePaybackStatus.Done) {
|
|
creditNoteData.value.creditNoteStatus = CreditNoteStatus.Success;
|
|
initStatus();
|
|
}
|
|
}
|
|
}
|
|
|
|
async function uploadFile(
|
|
creditNoteId: string,
|
|
list: FileList,
|
|
slip?: boolean,
|
|
) {
|
|
const promises: ReturnType<
|
|
typeof creditNote.putAttachment | typeof creditNote.putFile
|
|
>[] = [];
|
|
|
|
if (!slip) {
|
|
attachmentData.value = attachmentData.value.filter((v) => !v.placeholder);
|
|
}
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
const data = {
|
|
name: list[i].name,
|
|
progress: 1,
|
|
loaded: 0,
|
|
total: 0,
|
|
url: `/credit-note/${creditNoteId}/${slip ? 'file-slip' : 'attachment'}/${list[i].name}`,
|
|
};
|
|
promises.push(
|
|
slip
|
|
? creditNote.putFile({
|
|
group: 'slip',
|
|
parentId: creditNoteId,
|
|
fileId: list[i].name,
|
|
file: list[i],
|
|
uploadUrl: true,
|
|
onUploadProgress: (e) => {
|
|
const exists = fileData?.value.find((v) => v.name === data.name);
|
|
if (!exists) return fileData?.value.push(data);
|
|
exists.total = e.total || 0;
|
|
exists.progress = e.progress || 0;
|
|
exists.loaded = e.loaded;
|
|
},
|
|
})
|
|
: creditNote.putAttachment({
|
|
parentId: creditNoteId,
|
|
name: list[i].name,
|
|
file: list[i],
|
|
onUploadProgress: (e) => {
|
|
const exists = attachmentData?.value.find(
|
|
(v) => v.name === data.name,
|
|
);
|
|
if (!exists) return attachmentData?.value.push(data);
|
|
exists.total = e.total || 0;
|
|
exists.progress = e.progress || 0;
|
|
exists.loaded = e.loaded;
|
|
},
|
|
}),
|
|
);
|
|
slip ? fileData?.value.push(data) : attachmentData?.value.push(data);
|
|
}
|
|
fileList.value = undefined;
|
|
attachmentList.value = undefined;
|
|
|
|
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 remove(creditNoteId: string, n: string, slip?: boolean) {
|
|
dialogWarningClose(t, {
|
|
message: t('dialog.message.confirmDelete'),
|
|
actionText: t('dialog.action.ok'),
|
|
action: async () => {
|
|
const res = slip
|
|
? await creditNote.delFile({
|
|
group: 'slip',
|
|
parentId: creditNoteId,
|
|
fileId: n,
|
|
})
|
|
: await creditNote.delAttachment({
|
|
parentId: creditNoteId,
|
|
name: n,
|
|
});
|
|
if (res) {
|
|
getFileList(creditNoteId, slip);
|
|
}
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
|
|
async function getFileList(creditNoteId: string, slip?: boolean) {
|
|
const list = slip
|
|
? await creditNote.listFile({
|
|
group: 'slip',
|
|
parentId: creditNoteId,
|
|
})
|
|
: await creditNote.listAttachment({
|
|
parentId: creditNoteId,
|
|
});
|
|
if (list)
|
|
slip
|
|
? (fileData.value = await Promise.all(
|
|
list.map(async (v) => {
|
|
const rse = await creditNote.headFile({
|
|
group: 'slip',
|
|
parentId: creditNoteId,
|
|
fileId: v,
|
|
});
|
|
|
|
let contentLength = 0;
|
|
if (rse) contentLength = Number(rse['content-length']);
|
|
|
|
return {
|
|
name: v,
|
|
progress: 1,
|
|
loaded: contentLength,
|
|
total: contentLength,
|
|
url: `/credit-note/${creditNoteId}/file-slip/${v}`,
|
|
};
|
|
}),
|
|
))
|
|
: (attachmentData.value = await Promise.all(
|
|
list.map(async (v) => {
|
|
const rse = await creditNote.headAttachment({
|
|
parentId: creditNoteId,
|
|
fileId: v,
|
|
});
|
|
|
|
let contentLength = 0;
|
|
if (rse) contentLength = Number(rse['content-length']);
|
|
|
|
return {
|
|
name: v,
|
|
progress: 1,
|
|
loaded: contentLength,
|
|
total: contentLength,
|
|
url: `/credit-note/${creditNoteId}/attachment/${v}`,
|
|
};
|
|
}),
|
|
));
|
|
}
|
|
|
|
function fileToUrl(file: File) {
|
|
return URL.createObjectURL(file);
|
|
}
|
|
|
|
function storeDataLocal() {
|
|
localStorage.setItem(
|
|
'credit-note-preview',
|
|
JSON.stringify({
|
|
data: {
|
|
...formData.value,
|
|
id:
|
|
route.name === 'CreditNoteNew' ? undefined : creditNoteData.value?.id,
|
|
customerBranchId: quotationData.value?.customerBranchId,
|
|
registeredBranchId: quotationData.value?.registeredBranchId,
|
|
},
|
|
meta: {
|
|
source: {
|
|
code:
|
|
route.name === 'CreditNoteNew' ? '-' : creditNoteData.value?.code,
|
|
createAt:
|
|
route.name === 'CreditNoteNew'
|
|
? Date.now()
|
|
: creditNoteData.value?.createdAt,
|
|
createBy: creditNoteData.value?.createdBy,
|
|
contactName: quotationData.value?.contactName,
|
|
contactTel: quotationData.value?.contactTel,
|
|
workName: quotationData.value?.workName,
|
|
},
|
|
summaryPrice: summaryPrice.value,
|
|
taskListGroup: { ...taskListGroup.value },
|
|
agentPrice: quotationData.value?.agentPrice,
|
|
|
|
createdBy: getName(),
|
|
},
|
|
}),
|
|
);
|
|
|
|
const url = new URL('/credit-note/document-view', window.location.origin);
|
|
|
|
window.open(url, '_blank');
|
|
}
|
|
|
|
function closeTab() {
|
|
dialogWarningClose(t, {
|
|
message: t('dialog.message.close'),
|
|
action: () => {
|
|
window.close();
|
|
},
|
|
cancel: () => {},
|
|
});
|
|
}
|
|
|
|
function undo() {
|
|
assignFormData();
|
|
pageState.mode = 'info';
|
|
}
|
|
|
|
onMounted(async () => {
|
|
initTheme();
|
|
initLang();
|
|
await useConfigStore().getConfig();
|
|
await getCreditNote();
|
|
await getQuotation();
|
|
if (creditNoteData.value) {
|
|
pageState.mode = 'info';
|
|
await getFileList(creditNoteData.value.id, true);
|
|
}
|
|
initStatus();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="column surface-0 fullscreen">
|
|
<div class="color-bar" :class="{ ['dark']: $q.dark.isActive }">
|
|
<div :class="{ ['indigo-segment']: true }"></div>
|
|
<div :class="{ ['light-indigo-segment']: true }"></div>
|
|
<div class="white-segment"></div>
|
|
</div>
|
|
|
|
<!-- SEC: Header -->
|
|
<header
|
|
class="row q-px-md q-py-sm items-center justify-between relative-position"
|
|
>
|
|
<section class="banner" :class="{ dark: $q.dark.isActive }"></section>
|
|
<div style="flex: 1" class="row items-center">
|
|
<RouterLink to="/credit-note">
|
|
<q-img src="/icons/favicon-512x512.png" width="3rem" />
|
|
</RouterLink>
|
|
<span class="column text-h6 text-bold q-ml-md">
|
|
{{ $t('creditNote.title') }}
|
|
<!-- {{ code || '' }} -->
|
|
<span class="text-caption text-regular app-text-muted">
|
|
{{
|
|
$t('quotation.processOn', {
|
|
msg: dateFormatJS({
|
|
date: creditNoteData?.createdAt || new Date(Date.now()),
|
|
monthStyle: 'long',
|
|
}),
|
|
})
|
|
}}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- SEC: Body -->
|
|
<article
|
|
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">
|
|
<nav
|
|
class="surface-1 q-pa-sm row no-wrap full-width scroll rounded"
|
|
style="gap: 10px"
|
|
>
|
|
<StateButton
|
|
v-for="i in statusTabForm"
|
|
:key="i.title"
|
|
:label="
|
|
$t(
|
|
`creditNote${i.title === 'title' ? '' : '.status'}.${i.title}`,
|
|
)
|
|
"
|
|
:status-active="i.active?.()"
|
|
:status-done="i.status === 'done'"
|
|
:status-waiting="i.status === 'waiting'"
|
|
@click="i.handler()"
|
|
/>
|
|
</nav>
|
|
|
|
<DocumentExpansion
|
|
:noLink="
|
|
route.query['isDebitNote'] === 'true' ||
|
|
quotationData?.code.startsWith('DN')
|
|
"
|
|
readonly
|
|
:registered-branch-id="quotationData?.registeredBranchId"
|
|
:customer-id="quotationData?.customerBranchId"
|
|
:quotation-code="quotationData?.code || '-'"
|
|
:quotation-work-name="quotationData?.workName || '-'"
|
|
:quotation-contact-name="quotationData?.contactName || '-'"
|
|
:quotation-contact-tel="quotationData?.contactTel || '-'"
|
|
@goto-quotation="goToQuotation"
|
|
/>
|
|
|
|
<q-form
|
|
ref="refForm"
|
|
greedy
|
|
@submit.prevent
|
|
@validation-success="submit"
|
|
>
|
|
<CreditNoteExpansion
|
|
v-if="view === null"
|
|
:readonly="pageState.mode === 'info'"
|
|
v-model:reason="formData.reason"
|
|
v-model:detail="formData.detail"
|
|
/>
|
|
</q-form>
|
|
|
|
<ProductExpansion
|
|
v-if="view === null"
|
|
creditNote
|
|
:readonly="pageState.mode === 'info'"
|
|
:agentPrice="quotationData?.agentPrice"
|
|
:task-list="taskListGroup"
|
|
@add-product="openProductDialog"
|
|
/>
|
|
|
|
<PaymentExpansion
|
|
v-if="view === null"
|
|
:readonly="pageState.mode === 'info'"
|
|
:total-price="summaryPrice.finalPrice"
|
|
v-model:payback-type="formData.paybackType"
|
|
v-model:payback-bank="formData.paybackBank"
|
|
v-model:payback-account="formData.paybackAccount"
|
|
v-model:payback-account-name="formData.paybackAccountName"
|
|
/>
|
|
|
|
<RefundInformation
|
|
v-if="view === CreditNoteStatus.Pending"
|
|
:readonly="!canAccess('related', 'edit')"
|
|
:total="creditNoteData?.value"
|
|
:paid="
|
|
creditNoteData?.paybackStatus === CreditNotePaybackStatus.Done
|
|
? creditNoteData?.value
|
|
: 0
|
|
"
|
|
:remain="
|
|
creditNoteData?.paybackStatus === CreditNotePaybackStatus.Pending
|
|
? creditNoteData?.value
|
|
: 0
|
|
"
|
|
:payback-status="creditNoteData?.paybackStatus"
|
|
v-model:payback-type="formData.paybackType"
|
|
v-model:payback-bank="formData.paybackBank"
|
|
v-model:payback-account="formData.paybackAccount"
|
|
v-model:payback-account-name="formData.paybackAccountName"
|
|
v-model:file-data="fileData"
|
|
:transform-url="
|
|
async (url: string) => {
|
|
const result = await api.get<string>(url);
|
|
return result.data;
|
|
}
|
|
"
|
|
@change-status="changePaybackStatus"
|
|
@upload="
|
|
async (f) => {
|
|
if (!creditNoteData) return;
|
|
|
|
fileList = f;
|
|
|
|
await uploadFile(creditNoteData.id, f, true);
|
|
}
|
|
"
|
|
@remove="
|
|
async (n) => {
|
|
if (!creditNoteData) return;
|
|
await remove(creditNoteData.id, n, true);
|
|
}
|
|
"
|
|
/>
|
|
|
|
<AdditionalFileExpansion
|
|
v-if="view !== CreditNoteStatus.Success"
|
|
:readonly="isRoleInclude(['sale', 'head_of_sale'])"
|
|
v-model:file-data="attachmentData"
|
|
:transform-url="
|
|
async (url: string) => {
|
|
if (!creditNoteData?.id) {
|
|
return url;
|
|
} else {
|
|
const result = await api.get<string>(url);
|
|
return result.data;
|
|
}
|
|
}
|
|
"
|
|
@fetch-file-list="
|
|
() => {
|
|
if (!creditNoteData) return;
|
|
getFileList(creditNoteData.id);
|
|
}
|
|
"
|
|
@upload="
|
|
async (f) => {
|
|
attachmentList = f;
|
|
|
|
if (!creditNoteData) {
|
|
attachmentData = [];
|
|
|
|
Array.from(f).forEach((el) => {
|
|
attachmentData.push({
|
|
name: el.name,
|
|
progress: 1,
|
|
loaded: 0,
|
|
total: el.size,
|
|
placeholder: true,
|
|
url: fileToUrl(el),
|
|
});
|
|
});
|
|
} else {
|
|
await uploadFile(creditNoteData.id, f);
|
|
}
|
|
}
|
|
"
|
|
@remove="
|
|
async (n) => {
|
|
if (!creditNoteData) {
|
|
const attIndex = attachmentData.findIndex(
|
|
(v) => v.name === n,
|
|
);
|
|
|
|
attachmentData.splice(attIndex, 1);
|
|
} else {
|
|
await remove(creditNoteData.id, n);
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
|
|
<RemarkExpansion
|
|
v-if="view !== CreditNoteStatus.Success"
|
|
v-model:remark="formData.remark"
|
|
:default-remark="defaultRemark"
|
|
:items="[]"
|
|
:readonly="pageState.mode === 'info'"
|
|
></RemarkExpansion>
|
|
|
|
<QuotationFormReceipt
|
|
v-if="creditNoteData && view === CreditNoteStatus.Success"
|
|
hide-example-btn
|
|
:title="$t('creditNote.label.refund')"
|
|
:amount="creditNoteData.value"
|
|
:success-label="$t('creditNote.label.refundSuccess')"
|
|
:date="
|
|
creditNoteData.paybackDate
|
|
? new Date(creditNoteData.paybackDate)
|
|
: undefined
|
|
"
|
|
@view="openSlipDialog"
|
|
>
|
|
<template #payType>
|
|
<span v-if="creditNoteData.paybackType === 'BankTransfer'">
|
|
<q-img
|
|
:src="`/img/bank/${creditNoteData?.paybackBank}.png`"
|
|
class="bordered q-mr-xs"
|
|
style="border-radius: 50%; width: 20px"
|
|
/>
|
|
{{ useOptionStore().mapOption(creditNoteData?.paybackBank) }}
|
|
{{ creditNoteData?.paybackAccount }}
|
|
{{
|
|
`${$t('creditNote.label.accountName')} ${creditNoteData?.paybackAccountName}`
|
|
}}
|
|
</span>
|
|
<span v-else>{{ $t('creditNote.label.Cash') }}</span>
|
|
</template>
|
|
</QuotationFormReceipt>
|
|
</div>
|
|
</section>
|
|
</article>
|
|
|
|
<!-- SEC: footer -->
|
|
<footer class="surface-1 q-pa-md full-width">
|
|
<nav class="row justify-end" style="gap: var(--size-2)">
|
|
<!-- TODO: view example -->
|
|
<MainButton
|
|
class="q-mr-auto"
|
|
outlined
|
|
icon="mdi-play-box-outline"
|
|
color="207 96% 32%"
|
|
@click="storeDataLocal()"
|
|
>
|
|
{{ $t('general.view', { msg: $t('general.example') }) }}
|
|
</MainButton>
|
|
|
|
<UndoButton v-if="pageState.mode === 'edit'" outlined @click="undo()" />
|
|
<CancelButton
|
|
v-if="pageState.mode !== 'edit'"
|
|
@click="closeTab()"
|
|
:label="$t('dialog.action.close')"
|
|
outlined
|
|
/>
|
|
<SaveButton
|
|
v-if="pageState.mode === 'edit'"
|
|
:disabled="taskListGroup.length === 0"
|
|
@click="(e) => refForm?.submit(e)"
|
|
solid
|
|
/>
|
|
<EditButton
|
|
v-if="
|
|
pageState.mode === 'info' &&
|
|
creditNoteData?.creditNoteStatus === CreditNoteStatus.Waiting
|
|
"
|
|
class="no-print"
|
|
@click="pageState.mode = 'edit'"
|
|
solid
|
|
/>
|
|
<SaveButton
|
|
v-if="
|
|
!creditNoteData ||
|
|
(creditNoteData?.creditNoteStatus === CreditNoteStatus.Waiting &&
|
|
canAccess('related', 'edit'))
|
|
"
|
|
:disabled="taskListGroup.length === 0 || pageState.mode === 'edit'"
|
|
type="submit"
|
|
@click.stop="(e) => refForm?.submit(e)"
|
|
:label="
|
|
$t(
|
|
`creditNote.label.${creditNoteData?.creditNoteStatus ? 'submit' : 'request'}`,
|
|
)
|
|
"
|
|
icon="mdi-account-multiple-check-outline"
|
|
solid
|
|
/>
|
|
</nav>
|
|
</footer>
|
|
</div>
|
|
|
|
<!-- SEC: Dialog -->
|
|
<SelectReadyRequestWork
|
|
v-if="quotationData"
|
|
creditNote
|
|
:task-list-group="taskListGroup"
|
|
:fetch-params="{ cancelOnly: true, quotationId: quotationData.id }"
|
|
v-model:open="pageState.productDialog"
|
|
v-model:task-list="formTaskList"
|
|
/>
|
|
|
|
<DialogViewFile
|
|
hide-tab
|
|
download
|
|
v-model="pageState.fileDialog"
|
|
:url="fileData[0]?.url"
|
|
:transform-url="
|
|
async (url: string) => {
|
|
const result = await api.get<string>(url);
|
|
return result.data;
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.color-bar {
|
|
width: 100%;
|
|
height: 1vh;
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgb(47, 68, 173) 0%,
|
|
rgba(255, 255, 255, 1) 77%,
|
|
rgba(204, 204, 204, 1) 100%
|
|
);
|
|
display: flex;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.color-bar.dark {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.indigo-segment {
|
|
background-color: var(--indigo-10);
|
|
flex-grow: 4;
|
|
}
|
|
|
|
.light-indigo-segment {
|
|
background-color: hsla(var(--indigo-10-hsl) / 0.2);
|
|
flex-grow: 0.5;
|
|
}
|
|
|
|
.white-segment {
|
|
background-color: #ffffff;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.indigo-segment,
|
|
.light-indigo-segment,
|
|
.white-segment {
|
|
transform: skewX(-60deg);
|
|
}
|
|
|
|
.banner {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: url('/images/building-banner.png');
|
|
background-repeat: no-repeat;
|
|
background-size: cover;
|
|
z-index: -1;
|
|
|
|
&.dark {
|
|
filter: invert(100%);
|
|
}
|
|
}
|
|
</style>
|