backup: 完成 HR_position_ 表格前綴重命名與欄位對照表整理

變更內容:
- 所有資料表加上 HR_position_ 前綴
- 整理完整欄位顯示名稱與 ID 對照表
- 模組化 JS 檔案 (admin.js, ai.js, csv.js 等)
- 專案結構優化 (docs/, scripts/, tests/ 等)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-09 12:05:20 +08:00
parent a068ef9704
commit a6af297623
82 changed files with 8685 additions and 4933 deletions

212
js/ui.js
View File

@@ -49,22 +49,51 @@ export function getPositionFormData() {
const formData = new FormData(form);
const data = { basicInfo: {}, recruitInfo: {} };
const basicFields = ['positionCode', 'positionName', 'positionCategory', 'positionCategoryName',
'positionNature', 'positionNatureName', 'headcount', 'positionLevel',
'effectiveDate', 'positionDesc', 'positionRemark'];
const recruitFields = ['minEducation', 'requiredGender', 'salaryRange', 'workExperience',
'minAge', 'maxAge', 'jobType', 'recruitPosition', 'jobTitle', 'jobDesc',
'positionReq', 'titleReq', 'majorReq', 'skillReq', 'langReq', 'otherReq',
'superiorPosition', 'recruitRemark'];
// 使用新的 pos_ prefix 欄位
const basicFieldMapping = {
'pos_code': 'positionCode',
'pos_name': 'positionName',
'pos_category': 'positionCategory',
'pos_categoryName': 'positionCategoryName',
'pos_type': 'positionType',
'pos_typeName': 'positionTypeName',
'pos_headcount': 'headcount',
'pos_level': 'positionLevel',
'pos_effectiveDate': 'effectiveDate',
'pos_desc': 'positionDesc',
'pos_remark': 'positionRemark'
};
basicFields.forEach(field => {
const value = formData.get(field);
if (value) data.basicInfo[field] = value;
// 使用新的 rec_ prefix 欄位
const recruitFieldMapping = {
'rec_eduLevel': 'minEducation',
'rec_gender': 'requiredGender',
'rec_salaryRange': 'salaryRange',
'rec_expYears': 'workExperience',
'rec_minAge': 'minAge',
'rec_maxAge': 'maxAge',
'rec_jobType': 'jobType',
'rec_position': 'recruitPosition',
'rec_jobTitle': 'jobTitle',
'rec_jobDesc': 'jobDesc',
'rec_positionReq': 'positionReq',
'rec_certReq': 'certReq',
'rec_majorReq': 'majorReq',
'rec_skillReq': 'skillReq',
'rec_langReq': 'langReq',
'rec_otherReq': 'otherReq',
'rec_superiorCode': 'superiorPosition',
'rec_remark': 'recruitRemark'
};
Object.entries(basicFieldMapping).forEach(([htmlId, dataKey]) => {
const el = document.getElementById(htmlId);
if (el && el.value) data.basicInfo[dataKey] = el.value;
});
recruitFields.forEach(field => {
const value = formData.get(field);
if (value) data.recruitInfo[field] = value;
Object.entries(recruitFieldMapping).forEach(([htmlId, dataKey]) => {
const el = document.getElementById(htmlId);
if (el && el.value) data.recruitInfo[dataKey] = el.value;
});
return data;
@@ -76,19 +105,31 @@ export function getPositionFormData() {
*/
export function getJobFormData() {
const form = document.getElementById('jobForm');
const formData = new FormData(form);
const data = {};
const fields = ['jobCategoryCode', 'jobCategoryName', 'jobCode', 'jobName', 'jobNameEn',
'jobEffectiveDate', 'jobHeadcount', 'jobSortOrder', 'jobRemark', 'jobLevel'];
// 使用新的 job_ prefix 欄位
const fieldMapping = {
'job_category': 'jobCategoryCode',
'job_categoryName': 'jobCategoryName',
'job_code': 'jobCode',
'job_name': 'jobName',
'job_nameEn': 'jobNameEn',
'job_level': 'jobLevel',
'job_effectiveDate': 'jobEffectiveDate',
'job_sortOrder': 'jobSortOrder',
'job_headcount': 'jobHeadcount',
'job_remark': 'jobRemark'
};
fields.forEach(field => {
const value = formData.get(field);
if (value) data[field] = value;
Object.entries(fieldMapping).forEach(([htmlId, dataKey]) => {
const el = document.getElementById(htmlId);
if (el && el.value) data[dataKey] = el.value;
});
data.hasAttendanceBonus = document.getElementById('hasAttendanceBonus').checked;
data.hasHousingAllowance = document.getElementById('hasHousingAllowance').checked;
const hasAttBonus = document.getElementById('job_hasAttBonus');
const hasHouseAllow = document.getElementById('job_hasHouseAllow');
data.hasAttendanceBonus = hasAttBonus ? hasAttBonus.checked : false;
data.hasHousingAllowance = hasHouseAllow ? hasHouseAllow.checked : false;
return data;
}
@@ -101,33 +142,59 @@ export function getJobDescFormData() {
const form = document.getElementById('jobDescForm');
if (!form) return {};
const formData = new FormData(form);
const data = { basicInfo: {}, positionInfo: {}, responsibilities: {}, requirements: {} };
// Basic Info
['empNo', 'empName', 'positionCode', 'versionDate'].forEach(field => {
const el = document.getElementById('jd_' + field);
if (el && el.value) data.basicInfo[field] = el.value;
// Basic Info - 使用新的 jd_ prefix
const basicMapping = {
'jd_empNo': 'empNo',
'jd_empName': 'empName',
'jd_posCode': 'positionCode',
'jd_versionDate': 'versionDate'
};
Object.entries(basicMapping).forEach(([htmlId, dataKey]) => {
const el = document.getElementById(htmlId);
if (el && el.value) data.basicInfo[dataKey] = el.value;
});
// Position Info
['positionName', 'department', 'positionEffectiveDate', 'directSupervisor',
'positionGradeJob', 'reportTo', 'directReports', 'workLocation', 'empAttribute'].forEach(field => {
const el = document.getElementById('jd_' + field);
if (el && el.value) data.positionInfo[field] = el.value;
// Position Info - 使用新的 jd_ prefix
const posInfoMapping = {
'jd_posName': 'positionName',
'jd_department': 'department',
'jd_posLevel': 'positionLevel',
'jd_posEffDate': 'positionEffectiveDate',
'jd_supervisor': 'directSupervisor',
'jd_gradeJob': 'positionGradeJob',
'jd_reportTo': 'reportTo',
'jd_directReports': 'directReports',
'jd_location': 'workLocation',
'jd_empAttr': 'empAttribute'
};
Object.entries(posInfoMapping).forEach(([htmlId, dataKey]) => {
const el = document.getElementById(htmlId);
if (el && el.value) data.positionInfo[dataKey] = el.value;
});
// Purpose & Responsibilities
const purpose = document.getElementById('jd_positionPurpose');
// Purpose & Responsibilities - 使用新的 jd_ prefix
const purpose = document.getElementById('jd_purpose');
if (purpose && purpose.value) data.responsibilities.positionPurpose = purpose.value;
const mainResp = document.getElementById('jd_mainResponsibilities');
const mainResp = document.getElementById('jd_mainResp');
if (mainResp && mainResp.value) data.responsibilities.mainResponsibilities = mainResp.value;
// Requirements
['education', 'basicSkills', 'professionalKnowledge', 'workExperienceReq', 'otherRequirements'].forEach(field => {
const el = document.getElementById('jd_' + field);
if (el && el.value) data.requirements[field] = el.value;
// Requirements - 使用新的 jd_ prefix
const reqMapping = {
'jd_eduLevel': 'education',
'jd_basicSkills': 'basicSkills',
'jd_proKnowledge': 'professionalKnowledge',
'jd_expReq': 'workExperienceReq',
'jd_otherReq': 'otherRequirements'
};
Object.entries(reqMapping).forEach(([htmlId, dataKey]) => {
const el = document.getElementById(htmlId);
if (el && el.value) data.requirements[dataKey] = el.value;
});
return data;
@@ -141,16 +208,33 @@ export function getDeptFunctionFormData() {
const form = document.getElementById('deptFunctionForm');
if (!form) return {};
const formData = new FormData(form);
const data = {};
const fields = ['deptFunctionCode', 'deptFunctionName', 'deptFunctionBU',
'deptFunctionDept', 'deptManager', 'deptMission', 'deptVision',
'deptCoreFunctions', 'deptKPIs'];
// 使用新的 df_ prefix 欄位
const fieldMapping = {
'df_code': 'dfCode',
'df_name': 'dfName',
'df_businessUnit': 'businessUnit',
'df_division': 'division',
'df_department': 'department',
'df_section': 'section',
'df_posTitle': 'positionTitle',
'df_posLevel': 'positionLevel',
'df_managerTitle': 'managerTitle',
'df_effectiveDate': 'effectiveDate',
'df_headcountLimit': 'headcountLimit',
'df_status': 'status',
'df_mission': 'mission',
'df_vision': 'vision',
'df_coreFunc': 'coreFunctions',
'df_kpis': 'kpis',
'df_collab': 'collaboration',
'df_remark': 'remark'
};
fields.forEach(field => {
const value = formData.get(field);
if (value) data[field] = value;
Object.entries(fieldMapping).forEach(([htmlId, dataKey]) => {
const el = document.getElementById(htmlId);
if (el && el.value) data[dataKey] = el.value;
});
return data;
@@ -192,8 +276,11 @@ export function updatePreview() {
* 更新崗位類別中文名稱
*/
export function updateCategoryName() {
const category = document.getElementById('positionCategory').value;
document.getElementById('positionCategoryName').value = categoryMap[category] || '';
const categoryEl = document.getElementById('pos_category');
const categoryNameEl = document.getElementById('pos_categoryName');
if (categoryEl && categoryNameEl) {
categoryNameEl.value = categoryMap[categoryEl.value] || '';
}
updatePreview();
}
@@ -201,8 +288,11 @@ export function updateCategoryName() {
* 更新崗位性質中文名稱
*/
export function updateNatureName() {
const nature = document.getElementById('positionNature').value;
document.getElementById('positionNatureName').value = natureMap[nature] || '';
const typeEl = document.getElementById('pos_type');
const typeNameEl = document.getElementById('pos_typeName');
if (typeEl && typeNameEl) {
typeNameEl.value = natureMap[typeEl.value] || '';
}
updatePreview();
}
@@ -210,8 +300,11 @@ export function updateNatureName() {
* 更新職務類別中文名稱
*/
export function updateJobCategoryName() {
const category = document.getElementById('jobCategoryCode').value;
document.getElementById('jobCategoryName').value = jobCategoryMap[category] || '';
const categoryEl = document.getElementById('job_category');
const categoryNameEl = document.getElementById('job_categoryName');
if (categoryEl && categoryNameEl) {
categoryNameEl.value = jobCategoryMap[categoryEl.value] || '';
}
updatePreview();
}
@@ -219,10 +312,12 @@ export function updateJobCategoryName() {
* 修改崗位編號
*/
export function changePositionCode() {
const currentCode = document.getElementById('positionCode').value;
const codeEl = document.getElementById('pos_code');
if (!codeEl) return;
const currentCode = codeEl.value;
const newCode = prompt('請輸入新的崗位編號:', currentCode);
if (newCode && newCode !== currentCode) {
document.getElementById('positionCode').value = newCode;
codeEl.value = newCode;
showToast('崗位編號已更改!');
updatePreview();
}
@@ -232,10 +327,12 @@ export function changePositionCode() {
* 修改職務編號
*/
export function changeJobCode() {
const currentCode = document.getElementById('jobCode').value;
const codeEl = document.getElementById('job_code');
if (!codeEl) return;
const currentCode = codeEl.value;
const newCode = prompt('請輸入新的職務編號:', currentCode);
if (newCode && newCode !== currentCode) {
document.getElementById('jobCode').value = newCode;
codeEl.value = newCode;
showToast('職務編號已更改!');
updatePreview();
}
@@ -271,7 +368,10 @@ export function confirmMajor() {
document.querySelectorAll('#majorModal input[type="checkbox"]:checked').forEach(cb => {
selected.push(cb.value);
});
document.getElementById('majorReq').value = selected.join(', ');
const majorReqEl = document.getElementById('rec_majorReq');
if (majorReqEl) {
majorReqEl.value = selected.join(', ');
}
closeMajorModal();
updatePreview();
}