完善采购审批流程、企业微信通知和采购专用章
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""Additive legacy SQLite migration. Never rewrites existing order numbers."""
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def upgrade_database(path):
|
||||
path = Path(path)
|
||||
if not path.exists():
|
||||
return
|
||||
with sqlite3.connect(path) as con:
|
||||
tables = {r[0] for r in con.execute("SELECT name FROM sqlite_master WHERE type='table'")}
|
||||
if 'orders' not in tables:
|
||||
return
|
||||
version = con.execute('PRAGMA user_version').fetchone()[0]
|
||||
if version >= 3:
|
||||
return
|
||||
duplicates = con.execute('SELECT order_no, COUNT(*) FROM orders GROUP BY order_no HAVING COUNT(*) > 1').fetchall()
|
||||
if duplicates:
|
||||
raise RuntimeError('升级前请处理重复单号(原数据未修改):%s' % duplicates)
|
||||
backup = path.with_name(path.name + '.pre-upgrade-' + datetime.now().strftime('%Y%m%d-%H%M%S-%f') + '.bak')
|
||||
with sqlite3.connect(backup) as dest:
|
||||
con.backup(dest)
|
||||
con.execute('BEGIN IMMEDIATE')
|
||||
try:
|
||||
if version < 2:
|
||||
upgrade_fixed_amounts(con)
|
||||
merge_approval(con, tables)
|
||||
con.execute('PRAGMA user_version=3')
|
||||
con.commit()
|
||||
except Exception:
|
||||
con.rollback()
|
||||
raise
|
||||
|
||||
|
||||
def upgrade_fixed_amounts(con):
|
||||
additions = {
|
||||
'users': {'session_version': 'INTEGER NOT NULL DEFAULT 1', 'must_change_password': 'BOOLEAN NOT NULL DEFAULT 1'},
|
||||
'orders': {'reviewed_by_id': 'INTEGER', 'approved_by_id': 'INTEGER', 'revision': 'INTEGER NOT NULL DEFAULT 0', 'row_version': 'INTEGER NOT NULL DEFAULT 1'},
|
||||
'approval_logs': {'user_id': 'INTEGER', 'revision': 'INTEGER NOT NULL DEFAULT 0', 'from_status': 'VARCHAR(20)', 'to_status': 'VARCHAR(20)'},
|
||||
}
|
||||
for table, columns in additions.items():
|
||||
existing = {r[1] for r in con.execute('PRAGMA table_info(%s)' % table)}
|
||||
for name, definition in columns.items():
|
||||
if name not in existing:
|
||||
con.execute('ALTER TABLE %s ADD COLUMN %s %s' % (table, name, definition))
|
||||
for table, old, new, places in [
|
||||
('materials', 'price', 'price_units', 4), ('orders', 'total_amount', 'total_cents', 2),
|
||||
('order_items', 'qty', 'qty_units', 4), ('order_items', 'price', 'price_units', 4),
|
||||
('order_items', 'amount', 'amount_cents', 2),
|
||||
]:
|
||||
columns = {r[1] for r in con.execute('PRAGMA table_info(%s)' % table)}
|
||||
if new in columns:
|
||||
continue
|
||||
con.execute('ALTER TABLE %s ADD COLUMN %s INTEGER DEFAULT 0' % (table, new))
|
||||
for row_id, value in con.execute('SELECT id, %s FROM %s' % (old, table)).fetchall():
|
||||
val = Decimal(str(value or 0))
|
||||
if not val.is_finite() or abs(val) * 10 ** places > 2 ** 63 - 1:
|
||||
raise RuntimeError('无法迁移 %s.id=%s 的金额/数量,请检查原始数据' % (table, row_id))
|
||||
scaled = int((val * 10 ** places).quantize(Decimal('1'), rounding=ROUND_HALF_UP))
|
||||
con.execute('UPDATE %s SET %s=? WHERE id=?' % (table, new), (scaled, row_id))
|
||||
con.execute('CREATE UNIQUE INDEX IF NOT EXISTS uq_orders_order_no ON orders(order_no)')
|
||||
|
||||
|
||||
def merge_approval(con, tables):
|
||||
# Check queued tasks against the OLD status before consolidating stages.
|
||||
# Completed deliveries/logs and archived PDF bytes must remain untouched.
|
||||
if 'notifications' in tables:
|
||||
con.execute("""UPDATE notifications SET status='cancelled',
|
||||
last_error='审批流程已合并,旧通知已失效', lease_token=NULL, lease_until=NULL
|
||||
WHERE channel IN ('review','approval') AND status IN ('pending','retry','sending','failed')
|
||||
AND NOT EXISTS (SELECT 1 FROM orders o WHERE o.id=notifications.order_id
|
||||
AND o.status=notifications.expected_status AND o.revision=notifications.revision
|
||||
AND o.status IN ('pending_review','pending_approval'))""")
|
||||
con.execute("""UPDATE notifications SET channel='approval', expected_status='pending_approval',
|
||||
event='待审批(流程已合并)' WHERE channel IN ('review','approval')
|
||||
AND status IN ('pending','retry','sending','failed')""")
|
||||
if 'notification_settings' in tables:
|
||||
con.execute("""UPDATE notification_settings SET approval_webhook=
|
||||
CASE WHEN COALESCE(approval_webhook,'')='' THEN COALESCE(review_webhook,'')
|
||||
ELSE approval_webhook END, review_webhook=''""")
|
||||
con.execute("""UPDATE orders SET status='pending_approval', row_version=row_version+1
|
||||
WHERE status IN ('pending_review','pending_approval')""")
|
||||
con.execute("""UPDATE users SET role='approver', session_version=session_version+1
|
||||
WHERE role='reviewer'""")
|
||||
Reference in New Issue
Block a user