简单介绍一下买卖基金业务逻辑

首先介绍一下基金申购场景的业务逻辑
我们做的是【基金模拟投资大赛】。
用户申购一只基金时,需要选择申购的基金(FundCode)、申购的金额(Amount)。
现在要对以上两个参数作校验,主要逻辑为:
1.验证基金是否存在
2.封闭式、创新型封闭式、理财产品、QDII和首发基金不允许购买
3.验证基金是否处于封闭期或暂停申购
4.验证用户是否允许购买此基金
5.验证用户帐户余额是否不足
6.验证申购金额
7.验证用户的资产配置
解释一下各点含义:
1.用户申购基金时选择一支基金(UI的下拉列表中选择一项),即传入FundCode,逻辑代码需要验证传入的FundCode是否是非法的,这要在【基金档案】中查找是否有匹配项,如果没有,则返回【基金不存在】异常。
2.需要规定封闭式、创新型封闭式、理财产品、QDII类型的基金和首发基金是不允许购买的。
3.基金的状态是随时会变的,这取决于基金公司,今天我开放这支基金的申购,明天我关闭它的申购,赎回也一样。
4.根据需求,用户在加入大赛时会选择一个小组,管理员会事先配置每个小组允许购买的基金,如果小组中没有配置某支基金,例如【华夏成长(000001)】,就意味着属于这个小组的用户都是不允许购买【华夏成长】这支基金。
5.用户余额不足会返回【余额不足】的异常。
6.买基金的人都知道,有最低申购金额限制,不同基金最低申购金额是不一样的,一般股票型基金低的100,高的1000,大部分1000。货币基金从200到1000的都有,大部分是1000。系统也会事先设定好这项配置。
7.资产配置的意思是类似要求用户配置投资组合,比如购买的货币型基金不能超过多少金额,债券型的基金不能超过多少金额,仅此而已。
以上用例都是很简单的,所以你会看到有这样一个入口:


public virtual ActionResponse Subscribe(
int userId
, string fundCode
, decimal amount
, string remark
) {
ActionResponse response = new ActionResponse();
IUserRepository userRepository = RepositoryFactory.CreateUserRepository();
User user = userRepository.GetUser(userId);
Fund fund = FundFactory.BuildFund(fundCode);
IContext context = new SubscribeContext(
new SubscribeUser(user)
, new SubscribeFund(fund)
, amount
);
response = context.Interact();
return response;
}

哪个用户(UserId)用多少钱(Amount)购买哪支基金(FundCode),remark是备注、理财日志,可不用理会。
再解释一下以上这段代码的业务意义:
根据userId取出用户这个对象,让她扮演【申购用户(SubscribeUser)】这个角色;构造一支基金fund,让她扮演【申购的基金(SubscribeFund)】这个角色,然后构造场景,实现交互(Interact)。所以大家一定很想知道User的原型,然后我就贴上来:

/// <summary>
/// 用户
/// </summary>
public class User {

/// <summary>
/// 用户编号
/// </summary>
public int Id {
get;
set;
}

/// <summary>
/// 用户名称
/// </summary>
public string Name {
get;
set;
}

/// <summary>
/// 用户帐户
/// </summary>
public Account Account {
get;
set;
}

/// <summary>
/// 用户所属小组
/// </summary>
public Team Team {
get;
set;
}

/// <summary>
/// 用户状态
/// </summary>
public bool IsActive {
get;
set;
}

/// <summary>
/// 注册日期
/// </summary>
public DateTime RegisterDate {
get;
set;
}
}

然后就是她在【申购场景(SubscribeContext)】中扮演的【申购用户(SubscribeUser)】角色的代码:

/// <summary>
/// 申购用户
/// </summary>
public class SubscribeUser : IRole {

private User _user;

public SubscribeUser(User user) {
this._user = user;
}

/// <summary>
/// 选手不存在
/// </summary>
public bool NotExists {
get {
return this._user == null;
}
}

/// <summary>
/// 选手被禁赛
/// </summary>
public bool Suspended {
get {
return this._user.IsActive == false;
}
}

/// <summary>
/// 选手是否不允许购买基金
/// </summary>
/// <param name="fund"></param>
/// <returns></returns>
public bool CannotBuyFund(Fund fund) {
Team team = this._user.Team;
int teamCode = team.Code;
ITeamRepository teamRepository = RepositoryFactory.CreateTeamRepository();
IList<Fund> teamFunds = teamRepository.GetTeamFunds(teamCode);
return !(teamFunds.Count == 0 || teamFunds.Contains(fund));
}

/// <summary>
/// 选手账户余额不足
/// </summary>
/// <param name="amount"></param>
/// <returns></returns>
public bool InsufficientBalance(decimal amount) {
return this._user.Account.Balance < amount;
}
}

接着是Fund的原型:

/// <summary>
/// 基金
/// </summary>
public class Fund : IAggregateRoot {

/// <summary>
/// 基金代码
/// </summary>
/// <param name="fundCode"></param>
public Fund(string fundCode) {
this.Code = fundCode;
}

/// <summary>
/// 基金代码
/// </summary>
public string Code {
get;
set;
}

/// <summary>
/// 基金名称
/// </summary>
public string Name {
get;
set;
}

/// <summary>
/// 基金类型
/// </summary>
public FundType Type {
get;
set;
}

/// <summary>
/// 基金种类(A类、B类、C类)
/// </summary>
public FundClass Class {
get;
set;
}

/// <summary>
/// 基金所属公司
/// </summary>
public FundCompany Company {
get;
set;
}

/// <summary>
/// 基金成立日期
/// </summary>
public DateTime SetupDate {
get;
set;
}

region 重写

public override int GetHashCode() {
return this.Code.GetHashCode();
}

public override bool Equals(object obj) {
if (ReferenceEquals(obj, null)) {
return false;
}
return this.Code.Equals(((Fund)obj).Code);
}

public static bool operator ==(Fund destFund, Fund srcFund) {
if (ReferenceEquals(destFund, null) || ReferenceEquals(srcFund, null)) {
return false;
}
return destFund.Code.Equals(srcFund.Code);
}

public static bool operator !=(Fund destFund, Fund srcFund) {
return !(destFund == srcFund);
}

endregion
}

她在【申购场景(SubscribeContext)】中扮演的【申购基金(SubscribeFund)】角色的代码:

/// <summary>
/// 申购基金
/// </summary>
public class SubscribeFund : IRole {

private Fund _fund;

public SubscribeFund(Fund fund) {
this._fund = fund;
}

public Fund Value {
get {
return this._fund;
}
}

/// <summary>
/// 基金不存在
/// </summary>
public bool NotExists {
get {
return this._fund == null;
}
}

/// <summary>
/// 封闭式、创新型封闭式、理财产品、QDII和首发基金不允许购买
/// </summary>
public bool CannotSubscribe {
get {
bool isCannotBuyFundType = false;
switch (this._fund.Type.Code) {
case 6:
case 7:
case 9:
case 10:
isCannotBuyFundType = true;
break;
}
bool isUnSetuppedFund = (this._fund.SetupDate == DateTime.MinValue);
return isCannotBuyFundType || isUnSetuppedFund;
}
}

/// <summary>
/// 基金处于封闭期或暂停申购
/// </summary>
public bool SubscribeClosed {
get {
string fundCode = this._fund.Code;
return FundService.IsSubscribeClosed(fundCode);
}
}
}

最后应该是【申购场景】代码了:


/// <summary>
/// 申购场景
/// </summary>
public class SubscribeContext : IContext {

private SubscribeUser _user;
private SubscribeFund _fund;
private decimal _amount;

public SubscribeContext(
SubscribeUser user
,SubscribeFund fund
, decimal amount
) {
this._user = user;
this._fund = fund;
this._amount = amount;
}

public ActionResponse Interact() {
ActionResponse response = new ActionResponse();
//验证基金是否存在
if (this._fund.NotExists) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_NOT_EXISTS_FUND").Code
, MessageResourceManager.GetMessage(
"ERR_NOT_EXISTS_FUND").Text
);
}
//封闭式、创新型封闭式、理财产品、QDII和首发基金不允许购买
if (this._fund.CannotSubscribe) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_FUND").Code
, MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_FUND").Text
);
}
//验证基金是否处于封闭期或暂停申购
if (this._fund.SubscribeClosed) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_SUBSCRIBE_CLOSED_FUND").Code
, MessageResourceManager.GetMessage(
"ERR_SUBSCRIBE_CLOSED_FUND").Text
);
}
//验证选手是否允许购买此基金
if (this._user.CannotBuyFund(this._fund.Value)) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_THIS_FUND_USER").Code
, MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_THIS_FUND_USER").Text
);
}
//验证选手帐户余额是否不足
if (this._user.InsufficientBalance(this._amount)) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_INSUFFICIENT_BALANCE").Code
, MessageResourceManager.GetMessage(
"ERR_INSUFFICIENT_BALANCE").Text
);
}
//验证交易金额
SubscribeAmountValidationContext subscribeAmountValidationContext = new SubscribeAmountValidationContext(this._amount);
SubscribeAmountValidator subscribeAmountValidator = ValidatorFactory.CreateSubscribeAmountValidator(subscribeAmountValidationContext);
ValidationResult amountValidationResult = subscribeAmountValidator.Validate();
if (amountValidationResult.InValid) {
return new FailureResponse(
amountValidationResult.StatusCode
, amountValidationResult.StatusText
);
}
//验证资产配置
AssetValidationContext assetValidationContext = new AssetValidationContext(
new SubscribeUser()
, this._fund
, this._amount
);
AssetValidator assetValidator = ValidatorFactory.CreateAssetValidator(assetValidationContext);
ValidationResult assetValidationResult = assetValidator.Validate();
if (assetValidationResult.InValid) {
return new FailureResponse(
assetValidationResult.StatusCode
, assetValidationResult.StatusText
);
}
//创建订单的持久化操作,此处略...
return response;
}
}

不知道大家有没有明朗点?