结算中心模块
结算中心要开始选择优惠券了~~
表结构如下~~
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey from course.models import Account # Create your models here. __all__ = ["Coupon", "CouponRecord", "Order", "OrderDetail", "TransactionRecord"] class Coupon(models.Model): """优惠券生成规则""" name = models.CharField(max_length=64, verbose_name="活动名称") brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍") coupon_type_choices = ((0, '通用券'), (1, '满减券'), (2, '折扣券')) coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型") money_equivalent_value = models.IntegerField(verbose_name="等值货币", null=True, blank=True) off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True) minimum_consume = models.PositiveIntegerField("最低消费", default=0, help_text="仅在满减券时填写此字段", null=True, blank=True) content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定") # 不绑定代表全局优惠券 content_object = GenericForeignKey('content_type', 'object_id') open_date = models.DateField("优惠券领取开始时间") close_date = models.DateField("优惠券领取结束时间") valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True) valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True) coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True, help_text="自券被领时开始算起") date = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "13. 优惠券生成规则记录" db_table = verbose_name_plural verbose_name = verbose_name_plural def __str__(self): return "%s(%s)" % (self.get_coupon_type_display(), self.name) def save(self, *args, **kwargs): if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date): if self.valid_begin_date and self.valid_end_date: if self.valid_end_date <= self.valid_begin_date: raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ") if self.coupon_valid_days == 0: raise ValueError("coupon_valid_days 有效期不能为0") if self.close_date < self.open_date: raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间 ") super(Coupon, self).save(*args, **kwargs) class CouponRecord(models.Model): """优惠券发放、消费纪录""" coupon = models.ForeignKey("Coupon") number = models.CharField(max_length=64, unique=True, verbose_name="用户优惠券记录的流水号") account = models.ForeignKey(to=Account, verbose_name="拥有者") status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期')) status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间") order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单") # 一个订单可以有多个优惠券 class Meta: verbose_name_plural = "14. 用户优惠券领取使用记录表" db_table = verbose_name_plural verbose_name = verbose_name_plural def __str__(self): return '%s-%s-%s' % (self.account, self.number, self.status) class Order(models.Model): """订单""" payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里')) payment_type = models.SmallIntegerField(choices=payment_type_choices) payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True) order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True) # 考虑到订单合并支付的问题 account = models.ForeignKey(to=Account) actual_amount = models.FloatField(verbose_name="实付金额") status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')) status = models.SmallIntegerField(choices=status_choices, verbose_name="状态") date = models.DateTimeField(auto_now_add=True, verbose_name="订单生成时间") pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款时间") cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="订单取消时间") class Meta: verbose_name_plural = "15. 订单表" db_table = verbose_name_plural verbose_name = verbose_name_plural def __str__(self): return "%s" % self.order_number class OrderDetail(models.Model): """订单详情""" order = models.ForeignKey("Order") content_type = models.ForeignKey(ContentType) # 可关联普通课程或学位 object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') original_price = models.FloatField("课程原价") price = models.FloatField("折后价格") valid_period_display = models.CharField("有效期显示", max_length=32) # 在订单页显示 valid_period = models.PositiveIntegerField("有效期(days)") # 课程有效期 memo = models.CharField(max_length=255, blank=True, null=True, verbose_name="备忘录") def __str__(self): return "%s - %s - %s" % (self.order, self.content_type, self.price) class Meta: verbose_name_plural = "16. 订单详细" db_table = verbose_name_plural verbose_name = verbose_name_plural unique_together = ("order", 'content_type', 'object_id') class TransactionRecord(models.Model): """贝里交易纪录""" account = models.ForeignKey(to=Account) amount = models.IntegerField("金额") balance = models.IntegerField("账户余额") transaction_type_choices = ((0, '收入'), (1, '支出'), (2, '退款'), (3, "提现")) # 2 为了处理 订单过期未支付时,锁定期贝里的回退 transaction_type = models.SmallIntegerField(choices=transaction_type_choices) transaction_number = models.CharField(unique=True, verbose_name="流水号", max_length=128) date = models.DateTimeField(auto_now_add=True) memo = models.CharField(max_length=128, blank=True, null=True, verbose_name="备忘录") class Meta: verbose_name_plural = "17. 贝里交易记录" db_table = verbose_name_plural verbose_name = verbose_name_plural def __str__(self): return "%s" % self.transaction_number
视图代码如下~~
# by gaoxin from rest_framework.views import APIView from rest_framework.response import Response from utils.base_response import BaseResponse from utils.redis_pool import pool from django.utils.timezone import now import redis from utils.authentication import MyAuth from .shop import SHOPPING_CAR_KEY from pay.models import CouponRecord from django.contrib.contenttypes.models import ContentType import json """ 结算中心 在购物车里选择了商品以及价格策略点击结算 才进入结算中心 在结算中心用户可以选择优惠券 传过来的数据 [course_id, course_id] 根据用户id 以及商品id 构建一个存入redis的key SETTLEMENT_KEY = "settlement_%s_%s" 接下来构建结算中心数据结构 redis = { settlement_用户id_课程id: { "id": course.id, 带用户id是为了用户查看购物车的时候需要 "title": course.name, "img": str(course.course_img), "period_display": item.get_valid_period_display(), 价格策略中文 "price": item.price, 价格 "course_coupons": { course_coupon_id: {...} } 用户选中的优惠券id 在更新请求 也就是用户选了优惠券的时候更改 前端默认优惠券可以是不选的 根据业务决定 default_coupon_id: 1 } global_coupon_用户id: { global_coupon_id: {....}, # 在用户进入结算中心选择优惠券的时候 也就是更新请求的时候更改 default_global_coupon_id: 1 } } """ SETTLEMENT_KEY = "settlement_%s_%s" GLOBAL_COUPON_KEY = "global_coupon_%s" REDIS_CONN = redis.Redis(connection_pool=pool) class SettlementView(APIView): """ code为1020开始的都是结算中心的错误 1021 获取课程id失败 """ authentication_classes = [MyAuth, ] # 进入结算中心 也就是用户查看自己的未支付订单 def get(self, request, *args, **kwargs): res = BaseResponse() try: # 1 从redis结算中心获取数据并展示 # 根据用户id 得到 settlement_id_userid_* user_id = request.user.pk settlement_key = SETTLEMENT_KEY % (user_id, "*") # scan_iter 得到所有的key all_settlement_keys = REDIS_CONN.scan_iter(settlement_key) global_coupon_key = GLOBAL_COUPON_KEY % user_id # 2 循环遍历得到所有信息 settlement_list = [] for key in all_settlement_keys: settlement_info = REDIS_CONN.hgetall(key) settlement_list.append(settlement_info) global_coupon_info = REDIS_CONN.hgetall(global_coupon_key) res.data = {"settlement_list": settlement_list, "global_coupon_info": global_coupon_info} except Exception as e: res.code = 1024 res.error = "获取结算中心失败" return Response(res.dict) # 把商品添加到结算中心 用户传过来的数据是 【course_id,course_id】 def post(self, request, *args, **kwargs): res = BaseResponse() # 1 获取前端传过来的数据以及用户id try: course_list = request.data.get("course_list", "") if not course_list: res.code = 1021 res.error = "获取课程id失败" return Response(res.dict) user_id = request.user.pk # 2 检查数据的合法性 查看课程id 是否在购物车中 for course_id in course_list: # course_id = int(course_id) shopping_car_key = SHOPPING_CAR_KEY % (user_id, course_id) if not REDIS_CONN.exists(shopping_car_key): res.code = 1022 res.error = "结算的课程id不合法" return Response(res.dict) # 3 从购物车中获取课程的信息 并构建成结算中心的数据格式放入redis # 3.1 先从redis中拿购物车中的数据 course_info = REDIS_CONN.hgetall(shopping_car_key) # 3.2 获取用户所有符合条件优惠券 # 注意优惠券的使用期限以及状态 now_time = now() # course_table_id = ContentType.objects.filter(app_label="course", model="course").first().pk user_all_coupons = CouponRecord.objects.filter( account_id=user_id, status=0, coupon__valid_begin_date__lte=now_time, coupon__valid_end_date__gte=now_time, # content_type_id=course_table_id, # object_id=course_id ).all() # 3.2 构建数据结构 course_coupons = {} global_coupons = {} for coupon_record in user_all_coupons: # 证明这个优惠券是绑定课程的优惠券 coupon = coupon_record.coupon if coupon.object_id == course_id: course_coupons[coupon.id] = { "name": coupon.name, "coupon_type": coupon.get_coupon_type_display(), "money_equivalent_value": coupon.money_equivalent_value, "off_percent": coupon.off_percent, "minimum_consume": coupon.minimum_consume, "object_id": coupon.object_id, } elseif coupon.object_id == "": global_coupons[coupon.id] = { "name": coupon.name, "coupon_type": coupon.get_coupon_type_display(), "money_equivalent_value": coupon.money_equivalent_value, "off_percent": coupon.off_percent, "minimum_consume": coupon.minimum_consume, "object_id": coupon.object_id, } # print(json.loads(course_info["price_policy_dict"])) price_policy_dict = json.loads(course_info["price_policy_dict"]) default_policy_id = course_info["default_policy"] price = price_policy_dict[default_policy_id]["price"] print(price) period_display = price_policy_dict[default_policy_id]["valid_period_text"] print(period_display) settlement_info = { "id": course_info["id"], "title": course_info["title"], "img_src": course_info["img_src"], "price": price, "period_display": period_display, "course_coupons": json.dumps(course_coupons, ensure_ascii=False), } # 3.3 准备构建结算中心的key settlement_key = SETTLEMENT_KEY % (user_id, course_id) global_coupon_key = GLOBAL_COUPON_KEY % user_id # # 3.4 写入redis REDIS_CONN.hmset(settlement_key, settlement_info) print(global_coupons) if global_coupons: REDIS_CONN.hmset(global_coupon_key, global_coupons) # # 4 加入结算中心成功 清除用户购物车中的相应课程的数据 REDIS_CONN.delete(shopping_car_key) res.data = "加入结算中心成功" except Exception as e: print(e) res.code = 1020 res.error = "结算失败" return Response(res.dict) # 用户在结算中心的时候更改优惠券 # 用户在选择优惠券的时候前端传过来course_id 以及coupon_id # 如果选的是全局优惠券 就传 global_coupon_id def patch(self, request, *args, **kwargs): res = BaseResponse() try: # 1 获取前端传过来的数据 course_id = request.data.get("course_id", "") coupon_id = request.data.get("coupon_id", "") global_coupon_id = request.data.get("global_coupon_id", "") user_id = request.user.pk # 2 校验数据的合法性 # 2.2 校验course_id是否合法 settlement_key = SETTLEMENT_KEY % (user_id, course_id) if not REDIS_CONN.exists(settlement_key): res.code = 1027 res.error = "课程id不合法" return Response(res.dict) # 2.3 校验coupon_id 是否合法 course_info = REDIS_CONN.hgetall(settlement_key) course_coupon_dict = json.loads(course_info["course_coupons"]) if coupon_id not in course_coupon_dict: res.code = 1028 res.error = "课程的优惠券不合法" return Response(res.dict) # 2.4 校验global_coupon_id是否合法 如果合法把默认的全局优惠信息放入redis if global_coupon_id: global_coupon_key = GLOBAL_COUPON_KEY % user_id if not REDIS_CONN.exists(global_coupon_key): res.code = 1029 res.error = "全局优惠券不合法" return Response(res.dict) REDIS_CONN.hset(global_coupon_key, "default_coupon_id", global_coupon_id) # 3 给数据里加入默认选择的课程优惠券 REDIS_CONN.hset(settlement_key, "default_course_coupon_id", coupon_id) res.data = "修改优惠券成功" except Exception as e: res.code = 1026 res.error = "更改优惠券失败" return Response(res.dict)