[CLAUDE] Hrm: P11-C Vehicle+Driver catalogs (Mig 44) + gotcha #57 filtered-unique 3 HRM catalog (Mig 45)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m18s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m18s
P11-C: extend HrmConfigs +2 kind (Vehicle/Driver) declarative. Mig 44 AddVehicleAndDriverCatalogs (2 table filtered-unique Code, tables 91->93). Domain entity + EF config (filtered day-1) + 2 DbSet + HrmConfigFeatures Region5/6 CRUD + Controller +2 route-group (GET public / write Roles=Admin) + MenuKeys +2 +All (auto Admin perm) + DbInitializer 2 menu leaf + idempotent seed 2 veh/2 drv. FE declarative KIND_CONFIG +2 kind x2 app (SHA256 mirror) + 4-place (types/page/menuKeys/Layout staticMap), :kind-driven no new route. gotcha #57 (bundled; OtPolicy missed in backlog, caught via grep) - Mig 45 FilterHrmCatalogUniqueIndexesByIsDeleted: LeaveType+ShiftPattern+OtPolicy bare .IsUnique() -> .HasFilter([IsDeleted]=0) (recreate-on-soft-deleted-slot 500 fix, mirror Holiday Mig 43). Tests +5 HrmConfigFilteredUniqueTests (181->186 PASS) test-before RED->GREEN. Reviewer caught FE<->BE Driver required-field mismatch -> fixed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -83,6 +83,9 @@ function resolvePath(key: string): string | null {
|
||||
Hrm_Config_Holidays: '/hrm/configs/holidays',
|
||||
Hrm_Config_Shifts: '/hrm/configs/shifts',
|
||||
Hrm_Config_OtPolicies: '/hrm/configs/ot-policies',
|
||||
// [P11-C S51] danh mục xe công + tài xế — cùng page :kind-driven.
|
||||
Hrm_Config_Vehicles: '/hrm/configs/vehicles',
|
||||
Hrm_Config_Drivers: '/hrm/configs/drivers',
|
||||
// [Phase 10.2 G-O1 S34 2026-05-27] Module Văn phòng số — Danh bạ nội bộ.
|
||||
// 4-place mirror Pattern 16-bis: types/ + pages/ + App.tsx + menuKeys + staticMap.
|
||||
Off_DanhBa: '/directory',
|
||||
|
||||
@ -5,7 +5,7 @@ export const MenuKeys = {
|
||||
Suppliers: 'Suppliers',
|
||||
Projects: 'Projects',
|
||||
Departments: 'Departments',
|
||||
// 4 master catalogs cho Details add form autocomplete (Plan CA S29 — UI ở fe-user)
|
||||
// 4 master catalogs cho Details add form autocomplete (Plan CA S29 — UI sang fe-user)
|
||||
Catalogs: 'Catalogs',
|
||||
CatalogUnits: 'CatalogUnits',
|
||||
CatalogMaterials: 'CatalogMaterials',
|
||||
@ -40,6 +40,9 @@ export const MenuKeys = {
|
||||
HrmConfigHolidays: 'Hrm_Config_Holidays',
|
||||
HrmConfigShifts: 'Hrm_Config_Shifts',
|
||||
HrmConfigOtPolicies: 'Hrm_Config_OtPolicies',
|
||||
// P11-C (S51) — danh mục xe công + tài xế (dùng khi đặt xe)
|
||||
HrmConfigVehicles: 'Hrm_Config_Vehicles',
|
||||
HrmConfigDrivers: 'Hrm_Config_Drivers',
|
||||
// Module Văn phòng số — Danh bạ nội bộ (Phase 10.2 G-O1 Session 34, 2026-05-27)
|
||||
Off: 'Off',
|
||||
OffDanhBa: 'Off_DanhBa',
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import { useState, type ComponentType, type FormEvent } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { Settings2, Calendar, Clock, Pencil, Plus, Plane, Repeat, Trash2, Search } from 'lucide-react'
|
||||
import { Settings2, Calendar, Clock, Pencil, Plus, Plane, Repeat, Trash2, Search, Car, IdCard } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
@ -109,9 +109,36 @@ const KIND_CONFIG: Record<Kind, {
|
||||
],
|
||||
columns: ['Mã', 'Tên', 'Hệ số WD/WE/HL', 'Max h/year', 'Trạng thái'],
|
||||
},
|
||||
'vehicles': {
|
||||
label: 'Xe công',
|
||||
description: 'Danh mục xe công ty — dùng khi đăng ký đặt xe.',
|
||||
icon: Car,
|
||||
fields: [
|
||||
{ key: 'code', label: 'Mã *', type: 'text', required: true, placeholder: 'XE-01...' },
|
||||
{ key: 'name', label: 'Tên xe *', type: 'text', required: true, placeholder: 'Toyota Innova 7 chỗ' },
|
||||
{ key: 'licensePlate', label: 'Biển số *', type: 'text', required: true, placeholder: '30A-12345' },
|
||||
{ key: 'seatCount', label: 'Số chỗ', type: 'number', placeholder: '7' },
|
||||
{ key: 'description', label: 'Mô tả', type: 'textarea' },
|
||||
],
|
||||
columns: ['Mã', 'Tên xe', 'Biển số', 'Số chỗ', 'Trạng thái'],
|
||||
},
|
||||
'drivers': {
|
||||
label: 'Tài xế',
|
||||
description: 'Danh mục tài xế — dùng khi đăng ký đặt xe.',
|
||||
icon: IdCard,
|
||||
fields: [
|
||||
{ key: 'code', label: 'Mã *', type: 'text', required: true, placeholder: 'TX-01...' },
|
||||
{ key: 'name', label: 'Họ tên *', type: 'text', required: true, placeholder: 'Nguyễn Văn Tài' },
|
||||
{ key: 'phoneNumber', label: 'SĐT *', type: 'text', required: true, placeholder: '0901xxxxxx' },
|
||||
{ key: 'licenseNumber', label: 'Số GPLX *', type: 'text', required: true, placeholder: '012345678' },
|
||||
{ key: 'licenseClass', label: 'Hạng *', type: 'text', required: true, placeholder: 'B2, C, D, E' },
|
||||
{ key: 'description', label: 'Mô tả', type: 'textarea' },
|
||||
],
|
||||
columns: ['Mã', 'Họ tên', 'SĐT', 'Số GPLX', 'Hạng', 'Trạng thái'],
|
||||
},
|
||||
}
|
||||
|
||||
const KINDS: Kind[] = ['leave-types', 'holidays', 'shifts', 'ot-policies']
|
||||
const KINDS: Kind[] = ['leave-types', 'holidays', 'shifts', 'ot-policies', 'vehicles', 'drivers']
|
||||
|
||||
type ConfigRow = Record<string, unknown> & { id: string; isActive?: boolean }
|
||||
|
||||
@ -427,6 +454,29 @@ function renderCells(kind: Kind, row: ConfigRow) {
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (kind === 'vehicles') {
|
||||
return (
|
||||
<>
|
||||
<td className="px-3 py-2 font-mono text-xs">{row.code as string}</td>
|
||||
<td className="px-3 py-2">{row.name as string}</td>
|
||||
<td className="px-3 py-2 font-mono text-xs text-slate-600">{row.licensePlate as string}</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{(row.seatCount as number) ?? '—'}</td>
|
||||
<td className="px-3 py-2">{statusBadge}</td>
|
||||
</>
|
||||
)
|
||||
}
|
||||
if (kind === 'drivers') {
|
||||
return (
|
||||
<>
|
||||
<td className="px-3 py-2 font-mono text-xs">{row.code as string}</td>
|
||||
<td className="px-3 py-2">{row.name as string}</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{(row.phoneNumber as string) || '—'}</td>
|
||||
<td className="px-3 py-2 font-mono text-xs text-slate-600">{(row.licenseNumber as string) || '—'}</td>
|
||||
<td className="px-3 py-2 text-xs text-slate-600">{(row.licenseClass as string) || '—'}</td>
|
||||
<td className="px-3 py-2">{statusBadge}</td>
|
||||
</>
|
||||
)
|
||||
}
|
||||
// ot-policies
|
||||
const mw = Number(row.multiplierWeekday ?? 0)
|
||||
const me = Number(row.multiplierWeekend ?? 0)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Types cho HRM Config module — mirror BE Domain.Hrm.* entities.
|
||||
// Phase 10.2 G-H2 (Mig 35 — S34 schema + S35 BE CRUD wire + FE this file).
|
||||
|
||||
export type HrmConfigKind = 'leave-types' | 'holidays' | 'shifts' | 'ot-policies'
|
||||
export type HrmConfigKind = 'leave-types' | 'holidays' | 'shifts' | 'ot-policies' | 'vehicles' | 'drivers'
|
||||
|
||||
// ========== DTOs (mirror BE DTOs HrmConfigFeatures.cs records) ==========
|
||||
|
||||
@ -53,6 +53,28 @@ export type OtPolicyDto = {
|
||||
description: string | null
|
||||
}
|
||||
|
||||
// Phase 11 P11-C (S51) — danh mục xe công + tài xế, dùng khi đăng ký đặt xe.
|
||||
export type VehicleDto = {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
licensePlate: string
|
||||
seatCount: number | null
|
||||
isActive: boolean
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export type DriverDto = {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
phoneNumber: string | null
|
||||
licenseNumber: string | null
|
||||
licenseClass: string | null
|
||||
isActive: boolean
|
||||
description: string | null
|
||||
}
|
||||
|
||||
// ========== Create/Update Input Commands (mirror BE record Command shape) ==========
|
||||
|
||||
export type CreateLeaveTypeInput = {
|
||||
@ -102,3 +124,24 @@ export type CreateOtPolicyInput = {
|
||||
}
|
||||
|
||||
export type UpdateOtPolicyInput = CreateOtPolicyInput & { id: string; isActive: boolean }
|
||||
|
||||
export type CreateVehicleInput = {
|
||||
code: string
|
||||
name: string
|
||||
licensePlate: string
|
||||
seatCount: number | null
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export type UpdateVehicleInput = CreateVehicleInput & { id: string; isActive: boolean }
|
||||
|
||||
export type CreateDriverInput = {
|
||||
code: string
|
||||
name: string
|
||||
phoneNumber: string | null
|
||||
licenseNumber: string | null
|
||||
licenseClass: string | null
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export type UpdateDriverInput = CreateDriverInput & { id: string; isActive: boolean }
|
||||
|
||||
Reference in New Issue
Block a user