jws-frontend/src/pages/09_task-order/SelectReadyRequestWork.vue
puriphatt f646b3c9ba
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 7s
refactor: credit note role check
2025-07-04 17:13:07 +07:00

400 lines
11 KiB
Vue

<script setup lang="ts">
import { computed, ref, reactive, watch, onMounted } from 'vue';
import {
useRequestList,
RequestWork,
RequestWorkStatus,
RequestDataStatus,
} from 'src/stores/request-list';
import DialogHeader from 'src/components/dialog/DialogHeader.vue';
import CancelButton from 'src/components/button/CancelButton.vue';
import SaveButton from 'src/components/button/SaveButton.vue';
import DialogFormContainer from 'src/components/dialog/DialogFormContainer.vue';
import TableEmployee from './TableEmployee.vue';
import FormGroupHead from '../08_request-list/FormGroupHead.vue';
import NoData from 'src/components/NoData.vue';
import { baseUrl } from 'src/stores/utils';
import { Task, TaskStatus } from 'src/stores/task-order/types';
const emit = defineEmits<{
(e: 'select', value: RequestWork[]): void;
(e: 'afterSubmit'): void;
}>();
const props = defineProps<{
taskListGroup?: {
product:
| RequestWork['productService']['product']
| RequestWork['productService'];
list: (RequestWork & {
_template?: {
id: string;
templateName: string;
templateStepName: string;
step: number;
responsibleInstitution: (string | { group: string })[];
} | null;
})[];
}[];
creditNote?: boolean;
fetchParams?: Parameters<typeof requestListStore.getRequestWorkList>[0];
}>();
const requestListStore = useRequestList();
const taskList = defineModel<
{
step?: number;
requestWorkId: string;
requestWorkStep?: Task | { requestWork: RequestWork };
}[]
>('taskList', {
default: [],
});
const open = defineModel<boolean>('open', { default: false });
const tempGroupEdit = defineModel<
{
product: RequestWork['productService']['product'];
list: (RequestWork & {
_template?: {
id: string;
templateName: string;
templateStepName: string;
step: number;
responsibleInstitution: (string | { group: string })[];
} | null;
})[];
}[]
>('tempGroupEdit', { default: [] });
const selectedEmployee = ref<
(RequestWork & {
taskStatus: TaskStatus;
_template?: {
id: string;
templateName: string;
templateStepName: string;
step: number;
} | null;
})[]
>([]);
let data = ref<RequestWork[]>([]);
let group = computed(() =>
data.value.reduce<
{
product: RequestWork['productService']['product'];
list: (RequestWork & {
_template?: {
id: string;
templateName: string;
templateStepName: string;
step: number;
responsibleInstitution: (string | { group: string })[];
} | null;
})[];
}[]
>((acc, curr) => {
let exist = acc.find(
(item) => curr.productService.productId == item.product.id,
);
if (exist) exist.list.push({ ...curr, _template: getTemplateData(curr) });
else
acc.push({
product: curr.productService.product,
// list: [curr],
list: [{ ...curr, _template: getTemplateData(curr) }],
});
return acc;
}, []),
);
let state = reactive({
search: '',
});
onMounted(async () => {
await getList();
if (tempGroupEdit.value.length === 0) {
tempGroupEdit.value = JSON.parse(JSON.stringify(group.value));
}
});
watch(() => state.search, getList);
async function getList() {
let res = await requestListStore.getRequestWorkList({
page: 1,
pageSize: 99999,
query: state.search,
...props.fetchParams,
});
if (!res) return;
data.value = res.result;
}
function getStep(requestWork: RequestWork) {
if (!requestWork.stepStatus) return 0;
const target = requestWork.stepStatus.find(
(v) =>
v.workStatus === RequestWorkStatus.Ready ||
v.workStatus === RequestWorkStatus.InProgress,
);
return target?.step || 0;
}
function getTemplateData(requestWork: RequestWork) {
const target = getStep(requestWork);
if (!target) return null;
const flow = requestWork.productService.service?.workflow;
if (!flow) return null;
const step = flow.step.find((v) => v.order === target);
if (!step) return null;
return {
id: step.id,
step: step.order,
templateName: flow.name,
templateStepName: step.name || '-',
responsibleInstitution: step.responsibleInstitution || [],
};
}
function submit() {
let selected: {
step: number;
requestWorkId: string;
requestWorkStep?: Task;
}[] = [];
selectedEmployee.value.forEach((v, i) => {
if (!v.stepStatus || v.stepStatus.length === 0) {
selected.push({
step: 0,
requestWorkId: v.id || '',
requestWorkStep: {
taskOrderId: '',
requestWork: v,
step: 0,
workStatus: '',
requestWorkId: '',
attributes: undefined,
},
});
} else {
const curr = v.stepStatus.find(
(s) =>
s.workStatus ===
(props.creditNote
? RequestWorkStatus.Canceled
: RequestWorkStatus.Ready) ||
s.workStatus ===
(props.creditNote
? RequestWorkStatus.Canceled
: RequestWorkStatus.InProgress) ||
v.request.requestDataStatus === RequestDataStatus.Canceled,
);
if (curr) {
const task: Task = {
...curr,
attributes: curr.attributes,
workStatus:
curr.workStatus || props.creditNote
? RequestWorkStatus.Ready
: RequestWorkStatus.Canceled,
taskOrderId: '',
requestWork: selectedEmployee.value[i],
};
selected.push({
step: task.step,
requestWorkId: task.requestWorkId,
requestWorkStep: task,
});
}
}
});
if (selected) {
taskList.value = selected;
emit('afterSubmit');
}
open.value = false;
}
function close() {
open.value = false;
}
function onDialogOpen() {
// assign selected to group
assignTempGroup();
// match group to check
selectedEmployee.value = [];
if (taskList.value.length === 0) return;
const matchingItems = tempGroupEdit.value
.flatMap((g) => g.list)
.filter((l) => {
if (!l.stepStatus || l.stepStatus.length === 0) {
return taskList.value.some(
(t) => t.requestWorkStep?.requestWork.id === l.id,
);
} else {
return l.stepStatus.some((s) =>
taskList.value.some((t) => s.requestWorkId === t.requestWorkId),
);
}
});
selectedEmployee.value = JSON.parse(JSON.stringify(matchingItems));
}
function assignTempGroup() {
if (!props.taskListGroup) return;
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(
(g) => g.product.id === productId,
);
if (existingGroup) {
newGroup.list.forEach((newItem) => {
if (!existingGroup.list.some((item) => item.id === newItem.id)) {
existingGroup.list.push(newItem);
}
});
} else {
tempGroupEdit.value.push({
product: props.creditNote
? (newGroup.product as RequestWork['productService']).product
: (newGroup.product as RequestWork['productService']['product']),
list: [...newGroup.list], // Ensure a new reference
});
}
});
}
</script>
<template>
<DialogFormContainer v-model="open" v-on:open="onDialogOpen">
<template #header>
<DialogHeader
:title="$t('general.list', { msg: $t('productService.title') })"
/>
</template>
<section class="col column full-width no-wrap surface-2 scroll">
<template v-if="tempGroupEdit.length > 0">
<div
v-for="{ product, list } in tempGroupEdit"
:key="product.id"
class="bordered-b"
>
<q-expansion-item
dense
class="overflow-hidden"
switch-toggle-side
style="border-radius: var(--radius-2)"
:default-opened="
list.some((v) => selectedEmployee.some((s) => s.id === v.id))
"
expand-icon="mdi-chevron-down-circle"
header-class="q-py-sm text-medium text-body items-center rounded q-mx-md q-my-sm"
>
<template #header>
<q-avatar class="q-mr-md" size="md">
<q-img
class="text-center"
:ratio="1"
:src="`${baseUrl}/product/${product.id}/image/${product.selectedImage}`"
>
<template #error>
<q-icon
class="full-width full-height"
name="mdi-shopping-outline"
:style="`color: var(--teal-10); background: hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`"
/>
</template>
</q-img>
</q-avatar>
<span>
{{ product.name }}
<div class="app-text-muted text-caption">
{{ product.code }}
</div>
</span>
<span class="q-ml-auto">
<div
class="rounded q-px-sm row items-center"
style="background: hsl(var(--text-mute) / 0.15)"
>
<q-icon
name="mdi-account-group-outline"
size="xs"
class="q-pr-sm"
/>
{{ list.length }}
</div>
</span>
</template>
<div>
<FormGroupHead>
{{ $t('quotation.employeeList') }}
</FormGroupHead>
<div class="q-pa-md full-width">
<TableEmployee
checkbox-on
check-all
select-ready
:step-on="!creditNote"
:statusOn="creditNote"
:rows="
list.map((v) =>
Object.assign(v, { _template: getTemplateData(v) }),
)
"
v-model:selected-employee="selectedEmployee"
/>
</div>
</div>
</q-expansion-item>
</div>
</template>
<div v-else class="row items-center justify-center full-height">
<NoData :text="$t('taskOrder.noRequestAvailable')" />
</div>
</section>
<template #footer>
<CancelButton class="q-ml-auto" outlined @click="close" />
<SaveButton
:label="$t('general.select')"
class="q-ml-sm"
icon="mdi-check"
solid
@click="submit"
/>
</template>
</DialogFormContainer>
</template>
<style scoped>
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
color: hsl(var(--text-mute));
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}
.active {
background: red;
}
</style>