feat: menu request list (#75)
* feat: i18n * feat: request list * refactor: hide stat transition on app.scss * feat: request list i18n * feat: request list => constants and main page * feat: add store * feat: add fetch data * feat: add utilities fn * feat: add store function / types * refactor: request list type * refactor: request list constants * refactor: quotation card => add customData and badge color props * feat: avatar group components * feat: request list group * refactor: request list => remove tab, add table data * feat: send search query * feat: add parameter * refactor: remove unused function * fix: rename component lits to list * feat: show stats from api * chore: cleanup * refactor: make it type safe * refactor: accept rotate flow id as parameter * feat: use page size component * feat: add component, data display & expansion product * feat: i18n * refactor: constants and request list table * refactor: type code, createdAt, updatedAt * refactor: utils function changThemeMode * feat: request list => view page * refactor: use type instead of infer from value * fix: function getEmployeeName att type * refactor: fetch work list * refactor: loop work list * feat: add i18n duty * feat: add form issue component * feat: add form issue section * fix: store error * refactor: edit by value * refactor: accept basic info from outside instead * feat: add status filter support on fetch * refactor: remove delete button * refactor: wording * feat/fix: request list i18n & constant * feat: document type * feat/refactor: request list => document expansion * refactor: doc expansion use FormGroupHead * refactor: fetch data based on id from route param * refactor: text area disable * feat: properties expansion display (mocking) * refactor: add document at product relation * refactor: edit get value product * feat: get workflow step to show on top * refactor: add type * refactor: add get attachment * refactor: add view attachment * refactor: edit file name * refactor: define props get hide icon * refactor: edit align row * refactor: by value table document * refactor: by value row table * feat: add independent ocr dialog * chore: clean up * refactor: accept more props and small adjustment * fix: error withDefault call * feat: accept default metadata when open * fix: typo * feat: add override hook when finish ocr * feat: reset state on open * feat: detect reader result is actually string * fix: variable name conflict * feat: properties to input component * feat: properties input in properties expansion * feat: properties expansion data (temporary) * refactor: add i18n status work * refactor: edit type work status and add step status * refactor: add edit status work * refactor: edit step work * refactor: properties data type * refactor: filter selected product & specific properties * feat: add emit event * refactor: change variable name for better understanding * refactor: hide step that no properties * refactor: work status type to validate * feat: work status color * refactor: key for filename * refactor: close expansion when change step * refactor: responsive meta data * refactor: product expansion responsive * fix: dark mode step text color * fix: document expansion table no data label * refactor: main page body bordered and overflow hidden * refactor: use utils function instead * refactor: add process * refactor: by value name * refactor: add upload file * refactor: upload file * refactor: by value * fix: option worker type * refactor: fetchRequestAttachment after edit * fix: metadata display * refactor: add class full-height * refactor: edit type * refactor: fetch file * refactor: by value visa * refactor: request list attributes type * fix: properties to input props (placeholder, readonly, disable) * feat: request list properties function * fix: error when no workflow * docs: update comment to fix indent * refactor: step type (attributes) * refactor: add attributes payload on editStatusRequestWork function * feat/refactor: functional form expansion/filter worklist * refactor: set attributes properties after submit * refactor: add request work ready status * feat: request list => form employee component * feat/refactor: form expansion select user/layout * fix: properties readonly --------- Co-authored-by: puriphatt <puriphat@frappet.com> Co-authored-by: Thanaphon Frappet <thanaphon@frappet.com>
This commit is contained in:
parent
9105dcf7fe
commit
972f6ba13e
36 changed files with 3653 additions and 57 deletions
|
|
@ -121,7 +121,7 @@ type EmployeeRelation = {
|
|||
id: string;
|
||||
};
|
||||
|
||||
type ProductRelation = {
|
||||
export type ProductRelation = {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
|
|
@ -144,6 +144,7 @@ type ProductRelation = {
|
|||
updatedAt: string;
|
||||
updatedByUserId: string;
|
||||
productGroup?: ProductGroup;
|
||||
document?: string[];
|
||||
};
|
||||
|
||||
type WorkRelation = {
|
||||
|
|
|
|||
273
src/stores/request-list/index.ts
Normal file
273
src/stores/request-list/index.ts
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
RequestData,
|
||||
RequestDataStatus,
|
||||
RequestWork,
|
||||
RequestWorkStatus,
|
||||
Step,
|
||||
} from './types';
|
||||
import { api } from 'src/boot/axios';
|
||||
import { PaginationResult } from 'src/types';
|
||||
import {
|
||||
EmployeePassportPayload,
|
||||
EmployeeVisaPayload,
|
||||
} from 'stores/employee/types';
|
||||
|
||||
import { manageAttachment, manageFile, manageMeta } from '../utils';
|
||||
|
||||
export const useRequestList = defineStore('request-list', () => {
|
||||
const data = ref<RequestData[]>([]);
|
||||
const page = ref<number>(1);
|
||||
const pageMax = ref<number>(1);
|
||||
const pageSize = ref<number>(30);
|
||||
const stats = ref<Record<RequestDataStatus, number>>({
|
||||
[RequestDataStatus.Pending]: 0,
|
||||
[RequestDataStatus.InProgress]: 0,
|
||||
[RequestDataStatus.Completed]: 0,
|
||||
});
|
||||
|
||||
type TypeFile =
|
||||
| 'passport'
|
||||
| 'visa'
|
||||
| 'citizen'
|
||||
| 'house-registration'
|
||||
| 'commercial-registration'
|
||||
| 'vat-registration'
|
||||
| 'power-of-attorney';
|
||||
|
||||
async function uploadAttachmentRequest(opt: {
|
||||
id: string;
|
||||
type: 'customer' | 'employee';
|
||||
group: string;
|
||||
file: File;
|
||||
form?: EmployeePassportPayload | EmployeeVisaPayload;
|
||||
name?: string;
|
||||
}) {
|
||||
const base = { customer: 'customer-branch', employee: 'employee' }[
|
||||
opt.type
|
||||
];
|
||||
const attachmentManag = manageAttachment(api, base);
|
||||
const metaManager = manageMeta<TypeFile>(api, base);
|
||||
|
||||
let res;
|
||||
const group = [
|
||||
'passport',
|
||||
'visa',
|
||||
'citizen',
|
||||
'house-registration',
|
||||
'commercial-registration',
|
||||
'vat-registration',
|
||||
'power-of-attorney',
|
||||
];
|
||||
|
||||
console.log(opt.group);
|
||||
|
||||
if (group.includes(opt.group)) {
|
||||
res = await metaManager.postMeta({
|
||||
group: opt.group as TypeFile,
|
||||
parentId: opt.id,
|
||||
meta: opt.form,
|
||||
file: opt.file,
|
||||
});
|
||||
} else {
|
||||
res = await attachmentManag.putAttachment({
|
||||
parentId: opt.id,
|
||||
name: opt.name || '',
|
||||
file: opt.file,
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function viewAttachmentRequest(opt: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'customer' | 'employee';
|
||||
group: string;
|
||||
download?: boolean;
|
||||
}) {
|
||||
const base = { customer: 'customer-branch', employee: 'employee' }[
|
||||
opt.type
|
||||
];
|
||||
const attachmentManag = manageAttachment(api, base);
|
||||
const fileManager = manageFile<TypeFile>(api, base);
|
||||
|
||||
let res;
|
||||
const group = [
|
||||
'passport',
|
||||
'visa',
|
||||
'citizen',
|
||||
'house-registration',
|
||||
'commercial-registration',
|
||||
'vat-registration',
|
||||
'power-of-attorney',
|
||||
];
|
||||
|
||||
if (group.includes(opt.group)) {
|
||||
res = await fileManager.getFile({
|
||||
parentId: opt.id,
|
||||
group: opt.group as TypeFile,
|
||||
fileId: opt.name,
|
||||
download: opt.download,
|
||||
});
|
||||
}
|
||||
|
||||
if (!group.includes(opt.group)) {
|
||||
res = await attachmentManag.getAttachment({
|
||||
parentId: opt.id,
|
||||
name: opt.name,
|
||||
download: opt.download,
|
||||
});
|
||||
}
|
||||
|
||||
if (res) return res;
|
||||
}
|
||||
|
||||
async function getAttachmentRequest(
|
||||
id: string,
|
||||
type: 'customer' | 'employee',
|
||||
) {
|
||||
const base = { customer: 'customer-branch', employee: 'employee' }[type];
|
||||
|
||||
const attachmentManag = manageAttachment(api, base);
|
||||
const fileManager = manageFile<TypeFile>(api, base);
|
||||
|
||||
const resFiles: Partial<Record<string, any>> = {};
|
||||
|
||||
if (type === 'employee') {
|
||||
const resPassport = await fileManager.listFile({
|
||||
group: 'passport',
|
||||
parentId: id,
|
||||
});
|
||||
|
||||
const resVisa = await fileManager.listFile({
|
||||
group: 'visa',
|
||||
parentId: id,
|
||||
});
|
||||
|
||||
resFiles['passport'] = { ...resPassport };
|
||||
resFiles['visa'] = { ...resVisa };
|
||||
} else if (type === 'customer') {
|
||||
const groups = [
|
||||
'citizen',
|
||||
'house-registration',
|
||||
'commercial-registration',
|
||||
'vat-registration',
|
||||
'power-of-attorney',
|
||||
] as const;
|
||||
|
||||
for (const group of groups) {
|
||||
const res = await fileManager.listFile({
|
||||
group,
|
||||
parentId: id,
|
||||
});
|
||||
resFiles[group] = { ...res };
|
||||
}
|
||||
}
|
||||
|
||||
const resAttachment = await attachmentManag.listAttachment({
|
||||
parentId: id,
|
||||
});
|
||||
|
||||
if (resAttachment)
|
||||
for (const item of resAttachment) {
|
||||
const [key] = item.split('-').map((s) => s.trim());
|
||||
if (key) {
|
||||
if (!resFiles[key]) {
|
||||
resFiles[key] = [];
|
||||
}
|
||||
|
||||
if (!resFiles[key].includes(item)) {
|
||||
resFiles[key].push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(resFiles);
|
||||
|
||||
return resFiles;
|
||||
}
|
||||
|
||||
async function getRequestDataStats() {
|
||||
const res = await api.get<typeof stats.value>('/request-data/stats');
|
||||
if (res.status < 400) return res.data;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getRequestData(id: string) {
|
||||
const res = await api.get<RequestData>(`/request-data/${id}`);
|
||||
if (res.status < 400) return res.data;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getRequestDataList(params?: {
|
||||
query?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
requestDataStatus?: RequestDataStatus;
|
||||
}) {
|
||||
const res = await api.get<PaginationResult<RequestData>>('/request-data', {
|
||||
params,
|
||||
});
|
||||
if (res.status < 400) return res.data;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getRequestWorkList(params?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
requestDataId?: string;
|
||||
}) {
|
||||
const res = await api.get<PaginationResult<RequestWork>>('/request-work', {
|
||||
params,
|
||||
});
|
||||
if (res.status < 400) return res.data;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function editRequestWork(body: Partial<RequestWork>) {
|
||||
const res = await api.put(`/request-work/${body.id}`, {
|
||||
...body,
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
if (res.status < 400) return res.data;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function editStatusRequestWork(body: Step) {
|
||||
const res = await api.put<Step>(
|
||||
`/request-work/${body.requestWorkId}/step-status/${body.step}`,
|
||||
{
|
||||
attributes: body.attributes,
|
||||
requestWorkStatus: body.workStatus,
|
||||
requestWorkId: undefined,
|
||||
step: undefined,
|
||||
},
|
||||
);
|
||||
|
||||
if (res.status < 400) return res.data;
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
page,
|
||||
pageMax,
|
||||
pageSize,
|
||||
stats,
|
||||
|
||||
viewAttachmentRequest,
|
||||
getAttachmentRequest,
|
||||
uploadAttachmentRequest,
|
||||
|
||||
getRequestDataStats,
|
||||
getRequestData,
|
||||
getRequestDataList,
|
||||
getRequestWorkList,
|
||||
|
||||
editRequestWork,
|
||||
editStatusRequestWork,
|
||||
};
|
||||
});
|
||||
89
src/stores/request-list/types.ts
Normal file
89
src/stores/request-list/types.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { Employee } from '../employee/types';
|
||||
import { QuotationFull, Quotation } from '../quotations/types';
|
||||
|
||||
export type RequestData = {
|
||||
id: string;
|
||||
employee: Employee;
|
||||
employeeId: string;
|
||||
code: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
quotation: QuotationFull;
|
||||
quotationId: string;
|
||||
|
||||
flow: Record<string, any>;
|
||||
|
||||
requestWork: RequestWork[];
|
||||
requestDataStatus: RequestDataStatus;
|
||||
};
|
||||
|
||||
export enum RequestDataStatus {
|
||||
Pending = 'Pending',
|
||||
InProgress = 'InProgress',
|
||||
Completed = 'Completed',
|
||||
}
|
||||
|
||||
export enum RequestWorkStatus {
|
||||
Pending = 'Pending',
|
||||
Ready = 'Ready',
|
||||
Waiting = 'Waiting',
|
||||
InProgress = 'InProgress',
|
||||
Validate = 'Validate',
|
||||
Ended = 'Ended',
|
||||
Completed = 'Completed',
|
||||
}
|
||||
|
||||
export enum DocStatus {
|
||||
AwaitReview = 'AwaitReview',
|
||||
UploadedAwaitReview = 'UploadedAwaitReview',
|
||||
ReviewedAwaitUpload = 'ReviewedAwaitUpload',
|
||||
Reviewed = 'Reviewed',
|
||||
ApprovedReview = 'ApprovedReview',
|
||||
}
|
||||
|
||||
export type RequestWork = {
|
||||
id?: string;
|
||||
stepStatus: Step[];
|
||||
requestDataId: string;
|
||||
productService: QuotationFull['productServiceList'][number];
|
||||
productServiceId: string;
|
||||
request: RequestData;
|
||||
attributes?: Attributes;
|
||||
};
|
||||
|
||||
export type RowDocument = {
|
||||
no: number;
|
||||
documentType: string;
|
||||
amount: number;
|
||||
documentInSystem: boolean;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type Attributes = {
|
||||
customer?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
employee?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
properties?: { [field: string]: string | number | null | undefined };
|
||||
remark?: string;
|
||||
};
|
||||
|
||||
export type AttributesForm = {
|
||||
customerDuty: boolean;
|
||||
customerDutyCost?: number;
|
||||
companyDuty: boolean;
|
||||
companyDutyCost?: number;
|
||||
individualDuty: boolean;
|
||||
localEmployee: boolean;
|
||||
employeeId?: string;
|
||||
};
|
||||
|
||||
export type Step = {
|
||||
attributes?: { form?: AttributesForm };
|
||||
requestWorkId: string;
|
||||
workStatus?: RequestWorkStatus;
|
||||
step: number;
|
||||
};
|
||||
|
|
@ -1,4 +1,11 @@
|
|||
import { Dialog, QSelect, Notify, QNotifyCreateOptions } from 'quasar';
|
||||
import {
|
||||
Dialog,
|
||||
QSelect,
|
||||
Notify,
|
||||
QNotifyCreateOptions,
|
||||
QVueGlobals,
|
||||
useQuasar,
|
||||
} from 'quasar';
|
||||
import { Ref, ref } from 'vue';
|
||||
import axios, { AxiosInstance, AxiosProgressEvent } from 'axios';
|
||||
import { ComposerTranslation } from 'vue-i18n';
|
||||
|
|
@ -498,3 +505,31 @@ export function createDataRefBase<T>(
|
|||
pageSize: ref<number>(defaultPageSize),
|
||||
};
|
||||
}
|
||||
|
||||
export function changeMode(mode: string) {
|
||||
const $q = useQuasar();
|
||||
if (mode === 'light') {
|
||||
localStorage.setItem('currentTheme', 'light');
|
||||
$q.dark.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === 'dark') {
|
||||
localStorage.setItem('currentTheme', 'dark');
|
||||
$q.dark.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === 'baseOnDevice') {
|
||||
localStorage.setItem('currentTheme', 'baseOnDevice');
|
||||
if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
) {
|
||||
$q.dark.set(true);
|
||||
} else {
|
||||
$q.dark.set(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue