#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""修复：① 取值安全（税号 18 位加右边界断言、名称必须含中文）
        ② 判定安全（无效值不参与判定；金额不自洽降为待核；发票类型归一化；支持票面人工覆盖）
"""
import ast

# ══════════ ① invoice_parse：值批次正则加护栏 ══════════
P = '脚本/invoice_parse.py'
s = open(P, encoding='utf-8').read()
old = """    if not out.get('销方名称'):
        m = re.search(r'([0-9]{20})\\s+(20\\d{2}\\s*年\\s*\\d{1,2}\\s*月\\s*\\d{1,2}\\s*日)'
                      r'\\s+([^\\n]{2,40}?)\\s+([0-9A-Z]{18})\\s+([^\\n]{2,40}?)\\s+([0-9A-Z]{18})', full, re.S)"""
new = """    if not out.get('销方名称'):
        # 护栏：① 税号必须恰为 18 位（右侧加边界断言，否则 20 位发票号码会被截成 18 位误判为税号）
        #       ② 名称必须含中文（公司名/人名），排除把号码或标签当名称
        _CJK = '[\\u4e00-\\u9fa5]'
        m = re.search(r'\\b([0-9]{20})\\b\\s+(20\\d{2}\\s*年\\s*\\d{1,2}\\s*月\\s*\\d{1,2}\\s*日)'
                      r'\\s+(' + _CJK + r'[^\\n]{1,39}?)\\s+([0-9A-Z]{18})(?![0-9A-Z])'
                      r'\\s+(' + _CJK + r'[^\\n]{1,39}?)\\s+([0-9A-Z]{18})(?![0-9A-Z])', full, re.S)"""
assert old in s, 'parse 值批次锚点未命中'
s = s.replace(old, new, 1)

# 名称清洗再加一层：纯数字/标签残留 → None
old2 = """    for k in ('买方名称', '销方名称'):
        v = (out.get(k) or '').strip()
        if len(v) < 3 or v.replace('：', '').replace(':', '').strip in ('名称', '销方', '购方'):
            out[k] = None"""
old2b = """    for k in ('买方名称', '销方名称'):
        v = (out.get(k) or '').strip()
        if len(v) < 3 or v.replace('：', '').replace(':', '').strip() in ('名称', '销方', '购方'):
            out[k] = None"""
new2 = """    import re as _re
    for k in ('买方名称', '销方名称'):
        v = (out.get(k) or '').strip()
        bad = (len(v) < 3
               or v.replace('：', '').replace(':', '').strip() in ('名称', '销方', '购方')
               or _re.fullmatch(r'[0-9A-Z]{8,}', v)
               or any(t in v for t in ('纳税人识别号', '统一社会信用代码', '号码', '开票日期'))
               or not _re.search(r'[\\u4e00-\\u9fa5]', v))
        if bad:
            out[k] = None"""
if old2b in s:
    s = s.replace(old2b, new2, 1)
else:
    assert old2 in s, 'parse 名称清洗锚点未命中'
    s = s.replace(old2, new2, 1)
open(P, 'w', encoding='utf-8').write(s)
ast.parse(s)
print('① ✅ invoice_parse.py 取值护栏已加（税号边界断言 + 名称必须含中文）')

# ══════════ ② invoice_check：判定安全 ══════════
C = '脚本/invoice_check.py'
c = open(C, encoding='utf-8').read()

# 2.1 无效名称守卫 + 票面人工覆盖
old = """def check_record(rec, rules, led=None, peers=None):
    if rec.get('类型') == '附件':
        return {'状态': '附件', '问题': [], '提示': ['配套材料，随发票一并提交']}
    issues, notes, status = [], [], []"""
new = """def _bad_name(v):
    \"\"\"名称字段是否无效（纯号码/标签残留/非中文）→ 视为『未从票面提取到』，不参与判定\"\"\"
    s = str(v or '').strip()
    if len(s) < 3:
        return True
    if re.fullmatch(r'[0-9A-Z]{8,}', s):
        return True
    if any(t in s for t in ('纳税人识别号', '统一社会信用代码', '名称：', '号码')):
        return True
    return not re.search(r'[\\u4e00-\\u9fa5]', s)


def check_record(rec, rules, led=None, peers=None):
    if rec.get('类型') == '附件':
        return {'状态': '附件', '问题': [], '提示': ['配套材料，随发票一并提交']}
    issues, notes, status = [], [], []
    # 票面人工覆盖：rec['票面'] 优先（页面补录后回灌）
    if rec.get('票面'):
        for k, v in (rec['票面'] or {}).items():
            if v not in (None, ''):
                rec[k] = v
        rec['_票面已人工补录'] = True"""
assert old in c, 'check 头部锚点未命中'
c = c.replace(old, new, 1)

# 2.2 抬头/税号：无效值不判定
old = """    ci = rules.get('公司开票信息') or {}
    if core.is_configured(ci.get('名称')):
        if rec.get('买方名称') and ci['名称'].strip() not in str(rec['买方名称']):
            status.append('异常')
            issues.append('抬头不符（票面：%s）→ 须重开' % rec['买方名称'])
    else:
        notes.append('规则未配置：公司抬头')
    if core.is_configured(ci.get('税号')):
        if rec.get('买方税号') and str(rec['买方税号']).strip().upper() != str(ci['税号']).strip().upper():
            status.append('异常')
            issues.append('税号不符（票面：%s）→ 须重开' % rec['买方税号'])
    else:
        notes.append('规则未配置：公司税号')"""
new = """    ci = rules.get('公司开票信息') or {}
    if core.is_configured(ci.get('名称')):
        if _bad_name(rec.get('买方名称')):
            status.append('待补')
            issues.append('票面抬头未能可靠提取 → 请在页面补录「买方名称」后重判')
        elif ci['名称'].strip() not in str(rec['买方名称']):
            status.append('异常')
            issues.append('抬头不符（票面：%s）→ 须重开' % rec['买方名称'])
    else:
        notes.append('规则未配置：公司抬头')
    bt = str(rec.get('买方税号') or '').strip()
    if core.is_configured(ci.get('税号')):
        if not re.fullmatch(r'[0-9A-Z]{18}', bt):
            status.append('待补')
            issues.append('票面税号未能可靠提取 → 请在页面补录「买方税号」后重判')
        elif bt.upper() != str(ci['税号']).strip().upper():
            status.append('异常')
            issues.append('税号不符（票面：%s）→ 须重开' % bt)
    else:
        notes.append('规则未配置：公司税号')"""
assert old in c, 'check 抬头锚点未命中'
c = c.replace(old, new, 1)

# 2.3 金额不自洽：降级为待核（解析疑点，不是票面违规）
old = """        if amt is not None and tax is not None and abs((amt + tax) - total) > 0.02:
            status.append('异常')
            issues.append('金额不自洽（%.2f + %.2f ≠ %.2f）' % (amt, tax, total))"""
new = """        if amt is not None and tax is not None and abs((amt + tax) - total) > 0.02:
            # 发票「金额+税额」必然等于价税合计；不等几乎都是提取误差 → 待核，不判异常
            status.append('待核')
            notes.append('金额/税额与合计不一致（%.2f + %.2f ≠ %.2f），疑为提取误差，请核对票面'
                         % (amt, tax, total))"""
assert old in c, 'check 自洽锚点未命中'
c = c.replace(old, new, 1)

# 2.4 发票类型归一化 + 宽松白名单
old = """    t = str(rec.get('发票类型') or '')
    if t:
        if any(d and d in t for d in deny):
            status.append('异常')
            issues.append('发票类型不可报：%s' % t)
        elif allow and not any(a and (a in t or t in a) for a in allow):
            status.append('待核')
            issues.append('发票类型不在允许清单：%s' % t)"""
new = """    t = re.sub(r'\\s+', '', str(rec.get('发票类型') or ''))   # OCR 常把换行/空格插进类型名
    if t:
        if any(d and re.sub(r'\\s+', '', d) in t for d in deny):
            status.append('异常')
            issues.append('发票类型不可报：%s' % t)
        else:
            # 宽松白名单：命中任一关键类别即视为允许（OCR 误字如「普→晋」不应误报）
            keys = ['电子发票', '增值税', '数电票', '铁路电子客票', '航空运输电子客票', '行程单']
            if not any(k in t for k in keys):
                status.append('待核')
                issues.append('发票类型不在允许清单：%s' % t)"""
assert old in c, 'check 类型锚点未命中'
c = c.replace(old, new, 1)
open(C, 'w', encoding='utf-8').write(c)
ast.parse(c)
print('② ✅ invoice_check.py 判定安全已加固（无效值不判定/自洽降级/类型归一化/支持票面覆盖）')
