feat: enhance credit note functionality and improve task order handling

This commit is contained in:
puriphatt 2025-02-24 16:20:56 +07:00
parent 91c7db7c7b
commit dc225ac31b
3 changed files with 125 additions and 36 deletions

View file

@ -23,7 +23,9 @@ const emit = defineEmits<{
const props = defineProps<{ const props = defineProps<{
taskListGroup?: { taskListGroup?: {
product: RequestWork['productService']['product']; product:
| RequestWork['productService']['product']
| RequestWork['productService'];
list: (RequestWork & { list: (RequestWork & {
_template?: { _template?: {
id: string; id: string;
@ -135,6 +137,7 @@ async function getList() {
} }
function getStep(requestWork: RequestWork) { function getStep(requestWork: RequestWork) {
if (!requestWork.stepStatus) return 0;
const target = requestWork.stepStatus.find( const target = requestWork.stepStatus.find(
(v) => (v) =>
v.workStatus === RequestWorkStatus.Ready || v.workStatus === RequestWorkStatus.Ready ||
@ -166,7 +169,7 @@ function submit() {
requestWorkStep?: Task; requestWorkStep?: Task;
}[] = []; }[] = [];
selectedEmployee.value.forEach((v, i) => { selectedEmployee.value.forEach((v, i) => {
if (v.stepStatus.length === 0) { if (!v.stepStatus || v.stepStatus.length === 0) {
selected.push({ selected.push({
step: 0, step: 0,
requestWorkId: v.id || '', requestWorkId: v.id || '',
@ -224,7 +227,7 @@ function close() {
function onDialogOpen() { function onDialogOpen() {
// assign selected to group // assign selected to group
!props.creditNote && assignTempGroup(); assignTempGroup();
// match group to check // match group to check
selectedEmployee.value = []; selectedEmployee.value = [];
@ -232,7 +235,7 @@ function onDialogOpen() {
const matchingItems = tempGroupEdit.value const matchingItems = tempGroupEdit.value
.flatMap((g) => g.list) .flatMap((g) => g.list)
.filter((l) => { .filter((l) => {
if (l.stepStatus.length === 0) { if (!l.stepStatus || l.stepStatus.length === 0) {
return taskList.value.some( return taskList.value.some(
(t) => t.requestWorkStep?.requestWork.id === l.id, (t) => t.requestWorkStep?.requestWork.id === l.id,
); );
@ -248,8 +251,12 @@ function onDialogOpen() {
function assignTempGroup() { function assignTempGroup() {
if (!props.taskListGroup) return; if (!props.taskListGroup) return;
props.taskListGroup.forEach((newGroup) => { props.taskListGroup.forEach((newGroup) => {
const productId = props.creditNote
? (newGroup.product as RequestWork['productService']).productId
: (newGroup.product as RequestWork['productService']['product']).id;
const existingGroup = tempGroupEdit.value.find( const existingGroup = tempGroupEdit.value.find(
(g) => g.product.id === newGroup.product.id, (g) => g.product.id === productId,
); );
if (existingGroup) { if (existingGroup) {
@ -260,7 +267,9 @@ function assignTempGroup() {
}); });
} else { } else {
tempGroupEdit.value.push({ tempGroupEdit.value.push({
...newGroup, product: props.creditNote
? (newGroup.product as RequestWork['productService']).product
: (newGroup.product as RequestWork['productService']['product']),
list: [...newGroup.list], // Ensure a new reference list: [...newGroup.list], // Ensure a new reference
}); });
} }

View file

@ -24,7 +24,13 @@ import SelectReadyRequestWork from '../09_task-order/SelectReadyRequestWork.vue'
import RefundInformation from './RefundInformation.vue'; import RefundInformation from './RefundInformation.vue';
import QuotationFormReceipt from '../05_quotation/QuotationFormReceipt.vue'; import QuotationFormReceipt from '../05_quotation/QuotationFormReceipt.vue';
import DialogViewFile from 'src/components/dialog/DialogViewFile.vue'; import DialogViewFile from 'src/components/dialog/DialogViewFile.vue';
import { MainButton, SaveButton } from 'src/components/button'; import {
MainButton,
SaveButton,
CancelButton,
EditButton,
UndoButton,
} from 'src/components/button';
import { RequestWork } from 'src/stores/request-list/types'; import { RequestWork } from 'src/stores/request-list/types';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
@ -76,15 +82,16 @@ const statusTabForm = ref<
}[] }[]
>([]); >([]);
const readonly = computed( // const readonly = computed(
() => // () =>
creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Pending || // creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Pending ||
creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Success, // creditNoteData.value?.creditNoteStatus === CreditNoteStatus.Success,
); // );
const pageState = reactive({ const pageState = reactive({
productDialog: false, productDialog: false,
fileDialog: false, fileDialog: false,
mode: 'view' as 'view' | 'edit' | 'info',
}); });
const defaultRemark = '#[quotation-labor]<br/><br/>#[quotation-payment]'; const defaultRemark = '#[quotation-labor]<br/><br/>#[quotation-payment]';
@ -161,7 +168,9 @@ async function initStatus() {
{ {
title: 'Pending', title: 'Pending',
status: creditNoteData.value?.id status: creditNoteData.value?.id
? creditNoteData.value.creditNoteStatus === CreditNoteStatus.Success ? creditNoteData.value.creditNoteStatus === CreditNoteStatus.Waiting
? 'waiting'
: creditNoteData.value.creditNoteStatus === CreditNoteStatus.Success
? 'done' ? 'done'
: 'doing' : 'doing'
: 'waiting', : 'waiting',
@ -253,8 +262,9 @@ function assignFormData() {
requestWorkStep: { requestWorkStep: {
requestWork: { requestWork: {
...v, ...v,
stepStatus: v.stepStatus || [],
request: { ...v.request, quotation: current.quotation }, request: { ...v.request, quotation: current.quotation },
}, } as RequestWork,
}, },
})); }));
} }
@ -285,12 +295,24 @@ async function getQuotation() {
async function submit() { async function submit() {
const payload = formData.value; const payload = formData.value;
payload.requestWorkId = formTaskList.value.map((v) => v.requestWorkId); payload.requestWorkId = formTaskList.value.map((v) => v.requestWorkId);
payload.quotationId = payload.quotationId =
typeof route.query['quotationId'] === 'string' (pageState.mode === 'edit'
? creditNoteData.value?.quotationId
: typeof route.query['quotationId'] === 'string'
? route.query['quotationId'] ? route.query['quotationId']
: ''; : '') || '';
const res = await creditNote.createCreditNote(payload);
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) { if (res) {
await router.push(`/credit-note/${res.id}`); await router.push(`/credit-note/${res.id}`);
@ -302,6 +324,7 @@ async function submit() {
} }
initStatus(); initStatus();
pageState.mode = 'info';
} }
} }
@ -526,13 +549,31 @@ function storeDataLocal() {
window.open(url, '_blank'); 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 () => { onMounted(async () => {
initTheme(); initTheme();
initLang(); initLang();
await useConfigStore().getConfig(); await useConfigStore().getConfig();
await getCreditNote(); await getCreditNote();
await getQuotation(); await getQuotation();
creditNoteData.value && (await getFileList(creditNoteData.value.id, true)); if (creditNoteData.value) {
pageState.mode = 'info';
await getFileList(creditNoteData.value.id, true);
}
initStatus(); initStatus();
}); });
</script> </script>
@ -587,7 +628,7 @@ onMounted(async () => {
:key="i.title" :key="i.title"
:label=" :label="
$t( $t(
`creditNote${i.title === 'title' ? '' : '.stats'}.${i.title}`, `creditNote${i.title === 'title' ? '' : '.status'}.${i.title}`,
) )
" "
:status-active="i.active?.()" :status-active="i.active?.()"
@ -616,7 +657,7 @@ onMounted(async () => {
> >
<CreditNoteExpansion <CreditNoteExpansion
v-if="view === null" v-if="view === null"
:readonly="readonly" :readonly="pageState.mode === 'info'"
v-model:reason="formData.reason" v-model:reason="formData.reason"
v-model:detail="formData.detail" v-model:detail="formData.detail"
/> />
@ -625,7 +666,7 @@ onMounted(async () => {
<ProductExpansion <ProductExpansion
v-if="view === null" v-if="view === null"
creditNote creditNote
:readonly="readonly" :readonly="pageState.mode === 'info'"
:agentPrice="quotationData?.agentPrice" :agentPrice="quotationData?.agentPrice"
:task-list="taskListGroup" :task-list="taskListGroup"
@add-product="openProductDialog" @add-product="openProductDialog"
@ -633,7 +674,7 @@ onMounted(async () => {
<PaymentExpansion <PaymentExpansion
v-if="view === null" v-if="view === null"
:readonly="readonly" :readonly="pageState.mode === 'info'"
:total-price="summaryPrice.finalPrice" :total-price="summaryPrice.finalPrice"
v-model:payback-type="formData.paybackType" v-model:payback-type="formData.paybackType"
v-model:payback-bank="formData.paybackBank" v-model:payback-bank="formData.paybackBank"
@ -746,7 +787,7 @@ onMounted(async () => {
v-model:remark="formData.remark" v-model:remark="formData.remark"
:default-remark="defaultRemark" :default-remark="defaultRemark"
:items="[]" :items="[]"
:readonly :readonly="pageState.mode === 'info'"
> >
<template #hint> <template #hint>
{{ $t('general.hintRemark') }} {{ $t('general.hintRemark') }}
@ -793,7 +834,7 @@ onMounted(async () => {
<!-- SEC: footer --> <!-- SEC: footer -->
<footer class="surface-1 q-pa-md full-width"> <footer class="surface-1 q-pa-md full-width">
<nav class="row justify-end"> <nav class="row justify-end" style="gap: var(--size-2)">
<!-- TODO: view example --> <!-- TODO: view example -->
<MainButton <MainButton
class="q-mr-auto" class="q-mr-auto"
@ -804,16 +845,48 @@ onMounted(async () => {
> >
{{ $t('general.view', { msg: $t('general.example') }) }} {{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton> </MainButton>
<!-- @click="submit" -->
<UndoButton v-if="pageState.mode === 'edit'" outlined @click="undo()" />
<CancelButton
v-if="
pageState.mode === 'info' &&
creditNoteData?.creditNoteStatus === CreditNoteStatus.Waiting
"
@click="closeTab()"
:label="$t('dialog.action.close')"
outlined
/>
<SaveButton <SaveButton
v-if="!readonly" v-if="
:disabled="taskListGroup.length === 0" !creditNoteData ||
creditNoteData?.creditNoteStatus === CreditNoteStatus.Waiting
"
:disabled="taskListGroup.length === 0 || pageState.mode === 'edit'"
type="submit" type="submit"
@click.stop="(e) => refForm?.submit(e)" @click.stop="(e) => refForm?.submit(e)"
:label="$t('creditNote.label.submit')" :label="
$t(
`creditNote.label.${creditNoteData?.creditNoteStatus ? 'submit' : 'request'}`,
)
"
icon="mdi-account-multiple-check-outline" icon="mdi-account-multiple-check-outline"
solid solid
></SaveButton> />
<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
/>
</nav> </nav>
</footer> </footer>
</div> </div>
@ -822,6 +895,7 @@ onMounted(async () => {
<SelectReadyRequestWork <SelectReadyRequestWork
v-if="quotationData" v-if="quotationData"
creditNote creditNote
:task-list-group="taskListGroup"
:fetch-params="{ cancelOnly: true, quotationId: quotationData.id }" :fetch-params="{ cancelOnly: true, quotationId: quotationData.id }"
v-model:open="pageState.productDialog" v-model:open="pageState.productDialog"
v-model:task-list="formTaskList" v-model:task-list="formTaskList"

View file

@ -35,7 +35,7 @@ const { stats, pageMax, page, data, pageSize } = storeToRefs(creditNote);
// NOTE: Variable // NOTE: Variable
const pageState = reactive({ const pageState = reactive({
quotationId: '', quotationId: '',
currentTab: CreditNoteStatus.Pending, currentTab: CreditNoteStatus.Waiting,
hideStat: false, hideStat: false,
statusFilter: 'None', statusFilter: 'None',
inputSearch: '', inputSearch: '',
@ -180,16 +180,22 @@ watch(
<StatCardComponent <StatCardComponent
labelI18n labelI18n
:branch="[ :branch="[
{
icon: 'icon-park-outline:loading-one',
count: stats[CreditNoteStatus.Pending] || 0,
label: `creditNote.status.${CreditNoteStatus.Waiting}`,
color: 'light-yellow',
},
{ {
icon: 'material-symbols-light:receipt-long', icon: 'material-symbols-light:receipt-long',
count: stats[CreditNoteStatus.Pending] || 0, count: stats[CreditNoteStatus.Pending] || 0,
label: `creditNote.stats.${CreditNoteStatus.Pending}`, label: `creditNote.status.${CreditNoteStatus.Pending}`,
color: 'orange', color: 'orange',
}, },
{ {
icon: 'mdi-check-decagram-outline', icon: 'mdi-check-decagram-outline',
count: stats[CreditNoteStatus.Success] || 0, count: stats[CreditNoteStatus.Success] || 0,
label: `creditNote.stats.${CreditNoteStatus.Success}`, label: `creditNote.status.${CreditNoteStatus.Success}`,
color: 'blue', color: 'blue',
}, },
]" ]"
@ -345,7 +351,7 @@ watch(
<TableCreditNote <TableCreditNote
:grid="pageState.gridView" :grid="pageState.gridView"
:visible-columns="pageState.fieldSelected" :visible-columns="pageState.fieldSelected"
:hide-delete="pageState.currentTab === CreditNoteStatus.Success" :hide-delete="pageState.currentTab !== CreditNoteStatus.Waiting"
@view="(v) => navigateTo({ statusDialog: 'info', creditId: v.id })" @view="(v) => navigateTo({ statusDialog: 'info', creditId: v.id })"
@delete="(v) => triggerDelete(v.id)" @delete="(v) => triggerDelete(v.id)"
> >