Files
Django/gvsdsdk/commission/strategies/engine.py

157 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:分佣策略引擎
策略模式实现——每种分账方式独立封装,新增分账逻辑只需添加新策略类。
支持四种策略:
PercentageStrategy: 比例分账 → TotalAmount × rate
FixedStrategy: 固定金额 → 固定值(受保底/封顶约束)
TieredStrategy: 阶梯分账 → 按次数/档位查表
RemainderStrategy: 剩余归平台 → TotalAmount - SUM(其他参与者)
"""
from abc import ABC, abstractmethod
from decimal import Decimal
from typing import Dict, List, Optional
from gvsdsdk.commission.models import RateTable
class BaseSplitStrategy(ABC):
"""分账策略抽象基类"""
def __init__(self, participant_rule: dict, rate_table: Optional[RateTable] = None):
self._rule = participant_rule
self._rate_table = rate_table
@abstractmethod
def Calculate(self, total_amount: Decimal, tier_index: int = 0, **context) -> Decimal:
...
@property
def Role(self):
return self._rule.get('Role', 'unknown')
@property
def SplitType(self):
return self._rule.get('Type', 'unknown')
def _get_rate_from_table(self, key):
if self._rate_table:
rate = self._rate_table.GetRate(key)
if rate is not None:
return rate
return self._rule.get('Rate', Decimal('0'))
class PercentageStrategy(BaseSplitStrategy):
"""比例分账策略
配置示例:
{ role: 'worker', type: 'percentage', value: 0.80 }
{ role: 'worker', type: 'percentage', rate_table_key: '1' }
"""
def Calculate(self, total_amount: Decimal, tier_index: int = 0, **context) -> Decimal:
rate_key = self._rule.get('RateTableKey', '')
if rate_key:
rate = self._get_rate_from_table(rate_key)
else:
rate = Decimal(str(self._rule.get('Value', 0)))
amount = total_amount * rate
return self._apply_bounds(amount, total_amount)
def _apply_bounds(self, amount, total_amount):
minimum = self._rule.get('MinAmount')
maximum = self._rule.get('MaxAmount')
if minimum is not None:
amount = max(amount, Decimal(str(minimum)))
if maximum is not None:
amount = min(amount, Decimal(str(maximum)))
return amount
class FixedStrategy(BaseSplitStrategy):
"""固定金额策略
配置示例:
{ role: 'agent', type: 'fixed', value: 10 }
{ role: 'leader', type: 'fixed', rate_table_key: 'guanshi_fc' }
"""
def Calculate(self, total_amount: Decimal, tier_index: int = 0, **context) -> Decimal:
rate_key = self._rule.get('RateTableKey', '')
if rate_key:
amount = self._get_rate_from_table(rate_key)
else:
amount = Decimal(str(self._rule.get('Value', 0)))
return amount
class TieredStrategy(BaseSplitStrategy):
"""阶梯分账策略——按次数/档位查表
配置示例:
{ role: 'agent', type: 'tiered',
tiers: { 1: 5, 2: 8, 3: 10 } }
或从费率表中按 key_N 形式查找。
"""
def Calculate(self, total_amount: Decimal, tier_index: int = 0, **context) -> Decimal:
tiers = self._rule.get('Tiers', {})
if tiers:
tier_key = str(tier_index)
raw_value = tiers.get(tier_key, tiers.get('default', 0))
if isinstance(raw_value, str) and raw_value.endswith('%'):
return total_amount * Decimal(raw_value.rstrip('%')) / Decimal('100')
return Decimal(str(raw_value))
tier_key = self._rule.get('TierKeyPrefix', '')
if tier_key and self._rate_table:
rate = self._rate_table.GetRate(f'{tier_key}_{tier_index}')
if rate is not None:
return rate
return Decimal('0')
class RemainderStrategy(BaseSplitStrategy):
"""剩余归平台策略——扣除所有其他参与者后的余额
配置示例:
{ role: 'platform', type: 'remainder' }
"""
def Calculate(self, total_amount: Decimal, tier_index: int = 0, **context) -> Decimal:
allocated = context.get('AllocatedAmount', Decimal('0'))
remainder = total_amount - allocated
return remainder if remainder > 0 else Decimal('0')
class WithdrawalFeeStrategy(BaseSplitStrategy):
"""提现手续费策略
配置示例:
{ role: 'platform', type: 'withdrawal_fee', value: 0.01 }
"""
def Calculate(self, total_amount: Decimal, tier_index: int = 0, **context) -> Decimal:
rate_key = self._rule.get('RateTableKey', '')
if rate_key:
rate = self._get_rate_from_table(rate_key)
else:
rate = Decimal(str(self._rule.get('Value', 0)))
return total_amount * rate
STRATEGY_MAP: Dict[str, type] = {
'percentage': PercentageStrategy,
'fixed': FixedStrategy,
'tiered': TieredStrategy,
'remainder': RemainderStrategy,
'withdrawal_fee': WithdrawalFeeStrategy,
}
def CreateStrategy(participant_rule: dict, rate_table: Optional[RateTable] = None) -> BaseSplitStrategy:
split_type = participant_rule.get('Type', 'percentage')
strategy_class = STRATEGY_MAP.get(split_type, PercentageStrategy)
return strategy_class(participant_rule, rate_table)