diff --git a/backend/app/routers/user_order.py b/backend/app/routers/user_order.py index 34043a9..56f97f4 100644 --- a/backend/app/routers/user_order.py +++ b/backend/app/routers/user_order.py @@ -298,66 +298,39 @@ async def notify(request: Request): success_time = resp.get('success_time') payer = resp.get('payer') amount = resp.get('amount').get('total') + print(float(amount/100)) + + # 获取数据库连接 + connection = get_connection() + cursor = connection.cursor(dictionary=True) + + # 查询订单 + cursor.execute(""" + SELECT order_id, payable_price + FROM orders + WHERE out_trade_no = %s + FOR UPDATE""", (out_trade_no,)) + order = cursor.fetchone() + + # 更新订单状态 + cursor.execute(""" + UPDATE orders SET + order_status = 'completed', + payment_method = 'wechat', + paid_price = %s, + wx_transaction_id = %s, + settlement_time = NOW() + WHERE order_id = %s""", + (float(amount/100),transaction_id, order['order_id'])) + + connection.commit() + if cursor: + cursor.close() + if connection: + connection.close() return JSONResponse(content={'code': 'SUCCESS', 'message': '成功'}, status_code=200) else: - return JSONResponse(content={'code': 'FAILED', 'message': '失败'}, status_code=500) -# async def wxpay_notify(request: Request): - - # connection = None - # cursor = None - # try: - # # 获取回调数据 - # headers = dict(request.headers) - # data = await request.body() - # - # # 解密回调数据 - # result = wxpay.callback(headers, data) - # if result and result.get('event_type') == 'TRANSACTION.SUCCESS': - # print("收到支付成功信息") - # print(result) - # out_trade_no = result.get('out_trade_no') - # print(out_trade_no) - # transaction_id = result.get('transaction_id') - # print(transaction_id) - # - # - # # 获取数据库连接 - # # connection = get_connection() - # # cursor = connection.cursor(dictionary=True) - # # - # # # 查询订单并锁定 - # # cursor.execute(""" - # # SELECT order_id, payable_price - # # FROM orders - # # WHERE out_trade_no = %s - # # FOR UPDATE""", (out_trade_no,)) - # # order = cursor.fetchone() - # # - # # cursor.execute(""" - # # UPDATE orders SET - # # order_status = 'completed', - # # payment_method = 'wechat', - # # wx_transaction_id = %s, - # # settlement_time = NOW() - # # WHERE order_id = %s""", - # # (transaction_id, order['order_id'])) - # # - # # - # # connection.commit() - # - # # 返回微信要求的成功响应 - # return JSONResponse(content={"code": "SUCCESS", "message": "OK"}) - # - # except Exception as e: - # logging.error(f"回调处理异常: {str(e)}", exc_info=True) - # # if connection: - # # connection.rollback() - # return JSONResponse(content={"code": "FAIL", "message": "系统错误"}, status_code=500) - # # finally: - # # if cursor: - # # cursor.close() - # # if connection: - # # connection.close() + return JSONResponse(content={'code': 'FAILED', 'message': '失败'}, status_code=500) \ No newline at end of file diff --git a/backend/app/services/admin_order_service.py b/backend/app/services/admin_order_service.py index 255d17f..96c1a40 100644 --- a/backend/app/services/admin_order_service.py +++ b/backend/app/services/admin_order_service.py @@ -1,16 +1,15 @@ from fastapi import HTTPException -from datetime import datetime -from decimal import Decimal, ROUND_HALF_UP +from decimal import Decimal from ..db import get_connection from ..utils.jwt_handler import verify_token import configparser from datetime import datetime import pytz +from ..utils import calculate_price # 读取配置文件 config = configparser.ConfigParser() config.read('backend/config.conf') -unit_price = Decimal(config.get('price', 'unit_price')) points_rate = Decimal(config.get('price', 'points_rate')) get_points_rate = Decimal(config.get('price', 'get_points_rate')) @@ -79,53 +78,51 @@ def query_orders_by_status_and_range(token: str, start: int, end: int, order_sta connection.close() def complete_order(token: str, order_id: int, end_datetime: datetime): - """结束订单并计算原始价格""" + """结束订单并计算最终价格""" verify_admin_permission(token) connection = get_connection() try: with connection.cursor(dictionary=True) as cursor: + # 查询订单基础信息(确保订单存在并锁定) cursor.execute(""" - SELECT o.start_datetime, t.price AS table_price, - COALESCE(g.price, 1) AS game_price, o.num_players -- 处理空值情况 - FROM orders o - JOIN game_tables t ON o.game_table_id = t.table_id - LEFT JOIN games g ON o.game_id = g.game_id - WHERE o.order_id = %s - FOR UPDATE - """, (order_id,)) + SELECT start_datetime + FROM orders + WHERE order_id = %s + FOR UPDATE + """, (order_id,)) order = cursor.fetchone() - # 将数据库中的时间和输入的时间转换为 naive(移除时区信息) + if not order: + raise HTTPException(status_code=404, detail="订单不存在") + + # 处理时间 db_start = order['start_datetime'].replace(tzinfo=None) input_end = end_datetime.replace(tzinfo=None) - # 计算游戏时长(分钟),duration_minutes 是 float 类型 + # 计算游戏时长(分钟) duration_minutes = (input_end - db_start).total_seconds() / 60 - # 将时长转换为 Decimal 类型,避免与 Decimal 相乘时报错 duration_minutes_dec = Decimal(str(duration_minutes)) - # 计算原始价格(假设 table_price 单位为每分钟价格) - base_price = ( - order['table_price'] * - order['game_price'] * - order['num_players'] * - duration_minutes_dec * - unit_price - ) - print(f"Base Price: {base_price}") - # 更新订单信息 + # 更新订单状态并设置结束时间 cursor.execute(""" UPDATE orders SET end_datetime = %s, - payable_price = %s, - game_process_time = %s, + game_process_time = %s, order_status = 'pending' WHERE order_id = %s - """, (end_datetime, base_price, duration_minutes_dec, order_id)) + """, (end_datetime, duration_minutes_dec, order_id)) + # 提交事务,确保订单已结束 connection.commit() - return {"message": "订单已结束待结算", "base_price": base_price} + + # 调用价格计算逻辑(会自动写入数据库) + success = calculate_price.calculate_order_price(order_id) + + if not success: + raise HTTPException(status_code=500, detail="订单价格计算失败") + + return {"message": "订单已结束待结算"} except Exception as e: connection.rollback() @@ -133,6 +130,7 @@ def complete_order(token: str, order_id: int, end_datetime: datetime): finally: connection.close() + def settle_order(token: str, order_id: int, used_points: int = 0, coupon_id: int = None): """结算最终订单""" verify_admin_permission(token) diff --git a/backend/app/services/user_order_service.py b/backend/app/services/user_order_service.py index 28bc636..26c23cd 100644 --- a/backend/app/services/user_order_service.py +++ b/backend/app/services/user_order_service.py @@ -4,12 +4,14 @@ from decimal import Decimal from ..db import get_connection from ..utils.jwt_handler import verify_token import configparser +from ..utils import calculate_price config = configparser.ConfigParser() config.read('backend/config.conf') unit_price = Decimal(config.get('price', 'unit_price')) points_rate = Decimal(config.get('price', 'points_rate')) get_points_rate = Decimal(config.get('price', 'get_points_rate')) +strategy_id = int(config.get('now_price', 'strategy_id')) def get_user_active_order(token: str): try: @@ -86,14 +88,14 @@ def create_user_order(token: str, table_id: int, num_players: int): raise HTTPException(status_code=404, detail="用户不存在") # 检查桌子占用 - cursor.execute(""" - SELECT EXISTS( - SELECT 1 FROM orders - WHERE game_table_id = %s - AND order_status = 'in_progress' - )""", (table_id,)) - if cursor.fetchone()[0]: - raise HTTPException(status_code=400, detail="桌子已被占用") + # cursor.execute(""" + # SELECT EXISTS( + # SELECT 1 FROM orders + # WHERE game_table_id = %s + # AND order_status = 'in_progress' + # )""", (table_id,)) + # if cursor.fetchone()[0]: + # raise HTTPException(status_code=400, detail="桌子已被占用") # 创建订单 order_date = datetime.now().date() @@ -103,9 +105,9 @@ def create_user_order(token: str, table_id: int, num_players: int): INSERT INTO orders ( order_id, user_id, game_table_id, order_date, start_datetime, num_players, - order_status - ) VALUES (%s, %s, %s, %s, %s, %s, %s) - """, (order_id, user[0], table_id, order_date, start_datetime, num_players, 'in_progress')) + order_status, pricing_strategy_id + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + """, (order_id, user[0], table_id, order_date, start_datetime, num_players, 'in_progress', strategy_id)) connection.commit() return {"order_id": order_id, "message": "订单创建成功"} @@ -118,8 +120,9 @@ def create_user_order(token: str, table_id: int, num_players: int): def complete_user_order(token: str, order_id: int): - """完成用户订单""" + """完成用户订单(计算最终价格)""" try: + # 验证用户身份 payload = verify_token(token) phone_number = payload["sub"] except ValueError as e: @@ -127,66 +130,54 @@ def complete_user_order(token: str, order_id: int): connection = get_connection() try: - cursor = connection.cursor(dictionary=True) - # 修改后的SQL查询 - cursor.execute(""" - SELECT - o.user_id, - t.price AS table_price, - COALESCE(g.price, 1.0) AS game_price, - o.num_players, - o.start_datetime - FROM orders o - JOIN game_tables t ON o.game_table_id = t.table_id - LEFT JOIN games g ON o.game_id = g.game_id - WHERE o.order_id = %s - FOR UPDATE - """, (order_id,)) - order = cursor.fetchone() + with connection.cursor(dictionary=True) as cursor: + # 获取订单信息 + cursor.execute(""" + SELECT o.user_id, o.start_datetime + FROM orders o + WHERE o.order_id = %s + FOR UPDATE + """, (order_id,)) + order = cursor.fetchone() - if not order: - raise HTTPException(status_code=404, detail="订单不存在") + if not order: + raise HTTPException(status_code=404, detail="订单不存在") - # 验证用户权限 - cursor.execute("SELECT user_id FROM users WHERE phone_number = %s", (phone_number,)) - user = cursor.fetchone() - if order['user_id'] != user['user_id']: - raise HTTPException(status_code=403, detail="无权操作此订单") + # 验证用户权限 + cursor.execute("SELECT user_id FROM users WHERE phone_number = %s", (phone_number,)) + user = cursor.fetchone() + if not user or order['user_id'] != user['user_id']: + raise HTTPException(status_code=403, detail="无权操作此订单") - # 计算时长(优化精度处理) - end_datetime = datetime.now() - duration = end_datetime - order['start_datetime'] - duration_minutes = duration.total_seconds() / 60 - duration_minutes_dec = Decimal(duration_minutes).quantize(Decimal('0.0000')) + # 计算订单时长 + end_datetime = datetime.now() + duration_minutes = (end_datetime - order['start_datetime']).total_seconds() / 60 + duration_minutes_dec = Decimal(str(duration_minutes)) - # 直接使用Decimal类型计算 - unit_price = Decimal('0.1667') # 示例单位价格(需按实际调整) - base_price = ( - order['table_price'] * - order['game_price'] * - order['num_players'] * - duration_minutes_dec * - unit_price - ) - total_price = round(base_price, 2) - - # 使用Decimal类型更新数据库 - cursor.execute(""" - UPDATE orders SET - end_datetime = %s, - payable_price = %s, - game_process_time = %s, - order_status = 'pending' - WHERE order_id = %s - """, (end_datetime, total_price, duration_minutes_dec, order_id)) + # 先更新订单状态 + cursor.execute(""" + UPDATE orders + SET end_datetime = %s, + game_process_time = %s, + order_status = 'pending' + WHERE order_id = %s + """, (end_datetime, duration_minutes_dec, order_id)) + # 提交事务,确保订单结束 connection.commit() - return {"message": "订单已结束待结算", "base_price": float(base_price)} + + # 调用价格计算逻辑(会自动写入数据库) + success = calculate_price.calculate_order_price(order_id) + + if not success: + raise HTTPException(status_code=500, detail="订单价格计算失败") + + return {"message": "订单已结束待结算"} + except Exception as e: connection.rollback() raise HTTPException(status_code=500, detail=str(e)) finally: - cursor.close() connection.close() diff --git a/backend/app/services/user_table_service.py b/backend/app/services/user_table_service.py index bc00c9a..c67d4ae 100644 --- a/backend/app/services/user_table_service.py +++ b/backend/app/services/user_table_service.py @@ -38,7 +38,7 @@ def check_table_occupancy_service(table_id: int): ) AS is_occupied """, (table_id,)) result = cursor.fetchone() - return {"is_occupied": bool(result['is_occupied'])} + return {"is_occupied": "false"} # 不检测桌子状态 except Exception as e: raise HTTPException(status_code=500, detail=str(e)) finally: diff --git a/backend/app/utils/calculate_price.py b/backend/app/utils/calculate_price.py new file mode 100644 index 0000000..6f00d05 --- /dev/null +++ b/backend/app/utils/calculate_price.py @@ -0,0 +1,128 @@ +from datetime import datetime, timedelta +from decimal import Decimal +import configparser +from fastapi import HTTPException +from backend.app.db import get_connection + +def get_pricing_strategy(strategy_id, cursor): + """获取指定 ID 的价格策略""" + cursor.execute(""" + SELECT segment1_threshold, segment1_price, + segment2_threshold, segment2_price, + segment3_price, segment3_threshold + FROM pricing_strategies + WHERE strategy_id = %s + """, (strategy_id,)) + return cursor.fetchone() + +def get_table_pricing_strategy(strategy_id, cursor): + """获取指定 ID 的桌费策略""" + cursor.execute(""" + SELECT segment1_threshold, segment1_price, + segment2_threshold, segment2_price, + segment3_price, segment3_threshold + FROM table_pricing_strategies + WHERE strategy_id = %s + """, (strategy_id,)) + return cursor.fetchone() + +def calculate_segmented_price(duration, strategy): + """ + 计算按照分段收费策略的价格: + - duration: 总时长(分钟) + - strategy: 包含分段时间和价格的策略字典 + """ + price = Decimal(0) + s1, p1, s2, p2, p3, s3 = strategy.values() + + if duration <= s1: + price = duration * p1 + elif duration <= s2: + price = (s1 * p1) + ((duration - s1) * p2) + else: + # 超过 s2 但小于 s3(如果 s3 设定) + if s3 is not None and duration > s3: + duration = s3 # 限制最大计费时间 + price = (s1 * p1) + ((s2 - s1) * p2) + ((duration - s2) * p3) + + return price + +def calculate_overtime_fee(start_datetime, end_datetime): + """计算超时费用(深夜时段收费)""" + config = configparser.ConfigParser() + config.read('backend/config.conf') + stay_up_late = config['stay_up_late'] + start_time = datetime.strptime(stay_up_late['start_time'], '%H:%M:%S').time() + end_time = datetime.strptime(stay_up_late['end_time'], '%H:%M:%S').time() + price_per_minute = Decimal(stay_up_late['price_minutes']) + + overtime_fee = Decimal(0) + current_time = start_datetime + + while current_time < end_datetime: + if start_time <= current_time.time() or current_time.time() < end_time: + overtime_fee += price_per_minute + current_time += timedelta(minutes=1) + + return overtime_fee + +def calculate_order_price(order_id): + """计算订单价格并写入数据库""" + connection = get_connection() + try: + with connection.cursor(dictionary=True) as cursor: + # 获取订单信息 + cursor.execute(""" + SELECT o.start_datetime, o.end_datetime, o.num_players, + o.pricing_strategy_id, t.table_pricing_strategy_id, + o.game_table_id + FROM orders o + JOIN game_tables t ON o.game_table_id = t.table_id + WHERE o.order_id = %s + FOR UPDATE + """, (order_id,)) + order = cursor.fetchone() + + if not order or not order['end_datetime']: + raise HTTPException(status_code=404, detail="订单不存在或未完成") + + # 计算游戏时长(分钟) + start_time = order['start_datetime'].replace(tzinfo=None) + end_time = order['end_datetime'].replace(tzinfo=None) + duration = (end_time - start_time).total_seconds() / 60 + duration_dec = Decimal(str(duration)) + + # 获取价格策略 + pricing_strategy = get_pricing_strategy(order['pricing_strategy_id'], cursor) + table_strategy = get_table_pricing_strategy(order['table_pricing_strategy_id'], cursor) + + if not pricing_strategy or not table_strategy: + raise HTTPException(status_code=400, detail="价格策略未找到") + + # 计算价格 + unit_price = calculate_segmented_price(duration_dec, pricing_strategy) + table_price = calculate_segmented_price(duration_dec, table_strategy) + + # 计算总价格 + base_price = (unit_price * order['num_players']) + table_price + overtime_fee = calculate_overtime_fee(start_time, end_time) + total_price = base_price + overtime_fee + + # 更新订单价格 + cursor.execute(""" + UPDATE orders + SET payable_price = %s, + game_process_time = %s, + overtime_fee = %s + WHERE order_id = %s + """, (total_price, duration_dec, overtime_fee, order_id)) + + connection.commit() + return True + + except Exception as e: + connection.rollback() + print(f"Error calculating order price: {e}") + return False + finally: + connection.close()