แก้ไขตำแหน่งเงินเดือน

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2025-03-21 11:53:41 +07:00
parent e03d2a84a5
commit cc0c600174
8 changed files with 255 additions and 108 deletions

View file

@ -6,4 +6,31 @@ interface DataFilter {
pageSize: number;
}
export type { DataFilter };
interface FormDataSalary {
commandCode: string; //ประเภทคำสั่ง
commandNo: string; //เลขที่คำสั่ง
commandYear: number | null; //ปี
commandDateAffect: Date | null; //วันที่มีผล
commandDateSign: Date | null; //วันที่ลงนาม
posNoAbb: string; //ตัวย่อเลขที่ตำแหน่ง
posNo: string; //เลขที่ตำแหน่ง
positionName: string; //ตำแหน่ง
positionType: string; //ประเภทตำแหน่ง, กลุ่มงาน
positionLevel: string; //ระดับตำแหน่ง, ระดับชั้นงาน
positionCee: string; //ระดับซี
positionLine: string; // สายงาน
positionPathSide: string; //ด้าน/สาขา
positionExecutive: string; //ตำแหน่งทางการบริหาร
amount: number | null; //เงินเดือน
amountSpecial: number | null; //เงินค่าตอบแทนพิเศษ
positionSalaryAmount: number | null; //เงินประจำตำแหน่ง
mouthSalaryAmount: number | null; //เงินค่าตอบแทนรายเดือน
orgRoot: string; //หน่วยงาน
orgChild1: string; //ส่วนราชการระดับ 1
orgChild2: string; //ส่วนราชการระดับ 2
orgChild3: string; //ส่วนราชการระดับ 3
orgChild4: string; //ส่วนราชการระดับ 4
remark: string; //หมายเหตุstring
}
export type { DataFilter, FormDataSalary };

View file

@ -23,4 +23,51 @@ interface DataSalaryPos {
type: string;
}
export type { DataSalaryPos };
interface DataPosition {
amount: number;
amountSpecial: number;
commandCode: string;
commandDateAffect: string;
commandDateSign: string;
commandId: string;
commandName: string;
commandNo: string;
commandYear: number;
createdAt: string;
createdFullName: string;
createdUserId: string;
dateGovernment: string;
id: string;
isDelete: boolean;
isEdit: boolean;
isEntry: boolean;
isGovernment: string;
lastUpdateFullName: string;
lastUpdateUserId: string;
lastUpdatedAt: string;
mouthSalaryAmount: number;
order: number;
orgChild1: string;
orgChild2: string;
orgChild3: string;
orgChild4: string;
orgRoot: string;
posNo: string;
posNoAbb: string;
positionCee: string;
positionExecutive: string;
positionLevel: string;
positionLine: string;
positionName: string;
positionPathSide: string;
positionSalaryAmount: number;
positionType: string;
profileEmployeeId: string;
profileId: string;
refId: number;
remark: string;
salaryId: string;
status: string;
}
export type { DataSalaryPos, DataPosition };

View file

@ -4,6 +4,8 @@ import { defineStore } from "pinia";
import type { DataOption } from "@/modules/04_registryPerson/interface/index/Main";
export const useEditPosDataStore = defineStore("EditPos", () => {
const orgData = ref<DataOption[]>([]);
const commandCodeData = ref<DataOption[]>([]);
const posTypeData = ref<DataOption[]>([]); //รายการประเภทตำแหน่ง | กลุ่มงาน
const posLevelData = ref<DataOption[]>([]); //รายการระดับตำแหน่ง | ระดับชั้นงาน
@ -25,6 +27,7 @@ export const useEditPosDataStore = defineStore("EditPos", () => {
posLineData,
posPathSideData,
posExecutiveData,
orgData,
// Function
convertCommandCodeName,

View file

@ -8,6 +8,15 @@ import http from "@/plugins/http";
import config from "@/app.config";
import type { DataOption } from "@/modules/04_registryPerson/interface/index/Main";
import type { DataPosition } from "@/modules/04_registryPerson/interface/response/Edit";
import type { FormDataSalary } from "@/modules/04_registryPerson/interface/request/Edit";
import type {
DataCommandCode,
DataPosType,
DataPosLevel,
DataPosPosition,
DataPosExecutive,
} from "@/modules/04_registryPerson/interface/response/Position";
import DialogHeader from "@/components/DialogHeader.vue";
import FormPosition from "@/modules/04_registryPerson/views/edit/components/FormPosition.vue";
@ -19,21 +28,20 @@ const {
hideLoader,
messageError,
success,
convertDateToAPI,
} = useCounterMixin();
const store = useEditPosDataStore();
const modal = defineModel<boolean>("modal", { required: true });
const empType = defineModel<string>("empType", { required: true });
const rowData = defineModel<any[]>("rowData", { required: true });
const rowData = defineModel<DataPosition[]>("rowData", { required: true });
const rowIndex = defineModel<number>("rowIndex", { required: true });
const props = defineProps({
fetchData: { type: Function, required: true },
});
const formData = reactive({
const formData = reactive<FormDataSalary>({
commandCode: "", //
commandNo: "", //
commandYear: null, //
@ -59,7 +67,7 @@ const formData = reactive({
orgChild4: "", // 4
remark: "", //
});
const formReadonly = reactive({
const formReadonly = reactive<FormDataSalary>({
commandCode: "", //
commandNo: "", //
commandYear: null, //
@ -86,7 +94,7 @@ const formReadonly = reactive({
remark: "", //
});
const dataLevel = ref<any[]>([]); //
const dataLevel = ref<DataPosType[]>([]); //
const commandCodeOptions = ref<DataOption[]>(store.commandCodeData); //
const posTypeOptions = ref<DataOption[]>(store.posTypeData); // |
const posLevelOptions = ref<DataOption[]>(store.posLevelData); // |
@ -98,12 +106,15 @@ const salaryId = ref<string>("");
async function fetchDataPosition() {
try {
showLoader();
const res = await http.get(
config.API.salaryTemp + `/get/${salaryId.value}`
);
return res.data.result;
} catch (err) {
messageError($q, err);
} finally {
hideLoader();
}
}
@ -115,7 +126,7 @@ async function fetchDataCommandCode() {
.then((res) => {
const data = res.data.result;
store.commandCodeData = data.map((e: any) => ({
store.commandCodeData = data.map((e: DataCommandCode) => ({
id: e.code.toString(),
name: e.name,
}));
@ -133,7 +144,7 @@ async function fetchType() {
.get(config.API.orgPosType)
.then((res) => {
dataLevel.value = res.data.result;
store.posTypeData = res.data.result.map((e: any) => ({
store.posTypeData = res.data.result.map((e: DataPosType) => ({
id: e.id,
name: e.posTypeName,
}));
@ -150,7 +161,7 @@ async function fetchOptionGroup() {
.get(config.API.orgEmployeeType)
.then((res) => {
dataLevel.value = res.data.result;
store.posTypeData = res.data.result.map((e: any) => ({
store.posTypeData = res.data.result.map((e: DataPosType) => ({
id: e.id,
name: e.posTypeName,
}));
@ -174,7 +185,7 @@ async function fetchDataOption() {
const seen = new Set();
const seen2 = new Set();
const listPositionField = data.filter((item: any) => {
const listPositionField = data.filter((item: DataPosPosition) => {
if (seen.has(item.positionField)) {
return false;
} else {
@ -182,12 +193,12 @@ async function fetchDataOption() {
return true;
}
});
store.posLineData = listPositionField.map((e: any) => ({
store.posLineData = listPositionField.map((e: DataPosPosition) => ({
id: e.positionField,
name: e.positionField,
}));
const listPositionArea = data.filter((item: any) => {
const listPositionArea = data.filter((item: DataPosPosition) => {
if (
item.positionArea === null ||
item.positionArea === "" ||
@ -200,7 +211,7 @@ async function fetchDataOption() {
return true;
}
});
store.posPathSideData = listPositionArea.map((e: any) => ({
store.posPathSideData = listPositionArea.map((e: DataPosPosition) => ({
id: e.positionArea,
name: e.positionArea,
}));
@ -216,7 +227,7 @@ async function fetchDataOptionExecutive() {
.get(config.API.orgPosExecutive)
.then((res) => {
const data = res.data.result;
store.posExecutiveData = data.map((e: any) => ({
store.posExecutiveData = data.map((e: DataPosExecutive) => ({
id: e.posExecutiveName,
name: e.posExecutiveName,
}));
@ -233,11 +244,11 @@ async function fetchDataOptionExecutive() {
*/
async function updateSelectType(val: string, status: boolean = false) {
const listLevel = val
? dataLevel.value.find((e: any) => e.posTypeName === val)
? dataLevel.value.find((e: DataPosType) => e.posTypeName === val)
: null;
if (listLevel) {
store.posLevelData = listLevel.posLevels.map((e: any) => ({
store.posLevelData = listLevel.posLevels.map((e: DataPosLevel) => ({
id: e.id,
name:
empType.value === "officer"
@ -393,7 +404,7 @@ onMounted(async () => {
<q-card>
<q-form greedy @submit.prevent @validation-success="onSubmit">
<DialogHeader
:tittle="'แก้ไขตำแหน่งเงินเดือนเงินเดือน'"
:tittle="'แก้ไขตำแหน่ง/เงินเดือน'"
:close="onClickCloseDialog"
/>
<q-separator />
@ -402,18 +413,32 @@ onMounted(async () => {
<div class="col-12 row">
<div class="col-xs-12 col-md-6 row no-wrap">
<div class="col-12 q-pa-md">
<FormPosition
:is-readonly="true"
:form-data="formReadonly"
:command-code-options="commandCodeOptions"
:pos-type-options="posTypeOptions"
:pos-level-options="posLevelOptions"
:pos-line-options="posLineOptions"
:pos-path-side-options="posPathSideOptions"
:pos-executive-options="posExecutiveOptions"
:emp-type="empType"
:update-select-type="updateSelectType"
/>
<q-card
bordered
class="col-12"
style="border: 1px solid #d6dee1"
>
<div
class="col-12 text-weight-medium bg-grey-1 q-py-xs q-px-md"
>
อมลป
</div>
<div class="col-12"><q-separator /></div>
<q-card-section>
<FormPosition
:is-readonly="true"
:form-data="formReadonly"
:command-code-options="commandCodeOptions"
:pos-type-options="posTypeOptions"
:pos-level-options="posLevelOptions"
:pos-line-options="posLineOptions"
:pos-path-side-options="posPathSideOptions"
:pos-executive-options="posExecutiveOptions"
:emp-type="empType"
:update-select-type="updateSelectType"
/>
</q-card-section>
</q-card>
</div>
<div class="col-12 row">
<q-separator :vertical="!$q.screen.lt.md" />
@ -422,18 +447,34 @@ onMounted(async () => {
<div class="col-xs-12 col-md-6 row">
<div class="col-12 q-pa-md">
<FormPosition
:is-readonly="false"
v-model:form-data="formData"
v-model:command-code-options="commandCodeOptions"
v-model:pos-type-options="posTypeOptions"
v-model:pos-level-options="posLevelOptions"
v-model:pos-line-options="posLineOptions"
v-model:pos-path-side-options="posPathSideOptions"
v-model:pos-executive-options="posExecutiveOptions"
:emp-type="empType"
:update-select-type="updateSelectType"
/>
<q-card
bordered
class="col-12"
style="border: 1px solid #d6dee1"
>
<div
class="col-12 text-weight-medium bg-grey-1 q-py-xs q-px-md"
>
อมลทแกไข
</div>
<div class="col-12">
<q-separator />
<q-card-section>
<FormPosition
:is-readonly="false"
v-model:form-data="formData"
v-model:command-code-options="commandCodeOptions"
v-model:pos-type-options="posTypeOptions"
v-model:pos-level-options="posLevelOptions"
v-model:pos-line-options="posLineOptions"
v-model:pos-path-side-options="posPathSideOptions"
v-model:pos-executive-options="posExecutiveOptions"
:emp-type="empType"
:update-select-type="updateSelectType"
/>
</q-card-section>
</div>
</q-card>
</div>
</div>
</div>
@ -442,26 +483,29 @@ onMounted(async () => {
<q-separator />
<q-card-actions align="right">
<q-btn label="บันทึก" id="onSubmit" type="submit" color="public">
<q-tooltip>นทกขอม</q-tooltip>
</q-btn>
<q-btn
flat
round
:disable="rowIndex === 0"
label="ก่อนหน้า"
color="public"
icon="mdi-arrow-left"
:color="rowIndex === 0 ? 'grey' : 'public'"
icon="mdi-chevron-left"
@click.stop.pervent="onNavigateRow('previous')"
>
<q-tooltip>นทกขอม</q-tooltip>
<q-tooltip>อมลกอนหน</q-tooltip>
</q-btn>
<q-btn
flat
round
:disable="rowIndex + 1 === rowData.length"
label="ถัดไป"
color="public"
icon-right="mdi-arrow-right"
:color="rowIndex + 1 === rowData.length ? 'grey' : 'public'"
icon="mdi-chevron-right"
@click.stop.pervent="onNavigateRow('next')"
>
<q-tooltip>อมลถดไป</q-tooltip>
</q-btn>
<q-btn label="บันทึก" id="onSubmit" type="submit" color="public">
<q-tooltip>นทกขอม</q-tooltip>
</q-btn>
</q-card-actions>

View file

@ -5,23 +5,14 @@ import { useCounterMixin } from "@/stores/mixin";
import { useEditPosDataStore } from "@/modules/04_registryPerson/stores/Edit";
import type { DataOption } from "@/modules/04_registryPerson/interface/index/Main";
import type { FormDataSalary } from "@/modules/04_registryPerson/interface/request/Edit";
const {
date2Thai,
dialogConfirm,
showLoader,
hideLoader,
messageError,
success,
pathRegistryEmp,
onSearchDataTable,
convertDateToAPI,
} = useCounterMixin();
const { date2Thai } = useCounterMixin();
const store = useEditPosDataStore();
const isReadonly = defineModel<boolean>("isReadonly", { required: true });
const empType = defineModel<string>("empType", { required: true });
const formData = defineModel<any>("formData", { required: true });
const formData = defineModel<FormDataSalary>("formData", { required: true });
const commandCodeOptions = defineModel<DataOption[]>("commandCodeOptions", {
required: true,
});
@ -209,7 +200,7 @@ function classInput(val: boolean) {
autoApply
year-picker
:enableTimePicker="false"
class="inputgreen"
:class="classInput(isReadonly)"
:readonly="isReadonly"
>
<template #year="{ year }">{{ year + 543 }}</template>

View file

@ -1,27 +1,21 @@
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from "vue";
import { ref, onMounted, computed } from "vue";
import { useQuasar } from "quasar";
import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useRoute } from "vue-router";
import { useCounterMixin } from "@/stores/mixin";
import { useEditPosDataStore } from "@/modules/04_registryPerson/stores/Edit";
const route = useRoute();
const store = useEditPosDataStore();
const empType = ref<string>(route.params.type.toString());
const profileId = ref<string>(route.params.id.toString());
const tabs = defineModel<string>("tabs", { required: true });
import type { QTableColumn } from "quasar";
import type { DataPosition } from "@/modules/04_registryPerson/interface/response/Edit";
import DialogForm from "@/modules/04_registryPerson/views/edit/components/DialogForm.vue";
const $q = useQuasar();
const route = useRoute();
const store = useEditPosDataStore();
const {
date2Thai,
findOrgName,
@ -33,11 +27,19 @@ const {
success,
} = useCounterMixin();
const empType = ref<string>(route.params.type.toString());
const profileId = ref<string>(route.params.id.toString());
const tabs = defineModel<string>("tabs", { required: true });
const statusCheckEdit = defineModel<string>("statusCheckEdit", {
required: true,
});
//Table
const isLoad = ref<boolean>(true);
const rowIndex = ref<number>(0);
const rows = ref<any[]>([]);
const rowsMain = ref<any[]>([]);
const rows = ref<DataPosition[]>([]);
const rowsMain = ref<DataPosition[]>([]);
const keyword = ref<string>("");
const baseColumns = ref<QTableColumn[]>([
{
@ -291,6 +293,7 @@ const columns = computed<QTableColumn[]>(() => {
const modal = ref<boolean>(false);
/** function fetch ข้อมูลรายการตำแหน่งเงินเดือน*/
async function fetchData() {
isLoad.value = true;
rowsMain.value = [];
@ -314,6 +317,7 @@ async function fetchData() {
});
}
/** function ค้นหาข้อมูลรายการในตาราง*/
function serchDataTable() {
rows.value = onSearchDataTable(
keyword.value,
@ -322,6 +326,10 @@ function serchDataTable() {
);
}
/**
* funciton
* @param index
*/
function onEditData(index: number) {
rowIndex.value = index;
modal.value = true;
@ -342,11 +350,12 @@ async function onSwapData(action: string, id: string) {
});
}
function onDeleteData(id: string) {
function onDeleteData(id: string, isDelete: boolean) {
dialogRemove($q, async () => {
showLoader();
const path = isDelete ? "/delete-renew" : "/delete";
await http
.post(config.API.salaryTemp + `/delete`, {
.post(config.API.salaryTemp + `${path}`, {
type: empType.value,
salaryId: id,
})
@ -436,7 +445,7 @@ onMounted(() => {
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td v-if="tabs === 'PENDING'">
<q-td v-if="tabs === 'PENDING' && statusCheckEdit !== 'CHECKED'">
<q-btn
dense
flat
@ -471,23 +480,28 @@ onMounted(() => {
flat
dense
round
icon="delete"
:color="props.row.isDelete ? 'grey' : 'red'"
:disable="props.row.isDelete"
@click.stop.pervent="onDeleteData(props.row.id)"
:icon="props.row.isDelete ? 'mdi-refresh-circle' : 'delete'"
:color="props.row.isDelete ? 'orange' : 'red'"
@click.stop.pervent="
onDeleteData(props.row.id, props.row.isDelete)
"
>
<q-tooltip>ลบ</q-tooltip>
<q-tooltip>{{
props.row.isDelete ? "ย้อนกลับข้อมูล" : "ลบ"
}}</q-tooltip>
</q-btn>
</q-td>
<q-td
v-for="col in props.cols"
:key="col.id"
:class="
classColorRow(
props.row.isDelete,
props.row.isEdit,
props.row.isEntry
)
tabs === 'PENDING'
? classColorRow(
props.row.isDelete,
props.row.isEdit,
props.row.isEntry
)
: ''
"
>
<div

View file

@ -6,6 +6,7 @@ import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import { useRoute, useRouter } from "vue-router";
import { useEditPosDataStore } from "@/modules/04_registryPerson/stores/Edit";
/** importType*/
import type { QTableColumn } from "quasar";
@ -18,9 +19,10 @@ import type { DataSalaryPos } from "@/modules/04_registryPerson/interface/respon
import type { DataFilter } from "@/modules/04_registryPerson/interface/request/Edit";
const $q = useQuasar();
const { showLoader, hideLoader, messageError } = useCounterMixin();
const route = useRoute();
const router = useRouter();
const store = useEditPosDataStore();
const { showLoader, hideLoader, messageError } = useCounterMixin();
const organizationOps = ref<DataOption[]>([]);
const organizationOpsMain = ref<DataOption[]>([]);
@ -160,14 +162,16 @@ const visibleColumns = ref<string[]>([
/** function fetch ข้อมูลโครงสร้างปัจจุบัน*/
async function fetchActiveOrg() {
showLoader();
http
await http
.get(config.API.activeOrganization)
.then((res) => {
.then(async (res) => {
const data = res.data.result;
fetchListOrg(data.activeId);
await fetchListOrg(data.activeId);
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
}
@ -176,17 +180,17 @@ async function fetchActiveOrg() {
* function fetch อมลหนวยงาน
* @param id โครงสรางปจจ
*/
function fetchListOrg(id: string) {
async function fetchListOrg(id: string) {
showLoader();
http
await http
.get(config.API.orgByIdSystem(id, route.meta.Key as string))
.then(async (res) => {
const data = await res.data.result.map((item: DataStructureTree) => ({
store.orgData = await res.data.result.map((item: DataStructureTree) => ({
id: item.orgTreeId,
name: item.orgName,
}));
organizationOpsMain.value = data;
organizationOps.value = data;
organizationOpsMain.value = store.orgData;
organizationOps.value = store.orgData;
})
.catch((err) => {
messageError($q, err);
@ -295,7 +299,12 @@ function onRedirectToPosition(id: string, type: string) {
/** hook เมื่อมีการเรียกใช้ Components*/
onMounted(async () => {
await fetchActiveOrg();
if (store.orgData.length === 0) {
await fetchActiveOrg();
} else {
organizationOpsMain.value = store.orgData;
organizationOps.value = store.orgData;
}
});
</script>

View file

@ -26,10 +26,11 @@ const router = useRouter();
const dataProfile = ref<DataProfile>();
const tabs = ref<string>("PENDING");
const statusCheckEdit = ref<string>("");
const empType = ref<string>(route.params.type.toString());
const profileId = ref<string>(route.params.id.toString());
/** funtion*/
/** funtion fetch ข้อมูลส่วนตัว*/
async function fetchDataProfile() {
showLoader();
await http
@ -40,6 +41,7 @@ async function fetchDataProfile() {
)
.then(async (res) => {
const data = res.data.result;
statusCheckEdit.value = data.statusCheckEdit;
dataProfile.value = {
fullName: `${data.prefix}${data.firstName} ${data.lastName}`,
position: data.position,
@ -69,7 +71,8 @@ function onConfirmEdit() {
profileId: profileId.value,
type: empType.value?.toLocaleUpperCase(),
})
.then(() => {
.then(async () => {
await fetchDataProfile();
success($q, "ยืนยันเสร็จสิ้นการแก้ไขสำเร็จ");
})
.catch((err) => {
@ -95,7 +98,8 @@ function onConfirmDone() {
profileId: profileId.value,
type: empType.value?.toLocaleUpperCase(),
})
.then(() => {
.then(async () => {
await fetchDataProfile();
success($q, "ยืนยันข้อมูลถูกต้องสำเร็จ");
})
.catch((err) => {
@ -212,8 +216,15 @@ onMounted(async () => {
<div class="row q-col-gutter-sm justify-center">
<div class="col-2">
<q-btn
:disable="
statusCheckEdit === 'EDITED' || statusCheckEdit === 'CHECKED'
"
:color="
statusCheckEdit === 'EDITED' || statusCheckEdit === 'CHECKED'
? 'grey'
: 'public'
"
outline
color="public"
label="ยืนยันการแก้ไขสำเร็จ"
class="full-width"
@click.stop.pervent="onConfirmEdit"
@ -221,8 +232,9 @@ onMounted(async () => {
</div>
<div class="col-2">
<q-btn
:disable="statusCheckEdit === 'CHECKED'"
:color="statusCheckEdit === 'CHECKED' ? 'grey' : 'public'"
outline
color="public"
label="ยืนยันข้อมูลถูกต้อง"
class="full-width"
@click.stop.pervent="onConfirmDone"
@ -253,10 +265,10 @@ onMounted(async () => {
<q-tab-panels v-model="tabs" animated>
<q-tab-panel name="PENDING">
<Table :tabs="tabs" />
<Table :tabs="tabs" :status-check-edit="statusCheckEdit" />
</q-tab-panel>
<q-tab-panel name="CHECKED">
<Table :tabs="tabs" />
<Table :tabs="tabs" :status-check-edit="statusCheckEdit" />
</q-tab-panel>
</q-tab-panels>
</q-card-section>