feat: 智能排班人员预测系统
- 新增人员预测tab,基于多维度产能指标生成招聘/优化建议 - 规则引擎:7条优化规则 + 7条招聘规则 + 4步决策逻辑 - 动态标准值:基于全部门店中位数计算,替代硬编码阈值 - 互斥逻辑:有优化信号时抑制所有招聘信号 - 占比超配时不触发招聘信号(R9/R11/R12/R13) - 离职缺口需出勤不足才触发招聘(R10) - 管理岗满编时不触发出勤不足招聘(R9) - 前端可折叠规则引擎面板,展示全部规则和触发条件 - 产能指标对比展示实际值 vs 中位数标准
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
薪资拆分明细表 + 考勤数据表 导入脚本
|
||||
遵循现有 import_log + records 模式
|
||||
"""
|
||||
import openpyxl
|
||||
import hashlib
|
||||
import psycopg2
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
DB_CONFIG = {
|
||||
'host': 'localhost',
|
||||
'port': 5432,
|
||||
'dbname': 'bill_query',
|
||||
'user': 'freedak',
|
||||
'password': '',
|
||||
}
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SALARY_FILE = os.path.join(BASE_DIR, '马兰拉面数据分析', '薪资拆分明细表_脱敏.xlsx')
|
||||
ATTENDANCE_FILE = os.path.join(BASE_DIR, '马兰拉面数据分析', '考勤数据表-脱敏.xlsx')
|
||||
|
||||
def file_sha256(filepath):
|
||||
h = hashlib.sha256()
|
||||
with open(filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(8192), b''):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
def to_num(val):
|
||||
if val is None or val == '':
|
||||
return 0
|
||||
try:
|
||||
return float(val)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
def to_text(val):
|
||||
if val is None:
|
||||
return None
|
||||
s = str(val).strip()
|
||||
return s if s else None
|
||||
|
||||
def import_salary(conn):
|
||||
print(f'导入薪资拆分明细表: {SALARY_FILE}')
|
||||
sha = file_sha256(SALARY_FILE)
|
||||
wb = openpyxl.load_workbook(SALARY_FILE, read_only=True, data_only=True)
|
||||
ws = wb[wb.sheetnames[0]]
|
||||
|
||||
# Row 1: title, Row 2: merged title, Row 3: headers, Row 4+: data
|
||||
rows = list(ws.iter_rows(min_row=4, values_only=True))
|
||||
data_rows = [r for r in rows if r[0] is not None and r[9] is not None and str(r[9]).strip()]
|
||||
print(f' 数据行数: {len(data_rows)}')
|
||||
|
||||
report_month = '2026-04-01'
|
||||
source_file = os.path.basename(SALARY_FILE)
|
||||
|
||||
cur = conn.cursor()
|
||||
# Check if already imported
|
||||
cur.execute(
|
||||
'SELECT import_id FROM public.salary_import_log WHERE report_month = %s AND source_file = %s AND file_sha256 = %s',
|
||||
(report_month, source_file, sha)
|
||||
)
|
||||
existing = cur.fetchone()
|
||||
if existing:
|
||||
print(f' 已导入过, import_id={existing[0]}, 跳过')
|
||||
wb.close()
|
||||
return
|
||||
|
||||
# Insert import log
|
||||
cur.execute(
|
||||
'''INSERT INTO public.salary_import_log (report_month, source_file, file_sha256, workbook_rows, imported_rows)
|
||||
VALUES (%s, %s, %s, %s, %s) RETURNING import_id''',
|
||||
(report_month, source_file, sha, len(data_rows), len(data_rows))
|
||||
)
|
||||
import_id = cur.fetchone()[0]
|
||||
print(f' import_id={import_id}')
|
||||
|
||||
# Batch insert
|
||||
insert_sql = '''INSERT INTO public.salary_detail_records (
|
||||
import_id, source_row,
|
||||
org_level1, org_level2, org_level3, org_level4, org_level5, org_level6, org_level7, org_level8,
|
||||
employee_code, salary_period, position, work_type, employment_type, hire_date, leave_date,
|
||||
salary_standard, base_wage, overtime_subsidy, social_subsidy, position_wage, tenure_wage,
|
||||
hourly_rate, total_wage, brand_wage, rent_subsidy,
|
||||
month_days, expected_attend, calc_attend, actual_attend, actual_hours,
|
||||
expected_rest, actual_rest, expected_legal_holiday, actual_legal_holiday,
|
||||
comp_leave_days, annual_leave, marriage_leave, paid_leave, bereavement_leave,
|
||||
personal_leave_days, personal_leave_deduction, furlough_days, furlough_deduction,
|
||||
injury_leave_days, injury_leave_deduction, absent_days, absent_deduction, join_leave_absent,
|
||||
attendance_wage, rest_subsidy, overtime_pay, shift_subsidy, comp_leave_wage,
|
||||
cashier_amount, bonus, subsidy, subsidy_remark, rush_wage, total_supplement,
|
||||
late_deduction, no_punch_deduction, internal_social_total, internal_social_remark,
|
||||
loan, loan_remark, fine, compensation, total_deduction, injury_wage, singapore_subsidy,
|
||||
perf_standard, perf_score, perf_amount, phone_allowance, dorm_fee,
|
||||
net_salary, gross_pay, income_tax, net_pay,
|
||||
external_base_standard, external_overtime, external_absence, external_bonus,
|
||||
external_subsidy, external_other_deduction, external_gross,
|
||||
pension_deduction, medical_deduction, unemployment_deduction,
|
||||
external_social_total, external_tax, external_net, internal_tax, internal_net,
|
||||
external_unit, attendance_remark, employment_type_orig, salary_category
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'''
|
||||
|
||||
batch = []
|
||||
batch_size = 500
|
||||
for idx, r in enumerate(data_rows):
|
||||
source_row = idx + 4 # Excel row number
|
||||
vals = [
|
||||
import_id, source_row,
|
||||
to_text(r[1]), to_text(r[2]), to_text(r[3]), to_text(r[4]), to_text(r[5]), to_text(r[6]), to_text(r[7]), to_text(r[8]),
|
||||
to_text(r[9]), to_text(r[10]), to_text(r[11]), to_text(r[12]), to_text(r[13]), to_text(r[14]), to_text(r[15]),
|
||||
to_num(r[16]), to_num(r[17]), to_num(r[18]), to_num(r[19]), to_num(r[20]), to_num(r[21]),
|
||||
to_num(r[22]), to_num(r[23]), to_num(r[24]), to_num(r[25]),
|
||||
to_num(r[26]), to_num(r[27]), to_num(r[28]), to_num(r[29]), to_num(r[30]),
|
||||
to_num(r[31]), to_num(r[32]), to_num(r[33]), to_num(r[34]),
|
||||
to_num(r[35]), to_num(r[36]), to_num(r[37]), to_num(r[38]), to_num(r[39]),
|
||||
to_num(r[40]), to_num(r[41]), to_num(r[42]), to_num(r[43]),
|
||||
to_num(r[44]), to_num(r[45]), to_num(r[46]), to_num(r[47]), to_num(r[48]),
|
||||
to_num(r[49]), to_num(r[50]), to_num(r[51]), to_num(r[52]), to_num(r[53]),
|
||||
to_num(r[54]), to_num(r[55]), to_num(r[56]), to_text(r[57]), to_num(r[58]), to_num(r[59]),
|
||||
to_num(r[60]), to_num(r[61]), to_num(r[62]), to_text(r[63]),
|
||||
to_num(r[64]), to_text(r[65]), to_num(r[66]), to_num(r[67]), to_num(r[68]), to_num(r[69]), to_num(r[70]),
|
||||
to_num(r[71]), to_num(r[72]), to_num(r[73]), to_num(r[74]), to_num(r[75]),
|
||||
to_num(r[76]), to_num(r[77]), to_num(r[78]), to_num(r[79]),
|
||||
to_num(r[80]), to_num(r[81]), to_num(r[82]), to_num(r[83]),
|
||||
to_num(r[84]), to_num(r[85]), to_num(r[86]),
|
||||
to_num(r[87]), to_num(r[88]), to_num(r[89]),
|
||||
to_num(r[90]), to_num(r[91]), to_num(r[92]), to_num(r[93]), to_num(r[94]),
|
||||
to_text(r[95]), to_text(r[96]), to_text(r[97]), to_text(r[98]),
|
||||
]
|
||||
batch.append(vals)
|
||||
if len(batch) >= batch_size:
|
||||
cur.executemany(insert_sql, batch)
|
||||
conn.commit()
|
||||
print(f' 已导入 {idx + 1}/{len(data_rows)} 行')
|
||||
batch = []
|
||||
|
||||
if batch:
|
||||
cur.executemany(insert_sql, batch)
|
||||
conn.commit()
|
||||
print(f' 已导入 {len(data_rows)}/{len(data_rows)} 行')
|
||||
|
||||
wb.close()
|
||||
print(f' 薪资明细导入完成: {len(data_rows)} 行')
|
||||
|
||||
def import_attendance(conn):
|
||||
print(f'\n导入考勤数据表: {ATTENDANCE_FILE}')
|
||||
sha = file_sha256(ATTENDANCE_FILE)
|
||||
wb = openpyxl.load_workbook(ATTENDANCE_FILE, read_only=True, data_only=True)
|
||||
ws = wb[wb.sheetnames[0]]
|
||||
|
||||
# Row 1: headers, Row 2+: data
|
||||
rows = list(ws.iter_rows(min_row=2, values_only=True))
|
||||
data_rows = [r for r in rows if r[0] is not None]
|
||||
print(f' 数据行数: {len(data_rows)}')
|
||||
|
||||
report_month = '2026-04-01'
|
||||
source_file = os.path.basename(ATTENDANCE_FILE)
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
'SELECT import_id FROM public.attendance_import_log WHERE report_month = %s AND source_file = %s AND file_sha256 = %s',
|
||||
(report_month, source_file, sha)
|
||||
)
|
||||
existing = cur.fetchone()
|
||||
if existing:
|
||||
print(f' 已导入过, import_id={existing[0]}, 跳过')
|
||||
wb.close()
|
||||
return
|
||||
|
||||
cur.execute(
|
||||
'''INSERT INTO public.attendance_import_log (report_month, source_file, file_sha256, workbook_rows, imported_rows)
|
||||
VALUES (%s, %s, %s, %s, %s) RETURNING import_id''',
|
||||
(report_month, source_file, sha, len(data_rows), len(data_rows))
|
||||
)
|
||||
import_id = cur.fetchone()[0]
|
||||
print(f' import_id={import_id}')
|
||||
|
||||
# 33 columns: 工号, 岗位, 所在部门, day_01..day_30
|
||||
cols = ['import_id', 'source_row', 'employee_code', 'position', 'department']
|
||||
cols += [f'day_{str(i).zfill(2)}' for i in range(1, 31)]
|
||||
placeholders = ', '.join(['%s'] * len(cols))
|
||||
col_names = ', '.join(cols)
|
||||
insert_sql = f'INSERT INTO public.attendance_records ({col_names}) VALUES ({placeholders})'
|
||||
|
||||
batch = []
|
||||
batch_size = 500
|
||||
for idx, r in enumerate(data_rows):
|
||||
source_row = idx + 2
|
||||
vals = [import_id, source_row, to_text(r[0]), to_text(r[1]), to_text(r[2])]
|
||||
# Days 1-30 (columns 3-32)
|
||||
for d in range(30):
|
||||
vals.append(to_text(r[3 + d]) if 3 + d < len(r) else None)
|
||||
batch.append(vals)
|
||||
if len(batch) >= batch_size:
|
||||
cur.executemany(insert_sql, batch)
|
||||
conn.commit()
|
||||
print(f' 已导入 {idx + 1}/{len(data_rows)} 行')
|
||||
batch = []
|
||||
|
||||
if batch:
|
||||
cur.executemany(insert_sql, batch)
|
||||
conn.commit()
|
||||
print(f' 已导入 {len(data_rows)}/{len(data_rows)} 行')
|
||||
|
||||
wb.close()
|
||||
print(f' 考勤数据导入完成: {len(data_rows)} 行')
|
||||
|
||||
def main():
|
||||
conn = psycopg2.connect(**DB_CONFIG)
|
||||
conn.autocommit = False
|
||||
try:
|
||||
import_salary(conn)
|
||||
import_attendance(conn)
|
||||
print('\n=== 导入完成 ===')
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(f'错误: {e}', file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,213 @@
|
||||
-- ============================================================
|
||||
-- 薪资拆分明细表 + 考勤数据表 建表SQL
|
||||
-- 创建时间: 2026-07-29
|
||||
-- 数据来源: 薪资拆分明细表_脱敏.xlsx / 考勤数据表-脱敏.xlsx
|
||||
-- 导入规则: 遵循现有 import_log + records 模式
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- 一、薪资拆分明细表
|
||||
-- ============================================================
|
||||
|
||||
-- 1. 导入日志表
|
||||
CREATE TABLE IF NOT EXISTS public.salary_import_log (
|
||||
import_id BIGSERIAL PRIMARY KEY,
|
||||
report_month DATE NOT NULL,
|
||||
source_file TEXT NOT NULL,
|
||||
file_sha256 TEXT NOT NULL,
|
||||
workbook_rows INTEGER,
|
||||
imported_rows INTEGER,
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(report_month, source_file, file_sha256)
|
||||
);
|
||||
|
||||
-- 2. 薪资明细记录表
|
||||
CREATE TABLE IF NOT EXISTS public.salary_detail_records (
|
||||
record_id BIGSERIAL PRIMARY KEY,
|
||||
import_id BIGINT NOT NULL REFERENCES public.salary_import_log(import_id) ON DELETE CASCADE,
|
||||
source_row INTEGER NOT NULL,
|
||||
|
||||
-- 组织层级
|
||||
org_level1 TEXT,
|
||||
org_level2 TEXT,
|
||||
org_level3 TEXT,
|
||||
org_level4 TEXT,
|
||||
org_level5 TEXT, -- 门店名
|
||||
org_level6 TEXT,
|
||||
org_level7 TEXT,
|
||||
org_level8 TEXT,
|
||||
|
||||
-- 员工信息
|
||||
employee_code TEXT NOT NULL,
|
||||
salary_period TEXT,
|
||||
position TEXT,
|
||||
work_type TEXT,
|
||||
employment_type TEXT,
|
||||
hire_date TEXT,
|
||||
leave_date TEXT,
|
||||
|
||||
-- 工资构成
|
||||
salary_standard NUMERIC(18,2) DEFAULT 0, -- 工资额度
|
||||
base_wage NUMERIC(18,2) DEFAULT 0, -- 基本工资
|
||||
overtime_subsidy NUMERIC(18,2) DEFAULT 0, -- 加班补贴
|
||||
social_subsidy NUMERIC(18,2) DEFAULT 0, -- 社保补贴
|
||||
position_wage NUMERIC(18,2) DEFAULT 0, -- 岗位工资
|
||||
tenure_wage NUMERIC(18,2) DEFAULT 0, -- 工龄工资
|
||||
hourly_rate NUMERIC(18,2) DEFAULT 0, -- 时薪
|
||||
total_wage NUMERIC(18,2) DEFAULT 0, -- 工资总额
|
||||
brand_wage NUMERIC(18,2) DEFAULT 0, -- 品牌工资
|
||||
rent_subsidy NUMERIC(18,2) DEFAULT 0, -- 租房补贴
|
||||
|
||||
-- 出勤信息
|
||||
month_days NUMERIC(18,2) DEFAULT 0, -- 本月天数
|
||||
expected_attend NUMERIC(18,2) DEFAULT 0, -- 应出勤天数
|
||||
calc_attend NUMERIC(18,2) DEFAULT 0, -- 算薪天数
|
||||
actual_attend NUMERIC(18,2) DEFAULT 0, -- 实出勤天数
|
||||
actual_hours NUMERIC(18,2) DEFAULT 0, -- 实际出勤时长
|
||||
expected_rest NUMERIC(18,2) DEFAULT 0, -- 应休公休天数
|
||||
actual_rest NUMERIC(18,2) DEFAULT 0, -- 实公休天数
|
||||
expected_legal_holiday NUMERIC(18,2) DEFAULT 0,
|
||||
actual_legal_holiday NUMERIC(18,2) DEFAULT 0,
|
||||
comp_leave_days NUMERIC(18,2) DEFAULT 0, -- 调休天数
|
||||
annual_leave NUMERIC(18,2) DEFAULT 0, -- 年假天数
|
||||
marriage_leave NUMERIC(18,2) DEFAULT 0,
|
||||
paid_leave NUMERIC(18,2) DEFAULT 0,
|
||||
bereavement_leave NUMERIC(18,2) DEFAULT 0,
|
||||
personal_leave_days NUMERIC(18,2) DEFAULT 0,
|
||||
personal_leave_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
furlough_days NUMERIC(18,2) DEFAULT 0,
|
||||
furlough_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
injury_leave_days NUMERIC(18,2) DEFAULT 0,
|
||||
injury_leave_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
absent_days NUMERIC(18,2) DEFAULT 0, -- 旷工天数
|
||||
absent_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
join_leave_absent NUMERIC(18,2) DEFAULT 0,
|
||||
|
||||
-- 薪资计算
|
||||
attendance_wage NUMERIC(18,2) DEFAULT 0, -- 出勤薪资
|
||||
rest_subsidy NUMERIC(18,2) DEFAULT 0, -- 公休补助
|
||||
overtime_pay NUMERIC(18,2) DEFAULT 0, -- 加班
|
||||
shift_subsidy NUMERIC(18,2) DEFAULT 0, -- 班次补贴
|
||||
comp_leave_wage NUMERIC(18,2) DEFAULT 0,
|
||||
cashier_amount NUMERIC(18,2) DEFAULT 0, -- 收银金额
|
||||
bonus NUMERIC(18,2) DEFAULT 0, -- 奖励
|
||||
subsidy NUMERIC(18,2) DEFAULT 0, -- 补助
|
||||
subsidy_remark TEXT,
|
||||
rush_wage NUMERIC(18,2) DEFAULT 0, -- 抢班工资
|
||||
total_supplement NUMERIC(18,2) DEFAULT 0, -- 应补合计
|
||||
late_deduction NUMERIC(18,2) DEFAULT 0, -- 迟到早退扣款
|
||||
no_punch_deduction NUMERIC(18,2) DEFAULT 0, -- 未打卡扣款
|
||||
internal_social_total NUMERIC(18,2) DEFAULT 0,
|
||||
internal_social_remark TEXT,
|
||||
loan NUMERIC(18,2) DEFAULT 0,
|
||||
loan_remark TEXT,
|
||||
fine NUMERIC(18,2) DEFAULT 0,
|
||||
compensation NUMERIC(18,2) DEFAULT 0,
|
||||
total_deduction NUMERIC(18,2) DEFAULT 0, -- 应扣合计
|
||||
injury_wage NUMERIC(18,2) DEFAULT 0,
|
||||
singapore_subsidy NUMERIC(18,2) DEFAULT 0,
|
||||
|
||||
-- 绩效
|
||||
perf_standard NUMERIC(18,2) DEFAULT 0,
|
||||
perf_score NUMERIC(18,2) DEFAULT 0,
|
||||
perf_amount NUMERIC(18,2) DEFAULT 0,
|
||||
|
||||
-- 其他
|
||||
phone_allowance NUMERIC(18,2) DEFAULT 0,
|
||||
dorm_fee NUMERIC(18,2) DEFAULT 0,
|
||||
net_salary NUMERIC(18,2) DEFAULT 0, -- 实得薪资
|
||||
gross_pay NUMERIC(18,2) DEFAULT 0, -- 应发工资
|
||||
income_tax NUMERIC(18,2) DEFAULT 0, -- 个人所得税
|
||||
net_pay NUMERIC(18,2) DEFAULT 0, -- 实发工资
|
||||
|
||||
-- 外账
|
||||
external_base_standard NUMERIC(18,2) DEFAULT 0,
|
||||
external_overtime NUMERIC(18,2) DEFAULT 0,
|
||||
external_absence NUMERIC(18,2) DEFAULT 0,
|
||||
external_bonus NUMERIC(18,2) DEFAULT 0,
|
||||
external_subsidy NUMERIC(18,2) DEFAULT 0,
|
||||
external_other_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
external_gross NUMERIC(18,2) DEFAULT 0,
|
||||
pension_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
medical_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
unemployment_deduction NUMERIC(18,2) DEFAULT 0,
|
||||
external_social_total NUMERIC(18,2) DEFAULT 0,
|
||||
external_tax NUMERIC(18,2) DEFAULT 0,
|
||||
external_net NUMERIC(18,2) DEFAULT 0,
|
||||
internal_tax NUMERIC(18,2) DEFAULT 0,
|
||||
internal_net NUMERIC(18,2) DEFAULT 0,
|
||||
|
||||
-- 其他字段
|
||||
external_unit TEXT,
|
||||
attendance_remark TEXT, -- 考勤备注_店长填写
|
||||
employment_type_orig TEXT, -- 雇佣类型
|
||||
salary_category TEXT, -- 薪酬类别
|
||||
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_salary_import_id ON public.salary_detail_records(import_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_salary_employee ON public.salary_detail_records(employee_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_salary_org5 ON public.salary_detail_records(org_level5);
|
||||
CREATE INDEX IF NOT EXISTS idx_salary_period ON public.salary_detail_records(salary_period);
|
||||
|
||||
-- ============================================================
|
||||
-- 二、考勤数据表
|
||||
-- ============================================================
|
||||
|
||||
-- 1. 导入日志表
|
||||
CREATE TABLE IF NOT EXISTS public.attendance_import_log (
|
||||
import_id BIGSERIAL PRIMARY KEY,
|
||||
report_month DATE NOT NULL,
|
||||
source_file TEXT NOT NULL,
|
||||
file_sha256 TEXT NOT NULL,
|
||||
workbook_rows INTEGER,
|
||||
imported_rows INTEGER,
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(report_month, source_file, file_sha256)
|
||||
);
|
||||
|
||||
-- 2. 考勤记录表(宽表:工号×日期)
|
||||
CREATE TABLE IF NOT EXISTS public.attendance_records (
|
||||
record_id BIGSERIAL PRIMARY KEY,
|
||||
import_id BIGINT NOT NULL REFERENCES public.attendance_import_log(import_id) ON DELETE CASCADE,
|
||||
source_row INTEGER NOT NULL,
|
||||
employee_code TEXT NOT NULL,
|
||||
position TEXT,
|
||||
department TEXT, -- 所在部门
|
||||
day_01 TEXT,
|
||||
day_02 TEXT,
|
||||
day_03 TEXT,
|
||||
day_04 TEXT,
|
||||
day_05 TEXT,
|
||||
day_06 TEXT,
|
||||
day_07 TEXT,
|
||||
day_08 TEXT,
|
||||
day_09 TEXT,
|
||||
day_10 TEXT,
|
||||
day_11 TEXT,
|
||||
day_12 TEXT,
|
||||
day_13 TEXT,
|
||||
day_14 TEXT,
|
||||
day_15 TEXT,
|
||||
day_16 TEXT,
|
||||
day_17 TEXT,
|
||||
day_18 TEXT,
|
||||
day_19 TEXT,
|
||||
day_20 TEXT,
|
||||
day_21 TEXT,
|
||||
day_22 TEXT,
|
||||
day_23 TEXT,
|
||||
day_24 TEXT,
|
||||
day_25 TEXT,
|
||||
day_26 TEXT,
|
||||
day_27 TEXT,
|
||||
day_28 TEXT,
|
||||
day_29 TEXT,
|
||||
day_30 TEXT,
|
||||
day_31 TEXT,
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_attendance_import_id ON public.attendance_records(import_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_attendance_employee ON public.attendance_records(employee_code);
|
||||
Reference in New Issue
Block a user