save code
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Excel数据导入脚本
|
||||
从docs/example.xls导入工程项目数据到数据库
|
||||
"""
|
||||
|
||||
import sys
|
||||
import xlrd
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
# 配置
|
||||
EXCEL_FILE = '../docs/example.xls'
|
||||
DB_CONFIG = {
|
||||
'host': 'localhost',
|
||||
'user': 'root',
|
||||
'password': 'rootpassword',
|
||||
'database': 'project_manager',
|
||||
'charset': 'utf8mb4'
|
||||
}
|
||||
|
||||
def get_database_connection():
|
||||
"""获取数据库连接"""
|
||||
try:
|
||||
connection = mysql.connector.connect(**DB_CONFIG)
|
||||
return connection
|
||||
except Error as e:
|
||||
print(f'数据库连接错误: {e}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def parse_date_value(cell_value, workbook):
|
||||
"""解析Excel日期值"""
|
||||
if cell_value == '':
|
||||
return None
|
||||
|
||||
# 检查是否是日期类型
|
||||
try:
|
||||
date_tuple = xlrd.xldate_as_tuple(cell_value, workbook.datemode)
|
||||
return f'{date_tuple[0]}-{date_tuple[1]:02d}-{date_tuple[2]:02d}'
|
||||
except:
|
||||
pass
|
||||
|
||||
# 检查特殊值
|
||||
if cell_value in ['未到期', '可退质保金', '未开工']:
|
||||
return None
|
||||
|
||||
# 尝试解析字符串日期
|
||||
try:
|
||||
date_obj = datetime.strptime(str(cell_value), '%Y-%m-%d')
|
||||
return date_obj.strftime('%Y-%m-%d')
|
||||
except:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def parse_number_value(cell_value):
|
||||
"""解析数字值"""
|
||||
if cell_value == '':
|
||||
return None
|
||||
try:
|
||||
return float(cell_value)
|
||||
except:
|
||||
return None
|
||||
|
||||
def parse_int_value(cell_value):
|
||||
"""解析整数值"""
|
||||
num = parse_number_value(cell_value)
|
||||
if num is not None:
|
||||
return int(num)
|
||||
return None
|
||||
|
||||
def parse_enum_value(cell_value, allowed_values):
|
||||
"""解析枚举值"""
|
||||
if cell_value == '':
|
||||
return None
|
||||
if cell_value in allowed_values:
|
||||
return cell_value
|
||||
return None
|
||||
|
||||
def clean_text_value(cell_value):
|
||||
"""清理文本值"""
|
||||
if cell_value == '':
|
||||
return None
|
||||
return str(cell_value).strip()
|
||||
|
||||
def import_projects_from_sheet(sheet, connection, cursor, sheet_name):
|
||||
"""从工作表导入项目数据"""
|
||||
print(f'\n正在导入工作表: {sheet_name}')
|
||||
print(f'总行数: {sheet.nrows}')
|
||||
|
||||
# 获取管理员用户ID(用于created_by字段)
|
||||
cursor.execute("SELECT id FROM users WHERE username='admin' LIMIT 1")
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
print('错误: 未找到管理员用户', file=sys.stderr)
|
||||
return False
|
||||
admin_id = result[0]
|
||||
|
||||
success_count = 0
|
||||
skip_count = 0
|
||||
error_count = 0
|
||||
|
||||
# 从第6行开始(前5行是表头)
|
||||
for row_idx in range(5, sheet.nrows):
|
||||
try:
|
||||
# 读取数据
|
||||
row_data = [sheet.cell_value(row_idx, col_idx) for col_idx in range(min(61, sheet.ncols))]
|
||||
|
||||
# 检查是否是合计行或空行
|
||||
if row_data[0] == '' or str(row_data[0]).strip() == '':
|
||||
skip_count += 1
|
||||
continue
|
||||
|
||||
# 准备SQL插入语句
|
||||
sql = """
|
||||
INSERT INTO projects (
|
||||
project_no, power_contract_no, name, subitem_count, subitem_code,
|
||||
total_investment, contract_amount, warranty_ratio, settlement_amount,
|
||||
total_cost_estimated, voltage_level, engineering_type, owner_unit,
|
||||
owner_contact, bidding_type, signing_date, start_date, planned_end_date,
|
||||
actual_end_date, warranty_amount, warranty_expiry_date, actual_warranty_refund_date,
|
||||
project_department, project_leader, payment_method, total_cost_control,
|
||||
is_adjusted, labor_cost_control, labor_cost_planned, labor_cost_paid,
|
||||
material_cost_control, material_cost_payable, material_cost_actual,
|
||||
material_cost_paid, other_cost_control, other_cost_payable, other_cost_actual,
|
||||
tax_amount, profit, actual_profit, cost_settlement_amount, cumulative_progress,
|
||||
receivable_amount, invoice_amount, actual_receipt_amount, receipt_completion_rate,
|
||||
payable_amount, actual_payment_amount, unpaid_amount, payment_completion_rate,
|
||||
labor_debt_amount, settlement_cost_amount, settlement_labor_cost,
|
||||
settlement_material_cost, settlement_other_cost, due_settlement_count,
|
||||
unsettlement_count, problems, suggestions, remarks, status, created_by
|
||||
) 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
|
||||
)
|
||||
"""
|
||||
|
||||
# 准备参数
|
||||
params = (
|
||||
clean_text_value(row_data[1]), # project_no
|
||||
clean_text_value(row_data[2]), # power_contract_no
|
||||
clean_text_value(row_data[3]), # name
|
||||
parse_int_value(row_data[4]), # subitem_count
|
||||
clean_text_value(row_data[5]), # subitem_code
|
||||
parse_number_value(row_data[6]), # total_investment
|
||||
parse_number_value(row_data[7]), # contract_amount
|
||||
parse_number_value(row_data[8]), # warranty_ratio
|
||||
parse_number_value(row_data[9]), # settlement_amount
|
||||
parse_number_value(row_data[10]), # total_cost_estimated
|
||||
clean_text_value(row_data[11]), # voltage_level
|
||||
clean_text_value(row_data[12]), # engineering_type
|
||||
clean_text_value(row_data[13]), # owner_unit
|
||||
clean_text_value(row_data[14]), # owner_contact
|
||||
clean_text_value(row_data[15]), # bidding_type
|
||||
parse_date_value(row_data[16], connection._connection.workbook), # signing_date
|
||||
parse_date_value(row_data[17], connection._connection.workbook), # start_date
|
||||
parse_date_value(row_data[18], connection._connection.workbook), # planned_end_date
|
||||
parse_date_value(row_data[19], connection._connection.workbook), # actual_end_date
|
||||
parse_number_value(row_data[20]), # warranty_amount
|
||||
parse_date_value(row_data[21], connection._connection.workbook), # warranty_expiry_date
|
||||
parse_date_value(row_data[22], connection._connection.workbook), # actual_warranty_refund_date
|
||||
clean_text_value(row_data[23]), # project_department
|
||||
clean_text_value(row_data[24]), # project_leader
|
||||
clean_text_value(row_data[25]), # payment_method
|
||||
parse_number_value(row_data[26]), # total_cost_control
|
||||
parse_enum_value(row_data[27], ['是', '否']), # is_adjusted
|
||||
parse_number_value(row_data[28]), # labor_cost_control
|
||||
parse_number_value(row_data[29]), # labor_cost_planned
|
||||
parse_number_value(row_data[30]), # labor_cost_paid
|
||||
parse_number_value(row_data[31]), # material_cost_control
|
||||
parse_number_value(row_data[32]), # material_cost_payable
|
||||
parse_number_value(row_data[33]), # material_cost_actual
|
||||
parse_number_value(row_data[34]), # material_cost_paid
|
||||
parse_number_value(row_data[35]), # other_cost_control
|
||||
parse_number_value(row_data[36]), # other_cost_payable
|
||||
parse_number_value(row_data[37]), # other_cost_actual
|
||||
parse_number_value(row_data[38]), # tax_amount
|
||||
parse_number_value(row_data[39]), # profit
|
||||
parse_number_value(row_data[40]), # actual_profit
|
||||
parse_number_value(row_data[41]), # cost_settlement_amount
|
||||
parse_number_value(row_data[42]), # cumulative_progress
|
||||
parse_number_value(row_data[43]), # receivable_amount
|
||||
parse_number_value(row_data[44]), # invoice_amount
|
||||
parse_number_value(row_data[45]), # actual_receipt_amount
|
||||
parse_number_value(row_data[46]), # receipt_completion_rate
|
||||
parse_number_value(row_data[47]), # payable_amount
|
||||
parse_number_value(row_data[48]), # actual_payment_amount
|
||||
parse_number_value(row_data[49]), # unpaid_amount
|
||||
parse_number_value(row_data[50]), # payment_completion_rate
|
||||
parse_number_value(row_data[51]), # labor_debt_amount
|
||||
parse_number_value(row_data[52]), # settlement_cost_amount
|
||||
parse_number_value(row_data[53]), # settlement_labor_cost
|
||||
parse_number_value(row_data[54]), # settlement_material_cost
|
||||
parse_number_value(row_data[55]), # settlement_other_cost
|
||||
parse_int_value(row_data[56]), # due_settlement_count
|
||||
parse_int_value(row_data[57]), # unsettlement_count
|
||||
clean_text_value(row_data[58]), # problems
|
||||
clean_text_value(row_data[59]), # suggestions
|
||||
clean_text_value(row_data[60]), # remarks
|
||||
'新建', # status (默认新建)
|
||||
admin_id # created_by
|
||||
)
|
||||
|
||||
# 跳过没有项目名称的行
|
||||
if not params[2]:
|
||||
skip_count += 1
|
||||
continue
|
||||
|
||||
cursor.execute(sql, params)
|
||||
success_count += 1
|
||||
|
||||
except Error as e:
|
||||
print(f'第{row_idx+1}行导入错误: {e}', file=sys.stderr)
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
print(f'导入完成: 成功 {success_count} 条, 跳过 {skip_count} 条, 错误 {error_count} 条')
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print('='*60)
|
||||
print('工程项目数据导入脚本')
|
||||
print('='*60)
|
||||
|
||||
# 检查Excel文件是否存在
|
||||
if not os.path.exists(EXCEL_FILE):
|
||||
print(f'错误: Excel文件不存在: {EXCEL_FILE}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 打开Excel文件
|
||||
try:
|
||||
workbook = xlrd.open_workbook(EXCEL_FILE)
|
||||
print(f'已打开Excel文件: {EXCEL_FILE}')
|
||||
print(f'工作表数量: {len(workbook.sheet_names())}')
|
||||
except Exception as e:
|
||||
print(f'错误: 无法打开Excel文件: {e}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 获取数据库连接
|
||||
connection = get_database_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
# 导入主要工作表
|
||||
main_sheets = [
|
||||
'1-1基建',
|
||||
'1-2业扩',
|
||||
'1-3客户',
|
||||
'1-4营销',
|
||||
'2检修、技改、应急抢修项目'
|
||||
]
|
||||
|
||||
for sheet_name in main_sheets:
|
||||
if sheet_name in workbook.sheet_names():
|
||||
sheet = workbook.sheet_by_name(sheet_name)
|
||||
import_projects_from_sheet(sheet, connection, cursor, sheet_name)
|
||||
else:
|
||||
print(f'警告: 工作表不存在: {sheet_name}')
|
||||
|
||||
# 提交事务
|
||||
connection.commit()
|
||||
|
||||
# 显示统计信息
|
||||
cursor.execute("SELECT COUNT(*) FROM projects")
|
||||
total_count = cursor.fetchone()[0]
|
||||
print(f'\n数据库中共有 {total_count} 个项目')
|
||||
|
||||
cursor.execute("SELECT engineering_type, COUNT(*) as count FROM projects GROUP BY engineering_type")
|
||||
print('\n按工程类别统计:')
|
||||
for row in cursor.fetchall():
|
||||
print(f' {row[0]}: {row[1]} 个')
|
||||
|
||||
print('\n' + '='*60)
|
||||
print('数据导入完成!')
|
||||
print('='*60)
|
||||
|
||||
except Exception as e:
|
||||
print(f'错误: {e}', file=sys.stderr)
|
||||
connection.rollback()
|
||||
sys.exit(1)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,225 @@
|
||||
-- 工程项目管理系统 - 数据库初始化脚本
|
||||
-- 基于docs/example.xls中的工程项目管理台账
|
||||
-- 创建时间: 2026-01-25
|
||||
|
||||
-- ========================================
|
||||
-- 1. 创建数据库(如果不存在)
|
||||
-- ========================================
|
||||
CREATE DATABASE IF NOT EXISTS project_manager
|
||||
DEFAULT CHARACTER SET utf8mb4
|
||||
DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE project_manager;
|
||||
|
||||
-- ========================================
|
||||
-- 2. 创建用户表
|
||||
-- ========================================
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
|
||||
username VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
|
||||
password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希',
|
||||
real_name VARCHAR(100) NOT NULL COMMENT '真实姓名',
|
||||
department VARCHAR(50) NOT NULL COMMENT '部门(市场部/技术部/财务部等)',
|
||||
role ENUM('admin', 'market', 'other') NOT NULL COMMENT '角色(admin/market/other)',
|
||||
email VARCHAR(100) UNIQUE COMMENT '邮箱',
|
||||
phone VARCHAR(20) COMMENT '电话',
|
||||
is_active BOOLEAN DEFAULT TRUE COMMENT '是否激活',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX idx_users_username (username),
|
||||
INDEX idx_users_department (department),
|
||||
INDEX idx_users_role (role)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
|
||||
|
||||
-- ========================================
|
||||
-- 3. 创建项目表(基于Excel结构)
|
||||
-- ========================================
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '项目ID',
|
||||
project_no VARCHAR(50) UNIQUE NOT NULL COMMENT '合同编号',
|
||||
power_contract_no VARCHAR(100) COMMENT '供电局项目合同编号',
|
||||
name VARCHAR(200) NOT NULL COMMENT '项目名称',
|
||||
subitem_count INT DEFAULT 0 COMMENT '子项个数',
|
||||
subitem_code VARCHAR(50) COMMENT '子项编码',
|
||||
total_investment DECIMAL(15,2) COMMENT '项目总投资(万元)',
|
||||
contract_amount DECIMAL(15,2) COMMENT '中标合同金额(万元)',
|
||||
warranty_ratio DECIMAL(5,2) COMMENT '质保金比例',
|
||||
settlement_amount DECIMAL(15,2) COMMENT '结算金额(万元)',
|
||||
total_cost_estimated DECIMAL(15,2) COMMENT '总成本测算',
|
||||
voltage_level VARCHAR(50) COMMENT '工程电压等级',
|
||||
engineering_type VARCHAR(50) COMMENT '工程类别(基建/业扩/客户/营销/检修)',
|
||||
owner_unit VARCHAR(200) COMMENT '业主单位',
|
||||
owner_contact VARCHAR(200) COMMENT '业主联系人及电话',
|
||||
bidding_type VARCHAR(50) COMMENT '中标形式',
|
||||
signing_date DATE COMMENT '签订日期',
|
||||
start_date DATE COMMENT '开工日期',
|
||||
planned_end_date DATE COMMENT '计划竣工日期',
|
||||
actual_end_date DATE COMMENT '实际竣工日期',
|
||||
warranty_amount DECIMAL(15,2) DEFAULT 0 COMMENT '质保金(万元)',
|
||||
warranty_expiry_date DATE COMMENT '质保期截止日',
|
||||
actual_warranty_refund_date DATE COMMENT '实际退质保金日期',
|
||||
project_department VARCHAR(100) COMMENT '所属项目部',
|
||||
project_leader VARCHAR(200) COMMENT '项目负责人及电话',
|
||||
payment_method TEXT COMMENT '工程款拨付方式',
|
||||
total_cost_control DECIMAL(15,2) COMMENT '总体成本(控制)',
|
||||
is_adjusted ENUM('是', '否') DEFAULT '否' COMMENT '是否调整',
|
||||
labor_cost_control DECIMAL(15,2) COMMENT '其中:人工成本(控制)',
|
||||
labor_cost_planned DECIMAL(15,2) COMMENT '农民工工资(按进度计划)',
|
||||
labor_cost_paid DECIMAL(15,2) COMMENT '农民工工资(实付)',
|
||||
material_cost_control DECIMAL(15,2) COMMENT '其中:乙供材料费(控制)',
|
||||
material_cost_payable DECIMAL(15,2) COMMENT '应付材料费(按收款比例)',
|
||||
material_cost_actual DECIMAL(15,2) COMMENT '实际发生材料费',
|
||||
material_cost_paid DECIMAL(15,2) COMMENT '实际支付材料费',
|
||||
other_cost_control DECIMAL(15,2) COMMENT '其中:其他费用(控制)',
|
||||
other_cost_payable DECIMAL(15,2) COMMENT '应付其他费',
|
||||
other_cost_actual DECIMAL(15,2) COMMENT '实际其他费用',
|
||||
tax_amount DECIMAL(15,2) COMMENT '税金',
|
||||
profit DECIMAL(15,2) COMMENT '利润(万元)',
|
||||
actual_profit DECIMAL(15,2) COMMENT '实际利润(万元)',
|
||||
cost_settlement_amount DECIMAL(15,2) COMMENT '成本结算金额(万元)',
|
||||
cumulative_progress DECIMAL(5,2) COMMENT '累计进度',
|
||||
receivable_amount DECIMAL(15,2) COMMENT '应收款(完成进度款)',
|
||||
invoice_amount DECIMAL(15,2) COMMENT '开票金额(万元)',
|
||||
actual_receipt_amount DECIMAL(15,2) COMMENT '实际收款金额(万元)',
|
||||
receipt_completion_rate DECIMAL(5,2) COMMENT '实际收款完成率',
|
||||
payable_amount DECIMAL(15,2) COMMENT '应付款金额(万元)',
|
||||
actual_payment_amount DECIMAL(15,2) COMMENT '实际付款金额(万元)',
|
||||
unpaid_amount DECIMAL(15,2) COMMENT '未收款(万元)',
|
||||
payment_completion_rate DECIMAL(5,2) COMMENT '实际付款完成率',
|
||||
labor_debt_amount DECIMAL(15,2) COMMENT '民工工资清欠金额(万元)',
|
||||
settlement_cost_amount DECIMAL(15,2) COMMENT '结算后成本测算金额',
|
||||
settlement_labor_cost DECIMAL(15,2) COMMENT '结算人工费',
|
||||
settlement_material_cost DECIMAL(15,2) COMMENT '结算材料费',
|
||||
settlement_other_cost DECIMAL(15,2) COMMENT '结算其他费',
|
||||
due_settlement_count INT COMMENT '到期应结算项目个数',
|
||||
unsettlement_count INT COMMENT '到期未完成结算个数',
|
||||
problems TEXT COMMENT '存在的问题',
|
||||
suggestions TEXT COMMENT '建议措施',
|
||||
remarks TEXT COMMENT '备注',
|
||||
status VARCHAR(50) DEFAULT '新建' COMMENT '项目状态',
|
||||
created_by INT NOT NULL COMMENT '创建人ID',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT,
|
||||
INDEX idx_projects_project_no (project_no),
|
||||
INDEX idx_projects_engineering_type (engineering_type),
|
||||
INDEX idx_projects_project_department (project_department),
|
||||
INDEX idx_projects_status (status),
|
||||
INDEX idx_projects_created_by (created_by),
|
||||
INDEX idx_projects_signing_date (signing_date),
|
||||
INDEX idx_projects_planned_end_date (planned_end_date),
|
||||
INDEX idx_projects_type_status (engineering_type, status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工程项目表';
|
||||
|
||||
-- ========================================
|
||||
-- 4. 插入初始管理员用户
|
||||
-- ========================================
|
||||
INSERT INTO users (username, password_hash, real_name, department, role, email, phone) VALUES
|
||||
('admin', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyW5q7K1U1.2', '系统管理员', '管理部', 'admin', 'admin@example.com', '13800000000')
|
||||
ON DUPLICATE KEY UPDATE username=username;
|
||||
|
||||
-- ========================================
|
||||
-- 5. 创建视图:项目统计视图
|
||||
-- ========================================
|
||||
CREATE OR REPLACE VIEW project_statistics AS
|
||||
SELECT
|
||||
engineering_type AS 工程类别,
|
||||
COUNT(*) AS 项目总数,
|
||||
SUM(contract_amount) AS 合同总金额,
|
||||
SUM(actual_receipt_amount) AS 实际收款总金额,
|
||||
SUM(actual_payment_amount) AS 实际付款总金额,
|
||||
AVG(receipt_completion_rate) AS 平均收款完成率,
|
||||
AVG(payment_completion_rate) AS 平均付款完成率,
|
||||
SUM(CASE WHEN status = '新建' THEN 1 ELSE 0 END) AS 新建项目,
|
||||
SUM(CASE WHEN status = '进行中' THEN 1 ELSE 0 END) AS 进行中项目,
|
||||
SUM(CASE WHEN status = '已完成' THEN 1 ELSE 0 END) AS 已完成项目
|
||||
FROM projects
|
||||
GROUP BY engineering_type;
|
||||
|
||||
-- ========================================
|
||||
-- 6. 创建视图:项目详情视图
|
||||
-- ========================================
|
||||
CREATE OR REPLACE VIEW project_detail_view AS
|
||||
SELECT
|
||||
p.id,
|
||||
p.project_no,
|
||||
p.name,
|
||||
p.engineering_type,
|
||||
p.contract_amount,
|
||||
p.actual_receipt_amount,
|
||||
p.actual_payment_amount,
|
||||
p.receipt_completion_rate,
|
||||
p.payment_completion_rate,
|
||||
p.status,
|
||||
p.project_department,
|
||||
p.project_leader,
|
||||
p.signing_date,
|
||||
p.planned_end_date,
|
||||
p.actual_end_date,
|
||||
u.real_name AS created_by_name,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM projects p
|
||||
LEFT JOIN users u ON p.created_by = u.id;
|
||||
|
||||
-- ========================================
|
||||
-- 7. 创建存储过程:更新项目收款完成率
|
||||
-- ========================================
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE update_receipt_completion_rate(IN project_id INT)
|
||||
BEGIN
|
||||
UPDATE projects
|
||||
SET receipt_completion_rate = CASE
|
||||
WHEN contract_amount > 0 THEN ROUND(actual_receipt_amount / contract_amount * 100, 2)
|
||||
ELSE 0
|
||||
END
|
||||
WHERE id = project_id;
|
||||
END //
|
||||
DELIMITER ;
|
||||
|
||||
-- ========================================
|
||||
-- 8. 创建存储过程:更新项目付款完成率
|
||||
-- ========================================
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE update_payment_completion_rate(IN project_id INT)
|
||||
BEGIN
|
||||
UPDATE projects
|
||||
SET payment_completion_rate = CASE
|
||||
WHEN payable_amount > 0 THEN ROUND(actual_payment_amount / payable_amount * 100, 2)
|
||||
ELSE 0
|
||||
END
|
||||
WHERE id = project_id;
|
||||
END //
|
||||
DELIMITER ;
|
||||
|
||||
-- ========================================
|
||||
-- 9. 创建触发器:插入项目前自动生成合同编号
|
||||
-- ========================================
|
||||
DELIMITER //
|
||||
CREATE TRIGGER before_project_insert
|
||||
BEFORE INSERT ON projects
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
IF NEW.project_no IS NULL OR NEW.project_no = '' THEN
|
||||
SET NEW.project_no = CONCAT('PRJ', DATE_FORMAT(NOW(), '%Y%m%d'), LPAD((SELECT COUNT(*) + 1 FROM projects), 4, '0'));
|
||||
END IF;
|
||||
END //
|
||||
DELIMITER ;
|
||||
|
||||
-- ========================================
|
||||
-- 10. 创建触发器:更新项目时自动更新时间戳
|
||||
-- ========================================
|
||||
DELIMITER //
|
||||
CREATE TRIGGER before_project_update
|
||||
BEFORE UPDATE ON projects
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
SET NEW.updated_at = NOW();
|
||||
END //
|
||||
DELIMITER ;
|
||||
|
||||
-- ========================================
|
||||
-- 完成
|
||||
-- ========================================
|
||||
-- 数据库初始化完成
|
||||
-- 执行 backend/config/import-excel-data.py 来导入Excel数据
|
||||
@@ -0,0 +1,266 @@
|
||||
# Excel数据导入指南
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本指南说明如何将 `docs/example.xls` 中的工程项目数据导入到数据库中。
|
||||
|
||||
## 2. 准备工作
|
||||
|
||||
### 2.1 安装依赖
|
||||
|
||||
```bash
|
||||
pip install mysql-connector-python xlrd
|
||||
```
|
||||
|
||||
### 2.2 配置数据库
|
||||
|
||||
编辑 `backend/config/.env` 文件,配置数据库连接信息:
|
||||
|
||||
```env
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=rootpassword
|
||||
DB_NAME=project_manager
|
||||
DB_CHARSET=utf8mb4
|
||||
```
|
||||
|
||||
### 2.3 初始化数据库
|
||||
|
||||
```bash
|
||||
cd backend/config
|
||||
mysql -u root -p < init-database.sql
|
||||
```
|
||||
|
||||
## 3. 导入数据
|
||||
|
||||
### 3.1 方法一:使用Python脚本(推荐)
|
||||
|
||||
```bash
|
||||
cd backend/config
|
||||
python3 import-excel-data.py
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
- 确保 `docs/example.xls` 文件存在
|
||||
- 确保数据库连接正常
|
||||
- 确保管理员用户已创建
|
||||
|
||||
### 3.2 方法二:手动导入
|
||||
|
||||
1. 将Excel转换为CSV
|
||||
2. 使用MySQL导入工具
|
||||
3. 手动执行SQL INSERT语句
|
||||
|
||||
## 4. 数据映射
|
||||
|
||||
### 4.1 Excel工作表映射
|
||||
|
||||
| Excel工作表 | 对应的工程类别 |
|
||||
|-----------|---------------|
|
||||
| 1-1基建 | 基建工程 |
|
||||
| 1-2业扩 | 业扩工程 |
|
||||
| 1-3客户 | 客户工程 |
|
||||
| 1-4营销 | 营销工程 |
|
||||
| 2检修、技改、应急抢修项目 | 检修工程 |
|
||||
|
||||
### 4.2 字段映射关系
|
||||
|
||||
详见 `docs/database-design.md` 第4章。
|
||||
|
||||
## 5. 数据验证
|
||||
|
||||
### 5.1 检查导入数量
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM projects;
|
||||
```
|
||||
|
||||
### 5.2 按工程类别统计
|
||||
|
||||
```sql
|
||||
SELECT engineering_type, COUNT(*) as count
|
||||
FROM projects
|
||||
GROUP BY engineering_type;
|
||||
```
|
||||
|
||||
### 5.3 检查关键字段
|
||||
|
||||
```sql
|
||||
-- 检查合同编号为空的项目
|
||||
SELECT * FROM projects WHERE project_no IS NULL OR project_no = '';
|
||||
|
||||
-- 检查项目名称为空的项目
|
||||
SELECT * FROM projects WHERE name IS NULL OR name = '';
|
||||
|
||||
-- 检查金额字段异常的项目
|
||||
SELECT * FROM projects WHERE contract_amount < 0;
|
||||
```
|
||||
|
||||
## 6. 常见问题
|
||||
|
||||
### 6.1 日期格式错误
|
||||
|
||||
**问题**: Excel中的日期格式无法识别
|
||||
|
||||
**解决**:
|
||||
- 检查Excel中的日期格式是否正确
|
||||
- 特殊值如"未到期"、"未开工"会被设置为NULL
|
||||
|
||||
### 6.2 数字格式错误
|
||||
|
||||
**问题**: 数字字段包含非数字字符
|
||||
|
||||
**解决**:
|
||||
- 使用 `parse_number_value()` 函数自动处理
|
||||
- 空值会被设置为NULL
|
||||
|
||||
### 6.3 重复数据
|
||||
|
||||
**问题**: 合同编号重复
|
||||
|
||||
**解决**:
|
||||
- 检查Excel中是否有重复的合同编号
|
||||
- 使用 `INSERT IGNORE` 或 `ON DUPLICATE KEY UPDATE`
|
||||
|
||||
### 6.4 字符编码问题
|
||||
|
||||
**问题**: 中文字符乱码
|
||||
|
||||
**解决**:
|
||||
- 确保数据库使用 utf8mb4 字符集
|
||||
- 确保Python脚本使用正确的编码
|
||||
|
||||
## 7. 更新数据
|
||||
|
||||
### 7.1 完全重新导入
|
||||
|
||||
```bash
|
||||
# 清空现有数据
|
||||
mysql -u root -p project_manager -e "TRUNCATE TABLE projects;"
|
||||
|
||||
# 重新导入
|
||||
cd backend/config
|
||||
python3 import-excel-data.py
|
||||
```
|
||||
|
||||
### 7.2 增量导入
|
||||
|
||||
修改Python脚本,只导入新增的项目:
|
||||
|
||||
```python
|
||||
# 检查项目是否已存在
|
||||
cursor.execute("SELECT id FROM projects WHERE project_no = %s", (project_no,))
|
||||
if cursor.fetchone():
|
||||
continue # 跳过已存在的项目
|
||||
```
|
||||
|
||||
## 8. 自动化部署
|
||||
|
||||
### 8.1 创建部署脚本
|
||||
|
||||
`scripts/deploy.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
echo "开始部署..."
|
||||
|
||||
# 1. 停止服务
|
||||
echo "停止服务..."
|
||||
systemctl stop project-manager-backend
|
||||
|
||||
# 2. 备份数据库
|
||||
echo "备份数据库..."
|
||||
mysqldump -u root -p project_manager > backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
|
||||
# 3. 初始化数据库
|
||||
echo "初始化数据库..."
|
||||
mysql -u root -p < backend/config/init-database.sql
|
||||
|
||||
# 4. 导入Excel数据
|
||||
echo "导入Excel数据..."
|
||||
cd backend/config
|
||||
python3 import-excel-data.py
|
||||
|
||||
# 5. 启动服务
|
||||
echo "启动服务..."
|
||||
systemctl start project-manager-backend
|
||||
|
||||
echo "部署完成!"
|
||||
```
|
||||
|
||||
### 8.2 添加定时任务
|
||||
|
||||
```bash
|
||||
# 每天凌晨2点自动导入数据
|
||||
crontab -e
|
||||
|
||||
# 添加以下行
|
||||
0 2 * * * /path/to/scripts/deploy.sh >> /var/log/project-manager/deploy.log 2>&1
|
||||
```
|
||||
|
||||
## 9. 监控和日志
|
||||
|
||||
### 9.1 导入日志
|
||||
|
||||
```bash
|
||||
# 查看导入日志
|
||||
tail -f /var/log/project-manager/import.log
|
||||
```
|
||||
|
||||
### 9.2 数据完整性检查
|
||||
|
||||
创建定时任务检查数据完整性:
|
||||
|
||||
```sql
|
||||
-- 检查缺失字段
|
||||
SELECT
|
||||
COUNT(*) as missing_contract_no
|
||||
FROM projects
|
||||
WHERE project_no IS NULL OR project_no = '';
|
||||
```
|
||||
|
||||
## 10. 性能优化
|
||||
|
||||
### 10.1 批量插入
|
||||
|
||||
使用批量插入提高性能:
|
||||
|
||||
```python
|
||||
# 一次插入100条记录
|
||||
cursor.executemany(sql, params_list)
|
||||
```
|
||||
|
||||
### 10.2 禁用索引
|
||||
|
||||
导入数据时临时禁用索引:
|
||||
|
||||
```sql
|
||||
ALTER TABLE projects DISABLE KEYS;
|
||||
-- 导入数据
|
||||
ALTER TABLE projects ENABLE KEYS;
|
||||
```
|
||||
|
||||
## 11. 安全注意事项
|
||||
|
||||
### 11.1 数据库权限
|
||||
|
||||
不要使用root用户导入数据,创建专用用户:
|
||||
|
||||
```sql
|
||||
CREATE USER 'import_user'@'localhost' IDENTIFIED BY 'secure_password';
|
||||
GRANT INSERT, SELECT ON project_manager.* TO 'import_user'@'localhost';
|
||||
```
|
||||
|
||||
### 11.2 SQL注入防护
|
||||
|
||||
使用参数化查询,避免SQL注入。
|
||||
|
||||
### 11.3 敏感信息保护
|
||||
|
||||
不要将密码硬编码在脚本中,使用环境变量或配置文件。
|
||||
|
||||
---
|
||||
|
||||
**文档维护**: 本文档由后端程序员维护
|
||||
**更新时间**: 2026-01-25
|
||||
Reference in New Issue
Block a user