gaoluyang
14 小时以前 e40a608ef9bf51db294d8eefada6a8e2e80cebca
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import sys, json, csv, argparse
from collections import defaultdict, Counter
from pathlib import Path
from datetime import datetime
 
sys.stdout.reconfigure(encoding='utf-8')
ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(ROOT.parent / 'iqc-import-20261012/deps'))
import pymysql
 
FIELDS = ('mobile', 'telephone', 'email')
POSITION_RANK = {
    '总经理': 50,
    '采购经理': 40, '供应链经理': 40, '商品部经理': 40,
    '采购主管': 35, '商务主管': 35,
    '店长': 30, '门店经理': 30, '药房负责人': 30,
    '办公室主任': 25, '财务负责人': 25, '质量负责人': 25, '库房主管': 25,
    '采购员': 10,
}
RULE = '关键决策人 > 职位层级 > 同客户直属下属数量 > 联系方式完整度 > 最近跟进时间 > 最近更新时间 > 联系人ID升序;三字段取同一联系人,空值保留客户原值'
 
def clean(value):
    return value.strip() if isinstance(value, str) else value
 
def rank(contact, children):
    return (int(contact.get('master') == 1), POSITION_RANK.get(clean(contact.get('post')), 0),
            children[contact['id']], sum(bool(clean(contact.get(field))) for field in FIELDS),
            contact.get('contact_last_time') or datetime.min,
            contact.get('update_time') or datetime.min, -contact['id'])
 
def build_plan(customers, contacts):
    grouped = defaultdict(list)
    for contact in contacts:
        grouped[contact['customer_id']].append(contact)
    result = []
    for customer in customers:
        candidates = grouped.get(customer['id'], [])
        if not candidates:
            continue
        candidate_ids = {c['id'] for c in candidates}
        children = Counter(c['parent_id'] for c in candidates if c['parent_id'] in candidate_ids)
        chosen = max(candidates, key=lambda c: rank(c, children))
        values = {field: clean(chosen[field]) if clean(chosen.get(field)) else customer[field] for field in FIELDS}
        changed = [field for field in FIELDS if values[field] != customer[field]]
        result.append({'customer_id': customer['id'], 'customer_name': customer['name'],
                       'contact_id': chosen['id'], 'contact_name': chosen['name'],
                       'master': chosen['master'], 'post': chosen['post'], 'candidate_count': len(candidates),
                       'old': {field: customer[field] for field in FIELDS}, 'new': values, 'changed': changed})
    return result
 
def write_json(path, value):
    path.write_text(json.dumps(value, ensure_ascii=False, default=str, indent=2), encoding='utf-8')
 
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--apply', action='store_true')
    options = parser.parse_args()
    password = sys.stdin.readline().strip()
    args = dict(host='36.213.150.237', port=10000, user='root', password=password,
                database='mom-hm', charset='utf8mb4', connect_timeout=15, read_timeout=60,
                cursorclass=pymysql.cursors.DictCursor, autocommit=False)
    connection = pymysql.connect(**args)
    try:
        with connection.cursor() as cursor:
            cursor.execute('SELECT DATABASE() AS db, NOW() AS now')
            state = cursor.fetchone()
            assert state['db'] == 'mom-hm'
            lock = ' FOR UPDATE' if options.apply else ''
            # Lock both source and target to keep selection and writes consistent.
            cursor.execute('SELECT * FROM crm_customer ORDER BY id' + lock)
            all_customers = cursor.fetchall()
            cursor.execute('SELECT * FROM crm_contact ORDER BY id' + lock)
            all_contacts = cursor.fetchall()
            customers = [row for row in all_customers if row['deleted'] == 0]
            customer_ids = {row['id'] for row in customers}
            contacts = [row for row in all_contacts if row['deleted'] == 0 and row['customer_id'] in customer_ids]
            plan = build_plan(customers, contacts)
            key_counts = Counter(row['customer_id'] for row in contacts if row['master'] == 1)
            changes = [row for row in plan if row['changed']]
            summary = {'database': 'mom-hm', 'rule': RULE, 'active_customers': len(customers),
                       'active_contacts': sum(row['deleted'] == 0 for row in all_contacts),
                       'matched_customers': len(plan), 'multiple_contact_customers': sum(row['candidate_count'] > 1 for row in plan),
                       'updated_customers': len(changes),
                       'field_updates': {field: sum(field in row['changed'] for row in changes) for field in FIELDS},
                       'selected_positions': dict(Counter(row['post'] for row in plan)),
                       'selected_key_decision_makers': sum(row['master'] == 1 for row in plan)}
            summary['customers_with_multiple_key_decision_makers'] = sum(count > 1 for count in key_counts.values())
            if not options.apply:
                write_json(ROOT / 'preview.json', {'summary': summary, 'plan': plan})
                connection.rollback()
                print(json.dumps(summary, ensure_ascii=False))
                return
            stamp = state['now'].strftime('%Y%m%d-%H%M%S')
            backup = ROOT / ('backup-' + stamp + '.json')
            write_json(backup, {'at': state['now'], 'database': state['db'], 'rule': RULE,
                                'customers': all_customers, 'contacts': all_contacts, 'plan': plan})
            for row in changes:
                cursor.execute('UPDATE crm_customer SET mobile=%s, telephone=%s, email=%s, updater=%s, update_time=%s WHERE id=%s AND deleted=0',
                               tuple(row['new'][field] for field in FIELDS) + ('crm-contact-sync-20261013', state['now'], row['customer_id']))
                assert cursor.rowcount == 1
            cursor.execute('SELECT * FROM crm_customer ORDER BY id')
            after = {row['id']: row for row in cursor.fetchall()}
            expected = {row['customer_id']: row for row in changes}
            assert len(after) == len(all_customers)
            for original in all_customers:
                current = after[original['id']]
                allowed = set(FIELDS) | {'updater', 'update_time'} if original['id'] in expected else set()
                assert all(current[key] == value for key, value in original.items() if key not in allowed)
                if original['id'] in expected:
                    assert all(current[field] == expected[original['id']]['new'][field] for field in FIELDS)
            cursor.execute('SELECT * FROM crm_contact ORDER BY id')
            assert cursor.fetchall() == all_contacts
            connection.commit()
            summary.update({'committed': True, 'executed_at': str(state['now']), 'backup': str(backup)})
        # Reconnect to verify committed values, including unmatched and deleted customer rows.
        with pymysql.connect(**args) as verify_connection:
            with verify_connection.cursor() as cursor:
                cursor.execute('SELECT * FROM crm_customer ORDER BY id')
                assert {row['id']: row for row in cursor.fetchall()} == after
        summary['verified_after_commit'] = True
        write_json(ROOT / 'result.json', summary)
        with (ROOT / '同步明细.csv').open('w', encoding='utf-8-sig', newline='') as file:
            writer = csv.writer(file)
            writer.writerow(['客户ID', '客户', '联系人ID', '选中联系人', '关键决策人', '职位', '候选人数',
                             '原手机号', '同步后手机号', '原电话', '同步后电话', '原邮箱', '同步后邮箱', '变更字段'])
            for row in plan:
                writer.writerow([row['customer_id'], row['customer_name'], row['contact_id'], row['contact_name'],
                                 row['master'], row['post'], row['candidate_count'],
                                 *[value for field in FIELDS for value in (row['old'][field], row['new'][field])],
                                 ','.join(row['changed'])])
        print(json.dumps(summary, ensure_ascii=False))
    except Exception:
        connection.rollback()
        raise
    finally:
        connection.close()
 
if __name__ == '__main__':
    main()