jws-frontend/src/pages/09_task-order/SelectReadyRequestWork.vue
Methapon Metanipat 5e2100eb8d
feat: credit note (#171)
* feat: add main page credit note

* feat: enable credit note route and update menu item states

* refactor: add i18n

* refactor: edit i18n status

* feat: add action column

* feat: add empty form page

* feat: add get data

* feat: add type credit note status

* refactor: add type name en

* refactor: add type credit note status in type credit note

* feat: add hsla colors

* refactor: add slot grid

* refactor: handle  hide kebab edit show only tab tssued

* feat: show grid card

* feat: i18n

* feat: add credit note form and dialog

* refactor: add props hide kebab deelete

* refactor: hide kebab

* style: update color segments to indigo theme

* feat: i18n

* fix: update labels for credit note fields

* refactor: add type

* feat: new select quotation

* refactor: use new select quotation

* feat: navigate to

* refactor: function trigger and navigate to

* feat: i18n bank

* feat: add payment expansion component and integrate into credit note form

* refactor: bind i18n pay condition

* refactor: navigate to get quotation id

* feat: i18n

* fix: update label for createdBy field in credit note form

* feat: add credit note information expansion component

* feat: add Credit Note expansion component and update form layout

* refactor: bind quotation id and send

* refactor: deelete duplicate type

* refactor: show state button

* refactor: handle show status

* feat: add function update payback status

* feat: add return and canceled reasons to credit note translations

* feat: enhance SelectReadyRequestWork component with credit note handling and fetch parameters

* feat: type

* feat: add status handling and optional display for employee table

* refactor: rename selectedQuotationId to quotationId in FormCredit component

* feat: set default opened state for CreditNoteExpansion and add reason options

* feat: update PaymentExpansion to handle payback type selection and clear fields for cash payments

* feat: enhance ProductExpansion to support credit note handling and adjust price calculations

* feat: implement product handling and price calculation in CreditNote form

* feat: add manage attachment function to store

* refactor: bind delete credit note

* feat: add credit note status and reference fields to types

* refactor: update task step handling and simplify request work structure in credit note form

* feat: add navigation to quotation from credit note form

* feat: enhance upload section layout based on file data

* feat: add readonly functionality to credit note form and related components

* refactor: remove console log

* feat: update i18n

* style: add rounded corners to complete view container in quotation form

* feat: add RefundInformation component and update credit note form status handling

* feat: i18n

* feat: update payback status endpoint and add paybackStatus to CreditNote type

* feat: enhance QuotationFormReceipt component with optional props and slot support

* feat: integrate payback status handling in RefundInformation and FormPage components

* feat: add external file group

* feat: update API endpoint paths for credit note operations

* feat: improve layout and styling in UploadFile components

* feat: implement file upload and management in Credit Note

* refactor: update upload to check if it is redirect or not

* feat: upload file slips

* feat: add payback date dispaly

* refactor: change module no

* fix: icon link to main page instead

* feat: add file dialog with image download functionality

* fix: view slip

* feat: add download button to image viewer

* feat: handle after submit

* feat: conditionally render bank transfer information

* feat: handle upload file on create

* feat: handle change payback status

* feat: payback type in credit note form

* fix: correct reference to quotation data in goToQuotation function

---------

Co-authored-by: Methapon2001 <61303214+Methapon2001@users.noreply.github.com>
Co-authored-by: puriphatt <puriphat@frappet.com>
Co-authored-by: Thanaphon Frappet <thanaphon@frappet.com>
2025-01-14 09:08:31 +07:00

315 lines
8.4 KiB
Vue

<script setup lang="ts">
import { computed, ref, reactive, watch, onMounted } from 'vue';
import {
useRequestList,
RequestWork,
RequestWorkStatus,
} 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<{
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 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[];
}[]
>((acc, curr) => {
let exist = acc.find(
(item) => curr.productService.productId == item.product.id,
);
if (exist) exist.list.push(curr);
else acc.push({ product: curr.productService.product, list: [curr] });
return acc;
}, []),
);
let state = reactive({
search: '',
});
onMounted(getList);
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 toggle(item: RequestWork) {
// switch (selected(item)) {
// case true:
// return deselect(item);
// case false:
// return select(item);
// }
// }
// function select(item: RequestWork) {
// if (selected(item)) return;
// selectedEmployee.value = selectedEmployee.value
// ? selectedEmployee.value.concat(item)
// : [item];
// }
// function deselect(item: RequestWork) {
// const idx = selectedEmployee.value?.findIndex((v) => v.id === item.id);
// if (idx !== -1) selectedEmployee.value?.splice(idx, 1);
// }
// function selected(item: RequestWork): boolean {
// return !!selectedEmployee.value?.some((v) => v.id === item.id);
// }
//
function getStep(requestWork: RequestWork) {
const target = requestWork.stepStatus.find(
(v) => v.workStatus === RequestWorkStatus.Ready,
);
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 || '-',
};
}
function submit() {
let selected: {
step: number;
requestWorkId: string;
requestWorkStep?: Task;
}[] = [];
selectedEmployee.value.forEach((v, i) => {
const curr = v.stepStatus.find(
(s) =>
s.workStatus ===
(props.creditNote
? RequestWorkStatus.Canceled
: RequestWorkStatus.Ready),
);
if (curr) {
const task: Task = {
...curr,
attributes: curr.attributes,
workStatus: props.creditNote
? RequestWorkStatus.Canceled
: RequestWorkStatus.Ready,
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() {
selectedEmployee.value = [];
if (taskList.value.length === 0) return;
const matchingItems = group.value
.flatMap((g) => g.list)
.filter((l) =>
l.stepStatus.some((s) =>
taskList.value.some((t) => s.requestWorkId === t.requestWorkId),
),
);
selectedEmployee.value = JSON.parse(JSON.stringify(matchingItems));
}
</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="group.length > 0">
<div
v-for="{ product, list } in group"
: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
: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>