将文件夹命名从拼音改为英文,整理了一些代码,将导入语句迁移到了头部
This commit is contained in:
0
shop/__init__.py
Normal file
0
shop/__init__.py
Normal file
3
shop/admin.py
Normal file
3
shop/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
shop/apps.py
Normal file
6
shop/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ShangdianConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'shangdian'
|
||||
92
shop/migrations/0001_initial.py
Normal file
92
shop/migrations/0001_initial.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-21 12:36
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('shangpin', '0010_shangpinleixing_is_cross_enabled'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='YonghuPingzheng',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='自增主键ID')),
|
||||
('zhanghao', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='登录账号')),
|
||||
('mima', models.CharField(max_length=255, verbose_name='密码')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('yonghu', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='yonghuid', verbose_name='关联用户ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户认证凭证',
|
||||
'verbose_name_plural': '用户认证凭证',
|
||||
'db_table': 'yonghu_pingzheng',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Dianpu',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='店铺自增ID')),
|
||||
('dianpu_mingcheng', models.CharField(max_length=50, verbose_name='店铺名称')),
|
||||
('dianpu_touxiang', models.CharField(blank=True, max_length=500, null=True, verbose_name='店铺头像URL')),
|
||||
('lianxi_dianhua', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系电话')),
|
||||
('weixinhao', models.CharField(blank=True, max_length=50, null=True, verbose_name='微信号')),
|
||||
('zhuangtai', models.IntegerField(choices=[(0, '封禁'), (1, '正常')], default=1, verbose_name='店铺状态')),
|
||||
('bangding_yonghushu', models.PositiveIntegerField(default=0, verbose_name='绑定用户总数')),
|
||||
('erweima_url', models.CharField(blank=True, max_length=500, null=True, verbose_name='店铺二维码相对URL')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('pingzheng', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='shangdian.yonghupingzheng', verbose_name='关联用户凭证')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '平台商家店铺',
|
||||
'verbose_name_plural': '平台商家店铺',
|
||||
'db_table': 'dianpu',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='YonghuDianpuBangding',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='绑定自增ID')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('dianpu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangdian.dianpu', verbose_name='关联店铺ID')),
|
||||
('yonghu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='yonghuid', verbose_name='用户ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户店铺绑定关系',
|
||||
'verbose_name_plural': '用户店铺绑定关系',
|
||||
'db_table': 'yonghu_dianpu_bangding',
|
||||
'unique_together': {('yonghu', 'dianpu')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ShangpinLeixingDianpu',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='类型自增ID')),
|
||||
('jieshao', models.CharField(max_length=45, verbose_name='类型介绍')),
|
||||
('tupian_url', models.CharField(blank=True, max_length=500, null=True, verbose_name='类型图片URL')),
|
||||
('paixu', models.IntegerField(default=0, verbose_name='排序权重')),
|
||||
('shangjia_zhuangtai', models.BooleanField(default=True, verbose_name='是否上架')),
|
||||
('fengjin_zhuangtai', models.BooleanField(default=False, verbose_name='是否封禁')),
|
||||
('shenhe_zhuangtai', models.BooleanField(default=False, help_text='True=审核通过,False=未审核', verbose_name='平台审核状态')),
|
||||
('dianpu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangdian.dianpu', verbose_name='所属店铺')),
|
||||
('gonggong_leixing', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shangpin.shangpinleixing', verbose_name='映射的公共商品类型')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '店铺商品类型映射',
|
||||
'verbose_name_plural': '店铺商品类型映射',
|
||||
'db_table': 'shangdian_shangpin_leixing',
|
||||
'indexes': [models.Index(fields=['dianpu'], name='shangdian_s_dianpu__100f93_idx'), models.Index(fields=['gonggong_leixing'], name='shangdian_s_gonggon_88a9a0_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
18
shop/migrations/0002_alter_yonghupingzheng_yonghu.py
Normal file
18
shop/migrations/0002_alter_yonghupingzheng_yonghu.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-21 13:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shangdian', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='yonghupingzheng',
|
||||
name='yonghu',
|
||||
field=models.CharField(db_index=True, max_length=7, unique=True, verbose_name='关联用户ID'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,85 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-23 14:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shangdian', '0002_alter_yonghupingzheng_yonghu'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DianpuMorenPeizhi',
|
||||
fields=[
|
||||
('id', models.PositiveSmallIntegerField(default=1, editable=False, primary_key=True, serialize=False)),
|
||||
('moren_choucheng_feilv', models.DecimalField(decimal_places=4, default=0.1, max_digits=5, verbose_name='默认订单抽成费率(小数,0.1000=10%)')),
|
||||
('moren_tixian_xiane', models.DecimalField(decimal_places=2, default=1000.0, max_digits=10, verbose_name='默认可提现限额')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '店铺默认配置',
|
||||
'verbose_name_plural': '店铺默认配置',
|
||||
'db_table': 'dianpu_moren_peizhi',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='kaiqi_meiri_xiane',
|
||||
field=models.BooleanField(default=False, verbose_name='是否开启每日提现限额'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='kaiqi_shouyi_choucheng',
|
||||
field=models.BooleanField(default=False, verbose_name='是否开启店铺订单收益抽成'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='ketixian_yue',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='店铺可提现余额'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='meiri_tixian_xiane',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, default=0.0, max_digits=10, null=True, verbose_name='店铺每日提现限额'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='shouyi_choucheng_feilv',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, default=0.0, max_digits=5, null=True, verbose_name='店铺订单收益抽成费率(小数,如0.1000=10%)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='zhengqu_zonge',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='店铺赚取总额'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DianpuShouzhiMeiriTongji',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('dianpu_id', models.PositiveIntegerField(db_index=True, verbose_name='店铺ID')),
|
||||
('riqi', models.DateField(db_index=True, verbose_name='统计日期')),
|
||||
('nian', models.PositiveSmallIntegerField(verbose_name='年份')),
|
||||
('yue', models.PositiveSmallIntegerField(verbose_name='月份')),
|
||||
('ri', models.PositiveSmallIntegerField(verbose_name='日')),
|
||||
('xiadan_zongliang', models.PositiveIntegerField(default=0, verbose_name='下单总量')),
|
||||
('xiadan_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='下单总额')),
|
||||
('chengjiao_zongliang', models.PositiveIntegerField(default=0, verbose_name='成交总量')),
|
||||
('chengjiao_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='成交总额')),
|
||||
('tuikuan_zongliang', models.PositiveIntegerField(default=0, verbose_name='退款总量')),
|
||||
('tuikuan_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总额')),
|
||||
('shouyi_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='收益总额')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '店铺每日收支统计',
|
||||
'verbose_name_plural': '店铺每日收支统计',
|
||||
'db_table': 'dianpu_shouzhi_meiri_tongji',
|
||||
'indexes': [models.Index(fields=['dianpu_id', 'riqi'], name='dianpu_shou_dianpu__bffdb2_idx'), models.Index(fields=['nian', 'yue', 'ri'], name='dianpu_shou_nian_a0f415_idx')],
|
||||
'unique_together': {('dianpu_id', 'riqi')},
|
||||
},
|
||||
),
|
||||
]
|
||||
27
shop/migrations/0004_dianpushangpinshenheshezhi.py
Normal file
27
shop/migrations/0004_dianpushangpinshenheshezhi.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 4.2.27 on 2026-04-25 12:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shangdian', '0003_dianpumorenpeizhi_dianpu_kaiqi_meiri_xiane_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DianpuShangpinShenheShezhi',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('kaiqi_shenhe', models.BooleanField(default=False, verbose_name='是否开启商品审核模式')),
|
||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品审核模式配置',
|
||||
'verbose_name_plural': '商品审核模式配置',
|
||||
'db_table': 'dianpu_shangpin_shenhe_shezhi',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.27 on 2026-05-05 10:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shangdian', '0004_dianpushangpinshenheshezhi'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='fenhong_choucheng_feilv',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, default=0.05, max_digits=5, null=True, verbose_name='上级分红抽成费率(默认5%)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='shangji_fenhong_leiji',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='作为上级累计获得分红'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='shangji_ketixian_fenhong',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='作为上级可提现分红余额'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpu',
|
||||
name='yaoqing_dianpu_id',
|
||||
field=models.PositiveIntegerField(blank=True, db_index=True, null=True, verbose_name='邀请人店铺ID(上级)'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
# Generated by Django 4.2.27 on 2026-05-10 18:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shangdian', '0005_dianpu_fenhong_choucheng_feilv_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dianpushouzhimeiritongji',
|
||||
name='gei_shangji_cishu',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='给上级分红次数'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpushouzhimeiritongji',
|
||||
name='gei_shangji_zonge',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='给上级分红总额'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpushouzhimeiritongji',
|
||||
name='shangji_fenhong_cishu',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='作为上级获得分红次数'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpushouzhimeiritongji',
|
||||
name='shangji_fenhong_zonge',
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='作为上级获得分红总额'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dianpushouzhimeiritongji',
|
||||
name='shangji_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='当天对应的上级店铺ID(若无则为空)'),
|
||||
),
|
||||
]
|
||||
0
shop/migrations/__init__.py
Normal file
0
shop/migrations/__init__.py
Normal file
227
shop/models.py
Normal file
227
shop/models.py
Normal file
@@ -0,0 +1,227 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class YonghuPingzheng(models.Model):
|
||||
id = models.AutoField(primary_key=True, verbose_name='自增主键ID')
|
||||
yonghu = models.CharField(
|
||||
max_length=7,
|
||||
unique=True, # 确保一个用户只有一条凭证记录
|
||||
db_index=True,
|
||||
verbose_name='关联用户ID'
|
||||
)
|
||||
zhanghao = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='登录账号')
|
||||
mima = models.CharField(max_length=255, verbose_name='密码')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'yonghu_pingzheng'
|
||||
verbose_name = '用户认证凭证'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
class Dianpu(models.Model):
|
||||
id = models.AutoField(primary_key=True, verbose_name='店铺自增ID')
|
||||
pingzheng = models.OneToOneField(
|
||||
'shangdian.YonghuPingzheng', # 同应用内可简写,但完整路径更清晰
|
||||
on_delete=models.CASCADE,
|
||||
db_index=True,
|
||||
verbose_name='关联用户凭证'
|
||||
)
|
||||
dianpu_mingcheng = models.CharField(max_length=50, verbose_name='店铺名称')
|
||||
dianpu_touxiang = models.CharField(max_length=500, null=True, blank=True, verbose_name='店铺头像URL')
|
||||
|
||||
lianxi_dianhua = models.CharField(max_length=20, null=True, blank=True, verbose_name='联系电话')
|
||||
weixinhao = models.CharField(max_length=50, null=True, blank=True, verbose_name='微信号')
|
||||
zhuangtai = models.IntegerField(choices=[(0, '封禁'), (1, '正常')], default=1, verbose_name='店铺状态')
|
||||
bangding_yonghushu = models.PositiveIntegerField(default=0, verbose_name='绑定用户总数')
|
||||
erweima_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='店铺二维码相对URL')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
kaiqi_shouyi_choucheng = models.BooleanField(default=False, verbose_name='是否开启店铺订单收益抽成')
|
||||
shouyi_choucheng_feilv = models.DecimalField(max_digits=5, decimal_places=4, null=True, blank=True, default=0.0000,
|
||||
verbose_name='店铺订单收益抽成费率(小数,如0.1000=10%)')
|
||||
ketixian_yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='店铺可提现余额')
|
||||
zhengqu_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='店铺赚取总额')
|
||||
meiri_tixian_xiane = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, default=0.00,
|
||||
verbose_name='店铺每日提现限额')
|
||||
kaiqi_meiri_xiane = models.BooleanField(default=False, verbose_name='是否开启每日提现限额')
|
||||
|
||||
|
||||
yaoqing_dianpu_id = models.PositiveIntegerField(null=True, blank=True, db_index=True,
|
||||
verbose_name='邀请人店铺ID(上级)')
|
||||
fenhong_choucheng_feilv = models.DecimalField(max_digits=5, decimal_places=4, null=True, blank=True, default=0.0500,
|
||||
verbose_name='上级分红抽成费率(默认5%)')
|
||||
shangji_fenhong_leiji = models.DecimalField(max_digits=12, decimal_places=2, default=0.00,
|
||||
verbose_name='作为上级累计获得分红')
|
||||
shangji_ketixian_fenhong = models.DecimalField(max_digits=12, decimal_places=2, default=0.00,
|
||||
verbose_name='作为上级可提现分红余额')
|
||||
|
||||
class Meta:
|
||||
db_table = 'dianpu'
|
||||
verbose_name = '平台商家店铺'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DianpuMorenPeizhi(models.Model):
|
||||
id = models.PositiveSmallIntegerField(primary_key=True, default=1, editable=False)
|
||||
moren_choucheng_feilv = models.DecimalField(max_digits=5, decimal_places=4, default=0.1000, verbose_name='默认订单抽成费率(小数,0.1000=10%)')
|
||||
moren_tixian_xiane = models.DecimalField(max_digits=10, decimal_places=2, default=1000.00, verbose_name='默认可提现限额')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'dianpu_moren_peizhi'
|
||||
verbose_name = '店铺默认配置'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f"默认配置 - 费率:{self.moren_choucheng_feilv} 限额:{self.moren_tixian_xiane}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.id = 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DianpuShouzhiMeiriTongji(models.Model):
|
||||
dianpu_id = models.PositiveIntegerField(db_index=True, verbose_name='店铺ID')
|
||||
riqi = models.DateField(db_index=True, verbose_name='统计日期')
|
||||
nian = models.PositiveSmallIntegerField(verbose_name='年份')
|
||||
yue = models.PositiveSmallIntegerField(verbose_name='月份')
|
||||
ri = models.PositiveSmallIntegerField(verbose_name='日')
|
||||
xiadan_zongliang = models.PositiveIntegerField(default=0, verbose_name='下单总量')
|
||||
xiadan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='下单总额')
|
||||
chengjiao_zongliang = models.PositiveIntegerField(default=0, verbose_name='成交总量')
|
||||
chengjiao_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='成交总额')
|
||||
tuikuan_zongliang = models.PositiveIntegerField(default=0, verbose_name='退款总量')
|
||||
tuikuan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总额')
|
||||
shouyi_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='收益总额')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
shangji_fenhong_cishu = models.PositiveIntegerField(default=0, verbose_name='作为上级获得分红次数')
|
||||
shangji_fenhong_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00,
|
||||
verbose_name='作为上级获得分红总额')
|
||||
|
||||
# ---------- 作为下级,给上级的分红记录 ----------
|
||||
shangji_id = models.PositiveIntegerField(null=True, blank=True, verbose_name='当天对应的上级店铺ID(若无则为空)')
|
||||
gei_shangji_cishu = models.PositiveIntegerField(default=0, verbose_name='给上级分红次数')
|
||||
gei_shangji_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00,
|
||||
verbose_name='给上级分红总额')
|
||||
|
||||
|
||||
class Meta:
|
||||
db_table = 'dianpu_shouzhi_meiri_tongji'
|
||||
verbose_name = '店铺每日收支统计'
|
||||
verbose_name_plural = verbose_name
|
||||
unique_together = [['dianpu_id', 'riqi']]
|
||||
indexes = [
|
||||
models.Index(fields=['dianpu_id', 'riqi']),
|
||||
models.Index(fields=['nian', 'yue', 'ri']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"店铺ID:{self.dianpu_id} - {self.riqi}"
|
||||
|
||||
def clean(self):
|
||||
if self.riqi:
|
||||
self.nian = self.riqi.year
|
||||
self.yue = self.riqi.month
|
||||
self.ri = self.riqi.day
|
||||
super().clean()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
class YonghuDianpuBangding(models.Model):
|
||||
id = models.AutoField(primary_key=True, verbose_name='绑定自增ID')
|
||||
yonghu = models.ForeignKey(
|
||||
'yonghu.UserMain', # 🔥 完整路径
|
||||
to_field='yonghuid',
|
||||
on_delete=models.CASCADE,
|
||||
db_index=True,
|
||||
verbose_name='用户ID'
|
||||
)
|
||||
dianpu = models.ForeignKey('Dianpu', on_delete=models.CASCADE, db_index=True, verbose_name='关联店铺ID')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'yonghu_dianpu_bangding'
|
||||
verbose_name = '用户店铺绑定关系'
|
||||
verbose_name_plural = verbose_name
|
||||
unique_together = [['yonghu', 'dianpu']]
|
||||
|
||||
|
||||
# shangdian/models.py
|
||||
|
||||
class ShangpinLeixingDianpu(models.Model):
|
||||
"""
|
||||
店铺级商品类型映射表
|
||||
与公共商品类型表 ShangpinLeixing 强制关联,用于店铺自定义类型
|
||||
"""
|
||||
id = models.AutoField(primary_key=True, verbose_name='类型自增ID')
|
||||
dianpu = models.ForeignKey(
|
||||
'shangdian.Dianpu',
|
||||
on_delete=models.CASCADE,
|
||||
db_index=True,
|
||||
verbose_name='所属店铺'
|
||||
)
|
||||
# 🔥 强制映射到公共商品类型,不可为空
|
||||
gonggong_leixing = models.ForeignKey(
|
||||
'shangpin.ShangpinLeixing',
|
||||
on_delete=models.CASCADE,
|
||||
db_index=True,
|
||||
verbose_name='映射的公共商品类型'
|
||||
)
|
||||
jieshao = models.CharField(max_length=45, verbose_name='类型介绍')
|
||||
tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='类型图片URL')
|
||||
paixu = models.IntegerField(default=0, verbose_name='排序权重')
|
||||
shangjia_zhuangtai = models.BooleanField(default=True, verbose_name='是否上架')
|
||||
fengjin_zhuangtai = models.BooleanField(default=False, verbose_name='是否封禁')
|
||||
shenhe_zhuangtai = models.BooleanField(default=False, verbose_name='平台审核状态',help_text='True=审核通过,False=未审核')
|
||||
|
||||
class Meta:
|
||||
db_table = 'shangdian_shangpin_leixing'
|
||||
verbose_name = '店铺商品类型映射'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
models.Index(fields=['dianpu']),
|
||||
models.Index(fields=['gonggong_leixing']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.dianpu.dianpu_mingcheng} - {self.jieshao}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# shangdian/models.py 或独立应用 models.py
|
||||
class DianpuShangpinShenheShezhi(models.Model):
|
||||
"""店铺商品审核模式全局配置(仅一条记录)"""
|
||||
kaiqi_shenhe = models.BooleanField(default=False, verbose_name='是否开启商品审核模式')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'dianpu_shangpin_shenhe_shezhi'
|
||||
verbose_name = '商品审核模式配置'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.pk = 1 # 强制使用 ID=1 保证唯一
|
||||
super().save(*args, **kwargs)
|
||||
3
shop/tests.py
Normal file
3
shop/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
41
shop/urls.py
Normal file
41
shop/urls.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import BindDianpuView, ShopGoodsView, ShangdianLoginView, ShopOverviewView, ShopQrcodeView, \
|
||||
ShopProductConfigView, ShopProductModifyView,ShopProductListView, ShopProductModifyView1,\
|
||||
MemberListView,DianpuShouzhiTongjiView, QrcodeDownloadView, SubShopListView, SubShopModifyView,\
|
||||
ShopOrderManageView,ShopOrderStatisticsView, ShopRefundView, ShopForceCompleteView,\
|
||||
ShopTransferHallView, ShopFineApplyView
|
||||
|
||||
urlpatterns = [
|
||||
path('bdsd', BindDianpuView.as_view(), name='绑定或更新商家店铺'),
|
||||
path('hqsp', ShopGoodsView.as_view(), name='获取商品数据'),
|
||||
|
||||
path('login', ShangdianLoginView.as_view(), name='商家登录'),
|
||||
path('sjdpgl', ShopOverviewView.as_view(), name='商家获取店铺信息'),
|
||||
path('sdscdpm', ShopQrcodeView.as_view(), name='生成或更新店铺二维码'),
|
||||
|
||||
path('dphqspxx', ShopProductConfigView.as_view(), name='商店获取商品类型专区'),
|
||||
path('xgdpsplx', ShopProductModifyView.as_view(), name='商店修改添加修改商品类型专区'),
|
||||
|
||||
path('sdhqspsj', ShopProductListView.as_view(), name='商店获取商品数据'),
|
||||
path('xgdpsplx1', ShopProductModifyView1.as_view(), name='商店增删改商品数据'),
|
||||
|
||||
path('hqhy', MemberListView.as_view(), name='商店获取会员'),
|
||||
path('dpsztj', DianpuShouzhiTongjiView.as_view(), name='获取店铺数据统计'),
|
||||
|
||||
path('xzewm', QrcodeDownloadView.as_view(), name='下载店铺二维码'),
|
||||
|
||||
path('wdxjdp', SubShopListView.as_view(), name='获取下级店铺列表'), # 获取下级店铺列表
|
||||
path('xjdpbj', SubShopModifyView.as_view(), name='添加/修改下级店铺'), # 添加/修改下级店铺
|
||||
# 稍后添加具体路由
|
||||
# 稍后添加具体路由
|
||||
path('ddlbhq', ShopOrderManageView.as_view(), name='店铺订单数据统计与获取'),
|
||||
path('ddltj', ShopOrderStatisticsView.as_view(), name='店铺订单数量统计接口'),
|
||||
path('tk', ShopRefundView.as_view(), name='店铺退款'),
|
||||
|
||||
path('qzjd', ShopForceCompleteView.as_view(), name='店铺强制结算'),
|
||||
|
||||
path('zydt', ShopTransferHallView.as_view(), name='店铺转移大厅'),
|
||||
|
||||
path('fksq', ShopFineApplyView.as_view(), name='店铺开罚单'),
|
||||
]
|
||||
640
shop/utils.py
Normal file
640
shop/utils.py
Normal file
@@ -0,0 +1,640 @@
|
||||
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .models import Dianpu, YonghuPingzheng
|
||||
|
||||
|
||||
from products.models import Shangpin
|
||||
from .models import Dianpu, DianpuMorenPeizhi
|
||||
|
||||
# shangdian/utils.py 或 tongji/utils.py
|
||||
|
||||
import logging
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from django.db import transaction
|
||||
from django.db.models import F
|
||||
from .models import Dianpu, DianpuShouzhiMeiriTongji
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def verify_shop_permission(request, frontend_user_id=None):
|
||||
"""
|
||||
验证商家后台用户身份及店铺状态
|
||||
|
||||
参数:
|
||||
request: DRF request 对象
|
||||
frontend_user_id: 前端传递的用户ID(用于防越权校验,可选)
|
||||
|
||||
返回:
|
||||
(dianpu_obj, None) # 验证成功,返回店铺对象
|
||||
(None, Response) # 验证失败,返回错误响应
|
||||
"""
|
||||
current_user = request.user
|
||||
|
||||
# 1. 从 request.user 获取当前认证用户的 yonghuid
|
||||
# 注意:您的用户模型可能通过 JWT 的 sub 或其他字段标识,此处假设 request.user.yonghuid 存在
|
||||
if not hasattr(current_user, 'yonghuid'):
|
||||
logger.error(f"用户对象缺少 yonghuid 属性: {current_user}")
|
||||
return None, Response(
|
||||
{'code': 500, 'msg': '用户身份信息异常'}
|
||||
)
|
||||
|
||||
auth_user_id = current_user.yonghuid
|
||||
|
||||
# 2. 越权检测:如果前端传了用户ID,必须与当前登录用户一致
|
||||
if frontend_user_id is not None and str(frontend_user_id) != str(auth_user_id):
|
||||
logger.warning(
|
||||
f"越权尝试: 登录用户 {auth_user_id} 尝试操作 {frontend_user_id} 的数据"
|
||||
)
|
||||
return None, Response(
|
||||
{'code': 403, 'msg': '无权操作其他用户数据'}
|
||||
)
|
||||
|
||||
# 3. 查询用户凭证表,验证账号是否存在且状态正常
|
||||
try:
|
||||
pingzheng = YonghuPingzheng.objects.get(yonghu=auth_user_id)
|
||||
if not pingzheng.is_active:
|
||||
return None, Response(
|
||||
{'code': 403, 'msg': '用户账号已被禁用'}
|
||||
)
|
||||
except YonghuPingzheng.DoesNotExist:
|
||||
return None, Response(
|
||||
{'code': 404, 'msg': '用户凭证不存在'}
|
||||
)
|
||||
|
||||
# 4. 查询关联的店铺信息,验证店铺状态
|
||||
try:
|
||||
dianpu = Dianpu.objects.get(pingzheng=pingzheng)
|
||||
if dianpu.zhuangtai != 1: # 1 表示正常
|
||||
return None, Response(
|
||||
{'code': 403, 'msg': '店铺已被封禁'}
|
||||
)
|
||||
except Dianpu.DoesNotExist:
|
||||
return None, Response(
|
||||
{'code': 404, 'msg': '店铺信息不存在'}
|
||||
)
|
||||
|
||||
# 验证通过,返回店铺对象
|
||||
return dianpu, None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
'''def update_dianpu_daily_stat(
|
||||
dianpu_id,
|
||||
caozuo_leixing,
|
||||
dingdan_jiage=Decimal('0.00'),
|
||||
dianpu_shouyi=Decimal('0.00')
|
||||
):
|
||||
"""
|
||||
原子更新店铺每日统计数据,并根据结算操作更新店铺余额与总额(高并发安全)
|
||||
|
||||
参数:
|
||||
dianpu_id: 店铺ID (int)
|
||||
caozuo_leixing: 操作类型
|
||||
1 = 下单
|
||||
2 = 结算(成交)
|
||||
3 = 退款
|
||||
dingdan_jiage: 订单金额(商品价格),默认0.00
|
||||
dianpu_shouyi: 店铺本次收益金额,默认0.00
|
||||
|
||||
注意:
|
||||
- 金额若小于0则自动设为0
|
||||
- 日期自动取当天
|
||||
- 使用 select_for_update 配合事务,保证并发时数据绝对准确
|
||||
- 若店铺不存在,仅记录日志不中断主流程
|
||||
- 结算操作会同步更新 Dianpu 表的 ketixian_yue 和 zhengqu_zonge
|
||||
"""
|
||||
# 参数归一化与合法性处理
|
||||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||||
dianpu_shouyi = Decimal(str(dianpu_shouyi)) if dianpu_shouyi else Decimal('0.00')
|
||||
|
||||
if dingdan_jiage < 0:
|
||||
dingdan_jiage = Decimal('0.00')
|
||||
if dianpu_shouyi < 0:
|
||||
dianpu_shouyi = Decimal('0.00')
|
||||
|
||||
today = date.today()
|
||||
|
||||
# 校验店铺是否存在(不加锁,仅校验)
|
||||
if not Dianpu.objects.filter(id=dianpu_id).exists():
|
||||
logger.error(f"店铺ID {dianpu_id} 不存在,统计更新失败")
|
||||
return
|
||||
|
||||
# 开启事务,使用行锁确保并发安全
|
||||
with transaction.atomic():
|
||||
# 1. 获取或创建统计记录,并锁定该行
|
||||
tongji, created = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create(
|
||||
dianpu_id=dianpu_id,
|
||||
riqi=today,
|
||||
defaults={
|
||||
'nian': today.year,
|
||||
'yue': today.month,
|
||||
'ri': today.day,
|
||||
'xiadan_zongliang': 0,
|
||||
'xiadan_zonge': Decimal('0.00'),
|
||||
'chengjiao_zongliang': 0,
|
||||
'chengjiao_zonge': Decimal('0.00'),
|
||||
'tuikuan_zongliang': 0,
|
||||
'tuikuan_zonge': Decimal('0.00'),
|
||||
'shouyi_zonge': Decimal('0.00'),
|
||||
}
|
||||
)
|
||||
|
||||
# 根据操作类型构建统计更新字段
|
||||
update_fields = {}
|
||||
|
||||
if caozuo_leixing == 1: # 下单
|
||||
update_fields['xiadan_zongliang'] = F('xiadan_zongliang') + 1
|
||||
update_fields['xiadan_zonge'] = F('xiadan_zonge') + dingdan_jiage
|
||||
|
||||
elif caozuo_leixing == 2: # 结算(成交)
|
||||
update_fields['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1
|
||||
update_fields['chengjiao_zonge'] = F('chengjiao_zonge') + dingdan_jiage
|
||||
update_fields['shouyi_zonge'] = F('shouyi_zonge') + dianpu_shouyi
|
||||
|
||||
# 结算时同步更新店铺表的可提现余额和赚取总额(需加锁店铺行)
|
||||
try:
|
||||
dianpu = Dianpu.objects.select_for_update().get(id=dianpu_id)
|
||||
dianpu.ketixian_yue = F('ketixian_yue') + dianpu_shouyi
|
||||
dianpu.zhengqu_zonge = F('zhengqu_zonge') + dianpu_shouyi
|
||||
dianpu.save(update_fields=['ketixian_yue', 'zhengqu_zonge', 'update_time'])
|
||||
except Dianpu.DoesNotExist:
|
||||
logger.error(f"结算时店铺ID {dianpu_id} 不存在,但已校验过,此处不应发生")
|
||||
raise
|
||||
|
||||
elif caozuo_leixing == 3: # 退款
|
||||
update_fields['tuikuan_zongliang'] = F('tuikuan_zongliang') + 1
|
||||
update_fields['tuikuan_zonge'] = F('tuikuan_zonge') + dingdan_jiage
|
||||
# 退款是否扣减收益需根据业务决定,此处只累加退款统计
|
||||
|
||||
else:
|
||||
logger.warning(f"未知的操作类型: {caozuo_leixing}")
|
||||
return
|
||||
|
||||
# 原子更新统计字段
|
||||
if update_fields:
|
||||
for field, value in update_fields.items():
|
||||
setattr(tongji, field, value)
|
||||
tongji.save(update_fields=list(update_fields.keys()))
|
||||
|
||||
logger.info(
|
||||
f"店铺统计更新: 店铺ID={dianpu_id}, 操作类型={caozuo_leixing}, "
|
||||
f"订单金额={dingdan_jiage}, 店铺收益={dianpu_shouyi}, 日期={today}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def calculate_pingtai_and_dianpu_shouyi(shangpin_id, dingdan_jiage, dashou_fencheng):
|
||||
"""
|
||||
根据商品价格和打手分成计算平台收益和店铺收益
|
||||
|
||||
逻辑:
|
||||
总收益 = 订单金额 - 打手分成
|
||||
1. 从商品表获取所属店铺ID
|
||||
2. 若商品无店铺ID:平台收益 = 总收益,店铺收益 = 0
|
||||
3. 若有店铺ID:
|
||||
- 查询店铺配置:
|
||||
* 若店铺开启抽成,则费率 = 店铺表.shouyi_choucheng_feilv
|
||||
* 否则从店铺默认配置表(ID=1)获取默认费率,若记录不存在则自动创建
|
||||
- 平台收益 = 总收益 * 费率
|
||||
- 店铺收益 = 总收益 - 平台收益
|
||||
|
||||
参数:
|
||||
shangpin_id: 商品ID (int)
|
||||
dingdan_jiage: 订单金额(商品价格)(Decimal 或 数字)
|
||||
dashou_fencheng: 打手分成金额 (Decimal 或 数字)
|
||||
|
||||
返回:
|
||||
(dianpu_id, pingtai_shouyi, dianpu_shouyi) 三元组
|
||||
dianpu_id 可能为 None
|
||||
pingtai_shouyi 必定有值
|
||||
dianpu_shouyi 必定有值
|
||||
"""
|
||||
# 类型转换与安全处理
|
||||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||||
dashou_fencheng = Decimal(str(dashou_fencheng)) if dashou_fencheng else Decimal('0.00')
|
||||
if dingdan_jiage < 0:
|
||||
dingdan_jiage = Decimal('0.00')
|
||||
if dashou_fencheng < 0:
|
||||
dashou_fencheng = Decimal('0.00')
|
||||
|
||||
# 计算总收益(可用于抽成的基础)
|
||||
zong_shouyi = dingdan_jiage - dashou_fencheng
|
||||
if zong_shouyi < 0:
|
||||
zong_shouyi = Decimal('0.00')
|
||||
|
||||
# 获取商品信息及所属店铺
|
||||
try:
|
||||
shangpin = Shangpin.objects.select_related('dianpu').only('id', 'dianpu').get(id=shangpin_id)
|
||||
except Shangpin.DoesNotExist:
|
||||
logger.error(f"计算收益失败:商品ID {shangpin_id} 不存在")
|
||||
return None, zong_shouyi, Decimal('0.00') # 无商品时平台收益为总收益,店铺0
|
||||
|
||||
dianpu_id = shangpin.dianpu_id
|
||||
|
||||
# 无店铺ID:平台拿走全部总收益,店铺收益为0
|
||||
if dianpu_id is None:
|
||||
logger.info(f"商品 {shangpin_id} 未关联店铺,平台收益 = 总收益 {zong_shouyi}")
|
||||
return None, zong_shouyi, Decimal('0.00')
|
||||
|
||||
# 有店铺ID:查询店铺抽成配置
|
||||
try:
|
||||
dianpu = Dianpu.objects.only(
|
||||
'id', 'kaiqi_shouyi_choucheng', 'shouyi_choucheng_feilv'
|
||||
).get(id=dianpu_id)
|
||||
except Dianpu.DoesNotExist:
|
||||
logger.error(f"计算收益失败:店铺ID {dianpu_id} 不存在")
|
||||
return dianpu_id, zong_shouyi, Decimal('0.00') # 店铺不存在时平台收益也为总收益
|
||||
|
||||
if dianpu.kaiqi_shouyi_choucheng and dianpu.shouyi_choucheng_feilv is not None:
|
||||
feilv = dianpu.shouyi_choucheng_feilv
|
||||
else:
|
||||
# 从默认配置表获取或创建
|
||||
moren_peizhi, created = DianpuMorenPeizhi.objects.get_or_create(
|
||||
id=1,
|
||||
defaults={
|
||||
'moren_choucheng_feilv': Decimal('0.1000'), # 默认10%
|
||||
'moren_tixian_xiane': Decimal('1000.00')
|
||||
}
|
||||
)
|
||||
feilv = moren_peizhi.moren_choucheng_feilv
|
||||
|
||||
# 确保费率在合法范围内
|
||||
if feilv is None or feilv < 0:
|
||||
feilv = Decimal('0.0000')
|
||||
if feilv > 1:
|
||||
feilv = Decimal('1.0000')
|
||||
|
||||
# 计算平台收益(费率 * 总收益)
|
||||
pingtai_shouyi = zong_shouyi * feilv
|
||||
# 店铺收益 = 总收益 - 平台收益
|
||||
dianpu_shouyi = zong_shouyi - pingtai_shouyi
|
||||
|
||||
logger.info(
|
||||
f"收益计算: 商品ID={shangpin_id}, 订单金额={dingdan_jiage}, "
|
||||
f"打手分成={dashou_fencheng}, 总收益={zong_shouyi}, 费率={feilv}, "
|
||||
f"平台收益={pingtai_shouyi}, 店铺收益={dianpu_shouyi}"
|
||||
)
|
||||
|
||||
return dianpu_id, pingtai_shouyi, dianpu_shouyi'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def update_dianpu_daily_stat(
|
||||
dianpu_id,
|
||||
caozuo_leixing,
|
||||
dingdan_jiage=Decimal('0.00'),
|
||||
dianpu_shouyi=Decimal('0.00'),
|
||||
shangji_dianpu_id=None, # 新增:上级店铺ID
|
||||
shangji_fenhong=Decimal('0.00') # 新增:该笔订单给上级的分红
|
||||
):
|
||||
"""
|
||||
原子更新店铺每日统计数据,结算时同步更新店铺余额,并安全处理上级分红统计。
|
||||
|
||||
参数:
|
||||
dianpu_id: 本店铺ID
|
||||
caozuo_leixing: 1=下单, 2=结算, 3=退款
|
||||
dingdan_jiage: 订单金额
|
||||
dianpu_shouyi: 店铺自身收益
|
||||
shangji_dianpu_id: 上级店铺ID(可选)
|
||||
shangji_fenhong: 给上级的分红金额(可选)
|
||||
|
||||
注意:
|
||||
- 金额自动归一化,负数取0
|
||||
- 使用 select_for_update 行锁保证并发安全
|
||||
- 结算时,本店余额和统计先落定,上级分红统计放在 try 块内,失败不影响本店
|
||||
- 新增字段均包含在统计表中:给上级次数/总额/上级ID,以及作为上级获得次数/总额
|
||||
"""
|
||||
# 金额安全处理
|
||||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||||
dianpu_shouyi = Decimal(str(dianpu_shouyi)) if dianpu_shouyi else Decimal('0.00')
|
||||
shangji_fenhong = Decimal(str(shangji_fenhong)) if shangji_fenhong else Decimal('0.00')
|
||||
if dingdan_jiage < 0: dingdan_jiage = Decimal('0.00')
|
||||
if dianpu_shouyi < 0: dianpu_shouyi = Decimal('0.00')
|
||||
if shangji_fenhong < 0: shangji_fenhong = Decimal('0.00')
|
||||
|
||||
today = date.today()
|
||||
|
||||
# 轻量校验店铺存在性
|
||||
if not Dianpu.objects.filter(id=dianpu_id).exists():
|
||||
logger.error(f"店铺ID {dianpu_id} 不存在,统计更新失败")
|
||||
return
|
||||
|
||||
# 开启事务,全部使用行锁
|
||||
with transaction.atomic():
|
||||
# 1. 获取或创建本店今日统计记录并锁定
|
||||
tongji, created = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create(
|
||||
dianpu_id=dianpu_id,
|
||||
riqi=today,
|
||||
defaults={
|
||||
'nian': today.year,
|
||||
'yue': today.month,
|
||||
'ri': today.day,
|
||||
'xiadan_zongliang': 0,
|
||||
'xiadan_zonge': Decimal('0.00'),
|
||||
'chengjiao_zongliang': 0,
|
||||
'chengjiao_zonge': Decimal('0.00'),
|
||||
'tuikuan_zongliang': 0,
|
||||
'tuikuan_zonge': Decimal('0.00'),
|
||||
'shouyi_zonge': Decimal('0.00'),
|
||||
# 新增字段默认值
|
||||
'shangji_fenhong_cishu': 0,
|
||||
'shangji_fenhong_zonge': Decimal('0.00'),
|
||||
'gei_shangji_cishu': 0,
|
||||
'gei_shangji_zonge': Decimal('0.00'),
|
||||
'shangji_id': None,
|
||||
}
|
||||
)
|
||||
|
||||
# 构建更新字段字典
|
||||
update_fields_dict = {}
|
||||
|
||||
if caozuo_leixing == 1: # 下单
|
||||
update_fields_dict['xiadan_zongliang'] = F('xiadan_zongliang') + 1
|
||||
update_fields_dict['xiadan_zonge'] = F('xiadan_zonge') + dingdan_jiage
|
||||
|
||||
elif caozuo_leixing == 2: # 结算
|
||||
# --- 本店成交统计 ---
|
||||
update_fields_dict['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1
|
||||
update_fields_dict['chengjiao_zonge'] = F('chengjiao_zonge') + dingdan_jiage
|
||||
update_fields_dict['shouyi_zonge'] = F('shouyi_zonge') + dianpu_shouyi
|
||||
|
||||
# --- 更新本店主表余额(加锁) ---
|
||||
try:
|
||||
dianpu_locked = Dianpu.objects.select_for_update().get(id=dianpu_id)
|
||||
dianpu_locked.ketixian_yue = F('ketixian_yue') + dianpu_shouyi
|
||||
dianpu_locked.zhengqu_zonge = F('zhengqu_zonge') + dianpu_shouyi
|
||||
dianpu_locked.save(update_fields=['ketixian_yue', 'zhengqu_zonge', 'update_time'])
|
||||
except Dianpu.DoesNotExist:
|
||||
logger.error(f"结算时店铺 {dianpu_id} 不存在")
|
||||
raise
|
||||
|
||||
# --- 处理给上级的分红(本店作为下级)---
|
||||
if shangji_dianpu_id and shangji_fenhong > 0:
|
||||
# 更新本店统计的“给上级”字段
|
||||
update_fields_dict['gei_shangji_cishu'] = F('gei_shangji_cishu') + 1
|
||||
update_fields_dict['gei_shangji_zonge'] = F('gei_shangji_zonge') + shangji_fenhong
|
||||
|
||||
# 设置当天上级店铺ID(仅当天第一次结算时写入,后续保持不变)
|
||||
current_shangji_id = tongji.shangji_id
|
||||
if current_shangji_id is None:
|
||||
update_fields_dict['shangji_id'] = shangji_dianpu_id
|
||||
elif current_shangji_id != shangji_dianpu_id:
|
||||
logger.warning(
|
||||
f"店铺 {dianpu_id} 当天上级ID不一致,已有 {current_shangji_id},"
|
||||
f"新上级 {shangji_dianpu_id},保持原上级ID"
|
||||
)
|
||||
|
||||
# --- 更新上级店铺的“作为上级获得”统计(完全隔离保护) ---
|
||||
try:
|
||||
_update_shangji_gain_stat_within_tx(shangji_dianpu_id, shangji_fenhong)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"更新上级店铺 {shangji_dianpu_id} 统计失败,不影响本店: {e}"
|
||||
)
|
||||
|
||||
elif caozuo_leixing == 3: # 退款
|
||||
update_fields_dict['tuikuan_zongliang'] = F('tuikuan_zongliang') + 1
|
||||
update_fields_dict['tuikuan_zonge'] = F('tuikuan_zonge') + dingdan_jiage
|
||||
else:
|
||||
logger.warning(f"未知操作类型: {caozuo_leixing}")
|
||||
return
|
||||
|
||||
# 原子更新本店统计字段
|
||||
if update_fields_dict:
|
||||
for field, value in update_fields_dict.items():
|
||||
setattr(tongji, field, value)
|
||||
tongji.save(update_fields=list(update_fields_dict.keys()))
|
||||
|
||||
logger.info(
|
||||
f"店铺统计更新完成: dianpu={dianpu_id}, 操作={caozuo_leixing}, "
|
||||
f"金额={dingdan_jiage}, 店铺收益={dianpu_shouyi}, "
|
||||
f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}"
|
||||
)
|
||||
|
||||
|
||||
def _update_shangji_gain_stat_within_tx(shangji_dianpu_id, fenhong_jine):
|
||||
"""
|
||||
在事务内更新上级店铺的“作为上级获得”统计及主表余额。
|
||||
注意:本函数必须由处于同一事务中的 update_dianpu_daily_stat 调用,
|
||||
并且调用方已获取必要的行锁,这里再次加锁不会死锁(按相同顺序加锁)。
|
||||
"""
|
||||
today = date.today()
|
||||
|
||||
# 获取或创建上级店铺的今日统计记录并加锁
|
||||
tongji_shangji, _ = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create(
|
||||
dianpu_id=shangji_dianpu_id,
|
||||
riqi=today,
|
||||
defaults={
|
||||
'nian': today.year,
|
||||
'yue': today.month,
|
||||
'ri': today.day,
|
||||
'xiadan_zongliang': 0,
|
||||
'xiadan_zonge': Decimal('0.00'),
|
||||
'chengjiao_zongliang': 0,
|
||||
'chengjiao_zonge': Decimal('0.00'),
|
||||
'tuikuan_zongliang': 0,
|
||||
'tuikuan_zonge': Decimal('0.00'),
|
||||
'shouyi_zonge': Decimal('0.00'),
|
||||
'shangji_fenhong_cishu': 0,
|
||||
'shangji_fenhong_zonge': Decimal('0.00'),
|
||||
'gei_shangji_cishu': 0,
|
||||
'gei_shangji_zonge': Decimal('0.00'),
|
||||
'shangji_id': None,
|
||||
}
|
||||
)
|
||||
|
||||
# 更新上级店铺的“作为上级获得”字段
|
||||
tongji_shangji.shangji_fenhong_cishu = F('shangji_fenhong_cishu') + 1
|
||||
tongji_shangji.shangji_fenhong_zonge = F('shangji_fenhong_zonge') + fenhong_jine
|
||||
tongji_shangji.save(update_fields=['shangji_fenhong_cishu', 'shangji_fenhong_zonge', 'update_time'])
|
||||
|
||||
# 更新上级店铺主表的分红余额和累计分红
|
||||
shangji = Dianpu.objects.select_for_update().get(id=shangji_dianpu_id)
|
||||
shangji.shangji_fenhong_leiji = F('shangji_fenhong_leiji') + fenhong_jine
|
||||
shangji.shangji_ketixian_fenhong = F('shangji_ketixian_fenhong') + fenhong_jine
|
||||
shangji.save(update_fields=['shangji_fenhong_leiji', 'shangji_ketixian_fenhong', 'update_time'])
|
||||
|
||||
logger.info(f"上级店铺 {shangji_dianpu_id} 获得分红 +{fenhong_jine}")
|
||||
|
||||
|
||||
|
||||
def calculate_pingtai_and_dianpu_shouyi(shangpin_id, dingdan_jiage, dashou_fencheng):
|
||||
"""
|
||||
计算平台收益、店铺收益、上级分红
|
||||
|
||||
分配链路(上级分红完全隔离,不影响平台及店铺核心计算):
|
||||
总收益 = 订单金额 - 打手分成
|
||||
平台抽成 = 总收益 * (店铺开启则用店铺费率,否则用平台默认费率)
|
||||
剩余收益 = 总收益 - 平台抽成
|
||||
上级分红 = 剩余收益 * 本店设置的上级分红费率(若有上级且满足条件)
|
||||
店铺收益 = 剩余收益 - 上级分红
|
||||
|
||||
参数:
|
||||
shangpin_id: 商品ID
|
||||
dingdan_jiage: 订单金额
|
||||
dashou_fencheng: 打手分成金额
|
||||
|
||||
返回:
|
||||
五元组 (dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong)
|
||||
- dianpu_id: 店铺ID,可能为None
|
||||
- pingtai_shouyi: 平台收益
|
||||
- dianpu_shouyi: 店铺最终收益
|
||||
- shangji_dianpu_id: 上级店铺ID,可能为None
|
||||
- shangji_fenhong: 上级分红金额,默认0.00
|
||||
"""
|
||||
# ==================== 金额安全处理 ====================
|
||||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||||
dashou_fencheng = Decimal(str(dashou_fencheng)) if dashou_fencheng else Decimal('0.00')
|
||||
if dingdan_jiage < 0:
|
||||
dingdan_jiage = Decimal('0.00')
|
||||
if dashou_fencheng < 0:
|
||||
dashou_fencheng = Decimal('0.00')
|
||||
|
||||
zong_shouyi = dingdan_jiage - dashou_fencheng
|
||||
if zong_shouyi < 0:
|
||||
zong_shouyi = Decimal('0.00')
|
||||
|
||||
# ==================== 获取商品及店铺 ====================
|
||||
try:
|
||||
shangpin = Shangpin.objects.select_related('dianpu').only('id', 'dianpu').get(id=shangpin_id)
|
||||
except Shangpin.DoesNotExist:
|
||||
logger.error(f"收益计算失败:商品ID {shangpin_id} 不存在")
|
||||
return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00')
|
||||
|
||||
dianpu_id = shangpin.dianpu_id
|
||||
if dianpu_id is None:
|
||||
logger.info(f"商品 {shangpin_id} 未关联店铺,平台收益={zong_shouyi}")
|
||||
return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00')
|
||||
|
||||
# ==================== 获取店铺配置 ====================
|
||||
try:
|
||||
dianpu = Dianpu.objects.only(
|
||||
'id', 'kaiqi_shouyi_choucheng', 'shouyi_choucheng_feilv',
|
||||
'yaoqing_dianpu_id', 'fenhong_choucheng_feilv'
|
||||
).get(id=dianpu_id)
|
||||
except Dianpu.DoesNotExist:
|
||||
logger.error(f"收益计算失败:店铺ID {dianpu_id} 不存在")
|
||||
return dianpu_id, zong_shouyi, Decimal('0.00'), None, Decimal('0.00')
|
||||
|
||||
# ==================== 平台抽成 ====================
|
||||
if dianpu.kaiqi_shouyi_choucheng and dianpu.shouyi_choucheng_feilv is not None:
|
||||
feilv = dianpu.shouyi_choucheng_feilv
|
||||
else:
|
||||
moren_peizhi, created = DianpuMorenPeizhi.objects.get_or_create(
|
||||
id=1,
|
||||
defaults={
|
||||
'moren_choucheng_feilv': Decimal('0.1000'),
|
||||
'moren_tixian_xiane': Decimal('1000.00')
|
||||
}
|
||||
)
|
||||
feilv = moren_peizhi.moren_choucheng_feilv
|
||||
if feilv is None or feilv < 0:
|
||||
feilv = Decimal('0.0000')
|
||||
if feilv > 1:
|
||||
feilv = Decimal('1.0000')
|
||||
|
||||
pingtai_shouyi = zong_shouyi * feilv
|
||||
shengyu = zong_shouyi - pingtai_shouyi
|
||||
|
||||
# ==================== 上级分红(完全隔离,不影响店铺收益) ====================
|
||||
shangji_dianpu_id = dianpu.yaoqing_dianpu_id
|
||||
shangji_fenhong = Decimal('0.00')
|
||||
|
||||
if shangji_dianpu_id and dianpu.fenhong_choucheng_feilv:
|
||||
try:
|
||||
# 检查上级店铺状态
|
||||
shangji = Dianpu.objects.filter(id=shangji_dianpu_id).only('id', 'zhuangtai').first()
|
||||
if shangji and shangji.zhuangtai == 1:
|
||||
fz_rate = dianpu.fenhong_choucheng_feilv
|
||||
if fz_rate and fz_rate > 0:
|
||||
shangji_fenhong = shengyu * fz_rate
|
||||
if shangji_fenhong < 0:
|
||||
shangji_fenhong = Decimal('0.00')
|
||||
if shangji_fenhong > shengyu:
|
||||
shangji_fenhong = shengyu
|
||||
shengyu -= shangji_fenhong
|
||||
else:
|
||||
if shangji:
|
||||
logger.info(f"上级店铺 {shangji_dianpu_id} 状态非正常,不参与分红")
|
||||
else:
|
||||
logger.warning(f"上级店铺ID {shangji_dianpu_id} 不存在,不参与分红")
|
||||
except Exception as e:
|
||||
logger.exception(f"上级分红计算异常,忽略: {e}")
|
||||
shangji_fenhong = Decimal('0.00')
|
||||
# 注意:shengyu 未被修改,店铺收益不变
|
||||
|
||||
dianpu_shouyi = shengyu
|
||||
|
||||
logger.info(
|
||||
f"收益计算完成: 商品={shangpin_id}, 总收益={zong_shouyi}, 平台={pingtai_shouyi}, "
|
||||
f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}, 店铺={dianpu_shouyi}"
|
||||
)
|
||||
|
||||
return dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def validate_shangpin_and_dianpu(shangpin_id):
|
||||
"""
|
||||
校验商品及关联店铺(区分有无店铺)
|
||||
|
||||
无店铺:仅校验商品是否上架
|
||||
有店铺:校验商品审核、封禁、上架,以及店铺是否封禁
|
||||
|
||||
返回:
|
||||
(True, None, shangpin_obj) # 通过
|
||||
(False, "错误信息", None) # 失败
|
||||
"""
|
||||
try:
|
||||
shangpin = Shangpin.objects.select_related('dianpu').get(id=shangpin_id)
|
||||
except Shangpin.DoesNotExist:
|
||||
return False, "商品不存在", None
|
||||
|
||||
# 无店铺:只检查上架
|
||||
if not shangpin.dianpu_id:
|
||||
if not shangpin.shangjia_zhuangtai:
|
||||
return False, "商品已下架", None
|
||||
return True, None, shangpin
|
||||
|
||||
# 有店铺:完整校验
|
||||
if not shangpin.shenhe_zhuangtai:
|
||||
return False, "商品未通过平台审核", None
|
||||
if shangpin.fengjin_zhuangtai:
|
||||
return False, "商品已被封禁", None
|
||||
if not shangpin.shangjia_zhuangtai:
|
||||
return False, "商品已下架", None
|
||||
|
||||
try:
|
||||
if shangpin.dianpu.zhuangtai != 1:
|
||||
return False, "店铺已被封禁", None
|
||||
except Exception:
|
||||
return False, "关联店铺异常", None
|
||||
|
||||
return True, None, shangpin
|
||||
3373
shop/views.py
Normal file
3373
shop/views.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user