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>
This commit is contained in:
Methapon Metanipat 2025-01-14 09:08:31 +07:00 committed by GitHub
parent 0c694dee5d
commit 5e2100eb8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 2897 additions and 77 deletions

View file

@ -14,20 +14,25 @@ import FormGroupHead from '../08_request-list/FormGroupHead.vue';
import NoData from 'src/components/NoData.vue';
import { baseUrl } from 'src/stores/utils';
import { Task } from 'src/stores/task-order/types';
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;
step?: number;
requestWorkId: string;
requestWorkStep?: Task;
requestWorkStep?: Task | { requestWork: RequestWork };
}[]
>('taskList', {
default: [],
@ -36,6 +41,7 @@ const open = defineModel<boolean>('open', { default: false });
const selectedEmployee = ref<
(RequestWork & {
taskStatus: TaskStatus;
_template?: {
id: string;
templateName: string;
@ -76,7 +82,7 @@ async function getList() {
page: 1,
pageSize: 99999,
query: state.search,
readyToTask: true,
...props.fetchParams,
});
if (!res) return;
@ -141,13 +147,19 @@ function submit() {
selectedEmployee.value.forEach((v, i) => {
const curr = v.stepStatus.find(
(s) => s.workStatus === RequestWorkStatus.Ready,
(s) =>
s.workStatus ===
(props.creditNote
? RequestWorkStatus.Canceled
: RequestWorkStatus.Ready),
);
if (curr) {
const task: Task = {
...curr,
attributes: curr.attributes,
workStatus: RequestWorkStatus.Ready,
workStatus: props.creditNote
? RequestWorkStatus.Canceled
: RequestWorkStatus.Ready,
taskOrderId: '',
requestWork: selectedEmployee.value[i],
};
@ -254,7 +266,8 @@ function onDialogOpen() {
<div class="q-pa-md full-width">
<TableEmployee
checkbox-on
step-on
:step-on="!creditNote"
:statusOn="creditNote"
:rows="
list.map((v) =>
Object.assign(v, { _template: getTemplateData(v) }),

View file

@ -19,6 +19,7 @@ const props = withDefaults(
checkboxOn?: boolean;
checkAll?: boolean;
stepOn?: boolean;
statusOn?: boolean;
rows: QTableProps['rows'];
grid?: boolean;
}>(),
@ -180,7 +181,23 @@ function handleCheck(
},
...employeeColumn.slice(2),
]
: employeeColumn
: statusOn
? [
...employeeColumn,
{
name: 'urgent',
align: 'center',
label: '',
field: (v) => v.product.code,
},
{
name: 'status',
align: 'center',
label: 'general.status',
field: (v) => v.product.code,
},
]
: employeeColumn
"
:rows-per-page-options="[0]"
:no-data-label="$t('general.noDataTable')"
@ -316,7 +333,7 @@ function handleCheck(
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label && $t(col.label) }}
</q-th>
<q-th></q-th>
<q-th v-if="!statusOn"></q-th>
<q-th v-if="$slots.append"></q-th>
<q-th v-if="$slots.action"></q-th>
@ -338,7 +355,7 @@ function handleCheck(
>
<q-tr
:class="{
urgent: props.row.request.quotation.urgent,
urgent: props.row.request.quotation?.urgent,
dark: $q.dark.isActive,
['disabled-row']:
selectedEmployee.length > 0 &&
@ -392,7 +409,7 @@ function handleCheck(
<q-img
class="text-center"
:ratio="1"
:src="`${baseUrl}/employee/${props.row.request.employee.id}/image/${props.row.request.employee.selectedImage}`"
:src="`${baseUrl}/employee/${props.row.request.employee?.id}/image/${props.row.request.employee?.selectedImage}`"
>
<template #error>
<span class="full-width full-height">
@ -407,27 +424,27 @@ function handleCheck(
</div>
<div class="app-text-muted">
{{ props.row.request.employee.code }}
{{ props.row.request.employee?.code }}
</div>
</div>
<Icon
class="q-ml-md"
:class="`app-text-${props.row.request.employee.gender}`"
:icon="`material-symbols:${props.row.request.employee.gender}`"
:class="`app-text-${props.row.request.employee?.gender}`"
:icon="`material-symbols:${props.row.request.employee?.gender}`"
width="24px"
/>
</div>
</q-td>
<q-td>{{ calculateAge(props.row.request.employee.dateOfBirth) }}</q-td>
<q-td>{{ calculateAge(props.row.request.employee?.dateOfBirth) }}</q-td>
<q-td>
{{
useOptionStore().mapOption(props.row.request.employee.nationality)
useOptionStore().mapOption(props.row.request.employee?.nationality)
}}
</q-td>
<q-td>
{{
dateFormatJS({
date: props.row.request.quotation.dueDate,
date: props.row.request.quotation?.dueDate,
locale: $i18n.locale,
dayStyle: '2-digit',
monthStyle: 'short',
@ -436,7 +453,7 @@ function handleCheck(
</q-td>
<q-td>
<ExpirationDate
:expiration-date="new Date(props.row.request.quotation.dueDate)"
:expiration-date="new Date(props.row.request.quotation?.dueDate)"
/>
</q-td>
<q-td>
@ -444,12 +461,12 @@ function handleCheck(
class="cursor-pointer link"
@click="goToQuotation(props.row.request.quotation)"
>
{{ props.row.request.quotation.code }}
{{ props.row.request.quotation?.code }}
</span>
</q-td>
<q-td>
<BadgeComponent
v-if="props.row.request.quotation.urgent"
v-if="props.row.request.quotation?.urgent"
icon="mdi-fire"
:title="$t('general.urgent2')"
hsla-color="--gray-1-hsl"
@ -457,6 +474,12 @@ function handleCheck(
solid
/>
</q-td>
<q-td v-if="statusOn">
<BadgeComponent
:title="$t('creditNote.status.Canceled')"
hsla-color="--red-8-hsl"
/>
</q-td>
<q-td v-if="$slots.append">
<slot name="append" :props="props"></slot>
</q-td>

View file

@ -19,6 +19,7 @@ const fileData = defineModel<
loaded: number;
total: number;
url?: string;
placeholder?: boolean;
}[]
>('fileData', { default: [] });
</script>

View file

@ -26,12 +26,14 @@ const taskProduct = defineModel<{ productId: string; discount?: number }[]>(
},
);
defineProps<{
const props = defineProps<{
readonly?: boolean;
agentPrice?: boolean;
taskList: {
product: RequestWork['productService']['product'];
list: RequestWork[];
}[];
creditNote?: boolean;
}>();
defineEmits<{
@ -55,15 +57,28 @@ function openList(index: number) {
function calcPricePerUnit(product: RequestWork['productService']['product']) {
return product.vatIncluded
? product.serviceCharge / (1 + (config.value?.vat || 0.07))
: product.serviceCharge;
? (props.creditNote
? props.agentPrice
? product.agentPrice
: product.price
: product.serviceCharge) /
(1 + (config.value?.vat || 0.07))
: props.creditNote
? props.agentPrice
? product.agentPrice
: product.price
: product.serviceCharge;
}
function calcPrice(
product: RequestWork['productService']['product'],
amount: number,
) {
const pricePerUnit = product.serviceCharge;
const pricePerUnit = props.creditNote
? props.agentPrice
? product.agentPrice
: product.price
: product.serviceCharge;
const discount =
taskProduct.value.find((v) => v.productId === product.id)?.discount || 0;
const priceNoVat = product.vatIncluded
@ -102,7 +117,16 @@ function calcPrice(
<main class="q-px-md q-py-sm surface-1">
<q-table
:columns="productColumn"
:columns="
creditNote
? productColumn.filter(
(v) =>
v.name !== 'discount' &&
v.name !== 'priceBeforeVat' &&
v.name !== 'vat',
)
: productColumn
"
:rows="taskList"
bordered
flat
@ -174,7 +198,7 @@ function calcPrice(
}}
</q-td>
<!-- TODO: display price detail -->
<q-td align="center">
<q-td align="center" v-if="!creditNote">
<q-input
:readonly
:bg-color="readonly ? 'transparent' : ''"
@ -213,11 +237,11 @@ function calcPrice(
/>
</q-td>
<!-- before vat -->
<q-td class="text-right">
<q-td class="text-right" v-if="!creditNote">
{{ formatNumberDecimal(calcPricePerUnit(props.row.product), 2) }}
</q-td>
<!-- vat -->
<q-td class="text-right">
<q-td class="text-right" v-if="!creditNote">
{{
formatNumberDecimal(
props.row.product.calcVat
@ -267,7 +291,11 @@ function calcPrice(
<q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props">
<q-td colspan="100%" style="padding: 16px">
<TableEmployee step-on :rows="props.row.list" />
<TableEmployee
:step-on="!creditNote"
:status-on="creditNote"
:rows="props.row.list"
/>
</q-td>
</q-tr>
</template>

View file

@ -1130,6 +1130,7 @@ watch([currentFormData.value.taskStatus], () => {
<!-- SEC: Dialog -->
<SelectReadyRequestWork
:fetch-params="{ readyToTask: true }"
v-model:open="pageState.productDialog"
v-model:task-list="currentFormData.taskList"
@after-submit="