gongxiangp2/lib/performance-salary-templates.ts

125 lines
7.2 KiB
TypeScript

import { SALARY_ROLES, type SalaryRole } from './performance-salary-roles'
export const SALARY_TEMPLATE_BLANK_ROWS = 5
export const SALARY_PATH = '/workspaces/organization/performance-review'
export type SalaryTemplateColumn = {
key: string
label: string
kind: 'text' | 'money' | 'number' | 'percent'
width: number
}
export type SalaryTemplateGroup = { key: string; label: string; columns: SalaryTemplateColumn[] }
export type SalaryTemplate = {
role: SalaryRole
title: string
month: string
grouped: boolean
groups: SalaryTemplateGroup[]
columns: SalaryTemplateColumn[]
}
export function currentSalaryMonth(now = new Date()): string {
const parts = new Intl.DateTimeFormat('en-US', {
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit',
}).formatToParts(now)
return `${parts.find(part => part.type === 'year')!.value}-${parts.find(part => part.type === 'month')!.value}`
}
export function isSalaryMonth(value: unknown): value is string {
return typeof value === 'string' && /^(?!0000)\d{4}-(0[1-9]|1[0-2])$/.test(value)
}
export function resolveSalaryMonth(value: unknown, now = new Date()): string {
return isSalaryMonth(value) ? value : currentSalaryMonth(now)
}
export function salaryMonthLabel(month: string, offset = 0): string {
const [year, number] = month.split('-').map(Number)
const index = year * 12 + number - 1 + offset
return `${Math.floor(index / 12)}年${((index % 12) + 12) % 12 + 1}月`
}
export function salaryDetailsHref(role: SalaryRole, month: string): string {
return `${SALARY_PATH}?${new URLSearchParams({ role, month })}`
}
const column = (key: string, label: string, kind: SalaryTemplateColumn['kind'] = 'text', width = 144): SalaryTemplateColumn => ({ key, label, kind, width })
const group = (key: string, label: string, columns: SalaryTemplateColumn[]): SalaryTemplateGroup => ({ key, label, columns })
const identity = (brand = false) => [
column('name', '姓名', 'text', 104), column('nickname', '花名', 'text', 104),
column('department', '部门', 'text', 160), ...(brand ? [column('brand', '品牌', 'text', 136)] : []),
]
const baseWages = () => [column('baseSalary', '基本工资', 'money'), column('gradeSalary', '职级工资', 'money'), column('performanceSalary', '绩效工资', 'money')]
const review = (key: string, label: string, weight: string, scoreLabel = `${label}评分`) => [
column(`${key}Score`, scoreLabel, 'number', 176),
column(`${key}Weighted`, `${label}绩效占比${weight}`, 'percent', 192),
]
const aiSharing = () => [column('sharingScore', '知识分享/月度复盘分享', 'number', 184), column('sharingWeighted', '知识分享/月度复盘分享0%', 'percent', 192)]
export function getSalaryTemplate(role: SalaryRole, month: string): SalaryTemplate {
const current = salaryMonthLabel(month)
const previous = salaryMonthLabel(month, -1)
const beforePrevious = salaryMonthLabel(month, -2)
const salarySummary = () => group('salary', '薪资', [
column('currentTotal', `${current}合计总薪资`, 'money', 184),
column('previousTotal', `${previous}合计总薪资`, 'money', 184),
column('salaryDifference', '基础薪资差值', 'money', 160),
])
const settlement = () => group('settlement', '绩效结算', [column('performanceScore', '绩效得分', 'number')])
const remarks = () => group('remarks', '备注', [column('remarks', '备注', 'text', 224)])
const templates: Record<SalaryRole, SalaryTemplateGroup[]> = {
'ai-planning': [
group('identity', '基础信息', [...identity(true), ...baseWages()]),
group('points', '积分', [column('points', `${current}积分`, 'number', 160)]),
group('qualitative', '定性 · 维度权重25%', [
...review('brandReview', '品牌调性评审', '100%', '品牌调性评审案例评分'), ...review('collaboration', '跨部门协同', '0%'),
]),
group('quantitative', '定量 · 维度权重70%', [
...review('marketValidation', '创意成果市场验证', '10%'),
column('onTimeRate', '项目按时交付率', 'percent', 160), column('onTimeWeighted', '项目按时交付率绩效占比90%', 'percent', 192), ...aiSharing(),
]),
group('cooperation', '配合度 · 维度权重5%', review('cooperation', '工作配合度', '100%')),
settlement(), salarySummary(), remarks(),
],
photography: [group('photography', '', [
column('nickname', '花名', 'text', 104), column('level', 'P级', 'text', 104), column('position', '岗位', 'text', 160), column('department', '部门', 'text', 160),
column('baseSalaryBasis', '基础薪资基数', 'money', 160), column('baseSalary', '基础薪资', 'money'),
column('performanceSalaryBasis', '绩效薪资基数', 'money', 160), column('performanceCoefficient', '绩效系数', 'number'),
column('performanceSalary', '绩效薪资', 'money'), column('bonus', '奖金工资', 'money'), column('fixedPay', '固定薪酬', 'number'),
column('currentTotal', `${current}合计薪资`, 'money', 184), column('previousTotal', `${previous}合计薪资`, 'money', 184),
column('beforePreviousTotal', `${beforePrevious}合计薪资`, 'money', 184), column('monthOverMonth', '环比', 'percent'), column('remarks', '备注', 'text', 224),
])],
'spatial-visual': [
group('identity', '基础信息', [...identity(), ...baseWages()]),
group('qualitative', '定性 · 维度权重20%', [
...review('aiTools', '协助搭建部门AI辅助工具', '16%'), ...review('collaboration', '跨部门协同', '4%'),
]),
group('quantitative', '定量', [
column('onTimeRate', '项目按时交付率', 'percent', 160), column('onTimeWeighted', '项目按时交付率绩效占比90%', 'percent', 192), ...aiSharing(),
]),
group('cooperation', '配合度 · 维度权重5%', review('cooperation', '工作配合度', '6%')),
settlement(), salarySummary(), remarks(),
],
management: [
group('identity', '人员信息', identity()),
group('qualitative', '定性 · 维度权重70%', [
...review('visualTone', '视觉调性', '80%'), ...review('collaboration', '跨部门协同', '20%'),
]),
group('quantitative', '定量 · 维度权重20%', [
...review('marketValidation', '创意成果市场验证', '40%'), ...review('onTime', '项目按时交付率', '40%'),
...review('sharing', '知识分享/项目复盘', '20%', '知识分享/项目复盘分享评分'),
]),
group('satisfaction', '满意度 · 维度权重10%', review('cooperation', '工作配合度', '100%')),
group('settlement', '绩效得分', [column('performanceScore', `${current}绩效得分`, 'number', 184)]),
group('salary', '薪资', [
column('salaryBasis', '基础薪资', 'money'), column('baseSalary', '基本工资', 'money'), column('gradeSalary', '职级工资', 'money'),
column('performanceSalary', '绩效薪资', 'money'), column('performanceCoefficient', '绩效系数', 'number'),
column('currentTotal', `${current}新薪资`, 'money', 184), column('previousTotal', `${previous}薪资`, 'money', 184),
]),
],
}
const groups = templates[role]
return { role, title: SALARY_ROLES.find(item => item.key === role)!.title, month, grouped: role !== 'photography', groups, columns: groups.flatMap(item => item.columns) }
}