125 lines
7.2 KiB
TypeScript
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) }
|
|
}
|