diff --git a/src/controllers/ProfileAbsentLateController.ts b/src/controllers/ProfileAbsentLateController.ts index b9e64a8f..ef977097 100644 --- a/src/controllers/ProfileAbsentLateController.ts +++ b/src/controllers/ProfileAbsentLateController.ts @@ -18,6 +18,7 @@ import { CreateProfileAbsentLateBatch, UpdateProfileAbsentLate, } from "../entities/ProfileAbsentLate"; +import { ProfileAbsentLateHistory } from "../entities/ProfileAbsentLateHistory"; import HttpSuccess from "../interfaces/http-success"; import HttpStatus from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; @@ -32,6 +33,7 @@ import { setLogDataDiff } from "../interfaces/utils"; export class ProfileAbsentLateController extends Controller { private profileRepo = AppDataSource.getRepository(Profile); private absentLateRepo = AppDataSource.getRepository(ProfileAbsentLate); + private historyRepo = AppDataSource.getRepository(ProfileAbsentLateHistory); /** * API ดึงข้อมูลการมาสาย/ขาดราชการของ user @@ -99,8 +101,15 @@ export class ProfileAbsentLateController extends Controller { }; Object.assign(data, { ...body, ...meta }); + + // บันทึก history + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + await this.absentLateRepo.save(data, { data: req }); setLogDataDiff(req, { before, after: data }); + history.profileAbsentLateId = data.id; + await this.historyRepo.save(history, { data: req }); return new HttpSuccess(data.id); } @@ -114,8 +123,9 @@ export class ProfileAbsentLateController extends Controller { @Request() req: RequestWithUser, @Body() body: CreateProfileAbsentLateBatch, ) { + // กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย) if (!body.records || body.records.length === 0) { - throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณาระบุข้อมูลอย่างน้อย 1 รายการ"); + return new HttpSuccess({ count: 0, ids: [] }); } const profileIds = [...new Set(body.records.map((r) => r.profileId))]; @@ -126,8 +136,9 @@ export class ProfileAbsentLateController extends Controller { const foundProfileIds = new Set(profiles.map((p) => p.id)); const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileId)); + // กรณีไม่พบ profile เลย if (validRecords.length === 0) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ที่ระบุ"); + return new HttpSuccess({ count: 0, ids: [] }); } const meta = { @@ -147,6 +158,15 @@ export class ProfileAbsentLateController extends Controller { const result = await this.absentLateRepo.save(records, { data: req }); + // บันทึก history สำหรับแต่ละ record + const historyRecords = result.map((data) => { + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + history.profileAbsentLateId = data.id; + return history; + }); + await this.historyRepo.save(historyRecords, { data: req }); + return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) }); } @@ -170,15 +190,27 @@ export class ProfileAbsentLateController extends Controller { ); const before = structuredClone(record); + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); Object.assign(record, body); + Object.assign(history, { ...record, id: undefined }); + + history.profileAbsentLateId = absentLateId; record.lastUpdateUserId = req.user.sub; record.lastUpdateFullName = req.user.name; record.lastUpdatedAt = new Date(); + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = new Date(); + history.lastUpdatedAt = new Date(); await Promise.all([ this.absentLateRepo.save(record, { data: req }), setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), ]); return new HttpSuccess(); @@ -202,16 +234,44 @@ export class ProfileAbsentLateController extends Controller { await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_OFFICER", record.profileId); const before = structuredClone(record); + const history = new ProfileAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); + record.isDeleted = true; record.lastUpdateUserId = req.user.sub; record.lastUpdateFullName = req.user.name; record.lastUpdatedAt = new Date(); + history.profileAbsentLateId = absentLateId; + history.isDeleted = true; + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.lastUpdatedAt = new Date(); + await Promise.all([ this.absentLateRepo.save(record, { data: req }), setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), ]); return new HttpSuccess(); } + + /** + * API ดึงประวัติการมาสาย/ขาดราชการ + * @summary API ดึงประวัติการมาสาย/ขาดราชการ + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Get("history/{absentLateId}") + public async getHistory(@Path() absentLateId: string, @Request() req: RequestWithUser) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", record.profileId); + + const history = await this.historyRepo.find({ + where: { profileAbsentLateId: absentLateId }, + order: { createdAt: "DESC" }, + }); + return new HttpSuccess(history); + } } diff --git a/src/controllers/ProfileEmployeeAbsentLateController.ts b/src/controllers/ProfileEmployeeAbsentLateController.ts index a5e32fa1..cdd67050 100644 --- a/src/controllers/ProfileEmployeeAbsentLateController.ts +++ b/src/controllers/ProfileEmployeeAbsentLateController.ts @@ -18,6 +18,7 @@ import { CreateProfileEmployeeAbsentLateBatch, UpdateProfileEmployeeAbsentLate, } from "../entities/ProfileEmployeeAbsentLate"; +import { ProfileEmployeeAbsentLateHistory } from "../entities/ProfileEmployeeAbsentLateHistory"; import HttpSuccess from "../interfaces/http-success"; import HttpStatus from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; @@ -32,6 +33,7 @@ import { setLogDataDiff } from "../interfaces/utils"; export class ProfileEmployeeAbsentLateController extends Controller { private profileRepo = AppDataSource.getRepository(ProfileEmployee); private absentLateRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLate); + private historyRepo = AppDataSource.getRepository(ProfileEmployeeAbsentLateHistory); /** * API ดึงข้อมูลการมาสาย/ขาดราชการของ user @@ -99,8 +101,15 @@ export class ProfileEmployeeAbsentLateController extends Controller { }; Object.assign(data, { ...body, ...meta }); + + // บันทึก history + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + await this.absentLateRepo.save(data, { data: req }); setLogDataDiff(req, { before, after: data }); + history.profileEmployeeAbsentLateId = data.id; + await this.historyRepo.save(history, { data: req }); return new HttpSuccess(data.id); } @@ -114,8 +123,9 @@ export class ProfileEmployeeAbsentLateController extends Controller { @Request() req: RequestWithUser, @Body() body: CreateProfileEmployeeAbsentLateBatch, ) { + // กรณีไม่มีข้อมูลส่งมา (วันที่ไม่มีคนขาด/มาสาย) if (!body.records || body.records.length === 0) { - throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณาระบุข้อมูลอย่างน้อย 1 รายการ"); + return new HttpSuccess({ count: 0, ids: [] }); } const profileIds = [...new Set(body.records.map((r) => r.profileEmployeeId))]; @@ -126,8 +136,9 @@ export class ProfileEmployeeAbsentLateController extends Controller { const foundProfileIds = new Set(profiles.map((p) => p.id)); const validRecords = body.records.filter((r) => foundProfileIds.has(r.profileEmployeeId)); + // กรณีไม่พบ profile เลย if (validRecords.length === 0) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ที่ระบุ"); + return new HttpSuccess({ count: 0, ids: [] }); } const meta = { @@ -147,6 +158,15 @@ export class ProfileEmployeeAbsentLateController extends Controller { const result = await this.absentLateRepo.save(records, { data: req }); + // บันทึก history สำหรับแต่ละ record + const historyRecords = result.map((data) => { + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...data, id: undefined }); + history.profileEmployeeAbsentLateId = data.id; + return history; + }); + await this.historyRepo.save(historyRecords, { data: req }); + return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) }); } @@ -170,15 +190,27 @@ export class ProfileEmployeeAbsentLateController extends Controller { ); const before = structuredClone(record); + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); Object.assign(record, body); + Object.assign(history, { ...record, id: undefined }); + + history.profileEmployeeAbsentLateId = absentLateId; record.lastUpdateUserId = req.user.sub; record.lastUpdateFullName = req.user.name; record.lastUpdatedAt = new Date(); + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.createdUserId = req.user.sub; + history.createdFullName = req.user.name; + history.createdAt = new Date(); + history.lastUpdatedAt = new Date(); await Promise.all([ this.absentLateRepo.save(record, { data: req }), setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), ]); return new HttpSuccess(); @@ -202,16 +234,44 @@ export class ProfileEmployeeAbsentLateController extends Controller { await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); const before = structuredClone(record); + const history = new ProfileEmployeeAbsentLateHistory(); + Object.assign(history, { ...record, id: undefined }); + record.isDeleted = true; record.lastUpdateUserId = req.user.sub; record.lastUpdateFullName = req.user.name; record.lastUpdatedAt = new Date(); + history.profileEmployeeAbsentLateId = absentLateId; + history.isDeleted = true; + history.lastUpdateUserId = req.user.sub; + history.lastUpdateFullName = req.user.name; + history.lastUpdatedAt = new Date(); + await Promise.all([ this.absentLateRepo.save(record, { data: req }), setLogDataDiff(req, { before, after: record }), + this.historyRepo.save(history, { data: req }), ]); return new HttpSuccess(); } + + /** + * API ดึงประวัติการมาสาย/ขาดราชการ + * @summary API ดึงประวัติการมาสาย/ขาดราชการ + * @param absentLateId คีย์การมาสาย/ขาดราชการ + */ + @Get("history/{absentLateId}") + public async getHistory(@Path() absentLateId: string, @Request() req: RequestWithUser) { + const record = await this.absentLateRepo.findOneBy({ id: absentLateId }); + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_EMP", record.profileEmployeeId); + + const history = await this.historyRepo.find({ + where: { profileEmployeeAbsentLateId: absentLateId }, + order: { createdAt: "DESC" }, + }); + return new HttpSuccess(history); + } } diff --git a/src/entities/ProfileAbsentLateHistory.ts b/src/entities/ProfileAbsentLateHistory.ts new file mode 100644 index 00000000..7059bb23 --- /dev/null +++ b/src/entities/ProfileAbsentLateHistory.ts @@ -0,0 +1,20 @@ +import { Entity, Column } from "typeorm"; +import { + ProfileAbsentLate, + AbsentLateStatus, + StampType, +} from "./ProfileAbsentLate"; + +@Entity("profileAbsentLateHistory") +export class ProfileAbsentLateHistory extends ProfileAbsentLate { + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง ProfileAbsentLate", + default: null, + }) + profileAbsentLateId: string; +} + +// Export enums for re-use +export { AbsentLateStatus, StampType }; diff --git a/src/entities/ProfileEmployeeAbsentLateHistory.ts b/src/entities/ProfileEmployeeAbsentLateHistory.ts new file mode 100644 index 00000000..588801e0 --- /dev/null +++ b/src/entities/ProfileEmployeeAbsentLateHistory.ts @@ -0,0 +1,17 @@ +import { Entity, Column } from "typeorm"; +import { ProfileEmployeeAbsentLate } from "./ProfileEmployeeAbsentLate"; +import { AbsentLateStatus, StampType } from "./ProfileAbsentLate"; + +@Entity("profileEmployeeAbsentLateHistory") +export class ProfileEmployeeAbsentLateHistory extends ProfileEmployeeAbsentLate { + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง ProfileEmployeeAbsentLate", + default: null, + }) + profileEmployeeAbsentLateId: string; +} + +// Export enums for re-use +export { AbsentLateStatus, StampType }; diff --git a/src/migration/1774408245407-add_table_absentLateHistory.ts b/src/migration/1774408245407-add_table_absentLateHistory.ts new file mode 100644 index 00000000..ff70745b --- /dev/null +++ b/src/migration/1774408245407-add_table_absentLateHistory.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddTableAbsentLateHistory1774408245407 implements MigrationInterface { + name = 'AddTableAbsentLateHistory1774408245407' + + public async up(queryRunner: QueryRunner): Promise { + + await queryRunner.query(`CREATE TABLE \`profileEmployeeAbsentLateHistory\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileEmployeeId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง ProfileEmployee', \`status\` enum ('LATE', 'ABSENT') NOT NULL COMMENT 'สถานะ มาสาย/ขาดราชการ', \`stampDate\` datetime NOT NULL COMMENT 'วันที่และเวลาที่ลงเวลา', \`stampType\` enum ('FULL_DAY', 'MORNING', 'AFTERNOON') NOT NULL COMMENT 'เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย' DEFAULT 'FULL_DAY', \`stampAmount\` decimal(2,1) NOT NULL COMMENT 'จำนวน (1.0/0.5)' DEFAULT '1.0', \`remark\` varchar(250) NULL COMMENT 'หมายเหตุ', \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0, \`profileEmployeeAbsentLateId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง ProfileEmployeeAbsentLate', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`profileAbsentLateHistory\` (\`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`lastUpdatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`lastUpdateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`lastUpdateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`profileId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง Profile', \`status\` enum ('LATE', 'ABSENT') NOT NULL COMMENT 'สถานะ มาสาย/ขาดราชการ', \`stampDate\` datetime NOT NULL COMMENT 'วันที่และเวลาที่ลงเวลา', \`stampType\` enum ('FULL_DAY', 'MORNING', 'AFTERNOON') NOT NULL COMMENT 'เต็มวัน/ครึ่งเช้า/ครึ่งบ่าย' DEFAULT 'FULL_DAY', \`stampAmount\` decimal(2,1) NOT NULL COMMENT 'จำนวน (1.0/0.5)' DEFAULT '1.0', \`remark\` varchar(250) NULL COMMENT 'หมายเหตุ', \`isDeleted\` tinyint NOT NULL COMMENT 'สถานะลบข้อมูล' DEFAULT 0, \`profileAbsentLateId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง ProfileAbsentLate', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`ALTER TABLE \`profileEmployeeAbsentLateHistory\` ADD CONSTRAINT \`FK_8b06ca79d6f75c7d6577c86f3d4\` FOREIGN KEY (\`profileEmployeeId\`) REFERENCES \`profileEmployee\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`profileAbsentLateHistory\` ADD CONSTRAINT \`FK_0fa6a843d0e6d901a4f2f56c541\` FOREIGN KEY (\`profileId\`) REFERENCES \`profile\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE \`profileAbsentLateHistory\``); + await queryRunner.query(`DROP TABLE \`profileEmployeeAbsentLateHistory\``); + } + +}