Merge branch 'develop' into devTee

This commit is contained in:
setthawutttty 2025-09-03 10:13:35 +07:00
commit 18bdd0eda7
12 changed files with 386 additions and 140 deletions

View file

@ -52,6 +52,7 @@ export default {
orgPosExecutiveById: (id: string) => `${orgPos}/executive/${id}`,
orgPosHistory: (id: string) => `${orgPos}/history/${id}`,
orgPosHistoryUpdate: (id: string) => `${orgPos}/history-update/${id}`,
orgSalaryPosition: `${orgPos}/position?keyword=&type=ALL`,

View file

@ -2,6 +2,7 @@ interface attachments {
name: string;
url: string;
isReport: boolean;
isTemplate: boolean;
}
interface ResponseInbox {
body: string;
@ -30,4 +31,4 @@ interface DataInbox {
isOpen: boolean;
}
export type { ResponseInbox, DataInbox };
export type { ResponseInbox, DataInbox, attachments };

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { ref, watch, computed } from "vue";
import { useQuasar } from "quasar";
import http from "@/plugins/http";
@ -13,9 +13,7 @@ import { useOrganizational } from "@/modules/02_organization/store/organizationa
import type { QTableProps } from "quasar";
import type { HistoryPos } from "@/modules/02_organization/interface/response/organizational";
/**
* import Components
*/
/** import Components*/
import Header from "@/components/DialogHeader.vue";
/** Use*/
@ -23,21 +21,20 @@ const $q = useQuasar();
const store = useOrganizational();
const { showLoader, hideLoader, messageError, date2Thai } = useCounterMixin();
/**
* props
*/
/** props*/
const modal = defineModel<boolean>("modal", { required: true });
const props = defineProps({
historyType: {
type: String,
default: "HISTORY",
},
rowId: {
type: String,
},
});
/**
* อม Table
*/
const rows = ref<HistoryPos[]>([]); //
const columns = ref<QTableProps["columns"]>([
const baseColumns = ref<QTableProps["columns"]>([
{
name: "no",
align: "left",
@ -47,17 +44,17 @@ const columns = ref<QTableProps["columns"]>([
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
// {
// name: "fullname",
// align: "left",
// label: "",
// sortable: true,
// field: "fullname",
// headerStyle: "font-size: 14px",
// style: "font-size: 14px",
// sort: (a: string, b: string) =>
// a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
// },
{
name: "fullname",
align: "left",
label: "ชื่อคนครอง",
sortable: true,
field: "fullname",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
sort: (a: string, b: string) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
},
{
name: "orgShortName",
align: "left",
@ -165,18 +162,46 @@ const columns = ref<QTableProps["columns"]>([
style: "font-size: 14px",
},
]);
const pagination = ref<QTableProps["pagination"]>({
page: 1,
rowsPerPage: 10,
});
const columns = computed(() =>
props.historyType === "HISTORY"
? (baseColumns.value || []).filter((col: any) => col.name !== "fullname")
: baseColumns.value || []
);
const titleName = computed(() =>
props.historyType === "HISTORY" ? "ประวัติตำแหน่ง" : "ประวัติคนครอง"
);
/**
* function เรยกขอมลประวตำแหน
* @param id
*/
function fetchHistoryPos(id: string) {
async function fetchHistoryPos(id: string) {
showLoader();
http
.get(config.API.orgPosHistory(id))
const pathAPI =
props.historyType === "HISTORY"
? config.API.orgPosHistory(id)
: config.API.orgPosHistoryUpdate(id);
await http
.get(pathAPI)
.then((res) => {
const data = res.data.result;
rows.value = data;
rows.value =
props.historyType === "HISTORY"
? data
: data.map((e: any) => ({
...e,
fullname:
e.prefix && e.firstName && e.lastName
? `${e.prefix ?? ""} ${e.firstName ?? ""} ${e.lastName ?? ""}`
: "-",
shortName: e.orgShortName,
}));
})
.catch((err) => {
messageError($q, err);
@ -191,6 +216,10 @@ watch(
() => modal.value,
() => {
modal.value && props.rowId && fetchHistoryPos(props.rowId);
if (!modal.value) {
rows.value = [];
}
}
);
</script>
@ -198,7 +227,7 @@ watch(
<q-dialog v-model="modal" persistent>
<q-card style="min-width: 80%">
<Header
:tittle="'ประวัติตำแหน่ง'"
:tittle="titleName"
:close="
() => {
modal = false;

View file

@ -119,6 +119,12 @@ const listMenu = ref<ListMenu[]>([
type: "CONDITION",
color: "deep-orange",
},
{
label: "ประวัติคนครอง",
icon: "history",
type: "OWNER_HISTORY",
color: "deep-purple",
},
{
label: "ประวัติตำแหน่ง",
icon: "history",
@ -367,12 +373,12 @@ function onClickCopyPosition(
}
const dialogDetail = ref<boolean>(false); //
const dataDetailPos = ref<DataPosition[]>([]); //
const dataDetailPos = ref<PosMaster2>(); //
/**
* function รายละเอยดประวตำแหน
* @param data อม ประวตำแหน
*/
function onClickViewDetail(data: DataPosition[]) {
function onClickViewDetail(data: PosMaster2) {
dialogDetail.value = !dialogDetail.value;
dataDetailPos.value = data;
}
@ -432,13 +438,15 @@ function onClickMovePos(id: string, type: string) {
}
const modalDialogHistoryPos = ref<boolean>(false);
const historyType = ref<string>("HISTORY");
/**
* function ประวตำแหน
* @param id ID ตำแหน
*/
function onClickHistoryPos(id: string) {
function onClickHistoryPos(id: string, type: string = "HISTORY") {
modalDialogHistoryPos.value = !modalDialogHistoryPos.value;
rowId.value = id;
historyType.value = type;
}
/**
@ -454,7 +462,7 @@ function updatePagination(newPagination: NewPagination) {
* function เป pop เลอกตนครอง
* @param data อมลตำแหนงทองการเพมคนครอง
*/
function openSelectPerson(data: DataPosition[]) {
function openSelectPerson(data: PosMaster2) {
modalSelectPerson.value = true;
dataDetailPos.value = data;
}
@ -591,6 +599,39 @@ async function fetchDataCondition() {
);
}
function onClickAction(type: string, data: PosMaster2) {
console.log(data);
switch (type) {
case "EDIT":
onClickPosition(type, data.id, data);
break;
case "COPY":
onClickCopyPosition(type, data.id, data);
break;
case "DEL":
onClickDelete(data.id || "");
break;
case "MOVE":
onClickMovePos(data.id || "", "SINGER");
break;
case "INHERIT":
onClickInherit(data.id || "");
break;
case "CONDITION":
onClickCodition(data);
break;
case "OWNER_HISTORY":
onClickHistoryPos(data.ancestorDNA || "", type);
break;
case "HISTORY":
onClickHistoryPos(data.id || "");
break;
case "VIEW":
onClickViewDetail(data);
break;
}
}
const pagination = ref({
page: reqMaster.value.page,
rowsPerPage: reqMaster.value.pageSize,
@ -789,25 +830,7 @@ watch(
:key="index"
clickable
v-close-popup
@click="
item.type === 'EDIT'
? onClickPosition('EDIT', props.row.id, props.row)
: item.type === 'DEL'
? onClickDelete(props.row.id)
: item.type === 'MOVE'
? onClickMovePos(props.row.id, 'SINGER')
: item.type === 'HISTORY'
? onClickHistoryPos(props.row.id)
: item.type === 'INHERIT'
? onClickInherit(props.row.id)
: item.type === 'COPY'
? onClickCopyPosition('COPY', props.row.id, props.row)
: item.type === 'CONDITION'
? onClickCodition(props.row)
: item.type === 'VIEW'
? onClickViewDetail(props.row)
: null
"
@click="onClickAction(item.type, props.row)"
>
<q-item-section>
<div class="row items-center">
@ -836,6 +859,40 @@ watch(
</q-menu>
</q-btn>
<q-btn
v-else-if="checkPermission($route)?.attrIsGet"
flat
color="secondary"
icon="mdi-dots-horizontal-circle-outline"
round
class="q-pa-none q-ml-xs"
>
<q-menu>
<q-list dense style="min-width: 150px">
<q-item
v-for="(item, index) in listMenu.filter(
(e) => e.type === 'HISTORY' || e.type === 'OWNER_HISTORY'
)"
:key="index"
clickable
v-close-popup
@click="onClickAction(item.type, props.row)"
>
<q-item-section>
<div class="row items-center">
<q-icon
:color="item.color"
size="xs"
:name="item.icon"
/>
<div class="q-pl-md">{{ item.label }}</div>
</div>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<q-btn
v-else-if="checkPermission($route)?.attrIsGet"
flat
@ -1077,7 +1134,11 @@ watch(
/>
<!-- ประวตำแหน -->
<DialogHistoryPos v-model:modal="modalDialogHistoryPos" :rowId="rowId" />
<DialogHistoryPos
v-model:modal="modalDialogHistoryPos"
:rowId="rowId"
:history-type="historyType"
/>
<!-- เลอกคนครอง -->
<DialogSelectPerson

View file

@ -162,6 +162,7 @@ interface PosMaster2 {
current_holderId?: string;
next_holderId?: string;
isSit?: boolean;
ancestorDNA: string;
}
interface HistoryPos {

View file

@ -214,6 +214,8 @@ const columns2 = ref<QTableProps["columns"]>([
? "เลื่อน"
: val === "MOVE"
? "ย้าย"
: val === "ROYAL"
? "โปรดเกล้าฯ"
: "-";
},
},
@ -274,26 +276,19 @@ async function sendToCommand() {
// });
}
/** ฟังก์ชันเลือกประเภทคำสั่ง */
function filterSelectOrder() {
const data = rowsData.value;
selected.value = [];
rows.value = data.filter((v: PersonData) => {
function filterSelectOrder(data: PersonData[]) {
return data.filter((v: PersonData) => {
switch (commandType.value) {
case "C-PM-05":
return v.typeCommand === "APPOINT";
case "C-PM-39":
return v.typeCommand === "SLIP";
case "C-PM-07":
return v.typeCommand === "MOVE";
case "C-PM-47":
return v.posTypeName === "บริหาร" || v.posTypeName === "อำนวยการ";
return v.typeCommand === "ROYAL";
default:
return [];
return []; //
}
});
}
@ -312,9 +307,11 @@ function filterSelector(val: string, update: Function) {
}
function onSearch() {
selected.value = [];
const filteredByType = filterSelectOrder(rowsData.value);
rows.value = onSearchDataTable(
filterKeyword2.value,
rowsData.value,
filteredByType,
columns2.value ? columns2.value : []
);
}
@ -328,6 +325,7 @@ watch(
async () => {
if (props.Modal === true) {
selected.value = [];
rows.value = rowsData.value;
commandType.value = "";
const data = await storeCommand.getCommandTypes();
commandMainOp.value = data.filter(
@ -364,7 +362,7 @@ watch(
use-input
hide-selected
fill-input
@update:model-value="filterSelectOrder"
@update:model-value="onSearch"
@filter="(inputValue:string,
doneFn:Function) => filterSelector(inputValue, doneFn
) "
@ -411,7 +409,7 @@ watch(
<d-table
:columns="columns2"
:rows="rows"
row-key="profileId"
row-key="id"
flat
:visible-columns="visibleColumns2"
selection="multiple"
@ -454,6 +452,7 @@ watch(
<div
v-else-if="col.name == 'organizationNameOld'"
class="text-html"
style="min-width: 200px"
>
{{
props.row.organizationPositionOld !== null
@ -462,7 +461,10 @@ watch(
}}
</div>
<div v-else-if="col.name == 'organizationName'">
<div
v-else-if="col.name == 'organizationName'"
style="min-width: 200px"
>
{{
props.row.position !== null ? props.row.position : ""
}}{{

View file

@ -313,7 +313,7 @@ watch(
<d-table
:columns="columns2"
:rows="rows"
row-key="profileId"
row-key="id"
flat
:visible-columns="visibleColumns2"
selection="multiple"

View file

@ -325,7 +325,7 @@ watch(
<d-table
:columns="columns"
:rows="rows"
row-key="personalId"
row-key="id"
:visible-columns="visibleColumns"
selection="multiple"
v-model:selected="selected"

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ref, onMounted, computed } from "vue";
import { useQuasar } from "quasar";
import { useRouter } from "vue-router";
import { useRouter, useRoute } from "vue-router";
import {
checkPermission,
checkPermissionList,
@ -26,6 +26,7 @@ import DialogOrgSelect from "@/components/Dialogs/DialogOrgSelect.vue"; // เ
const $q = useQuasar();
const router = useRouter();
const route = useRoute();
const store = useTransferDataStore();
const { statusText, filterOption } = useTransferDataStore();
const {
@ -194,6 +195,8 @@ const columns = ref<QTableProps["columns"]>([
? "เลื่อน"
: val === "MOVE"
? "ย้าย"
: val === "ROYAL"
? "โปรดเกล้าฯ"
: "-";
},
},
@ -242,6 +245,121 @@ const pagination = ref({
rowsPerPage: 10,
});
const getMenuItems = computed(() => {
return (row: PersonData) => {
const baseCondition = {
canUpdate:
checkPermission(route)?.attrIsUpdate &&
checkPermission(route)?.attrIsGet,
canDelete: checkPermission(route)?.attrIsDelete,
canView: checkPermission(route)?.attrIsGet,
isNotDone: row.status !== "REPORT" && row.status !== "DONE",
};
const allMenuItems = [
{
id: "appoint",
label: "เลือกหน่วยงานที่รับแต่งตั้ง",
icon: "mdi-bookmark-outline",
color: "primary",
type: "APPOINT",
condition: false,
},
{
id: "slip",
label: "เลือกหน่วยงานที่รับเลื่อน",
icon: "mdi-bookmark-outline",
color: "primary",
type: "SLIP",
condition: false,
},
{
id: "move",
label: "เลือกหน่วยงานที่รับย้าย",
icon: "mdi-bookmark-outline",
color: "primary",
type: "MOVE",
condition: false,
},
{
id: "royal",
label: "เลือกหน่วยงานที่โปรดเกล้าฯ",
icon: "mdi-bookmark-outline",
color: "primary",
type: "ROYAL",
condition: false,
},
{
id: "detail",
label: "รายละเอียด",
icon: "mdi-eye",
color: "info",
type: "DETAIL",
condition: baseCondition.canView,
},
{
id: "delete",
label: "ลบ",
icon: "mdi-delete",
color: "red",
type: "DELETE",
condition: baseCondition.canDelete && baseCondition.isNotDone,
},
];
//
const posType = row.posTypeNameOld?.toLowerCase() || "";
const posLevel = row.posLevelNameOld?.toLowerCase() || "";
//
const appointItem = allMenuItems.find((item) => item.id === "appoint");
const slipItem = allMenuItems.find((item) => item.id === "slip");
const moveItem = allMenuItems.find((item) => item.id === "move");
const royalItem = allMenuItems.find((item) => item.id === "royal");
// - "" ""
if (posType === "วิชาการ" && posLevel === "เชี่ยวชาญ") {
if (moveItem)
moveItem.condition = baseCondition.canUpdate && baseCondition.isNotDone;
if (royalItem)
royalItem.condition =
baseCondition.canUpdate && baseCondition.isNotDone;
}
// - "" ""
else if (posType === "บริหาร" && posLevel === "ต้น") {
if (moveItem)
moveItem.condition = baseCondition.canUpdate && baseCondition.isNotDone;
if (royalItem)
royalItem.condition =
baseCondition.canUpdate && baseCondition.isNotDone;
}
// - ""
else if (posType === "วิชาการ" && posLevel === "ทรงคุณวุฒิ") {
if (royalItem)
royalItem.condition =
baseCondition.canUpdate && baseCondition.isNotDone;
}
// - ""
else if (posType === "บริหาร" && posLevel === "สูง") {
if (royalItem)
royalItem.condition =
baseCondition.canUpdate && baseCondition.isNotDone;
}
// - "", "", ""
else {
if (appointItem)
appointItem.condition =
baseCondition.canUpdate && baseCondition.isNotDone;
if (slipItem)
slipItem.condition = baseCondition.canUpdate && baseCondition.isNotDone;
if (moveItem)
moveItem.condition = baseCondition.canUpdate && baseCondition.isNotDone;
}
return allMenuItems.filter((item) => item.condition);
};
});
/** fetch รายการแต่งตั้ง-เลื่อน-ย้าย*/
async function fecthlistappointment() {
showLoader();
@ -376,6 +494,24 @@ async function onSearch() {
);
}
//
function handleMenuClick(row: PersonData, type: string) {
switch (type) {
case "APPOINT":
case "SLIP":
case "MOVE":
case "ROYAL":
openModalTree(row, type);
break;
case "DETAIL":
nextPage(row.id);
break;
case "DELETE":
clickDelete(row.id);
break;
}
}
/** ทำงานเมื่อมีการเรียกใช้ Components*/
onMounted(async () => {
await fecthlistappointment();
@ -491,7 +627,7 @@ onMounted(async () => {
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<!-- <q-td auto-width>
<q-btn
v-if="
(props.row.status !== 'REPORT' &&
@ -614,6 +750,51 @@ onMounted(async () => {
</q-list>
</q-menu>
</q-btn>
</q-td> -->
<q-td auto-width>
<q-btn
v-if="
(props.row.status !== 'REPORT' &&
props.row.status !== 'DONE' &&
checkPermission($route)?.attrIsGet) ||
(checkPermission($route)?.attrIsDelete &&
props.row.status !== 'REPORT' &&
props.row.status !== 'DONE') ||
(checkPermission($route)?.attrIsGet &&
checkPermission($route)?.attrIsDelete) ||
checkPermission($route)?.attrIsGet
"
icon="mdi-dots-horizontal-circle-outline"
color="secondary"
flat
round
dense
>
<q-menu
transition-show="jump-down"
transition-hide="jump-up"
>
<q-list dense style="min-width: 250px">
<q-item
v-for="item in getMenuItems(props.row)"
:key="item.id"
clickable
v-close-popup
@click="handleMenuClick(props.row, item.type)"
>
<q-item-section style="min-width: 0px" avatar>
<q-icon
:color="item.color"
size="xs"
:name="item.icon"
/>
</q-item-section>
<q-item-section>{{ item.label }}</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<div v-if="col.name == 'no'">

View file

@ -1245,17 +1245,15 @@ onMounted(async () => {
</q-btn>
</q-td>
<!-- <q-td auto-width>
<q-td auto-width>
<btnDownloadFile
v-if="
props.row.insigniaSend == 'เหรียญจักรพรรดิมาลา (ร.จ.พ.)' &&
checkPermission($route)?.attrIsGet
"
:profileId="props.row.profileId"
:round="DataStore.roundId"
:optionRound="DataStore.optionRound"
:rowId="props.row.id"
/>
</q-td> -->
</q-td>
<q-td key="no" :props="props">
{{ props.rowIndex + 1 }}

View file

@ -1,12 +1,12 @@
<script setup lang="ts">
import { ref } from "vue";
import { useQuasar } from "quasar";
import { useCounterMixin } from "@/stores/mixin";
import http from "@/plugins/http";
import config from "@/app.config";
import type { Optionround } from "@/modules/07_insignia/interface/index/Main";
import genReport from "@/plugins/genreport";
import genReportXLSX from "@/plugins/genreportxlsx";
/** use*/
const $q = useQuasar();
@ -15,68 +15,38 @@ const { messageError, showLoader, hideLoader } = mixin;
/** props*/
const props = defineProps({
profileId: {
rowId: {
type: String,
},
round: {
type: String,
},
optionRound: {
type: Object,
},
});
const titleReport = ref<string>(""); //
/**
* function ดาวนโหลดไฟล
* @param type ประเภท file
*/
async function downloadDocument(type: string) {
let round = [];
if (props.optionRound) {
round = props.optionRound.find((e: Optionround) => e.id === props.round);
}
titleReport.value = round.name;
const download: boolean = true;
if (props.profileId) {
showLoader();
await http
.get(config.API.reportInsignia("46", type, props.profileId), {
responseType: "blob",
})
.then(async (res) => {
if (download) {
downloadFile(
res,
`ประวัติสำหรับการเสนอขอพระราชทานเหรียญจักรพรรดิมาลา.${type}`
);
}
})
.catch(async (e) => {
messageError($q, JSON.parse(await e.response.data.text()));
})
.finally(() => {
hideLoader();
});
}
}
if (!props.rowId) return;
showLoader();
try {
const res = await http.post(
config.API.reportInsigniaNew + `/report8/${props.rowId}`
);
/**
* function โหลดเอกสาร
* @param response อม
* @param filename อไฟล
*/
function downloadFile(response: any, filename: string) {
const link = document.createElement("a");
var fileName = filename;
link.href = window.URL.createObjectURL(new Blob([response.data]));
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
const reportTitle = "ประวัติสำหรับการเสนอขอพระราชทานเหรียญจักรพรรดิมาลา";
if (type === "xlsx") {
await genReportXLSX(res.data.result, reportTitle, type);
} else {
await genReport(res.data.result, reportTitle, type);
}
} catch (error) {
messageError($q, error);
} finally {
hideLoader();
}
}
</script>
<template>
<q-btn
dense

View file

@ -11,6 +11,7 @@ import PopupReplyInbox from "@/components/Dialogs/PopupReplyInbox.vue";
import type {
ResponseInbox,
DataInbox,
attachments,
} from "@/interface/response/dashboard/dashboard";
const $q = useQuasar();
@ -114,14 +115,18 @@ const removeData = async (id: string) => {
hideLoader();
});
};
const fileOpen = (url: string, isReport: boolean, fileName: string) => {
if (isReport) {
const fileOpen = async (attachmentsData: attachments) => {
if (attachmentsData.isReport) {
showLoader();
http
.get(url)
await http
.get(attachmentsData.url)
.then(async (res) => {
const data = res.data.result;
await genReport(data, fileName, "pdf");
const result = res.data;
if (attachmentsData.isTemplate) {
await genReport(result.result, attachmentsData.name, "pdf");
} else {
window.open(result.downloadUrl, "_blank");
}
})
.catch((err) => {
messageError($q, err);
@ -130,7 +135,7 @@ const fileOpen = (url: string, isReport: boolean, fileName: string) => {
hideLoader();
});
} else {
window.open(url, "_blank");
window.open(attachmentsData.url, "_blank");
}
};
@ -363,10 +368,7 @@ const thaiOptions: Intl.DateTimeFormatOptions = {
v-for="(link, num) in d.payload.attachments"
:key="num"
>
<q-item
clickable
@click="fileOpen(link.url, link.isReport, link.name)"
>
<q-item clickable @click="fileOpen(link)">
<q-item-section>{{ link.name }}</q-item-section>
</q-item>
<q-separator />