Refactoring code module 13_salary

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2024-09-20 18:09:26 +07:00
parent c9dd0202c6
commit 4af366c03b
43 changed files with 1215 additions and 1167 deletions

View file

@ -0,0 +1,539 @@
<script setup lang="ts">
import { ref, onMounted, watch, computed } from "vue";
import { useQuasar } from "quasar";
import http from "@/plugins/http";
import config from "@/app.config";
import type { QTableProps } from "quasar";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import { useCounterMixin } from "@/stores/mixin";
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const { messageError, showLoader, hideLoader } = useCounterMixin();
const props = defineProps({
year: Number,
snapShot: String,
roundFilter: Object,
});
/** itemsCard*/
const itemsCardAPR = ref([
{
lable: "จำนวนคนทั้งหมด",
name: "group1",
color: "secondary",
total: 0,
},
{
lable: "15% ของจำนวนคน",
name: "group2",
color: "light-blue-4",
total: 0,
},
{
lable: "เลือกไปแล้ว",
name: "group2",
color: "primary",
total: 0,
},
{
lable: "คงเหลือโควตา",
name: "group2",
color: "indigo-6",
total: 0,
},
{
lable: "สำรอง",
name: "group2",
color: "red-6",
total: 0,
},
]);
const itemsCardOCT = ref([
{
lable: "จำนวนเงินคนครองปัจจุบัน",
name: "group1",
color: "secondary",
total: 0,
},
{
lable: "วงเงิน 6%",
name: "group2",
color: "light-blue-4",
total: 0,
},
{
lable: "ยอดเงินที่ใช้ไป",
name: "group2",
color: "primary",
total: 0,
},
{
lable: "วงเงิน 6%-ยอดเงินที่ใช้ไป",
name: "group2",
color: "indigo-6",
total: 0,
},
{
lable: "ใช้ไปเท่าไหร่",
name: "group2",
color: "blue-6",
total: 0,
},
{
lable: "เหลือเท่าไหร่",
name: "group2",
color: "green-6",
total: 0,
},
{
lable: "สำรอง",
name: "group2",
color: "red-6",
total: 0,
},
]);
const itemsCardSpeciel = ref([
{
lable: "จำนวนคนทั้งหมด",
name: "group1",
color: "secondary",
total: 0,
},
{
lable: "เลือกไปแล้ว",
name: "group2",
color: "primary",
total: 0,
},
{
lable: "สำรอง",
name: "group2",
color: "red-6",
total: 0,
},
]);
const itemsCard = computed(() => {
const items =
store.roundMainCode === "APR"
? itemsCardAPR.value
: store.roundMainCode === "OCT"
? itemsCardOCT.value
: itemsCardSpeciel.value;
return items;
});
const rows = ref<any[]>([]);
const columnsAPR = ref<QTableProps["columns"]>([
{
name: "org",
align: "left",
label: "หน่วยงาน",
sortable: true,
field: "org",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "total",
align: "left",
label: "จำนวนคนทั้งหมด",
sortable: true,
field: "total",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
{
name: "fifteenPercent",
align: "left",
label: "15% ของจำนวนคน",
sortable: true,
field: "fifteenPercent",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) =>
Number(v).toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
},
{
name: "chosen",
align: "left",
label: "เลือกไปแล้ว",
sortable: true,
field: "chosen",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
{
name: "remaining",
align: "left",
label: "คงเหลือโควตา",
sortable: true,
field: "remaining",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
{
name: "totalBackup",
align: "left",
label: "สำรอง",
sortable: true,
field: "totalBackup",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
]);
const columnsOCT = ref<QTableProps["columns"]>([
{
name: "org",
align: "left",
label: "หน่วยงาน",
sortable: true,
field: "org",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "currentAmount",
align: "left",
label: "จำนวนเงินคนครองปัจจุบัน",
sortable: true,
field: "currentAmount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) =>
Number(v).toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
},
{
name: "sixPercentAmount",
align: "left",
label: "วงเงิน 6%",
sortable: true,
field: "sixPercentAmount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) =>
Number(v).toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
},
{
name: "spentAmount",
align: "left",
label: "ยอดเงินที่ใช้ไป",
sortable: true,
field: "spentAmount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) =>
Number(v).toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
},
{
name: "sixPercentSpentAmount",
align: "left",
label: "วงเงิน 6%-ยอดเงินที่ใช้ไป",
sortable: true,
field: "sixPercentSpentAmount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) =>
Number(v).toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
},
{
name: "useAmount",
align: "left",
label: "ใช้ไปเท่าไหร่",
sortable: true,
field: "useAmount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) =>
Number(v).toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
},
{
name: "remainingAmount",
align: "left",
label: "เหลือเท่าไหร่",
sortable: true,
field: "remainingAmount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) =>
Number(v).toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
},
{
name: "totalBackup",
align: "left",
label: "สำรอง",
sortable: true,
field: "totalBackup",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
]);
const columnsSpeciel = ref<QTableProps["columns"]>([
{
name: "org",
align: "left",
label: "หน่วยงาน",
sortable: true,
field: "org",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "total",
align: "left",
label: "จำนวนคนทั้งหมด",
sortable: true,
field: "total",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
{
name: "chosen",
align: "left",
label: "เลือกไปแล้ว",
sortable: true,
field: "chosen",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
{
name: "totalBackup",
align: "left",
label: "สำรอง",
sortable: true,
field: "totalBackup",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => Number(v).toLocaleString(),
},
]);
const columns = computed(() => {
const columnsss =
store.roundMainCode === "APR"
? columnsAPR.value
: store.roundMainCode === "OCT"
? columnsOCT.value
: columnsSpeciel.value;
return columnsss;
});
const visibleColumns = ref<string[]>(
store.roundMainCode === "APR"
? ["org", "total", "fifteenPercent", "chosen", "remaining", "totalBackup"]
: store.roundMainCode === "OCT"
? [
"org",
"currentAmount",
"sixPercentAmount",
"spentAmount",
"sixPercentSpentAmount",
"useAmount",
"remainingAmount",
"totalBackup",
]
: ["org", "total", "chosen", "totalBackup"]
);
const filter = ref<string>("");
function fetchDataDashboard() {
showLoader();
const formData = {
year: props?.year,
group: "GROUP1",
period: props?.roundFilter?.id,
snapshot: props?.snapShot,
};
http
.post(config.API.salaryDashboardEmp, formData)
.then((res) => {
const quota = res.data.result.dashboard;
itemsCardAPR.value[0].total = quota.total;
itemsCardAPR.value[1].total = quota.fifteenPercent.toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
itemsCardAPR.value[2].total = quota.chosen;
itemsCardAPR.value[3].total = quota.remaining;
itemsCardAPR.value[4].total = quota.totalBackup;
itemsCardOCT.value[0].total = quota.currentAmount;
itemsCardOCT.value[1].total = quota.sixPercentAmount;
itemsCardOCT.value[2].total = quota.spentAmount;
itemsCardOCT.value[3].total = quota.sixPercentSpentAmount;
itemsCardOCT.value[4].total = quota.useAmount;
itemsCardOCT.value[5].total = quota.remainingAmount;
itemsCardOCT.value[6].total = quota.totalBackup;
itemsCardSpeciel.value[0].total = quota.total;
itemsCardSpeciel.value[1].total = quota.chosen;
itemsCardSpeciel.value[2].total = quota.totalBackup;
rows.value = res.data.result.salaryOrg;
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
}
onMounted(() => {
fetchDataDashboard();
});
watch([() => props?.snapShot, () => props.roundFilter], () => {
fetchDataDashboard();
});
</script>
<template>
<!-- Card โควต -->
<div class="row col-12 q-pa-md bg-grey-2">
<div class="row col-12 q-col-gutter-sm">
<div
v-for="(item, index) in itemsCard"
:key="index"
:class="
store.roundMainCode === 'APR'
? 'col-6 col-sm-4 col-md-3 col-lg-2'
: 'col-3'
"
>
<q-card>
<q-card-section>
<div class="row items-center no-wrap">
<div class="col">
<div class="">{{ item.lable }}</div>
</div>
<div :class="`text-${item.color} text-bold`">
{{
item.total
? store.roundMainCode === "OCT"
? item.total.toLocaleString("en", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
: item.total
: 0
}}
</div>
</div>
</q-card-section>
</q-card>
</div>
</div>
</div>
<q-separator />
<div class="row q-pa-md">
<div class="col-12">
<q-toolbar style="padding: 0px">
<q-space />
<div class="row q-col-gutter-sm">
<q-input
borderless
dense
debounce="300"
v-model="filter"
placeholder="ค้นหา"
outlined
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<q-select
v-model="visibleColumns"
multiple
outlined
dense
options-dense
:display-value="$q.lang.table.columns"
emit-value
map-options
:options="columns"
option-value="name"
options-cover
style="min-width: 150px"
/>
</div>
</q-toolbar>
</div>
<div class="col-12">
<d-table
for="table"
ref="table"
:columns="columns"
:rows="rows"
row-key="id"
flat
bordered
dense
class="custom-header-table"
:filter="filter"
:visible-columns="visibleColumns"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th v-for="col in props.cols" :key="col.name" :props="props">
<span class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<div class="table_ellipsis">
{{ col.value }}
</div>
</q-td>
</q-tr>
</template>
</d-table>
</div>
</div>
</template>
<style scoped></style>

View file

@ -0,0 +1,321 @@
<script setup lang="ts">
import { ref, reactive, watch } from "vue";
import { useQuasar } from "quasar";
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import config from "@/app.config";
import http from "@/plugins/http";
/** importType*/
import type { QTableProps } from "quasar";
import type { NewPagination } from "@/modules/13_salary/interface/index/Main";
import type { DataFilterPerson } from "@/modules/13_salary/interface/index/SalaryList";
import type { DataPersonReq } from "@/modules/13_salary/interface/request/SalaryList";
import type { DataPerson } from "@/modules/13_salary/interface/response/SalaryList";
/** importComponents*/
import Header from "@/components/DialogHeader.vue";
/** use*/
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const { messageError, showLoader, hideLoader, dialogConfirm, success } =
useCounterMixin();
/** props*/
const modal = defineModel<boolean>("modal", { required: true });
const props = defineProps({
fetchData: {
type: Function,
},
});
/** Table*/
const rows = ref<DataPerson[]>([]);
const columns = ref<QTableProps["columns"]>([
{
name: "no",
align: "left",
label: "ลำดับ",
sortable: false,
field: "no",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "citizenId",
align: "left",
label: "เลขประจำตัวประชาชน",
sortable: true,
field: "citizenId",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "fullName",
align: "left",
label: "ชื่อ-นามสกุล",
field: "fullName",
sortable: true,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "position",
align: "left",
label: "ตำแหน่ง",
sortable: true,
field: "position",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posTypeName",
align: "left",
label: "กลุ่มงาน",
sortable: true,
field: "posTypeName",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posLevelName",
align: "left",
label: "ระดับชั้นงาน",
sortable: true,
field: "posLevelName",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
]);
/** ข้อมูุลค้นหา*/
const formFilter = reactive<DataFilterPerson>({
page: 1,
pageSize: 10,
keyword: "",
rootId: "",
year: 0,
period: "",
});
const maxPage = ref<number>(1);
/**
* function close popup
*/
function closeModal() {
modal.value = false;
formFilter.page = 1;
formFilter.keyword = "";
}
/**
* function เรยกรายช คนเลอนเงนเดอน
*/
async function fetchListPerson() {
showLoader();
formFilter.rootId = store.rootId;
formFilter.period = store.roundMainCode;
formFilter.year = store.roundYear;
await http
.post(config.API.salaryListPersonEmp, formFilter)
.then((res) => {
const data = res.data.result.data;
maxPage.value = Math.ceil(res.data.result.total / formFilter.pageSize);
rows.value = data;
})
.catch((err) => {
messageError($q, err);
closeModal();
})
.finally(() => {
hideLoader();
});
}
/**
* function นยนการเพมคนเลอนเงนเดอน
* @param data อมลคนทเพ
*/
function onClickAddPerson(data: DataPerson) {
data.rank = undefined;
const body: DataPersonReq = {
id: store.groupId,
type: store.tabType,
...data,
};
dialogConfirm(
$q,
() => {
showLoader();
http
.post(config.API.salaryPeriodProfileEmp, body)
.then(async () => {
await props.fetchData?.();
await success($q, "เพื่มรายชื่อสำเร็จ");
closeModal();
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
},
"ยืนยันการเพิ่มรายชื่อ",
"ต้องการยืนยันการเพิ่มรายชื่อนี้ใช่หรือไม่?"
);
}
/**
* function updatePage
*/
async function updatePagePagination() {
fetchListPerson();
}
/**
* function updatePageSize
*/
function updatePageSizePagination(newPagination: NewPagination) {
formFilter.page = 1;
formFilter.pageSize = newPagination.rowsPerPage;
}
/**
* function นหาขอมลตาม keyword
*/
function searchData() {
formFilter.page = 1;
fetchListPerson();
}
/**
* callblack function เรยกขอมลรายชอคนเลอนเงนเดอน เมอมการเป Popup
*/
watch(
() => modal.value,
() => {
if (modal.value) {
fetchListPerson();
}
}
);
/**
* callblack function เรยกขอมลรายชอคนเลอนเงนเดอน เมอมการเปลยน PageSize
*/
watch(
() => formFilter.pageSize,
() => {
updatePagePagination();
}
);
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card style="max-width: 100vw">
<Header :tittle="'เพิ่มคนเลื่อนเงินเดือน'" :close="closeModal" />
<q-separator />
<q-card-section class="scroll" style="max-height: 70vh">
<div class="row q-col-gutter-sm">
<div class="col-12">
<q-input
borderless
dense
debounce="300"
outlined
placeholder="ค้นหา"
v-model="formFilter.keyword"
@keydown.enter.prevent="searchData"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</div>
<div class="col-12">
<d-table
ref="table"
:columns="columns"
:rows="rows"
row-key="id"
flat
bordered
:paging="true"
dense
:rows-per-page-options="[10, 25, 50, 100]"
@update:pagination="updatePageSizePagination"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<span class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer">
<q-td>
<q-btn
outline
color="primary"
label="เพิ่ม"
@click="onClickAddPerson(props.row)"
/>
</q-td>
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<div v-if="col.name === 'no'">
{{
(formFilter.page - 1) * formFilter.pageSize +
props.rowIndex +
1
}}
</div>
<div v-else-if="col.name === 'fullName'">
{{
`${props.row.prefix}${props.row.firstName} ${props.row.lastName}`
}}
</div>
<div v-else>
{{ col.value ? col.value : "-" }}
</div>
</q-td>
</q-tr>
</template>
<template v-slot:pagination="scope">
<q-pagination
v-model="formFilter.page"
active-color="primary"
color="dark"
:max="Number(maxPage)"
:max-pages="5"
size="sm"
boundary-links
direction-links
@update:model-value="updatePagePagination()"
></q-pagination>
</template>
</d-table>
</div>
</div>
</q-card-section>
<q-separator />
</q-card>
</q-dialog>
</template>
<style scoped></style>

View file

@ -0,0 +1,104 @@
<script setup lang="ts">
import { useQuasar } from "quasar";
import { useCounterMixin } from "@/stores/mixin";
import http from "@/plugins/http";
import config from "@/app.config";
/** importComponents*/
import Header from "@/components/DialogHeader.vue";
/** use*/
const $q = useQuasar();
const mixin = useCounterMixin();
const { dialogConfirm, success, messageError, showLoader, hideLoader } = mixin;
/** props*/
const modal = defineModel<boolean>("modal", { required: true });
const amount = defineModel<number | null>("amount", { required: true });
const profileId = defineModel<string>("profileId", { required: true });
const props = defineProps({
fetchData: {
type: Function,
},
});
/** function ปืด Popup */
function close() {
modal.value = false;
amount.value = null;
}
/**
* function นยนการบนทกขอม
*/
function onSubmit() {
dialogConfirm($q, async () => {
if (amount.value !== null) {
showLoader();
const amountString: string = amount.value.toString();
const body = {
profileId: profileId.value,
amount:
typeof amount.value === "number"
? amount.value
: Number(amountString.replace(/,/g, "")),
};
await http
.post(config.API.salaryPeriodEmp() + `/change/amount`, body)
.then(async () => {
await props.fetchData?.();
await success($q, "บันทึกข้อมูลสำเร็จ");
close();
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
});
}
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card class="col-12" style="width: 30%">
<q-form greedy @submit.prevent @validation-success="onSubmit">
<Header :tittle="`แก้ไขเงินเดือน`" :close="close" />
<q-separator />
<q-card-section class="scroll" style="max-height: 70vh">
<div class="q-gutter-y-sm">
<q-input
ref="amountRef"
dense
outlined
v-model="amount"
label="เงินเดือนฐาน"
mask="###,###,###,###"
reverse-fill-mask
:rules="[(val) => !!val || `${'กรุณากรอกเงินเดือนฐาน'}`]"
lazy-rules
hide-bottom-space
class="inputgreen"
/>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn
type="submit"
for="#submitForm"
color="secondary"
label="บันทึก"
/>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,163 @@
<script setup lang="ts">
import { ref } from "vue";
import Header from "@/components/DialogHeader.vue";
const modal = defineModel<boolean>("modal", { required: true });
const separator = ref<any>("cell");
/**
* Dialog
*/
function closeDialog() {
modal.value = !modal.value;
}
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card class="col-12" full-width>
<Header
tittle="หลักเกณฑ์การพิจารณาเลื่อนขั้นเงินข้าราชการ"
:close="closeDialog"
/>
<q-separator />
<q-card-section>
<div class="q-pa-md">
<q-markup-table
:separator="separator"
flat
bordered
class="custom-header-table"
>
<thead>
<tr>
<th class="text-center">หลกเกณฑในการพจารณาเลอนข </th>
<th class="text-center">หนงข</th>
<th class="text-center">ครงข</th>
<th class="text-center">ไมไดบการเลอนข</th>
</tr>
</thead>
<tbody>
<!-- อ1 -->
<tr>
<td class="text-left">1. ผลการประเมนผลการปฎราชการ</td>
<td class="text-center">
ผลการประเมนผลในระดบดเด<br />(90-100%)
</td>
<td class="text-center">
ผลการประเมนเปนทยอมรบได<br />(60-89%)
</td>
<td class="text-center">
ผลการประเมนตองปรบปร<br />(ำกว 60%)
</td>
</tr>
<!-- อ2 -->
<tr>
<td class="text-left">
2. ระยะเวลาการปฎราชการในรอบครงป
</td>
<td class="text-center">ไมอยกว 4 เดอน</td>
<td class="text-center">ไมอยกว 4 เดอน</td>
<td class="text-center">
อยกว 4 เดอน<br />(บรรจใหม ลาศกษา กอบรมดงาน)
</td>
</tr>
<!-- อ3 -->
<tr>
<td class="text-left">3. การลงโทษทางว</td>
<td class="text-center">
กสงลงโทษไมหนกกว<br />ภาคทณฑ
</td>
<td class="text-center">
กสงลงโทษไมหนกกว<br />ภาคทณฑ
</td>
<td class="text-center">
กสงลงโทษไมหนกกว<br />ภาคทณฑ
</td>
</tr>
<!-- อ4 -->
<tr>
<td class="text-left">4. กราชการ</td>
<td class="text-center">ไมกสงพกราชการ</td>
<td class="text-center">
กสงพกราชการ<br />ไมเก 2 เดอน
</td>
<td class="text-center">
กสงพกราชการ<br />ไมเก 2 เดอน
</td>
</tr>
<!-- อ5 -->
<tr>
<td class="text-left">5. ขาดราชการ</td>
<td class="text-center">ไมขาดราชการ</td>
<td class="text-center">ไมขาดราชการ</td>
<td class="text-center">ไมขาดราชการ</td>
</tr>
<!-- อ6 -->
<tr class="vertical-top">
<td class="text-left">6. นลา</td>
<td class="text-left">
<p class="q-mb-none txt-under">
ไมเก 5 คร 23 <br />สายไมเก 5 คร
</p>
<p class="q-mb-none">(บเฉพาะการลาปวยลาก)</p>
<p class="text-bold q-mb-none">ยกเว</p>
<p class="q-mb-none">
- ลาอปสมบท
<br />
- ลาคลอดบตร (ไมเก 90 )
<br />
* กรณลาปวยซงจำเปนตองรกษาตวเปนเวลานาน<br />ไมาคราวเดยวหรอหลายคราว
รวมก<span class="txt-under">ไมเก</span> 60 นทำการ
</p>
</td>
<td class="text-left">
<p class="txt-under q-mb-none">
ไมเก 8 คร 23 <br />สายไมเก 23 คร
</p>
<p class="q-mb-none">(บเฉพาะการลาปวยลาก)</p>
<p class="text-bold q-mb-none">ยกเว</p>
<p class="q-mb-none">
- ลาอปสมบท
<br />
- ลาคลอดบตร (ไมเก 90 )
<br />
* กรณลาปวยซงจำเปนตองรกษาตวเปนเวลานาน<br />ไมาคราวเดยวหรอหลายคราว
รวมก<span class="txt-under">ไมเก</span> 60 นทำการ
</p>
</td>
<td class="text-left">
<p class="txt-under q-mb-none">
ไมเก 8 คร 23 <br />สายไมเก 23 คร
</p>
<p>(บเฉพาะการลาปวยลาก)</p>
<br />
<br />
<p class="q-mb-none">
* กรณลาปวยซงจำเปนตองรกษาตวเปนเวลานาน<br />ไมาคราวเดยวหรอหลายคราว
รวมก<span class="txt-under">ไมเก</span> 60 นทำการ
</p>
</td>
</tr>
</tbody>
</q-markup-table>
</div>
</q-card-section>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped>
.txt-under {
text-decoration: underline;
}
.q-table th {
font-size: 14px !important;
}
</style>

View file

@ -0,0 +1,128 @@
<script setup lang="ts">
import { ref, defineModel, watch } from "vue";
import { useQuasar } from "quasar";
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import http from "@/plugins/http";
import config from "@/app.config";
/** importComponents*/
import Header from "@/components/DialogHeader.vue";
/** use*/
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const { dialogConfirm, success, messageError, showLoader, hideLoader } =
useCounterMixin();
/** props*/
const modal = defineModel<boolean>("modal", { required: true });
const profileId = defineModel<string>("profileId", { required: true });
const props = defineProps({
group: { type: String },
fetchData: {
type: Function,
},
});
/** ตัวแปร*/
const group = ref<string>(""); //
const isReadonly = ref<boolean>(false); //
/**
* function Popup
*/
function close() {
modal.value = false;
group.value = "";
}
/**
* function นยนการบนทกขอม
*/
function onSubmit() {
dialogConfirm($q, async () => {
showLoader();
const body = {
profileId: profileId.value,
groupId: group.value,
};
await http
.post(config.API.salaryPeriod() + `/change/group`, body)
.then(async () => {
await props.fetchData?.();
await success($q, "บันทึกข้อมูลสำเร็จ");
close();
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
});
}
function inputEdit(val: boolean) {
return {
"full-width cursor-pointer inputgreen ": val,
"full-width cursor-pointer inputgreen": !val,
};
}
watch(
() => modal.value,
() => {
if (modal.value) {
group.value =
props.group === "กลุ่ม1" ? store.groupOp[1].id : store.groupOp[0].id;
}
}
);
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card class="col-12" style="width: 30%">
<q-form greedy @submit.prevent @validation-success="onSubmit">
<Header :tittle="`ย้ายกลุ่ม`" :close="close" />
<q-separator />
<q-card-section class="scroll" style="max-height: 70vh">
<div class="q-gutter-y-sm">
<q-select
ref="groupRef"
:class="inputEdit(isReadonly)"
v-model="group"
label="กลุ่ม"
dense
outlined
emit-value
map-options
option-label="name"
option-value="id"
:options="store.groupOp.filter((e) => e.name !== props.group)"
:rules="[(val) => !!val || `${'กรุณากลุ่ม'}`]"
lazy-rules
hide-bottom-space
/>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn
type="submit"
for="#submitForm"
color="secondary"
label="บันทึก"
/>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,182 @@
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { useQuasar } from "quasar";
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import http from "@/plugins/http";
import config from "@/app.config";
/** importComponents*/
import Header from "@/components/DialogHeader.vue";
/** use*/
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const { dialogConfirm, success, messageError, showLoader, hideLoader } =
useCounterMixin();
/**porps*/
const modal = defineModel<boolean>("modal", { required: true });
const profileId = defineModel<string>("profileId", { required: true });
const props = defineProps({
typeLevel: { type: String, required: true },
isReserve: { type: Boolean, required: true },
remark: { type: String, required: true },
fetchData: {
type: Function,
},
});
const type = ref<string>(""); //
const note = ref<string>(""); //
const isReadonly = ref<boolean>(false); //
const isChange = ref<boolean>(false); //
const isReserve = ref<boolean>(false); //
const typeRangeOps = computed(() => {
return store.roundMainCode === "OCT"
? [
{ id: "NONE", name: "ไม่ได้เลื่อน" },
{ id: "HAFT", name: "0.5 ขั้น" },
{ id: "FULL", name: "1 ขั้น" },
{ id: "FULLHAFT", name: "1.5 ขั้น" },
]
: store.roundMainCode === "APR"
? [
{ id: "NONE", name: "ไม่ได้เลื่อน" },
{ id: "HAFT", name: "0.5 ขั้น" },
{ id: "FULL", name: "1 ขั้น" },
]
: [
{ id: "HAFT", name: "0.5 ขั้น" },
{ id: "FULL", name: "1 ขั้น" },
];
});
/**
* function Popup
*/
function close() {
modal.value = false;
type.value = "";
}
/**
* function นยนการบนทกขอม
*/
function onSubmit() {
dialogConfirm($q, async () => {
showLoader();
const body = {
profileId: profileId.value,
type: type.value,
isReserve: isReserve.value,
remark: type.value === "NONE" ? note.value : undefined,
};
await http
.post(config.API.salaryPeriodEmp() + `/change/type`, body)
.then(async () => {
await props.fetchData?.();
await success($q, "บันทึกข้อมูลสำเร็จ");
close();
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
});
}
/**
* function เปลยนระด
*/
function chengType() {
note.value = props.typeLevel === "NONE" ? props.remark : "";
}
function inputEdit(val: boolean) {
return {
"full-width cursor-pointer inputgreen ": val,
"full-width cursor-pointer inputgreen": !val,
};
}
watch(
() => modal.value,
() => {
type.value = props.typeLevel == "PENDING" ? "" : props.typeLevel;
note.value = props.typeLevel === "NONE" ? props.remark : "";
isReserve.value = props.isReserve;
isChange.value = false;
}
);
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card class="col-12" style="width: 30%">
<q-form greedy @submit.prevent @validation-success="onSubmit">
<Header :tittle="`เลื่อนขั้น`" :close="close" />
<q-separator />
<q-card-section class="scroll" style="max-height: 70vh">
<div class="q-gutter-y-sm">
<q-select
ref="typeRef"
:class="inputEdit(isReadonly)"
v-model="type"
label="เลื่อนขั้น"
dense
outlined
emit-value
map-options
option-label="name"
option-value="id"
:options="typeRangeOps"
:rules="[(val) => !!val || `${'กรุณาเลือก ขั้น'}`]"
lazy-rules
hide-bottom-space
@update:model-value="(isChange = true), chengType()"
/>
<!-- :options="typeRangeOps.filter((e) => e.id !== store.tabType)" -->
<q-checkbox
v-if="type === 'FULL'"
keep-color
label="สำรอง"
dense
v-model="isReserve"
@update:model-value="isChange = true"
/>
<q-input
v-if="type === 'NONE'"
:class="inputEdit(isReadonly)"
outlined
dense
v-model="note"
label="หมายเหตุ"
type="textarea"
@update:model-value="isChange = true"
/>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn
:disabled="!isChange"
type="submit"
for="#submitForm"
color="secondary"
label="บันทึก"
/>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,148 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useQuasar } from "quasar";
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import http from "@/plugins/http";
import config from "@/app.config";
/** importComponents*/
import Header from "@/components/DialogHeader.vue";
/** importStore*/
/** use*/
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const {
dialogConfirm,
success,
messageError,
showLoader,
hideLoader,
dialogMessageNotify,
} = useCounterMixin();
/**porps*/
const modal = defineModel<boolean>("modal", { required: true });
const profileId = defineModel<string>("id", { required: true });
const props = defineProps({
isPunish: { type: Boolean, required: true },
isSuspension: { type: Boolean, required: true },
isAbsent: { type: Boolean, required: true },
isLeave: { type: Boolean, required: true },
fetchData: {
type: Function,
},
});
const type = ref<string>("");
const isPunish = ref<boolean>(false); //
const isSuspension = ref<boolean>(false); //
const isAbsent = ref<boolean>(false); //
const isLeave = ref<boolean>(false); //
/**
* function Popup
*/
function close() {
modal.value = false;
type.value = "";
}
/**
* function นยนการบนทกขอม
*/
function onSubmit() {
dialogConfirm($q, async () => {
showLoader();
const body = {
isPunish: isPunish.value,
isSuspension: isSuspension.value,
isAbsent: isAbsent.value,
isLeave: isLeave.value,
};
await http
.put(config.API.salaryPropertyEmp(profileId.value), body)
.then(async () => {
await props.fetchData?.();
await success($q, "บันทึกข้อมูลสำเร็จ");
close();
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
});
}
watch(
() => modal.value,
() => {
isPunish.value = props.isPunish;
isSuspension.value = props.isSuspension;
isAbsent.value = props.isAbsent;
isLeave.value = props.isLeave;
}
);
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card class="col-12" style="width: 20%">
<q-form greedy @submit.prevent @validation-success="onSubmit">
<Header :tittle="`แก้ไขคุณสมบัติ`" :close="close" />
<q-separator />
<q-card-section class="scroll" style="max-height: 70vh">
<div class="q-gutter-y-sm column">
<q-checkbox
toggle-indeterminate
keep-color
label="ไม่ถูกลงโทษทางวินัย"
dense
v-model="isPunish"
/>
<q-checkbox
toggle-indeterminate
keep-color
label="ไม่ถูกพักราชการ"
dense
v-model="isSuspension"
/>
<q-checkbox
toggle-indeterminate
keep-color
label="ไม่ขาดราชการ"
dense
v-model="isAbsent"
/>
<q-checkbox
toggle-indeterminate
keep-color
label="วันลาไม่เกิน"
dense
v-model="isLeave"
/>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right" class="bg-white text-teal">
<q-btn
type="submit"
for="#submitForm"
color="secondary"
label="บันทึก"
/>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,456 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useQuasar } from "quasar";
import axios from "axios";
import { checkPermission } from "@/utils/permissions";
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import http from "@/plugins/http";
import config from "@/app.config";
import DialogPopupReason from "@/components/Dialogs/PopupReason.vue"; //
const $q = useQuasar(); // noti quasar
const mixin = useCounterMixin();
const store = useSalaryEmployeeListSDataStore();
const {
messageError,
dialogConfirm,
showLoader,
hideLoader,
success,
dialogRemove,
} = mixin;
const props = defineProps({
rootId: String,
periodId: String,
getData: Function,
});
const modalRecommend = ref<boolean>(false); //popup
const titleRecommend = ref<string>(""); // Popup
const sendStep = ref<number>(1);
const fileUpload = ref<any>(null); //
const type = ref<string>(""); //
const listFile = ref<any[]>([]); //
/**
* function ปโหลดไฟลเจาหนาท
* @param event file
*/
async function uploadFile(event: any) {
dialogConfirm(
$q,
async () => {
showLoader();
http
.post(
config.API.subFile(
"ระบบเงินเดือน",
"เลื่อนค่าจ้าง",
props.periodId ? props.periodId : "",
props.rootId ? props.rootId : ""
),
{
replace: false, //
fileList: [
{
fileName: event.name, //
},
],
}
)
.then(async (res) => {
const foundKey: any = Object.keys(res.data).find(
(key) =>
res.data[key]?.fileName !== undefined &&
res.data[key]?.fileName !== ""
);
const link = res.data[foundKey]?.uploadUrl;
await fileUpLoad(link);
})
.catch((err) => {
messageError($q, err);
hideLoader();
});
},
"ยืนยันการอัปโหลดไฟล์",
"ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่ ?"
);
}
/**
* functoin ปโหลดไฟล
* @param uploadUrl link ปโหลด
*/
function fileUpLoad(url: string) {
axios
.put(url, fileUpload.value, {
headers: { "Content-Type": fileUpload.value?.type },
onUploadProgress: (e) => console.log(e),
})
.then(async () => {
await fetchListFile();
await success($q, "อัปโหลดไฟล์สำเร็จ");
fileUpload.value = null;
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
/**
* นยนการบนทกคำแนะนำ
* @param reason คำแนะนำ
*/
function saveReccommend(reason: string) {
dialogConfirm(
$q,
async () => {
showLoader();
http
.put(
config.API.salaryPeriodStatusCommentEmp(
type.value,
props.periodId ? props.periodId : "",
props.rootId ? props.rootId : ""
),
{
titleRecommend: reason,
}
)
.then(async () => {
await props.getData?.();
sendStep.value = sendStep.value + 1;
modalRecommend.value = false;
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
},
"ยืนยันการ" + titleRecommend.value,
"ต้องการยืนยันการ" + titleRecommend.value + "หรือไม่?"
);
}
/**
* นยนการส
* @param msg งเอกสารให ผอ. ตรวจสอบ,นยนและสงเอกสารให,นยนการตรวจสอบ
* @param type officer, head,owner
*/
function sendToDirector(msg: string, type: string) {
dialogConfirm(
$q,
async () => {
showLoader();
http
.get(
config.API.salaryPeriodStatusEmp(
type,
props.periodId ? props.periodId : "",
props.rootId ? props.rootId : ""
)
)
.then(async () => {
await props.getData?.();
sendStep.value = sendStep.value == 3 ? 6 : sendStep.value + 1;
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
},
"ยืนยันการ" + msg,
"ต้องการยืนยันการ" + msg + "หรือไม่?"
);
}
/**
* งคำแนะนำให ผอ. ตรวจสอบ
* @param title วข
* @param typeOrder ประเภทคำส
*/
function sendAndRecommend(title: string, typeOrder: string) {
modalRecommend.value = true;
titleRecommend.value = title;
type.value = typeOrder;
}
/**
* fetch รายการไฟล
*/
async function fetchListFile() {
await http
.get(
config.API.subFile(
"ระบบเงินเดือน",
"เลื่อนค่าจ้าง",
props.periodId ? props.periodId : "",
props.rootId ? props.rootId : ""
)
)
.then(async (res) => {
listFile.value = res.data;
})
.catch((err) => {
messageError($q, err);
});
}
/**
* ลบไฟล
* @param fileName อไฟล
*/
function onDeleteFile(fileName: string) {
dialogRemove($q, () => {
showLoader();
http
.delete(
config.API.subFileByFileName(
"ระบบเงินเดือน",
"เลื่อนค่าจ้าง",
props.periodId ? props.periodId : "",
props.rootId ? props.rootId : "",
fileName
)
)
.then(() => {
setTimeout(async () => {
await fetchListFile();
await success($q, "ลบไฟล์สำเร็จ");
await hideLoader();
}, 2000);
})
.catch((e) => {
messageError($q, e);
hideLoader();
});
});
}
/**
* โหลดไฟล
* @param fileName อไฟล
*/
function downloadFile(fileName: string) {
showLoader();
http
.get(
config.API.subFileByFileName(
"ระบบเงินเดือน",
"เลื่อนค่าจ้าง",
props.periodId ? props.periodId : "",
props.rootId ? props.rootId : "",
fileName
)
)
.then((res) => {
const data = res.data;
window.open(data.downloadUrl, "_blank");
})
.catch((e) => {
messageError($q, e);
})
.finally(async () => {
hideLoader();
});
}
onMounted(() => {
if (props.rootId) {
fetchListFile();
}
});
</script>
<template>
<q-card class="col-12">
<q-card-section>
<q-toolbar
class="q-pa-none"
v-if="
store.statusQuota == 'PENDING' &&
checkPermission($route)?.attrIsUpdate
"
>
<q-file
bg-color="white"
clearable
outlined
dense
v-model="fileUpload"
label="อัปโหลดไฟล์"
>
<template v-slot:prepend>
<q-icon color="light-blue" name="attach_file" />
<q-tooltip>ปโหลดไฟล</q-tooltip>
</template>
</q-file>
<q-btn
dense
flat
round
color="light-blue"
icon="upload"
@click="uploadFile(fileUpload)"
v-if="fileUpload !== null"
>
<q-tooltip>ปโหลดไฟล</q-tooltip>
</q-btn>
<q-space />
</q-toolbar>
<div class="row">
<div class="col-6" v-if="listFile.length !== 0">
<q-card bordered style="border: 1px solid #d6dee1">
<div class="text-weight-medium bg-grey-1 q-py-sm q-px-md">
รายการเอกสาร
</div>
<q-list bordered separator>
<q-item clickable v-ripple v-for="item in listFile">
<q-item-section>{{ item.fileName }}</q-item-section>
<q-item-section avatar>
<div class="row">
<div>
<q-btn
v-if="checkPermission($route)?.attrIsGet"
dense
flat
round
color="blue"
icon="mdi-download"
@click="downloadFile(item.fileName)"
>
<q-tooltip>ดาวนโหลดเอกสาร</q-tooltip>
</q-btn>
</div>
<div>
<q-btn
v-if="checkPermission($route)?.attrIsDelete"
dense
flat
round
color="red"
icon="mdi-delete"
@click="onDeleteFile(item.fileName)"
><q-tooltip>ลบเอกสาร</q-tooltip></q-btn
>
</div>
</div>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<div>
<!-- การเจาหนาทของหนวยงานสงเอกสารให ผอ. หนวยงานตรวจสอบ -->
<q-btn
v-if="
store.statusQuota == 'PENDING' &&
checkPermission($route)?.attrIsUpdate
"
unelevated
color="public"
label="ส่งเอกสารให้ ผอ. ตรวจสอบ"
@click="sendToDirector('ส่งเอกสารให้ ผอ. ตรวจสอบ', 'officer')"
/>
<!-- ผอ. หนวยงานทำการยนยนและสงให สกจ. -->
<q-btn
v-if="
store.statusQuota == 'WAITHEAD1' &&
checkPermission($route)?.attrIsUpdate
"
unelevated
color="public"
label="ยืนยันและส่งเอกสารให้ สกจ."
@click="sendToDirector('ยืนยันและส่งเอกสารให้ สกจ.', 'head')"
/>
<!-- สกจ. ตรวจสอบเอกสารและขอมลรายการเงนเดอนทแตละหนวยงานสงมา ไมปรบโควต -->
<q-btn
v-if="
store.statusQuota == 'WAITOWNER1' &&
checkPermission($route)?.attrIsUpdate
"
unelevated
color="green"
label="ยืนยันการตรวจสอบ"
@click="sendToDirector('ยืนยันการตรวจสอบ', 'owner')"
/>
<!-- สกจ. ตรวจสอบเอกสารและขอมลรายการเงนเดอนทแตละหนวยงานสงมา ปรบโควต -->
<q-btn
v-if="
store.statusQuota == 'WAITOWNER1' &&
checkPermission($route)?.attrIsUpdate
"
class="q-ml-sm"
unelevated
color="warning"
label="ส่งคำแนะนำให้ ผอ. ตรวจสอบ"
@click="sendAndRecommend('ส่งคำแนะนำให้ ผอ. ตรวจสอบ', 'owner')"
/>
<!-- ผอ.หนวยงานสงคำแนะนำใหการเจาหนาทหนวยงาน -->
<q-btn
v-if="
store.statusQuota == 'WAITHEAD2' &&
checkPermission($route)?.attrIsUpdate
"
unelevated
color="public"
label="ส่งคำแนะนำให้การเจ้าหน้าที่หน่วยงาน"
@click="
sendAndRecommend('ส่งคำแนะนำให้การเจ้าหน้าที่หน่วยงาน', 'head')
"
/>
<q-btn
v-if="
store.statusQuota == 'WAITOFFICER2' &&
checkPermission($route)?.attrIsUpdate
"
unelevated
color="public"
label="ส่งไปออกคำสั่ง"
/>
<q-btn
v-if="
store.statusQuota == 'REPORT' &&
checkPermission($route)?.attrIsUpdate
"
unelevated
color="public"
disable
label="รอออกคำสั่ง"
/>
</div>
</q-card-actions>
</q-card>
<DialogPopupReason
v-model:modal="modalRecommend"
:title="titleRecommend"
label="คำแนะนำ"
:savaForm="saveReccommend"
textReport=""
/>
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,543 @@
<script setup lang="ts">
import { ref, onMounted, reactive, computed } from "vue";
import { useQuasar } from "quasar";
import { checkPermission } from "@/utils/permissions";
import http from "@/plugins/http";
import config from "@/app.config";
import genReportXLSX from "@/plugins/genreportxlsx";
/** importType*/
import type { DataOption } from "@/modules/13_salary/interface/index/Main";
import type { DataFilter } from "@/modules/13_salary/interface/index/SalaryList";
import type {
DataPeriodLatest,
DataPeriod,
} from "@/modules/13_salary/interface/response/SalaryList";
/** importComponents*/
import TableTabType1 from "@/modules/13_salary/components/05_salaryListsEmployee/TableTypePending.vue";
import TableTabType2 from "@/modules/13_salary/components/05_salaryListsEmployee/TableTypeOther.vue";
import DialogInfoCriteria from "@/modules/13_salary/components/05_salaryListsEmployee/DialogInfoCriteria.vue";
/** importStore*/
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
/** use*/
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const { messageError, showLoader, hideLoader } = useCounterMixin();
/** props*/
const props = defineProps({
periodLatest: { type: Object as () => DataPeriodLatest, require: true },
rootId: { type: String, require: true },
periodId: { type: String, require: true },
roundFilter: { type: Object, require: true },
});
const splitterModel = ref<number>(13);
const modalDialogInfoCriteria = ref<boolean>(false); //popup
const isRetire = ref<boolean | string>(false); //
const rows = ref<DataPeriod[]>([]); //
const total = ref<number>(0); //
const maxPage = ref<number>(1); //
//itemsTab
const itemsTabGroup = ref([
{
lable: "กลุ่ม 1",
name: "group1",
},
{
lable: "กลุ่ม 2",
name: "group2",
},
]);
//itemsTab
const itemsTabType = computed(() => {
return store.roundMainCode === "OCT"
? [
{
lable: "รายชื่อคนครอง",
name: "tab1",
type: "PENDING",
},
{
lable: "1 ขั้น",
name: "tab2",
type: "FULL",
},
{
lable: "0.5 ขั้น",
name: "tab3",
type: "HAFT",
},
{
lable: "1.5 ขั้น",
name: "tab4",
type: "FULLHAFT",
},
{
lable: "ไม่ได้เลื่อน",
name: "tab4",
type: "NONE",
},
]
: store.roundMainCode === "APR"
? [
{
lable: "รายชื่อคนครอง",
name: "tab1",
type: "PENDING",
},
{
lable: "1 ขั้น",
name: "tab2",
type: "FULL",
},
{
lable: "0.5 ขั้น",
name: "tab3",
type: "HAFT",
},
{
lable: "ไม่ได้เลื่อน",
name: "tab4",
type: "NONE",
},
]
: [
{
lable: "รายชื่อคนครอง",
name: "tab1",
type: "PENDING",
},
{
lable: "1 ขั้น",
name: "tab2",
type: "FULL",
},
{
lable: "0.5 ขั้น",
name: "tab3",
type: "HAFT",
},
];
});
//itemsCard
const itemsCard = ref([
{
lable: "จำนวนคนทั้งหมด",
name: "group1",
color: "secondary",
total: 0,
},
{
lable: "15% ของจำนวนคน",
name: "group2",
color: "light-blue-4",
total: 0,
},
{
lable: "เลือกไปแล้ว",
name: "group2",
color: "primary",
total: 0,
},
{
lable: "คงเหลือโควตา",
name: "group2",
color: "indigo-6",
total: 0,
},
{
lable: "สำรอง",
name: "group2",
color: "red-6",
total: 0,
},
{
lable: "จำนวนเงินคนครองปัจจุบัน",
name: "group1",
color: "secondary",
total: 0,
},
{
lable: "วงเงิน 6%",
name: "group2",
color: "light-blue-4",
total: 0,
},
{
lable: "ยอดเงินที่ใช้ไป",
name: "group2",
color: "primary",
total: 0,
},
{
lable: "วงเงิน 6%-ยอดเงินที่ใช้ไป",
name: "group2",
color: "indigo-6",
total: 0,
},
{
lable: "ใช้ไปเท่าไหร่",
name: "group2",
color: "blue-6",
total: 0,
},
{
lable: "เหลือเท่าไหร่",
name: "group2",
color: "green-6",
total: 0,
},
{
lable: "สำรอง",
name: "group2",
color: "red-6",
total: 0,
},
]);
//
const formFilter = reactive<DataFilter>({
page: 1,
pageSize: 10,
keyword: "",
type: store.tabType,
});
/**
* function เรยกขอมลจำนวนโควต
* @param id กล
*/
function fetchDataQuota(id: string) {
http
.get(config.API.salaryListPeriodQuotaEmp(id))
.then((res) => {
const data = res.data.result;
store.remaining = data.remaining;
store.statusQuota = data.status;
itemsCard.value[0].total = data.total;
itemsCard.value[1].total = data.fifteenPercent;
itemsCard.value[2].total = data.chosen;
itemsCard.value[3].total = data.remaining;
itemsCard.value[4].total = data.totalBackup;
itemsCard.value[5].total = data.currentAmount;
itemsCard.value[6].total = data.sixPercentAmount;
itemsCard.value[7].total = data.spentAmount;
itemsCard.value[8].total = data.sixPercentSpentAmount;
itemsCard.value[9].total = data.useAmount;
itemsCard.value[10].total = data.remainingAmount;
itemsCard.value[11].total = data.totalBackup;
})
.catch((err) => {
messageError($q, err);
});
}
/**
* function เรยกขอมลรายช
* @param id กล
*/
async function fetchDataPeriod(id: string) {
showLoader();
rows.value = [];
let formData = {
page: formFilter.page.toString(),
pageSize: formFilter.pageSize.toString(),
keyword: formFilter.keyword,
type: store.tabType,
isRetire:
store.roundMainCode !== "OCT"
? null
: isRetire.value === true
? "1"
: "0",
};
await http
.put(config.API.salaryListPeriodORGEmp(id), formData)
.then((res) => {
rows.value = res.data.result.data;
total.value = res.data.result.total;
maxPage.value = Math.ceil(res.data.result.total / formFilter.pageSize);
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
setTimeout(() => {
hideLoader();
}, 1000);
});
}
/**
* function เปลยนข
*/
function changeTabType() {
formFilter.page = 1;
formFilter.pageSize = 10;
formFilter.keyword = "";
store.groupId && fetchDataPeriod(store.groupId);
}
/**
* function เรยกขอมลรายชออกคร
*/
function fetchDataPeriodNew() {
store.groupId && fetchDataPeriod(store.groupId);
store.groupId && fetchDataQuota(store.groupId);
}
/**
* function โหลดไฟล
* @param data อมลทองการโหล
* @param type ประเภทไฟล docx,xlsx
*/
async function onClickDownload(data: DataOption, type: string = "xlsx") {
showLoader();
if (data.id === "emp-08" || data.id === "emp2-08") {
const formData = {
type: "HAFT",
startDate:
data.id === "emp-08"
? `${props?.roundFilter?.year - 1}-10-01`
: `${props?.roundFilter?.year}-04-01`,
endDate:
data.id === "emp-08"
? `${props?.roundFilter?.year}-03-31`
: `${props?.roundFilter?.year}-09-30`,
};
await http
.post(config.API.leaveReportLeaveday("employee"), formData)
.then(async (res) => {
const dataList = await res.data.result;
await genReportXLSX(dataList, data.name);
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
} else {
if (props.rootId && props.periodId) {
await http
.get(
config.API.salaryReportListsByid(
data.id,
props.rootId,
props.periodId
)
)
.then(async (res) => {
const dataList = await res.data.result;
await genReportXLSX(dataList, data.name, type);
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
}
}
/**
* funrion แสดงเฉพาะผเกษยณอายราชการ
*/
function updateIsShowRetire() {
isRetire.value = !isRetire.value;
fetchDataPeriodNew();
}
onMounted(() => {
if (props.rootId) {
fetchDataQuota(store.groupId);
fetchDataPeriod(store.groupId);
splitterModel.value = store.roundMainCode === "APR" ? 13 : 16;
}
});
</script>
<template>
<!-- <q-separator /> -->
<q-tab-panels v-model="store.tabGroup" animated class="bg-grey-1">
<q-tab-panel
style="padding: 0px"
v-for="(item, index) in itemsTabGroup"
:key="index"
:name="item.name"
>
<!-- Card โควต -->
<div class="row col-12 q-pa-md" v-if="store.roundMainCode !== 'SPECIAL'">
<div
:class="`row col-12 ${
store.roundMainCode === 'APR'
? `q-col-gutter-md`
: `q-col-gutter-md`
} items-start`"
>
<div
v-for="(item, index) in store.roundMainCode === 'APR'
? itemsCard.slice(0, 5)
: store.roundMainCode === 'OCT'
? itemsCard.slice(5, 12)
: []"
:key="index"
:class="
store.roundMainCode === 'APR'
? 'col-6 col-sm-4 col-md-3 col-lg-2'
: 'col-3'
"
>
<q-card>
<q-card-section>
<div class="row items-center no-wrap">
<div class="col">
<div class="">{{ item.lable }}</div>
</div>
<div :class="`text-${item.color} text-bold`">
{{ item.total ? item.total.toLocaleString() : 0 }}
</div>
</div>
</q-card-section>
</q-card>
</div>
<div
class="row col justify-end self-center"
v-if="
store.roundMainCode !== 'SPECIAL' &&
checkPermission($route)?.attrIsGet
"
>
<q-btn-dropdown color="blue-5" label="ดาวน์โหลด">
<q-list>
<q-item
v-for="(item, index) in store.roundMainCode === 'APR'
? store.itemDownloadApr
: store.roundMainCode === 'OCT'
? store.itemDownloadOct
: []"
:key="index"
clickable
v-close-popup
@click="onClickDownload(item, item.type)"
>
<q-item-section>
<q-item-label>{{ item.name }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<!-- <q-btn color="blue-5" icon="download" label="ดาวน์โหลด" /> -->
</div>
</div>
</div>
<q-separator />
<!-- Tab -->
<q-card flat bordered>
<div v-if="store.roundMainCode === 'OCT'" class="row col-12 q-pa-sm">
<q-toggle
dense
:model-value="isRetire"
color="primary"
@update:model-value="updateIsShowRetire"
class="q-pr-md"
>
แสดงเฉพาะผเกษยณอายราชการ
</q-toggle>
</div>
<q-splitter v-model="splitterModel" disable class="border-top">
<template v-slot:before>
<q-tabs
v-model="store.tabType"
vertical
dense
class="text-grey-black"
active-color="blue-5"
active-class="bg-blue-1"
indicator-color="blue-5"
align="left"
>
<div
v-for="(item, index) in itemsTabType"
:key="index"
class="row"
:style="index === 0 ? 'border-bottom: 1px solid #c8d3db;' : ''"
>
<q-tab
class="col-12"
style="justify-content: left"
:name="item.type"
:label="item.lable"
@click="changeTabType()"
/>
</div>
</q-tabs>
</template>
<template v-slot:after>
<q-tab-panels
v-model="store.tabType"
animated
swipeable
vertical
transition-prev="jump-up"
transition-next="jump-up"
>
<q-tab-panel
class="q-pa-md"
v-for="(item, index) in itemsTabType"
:key="index"
:name="item.type"
>
<TableTabType1
v-if="index === 0"
v-model:max-page="maxPage"
v-model:form-filter="formFilter"
:fetch-data-table="fetchDataPeriodNew"
:rows="rows"
:total="total"
/>
<TableTabType2
v-else
:rows="rows"
v-model:max-page="maxPage"
v-model:form-filter="formFilter"
:fetch-data-table="fetchDataPeriodNew"
:total="total"
:type="item.type"
/>
</q-tab-panel>
</q-tab-panels>
</template>
</q-splitter>
</q-card>
</q-tab-panel>
</q-tab-panels>
<DialogInfoCriteria v-model:modal="modalDialogInfoCriteria" />
</template>
<style scoped>
.my-card {
width: 100%;
max-width: 200px;
}
.q-tabs--vertical .q-tab {
padding: 0 20px;
}
.border-top {
border-top: 1px solid #ededed;
}
</style>

View file

@ -0,0 +1,563 @@
<script setup lang="ts">
import { ref, watch, computed } from "vue";
import { useQuasar } from "quasar";
import { checkPermission } from "@/utils/permissions";
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import config from "@/app.config";
import http from "@/plugins/http";
/** importType*/
import type { QTableProps } from "quasar";
import type { NewPagination } from "@/modules/13_salary/interface/index/Main";
import type { DataFilter } from "@/modules/13_salary/interface/index/SalaryList";
/** importComponents*/
import DialogAddPerson from "@/modules/13_salary/components/05_salaryListsEmployee//DialogAddPerson.vue";
import DialogFormEdit from "@/modules/13_salary/components/05_salaryListsEmployee/DialogFormEditSalary.vue";
import DialogMoveGroup from "@/modules/13_salary/components/05_salaryListsEmployee/DialogMoveGroup.vue";
import DialogMoveLevel from "@/modules/13_salary/components/05_salaryListsEmployee/DialogMoveLevel.vue";
/** use*/
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const { dialogRemove, messageError, showLoader, hideLoader, success } =
useCounterMixin();
/** props*/
const formFilter = defineModel<DataFilter>("formFilter", { required: true });
const maxPage = defineModel<Number>("maxPage", { required: true });
const props = defineProps({
rows: { type: Array },
fetchDataTable: {
type: Function,
},
total: {
type: Number,
},
type: {
type: String,
requird: true,
},
});
/** ข้อมูล Table*/
const baseColumns = ref<QTableProps["columns"]>([
{
name: "no",
align: "left",
label: "ลำดับ",
sortable: true,
field: "no",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posNo",
align: "left",
label: "ตำแหน่งเลขที่",
sortable: true,
field: "posNo",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "fullName",
align: "left",
label: "ชื่อ-นามสกุล",
field: "fullName",
sortable: true,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "position",
align: "left",
label: "ตำแหน่ง",
sortable: true,
field: "position",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posType",
align: "left",
label: "กลุ่มงาน",
sortable: false,
field: "posType",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posLevel",
align: "left",
label: "ระดับชั้นงาน",
sortable: false,
field: "posLevel",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "group",
align: "left",
label: "ผังค่าจ้าง(เดิม)",
sortable: false,
field: "group",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return val ? "กลุ่ม" + val : "-";
},
},
{
name: "salaryLevel",
align: "left",
label: "ขั้น(เดิม)",
sortable: false,
field: "salaryLevel",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "amount",
align: "left",
label: "ค่าจ้างฐาน",
sortable: false,
field: "amount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "amountUse",
align: "left",
label: "จำนวนเงินที่ใช้เลื่อน",
sortable: false,
field: "amountUse",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "positionSalaryAmount",
align: "left",
label: "ค่าจ้างหลังเลื่อน",
sortable: false,
field: "positionSalaryAmount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "isRetired",
align: "center",
label: "เกษียณอายุ",
sortable: false,
field: "isRetired",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "remark",
align: "center",
label: "หมายเหตุ",
sortable: false,
field: "remark",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
]);
const visibleColumns = ref<string[]>([
"no",
"posNo",
"fullName",
"position",
"posType",
"posLevel",
"group",
"salaryLevel",
"amount",
"amountUse",
"positionSalaryAmount",
"isRetired",
"remark",
]);
const columns = computed(() => {
if (props.type !== "NONE") {
if (baseColumns.value) {
return baseColumns.value.filter((column) => column.name !== "remark");
}
}
return baseColumns.value;
});
/** modalDialog*/
const modalDialogAddPerson = ref<boolean>(false); //popup
const modalDialogForm = ref<boolean>(false); //popup
const modalDialogMoveGroup = ref<boolean>(false); //popup
const modalDialogMoveLeve = ref<boolean>(false); //popup
const profileId = ref<string>(""); //id
const amount = ref<number>(0); //
const typeLevel = ref<string>(""); //
const isReserve = ref<boolean>(false); //
const remark = ref<string>(""); //
/**
* function openPopup เพมคนเลอนคาจาง
*/
function onClickAddPerson() {
modalDialogAddPerson.value = !modalDialogAddPerson.value;
}
/**
* function openPopup แกไขคาจาง
* @param id profileId
* @param amountSalary จำนวนคาจาง
*
*/
function onClickEdit(id: string, amountSalary: number) {
profileId.value = id;
amount.value = amountSalary;
modalDialogForm.value = !modalDialogForm.value;
}
/**
* function openPopup ายกล
* @param id profileId
*
*/
function onClickMovieGroup(id: string) {
profileId.value = id;
modalDialogMoveGroup.value = !modalDialogMoveGroup.value;
}
/**
* function openPopup ายกข
* @param id profileId
*
*/
function onClickMoveLevel(
id: string,
typeVal: string,
isReserveVal: boolean,
remarkVal: string = ""
) {
profileId.value = id;
modalDialogMoveLeve.value = !modalDialogMoveLeve.value;
typeLevel.value = typeVal;
isReserve.value = isReserveVal;
remark.value = typeVal === "NONE" ? remarkVal : "";
}
/**
* function นยนการลบรายช
* @param id profileId
*/
function onClickDelete(id: string) {
dialogRemove($q, async () => {
showLoader();
await http
.delete(config.API.salaryListPeriodProfileByIdEmp(id))
.then(async () => {
await props.fetchDataTable?.();
await success($q, "ลบข้อมูลสำเร็จ");
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
});
}
/**
* function updatePageTable
*/
function updatePagePagination() {
props.fetchDataTable?.();
}
/**
* function updatePageSizeTable
*/
function updatePageSizePagination(newPagination: NewPagination) {
formFilter.value.page = 1;
formFilter.value.pageSize = newPagination.rowsPerPage;
}
/**
* function นหาขอม Table
*/
function searchData() {
formFilter.value.page = 1;
props.fetchDataTable?.();
}
/**
* callblack function เรยกขอมลรายชอใหม เมอมการเปลยน PageSize
*/
watch(
() => formFilter.value.pageSize,
() => {
updatePagePagination();
}
);
</script>
<template>
<q-toolbar class="text-primary" style="padding: 0px">
<q-btn
v-if="!store.isClosedRound && checkPermission($route)?.attrIsCreate"
flat
round
dense
icon="add"
@click="onClickAddPerson"
>
<q-tooltip>เพ</q-tooltip>
</q-btn>
<q-space />
<q-input
borderless
dense
debounce="300"
outlined
v-model="formFilter.keyword"
placeholder="ค้นหา"
@keydown.enter.prevent="searchData"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<q-select
for="#select"
v-model="visibleColumns"
multiple
outlined
dense
options-dense
:display-value="$q.lang.table.columns"
emit-value
map-options
:options="
store.roundMainCode === 'OCT'
? columns
: columns
? columns.filter((e) => e.name !== 'isRetired')
: []
"
option-value="name"
options-cover
style="min-width: 150px"
class="col-xs-12 col-sm-3 col-md-2 q-ml-sm"
/>
</q-toolbar>
<d-table
ref="table"
:columns="
store.roundMainCode === 'OCT'
? columns
: columns
? columns.filter((e) => e.name !== 'isRetired')
: []
"
:rows="props.rows"
row-key="id"
flat
bordered
:paging="true"
dense
:rows-per-page-options="[10, 25, 50, 100]"
:visible-columns="visibleColumns"
@update:pagination="updatePageSizePagination"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
<span class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td>
<q-btn
v-if="
!store.isClosedRound &&
(checkPermission($route)?.attrIsUpdate ||
checkPermission($route)?.attrIsDelete)
"
flat
dense
color="secondary"
icon="mdi-dots-horizontal-circle-outline"
round
>
<q-menu>
<q-list dense style="min-width: 150px">
<q-item
v-for="(item, index) in checkPermission($route)?.attrIsUpdate && !checkPermission($route)?.attrIsDelete
? store.itemMenu.filter((x:any)=>x.type !== 'moveGroup' && x.type !== 'properties' && x.type !== 'delete')
: !checkPermission($route)?.attrIsUpdate && checkPermission($route)?.attrIsDelete
? store.itemMenu.filter((x:any)=> x.type === 'delete')
: store.itemMenu.filter((x:any)=>x.type !== 'moveGroup' && x.type !== 'properties' )"
:key="index"
clickable
v-close-popup
@click="
item.type === 'edit'
? onClickEdit(props.row.id, props.row.amount)
: item.type === 'moveGroup'
? onClickMovieGroup(props.row.id)
: item.type === 'moveLevel'
? onClickMoveLevel(
props.row.id,
props.row.type,
props.row.isReserve,
props.row.remark
)
: item.type === 'delete'
? onClickDelete(props.row.id)
: null
"
>
<q-item-section>
<div class="row items-center">
<q-icon
:color="item.color"
size="17px"
:name="item.icon"
/>
<div class="q-pl-md">{{ item.label }}</div>
</div>
</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"
:class="
store.tabType !== 'RETIRE'
? props.row.isNext == true
? 'text-red'
: ''
: ''
"
>
<div v-if="col.name === 'no'">
{{
(formFilter.page - 1) * formFilter.pageSize +
props.rowIndex +
1 +
(store.tabType == "FULL" && props.row.isReserve == true
? " (สำรอง)"
: "")
}}
</div>
<div v-else-if="col.name === 'posNo'">
{{ `${props.row.orgShortName}${props.row.posMasterNo}` }}
</div>
<div v-else-if="col.name === 'fullName'">
{{
`${props.row.prefix}${props.row.firstName} ${props.row.lastName}`
}}
</div>
<div v-else-if="col.name === 'amount'">
{{
props.row.amount !== null ? props.row.amount.toLocaleString() : ""
}}
</div>
<div v-else-if="col.name === 'amountUse'">
{{
props.row.amountUse !== null
? props.row.amountUse > 0
? props.row.amountUse.toLocaleString()
: 0
: ""
}}
{{
props.row.amountSpecial > 0
? `(${props.row.amountSpecial.toLocaleString()})`
: ""
}}
</div>
<div v-else-if="col.name === 'positionSalaryAmount'">
{{
props.row.positionSalaryAmount !== null
? props.row.positionSalaryAmount.toLocaleString()
: ""
}}
{{
props.row.amountSpecial > 0
? `(${props.row.amountSpecial.toLocaleString()})`
: ""
}}
</div>
<div v-else-if="col.name === 'isRetired'">
<q-icon
name="check"
color="primary"
size="24px"
v-if="props.row.isRetired && store.roundMainCode === 'OCT'"
/>
</div>
<div v-else class="table_ellipsis2">
{{ col.value ? col.value : "-" }}
</div>
</q-td>
</q-tr>
</template>
<template v-slot:pagination="scope">
งหมด {{ props.total }} รายการ
<q-pagination
v-model="formFilter.page"
active-color="primary"
color="dark"
:max="Number(maxPage)"
:max-pages="5"
size="sm"
boundary-links
direction-links
@update:model-value="updatePagePagination()"
></q-pagination>
</template>
</d-table>
<DialogAddPerson
v-model:modal="modalDialogAddPerson"
:fetch-data="props.fetchDataTable"
/>
<DialogFormEdit
v-model:modal="modalDialogForm"
v-model:profile-id="profileId"
v-model:amount="amount"
:fetch-data="props.fetchDataTable"
/>
<DialogMoveGroup
v-model:modal="modalDialogMoveGroup"
v-model:profile-id="profileId"
:fetch-data="props.fetchDataTable"
:group="store.tabGroup === 'group1' ? 'กลุ่ม1' : 'กลุ่ม2'"
/>
<DialogMoveLevel
v-model:modal="modalDialogMoveLeve"
v-model:profile-id="profileId"
:fetch-data="props.fetchDataTable"
:type-level="typeLevel"
:is-reserve="isReserve"
:type="store.tabType"
:remark="remark"
/>
</template>
<style scoped></style>

View file

@ -0,0 +1,595 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useQuasar } from "quasar";
import { checkPermission } from "@/utils/permissions";
import { useCounterMixin } from "@/stores/mixin";
import { useSalaryEmployeeListSDataStore } from "@/modules/13_salary/store/SalaryEmployeeListsStore";
import http from "@/plugins/http";
import config from "@/app.config";
/** importType*/
import type { QTableProps } from "quasar";
import type { NewPagination } from "@/modules/13_salary/interface/index/Main";
import type { DataFilter } from "@/modules/13_salary/interface/index/SalaryList";
/** importComponents*/
import DialogAddPerson from "@/modules/13_salary/components/05_salaryListsEmployee//DialogAddPerson.vue";
import DialogFormEdit from "@/modules/13_salary/components/05_salaryListsEmployee/DialogFormEditSalary.vue";
import DialogMoveGroup from "@/modules/13_salary/components/05_salaryListsEmployee/DialogMoveGroup.vue";
import DialogMoveLevel from "@/modules/13_salary/components/05_salaryListsEmployee/DialogMoveLevel.vue";
import DialogProperties from "@/modules/13_salary/components/05_salaryListsEmployee/DialogProperties.vue";
import DialogInfo from "@/modules/13_salary/components/DialogInfoMain.vue";
/** use*/
const $q = useQuasar();
const store = useSalaryEmployeeListSDataStore();
const { dialogRemove, messageError, showLoader, hideLoader, success } =
useCounterMixin();
/** Props*/
const formFilter = defineModel<DataFilter>("formFilter", { required: true });
const maxPage = defineModel<Number>("maxPage", { required: true });
const props = defineProps({
rows: { type: Array },
fetchDataTable: {
type: Function,
},
maxPage: {
type: Number,
},
total: {
type: Number,
},
});
/** ข้อมูล Table*/
const columns = ref<QTableProps["columns"]>([
{
name: "no",
align: "left",
label: "ลำดับ",
sortable: true,
field: "no",
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",
},
{
name: "posType",
align: "left",
label: "กลุ่มงาน",
sortable: true,
field: "posType",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "position",
align: "left",
label: "ตำแหน่ง",
field: "position",
sortable: true,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posLevel",
align: "left",
label: "ระดับชั้นงาน",
field: "posLevel",
sortable: true,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "group",
align: "left",
label: "ผังค่าจ้าง(เดิม)",
sortable: false,
field: "group",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return val ? "กลุ่ม" + val : "-";
},
},
{
name: "salaryLevel",
align: "left",
label: "ขั้น(เดิม)",
sortable: false,
field: "salaryLevel",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "amount",
align: "left",
label: "ค่าจ้าง",
field: "amount",
sortable: true,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "organization",
align: "left",
label: "สังกัด",
sortable: true,
field: "organization",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "result",
align: "center",
label: "ผลการประเมิน ฯ",
sortable: false,
field: "result",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "isSuspension",
align: "center",
label: "ไม่ถูกพักราชการ",
sortable: false,
field: "isSuspension",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posSalary",
align: "center",
label: "ประวัติตำแหน่ง/ค่าจ้าง",
sortable: false,
field: "posSalary",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "discipline",
align: "center",
label: "วินัย",
sortable: false,
field: "discipline",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "leave",
align: "center",
label: "การลา",
sortable: false,
field: "leave",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
]);
const visibleColumns = ref<string[]>([
"no",
"fullName",
"posType",
"position",
"posLevel",
"amount",
"organization",
"result",
"isSuspension",
"group",
"salaryLevel",
"posSalary",
"discipline",
"leave",
]);
/** modalDialog*/
const modalDialogAddPerson = ref<boolean>(false); //popup
const modalDialogForm = ref<boolean>(false); //popup
const modalDialogMoveGroup = ref<boolean>(false); //popup
const modalDialogMoveLeve = ref<boolean>(false); //popup
const modalDialogProperties = ref<boolean>(false); //popup
const modalDialogInfo = ref<boolean>(false); //popup
/** ตัวแปร*/
const profileId = ref<string>(""); //id
const amount = ref<number>(0); //
const typeLevel = ref<string>(""); //
const isReserve = ref<boolean>(false); //
const isPunish = ref<boolean>(false);
const isSuspension = ref<boolean>(false);
const isAbsent = ref<boolean>(false);
const isLeave = ref<boolean>(false);
/**
* function นยนการลบรายช
* @param id profileId
*/
function onClickDelete(id: string) {
dialogRemove($q, async () => {
showLoader();
await http
.delete(config.API.salaryListPeriodProfileByIdEmp(id))
.then(async () => {
await props.fetchDataTable?.();
await success($q, "ลบข้อมูลสำเร็จ");
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
});
}
/**
* function openPopup เพมคนเลอนคาจาง
*/
function onClickAddPerson() {
modalDialogAddPerson.value = !modalDialogAddPerson.value;
}
/**
* function openPopup แกไขคาจาง
* @param id profileId
* @param amountSalary จำนวนคาจาง
*
*/
function onClickEdit(id: string, amountSalary: number) {
profileId.value = id;
amount.value = amountSalary;
modalDialogForm.value = !modalDialogForm.value;
}
/**
* function openPopup ายกล
* @param id profileId
*
*/
function onClickMovieGroup(id: string) {
profileId.value = id;
modalDialogMoveGroup.value = !modalDialogMoveGroup.value;
}
/**
* function openPopup ายกข
* @param id profileId
*
*/
function onClickMoveLevel(id: string, typeVal: string, isReserveVal: boolean) {
profileId.value = id;
modalDialogMoveLeve.value = !modalDialogMoveLeve.value;
typeLevel.value = typeVal;
isReserve.value = isReserveVal;
}
/**
* function updatePageTable
*/
function updatePagePagination() {
props.fetchDataTable?.();
}
/**
* function updatePageSizeTable
*/
function updatePageSizePagination(newPagination: NewPagination) {
formFilter.value.page = 1;
formFilter.value.pageSize = newPagination.rowsPerPage;
}
/**
* function นหาขอม Table
*/
function searchData() {
formFilter.value.page = 1;
props.fetchDataTable?.();
}
/**
* function เป popup ณสมบ
* @param data อมลคณสมบ
*/
function onProperties(data: any) {
modalDialogProperties.value = true;
profileId.value = data.id;
isPunish.value = data.isPunish;
isSuspension.value = data.isSuspension;
isAbsent.value = data.isAbsent;
isLeave.value = data.isLeave;
}
const infoType = ref<string>(""); //
/**
* function อมลสวนต
* @param type ประเภทขอม
* @param id id องการด
*/
function onClickViewInfo(type: string, id: string) {
infoType.value = type;
profileId.value = id;
modalDialogInfo.value = true;
}
/**
* callblack function เรยกขอมลรายชอใหม เมอมการเปลยน PageSize
*/
watch(
() => formFilter.value.pageSize,
() => {
updatePagePagination();
}
);
</script>
<template>
<q-toolbar class="text-primary" style="padding: 0px">
<q-btn
v-if="!store.isClosedRound && checkPermission($route)?.attrIsCreate"
flat
round
dense
icon="add"
@click="onClickAddPerson"
>
<q-tooltip>เพ </q-tooltip>
</q-btn>
<q-space />
<q-input
borderless
dense
debounce="300"
outlined
v-model="formFilter.keyword"
placeholder="ค้นหา"
@keydown.enter.prevent="searchData"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<q-select
for="#select"
v-model="visibleColumns"
multiple
outlined
dense
options-dense
:display-value="$q.lang.table.columns"
emit-value
map-options
:options="!checkPermission($route)?.attrIsGet
? columns?.filter((col:any) => col.name !== 'posSalary' && col.name !== 'discipline' && col.name !== 'leave' )
: columns"
option-value="name"
options-cover
style="min-width: 150px"
class="col-xs-12 col-sm-3 col-md-2 q-ml-sm"
/>
</q-toolbar>
<d-table
ref="table"
:columns="!checkPermission($route)?.attrIsGet
? columns?.filter((col:any) => col.name !== 'posSalary' && col.name !== 'discipline' && col.name !== 'leave' )
: columns"
:rows="props.rows"
row-key="id"
flat
bordered
:paging="true"
dense
:rows-per-page-options="[10, 25, 50, 100]"
:visible-columns="visibleColumns"
@update:pagination="updatePageSizePagination"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width />
<q-th v-for="col in props.cols" :key="col.name" :props="props">
<span class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td>
<q-btn
v-if="
!store.isClosedRound &&
(checkPermission($route)?.attrIsUpdate ||
checkPermission($route)?.attrIsDelete)
"
flat
dense
color="secondary"
icon="mdi-dots-horizontal-circle-outline"
round
>
<q-menu>
<q-list dense style="min-width: 150px">
<q-item
v-for="(item, index) in checkPermission($route)?.attrIsUpdate &&!checkPermission($route)?.attrIsDelete
? store.itemMenu.filter((x:any)=>x.type !== 'moveGroup' && x.type !== 'delete')
: !checkPermission($route)?.attrIsUpdate && checkPermission($route)?.attrIsDelete
? store.itemMenu.filter((x:any)=> x.type === 'delete')
: store.itemMenu.filter((x:any)=>x.type !== 'moveGroup')"
:key="index"
clickable
v-close-popup
@click="
item.type === 'edit'
? onClickEdit(props.row.id, props.row.amount)
: item.type === 'moveGroup'
? onClickMovieGroup(props.row.id)
: item.type === 'moveLevel'
? onClickMoveLevel(
props.row.id,
props.row.type,
props.row.isReserve
)
: item.type === 'delete'
? onClickDelete(props.row.id)
: item.type === 'properties'
? onProperties(props.row)
: null
"
>
<q-item-section>
<div class="row items-center">
<q-icon
:color="item.color"
size="17px"
:name="item.icon"
/>
<div class="q-pl-md">{{ item.label }}</div>
</div>
</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'">
{{
(formFilter.page - 1) * formFilter.pageSize + props.rowIndex + 1
}}
</div>
<div v-else-if="col.name === 'fullName'">
{{
`${props.row.prefix}${props.row.firstName} ${props.row.lastName}`
}}
</div>
<div v-else-if="col.name == 'organization'" class="table_ellipsis">
<div
v-if="
props.row.child4 === null &&
props.row.child3 === null &&
props.row.child2 === null &&
props.row.child1 === null &&
props.row.root === null
"
>
-
</div>
{{
`${props.row.child4 ? props.row.child4 + "/" : ""}${
props.row.child3 ? props.row.child3 + "/" : ""
}${props.row.child2 ? props.row.child2 + "-" : ""}${
props.row.child1 ? props.row.child1 + "/" : ""
}${props.row.root ? props.row.root : ""}`
}}
</div>
<div v-else-if="col.name == 'isSuspension'">
<q-icon
v-if="props.row.isSuspension !== null"
:name="props.row.isSuspension ? 'done' : 'close'"
:color="props.row.isSuspension ? 'primary' : 'red'"
size="24px"
/>
<div v-else-if="props.row.isSuspension == null">
{{ props.row.isSuspension == null ? "-" : "" }}
</div>
</div>
<div v-else-if="col.name == 'amount'">
{{ Number(props.row.amount).toLocaleString() }}
</div>
<div
v-else-if="
col.name == 'posSalary' ||
col.name == 'discipline' ||
col.name == 'leave'
"
>
<q-btn
v-if="checkPermission($route)?.attrIsGet"
flat
dense
icon="info"
class="q-pa-none q-ml-xs"
color="info"
size="12px"
@click.pervent="onClickViewInfo(col.name, props.row.profileId)"
>
</q-btn>
</div>
<div v-else>
{{ col.value ? col.value : "-" }}
</div>
</q-td>
</q-tr>
</template>
<template v-slot:pagination="scope">
งหมด {{ props.total }} รายการ
<q-pagination
v-model="formFilter.page"
active-color="primary"
color="dark"
:max="Number(maxPage)"
:max-pages="5"
size="sm"
boundary-links
direction-links
@update:model-value="updatePagePagination()"
></q-pagination>
</template>
</d-table>
<DialogAddPerson
v-model:modal="modalDialogAddPerson"
:fetch-data="props.fetchDataTable"
/>
<DialogFormEdit
v-model:modal="modalDialogForm"
v-model:profile-id="profileId"
v-model:amount="amount"
:fetch-data="props.fetchDataTable"
/>
<DialogMoveGroup
v-model:modal="modalDialogMoveGroup"
v-model:profile-id="profileId"
:fetch-data="props.fetchDataTable"
:group="store.tabGroup === 'group1' ? 'กลุ่ม1' : 'กลุ่ม2'"
/>
<DialogMoveLevel
v-model:modal="modalDialogMoveLeve"
v-model:profile-id="profileId"
:type-level="typeLevel"
:is-reserve="isReserve"
:fetch-data="props.fetchDataTable"
:remark="''"
/>
<DialogProperties
v-model:modal="modalDialogProperties"
v-model:id="profileId"
:fetch-data="props.fetchDataTable"
:is-punish="isPunish"
:is-suspension="isSuspension"
:is-absent="isAbsent"
:is-leave="isLeave"
/>
<DialogInfo
v-model:modal="modalDialogInfo"
v-model:profile-id="profileId"
:type="infoType"
:employee-class="'-employee'"
/>
</template>
<style scoped></style>