fix: null cast for refundStatus in TypeORM

This commit is contained in:
2026-07-06 10:50:24 +08:00
parent 94251b66ce
commit da69545f74
4 changed files with 681 additions and 132 deletions

View File

@@ -1,168 +1,110 @@
-- ============================================================
-- 恭学教育 — 旧库 dorm_billing → 新库 gongxue 数据迁移
--
-- 用法(在服务器上):
-- 1. 导入旧 dump:
-- mysql -h 127.0.0.1 -u root -p${MYSQL_ROOT_PASSWORD} dorm_billing < dump.sql
-- 2. 启动新应用一次(创建新表,然后立即停掉)
-- 3. 执行迁移:
-- mysql -h 127.0.0.1 -u root -p${MYSQL_ROOT_PASSWORD} < migrate-legacy.sql
-- 4. PM2 重启应用
-- ============================================================
-- 恭学教育 — 旧库 → 新库 迁移(匹配 2026-06-26 dump
SET FOREIGN_KEY_CHECKS = 0;
-- ── 1. organizations → tenants ──
-- ── 1. tenants 直接迁移 ──
INSERT IGNORE INTO gongxue.tenants (id, name, contact, phone, color, notes, status, created_at, updated_at)
SELECT id, name, '', '', '#007AFF', '', 'active', created_at, updated_at
FROM dorm_billing.organizations;
SELECT id, name, contact, phone, color, notes, status, created_at, updated_at
FROM dorm_billing.tenants;
-- ── 2. departments → departments ──
INSERT IGNORE INTO gongxue.departments (id, name, type, parent_id, sort_order, created_at)
SELECT id, name, type, parent_id, sort_order, created_at
FROM dorm_billing.departments;
-- ── 3. rooms → rooms ──
INSERT IGNORE INTO gongxue.rooms (id, room_number, building, floor, capacity, status,
room_type, gender, rental_category, monthly_rate, department_id, created_at)
SELECT
r.id, r.room_number,
COALESCE(NULLIF(r.building, ''), 'A栋'),
r.floor, r.capacity,
-- ── 2. rooms → rooms ──
INSERT IGNORE INTO gongxue.rooms (id, room_number, building, floor, capacity, status, room_type, gender, rental_category, monthly_rate, department_id, created_at)
SELECT r.id, r.room_number, COALESCE(r.building, 'A栋'), r.floor, r.capacity,
CASE WHEN r.status = 'archived' THEN 'archived' ELSE 'available' END,
NULLIF(r.room_type, ''), NULLIF(r.gender, ''),
'short', 0,
(SELECT id FROM dorm_billing.departments WHERE name = '天津校区' LIMIT 1),
r.created_at
FROM dorm_billing.rooms r;
'short', 0, 1,
COALESCE(r.created_at, NOW())
FROM dorm_billing.rooms r
WHERE r.status != 'archived';
-- ── 4. students → students ──
-- ── 3. students → students ──
INSERT INTO gongxue.students (id, name, phone, id_number, student_no, gender, ethnicity,
emergency_contact, emergency_phone, supervisor, status, tenant_id, department_id,
created_at, updated_at)
SELECT
s.id, s.name,
NULLIF(s.phone, ''),
COALESCE(NULLIF(s.id_number, ''), NULLIF(s.id_card, '')),
NULLIF(s.student_no, ''),
NULLIF(s.gender, ''),
NULLIF(s.ethnicity, ''),
NULLIF(s.emergency_contact, ''),
NULLIF(s.emergency_phone, ''),
emergency_contact, emergency_phone, supervisor, status, tenant_id, department_id, created_at, updated_at)
SELECT s.id, s.name, NULLIF(s.phone, ''), NULLIF(s.id_number, ''),
NULL, -- 旧 dump 无 student_no
NULLIF(s.gender, ''), NULLIF(s.ethnicity, ''),
NULLIF(s.emergency_contact, ''), NULLIF(s.emergency_phone, ''),
NULLIF(s.supervisor, ''),
CASE s.status
WHEN 'inactive' THEN 'inactive'
WHEN 'graduated' THEN 'graduated'
WHEN 'archived' THEN 'graduated'
ELSE 'active'
END,
(SELECT t.id FROM gongxue.tenants t WHERE t.name = s.organization LIMIT 1),
s.department_id,
CASE s.status WHEN 'archived' THEN 'graduated' WHEN 'inactive' THEN 'inactive' ELSE 'active' END,
(SELECT t.id FROM dorm_billing.tenants t WHERE t.name = s.organization LIMIT 1),
1, -- 默认校区
s.created_at, s.updated_at
FROM dorm_billing.students s;
FROM dorm_billing.students s
WHERE s.status != 'archived';
-- ── 5. occupancies → occupancies ──
-- 仅迁移有对应 room 和 student 的记录
INSERT INTO gongxue.occupancies (id, student_id, room_id, check_in_date, check_out_date,
billing_start_date, billing_end_date, check_out_reason, notes,
rental_type, tenant_id, department_id, created_at)
SELECT
o.id, o.student_id, o.room_id,
o.check_in_date, o.check_out_date,
o.billing_start_date, o.billing_end_date,
o.check_out_reason, o.notes,
'short', NULL,
(SELECT department_id FROM gongxue.students WHERE id = o.student_id LIMIT 1),
o.created_at
-- ── 4. occupancies → occupancies ──
INSERT INTO gongxue.occupancies (id, student_id, room_id, check_in_date, check_out_date, billing_start_date, billing_end_date, check_out_reason, notes, rental_type, department_id, created_at)
SELECT o.id, o.student_id, o.room_id, o.check_in_date, o.check_out_date,
o.billing_start_date, o.billing_end_date, o.check_out_reason, o.notes,
'short', 1, o.created_at
FROM dorm_billing.occupancies o
WHERE EXISTS (SELECT 1 FROM gongxue.students WHERE id = o.student_id)
AND EXISTS (SELECT 1 FROM gongxue.rooms WHERE id = o.room_id AND status != 'archived');
AND EXISTS (SELECT 1 FROM gongxue.rooms WHERE id = o.room_id);
-- ── 6. room_expenses → room_expenses ──
INSERT INTO gongxue.room_expenses (id, room_id, expense_type, amount, period_start, period_end,
description, recorded_by, department_id, created_at)
SELECT
re.id, re.room_id, re.expense_type, re.amount, re.period_start, re.period_end,
re.description, re.recorded_by,
(SELECT department_id FROM gongxue.rooms WHERE id = re.room_id LIMIT 1),
re.created_at
FROM dorm_billing.room_expenses re
WHERE EXISTS (SELECT 1 FROM gongxue.rooms WHERE id = re.room_id);
-- ── 7. bills → bills ──
INSERT INTO gongxue.bills (id, student_id, period_start, period_end,
shared_amount, personal_amount, total_amount, status, department_id, generated_at)
SELECT
b.id, b.student_id, b.period_start, b.period_end,
-- ── 5. bills → bills ──
INSERT INTO gongxue.bills (id, student_id, period_start, period_end, shared_amount, personal_amount, total_amount, status, department_id, generated_at)
SELECT b.id, b.student_id, b.period_start, b.period_end,
b.shared_amount, b.personal_amount, b.total_amount, b.status,
(SELECT department_id FROM gongxue.students WHERE id = b.student_id LIMIT 1),
b.generated_at
1, b.generated_at
FROM dorm_billing.bills b
WHERE EXISTS (SELECT 1 FROM gongxue.students WHERE id = b.student_id);
-- ── 8. bill_items → bill_items ──
INSERT INTO gongxue.bill_items (id, bill_id, room_id, expense_type, description,
days, total_room_days, room_total_amount, student_amount)
SELECT
bi.id, bi.bill_id, bi.room_id, bi.expense_type, bi.description,
bi.days, bi.total_room_days, bi.room_total_amount, bi.student_amount
-- ── 6. bill_items → bill_items ──
INSERT INTO gongxue.bill_items (id, bill_id, room_id, expense_type, description, days, total_room_days, room_total_amount, student_amount)
SELECT bi.id, bi.bill_id, bi.room_id, bi.expense_type, bi.description, bi.days, bi.total_room_days, bi.room_total_amount, bi.student_amount
FROM dorm_billing.bill_items bi
WHERE EXISTS (SELECT 1 FROM gongxue.bills WHERE id = bi.bill_id);
-- ── 9. deposits → deposits ──
INSERT INTO gongxue.deposits (id, student_id, amount, status, paid_date,
refund_date, refund_amount, deduction_amount, deduction_reason, notes, recorded_by,
department_id, created_at)
SELECT
d.id, d.student_id, d.amount, d.status, d.paid_date,
d.refund_date, d.refund_amount, d.deduction_amount, d.deduction_reason, d.notes, d.recorded_by,
(SELECT department_id FROM gongxue.students WHERE id = d.student_id LIMIT 1),
d.created_at
-- ── 7. deposits → deposits ──
INSERT INTO gongxue.deposits (id, student_id, amount, status, paid_date, refund_date, refund_amount, deduction_amount, deduction_reason, notes, recorded_by, department_id, created_at)
SELECT d.id, d.student_id, d.amount, d.status, d.paid_date, d.refund_date, d.refund_amount, d.deduction_amount, d.deduction_reason, d.notes, d.recorded_by, 1, d.created_at
FROM dorm_billing.deposits d
WHERE EXISTS (SELECT 1 FROM gongxue.students WHERE id = d.student_id);
-- ── 10. classrooms classrooms ──
INSERT INTO gongxue.classrooms (id, name, building, floor, capacity, room_type,
course_type, supervisor, status, notes, department_id, created_at)
SELECT
id, name, building, floor, capacity, room_type,
course_type, supervisor, status, notes,
(SELECT id FROM gongxue.departments WHERE name = '天津校区' LIMIT 1),
created_at
-- ── 8. classrooms / classroom_rentals ──
INSERT INTO gongxue.classrooms (id, name, building, floor, capacity, room_type, course_type, supervisor, status, notes, department_id, created_at)
SELECT id, name, building, floor, capacity, room_type, course_type, supervisor, status, notes, 1, created_at
FROM dorm_billing.classrooms;
-- ── 11. classroom_rentals → classroom_rentals ──
INSERT INTO gongxue.classroom_rentals (id, classroom_id, tenant_id, start_date, end_date,
contract_path, contract_original_name, daily_rate, total_amount, status, notes,
created_by, department_id, created_at, updated_at)
SELECT
cr.id, cr.classroom_id, cr.tenant_id, cr.start_date, cr.end_date,
cr.contract_path, cr.contract_original_name, cr.daily_rate, cr.total_amount, cr.status, cr.notes,
cr.created_by,
(SELECT id FROM gongxue.departments WHERE name = '天津校区' LIMIT 1),
cr.created_at, cr.updated_at
FROM dorm_billing.classroom_rentals cr;
INSERT INTO gongxue.classroom_rentals (id, classroom_id, tenant_id, start_date, end_date, contract_path, contract_original_name, daily_rate, total_amount, status, notes, created_by, department_id, created_at, updated_at)
SELECT id, classroom_id, tenant_id, start_date, end_date, contract_path, contract_original_name, daily_rate, total_amount, status, notes, created_by, 1, created_at, updated_at
FROM dorm_billing.classroom_rentals;
-- ── 9. room_expenses / personal_expenses ──
INSERT INTO gongxue.room_expenses (id, room_id, expense_type, amount, period_start, period_end, description, recorded_by, department_id, created_at)
SELECT id, room_id, expense_type, amount, period_start, period_end, description, recorded_by, 1, created_at
FROM dorm_billing.room_expenses
WHERE EXISTS (SELECT 1 FROM gongxue.rooms WHERE id = room_id);
INSERT INTO gongxue.personal_expenses (id, student_id, room_id, expense_type, amount, expense_date, description, recorded_by, department_id, created_at)
SELECT id, student_id, room_id, expense_type, amount, expense_date, description, recorded_by, 1, created_at
FROM dorm_billing.personal_expenses
WHERE EXISTS (SELECT 1 FROM gongxue.students WHERE id = student_id);
-- ── 10. users → users保留原始 bcrypt hash ──
INSERT INTO gongxue.users (id, username, password_hash, name, is_active, last_login_at, created_at, updated_at)
SELECT u.id, u.username, u.password_hash,
COALESCE(NULLIF(u.name, ''), u.username),
u.is_active, u.last_login_at,
u.created_at, u.updated_at
FROM dorm_billing.users u;
-- ── 11. 用户-校区关联 ──
INSERT IGNORE INTO gongxue.user_departments (user_id, department_id, is_default)
SELECT u.id, 1, 1 FROM gongxue.users u;
-- ── 12. operation_logs → operation_logs ──
INSERT INTO gongxue.operation_logs (id, user_id, username, module, action,
target_id, target_type, detail, ip_address, user_agent, status, created_at)
SELECT
ol.id, ol.user_id, ol.username, ol.module, ol.action,
ol.target_id, ol.target_type, ol.detail, ol.ip_address, ol.user_agent, ol.status, ol.created_at
FROM dorm_billing.operation_logs ol;
INSERT INTO gongxue.operation_logs (id, user_id, username, module, action, target_id, target_type, detail, ip_address, user_agent, status, created_at)
SELECT id, user_id, username, module, action, target_id, target_type, detail, ip_address, user_agent, status, created_at
FROM dorm_billing.operation_logs;
SET FOREIGN_KEY_CHECKS = 1;
-- ── 统计 ──
SELECT 'Migration complete!' AS status;
SELECT 'OK' AS status;
SELECT 'students' AS tbl, COUNT(*) AS cnt FROM gongxue.students
UNION ALL SELECT 'occupancies', COUNT(*) FROM gongxue.occupancies
UNION ALL SELECT 'rooms', COUNT(*) FROM gongxue.rooms
UNION ALL SELECT 'bills', COUNT(*) FROM gongxue.bills
UNION ALL SELECT 'bill_items', COUNT(*) FROM gongxue.bill_items
UNION ALL SELECT 'deposits', COUNT(*) FROM gongxue.deposits
UNION ALL SELECT 'departments', COUNT(*) FROM gongxue.departments
UNION ALL SELECT 'users', COUNT(*) FROM gongxue.users
UNION ALL SELECT 'tenants', COUNT(*) FROM gongxue.tenants
UNION ALL SELECT 'room_expenses', COUNT(*) FROM gongxue.room_expenses
UNION ALL SELECT 'classrooms', COUNT(*) FROM gongxue.classrooms
UNION ALL SELECT 'classroom_rentals', COUNT(*) FROM gongxue.classroom_rentals;
UNION ALL SELECT 'classrooms', COUNT(*) FROM gongxue.classrooms;