| | |
| | | package com.nq.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.nq.dao.*; |
| | | import com.nq.enums.EStockType; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.sql.Timestamp; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.*; |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | |
| | | @Autowired |
| | | IPriceServices priceServices; |
| | | |
| | | @Autowired |
| | | UserAssetsMapper userAssetsMapper; |
| | | |
| | | @Transactional |
| | | public ServerResponse buy(Integer stockId, Integer buyNum, Integer buyType, Integer lever, BigDecimal profitTarget, BigDecimal stopTarget, HttpServletRequest request) { |
| | | public ServerResponse buy(Integer stockId, Integer buyNum, Integer buyType, Integer lever, BigDecimal profitTarget, BigDecimal stopTarget, HttpServletRequest request) { |
| | | |
| | | SiteProduct siteProduct = iSiteProductService.getProductSetting(); |
| | | |
| | | User user = this.iUserService.getCurrentRefreshUser(request); |
| | | if (siteProduct.getRealNameDisplay() && (StringUtils.isBlank(user.getRealName()) || StringUtils.isBlank(user.getIdCard()))) { |
| | | return ServerResponse.createByErrorMsg("订单失败,请先实名认证",request); |
| | | if (siteProduct.getRealNameDisplay() && user.getIsActive() != 2) { |
| | | return ServerResponse.createByErrorMsg("订单失败,请先实名认证", request); |
| | | } |
| | | SiteSetting siteSetting = this.iSiteSettingService.getSiteSetting(); |
| | | // 手续费率 |
| | | BigDecimal siteSettingBuyFee = siteSetting.getBuyFee(); |
| | | SiteSetting siteSetting = iSiteSettingService.getSiteSetting(); |
| | | // 手续费率 |
| | | BigDecimal siteSettingBuyFee = siteSetting.getBuyFee(); |
| | | |
| | | if (siteProduct.getRealNameDisplay() && user.getIsLock().intValue() == 1) { |
| | | return ServerResponse.createByErrorMsg("订单失败,帐户已被锁定",request); |
| | | return ServerResponse.createByErrorMsg("订单失败,帐户已被锁定", request); |
| | | } |
| | | |
| | | Stock stock = stockMapper.selectByPrimaryKey(stockId); |
| | | if (stock == null) { |
| | | return ServerResponse.createByErrorMsg("订单失败,股票代码不存在",request); |
| | | return ServerResponse.createByErrorMsg("订单失败,股票代码不存在", request); |
| | | } |
| | | //判断股票是否在可交易时间段 |
| | | Boolean b = tradingHourService.timeCheck(stock.getStockCode()); |
| | | if (!b) { |
| | | return ServerResponse.createByErrorMsg("订单失败,不在交易时间之内",request); |
| | | return ServerResponse.createByErrorMsg("订单失败,不在交易时间之内", request); |
| | | } |
| | | |
| | | |
| | | UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(stock.getStockType(), user.getId()); |
| | | if (stock.getIsLock() != 0) { |
| | | return ServerResponse.createByErrorMsg("订单失败,股票被锁定",request); |
| | | return ServerResponse.createByErrorMsg("订单失败,股票被锁定", request); |
| | | } |
| | | |
| | | //股票类型 现价 数据源的处理 |
| | | BigDecimal nowPrice = priceServices.getNowPrice(stock.getStockCode()); |
| | | BigDecimal nowPrice = priceServices.getNowPrice(stock.getStockCode()); |
| | | |
| | | if (nowPrice.compareTo(new BigDecimal("0")) == 0) { |
| | | return ServerResponse.createByErrorMsg("报价0,请稍后再试",request); |
| | | return ServerResponse.createByErrorMsg("报价0,请稍后再试", request); |
| | | } |
| | | |
| | | BigDecimal buyAmt = nowPrice.multiply(new BigDecimal(buyNum)); |
| | | BigDecimal orderFree = siteSettingBuyFee.multiply(buyAmt); |
| | | BigDecimal buyAmt = nowPrice.multiply(new BigDecimal(buyNum)).divide(new BigDecimal(lever)); |
| | | BigDecimal orderFree = siteSettingBuyFee.multiply(buyAmt); |
| | | |
| | | UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(stock.getStockType(),user.getId()); |
| | | if(userAssets.getAvailableBalance().compareTo(buyAmt.add(orderFree))<0){ |
| | | return ServerResponse.createByErrorMsg("订单失败,余额不足",request); |
| | | if (userAssets.getAvailableBalance().compareTo(buyAmt.add(orderFree)) < 0) { |
| | | return ServerResponse.createByErrorMsg("订单失败,配资不足", request); |
| | | } |
| | | UserPosition userPosition = new UserPosition(); |
| | | if (profitTarget != null && profitTarget.compareTo(new BigDecimal("0")) > 0) { |
| | |
| | | userPosition.setOrderStayDays(Integer.valueOf(0)); |
| | | userPosition.setOrderStayFee(BigDecimal.ZERO); |
| | | userPositionMapper.insert(userPosition); |
| | | iUserAssetsServices.availablebalanceChange(stock.getStockType(),user.getId(), EUserAssets.BUY,buyAmt.negate(),"",""); |
| | | iUserAssetsServices.availablebalanceChange(stock.getStockType(),user.getId(), EUserAssets.HANDLING_CHARGE,orderFree,"",""); |
| | | return ServerResponse.createBySuccessMsg("下单成功",request); |
| | | iUserAssetsServices.availablebalanceChange(stock.getStockType(), user.getId(), EUserAssets.BUY, buyAmt.negate(), "", ""); |
| | | iUserAssetsServices.availablebalanceChange(stock.getStockType(), user.getId(), EUserAssets.HANDLING_CHARGE, orderFree, "", ""); |
| | | return ServerResponse.createBySuccessMsg("下单成功", request); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | |
| | | @Transactional |
| | | public ServerResponse sell(String positionSn, int doType,HttpServletRequest request){ |
| | | public ServerResponse sell(String positionSn, int doType, HttpServletRequest request) { |
| | | UserPosition userPosition = this.userPositionMapper.findPositionBySn(positionSn); |
| | | BigDecimal siitteBuyFee = iSiteSettingService.getSiteSetting().getBuyFee(); |
| | | Boolean b = tradingHourService.timeCheck(userPosition.getStockCode()); |
| | | if (!b) { |
| | | return ServerResponse.createByErrorMsg("订单失败,不在交易时间之内",request); |
| | | } |
| | | |
| | | SiteSetting siteSetting = iSiteSettingService.getSiteSetting(); |
| | | // 手续费率 |
| | | BigDecimal siitteBuyFee = siteSetting.getSellFee(); |
| | | |
| | | if (userPosition == null) { |
| | | return ServerResponse.createByErrorMsg("平仓失败,订单不存在",request); |
| | | return ServerResponse.createByErrorMsg("平仓失败,订单不存在", request); |
| | | } |
| | | User user = this.userMapper.selectById(userPosition.getUserId()); |
| | | if (user == null) { |
| | | return ServerResponse.createByErrorMsg("平仓失败,用户不存在",request); |
| | | return ServerResponse.createByErrorMsg("平仓失败,用户不存在", request); |
| | | } |
| | | if (userPosition.getSellOrderId() != null) { |
| | | return ServerResponse.createByErrorMsg("平仓失败, 订单已平仓",request); |
| | | return ServerResponse.createByErrorMsg("平仓失败, 订单已平仓", request); |
| | | } |
| | | if (1 == userPosition.getIsLock().intValue()) { |
| | | return ServerResponse.createByErrorMsg("this order is closed " + userPosition.getLockMsg()); |
| | | } |
| | | Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("stock_code", userPosition.getStockCode())); |
| | | BigDecimal nowPrice = priceServices.getNowPrice(userPosition.getStockCode()); |
| | | BigDecimal nowPrice = priceServices.getNowPrice(userPosition.getStockCode()); |
| | | if (nowPrice.compareTo(new BigDecimal("0")) != 1) { |
| | | return ServerResponse.createByErrorMsg("报价0,平仓失败,请稍后再试",request); |
| | | return ServerResponse.createByErrorMsg("报价0,平仓失败,请稍后再试", request); |
| | | } |
| | | userPosition.setSellOrderId(GeneratePosition.getPositionId()); |
| | | userPosition.setSellOrderPrice(nowPrice); |
| | |
| | | BigDecimal xsPrice = sellOrderTotel.multiply(siitteBuyFee); |
| | | userPositionMapper.updateById(userPosition); |
| | | userAssetsServices.availablebalanceChange(stock.getStockType(), |
| | | userPosition.getUserId(),EUserAssets.CLOSE_POSITION_RETURN_SECURITY_DEPOSIT, |
| | | userPosition.getOrderTotalPrice(),"",""); |
| | | userPosition.getUserId(), |
| | | EUserAssets.CLOSE_POSITION_RETURN_SECURITY_DEPOSIT, |
| | | userPosition.getOrderTotalPrice(), "", ""); |
| | | userAssetsServices.availablebalanceChange(stock.getStockType(), |
| | | userPosition.getUserId(),EUserAssets.HANDLING_CHARGE, |
| | | xsPrice,"",""); |
| | | userPosition.getUserId(), EUserAssets.HANDLING_CHARGE, |
| | | xsPrice, "", ""); |
| | | |
| | | PositionProfitVO profitVO = UserPointUtil.getPositionProfitVO(userPosition,priceServices.getNowPrice(userPosition.getStockCode())); |
| | | userAssetsServices.availablebalanceChange(stock.getStockType(),userPosition.getUserId(),EUserAssets.CLOSE_POSITION, |
| | | profitVO.getAllProfitAndLose() ,"",""); |
| | | return ServerResponse.createBySuccessMsg("平仓成功!",request); |
| | | PositionProfitVO profitVO = UserPointUtil.getPositionProfitVO(userPosition, |
| | | priceServices.getNowPrice(userPosition.getStockCode())); |
| | | |
| | | userAssetsServices.availablebalanceChange(stock.getStockType(), |
| | | userPosition.getUserId(), EUserAssets.CLOSE_POSITION, |
| | | profitVO.getAllProfitAndLose(), "", ""); |
| | | return ServerResponse.createBySuccessMsg("平仓成功!", request); |
| | | } |
| | | |
| | | @Transactional |
| | |
| | | |
| | | List<UserPosition> userPositionList = userPositionMapper.selectList(queryWrapper); |
| | | for (int i = 0; i < userPositionList.size(); i++) { |
| | | sell(userPositionList.get(i).getPositionSn(),0); |
| | | sell(userPositionList.get(i).getPositionSn(),0,request); |
| | | } |
| | | return ServerResponse.createBySuccessMsg("平仓成功!"); |
| | | } |
| | |
| | | |
| | | PageHelper.startPage(pageNum, pageSize); |
| | | List<UserPosition> userPositions; |
| | | userPositions = userPositionMapper. |
| | | |
| | | |
| | | |
| | | userPositions = userPositionMapper. |
| | | findMyPositionByCodeAndSpell(user.getId(), |
| | | stockCode, stockSpell, |
| | | state, stockType); |
| | |
| | | List<UserPositionVO> userPositionVOS = Lists.newArrayList(); |
| | | if (userPositions.size() > 0) { |
| | | for (UserPosition position : userPositions) { |
| | | UserPositionVO userPositionVO = UserPointUtil.assembleUserPositionVO(position,priceServices.getNowPrice(position.getStockCode())); |
| | | UserPositionVO userPositionVO = UserPointUtil.assembleUserPositionVO(position, priceServices.getNowPrice(position.getStockCode())); |
| | | userPositionVO.setOrderTotalPrice(userPositionVO.getOrderTotalPrice().multiply(new BigDecimal(userPositionVO.getOrderLever()))); |
| | | userPositionVO.setProfitAndLose(userPositionVO.getProfitAndLose().multiply(new BigDecimal(userPositionVO.getOrderLever()))); |
| | | userPositionVOS.add(userPositionVO); |
| | | } |
| | | } |
| | |
| | | if (userStockSubscribe == null) { |
| | | return ServerResponse.createByErrorMsg("无该申购记录"); |
| | | } |
| | | StockSubscribe stockSubscribe = stockSubscribeMapper.selectOne(new QueryWrapper<StockSubscribe>().eq("code", userStockSubscribe.getNewCode())); |
| | | StockSubscribe stockSubscribe = stockSubscribeMapper.selectOne(new QueryWrapper<StockSubscribe>().eq("code", userStockSubscribe.getNewCode()).eq("type",userStockSubscribe.getType())); |
| | | if (userStockSubscribe == null) { |
| | | return ServerResponse.createByErrorMsg("该新股不存在"); |
| | | } |
| | | if (userStockSubscribe.getStatus() == 4 || userStockSubscribe.getStatus() == 3 && stockSubscribe.getType() == 2) { |
| | | |
| | | // String sinaStock = SinaStockApi.getSinaStock(stockSubscribe.getStockType()+userStockSubscribe.getNewCode()); |
| | | // String[] arrayOfString = sinaStock.split(","); |
| | | // if (arrayOfString.length < 10){ |
| | | // return ServerResponse.createByErrorMsg("数据源无该新股数据,转持仓失败"); |
| | | // } |
| | | Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>().eq(Stock::getStockCode, userStockSubscribe.getNewCode())); |
| | | if(null == stock){ |
| | | return ServerResponse.createByErrorMsg("该新股不存在"); |
| | | } |
| | | UserPosition userPosition = new UserPosition(); |
| | | userPosition.setPositionType(1); |
| | | userPosition.setPositionSn(KeyUtils.getUniqueKey()); |
| | | userPosition.setUserId(userStockSubscribe.getUserId()); |
| | | userPosition.setNickName(userStockSubscribe.getRealName()); |
| | | userPosition.setAgentId(userStockSubscribe.getAgentId()); |
| | | userPosition.setStockCode(userStockSubscribe.getNewCode()); |
| | | userPosition.setStockCode(stock.getStockCode()); |
| | | userPosition.setStockSpell(stock.getStockSpell()); |
| | | userPosition.setStockName(userStockSubscribe.getNewName()); |
| | | userPosition.setStockGid(stockSubscribe.getStockType() + userStockSubscribe.getNewCode()); |
| | | userPosition.setStockGid(stock.getStockType()); |
| | | |
| | | userPosition.setBuyOrderId(GeneratePosition.getPositionId()); |
| | | userPosition.setBuyOrderTime(new Date()); |
| | |
| | | userPosition.setIsLock(Integer.valueOf(0)); |
| | | |
| | | |
| | | userPosition.setOrderLever(10); |
| | | userPosition.setOrderLever(1); |
| | | |
| | | |
| | | //递延费特殊处理 |
| | |
| | | log.info("用户购买印花税(配资后总资金 * 百分比) = {}", buy_yhs_amt); |
| | | userPosition.setOrderSpread(buy_yhs_amt); |
| | | |
| | | // SiteSpread siteSpread = iSiteSpreadService.findSpreadRateOne(new BigDecimal(stock_crease), buy_amt, stock.getStockCode(), now_price); |
| | | // BigDecimal spread_rate_amt = new BigDecimal("0"); |
| | | // if(siteSpread != null){ |
| | | // spread_rate_amt = buy_amt.multiply(siteSpread.getSpreadRate()).setScale(2, 4); |
| | | // log.info("用户购买点差费(配资后总资金 * 百分比{}) = {}", siteSpread.getSpreadRate(), spread_rate_amt); |
| | | // } else{ |
| | | // log.info("用户购买点差费(配资后总资金 * 百分比{}) = {}", "设置异常", spread_rate_amt); |
| | | // } |
| | | BigDecimal spread_rate_amt = new BigDecimal(0); |
| | | userPosition.setSpreadRatePrice(spread_rate_amt); |
| | | |
| | |
| | | |
| | | int ret = 0; |
| | | ret = this.userPositionMapper.insert(userPosition); |
| | | |
| | | UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId(stock.getStockType(), userPosition.getUserId()); |
| | | if(null == userAssets){ |
| | | return ServerResponse.createByErrorMsg("新股转持仓失败"); |
| | | } |
| | | userAssetsMapper.updateById(userAssets); |
| | | if (ret > 0) { |
| | | userStockSubscribe.setStatus(5); |
| | | userStockSubscribeMapper.update1(userStockSubscribe); |
| | | if (userStockSubscribe.getType() == 1 || userStockSubscribe.getType() == 2) { |
| | | User user = userMapper.selectById(userStockSubscribe.getUserId()); |
| | | ret = userMapper.updateByPrimaryKey(user); |
| | | ret = userMapper.updateById(user); |
| | | } |
| | | if (ret > 0) { |
| | | return ServerResponse.createBySuccessMsg("新股转持仓成功"); |
| | |
| | | |
| | | } |
| | | |
| | | |
| | | @Override |
| | | @Transactional |
| | | public void stockConstraint(List<UserPosition> list) { |
| | | SiteSetting siteSetting = iSiteSettingService.getSiteSetting(); |
| | | |
| | | for (UserPosition position : list) { |
| | | Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>().eq(Stock::getStockCode, position.getStockCode())); |
| | | UserAssets userAssets = userAssetsMapper.selectOne(new LambdaQueryWrapper<UserAssets>() |
| | | .eq(UserAssets::getUserId, position.getUserId()) |
| | | .eq(UserAssets::getAccectType, stock.getStockType()) |
| | | ); |
| | | if(userAssets.getAmountToBeCovered().compareTo(BigDecimal.ZERO) > 0){ |
| | | continue; |
| | | } |
| | | //平仓检查 |
| | | Result result = getResult(position); |
| | | if (result == null) continue; |
| | | |
| | | Integer liquidation = 0; |
| | | liquidation = isLiquidation(position, result.signum, result.profit, liquidation); |
| | | if(liquidation != 0){ |
| | | extracted(position, result.nowPrice, result.stock,liquidation); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Result getResult(UserPosition position) { |
| | | // 检查订单是否存在 |
| | | if (position == null) { |
| | | log.info("订单不存在,订单id: {}", position.getId()); |
| | | return null; |
| | | } |
| | | |
| | | // 检查用户是否存在 |
| | | User user = this.userMapper.selectById(position.getUserId()); |
| | | if (user == null) { |
| | | log.info("用户不存在,订单id: {}", position.getId()); |
| | | return null; |
| | | } |
| | | |
| | | |
| | | //判断订单 |
| | | if (1 == position.getIsLock().intValue()) { |
| | | log.info("订单已终止,订单id: {}", position.getId()); |
| | | return null; |
| | | } |
| | | |
| | | Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("stock_code", position.getStockCode())); |
| | | |
| | | |
| | | BigDecimal nowPrice = priceServices.getNowPrice(position.getStockCode()); |
| | | |
| | | |
| | | //判断订单是否已到强制平仓价格 |
| | | BigDecimal purchaseAmount = position.getBuyOrderPrice().multiply(new BigDecimal(position.getOrderNum()));// 买入价总额 |
| | | BigDecimal nowPriceAmount = nowPrice.multiply(new BigDecimal(position.getOrderNum())); // 现价总额 |
| | | |
| | | BigDecimal profit = nowPriceAmount.subtract(purchaseAmount);//利润 |
| | | int signum = profit.signum(); // -1, 0, 1,分别表示 负数、零、正数 |
| | | Result result = new Result(stock, nowPrice, profit, signum); |
| | | return result; |
| | | } |
| | | |
| | | private static class Result { |
| | | public final Stock stock;//股票 |
| | | public final BigDecimal nowPrice;//现价 |
| | | public final BigDecimal profit;//利润 |
| | | public final int signum;// -1, 0, 1,分别表示 负数、零、正数 |
| | | |
| | | public Result(Stock stock, BigDecimal nowPrice, BigDecimal profit, int signum) { |
| | | this.stock = stock; |
| | | this.nowPrice = nowPrice; |
| | | this.profit = profit; |
| | | this.signum = signum; |
| | | } |
| | | } |
| | | |
| | | //判断平仓 |
| | | private Integer isLiquidation(UserPosition position, int signum, BigDecimal profit, Integer liquidation) { |
| | | //-1强平 0未触发 1止损强平 2止盈强平 |
| | | //最新报价 |
| | | BigDecimal nowPrice = priceServices.getNowPrice(position.getStockCode()); |
| | | if(position.getOrderDirection().equals("买涨")){ |
| | | //判断亏损金额是否达到保证金金额 |
| | | BigDecimal negate = profit.negate(); |
| | | //如果买涨 signum 为-1则表示亏损 |
| | | if(signum == -1){ |
| | | //止损 |
| | | if(null != position.getStopTargetPrice() && nowPrice.compareTo(position.getStopTargetPrice()) <= 0){ |
| | | //强制平仓 |
| | | return liquidation = 1; |
| | | } |
| | | if (negate.compareTo(position.getOrderTotalPrice()) >= 0){//亏平强平 |
| | | //强制平仓 |
| | | return liquidation = -1; |
| | | } |
| | | }else{ |
| | | //止盈 |
| | | if(null != position.getProfitTargetPrice() && nowPrice.compareTo(position.getProfitTargetPrice()) >= 0){ |
| | | //强制平仓 |
| | | return liquidation = 2; |
| | | } |
| | | } |
| | | }else{ |
| | | //买跌 signum |
| | | if(signum == 1){ |
| | | //止损 |
| | | if(null != position.getStopTargetPrice() && nowPrice.compareTo(position.getStopTargetPrice()) >= 0){ |
| | | //强制平仓 |
| | | return liquidation = 1; |
| | | } |
| | | //判断亏损金额是否达到保证金金额 |
| | | if (profit.compareTo(position.getOrderTotalPrice()) >= 0){//亏平强平 |
| | | //强制平仓 |
| | | return liquidation = -1; |
| | | } |
| | | }else{ |
| | | //止盈 |
| | | if(null != position.getProfitTargetPrice() && nowPrice.compareTo(position.getProfitTargetPrice()) <= 0){ |
| | | //强制平仓 |
| | | return liquidation = 2; |
| | | } |
| | | } |
| | | } |
| | | return liquidation; |
| | | } |
| | | |
| | | //平仓 |
| | | private void extracted(UserPosition position, BigDecimal nowPrice, Stock stock,Integer liquidation) { |
| | | //-1强平 0未触发 1止损强平 2止盈强平 |
| | | BigDecimal sellOrderTotel = nowPrice.multiply(new BigDecimal(position.getOrderNum())); |
| | | SiteSetting siteSetting = iSiteSettingService.getSiteSetting(); |
| | | // 手续费率 |
| | | BigDecimal xsPrice = sellOrderTotel.multiply(siteSetting.getForceStopFee());//手续费 |
| | | // 更新订单信息 |
| | | position.setSellOrderId(GeneratePosition.getPositionId()); |
| | | position.setSellOrderPrice(nowPrice); |
| | | position.setSellOrderTime(new Date()); |
| | | userPositionMapper.updateById(position); |
| | | if(liquidation == -1){ |
| | | userAssetsServices.availablebalanceChange(stock.getStockType(), |
| | | position.getUserId(), |
| | | EUserAssets.CONSTRAINT_CLOSE_POSITION, |
| | | position.getOrderTotalPrice(), "", ""); |
| | | }else if(liquidation == 1 || liquidation == 2){ |
| | | userAssetsServices.availablebalanceChange(stock.getStockType(), |
| | | position.getUserId(), EUserAssets.HANDLING_CHARGE, |
| | | xsPrice, "", ""); |
| | | PositionProfitVO profitVO = UserPointUtil.getPositionProfitVO(position, priceServices.getNowPrice(position.getStockCode())); |
| | | userAssetsServices.availablebalanceChange(stock.getStockType(), position.getUserId(), EUserAssets.CLOSE_POSITION, |
| | | profitVO.getAllProfitAndLose(), "", ""); |
| | | } |
| | | log.info("强制平仓成功,订单id: {}", position.getId()); |
| | | } |
| | | |
| | | } |
| | | |
| | | |