|
|
@@ -42,6 +42,7 @@ export const sites: Site[] = [
|
|
|
site({ id: 's4', name: '徐汇滨江绿地', code: 'XH-033', branchId: 'three', address: '徐汇区龙腾大道', contractHeadcount: 22, policeName: '滨江派出所', policePhone: '021-64380000', lat: 31.1868, lng: 121.4682, fenceM: 200 }),
|
|
|
site({ id: 's5', name: '静安寺商圈', code: 'JA-007', branchId: 'two', address: '静安区南京西路', contractHeadcount: 36, policeName: '静安寺派出所', policePhone: '021-62480000', lat: 31.2236, lng: 121.4458, fenceM: 160 }),
|
|
|
site({ id: 's6', name: '人防指挥备用点', code: 'RF-001', branchId: 'renfang', address: '黄浦区人民大道', contractHeadcount: 16, policeName: '南京东路派出所', policePhone: '021-63220000', lat: 31.2307, lng: 121.4737, fenceM: 130 }),
|
|
|
+ site({ id: 's7', name: '城市服务中心A驻点', code: 'CS-A01', branchId: 'one', address: '黄浦区人民大道 200 号', contractHeadcount: 18, policeName: '南京东路派出所', policePhone: '021-63221100', lat: 31.2316, lng: 121.4752, fenceM: 160, ownerName: '李海' }),
|
|
|
]
|
|
|
|
|
|
function p(
|
|
|
@@ -61,6 +62,7 @@ const extraPacks: { siteId: string; names: Array<[string, '男' | '女']> }[] =
|
|
|
{ siteId: 's4', names: [['汪洋', '男'], ['田丽', '女'], ['任杰', '男'], ['姜涛', '男'], ['范敏', '女'], ['方伟', '男'], ['石磊', '男'], ['纪芳', '女'], ['齐勇', '男'], ['项丽', '女'], ['蓝伟', '男'], ['池敏', '女']] },
|
|
|
{ siteId: 's5', names: [['姚芳', '女'], ['谭军', '男'], ['廖静', '女'], ['邹伟', '男'], ['熊丽', '女'], ['金涛', '男'], ['陆敏', '女'], ['郝强', '男'], ['易芳', '女'], ['申伟', '男'], ['宫丽', '女'], ['路强', '男'], ['皮娜', '女'], ['冉杰', '男']] },
|
|
|
{ siteId: 's6', names: [['孔伟', '男'], ['白静', '女'], ['崔军', '男'], ['康丽', '女'], ['毛伟', '男'], ['邱芳', '女'], ['解强', '男'], ['翟丽', '女'], ['尚伟', '男'], ['栾芳', '女']] },
|
|
|
+ { siteId: 's7', names: [['陈波', '男'], ['吴倩', '女'], ['高磊', '男']] },
|
|
|
]
|
|
|
|
|
|
export function extraPeople(): Person[] {
|
|
|
@@ -92,6 +94,7 @@ export function extraPeople(): Person[] {
|
|
|
partyMember: i % 8 === 0,
|
|
|
veteran: i % 9 === 0,
|
|
|
appRegistered: i % 6 !== 0,
|
|
|
+ post: pack.siteId === 's7' ? (i % 2 ? '巡逻' : '门岗') : undefined,
|
|
|
}))
|
|
|
i += 1
|
|
|
}
|
|
|
@@ -110,6 +113,28 @@ export const people: Person[] = [
|
|
|
p({ id: 'p8', name: '孙婷', no: 'RF10330', branchId: 'two', siteId: 's2', identity: '自有', identityFrom: '2021-11-11', identityHistory: [{ identity: '自有', from: '2021-11-11' }], siteHistory: [{ siteId: 's2', from: '2021-11-11' }], onDuty: true, gender: '女', age: 35, education: '大专', certificate: '上岗证', phone: '13800001008', hiredAt: '2021-11-11', partyMember: false, veteran: false, appRegistered: true }),
|
|
|
p({ id: 'p9', name: '吴磊', no: 'RF10412', branchId: 'one', siteId: 's3', identity: '劳务', identityFrom: '2024-07-01', identityHistory: [{ identity: '劳务', from: '2024-07-01' }], siteHistory: [{ siteId: 's3', from: '2024-07-01' }], onDuty: true, gender: '男', age: 38, education: '高中', certificate: '监控证', phone: '13800001009', hiredAt: '2024-07-01', partyMember: false, veteran: true, appRegistered: false }),
|
|
|
p({ id: 'p10', name: '郑浩', no: 'RF10501', branchId: 'three', siteId: 's4', identity: '供应商', identityFrom: '2025-01-15', identityHistory: [{ identity: '供应商', from: '2025-01-15' }], siteHistory: [{ siteId: 's4', from: '2025-01-15' }], onDuty: true, gender: '男', age: 31, education: '中专', certificate: '上岗证', phone: '13800001010', hiredAt: '2025-01-15', partyMember: false, veteran: false, appRegistered: true }),
|
|
|
+ p({
|
|
|
+ id: 'p-zm',
|
|
|
+ name: '张明',
|
|
|
+ no: 'RF10801',
|
|
|
+ branchId: 'one',
|
|
|
+ siteId: 's7',
|
|
|
+ identity: '自有',
|
|
|
+ identityFrom: '2024-03-01',
|
|
|
+ identityHistory: [{ identity: '劳务', from: '2023-03-01', to: '2024-02-29' }, { identity: '自有', from: '2024-03-01' }],
|
|
|
+ siteHistory: [{ siteId: 's1', from: '2023-03-01', to: '2024-02-29' }, { siteId: 's7', from: '2024-03-01' }],
|
|
|
+ onDuty: true,
|
|
|
+ gender: '男',
|
|
|
+ age: 28,
|
|
|
+ education: '高中',
|
|
|
+ certificate: '上岗证',
|
|
|
+ phone: '13800001081',
|
|
|
+ hiredAt: '2023-03-01',
|
|
|
+ partyMember: false,
|
|
|
+ veteran: false,
|
|
|
+ appRegistered: true,
|
|
|
+ post: '门岗',
|
|
|
+ }),
|
|
|
...extraPeople(),
|
|
|
]
|
|
|
|
|
|
@@ -120,11 +145,12 @@ export const contracts: ContractRow[] = [
|
|
|
{ id: 'c4', code: 'HT-2023-009', siteId: 's6', taxRate: '6%', otPrice: 38, amount: '70 万/年', headcount: 16, startDate: '2023-01-01', endDate: '2026-06-30', clause: '已过期示例', fileName: '扫描件待补' },
|
|
|
{ id: 'c5', code: 'HT-2026-040', siteId: 's3', taxRate: '6%', otPrice: 48, amount: '680 万/年', headcount: 120, startDate: '2026-01-01', endDate: '2027-12-31', clause: '机场安检缺员按合同单价扣减', fileName: '扫描件待补' },
|
|
|
{ id: 'c6', code: 'HT-2026-041', siteId: 's5', taxRate: '6%', otPrice: 41, amount: '210 万/年', headcount: 36, startDate: '2026-04-01', endDate: '2027-12-31', clause: '商圈夜间加岗按加班单价', fileName: '扫描件待补' },
|
|
|
+ { id: 'c7', code: 'HT-2026-A01', siteId: 's7', taxRate: '6%', otPrice: 43, amount: '120 万/年', headcount: 18, startDate: '2026-01-01', endDate: '2027-12-31', clause: '城市服务中心A门岗缺员按合同单价扣减', fileName: '扫描件待补' },
|
|
|
]
|
|
|
|
|
|
export const HOLIDAY_DAY = '2026-10-01'
|
|
|
export const WEEK_DUTY_DAYS = ['2026-09-28', '2026-09-29', '2026-09-30']
|
|
|
-const PREFER_NOS = ['RF10021', 'RF10022', 'RF10045', 'RF10102', 'RF10201', 'RF10008', 'RF10330', 'RF10412', 'RF10501']
|
|
|
+const PREFER_NOS = ['RF10801', 'RF10021', 'RF10022', 'RF10045', 'RF10102', 'RF10201', 'RF10008', 'RF10330', 'RF10412', 'RF10501']
|
|
|
|
|
|
export function onDutyAt(siteId: string, list: Person[] = people) {
|
|
|
return list.filter((p) => p.siteId === siteId && p.onDuty)
|
|
|
@@ -150,6 +176,7 @@ const SCHEDULE_SPEC: Array<Omit<ScheduleRow, 'crewNos'>> = [
|
|
|
{ id: 'sc4', siteId: 's4', postMode: '三人两岗', cycle: '上 5 休 2', workForm: '站岗 / 巡逻', hourType: '本班', holidaySecondary: '国定假按法定加班' },
|
|
|
{ id: 'sc5', siteId: 's5', postMode: '三人两岗', cycle: '上 5 休 2', workForm: '巡逻 / 安检', hourType: '本班', holidaySecondary: '国定假按法定加班' },
|
|
|
{ id: 'sc6', siteId: 's6', postMode: '两人一岗', cycle: '上 5 休 2', workForm: '站岗', hourType: '本班', holidaySecondary: '国定假按法定加班' },
|
|
|
+ { id: 'sc7', siteId: 's7', postMode: '一人一岗', cycle: '上 5 休 2', workForm: '站岗', hourType: '本班', holidaySecondary: '国定假按法定加班', startTime: '08:00', endTime: '16:00', punchWindow: '07:30–08:30', published: true },
|
|
|
]
|
|
|
|
|
|
export function buildSchedules(list: Person[] = people): ScheduleRow[] {
|
|
|
@@ -194,7 +221,23 @@ export function holidayCrew(list: Person[] = people, schs: ScheduleRow[] = sched
|
|
|
}
|
|
|
|
|
|
export function holidayDutyAttendance(list: Person[] = people, schs: ScheduleRow[] = schedules): AttendanceRow[] {
|
|
|
- return holidayCrew(list, schs).map((person, i) => holidayRow(person, i, HOLIDAY_DAY, true, schs))
|
|
|
+ const rows = holidayCrew(list, schs).map((person, i) => holidayRow(person, i, HOLIDAY_DAY, true, schs))
|
|
|
+ const zm = rows.find((a) => a.personId === 'p-zm')
|
|
|
+ if (zm) {
|
|
|
+ const site = sites.find((s) => s.id === 's7')
|
|
|
+ Object.assign(zm, {
|
|
|
+ status: '越界' as const,
|
|
|
+ hours: 8,
|
|
|
+ checkIn: '08:12',
|
|
|
+ checkOut: '16:00',
|
|
|
+ failReasons: ['超出驻点电子围栏'],
|
|
|
+ gates: { live: true, face: true, fence: false, window: true },
|
|
|
+ source: 'self' as const,
|
|
|
+ lat: (site?.lat || zm.lat || 31.23) + 0.018,
|
|
|
+ lng: (site?.lng || zm.lng || 121.47) + 0.016,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return rows
|
|
|
}
|
|
|
|
|
|
export function weekDutyAttendance(list: Person[] = people, schs: ScheduleRow[] = schedules): AttendanceRow[] {
|
|
|
@@ -203,6 +246,7 @@ export function weekDutyAttendance(list: Person[] = people, schs: ScheduleRow[]
|
|
|
WEEK_DUTY_DAYS.forEach((date, d) => {
|
|
|
crew.forEach((person, i) => {
|
|
|
if (person.id === 'p10' && date === '2026-09-30') return
|
|
|
+ if (person.id === 'p-zm' && date === '2026-09-30') return
|
|
|
out.push(holidayRow(person, i + d * 3, date, false, schs))
|
|
|
})
|
|
|
})
|
|
|
@@ -224,6 +268,7 @@ export const attendance: AttendanceRow[] = [
|
|
|
{ id: 'a8', personId: 'p8', siteId: 's2', date: '2026-08-31', checkIn: '07:56', checkOut: '19:00', status: '出勤中', hourType: '公司付加班', overtimeApply: true, hours: 11, holiday: false, crossMonth: false, lat: 31.1978, lng: 121.3272, accM: 40 },
|
|
|
{ id: 'a9', personId: 'p9', siteId: 's3', date: '2026-08-31', checkIn: '08:00', checkOut: '16:02', status: '早退', hourType: '本班', overtimeApply: false, hours: 8, holiday: false, crossMonth: false, lat: 31.1506, lng: 121.8072, accM: 55 },
|
|
|
{ id: 'a10', personId: 'p10', siteId: 's4', date: '2026-09-30', checkIn: '20:00', checkOut: '04:00', status: '出勤中', hourType: '本班', overtimeApply: false, hours: 8, holiday: false, crossMonth: true, lat: 31.1864, lng: 121.4690, accM: 44 },
|
|
|
+ { id: 'a-zm-cross', personId: 'p-zm', siteId: 's7', date: '2026-09-30', checkIn: '20:00', checkOut: '04:00', status: '出勤中', hourType: '本班', overtimeApply: false, hours: 8, holiday: false, crossMonth: true, lat: 31.2316, lng: 121.4752, accM: 32, source: 'seed' },
|
|
|
{ id: 'a12', personId: 'p3', siteId: 's1', date: '2026-08-30', checkIn: '08:00', checkOut: '16:00', status: '出勤中', hourType: '本班', overtimeApply: false, hours: 8, holiday: false, crossMonth: false },
|
|
|
...extraAttendance(),
|
|
|
]
|
|
|
@@ -241,6 +286,7 @@ export const finance: FinanceRow[] = [
|
|
|
{ id: 'f10', siteId: 's4', month: '2026-10', invoiced: 10.1, uninvoiced: 0.5, ownCost: 6.4, vendorCost: 2.9, equipCost: 0.3, reimbursed: 0.4, revenue: 8.9 },
|
|
|
{ id: 'f11', siteId: 's5', month: '2026-10', invoiced: 16.8, uninvoiced: 0.9, ownCost: 9.4, vendorCost: 4.5, equipCost: 0.5, reimbursed: 0.2, revenue: 17.6 },
|
|
|
{ id: 'f12', siteId: 's6', month: '2026-10', invoiced: 7.4, uninvoiced: 0.3, ownCost: 4.9, vendorCost: 0.6, equipCost: 0.2, reimbursed: 0.1, revenue: 7.6 },
|
|
|
+ { id: 'f13', siteId: 's7', month: '2026-10', invoiced: 9.6, uninvoiced: 0.4, ownCost: 5.8, vendorCost: 1.6, equipCost: 0.3, reimbursed: 0.2, revenue: 10.4 },
|
|
|
]
|
|
|
|
|
|
export const notices: NoticeRow[] = [
|
|
|
@@ -277,6 +323,17 @@ export const notices: NoticeRow[] = [
|
|
|
body: '虹桥枢纽 10 月 3 日 14:00 消防复训,应到人员带上岗证原件。覆盖率目标 88%。缺训不能顶安检岗。附件为场地图,请在作业端打开本通知后视为已读。',
|
|
|
readBy: [],
|
|
|
},
|
|
|
+ {
|
|
|
+ id: 'n-zm',
|
|
|
+ title: '越界待复核 · 张明',
|
|
|
+ scope: '城市服务中心A驻点',
|
|
|
+ sentAt: '2026-10-01 08:13',
|
|
|
+ read: 0,
|
|
|
+ unread: 0,
|
|
|
+ kind: '拍照打卡',
|
|
|
+ body: '张明于 08:12 在城市服务中心A驻点打卡,系统判定当前位置超出电子围栏,未计入正常出勤。请本点督导复核现场说明或要求重新打卡。',
|
|
|
+ readBy: [],
|
|
|
+ },
|
|
|
]
|
|
|
|
|
|
export const events: EventRow[] = [
|
|
|
@@ -284,6 +341,38 @@ export const events: EventRow[] = [
|
|
|
{ id: 'e2', level: '一般', siteId: 's1', title: '访客未登记进入大堂', status: '已闭环', createdAt: '2026-09-29 19:12', hrSynced: true },
|
|
|
{ id: 'e3', level: '非常重要', siteId: 's3', title: '夜间越界告警已核验', status: '已闭环', createdAt: '2026-09-29 02:18', hrSynced: true },
|
|
|
{ id: 'e4', level: '非常重要', siteId: 's2', title: '国庆值班越界待核验', status: '待处理', createdAt: '2026-10-01 07:58', hrSynced: true },
|
|
|
+ {
|
|
|
+ id: 'e-zm',
|
|
|
+ level: '重要',
|
|
|
+ siteId: 's7',
|
|
|
+ title: '越界打卡 · 张明 · 城市服务中心A驻点',
|
|
|
+ status: '待处理',
|
|
|
+ createdAt: '2026-10-01 08:13',
|
|
|
+ hrSynced: true,
|
|
|
+ personId: 'p-zm',
|
|
|
+ body: '张明于 08:12 打卡,定位超出城市服务中心A驻点电子围栏。未计入正常出勤,可提交说明或等待督导复核。',
|
|
|
+ dueAt: '2026-10-01 12:00',
|
|
|
+ logs: [{ at: '2026-10-01 08:13', actor: '系统', action: '生成异常', note: '围栏校验未通过' }],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'e-zm-done',
|
|
|
+ level: '一般',
|
|
|
+ siteId: 's7',
|
|
|
+ title: '越界复核已闭环 · 张明 · 外围交接',
|
|
|
+ status: '已闭环',
|
|
|
+ createdAt: '2026-09-30 09:20',
|
|
|
+ hrSynced: true,
|
|
|
+ personId: 'p-zm',
|
|
|
+ body: '历史示例:张明临时到驻点外围执行交接任务。原始越界记录保留。',
|
|
|
+ dueAt: '2026-09-30 12:00',
|
|
|
+ handleNote: '情况属实,按临时任务核验通过。原始越界记录保留,不删改打卡点。',
|
|
|
+ logs: [
|
|
|
+ { at: '2026-09-30 09:20', actor: '系统', action: '生成异常', note: '超出驻点电子围栏' },
|
|
|
+ { at: '2026-09-30 09:40', actor: '张明 · 保安队员', action: '补充说明', note: '临时到驻点外围执行交接任务,已补拍现场照片。' },
|
|
|
+ { at: '2026-09-30 10:05', actor: '李海 · 驻点督导', action: '下发整改', note: '请 12:00 前补现场照片并说明任务来源。' },
|
|
|
+ { at: '2026-09-30 10:28', actor: '李海 · 驻点督导', action: '复核闭环', note: '情况属实,原始越界保留。' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
]
|
|
|
|
|
|
export const alertRules: AlertRule[] = [
|
|
|
@@ -307,6 +396,7 @@ export const supplierPays: SupplierPay[] = [
|
|
|
{ id: 'sp7', siteId: 's4', month: '2026-10', quota: 2.9, paid: 2.1 },
|
|
|
{ id: 'sp8', siteId: 's5', month: '2026-10', quota: 4.5, paid: 1.4 },
|
|
|
{ id: 'sp9', siteId: 's6', month: '2026-10', quota: 0.6, paid: 0.3 },
|
|
|
+ { id: 'sp10', siteId: 's7', month: '2026-10', quota: 1.6, paid: 0.8 },
|
|
|
]
|
|
|
|
|
|
export const trainings: TrainingRow[] = [
|
|
|
@@ -318,6 +408,7 @@ export const trainings: TrainingRow[] = [
|
|
|
{ id: 't6', siteId: 's4', title: '滨江夜间巡逻', month: '2026-10', coverage: 80 },
|
|
|
{ id: 't7', siteId: 's5', title: '商圈客流疏导', month: '2026-10', coverage: 84 },
|
|
|
{ id: 't8', siteId: 's6', title: '人防备勤条例', month: '2026-10', coverage: 96 },
|
|
|
+ { id: 't9', siteId: 's7', title: '门岗礼仪与围栏纪律', month: '2026-10', coverage: 91 },
|
|
|
]
|
|
|
|
|
|
export const alarms: AlarmLog[] = []
|
|
|
@@ -402,6 +493,8 @@ function guardAccount(id: string, account: string, personId: string): SysUser {
|
|
|
|
|
|
export const sysUsers: SysUser[] = [
|
|
|
{ id: 'u1', account: 'linqm', name: '林启明', role: 'hq', branchId: 'all', siteId: '', personId: '', phone: '13800002001', enabled: true, duty: roleDuty('hq') },
|
|
|
+ guardAccount('u-zm', 'zhangming', 'p-zm'),
|
|
|
+ { id: 'u-lh', account: 'lihai', name: '李海', role: 'site', branchId: 'one', siteId: 's7', personId: '', phone: '13800002081', enabled: true, duty: roleDuty('site') },
|
|
|
{ id: 'u2', account: 'zhoujh', name: '周建华', role: 'branch', branchId: 'two', siteId: '', personId: '', phone: '13800002002', enabled: true, duty: roleDuty('branch') },
|
|
|
{ id: 'u3', account: 'mawg', name: '马卫国', role: 'site', branchId: 'two', siteId: 's2', personId: '', phone: '13800002003', enabled: true, duty: roleDuty('site') },
|
|
|
{ id: 'u4', account: 'sunting', name: '孙婷', role: 'guard', branchId: 'two', siteId: 's2', personId: 'p8', phone: '13800001008', enabled: true, duty: roleDuty('guard') },
|