Parcourir la source

相关数据脚本同步更改

zhaobo il y a 1 mois
Parent
commit
96b33e5b95
7 fichiers modifiés avec 838 ajouts et 91 suppressions
  1. 47 0
      check_data.py
  2. 22 6
      jobs/clinic_log.py
  3. 130 61
      jobs/consumption.py
  4. 41 24
      jobs/grossprofit.py
  5. 165 0
      recreate_tables.sql
  6. 116 0
      test.sql
  7. 317 0
      需求.md

+ 47 - 0
check_data.py

@@ -0,0 +1,47 @@
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+from db.pool import get_mysql_conn, get_doris_conn
+
+def check():
+    mysql_conn = get_mysql_conn()
+    doris_conn = get_doris_conn()
+    
+    mysql_cursor = mysql_conn.cursor()
+    doris_cursor = doris_conn.cursor()
+    
+    sdate = "2026-04-01 00:00:00"
+    edate = "2026-05-01 00:00:00"
+    group_id = 1
+    
+    # Check MySQL
+    sql = """
+        SELECT count(*) as cnt
+        FROM fee_incomedetail x
+        JOIN clinic_clinic c ON c.id = x.clinic_id
+        WHERE x.created >= %s AND x.created < %s
+          AND x.group_id = %s
+          AND x.visual = 1 AND x.org_id != 16
+          AND (x.flag IN (4, 5) OR (x.flag IN (1, 2, 6, 7, 8, 9, 10, 11, 12) AND x.should_amt != 0))
+    """
+    mysql_cursor.execute(sql, (sdate, edate, group_id))
+    mysql_res = mysql_cursor.fetchone()
+    print(f"MySQL count for group {group_id} between {sdate} and {edate}: {mysql_res['cnt']}")
+    
+    # Check Doris
+    doris_sql = """
+        SELECT count(*) as cnt
+        FROM fact_fee_grossprofit
+        WHERE pay_time >= %s AND pay_time < %s AND group_id = %s
+    """
+    doris_cursor.execute(doris_sql, (sdate, edate, group_id))
+    doris_res = doris_cursor.fetchone()
+    print(f"Doris count for group {group_id} between {sdate} and {edate}: {doris_res['cnt']}")
+    
+    mysql_cursor.close()
+    doris_cursor.close()
+    mysql_conn.close()
+    doris_conn.close()
+
+if __name__ == "__main__":
+    check()

+ 22 - 6
jobs/clinic_log.py

@@ -57,6 +57,8 @@ def run(start_date=None, end_date=None, group_id=1):
             c.accept_doctor_id,
             c.accept_optometrist_id,
             c.accept_assistant_id,
+            c.customer_manager_staff_id,
+            c.customer_optometrist_staff_id,
             c.come_time,
             c.patient_id,
             o.id as org_id,
@@ -67,7 +69,7 @@ def run(start_date=None, end_date=None, group_id=1):
         JOIN patient_custome p ON c.patient_id = p.id
         JOIN org_org o ON c.org_id = o.id
         LEFT JOIN org_group g ON o.group_id = g.id
-        WHERE c.reg_status IN (1, 2, 3, 4)
+        WHERE c.reg_status IN (2, 3, 4)
             AND c.pno = ''
             AND o.is_effective = 1
             AND o.group_id = %(group_id)s
@@ -99,6 +101,10 @@ def run(start_date=None, end_date=None, group_id=1):
             staff_id_list.append(d["accept_optometrist_id"])
         if d.get("accept_assistant_id"):
             staff_id_list.append(d["accept_assistant_id"])
+        if d.get("customer_manager_staff_id"):
+            staff_id_list.append(d["customer_manager_staff_id"])
+        if d.get("customer_optometrist_staff_id"):
+            staff_id_list.append(d["customer_optometrist_staff_id"])
 
     staff_id_list = list(set(staff_id_list))
 
@@ -106,13 +112,13 @@ def run(start_date=None, end_date=None, group_id=1):
     staff_dict = {}
     if staff_id_list:
         sql = """
-            SELECT s.id, u.name
+            SELECT s.id, u.name, s.qywx_userid, u.mobile
             FROM staff_staff s
             JOIN users_userprofile u ON s.user_id = u.id
             WHERE s.id IN %(ids)s
         """
         cursor.execute(sql, {"ids": staff_id_list})
-        staff_dict = {s["id"]: s["name"] for s in cursor.fetchall()}
+        staff_dict = {s["id"]: s for s in cursor.fetchall()}
 
     # 5. 查询咨询信息
     recode_dict = {}
@@ -230,6 +236,8 @@ def run(start_date=None, end_date=None, group_id=1):
         "r_nakesight", "l_nakesight", "org_id", "org_name", "appoint_time", "add_user",
         "group_id", "group_name", "matter", "accept_doctor_id", "accept_doctor_name", "accept_optometrist_id",
         "accept_optometrist_name", "accept_assistant_id", "accept_assistant_name",
+        "manager_id", "manager_name", "manager_qywx_id", "manager_mobile",
+        "optometrist_id", "optometrist_name", "optometrist_qywx_id", "optometrist_mobile",
         "come_time", "this_come_number", "inviter", "inviter_phone", "channel",
         "collect_date", "activity_location", "extension_user", "extension_user_mobile",
     ]
@@ -362,11 +370,19 @@ def run(start_date=None, end_date=None, group_id=1):
             safe_str(data.get("group_name")),
             safe_str(data.get("matter")),
             safe_int(data.get("accept_doctor_id")),
-            safe_str(staff_dict.get(data.get("accept_doctor_id"), "")),
+            safe_str(staff_dict.get(data.get("accept_doctor_id"), {}).get("name", "")),
             safe_int(data.get("accept_optometrist_id")),
-            safe_str(staff_dict.get(data.get("accept_optometrist_id"), "")),
+            safe_str(staff_dict.get(data.get("accept_optometrist_id"), {}).get("name", "")),
             safe_int(data.get("accept_assistant_id")),
-            safe_str(staff_dict.get(data.get("accept_assistant_id"), "")),
+            safe_str(staff_dict.get(data.get("accept_assistant_id"), {}).get("name", "")),
+            safe_int(data.get("customer_manager_staff_id")),
+            safe_str(staff_dict.get(data.get("customer_manager_staff_id"), {}).get("name", "")),
+            safe_str(staff_dict.get(data.get("customer_manager_staff_id"), {}).get("qywx_userid", "")),
+            safe_str(staff_dict.get(data.get("customer_manager_staff_id"), {}).get("mobile", "")),
+            safe_int(data.get("customer_optometrist_staff_id")),
+            safe_str(staff_dict.get(data.get("customer_optometrist_staff_id"), {}).get("name", "")),
+            safe_str(staff_dict.get(data.get("customer_optometrist_staff_id"), {}).get("qywx_userid", "")),
+            safe_str(staff_dict.get(data.get("customer_optometrist_staff_id"), {}).get("mobile", "")),
             come_time,
             safe_int(new_old_info.get("this_come_number")) if new_old_info.get("this_come_number") else 1,
             safe_str(data.get("inviter")),

+ 130 - 61
jobs/consumption.py

@@ -64,6 +64,7 @@ def run(start_date=None, end_date=None, group_id=1):
             x.coupon_name,
             x.sign_wxapp,
             x.add_manager,
+            x.not_transaction_reason,
             c.org_id,
             c.customer_manager_staff_id,
             c.customer_optometrist_staff_id,
@@ -80,7 +81,7 @@ def run(start_date=None, end_date=None, group_id=1):
             x.act_opt_name, x.matter, x.type, x.come_time, x.org_name, x.source1, x.source2,
             x.manager_name, x.first_opt_name, x.first_accept_date, x.created,
             x.type_flag, x.fee_class, x.by_user_name, x.coupon_name, x.sign_wxapp,
-            x.add_manager, c.org_id, c.customer_manager_staff_id, c.customer_optometrist_staff_id
+            x.add_manager, x.not_transaction_reason, c.org_id, c.customer_manager_staff_id, c.customer_optometrist_staff_id
     """
 
     logger.info("正在查询主数据...")
@@ -123,7 +124,7 @@ def run(start_date=None, end_date=None, group_id=1):
     staff_dict = {}
     if staff_ids:
         sql = """
-            SELECT s.id, u.name, s.qywx_userid
+            SELECT s.id, u.name, s.qywx_userid, u.mobile
             FROM staff_staff s
             JOIN users_userprofile u ON s.user_id = u.id
             WHERE s.id IN %(ids)s
@@ -147,6 +148,19 @@ def run(start_date=None, end_date=None, group_id=1):
         for c in clinic_info_list:
             clinic_info_dict[c["clinic_id"]] = c
 
+    # 5.1 查询患者检查信息
+    patient_ids = list(set([d.get("patientId") for d in data_list if d.get("patientId")]))
+    patient_exam_dict = {}
+    if patient_ids:
+        sql = """
+            SELECT patientId, r_sph, l_sph, r_nakeye, l_nakeye, al_od, al_os
+            FROM patient_custome
+            WHERE patientId IN %(ids)s
+        """
+        cursor.execute(sql, {"ids": patient_ids})
+        exam_list = cursor.fetchall()
+        patient_exam_dict = {e["patientId"]: e for e in exam_list}
+
     cursor.close()
     conn.close()
 
@@ -157,26 +171,28 @@ def run(start_date=None, end_date=None, group_id=1):
         "org_id", "org_name", "group_id", "group_name", "apt_doctor_id", "apt_doctor_name",
         "apt_optometrist_id", "apt_optometrist_name", "act_doctor_id", "act_doctor_name",
         "act_optometrist_id", "act_optometrist_name", "act_assistant_name",
-        "source1", "source2", "manager_id", "manager_name", "optometrist_id", "optometrist_name",
-        "first_opt_name", "first_accept_date", "total_amt", "pay_time", "by_user_id",
+        "source1", "source2", "manager_id", "manager_name", "manager_qywx_id", "manager_mobile",
+        "optometrist_id", "optometrist_name", "optometrist_qywx_id", "optometrist_mobile",
+        "first_opt_name", "first_accept_date", 
+        "r_sph", "l_sph", "r_nakeye", "l_nakeye", "al_od", "al_os", "not_transaction_reason",
+        "total_amt", "pay_time", "by_user_id",
         "by_user_name", "coupon_name", "add_manager", "sign_wxapp", "collect_date",
         "activity_location", "extension_user_id", "extension_user_name", "create_time",
     ]
 
     # 7. 处理数据
-    result = []
+    result_dict = {}
     for data in data_list:
         clinic_id = data["clinic_id"]
 
         # 处理金额(根据type_flag正负)
         total_amt = data.get("total_amt", 0)
-        if total_amt:
-            try:
-                total_amt = float(total_amt)
-            except (TypeError, ValueError):
-                total_amt = 0
+        try:
+            total_amt = float(total_amt) if total_amt is not None else 0.0
+        except (TypeError, ValueError):
+            total_amt = 0.0
 
-        if data.get("type_flag") != "1":
+        if str(data.get("type_flag")) != "1":
             total_amt = -total_amt if total_amt else 0
 
         # 处理日期
@@ -191,6 +207,15 @@ def run(start_date=None, end_date=None, group_id=1):
         else:
             pay_date = None
             pay_time_str = None
+            
+        if not pay_date:
+            continue
+            
+        key = f"{clinic_id}_{pay_date}"
+        
+        if key in result_dict:
+            result_dict[key]["total_amt"] += total_amt
+            continue
 
         come_time = data.get("come_time")
         if come_time:
@@ -215,6 +240,9 @@ def run(start_date=None, end_date=None, group_id=1):
         # 获取推广信息
         clinic_info = clinic_info_dict.get(clinic_id, {})
 
+        # 获取患者检查信息
+        patient_exam = patient_exam_dict.get(data.get("patientId"), {})
+
         # 处理性别
         sex_val = data.get("sex")
         if sex_val == "男":
@@ -248,56 +276,96 @@ def run(start_date=None, end_date=None, group_id=1):
                 return ""
             return str(v).strip()
 
+        record_dict = {
+            "clinic_id": safe_int(clinic_id),
+            "pay_date": pay_date,
+            "patientId": safe_str(data.get("patientId")),
+            "patient_name": safe_str(data.get("patient_name")),
+            "mobile": safe_str(data.get("mobile")),
+            "sex": safe_int(sex_val) if sex_val else None,
+            "birthday": data.get("birthday"),
+            "identity": safe_str(data.get("identity")),
+            "come_number": safe_int(data.get("come_number")),
+            "inviter_name": safe_str(data.get("inviter_name")),
+            "come_time": come_time_str,
+            "come_date": come_date,
+            "type": safe_int(type_val) if type_val else None,
+            "matter": safe_str(data.get("matter")),
+            "org_id": safe_int(org_id),
+            "org_name": safe_str(org_info.get("org_name")),
+            "group_id": safe_int(org_info.get("group_id")),
+            "group_name": safe_str(org_info.get("group_name")),
+            "apt_doctor_id": None,
+            "apt_doctor_name": safe_str(data.get("apt_doctor_name")),
+            "apt_optometrist_id": None,
+            "apt_optometrist_name": safe_str(data.get("apt_optometrist_name")),
+            "act_doctor_id": None,
+            "act_doctor_name": safe_str(data.get("act_doctor_name")),
+            "act_optometrist_id": None,
+            "act_optometrist_name": safe_str(data.get("act_optometrist_name")),
+            "act_assistant_name": safe_str(data.get("act_assistant_name")),
+            "source1": safe_str(data.get("source1")),
+            "source2": safe_str(data.get("source2")),
+            "manager_id": safe_int(data.get("customer_manager_staff_id")),
+            "manager_name": safe_str(manager_staff.get("name")),
+            "manager_qywx_id": safe_str(manager_staff.get("qywx_userid")),
+            "manager_mobile": safe_str(manager_staff.get("mobile")),
+            "optometrist_id": safe_int(data.get("customer_optometrist_staff_id")),
+            "optometrist_name": safe_str(optometrist_staff.get("name")),
+            "optometrist_qywx_id": safe_str(optometrist_staff.get("qywx_userid")),
+            "optometrist_mobile": safe_str(optometrist_staff.get("mobile")),
+            "first_opt_name": safe_str(data.get("first_opt_name")),
+            "first_accept_date": data.get("first_accept_date"),
+            "r_sph": safe_str(patient_exam.get("r_sph")),
+            "l_sph": safe_str(patient_exam.get("l_sph")),
+            "r_nakeye": safe_str(patient_exam.get("r_nakeye")),
+            "l_nakeye": safe_str(patient_exam.get("l_nakeye")),
+            "al_od": safe_str(patient_exam.get("al_od")),
+            "al_os": safe_str(patient_exam.get("al_os")),
+            "not_transaction_reason": safe_str(data.get("not_transaction_reason")),
+            "total_amt": total_amt,
+            "pay_time": pay_time_str,
+            "by_user_id": None,
+            "by_user_name": safe_str(data.get("by_user_name")),
+            "coupon_name": safe_str(data.get("coupon_name")),
+            "add_manager": "已添加" if data.get("add_manager") == 1 else "未添加",
+            "sign_wxapp": "已注册" if data.get("sign_wxapp") == 1 else "未注册",
+            "collect_date": clinic_info.get("collect_date"),
+            "activity_location": safe_str(clinic_info.get("activity_location")),
+            "extension_user_id": None,
+            "extension_user_name": safe_str(clinic_info.get("extension_user_name")),
+            "create_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+        }
+        result_dict[key] = record_dict
+
+    result = []
+    for key, record_dict in result_dict.items():
         record = (
-            safe_int(clinic_id),
-            pay_date,
-            safe_str(data.get("patientId")),
-            safe_str(data.get("patient_name")),
-            safe_str(data.get("mobile")),
-            safe_int(sex_val) if sex_val else None,
-            data.get("birthday"),
-            safe_str(data.get("identity")),
-            safe_int(data.get("come_number")),
-            safe_str(data.get("inviter_name")),
-            come_time_str,
-            come_date,
-            safe_int(type_val) if type_val else None,
-            safe_str(data.get("matter")),
-            safe_int(org_id),
-            safe_str(org_info.get("org_name")),
-            safe_int(org_info.get("group_id")),
-            safe_str(org_info.get("group_name")),
-            None,  # apt_doctor_id
-            safe_str(data.get("apt_doctor_name")),
-            None,  # apt_optometrist_id
-            safe_str(data.get("apt_optometrist_name")),
-            None,  # act_doctor_id
-            safe_str(data.get("act_doctor_name")),
-            None,  # act_optometrist_id
-            safe_str(data.get("act_optometrist_name")),
-            safe_str(data.get("act_assistant_name")),
-            safe_str(data.get("source1")),
-            safe_str(data.get("source2")),
-            safe_int(data.get("customer_manager_staff_id")),
-            safe_str(manager_staff.get("name")),
-            safe_int(data.get("customer_optometrist_staff_id")),
-            safe_str(optometrist_staff.get("name")),
-            safe_str(data.get("first_opt_name")),
-            data.get("first_accept_date"),  # 保持原样,应该已经是日期格式
-            float(total_amt) if total_amt else 0,
-            pay_time_str,
-            None,  # by_user_id
-            safe_str(data.get("by_user_name")),
-            safe_str(data.get("coupon_name")),
-            "已添加" if data.get("add_manager") == 1 else "未添加",
-            "已注册" if data.get("sign_wxapp") == 1 else "未注册",
-            clinic_info.get("collect_date"),  # 保持原样
-            safe_str(clinic_info.get("activity_location")),
-            None,  # extension_user_id
-            safe_str(clinic_info.get("extension_user_name")),
-            datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+            record_dict["clinic_id"], record_dict["pay_date"], record_dict["patientId"],
+            record_dict["patient_name"], record_dict["mobile"], record_dict["sex"],
+            record_dict["birthday"], record_dict["identity"], record_dict["come_number"],
+            record_dict["inviter_name"], record_dict["come_time"], record_dict["come_date"],
+            record_dict["type"], record_dict["matter"], record_dict["org_id"],
+            record_dict["org_name"], record_dict["group_id"], record_dict["group_name"],
+            record_dict["apt_doctor_id"], record_dict["apt_doctor_name"],
+            record_dict["apt_optometrist_id"], record_dict["apt_optometrist_name"],
+            record_dict["act_doctor_id"], record_dict["act_doctor_name"],
+            record_dict["act_optometrist_id"], record_dict["act_optometrist_name"],
+            record_dict["act_assistant_name"], record_dict["source1"], record_dict["source2"],
+            record_dict["manager_id"], record_dict["manager_name"],
+            record_dict["manager_qywx_id"], record_dict["manager_mobile"],
+            record_dict["optometrist_id"], record_dict["optometrist_name"],
+            record_dict["optometrist_qywx_id"], record_dict["optometrist_mobile"],
+            record_dict["first_opt_name"], record_dict["first_accept_date"],
+            record_dict["r_sph"], record_dict["l_sph"], record_dict["r_nakeye"],
+            record_dict["l_nakeye"], record_dict["al_od"], record_dict["al_os"],
+            record_dict["not_transaction_reason"],
+            float(record_dict["total_amt"]), record_dict["pay_time"],
+            record_dict["by_user_id"], record_dict["by_user_name"], record_dict["coupon_name"],
+            record_dict["add_manager"], record_dict["sign_wxapp"], record_dict["collect_date"],
+            record_dict["activity_location"], record_dict["extension_user_id"],
+            record_dict["extension_user_name"], record_dict["create_time"]
         )
-
         result.append(record)
 
     # 8. 写入Doris
@@ -310,9 +378,10 @@ def run(start_date=None, end_date=None, group_id=1):
     logger.info(f"清理该时间段的历史数据: {start_date} ~ {end_date}")
     delete_sql = """
         DELETE FROM fact_patientconsumption
-        WHERE pay_time >= %s AND pay_time <= %s
+        WHERE pay_date >= %s AND pay_date <= %s
+            AND group_id = %s
     """
-    cursor.execute(delete_sql, (f"{start_date} 00:00:00", f"{end_date} 23:59:59"))
+    cursor.execute(delete_sql, (start_date, end_date, int(group_id)))
     deleted_count = cursor.rowcount
     logger.info(f"已删除 {deleted_count} 条历史数据")
     conn.commit()

+ 41 - 24
jobs/grossprofit.py

@@ -88,7 +88,7 @@ def sync_group_data(mysql_cursor, doris_cursor, doris_conn, group_id, group_name
             x.class_name, x.combo_name, x.per_id, x.name, x.unit,
             x.price, x.num, x.sum_price, x.should_amt, x.discount,
             x.discount_memo, x.coupon_name, x.flag, x.type_flag, x.order_status,
-            x.pay_time, x.exec_time, x.exec_status, x.cost_price, x.mcost_price,
+            x.created AS pay_time, x.exec_time, x.exec_status, x.cost_price, x.mcost_price,
             x.jcost_price, x.cost_flag, x.cost_flag2, x.cost_update_time, x.cost_update_time2,
             x.profit_amt, x.profit_rate, x.mprofit_amt, x.mprofit_rate,
             x.jprofit_amt, x.jprofit_rate, x.blq_amt, x.swap_card_amt, x.eye_card_amt,
@@ -105,23 +105,23 @@ def sync_group_data(mysql_cursor, doris_cursor, doris_conn, group_id, group_name
             CASE WHEN x.flag IN (9, 11) THEN f.pay_time ELSE NULL END AS refund_pay_time,
             CASE WHEN x.type_flag = 2 AND x.flag NOT IN (9, 11) THEN fo.pay_time ELSE NULL END AS refund_order_pay_time
         FROM fee_incomedetail x
-        LEFT JOIN clinic_clinic c ON c.id = x.clinic_id
+        JOIN clinic_clinic c ON c.id = x.clinic_id
         LEFT JOIN finance_bill f ON x.type_flag = 2 AND x.flag IN (9, 11) AND f.order_no = x.order_no
         LEFT JOIN fee_order fo ON x.type_flag = 2 AND x.flag NOT IN (9, 11) AND fo.id = x.order_id
-        WHERE x.pay_time >= %(sdate)s AND x.pay_time < %(edate)s
+        WHERE x.created >= %(sdate)s AND x.created < %(edate)s
           AND x.group_id = %(group_id)s
           AND x.visual = 1 AND x.org_id != 16
           AND (x.flag IN (4, 5) OR (x.flag IN (1, 2, 6, 7, 8, 9, 10, 11, 12) AND x.should_amt != 0))
         ORDER BY x.id
         """
 
-        logger.info(f"查询MySQL数据... (pay_time: {sdate} ~ {edate})")
+        logger.info(f"查询MySQL数据... (created: {sdate} ~ {edate})")
         mysql_cursor.execute(sql, params)
         rows = mysql_cursor.fetchall()
         logger.info(f"获取到 {len(rows)} 条数据")
 
         if not rows:
-            logger.info("没有数据���要同步")
+            logger.info("没有数据要同步")
             return 0
 
         doris_insert_sql = """
@@ -177,40 +177,57 @@ def sync_group_data(mysql_cursor, doris_cursor, doris_conn, group_id, group_name
                 r = row  # DictCursor 已经返回字典
                 created = format_datetime(r.get("created"))
                 month = created[:7] if created else ""
-                pay_time = format_datetime(r.get("pay_time"))
-                fee_time = pay_time
-                if r.get("name") == "系统逾期项目":
-                    refund_pt = r.get("refund_pay_time") or r.get("refund_order_pay_time")
-                    fee_time = format_datetime(refund_pt) if refund_pt else format_datetime(r.get("sell_time"))
-                elif r.get("type_flag") == 2 and r.get("flag") not in [9, 11]:
-                    refund_pt = r.get("refund_order_pay_time")
-                    fee_time = format_datetime(refund_pt) if refund_pt else pay_time
-                elif r.get("type_flag") == 2 and r.get("flag") in [9, 11]:
-                    refund_pt = r.get("refund_pay_time")
-                    fee_time = format_datetime(refund_pt) if refund_pt else pay_time
+                pay_time = format_datetime(r.get("pay_time")) # 即 x.created
+                sell_time = format_datetime(r.get("sell_time")) # 即 x.pay_time
+
+                # fee_time 计算逻辑完全对齐 API
+                if r.get("name") != "系统逾期项目":
+                    fee_time = created
+                else:
+                    fee_time = sell_time
+
+                type_flag = r.get("type_flag", 1)
+                if type_flag == 2:
+                    if r.get("flag") in [9, 11]:
+                        fee_time = format_datetime(r.get("refund_pay_time"))
+                    else:
+                        if r.get("name") != "系统逾期项目":
+                            fee_time = format_datetime(r.get("refund_order_pay_time"))
+                        else:
+                            fee_time = sell_time
 
                 exec_status = r.get("exec_status", 0)
                 exec_status_name = "已交付" if exec_status == 1 else "未交付"
                 order_status = r.get("order_status", 1)
-                order_status_name = "作废" if order_status == 2 else "正常"
-                type_flag = r.get("type_flag", 1)
+                order_status_name = "正常"
+                # 完全对齐 API 中的 Bug: 若 order_status == 2,把 exec_status_name 设为 "作废"
+                if order_status == 2:
+                    exec_status_name = "作废"
+
                 pay_status = "已退费" if type_flag == 2 else "已收费"
 
-                cost_amt = r.get("cost_price", 0) or 0
-                profit_amt = r.get("profit_amt", 0) or 0
+                cost_amt = r.get("mcost_price", 0) or 0
+                profit_amt = r.get("mprofit_amt", 0) or 0
+                
+                sum_price = float(r.get("sum_price", 0) or 0)
+                should_amt = float(r.get("should_amt", 0) or 0)
+                num = r.get("num", 0) or 0
+                
                 if type_flag == 2:
                     cost_amt = -(cost_amt)
                     profit_amt = -(profit_amt)
+                    sum_price = -sum_price
+                    should_amt = -should_amt
+                    num = -num
 
-                profit_rate = format_profit_rate(r.get("profit_rate"))
+                profit_rate = format_profit_rate(r.get("mprofit_rate"))
                 discount = format_discount(r.get("discount"))
 
                 come_time = format_datetime(r.get("come_time"))
                 exec_time = format_datetime(r.get("exec_time"))
                 update_time = format_datetime(r.get("cost_update_time"))
                 update_time2 = format_datetime(r.get("cost_update_time2"))
-                sell_time = format_datetime(r.get("pay_time"))
-                sync_date = sell_time.split(" ")[0] if sell_time else None
+                sync_date = pay_time.split(" ")[0] if pay_time else None
 
                 blq_amt = float(r.get("blq_amt", 0) or 0) + float(r.get("swap_card_amt", 0) or 0) + float(r.get("eye_card_amt", 0) or 0)
 
@@ -251,7 +268,7 @@ def sync_group_data(mysql_cursor, doris_cursor, doris_conn, group_id, group_name
                     pay_status, r.get("order_no"), r.get("pk_code"), r.get("uuid_no"),
                     r.get("class_name"), r.get("line_name"), r.get("fee_class"),
                     r.get("name"), r.get("combo_name"), r.get("flag_memo"),
-                    r.get("price"), r.get("num"), r.get("unit"), r.get("sum_price"), r.get("should_amt"),
+                    r.get("price"), num, r.get("unit"), sum_price, should_amt,
                     cost_amt, profit_amt, profit_rate, discount,
                     r.get("discount_memo"), r.get("coupon_name"), blq_amt,
                     hx_sum_price, hx_num, hx_amt, hx_cost_price,

+ 165 - 0
recreate_tables.sql

@@ -0,0 +1,165 @@
+-- 1. 重建诊所日志宽表 (fact_cliniclog)
+DROP TABLE IF EXISTS `fact_cliniclog`;
+CREATE TABLE `fact_cliniclog` (
+  `id` bigint NOT NULL AUTO_INCREMENT(1) COMMENT "自增ID",
+  `come_time` datetime NOT NULL COMMENT "接诊时间",
+  `clinic_id` bigint NOT NULL COMMENT "挂号ID",
+  `patientId` varchar(500) NULL COMMENT "病例号",
+  `patient_name` varchar(5000) NULL COMMENT "姓名",
+  `sex` tinyint NULL COMMENT "性别 1男2女",
+  `birthday` date NULL COMMENT "出生日期",
+  `identity` varchar(500) NULL COMMENT "患者身份",
+  `mobile` varchar(500) NULL COMMENT "患者手机号",
+  `source1` varchar(10000) NULL COMMENT "患者来源1",
+  `source2` varchar(10000) NULL COMMENT "患者来源2",
+  `tag_list` varchar(10000) NULL COMMENT "标签列表",
+  `patient_type` varchar(500) NULL COMMENT "客户类型",
+  `visit_type` tinyint NULL COMMENT "接诊类型 1初诊2复诊3预约4复查",
+  `r_sph` varchar(500) NULL COMMENT "右眼球镜",
+  `l_sph` varchar(500) NULL COMMENT "左眼球镜",
+  `r_nakesight` varchar(500) NULL COMMENT "右眼远视力",
+  `l_nakesight` varchar(500) NULL COMMENT "左眼远视力",
+  `org_id` int NULL COMMENT "诊所ID",
+  `org_name` varchar(500) NULL COMMENT "诊所名称",
+  `appoint_time` datetime NULL COMMENT "预约时间",
+  `add_user` varchar(500) NULL COMMENT "预约创建人",
+  `group_id` int NULL COMMENT "集团ID",
+  `group_name` varchar(500) NULL COMMENT "集团名称",
+  `matter` varchar(10000) NULL COMMENT "预约就诊事项",
+  `accept_doctor_id` int NULL COMMENT "接诊医生ID",
+  `accept_doctor_name` varchar(500) NULL COMMENT "接诊医生",
+  `accept_optometrist_id` int NULL COMMENT "接诊视光师ID",
+  `accept_optometrist_name` varchar(500) NULL COMMENT "接诊视光师",
+  `accept_assistant_id` int NULL COMMENT "医生助理ID",
+  `accept_assistant_name` varchar(500) NULL COMMENT "医生助理",
+  `manager_id` int NULL COMMENT "专属助理ID",
+  `manager_name` varchar(500) NULL COMMENT "专属助理",
+  `manager_qywx_id` varchar(500) NULL COMMENT "专属助理企微ID",
+  `manager_mobile` varchar(500) NULL COMMENT "专属助理手机号",
+  `optometrist_id` int NULL COMMENT "专属视光师ID",
+  `optometrist_name` varchar(500) NULL COMMENT "专属视光师",
+  `optometrist_qywx_id` varchar(500) NULL COMMENT "专属视光师企微ID",
+  `optometrist_mobile` varchar(500) NULL COMMENT "专属视光师手机号",
+  `this_come_number` int NULL COMMENT "当前就诊次数",
+  `inviter` varchar(500) NULL COMMENT "推荐人",
+  `inviter_phone` varchar(500) NULL COMMENT "推荐人手机号",
+  `channel` varchar(500) NULL COMMENT "渠道",
+  `collect_date` date NULL COMMENT "收集日期",
+  `activity_location` varchar(10000) NULL COMMENT "收集点位",
+  `extension_user` varchar(500) NULL COMMENT "推广人员",
+  `extension_user_mobile` varchar(500) NULL COMMENT "推广人员手机号",
+  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",
+  INDEX idx_patientId (`patientId`) USING INVERTED,
+  INDEX idx_mobile (`mobile`) USING INVERTED,
+  INDEX idx_come_time (`come_time`) USING INVERTED,
+  INDEX idx_org_id (`org_id`) USING INVERTED
+) ENGINE=OLAP
+UNIQUE KEY(`id`, `come_time`)
+COMMENT '诊所日志宽表'
+AUTO PARTITION BY RANGE (date_trunc(`come_time`, 'month'))
+()
+DISTRIBUTED BY HASH(`id`) BUCKETS 32
+PROPERTIES (
+"replication_allocation" = "tag.location.default: 1",
+"min_load_replica_num" = "-1",
+"is_being_synced" = "false",
+"storage_medium" = "hdd",
+"storage_format" = "V2",
+"inverted_index_storage_format" = "V3",
+"enable_unique_key_merge_on_write" = "true",
+"light_schema_change" = "true",
+"disable_auto_compaction" = "false",
+"enable_single_replica_compaction" = "false",
+"group_commit_interval_ms" = "10000",
+"group_commit_data_bytes" = "134217728",
+"enable_mow_light_delete" = "false"
+);
+
+-- 2. 重建患者消费统计宽表 (fact_patientconsumption)
+DROP TABLE IF EXISTS `fact_patientconsumption`;
+CREATE TABLE `fact_patientconsumption` (
+  `id` bigint NOT NULL AUTO_INCREMENT(1) COMMENT "自增ID",
+  `pay_date` date NOT NULL COMMENT "收费日期",
+  `clinic_id` bigint NOT NULL COMMENT "挂号ID",
+  `patientId` varchar(500) NULL COMMENT "病例号",
+  `patient_name` varchar(5000) NULL COMMENT "姓名",
+  `mobile` varchar(500) NULL COMMENT "手机号",
+  `sex` tinyint NULL COMMENT "性别 1男2女",
+  `birthday` date NULL COMMENT "出生日期",
+  `identity` varchar(500) NULL COMMENT "身份",
+  `come_number` int NULL COMMENT "到诊次数 1新客户>1老客户",
+  `inviter_name` varchar(500) NULL COMMENT "推荐人",
+  `come_time` datetime NULL COMMENT "就诊时间",
+  `come_date` date NULL COMMENT "就诊日期",
+  `type` tinyint NULL COMMENT "就诊类型 1初诊2复诊3预约4复查",
+  `matter` varchar(10000) NULL COMMENT "就诊事项",
+  `org_id` int NULL COMMENT "诊所ID",
+  `org_name` varchar(500) NULL COMMENT "诊所名称",
+  `group_id` int NULL COMMENT "集团ID",
+  `group_name` varchar(500) NULL COMMENT "集团名称",
+  `apt_doctor_id` int NULL COMMENT "预约医生ID",
+  `apt_doctor_name` varchar(500) NULL COMMENT "预约医生",
+  `apt_optometrist_id` int NULL COMMENT "预约视光师ID",
+  `apt_optometrist_name` varchar(500) NULL COMMENT "预约视光师",
+  `act_doctor_id` int NULL COMMENT "接诊医生ID",
+  `act_doctor_name` varchar(500) NULL COMMENT "接诊医生",
+  `act_optometrist_id` int NULL COMMENT "接诊视光师ID",
+  `act_optometrist_name` varchar(500) NULL COMMENT "接诊视光师",
+  `act_assistant_name` varchar(500) NULL COMMENT "接诊医助",
+  `source1` varchar(10000) NULL COMMENT "来源1",
+  `source2` varchar(10000) NULL COMMENT "来源2",
+  `manager_id` int NULL COMMENT "专属医助ID",
+  `manager_name` varchar(500) NULL COMMENT "专属医助",
+  `manager_qywx_id` varchar(500) NULL COMMENT "专属医助企微ID",
+  `manager_mobile` varchar(500) NULL COMMENT "专属医助手机号",
+  `optometrist_id` int NULL COMMENT "专属视光师ID",
+  `optometrist_name` varchar(500) NULL COMMENT "专属视光师",
+  `optometrist_qywx_id` varchar(500) NULL COMMENT "专属视光师企微ID",
+  `optometrist_mobile` varchar(500) NULL COMMENT "专属视光师手机号",
+  `first_opt_name` varchar(500) NULL COMMENT "首诊视光师",
+  `first_accept_date` date NULL COMMENT "首次接诊日期",
+  `r_sph` varchar(500) NULL COMMENT "右眼球镜",
+  `l_sph` varchar(500) NULL COMMENT "左眼球镜",
+  `r_nakeye` varchar(500) NULL COMMENT "右眼裸眼视力",
+  `l_nakeye` varchar(500) NULL COMMENT "左眼裸眼视力",
+  `al_od` varchar(500) NULL COMMENT "右眼眼轴",
+  `al_os` varchar(500) NULL COMMENT "左眼眼轴",
+  `not_transaction_reason` varchar(10000) NULL COMMENT "未成交原因",
+  `total_amt` decimal(15,2) NULL DEFAULT "0" COMMENT "金额合计",
+  `pay_time` datetime NULL COMMENT "收费时间",
+  `by_user_id` int NULL COMMENT "收银员ID",
+  `by_user_name` varchar(500) NULL COMMENT "收银员",
+  `coupon_name` varchar(10000) NULL COMMENT "优惠券",
+  `add_manager` varchar(10) NULL COMMENT "加客户经理好友",
+  `sign_wxapp` varchar(10) NULL COMMENT "注册小程序",
+  `collect_date` date NULL COMMENT "收集日期",
+  `activity_location` varchar(10000) NULL COMMENT "活动地点",
+  `extension_user_id` int NULL COMMENT "推广专员ID",
+  `extension_user_name` varchar(500) NULL COMMENT "推广专员",
+  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",
+  INDEX idx_mobile (`mobile`) USING INVERTED,
+  INDEX idx_patient_id (`patientId`) USING INVERTED,
+  INDEX idx_org_id (`org_id`) USING INVERTED,
+  INDEX idx_come_time (`come_time`) USING INVERTED,
+  INDEX idx_pay_date (`pay_date`) USING INVERTED
+) ENGINE=OLAP
+UNIQUE KEY(`id`, `pay_date`)
+COMMENT '患者消费统计宽表'
+AUTO PARTITION BY RANGE (date_trunc(`pay_date`, 'month'))
+()
+DISTRIBUTED BY HASH(`id`) BUCKETS 32
+PROPERTIES (
+"replication_allocation" = "tag.location.default: 1",
+"min_load_replica_num" = "-1",
+"is_being_synced" = "false",
+"storage_medium" = "hdd",
+"storage_format" = "V2",
+"inverted_index_storage_format" = "V3",
+"enable_unique_key_merge_on_write" = "true",
+"light_schema_change" = "true",
+"disable_auto_compaction" = "false",
+"enable_single_replica_compaction" = "false",
+"group_commit_interval_ms" = "10000",
+"group_commit_data_bytes" = "134217728",
+"enable_mow_light_delete" = "false"
+);

+ 116 - 0
test.sql

@@ -0,0 +1,116 @@
+CREATE TABLE `fact_fee_grossprofit` (
+  `id` bigint NOT NULL AUTO_INCREMENT(1) COMMENT "自增ID",
+  `sync_date` date NOT NULL COMMENT "同步日期",
+  `fee_incomedetail_id` int NOT NULL COMMENT "收费明细源表ID",
+  `patientId` varchar(500) NULL COMMENT "病历号",
+  `created` datetime NULL COMMENT "创建时间",
+  `modified` datetime NULL COMMENT "修改时间",
+  `month` varchar(10) NULL COMMENT "月份",
+  `org_name` varchar(500) NULL COMMENT "诊所",
+  `group_id` int NULL COMMENT "集团ID",
+  `group_name` varchar(500) NULL COMMENT "集团名称",
+  `pay_time` datetime NULL COMMENT "收/退费时间",
+  `fee_time` datetime NULL COMMENT "原始购买时间",
+  `clinic_id` int NULL COMMENT "挂号ID",
+  `come_time` datetime NULL COMMENT "到诊时间",
+  `patient_name` varchar(5000) NULL COMMENT "姓名",
+  `patient_mobile` varchar(500) NULL COMMENT "患者手机",
+  `patient_identity` varchar(500) NULL COMMENT "身份",
+  `birthday` date NULL COMMENT "出生日期",
+  `age` int NULL COMMENT "年龄",
+  `source1` varchar(500) NULL COMMENT "一级来源",
+  `source2` varchar(500) NULL COMMENT "二级来源",
+  `inviter_name` varchar(500) NULL COMMENT "介绍人姓名",
+  `inviter_phone` varchar(500) NULL COMMENT "介绍人电话",
+  `first_accept_date` date NULL COMMENT "首诊日期",
+  `first_distance_day` int NULL COMMENT "首诊距今天数",
+  `by_user_name` varchar(500) NULL COMMENT "收银员",
+  `doctor_name` varchar(500) NULL COMMENT "接诊医生",
+  `opt_name` varchar(500) NULL COMMENT "接诊视光师",
+  `assistant_name` varchar(500) NULL COMMENT "接诊医助",
+  `pay_status` varchar(50) NULL COMMENT "交易类型",
+  `order_no` varchar(500) NULL COMMENT "收费单号",
+  `pk_code` varchar(500) NULL COMMENT "诊所唯一ID",
+  `uuid_no` varchar(500) NULL COMMENT "集采唯一ID",
+  `class_name` varchar(500) NULL COMMENT "项目分类",
+  `line_name` varchar(500) NULL COMMENT "产品线",
+  `fee_class` varchar(500) NULL COMMENT "收费分类",
+  `name` varchar(500) NULL COMMENT "收费项目",
+  `combo_name` varchar(500) NULL COMMENT "套餐名称",
+  `flag_memo` varchar(500) NULL COMMENT "收费项目类型",
+  `price` decimal(11,2) NULL COMMENT "单价",
+  `num` int NULL COMMENT "数量",
+  `unit` varchar(500) NULL COMMENT "单位",
+  `sum_price` decimal(11,2) NULL COMMENT "原价",
+  `should_amt` decimal(11,2) NULL COMMENT "应收金额",
+  `cost_amt` decimal(11,2) NULL COMMENT "成本价",
+  `profit_amt` decimal(11,2) NULL COMMENT "毛利",
+  `profit_rate` varchar(50) NULL COMMENT "毛利率",
+  `discount` varchar(50) NULL COMMENT "折扣比例%",
+  `discount_memo` text NULL COMMENT "折扣原因",
+  `coupon_name` varchar(10000) NULL COMMENT "优惠卷",
+  `blq_amt` decimal(11,2) NULL COMMENT "铂林券金额",
+  `hx_sum_price` decimal(11,2) NULL COMMENT "业绩原价",
+  `hx_num` int NULL COMMENT "业绩数量",
+  `hx_amt` decimal(11,2) NULL COMMENT "业绩应收",
+  `hx_cost_price` decimal(11,2) NULL COMMENT "业绩成本",
+  `pay_sum_price` decimal(11,2) NULL COMMENT "实际小计",
+  `pay_num` int NULL COMMENT "实际数量",
+  `pay_amt` decimal(11,2) NULL COMMENT "实际金额",
+  `pay_cost_price` decimal(11,2) NULL COMMENT "实际成本",
+  `pay_profit_amt` decimal(11,2) NULL COMMENT "业绩毛利",
+  `pay_profit_rate` decimal(11,2) NULL COMMENT "业绩毛利率",
+  `factory_name` varchar(500) NULL COMMENT "制造商",
+  `brands_name` varchar(500) NULL COMMENT "品牌",
+  `variety_name` varchar(500) NULL COMMENT "品种",
+  `exec_time` datetime NULL COMMENT "交付时间",
+  `exec_status_name` varchar(50) NULL COMMENT "交付状态",
+  `order_status_name` varchar(50) NULL COMMENT "订单状态",
+  `cost_flag` int NULL COMMENT "成本标识",
+  `update_time` datetime NULL COMMENT "成本更新时间",
+  `cost_flag2` int NULL COMMENT "成本标识2",
+  `update_time2` datetime NULL COMMENT "成本更新时间2",
+  `exec_status` int NULL COMMENT "交付状态",
+  `flag` int NULL COMMENT "标识",
+  `per_id` int NULL COMMENT "人员id",
+  `order_id` int NULL COMMENT "订单id",
+  `type_flag` int NULL COMMENT "类型标识",
+  `order_status` int NULL COMMENT "订单状态",
+  `sell_time` datetime NULL COMMENT "销售时间"
+) ENGINE=OLAP
+UNIQUE KEY(`id`, `sync_date`)
+COMMENT '销售订单毛利统计宽表'
+AUTO PARTITION BY RANGE (date_trunc(`sync_date`, 'month'))
+(PARTITION p20250101000000 VALUES [('2025-01-01'), ('2025-02-01')),
+PARTITION p20250201000000 VALUES [('2025-02-01'), ('2025-03-01')),
+PARTITION p20250301000000 VALUES [('2025-03-01'), ('2025-04-01')),
+PARTITION p20250401000000 VALUES [('2025-04-01'), ('2025-05-01')),
+PARTITION p20250501000000 VALUES [('2025-05-01'), ('2025-06-01')),
+PARTITION p20250601000000 VALUES [('2025-06-01'), ('2025-07-01')),
+PARTITION p20250701000000 VALUES [('2025-07-01'), ('2025-08-01')),
+PARTITION p20250801000000 VALUES [('2025-08-01'), ('2025-09-01')),
+PARTITION p20250901000000 VALUES [('2025-09-01'), ('2025-10-01')),
+PARTITION p20251001000000 VALUES [('2025-10-01'), ('2025-11-01')),
+PARTITION p20251101000000 VALUES [('2025-11-01'), ('2025-12-01')),
+PARTITION p20251201000000 VALUES [('2025-12-01'), ('2026-01-01')),
+PARTITION p20260101000000 VALUES [('2026-01-01'), ('2026-02-01')),
+PARTITION p20260201000000 VALUES [('2026-02-01'), ('2026-03-01')),
+PARTITION p20260301000000 VALUES [('2026-03-01'), ('2026-04-01')),
+PARTITION p20260401000000 VALUES [('2026-04-01'), ('2026-05-01')),
+PARTITION p20260501000000 VALUES [('2026-05-01'), ('2026-06-01')))
+DISTRIBUTED BY HASH(`id`) BUCKETS 32
+PROPERTIES (
+"replication_allocation" = "tag.location.default: 1",
+"min_load_replica_num" = "-1",
+"is_being_synced" = "false",
+"storage_medium" = "hdd",
+"storage_format" = "V2",
+"inverted_index_storage_format" = "V3",
+"enable_unique_key_merge_on_write" = "true",
+"light_schema_change" = "true",
+"disable_auto_compaction" = "false",
+"enable_single_replica_compaction" = "false",
+"group_commit_interval_ms" = "10000",
+"group_commit_data_bytes" = "134217728",
+"enable_mow_light_delete" = "false"
+);;

+ 317 - 0
需求.md

@@ -0,0 +1,317 @@
+### 数据同步需求
+- 这个项目主要是将mysql中的数据同步到doris中,以便进行数据分析
+- F:\公司\BI1.0\doris-sync\jobs\grossprofit.py   销售订单毛利统计同步脚本
+- 同步脚本参考src项目接口实现,这是接口 
+https://his.bolineyecare.com/api/census/order_grossprofit_analysis/?current_page=1&search=&org_id=&exec_flag=&name=&order_no=AC1F20260403170257450&pro_line_id=&exclude_flag=&cost_price_flag=&blq_flag=&start_day=&end_day=&start_date=2026-04-01&end_date=2026-04-30&page_size=50&fee_class=&type_flag=&discount_memo=
+- src项目 url 路由 F:\公司\BI1.0\src\apps\census\urls.py     url(r'^order_grossprofit_analysis/', OrderGrossprofitAnalysis_V2.as_view()),  # 销售订单毛利分析
+
+### 现在的问题
+- 现在财务说这个doris中的数据与src项目销售订单毛利分析接口返回的数据不一样
+- 你看下是不是同步的脚本F:\公司\BI1.0\doris-sync\jobs\grossprofit.py有问题
+- 注意: doris中的表我统计加了, group_id与group_name参数,注意这是需求,正确的 
+
+### 另外两个脚本的问题
+- F:\公司\BI1.0\doris-sync\jobs\clinic_log.py   诊所日志同步脚本 
+- F:\公司\BI1.0\doris-sync\jobs\consumption.py  患者消费统计宽表同步任务
+- 诊所日志同步脚本对应的的接口同样也在src项目,https://his.bolineyecare.com/api/census/org_day_note/?org_id=&doctor_id=&optometrist_id=&content=&current_page=1&customer_manager_staff_id=&customer_optometrist_staff_id=&start_date=2026-05-23&end_date=2026-05-23&page_size=50
+- 患者消费统计宽表同步任务对应的接口同样也在src项目,https://his.bolineyecare.com/api/census/clinic_pay_use/?org_id=&type=&appoint_doctor_id=&accept_doctor_id=&appoint_optometrist_id=&accept_optometrist_id=&by_user=&inviter=&content=&current_page=1&customer_manager_staff_id=&customer_optometrist_staff_id=&date_start=2026-05-24&date_end=2026-05-24&pay_time_start=&pay_time_end=&first_accept_start=&first_accept_end=&page_size=50
+
+### doris表结构
+- 患者消费统计宽表
+```sql
+CREATE TABLE `fact_patientconsumption` (
+  `id` bigint NOT NULL AUTO_INCREMENT(1) COMMENT "自增ID",
+  `pay_date` date NOT NULL COMMENT "收费日期",
+  `clinic_id` bigint NOT NULL COMMENT "挂号ID",
+  `patientId` varchar(500) NULL COMMENT "病例号",
+  `patient_name` varchar(5000) NULL COMMENT "姓名",
+  `mobile` varchar(500) NULL COMMENT "手机号",
+  `sex` tinyint NULL COMMENT "性别 1男2女",
+  `birthday` date NULL COMMENT "出生日期",
+  `identity` varchar(500) NULL COMMENT "身份",
+  `come_number` int NULL COMMENT "到诊次数 1新客户>1老客户",
+  `inviter_name` varchar(500) NULL COMMENT "推荐人",
+  `come_time` datetime NULL COMMENT "就诊时间",
+  `come_date` date NULL COMMENT "就诊日期",
+  `type` tinyint NULL COMMENT "就诊类型 1初诊2复诊3预约4复查",
+  `matter` varchar(10000) NULL COMMENT "就诊事项",
+  `org_id` int NULL COMMENT "诊所ID",
+  `org_name` varchar(500) NULL COMMENT "诊所名称",
+  `group_id` int NULL COMMENT "集团ID",
+  `group_name` varchar(500) NULL COMMENT "集团名称",
+  `apt_doctor_id` int NULL COMMENT "预约医生ID",
+  `apt_doctor_name` varchar(500) NULL COMMENT "预约医生",
+  `apt_optometrist_id` int NULL COMMENT "预约视光师ID",
+  `apt_optometrist_name` varchar(500) NULL COMMENT "预约视光师",
+  `act_doctor_id` int NULL COMMENT "接诊医生ID",
+  `act_doctor_name` varchar(500) NULL COMMENT "接诊医生",
+  `act_optometrist_id` int NULL COMMENT "接诊视光师ID",
+  `act_optometrist_name` varchar(500) NULL COMMENT "接诊视光师",
+  `act_assistant_name` varchar(500) NULL COMMENT "接诊医助",
+  `source1` varchar(10000) NULL COMMENT "来源1",
+  `source2` varchar(10000) NULL COMMENT "来源2",
+  `manager_id` int NULL COMMENT "专属医助ID",
+  `manager_name` varchar(500) NULL COMMENT "专属医助",
+  `optometrist_id` int NULL COMMENT "专属视光师ID",
+  `optometrist_name` varchar(500) NULL COMMENT "专属视光师",
+  `first_opt_name` varchar(500) NULL COMMENT "首诊视光师",
+  `first_accept_date` date NULL COMMENT "首次接诊日期",
+  `total_amt` decimal(15,2) NULL DEFAULT "0" COMMENT "金额合计",
+  `pay_time` datetime NULL COMMENT "收费时间",
+  `by_user_id` int NULL COMMENT "收银员ID",
+  `by_user_name` varchar(500) NULL COMMENT "收银员",
+  `coupon_name` varchar(10000) NULL COMMENT "优惠券",
+  `add_manager` varchar(10) NULL COMMENT "加客户经理好友",
+  `sign_wxapp` varchar(10) NULL COMMENT "注册小程序",
+  `collect_date` date NULL COMMENT "收集日期",
+  `activity_location` varchar(10000) NULL COMMENT "活动地点",
+  `extension_user_id` int NULL COMMENT "推广专员ID",
+  `extension_user_name` varchar(500) NULL COMMENT "推广专员",
+  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",
+  INDEX idx_mobile (`mobile`) USING INVERTED,
+  INDEX idx_patient_id (`patientId`) USING INVERTED,
+  INDEX idx_org_id (`org_id`) USING INVERTED,
+  INDEX idx_come_time (`come_time`) USING INVERTED,
+  INDEX idx_pay_date (`pay_date`) USING INVERTED
+) ENGINE=OLAP
+UNIQUE KEY(`id`, `pay_date`)
+COMMENT '患者消费统计宽表'
+AUTO PARTITION BY RANGE (date_trunc(`pay_date`, 'month'))
+(PARTITION p20250101000000 VALUES [('2025-01-01'), ('2025-02-01')),
+PARTITION p20250201000000 VALUES [('2025-02-01'), ('2025-03-01')),
+PARTITION p20250301000000 VALUES [('2025-03-01'), ('2025-04-01')),
+PARTITION p20250401000000 VALUES [('2025-04-01'), ('2025-05-01')),
+PARTITION p20250501000000 VALUES [('2025-05-01'), ('2025-06-01')),
+PARTITION p20250601000000 VALUES [('2025-06-01'), ('2025-07-01')),
+PARTITION p20250701000000 VALUES [('2025-07-01'), ('2025-08-01')),
+PARTITION p20250801000000 VALUES [('2025-08-01'), ('2025-09-01')),
+PARTITION p20250901000000 VALUES [('2025-09-01'), ('2025-10-01')),
+PARTITION p20251001000000 VALUES [('2025-10-01'), ('2025-11-01')),
+PARTITION p20251101000000 VALUES [('2025-11-01'), ('2025-12-01')),
+PARTITION p20251201000000 VALUES [('2025-12-01'), ('2026-01-01')),
+PARTITION p20260101000000 VALUES [('2026-01-01'), ('2026-02-01')),
+PARTITION p20260201000000 VALUES [('2026-02-01'), ('2026-03-01')),
+PARTITION p20260301000000 VALUES [('2026-03-01'), ('2026-04-01')),
+PARTITION p20260401000000 VALUES [('2026-04-01'), ('2026-05-01')),
+PARTITION p20260501000000 VALUES [('2026-05-01'), ('2026-06-01')))
+DISTRIBUTED BY HASH(`id`) BUCKETS 32
+PROPERTIES (
+"replication_allocation" = "tag.location.default: 1",
+"min_load_replica_num" = "-1",
+"is_being_synced" = "false",
+"storage_medium" = "hdd",
+"storage_format" = "V2",
+"inverted_index_storage_format" = "V3",
+"enable_unique_key_merge_on_write" = "true",
+"light_schema_change" = "true",
+"disable_auto_compaction" = "false",
+"enable_single_replica_compaction" = "false",
+"group_commit_interval_ms" = "10000",
+"group_commit_data_bytes" = "134217728",
+"enable_mow_light_delete" = "false"
+);;
+```
+- 诊所日志宽表
+```sql
+CREATE TABLE `fact_cliniclog` (
+  `id` bigint NOT NULL AUTO_INCREMENT(1) COMMENT "自增ID",
+  `come_time` datetime NOT NULL COMMENT "接诊时间",
+  `clinic_id` bigint NOT NULL COMMENT "挂号ID",
+  `patientId` varchar(500) NULL COMMENT "病例号",
+  `patient_name` varchar(5000) NULL COMMENT "姓名",
+  `sex` tinyint NULL COMMENT "性别 1男2女",
+  `birthday` date NULL COMMENT "出生日期",
+  `identity` varchar(500) NULL COMMENT "患者身份",
+  `mobile` varchar(500) NULL COMMENT "患者手机号",
+  `source1` varchar(10000) NULL COMMENT "患者来源1",
+  `source2` varchar(10000) NULL COMMENT "患者来源2",
+  `tag_list` varchar(10000) NULL COMMENT "标签列表",
+  `patient_type` varchar(500) NULL COMMENT "客户类型",
+  `visit_type` tinyint NULL COMMENT "接诊类型 1初诊2复诊3预约4复查",
+  `r_sph` varchar(500) NULL COMMENT "右眼球镜",
+  `l_sph` varchar(500) NULL COMMENT "左眼球镜",
+  `r_nakesight` varchar(500) NULL COMMENT "右眼远视力",
+  `l_nakesight` varchar(500) NULL COMMENT "左眼远视力",
+  `org_id` int NULL COMMENT "诊所ID",
+  `org_name` varchar(500) NULL COMMENT "诊所名称",
+  `appoint_time` datetime NULL COMMENT "预约时间",
+  `add_user` varchar(500) NULL COMMENT "预约创建人",
+  `group_id` int NULL COMMENT "集团ID",
+  `group_name` varchar(500) NULL COMMENT "集团名称",
+  `matter` varchar(10000) NULL COMMENT "预约就诊事项",
+  `accept_doctor_id` int NULL COMMENT "接诊医生ID",
+  `accept_doctor_name` varchar(500) NULL COMMENT "接诊医生",
+  `accept_optometrist_id` int NULL COMMENT "接诊视光师ID",
+  `accept_optometrist_name` varchar(500) NULL COMMENT "接诊视光师",
+  `accept_assistant_id` int NULL COMMENT "医生助理ID",
+  `accept_assistant_name` varchar(500) NULL COMMENT "医生助理",
+  `this_come_number` int NULL COMMENT "当前就诊次数",
+  `inviter` varchar(500) NULL COMMENT "推荐人",
+  `inviter_phone` varchar(500) NULL COMMENT "推荐人手机号",
+  `channel` varchar(500) NULL COMMENT "渠道",
+  `collect_date` date NULL COMMENT "收集日期",
+  `activity_location` varchar(10000) NULL COMMENT "收集点位",
+  `extension_user` varchar(500) NULL COMMENT "推广人员",
+  `extension_user_mobile` varchar(500) NULL COMMENT "推广人员手机号",
+  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",
+  INDEX idx_patientId (`patientId`) USING INVERTED,
+  INDEX idx_mobile (`mobile`) USING INVERTED,
+  INDEX idx_come_time (`come_time`) USING INVERTED,
+  INDEX idx_org_id (`org_id`) USING INVERTED
+) ENGINE=OLAP
+UNIQUE KEY(`id`, `come_time`)
+COMMENT '诊所日志宽表'
+AUTO PARTITION BY RANGE (date_trunc(`come_time`, 'month'))
+(PARTITION p20250101000000 VALUES [('2025-01-01 00:00:00'), ('2025-02-01 00:00:00')),
+PARTITION p20250201000000 VALUES [('2025-02-01 00:00:00'), ('2025-03-01 00:00:00')),
+PARTITION p20250301000000 VALUES [('2025-03-01 00:00:00'), ('2025-04-01 00:00:00')),
+PARTITION p20250401000000 VALUES [('2025-04-01 00:00:00'), ('2025-05-01 00:00:00')),
+PARTITION p20250501000000 VALUES [('2025-05-01 00:00:00'), ('2025-06-01 00:00:00')),
+PARTITION p20250601000000 VALUES [('2025-06-01 00:00:00'), ('2025-07-01 00:00:00')),
+PARTITION p20250701000000 VALUES [('2025-07-01 00:00:00'), ('2025-08-01 00:00:00')),
+PARTITION p20250801000000 VALUES [('2025-08-01 00:00:00'), ('2025-09-01 00:00:00')),
+PARTITION p20250901000000 VALUES [('2025-09-01 00:00:00'), ('2025-10-01 00:00:00')),
+PARTITION p20251001000000 VALUES [('2025-10-01 00:00:00'), ('2025-11-01 00:00:00')),
+PARTITION p20251101000000 VALUES [('2025-11-01 00:00:00'), ('2025-12-01 00:00:00')),
+PARTITION p20251201000000 VALUES [('2025-12-01 00:00:00'), ('2026-01-01 00:00:00')),
+PARTITION p20260101000000 VALUES [('2026-01-01 00:00:00'), ('2026-02-01 00:00:00')),
+PARTITION p20260201000000 VALUES [('2026-02-01 00:00:00'), ('2026-03-01 00:00:00')),
+PARTITION p20260301000000 VALUES [('2026-03-01 00:00:00'), ('2026-04-01 00:00:00')),
+PARTITION p20260401000000 VALUES [('2026-04-01 00:00:00'), ('2026-05-01 00:00:00')),
+PARTITION p20260501000000 VALUES [('2026-05-01 00:00:00'), ('2026-06-01 00:00:00')))
+DISTRIBUTED BY HASH(`id`) BUCKETS 32
+PROPERTIES (
+"replication_allocation" = "tag.location.default: 1",
+"min_load_replica_num" = "-1",
+"is_being_synced" = "false",
+"storage_medium" = "hdd",
+"storage_format" = "V2",
+"inverted_index_storage_format" = "V3",
+"enable_unique_key_merge_on_write" = "true",
+"light_schema_change" = "true",
+"disable_auto_compaction" = "false",
+"enable_single_replica_compaction" = "false",
+"group_commit_interval_ms" = "10000",
+"group_commit_data_bytes" = "134217728",
+"enable_mow_light_delete" = "false"
+);;
+```
+- 销售订单毛利统计表结构
+```sql
+CREATE TABLE `fact_fee_grossprofit` (
+  `id` bigint NOT NULL AUTO_INCREMENT(1) COMMENT "自增ID",
+  `sync_date` date NOT NULL COMMENT "同步日期",
+  `fee_incomedetail_id` int NOT NULL COMMENT "收费明细源表ID",
+  `patientId` varchar(500) NULL COMMENT "病历号",
+  `created` datetime NULL COMMENT "创建时间",
+  `modified` datetime NULL COMMENT "修改时间",
+  `month` varchar(10) NULL COMMENT "月份",
+  `org_name` varchar(500) NULL COMMENT "诊所",
+  `group_id` int NULL COMMENT "集团ID",
+  `group_name` varchar(500) NULL COMMENT "集团名称",
+  `pay_time` datetime NULL COMMENT "收/退费时间",
+  `fee_time` datetime NULL COMMENT "原始购买时间",
+  `clinic_id` int NULL COMMENT "挂号ID",
+  `come_time` datetime NULL COMMENT "到诊时间",
+  `patient_name` varchar(5000) NULL COMMENT "姓名",
+  `patient_mobile` varchar(500) NULL COMMENT "患者手机",
+  `patient_identity` varchar(500) NULL COMMENT "身份",
+  `birthday` date NULL COMMENT "出生日期",
+  `age` int NULL COMMENT "年龄",
+  `source1` varchar(500) NULL COMMENT "一级来源",
+  `source2` varchar(500) NULL COMMENT "二级来源",
+  `inviter_name` varchar(500) NULL COMMENT "介绍人姓名",
+  `inviter_phone` varchar(500) NULL COMMENT "介绍人电话",
+  `first_accept_date` date NULL COMMENT "首诊日期",
+  `first_distance_day` int NULL COMMENT "首诊距今天数",
+  `by_user_name` varchar(500) NULL COMMENT "收银员",
+  `doctor_name` varchar(500) NULL COMMENT "接诊医生",
+  `opt_name` varchar(500) NULL COMMENT "接诊视光师",
+  `assistant_name` varchar(500) NULL COMMENT "接诊医助",
+  `pay_status` varchar(50) NULL COMMENT "交易类型",
+  `order_no` varchar(500) NULL COMMENT "收费单号",
+  `pk_code` varchar(500) NULL COMMENT "诊所唯一ID",
+  `uuid_no` varchar(500) NULL COMMENT "集采唯一ID",
+  `class_name` varchar(500) NULL COMMENT "项目分类",
+  `line_name` varchar(500) NULL COMMENT "产品线",
+  `fee_class` varchar(500) NULL COMMENT "收费分类",
+  `name` varchar(500) NULL COMMENT "收费项目",
+  `combo_name` varchar(500) NULL COMMENT "套餐名称",
+  `flag_memo` varchar(500) NULL COMMENT "收费项目类型",
+  `price` decimal(11,2) NULL COMMENT "单价",
+  `num` int NULL COMMENT "数量",
+  `unit` varchar(500) NULL COMMENT "单位",
+  `sum_price` decimal(11,2) NULL COMMENT "原价",
+  `should_amt` decimal(11,2) NULL COMMENT "应收金额",
+  `cost_amt` decimal(11,2) NULL COMMENT "成本价",
+  `profit_amt` decimal(11,2) NULL COMMENT "毛利",
+  `profit_rate` varchar(50) NULL COMMENT "毛利率",
+  `discount` varchar(50) NULL COMMENT "折扣比例%",
+  `discount_memo` text NULL COMMENT "折扣原因",
+  `coupon_name` varchar(10000) NULL COMMENT "优惠卷",
+  `blq_amt` decimal(11,2) NULL COMMENT "铂林券金额",
+  `hx_sum_price` decimal(11,2) NULL COMMENT "业绩原价",
+  `hx_num` int NULL COMMENT "业绩数量",
+  `hx_amt` decimal(11,2) NULL COMMENT "业绩应收",
+  `hx_cost_price` decimal(11,2) NULL COMMENT "业绩成本",
+  `pay_sum_price` decimal(11,2) NULL COMMENT "实际小计",
+  `pay_num` int NULL COMMENT "实际数量",
+  `pay_amt` decimal(11,2) NULL COMMENT "实际金额",
+  `pay_cost_price` decimal(11,2) NULL COMMENT "实际成本",
+  `pay_profit_amt` decimal(11,2) NULL COMMENT "业绩毛利",
+  `pay_profit_rate` decimal(11,2) NULL COMMENT "业绩毛利率",
+  `factory_name` varchar(500) NULL COMMENT "制造商",
+  `brands_name` varchar(500) NULL COMMENT "品牌",
+  `variety_name` varchar(500) NULL COMMENT "品种",
+  `exec_time` datetime NULL COMMENT "交付时间",
+  `exec_status_name` varchar(50) NULL COMMENT "交付状态名称",
+  `order_status_name` varchar(50) NULL COMMENT "订单状态名称",
+  `cost_flag` int NULL COMMENT "成本标识",
+  `update_time` datetime NULL COMMENT "成本更新时间",
+  `cost_flag2` int NULL COMMENT "成本标识2",
+  `update_time2` datetime NULL COMMENT "成本更新时间2",
+  `exec_status` int NULL COMMENT "交付状态",
+  `flag` int NULL COMMENT "标识",
+  `per_id` int NULL COMMENT "人员id",
+  `order_id` int NULL COMMENT "订单id",
+  `type_flag` int NULL COMMENT "类型标识",
+  `order_status` int NULL COMMENT "订单状态",
+  `sell_time` datetime NULL COMMENT "销售时间"
+) ENGINE=OLAP
+UNIQUE KEY(`id`, `sync_date`)
+COMMENT '销售订单毛利统计宽表'
+AUTO PARTITION BY RANGE (date_trunc(`sync_date`, 'month'))
+(PARTITION p20250101000000 VALUES [('2025-01-01'), ('2025-02-01')),
+PARTITION p20250201000000 VALUES [('2025-02-01'), ('2025-03-01')),
+PARTITION p20250301000000 VALUES [('2025-03-01'), ('2025-04-01')),
+PARTITION p20250401000000 VALUES [('2025-04-01'), ('2025-05-01')),
+PARTITION p20250501000000 VALUES [('2025-05-01'), ('2025-06-01')),
+PARTITION p20250601000000 VALUES [('2025-06-01'), ('2025-07-01')),
+PARTITION p20250701000000 VALUES [('2025-07-01'), ('2025-08-01')),
+PARTITION p20250801000000 VALUES [('2025-08-01'), ('2025-09-01')),
+PARTITION p20250901000000 VALUES [('2025-09-01'), ('2025-10-01')),
+PARTITION p20251001000000 VALUES [('2025-10-01'), ('2025-11-01')),
+PARTITION p20251101000000 VALUES [('2025-11-01'), ('2025-12-01')),
+PARTITION p20251201000000 VALUES [('2025-12-01'), ('2026-01-01')),
+PARTITION p20260101000000 VALUES [('2026-01-01'), ('2026-02-01')),
+PARTITION p20260201000000 VALUES [('2026-02-01'), ('2026-03-01')),
+PARTITION p20260301000000 VALUES [('2026-03-01'), ('2026-04-01')),
+PARTITION p20260401000000 VALUES [('2026-04-01'), ('2026-05-01')),
+PARTITION p20260501000000 VALUES [('2026-05-01'), ('2026-06-01')))
+DISTRIBUTED BY HASH(`id`) BUCKETS 32
+PROPERTIES (
+"replication_allocation" = "tag.location.default: 1",
+"min_load_replica_num" = "-1",
+"is_being_synced" = "false",
+"storage_medium" = "hdd",
+"storage_format" = "V2",
+"inverted_index_storage_format" = "V3",
+"enable_unique_key_merge_on_write" = "true",
+"light_schema_change" = "true",
+"disable_auto_compaction" = "false",
+"enable_single_replica_compaction" = "false",
+"group_commit_interval_ms" = "10000",
+"group_commit_data_bytes" = "134217728",
+"enable_mow_light_delete" = "false"
+);;
+```