Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 8m15s

* develop:
  fix: async await
  fix:q-input maxlength
  fix currency
  fix(command): edit-salary
This commit is contained in:
Warunee Tamkoo 2025-12-15 15:52:25 +07:00
commit c2b021e243
10 changed files with 599 additions and 29 deletions

View file

@ -25,4 +25,6 @@ export default {
//Digital Signature
commandDirector: `${env.API_URI}/org/profile/commander-director`,
commandEditSalary: `${command}/tab2/edit-salary`,
};

View file

@ -44,7 +44,7 @@ const { inputRef, formattedValue, setValue } = useCurrencyInput({
currency: "THB",
currencyDisplay: "hidden" as any,
hideCurrencySymbolOnFocus: true,
hideGroupingSeparatorOnFocus: true,
hideGroupingSeparatorOnFocus: false,
hideNegligibleDecimalDigitsOnFocus: true,
autoDecimalDigits: false,
useGrouping: true,

View file

@ -346,7 +346,7 @@ watch(
keep-color
color="primary"
dense
:disable="commandType"
:disable="commandType ? false : true"
v-model="scope.selected"
/>
</template>
@ -357,7 +357,7 @@ watch(
keep-color
color="primary"
dense
:disable="commandType"
:disable="commandType ? false : true"
v-model="props.selected"
/>
</q-td>

View file

@ -774,7 +774,6 @@ function onSubmitDate() {
*/
function openModalOrder(val: boolean) {
// object #e.draft == "" &&
console.log(rowsOrder.value);
rowsOrder.value = rows.value.filter(
(e: any) =>

View file

@ -1015,6 +1015,7 @@ defineExpose({
v-model="formData.projectModalPlanned"
label="จำนวน (รุ่น)"
mask="#"
maxlength="5"
reverse-fill-mask
class="inputgreen"
@update:model-value="props.onCheckChangeData()"
@ -1198,6 +1199,7 @@ defineExpose({
v-model="formData.projectModalActual"
label="จำนวน (รุ่น)"
mask="#"
maxlength="5"
reverse-fill-mask
@update:model-value="props.onCheckChangeData()"
/>

View file

@ -222,6 +222,7 @@ const columnsPlannedGoals = ref<QTableProps["columns"]>([
align: "left",
label: "จำนวน(คน)",
sortable: true,
format: (val) => Number(val).toLocaleString("th-TH"),
field: "amount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
@ -264,9 +265,9 @@ const columnsActualGoals = ref<QTableProps["columns"]>([
label: "จำนวน(คน)",
sortable: true,
field: "amount",
format: (val) => Number(val).toLocaleString("th-TH"),
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (val) => Number(val),
},
]);
@ -290,7 +291,7 @@ const columnsRelated = ref<QTableProps["columns"]>([
field: "amount",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (val) => Number(val),
format: (val) => Number(val).toLocaleString("th-TH"),
},
]);
@ -1203,6 +1204,7 @@ onMounted(() => {
v-model="formGroupTarget.amount"
label="จำนวน(คน)"
mask="#"
maxlength="5"
reverse-fill-mask
:rules="[
(val:string) =>
@ -1352,6 +1354,7 @@ onMounted(() => {
v-model="formGroupTarget.amount"
label="จำนวน(คน)"
mask="#"
maxlength="5"
reverse-fill-mask
:rules="[
(val:string) =>
@ -1546,6 +1549,7 @@ onMounted(() => {
v-model="formGroupRelate.amount"
label="จำนวน(คน)"
mask="#"
maxlength="5"
reverse-fill-mask
:rules="[
(val:string) =>

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { ref, watch, computed, type PropType } from "vue";
import { useQuasar } from "quasar";
import http from "@/plugins/http";
@ -19,6 +19,7 @@ import type {
import type { DataListCommand } from "@/modules/18_command/interface/response/Main";
import DialogHeader from "@/components/DialogHeader.vue";
import DialogEditSalary from "@/modules/18_command/components/DialogEditSalary.vue";
const storeCommand = useCommandMainStore();
const router = useRouter();
@ -43,6 +44,10 @@ const props = defineProps({
},
});
const isSalary = computed(() => {
return !!commandOp.value.find((v) => v.id == commandType.value)?.isSalary;
});
const commandOp = ref<ListCommand[]>([]); //
const commandType = ref<string>(""); //
const commandNo = ref<string>(""); //
@ -130,6 +135,9 @@ const columns = ref<QTableProps["columns"]>([
const selectCreate = ref<string | null>("NEW"); // /
const modalSalary = ref<boolean>(false); //
const commandOrderId = ref<string>(""); //
/** ฟังก์ชันเรียกข้อมูลรายการคำสั่ง*/
async function getListCommandDraf() {
showLoader();
@ -191,12 +199,18 @@ function createCommand(isRedirect: boolean) {
.post(config.API.command + `/person`, body)
.then(async (res) => {
const id = await res.data.result;
commandOrderId.value = id;
modal.value = false;
if (isRedirect) {
router.push(`/command/edit/${id}`);
if (!isSalary.value) {
if (isRedirect) {
router.push(`/command/edit/${id}`);
} else {
clearValue();
props.fetchData(); // fetchData props
}
} else {
clearValue();
await props.fetchData(); // fetchData props
modal.value = false;
modalSalary.value = true;
}
})
.catch((e) => {
@ -237,12 +251,18 @@ function addPersonalToCommand(isRedirect: boolean) {
.post(config.API.command + `/person`, body)
.then(async (res) => {
const id = await res.data.result;
commandOrderId.value = id;
modal.value = false;
if (isRedirect) {
router.push(`/command/edit/${id}`);
if (!isSalary.value) {
if (isRedirect) {
router.push(`/command/edit/${id}`);
} else {
modal.value = false;
props.fetchData(); // fetchData props
}
} else {
modal.value = false;
await props.fetchData(); // fetchData props
modalSalary.value = true;
}
})
.catch((e) => {
@ -569,7 +589,7 @@ watch(
<q-card-actions align="right">
<q-btn
v-if="isHold"
v-if="isHold && !isSalary"
label="บันทึกและเลือกรายชื่อต่อ"
@click="() => onSubmit(false)"
:disable="
@ -580,7 +600,9 @@ watch(
<q-tooltip>นทกและเลอกรายชอต</q-tooltip>
</q-btn>
<q-btn
label="บันทึกและไปยังหน้าคำสั่ง"
:label="
!isSalary ? 'บันทึกและไปยังหน้าคำสั่ง' : 'บันทึกและไปยังหน้าแก้ไขเงินเดือน'
"
@click="() => onSubmit(true)"
:disable="
selectCreate == 'NEW' ? commandType == '' : selected.length == 0
@ -592,6 +614,19 @@ watch(
</q-card-actions>
</q-card>
</q-dialog>
<DialogEditSalary
v-model:modal="modalSalary"
:command-code="commandTypeCode || ''"
:command-id="
selectCreate === 'NEW'
? commandOrderId
: selected.length > 0
? selected[0].id
: ''
"
:fetch-data="fetchData"
/>
</template>
<style scoped></style>

View file

@ -0,0 +1,506 @@
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { useQuasar, type QTableProps } from "quasar";
import http from "@/plugins/http";
import config from "@/app.config";
import { useRouter } from "vue-router";
import { useCounterMixin } from "@/stores/mixin";
import DialogHeader from "@/components/DialogHeader.vue";
import CurruncyInput from "@/components/CurruncyInput.vue";
const $q = useQuasar();
const router = useRouter();
const {
dialogConfirm,
messageError,
showLoader,
hideLoader,
success,
onSearchDataTable,
} = useCounterMixin();
const modal = defineModel<boolean>("modal", {
type: Boolean,
required: true,
});
const props = defineProps({
commandCode: {
type: String,
required: true,
},
commandId: {
type: String,
required: true,
},
fetchData: {
type: Function,
default: () => {},
},
});
const baseColumns = ref<QTableProps["columns"]>([
{
name: "no",
align: "left",
label: "ลำดับ",
field: (row) => rows.value.indexOf(row) + 1,
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posNo",
align: "left",
label: "รักษาการ (เลขที่ตำแหน่ง)",
field: "posNo",
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "citizenId",
align: "left",
label: "เลขประจำตัวประชาชน",
field: "citizenId",
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "fullName",
align: "left",
label: "ชื่อ-นามสกุล",
field: "fullName",
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return `${row.prefix ?? ""}${row.firstName ?? ""} ${row.lastName ?? ""}`;
},
},
{
name: "position",
align: "left",
label: "ตำแหน่ง",
field: "position",
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "positionType",
align: "left",
label: "ประเภทตำแหน่ง",
field: "positionType",
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return row.posType
? `${row.posType} ${row.posLevel ? `(${row.posLevel})` : ""}`
: "-";
},
},
{
name: "amount",
align: "left",
label: "เงินเดือน",
field: "amount",
format(val) {
return val ? val.toLocaleString() : "-";
},
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "amountSpecial",
align: "left",
label: "เงินค่าตอบแทนพิเศษ",
field: "amountSpecial",
format(val) {
return val ? val.toLocaleString() : "-";
},
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "positionSalaryAmount",
align: "left",
label: "เงินประจำตำแหน่ง",
field: "positionSalaryAmount",
format(val) {
return val ? val.toLocaleString() : "-";
},
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "mouthSalaryAmount",
align: "left",
label: "เงินค่าตอบแทนรายเดือน",
field: "mouthSalaryAmount",
format(val) {
return val ? val.toLocaleString() : "-";
},
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "remarkHorizontal",
align: "left",
label: "หมายเหตุแนวนอน",
field: "remarkHorizontal",
format(val) {
return val ? val.toLocaleString() : "-";
},
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "remarkVertical",
align: "left",
label: "หมายเหตุแนวตั้ง",
field: "remarkVertical",
format(val) {
return val ? val.toLocaleString() : "-";
},
sortable: false,
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
]);
const columns = computed<QTableProps["columns"]>(() => {
if (
props.commandCode === "C-PM-01" ||
props.commandCode === "C-PM-02" ||
props.commandCode === "C-PM-03" ||
props.commandCode === "C-PM-04"
) {
return baseColumns.value;
} else if (props.commandCode == "C-PM-40") {
return baseColumns.value?.filter(
(e) => e.name !== "position" && e.name !== "positionType"
);
} else {
return baseColumns.value?.filter(
(e) =>
e.name !== "position" && e.name !== "positionType" && e.name !== "posNo"
);
}
});
const visibleColumns = ref<String[]>([
"no",
"posNo",
"citizenId",
"fullName",
"amount",
"position",
"positionType",
"amountSpecial",
"positionSalaryAmount",
"mouthSalaryAmount",
"remarkHorizontal",
"remarkVertical",
]);
const filter = ref<string>("");
const rowsMain = ref<Array<any>>([]);
const rows = ref<Array<any>>([]);
const isNext = ref<boolean>(false);
// Functions for number formatting
function formatNumber(value: any): string {
if (!value || isNaN(value)) return "";
return Number(value).toLocaleString();
}
function unformatNumber(value: string): number | null {
if (!value) return null;
const numStr = value.replace(/,/g, "");
const num = parseFloat(numStr);
return isNaN(num) ? null : num;
}
function onSearchData() {
rows.value = onSearchDataTable(
filter.value,
rowsMain.value,
columns.value ? columns.value : []
);
}
function closeModal() {
modal.value = false;
rows.value = [];
rowsMain.value = [];
filter.value = "";
}
/** ดึงข้อมูล บุคคล */
async function getPersonList() {
await http
.get(config.API.commandAction(props.commandId, "tab2"))
.then(async (res) => {
const data = await res.data.result;
rows.value = data.commandRecives;
rowsMain.value = data.commandRecives;
onSearchData();
})
.catch((e) => {
messageError($q, e);
});
}
function onSubmit() {
dialogConfirm($q, async () => {
showLoader();
try {
const payload = rows.value.map((item) => ({
id: item.id,
mouthSalaryAmount: Number(item.mouthSalaryAmount) || null,
positionSalaryAmount: Number(item.positionSalaryAmount) || null,
amount: Number(item.amount) || null,
amountSpecial: Number(item.amountSpecial) || null,
remarkVertical: item.remarkVertical,
remarkHorizontal: item.remarkHorizontal,
}));
await http.post(config.API.commandEditSalary, payload);
if (isNext.value) {
router.push(`/command/edit/${props.commandId}`);
} else {
await props.fetchData(); // fetchData props
closeModal();
}
success($q, "บันทึกข้อมูลาสำเร็จ");
} catch (error) {
messageError($q, error);
} finally {
hideLoader();
}
});
}
watch(
() => modal.value,
(newVal) => {
if (newVal) {
getPersonList();
}
}
);
</script>
<template>
<q-dialog v-model="modal" persistent full-width>
<q-card>
<DialogHeader :tittle="'แก้ไขเงินเดือน'" :close="closeModal" />
<q-form greedy @submit.prevent @validation-success="onSubmit">
<q-separator />
<q-card-section>
<div class="row q-col-gutter-sm">
<div class="col-12 row q-pb-sm items-center">
<q-space />
<div class="items-center" style="display: flex">
<!-- นหาขอความใน table -->
<q-input
standout
dense
v-model="filter"
ref="filterRef"
outlined
placeholder="ค้นหา"
@keydown.enter.prevent="onSearchData()"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<!-- แสดงคอลมนใน table -->
<q-select
v-model="visibleColumns"
:display-value="$q.lang.table.columns"
multiple
outlined
dense
:options="columns"
options-dense
option-value="name"
map-options
emit-value
style="min-width: 140px"
class="gt-xs q-ml-sm"
/>
</div>
</div>
<div class="col-12">
<d-table
:rows="rows"
:columns="columns"
:visible-columns="visibleColumns"
row-key="name"
>
<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 v-if="col.name == 'amount'">
<CurruncyInput
v-model="props.row.amount"
:edit="true"
dense
outlined
hide-bottom-space
lazy-rules
:rules="[
(val:any) => !!val || 'กรุณากรอกเงินเดือน',
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินเดือนต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
</div>
<div v-else-if="col.name == 'amountSpecial'">
<CurruncyInput
v-model="props.row.amountSpecial"
:edit="true"
dense
outlined
hide-bottom-space
lazy-rules
:rules="[
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินค่าตอบแทนพิเศษต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
<!-- :rules="[(val:string) => !!val || 'กรุณากรอกเงินค่าตอบแทนพิเศษ']" -->
</div>
<div v-else-if="col.name == 'positionSalaryAmount'">
<CurruncyInput
v-model="props.row.positionSalaryAmount"
:edit="true"
dense
outlined
hide-bottom-space
lazy-rules
:rules="[
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินประจำตำแหน่งต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
<!-- :rules="[(val:string) => !!val || 'กรุณากรอกเงินประจำตำแหน่ง']" -->
</div>
<div v-else-if="col.name == 'mouthSalaryAmount'">
<CurruncyInput
v-model="props.row.mouthSalaryAmount"
:edit="true"
dense
outlined
hide-bottom-space
lazy-rules
:rules="[
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินค่าตอบแทนรายเดือนต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
<!-- :rules="[(val:string) => !!val || 'กรุณากรอกเงินค่าตอบแทนรายเดือน']" -->
</div>
<div v-else-if="col.name == 'remarkHorizontal'">
<q-input
v-model="props.row.remarkHorizontal"
type="textarea"
autorows
hide-bottom-space
dense
borderless
outlined
rows="1"
/>
</div>
<div v-else-if="col.name == 'remarkVertical'">
<q-input
v-model="props.row.remarkVertical"
type="textarea"
autorows
hide-bottom-space
dense
borderless
outlined
rows="1"
/>
</div>
<div v-else>
{{ col.value ?? "-" }}
</div>
</q-td>
</q-tr>
</template>
</d-table>
</div>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn
label="บันทึกและเลือกรายชื่อต่อ"
color="blue"
@click="() => (isNext = false)"
type="submit"
>
<q-tooltip>นทกและเลอกรายชอต</q-tooltip>
</q-btn>
<q-btn
label="บันทึกและไปยังหน้าคำสั่ง"
@click="() => (isNext = true)"
color="public"
type="submit"
>
<q-tooltip>นทกและไปยงหนาคำส</q-tooltip>
</q-btn>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<style scoped></style>

View file

@ -8,6 +8,7 @@ import { useCounterMixin } from "@/stores/mixin";
import { useCommandMainStore } from "@/modules/18_command/store/Main";
import { useCommandDetail } from "@/modules/18_command/store/DetailStore";
import DialogHeader from "@/components/DialogHeader.vue";
import CurruncyInput from "@/components/CurruncyInput.vue";
const $q = useQuasar();
const { showLoader, hideLoader, dialogConfirm, messageError, success } =
@ -140,25 +141,30 @@ watch(
v-if="store.isShowSalary(type)"
class="col-xs-6 col-sm-6 col-md-6"
>
<q-input
<CurruncyInput
dense
outlined
lazy-rules
hide-bottom-space
v-model="formData.amount"
:class="getClass(storeDetail.isSalary)"
mask="###,###,###,###,###,###"
reverse-fill-mask
:readonly="!storeDetail.isSalary"
:label="`${'เงินเดือน'}`"
:rules="[(val: any) => !!val || `${'กรุณากรอกเงินเดือน'}`]"
:rules="[
(val:any) => !!val || 'กรุณากรอกเงินเดือน',
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินเดือนต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
</div>
<div
v-if="store.isShowSalary(type)"
class="col-xs-6 col-sm-6 col-md-6"
>
<q-input
<CurruncyInput
dense
outlined
lazy-rules
@ -166,16 +172,21 @@ watch(
v-model="formData.amountSpecial"
:class="getClass(storeDetail.isSalary)"
:readonly="!storeDetail.isSalary"
mask="###,###,###,###,###,###"
reverse-fill-mask
:label="`${'เงินค่าตอบแทนพิเศษ'}`"
:rules="[
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินค่าตอบแทนพิเศษต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
</div>
<div
v-if="store.isShowSalary(type)"
class="col-xs-6 col-sm-6 col-md-6"
>
<q-input
<CurruncyInput
dense
outlined
lazy-rules
@ -183,9 +194,14 @@ watch(
v-model="formData.positionSalaryAmount"
:class="getClass(storeDetail.isSalary)"
:readonly="!storeDetail.isSalary"
mask="###,###,###,###,###,###"
reverse-fill-mask
:label="`${'เงินประจำตำแหน่ง'}`"
:rules="[
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินประจำตำแหน่งต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
</div>
@ -193,7 +209,7 @@ watch(
v-if="store.isShowSalary(type)"
class="col-xs-6 col-sm-6 col-md-6"
>
<q-input
<CurruncyInput
dense
outlined
lazy-rules
@ -201,9 +217,14 @@ watch(
v-model="formData.monthSalaryAmount"
:class="getClass(storeDetail.isSalary)"
:readonly="!storeDetail.isSalary"
mask="###,###,###,###,###,###"
reverse-fill-mask
:label="`${'เงินค่าตอบแทนรายเดือน'}`"
:rules="[
(val:any) => {
if (!val) return true;
const numVal = typeof val === 'number' ? val : Number(String(val).replace(/,/g, ''));
return numVal <= 10000000 || 'เงินค่าตอบแทนรายเดือนต้องไม่เกิน 10,000,000 บาท';
}
]"
/>
</div>
</div>

View file

@ -60,6 +60,7 @@ interface ListCommand {
name: string;
subtitle?: string;
commandSysId: string;
isSalary?: boolean;
}
interface DataOrder {