| @@ -93,6 +93,10 @@ public class WxOrder implements Serializable { | |||||
| /**砍价人数**/ | /**砍价人数**/ | ||||
| @io.swagger.annotations.ApiModelProperty(value="砍价人数",name="pressCurrentNum") | @io.swagger.annotations.ApiModelProperty(value="砍价人数",name="pressCurrentNum") | ||||
| private Integer pressCurrentNum; | private Integer pressCurrentNum; | ||||
| /**已砍价格**/ | |||||
| @io.swagger.annotations.ApiModelProperty(value="已砍价格",name="pressCurrentValue") | |||||
| private Integer pressCurrentValue; | |||||
| public Long getOrderNumber() { | public Long getOrderNumber() { | ||||
| @@ -214,6 +218,14 @@ public class WxOrder implements Serializable { | |||||
| this.pressCurrentNum = pressCurrentNum; | this.pressCurrentNum = pressCurrentNum; | ||||
| } | } | ||||
| public Integer getPressCurrentValue() { | |||||
| return pressCurrentValue; | |||||
| } | |||||
| public void setPressCurrentValue(Integer pressCurrentValue) { | |||||
| this.pressCurrentValue = pressCurrentValue; | |||||
| } | |||||
| public static enum Field | public static enum Field | ||||
| { | { | ||||
| Id_ASC("`id` ASC"),Id_DESC("`id` DESC") | Id_ASC("`id` ASC"),Id_DESC("`id` DESC") | ||||
| @@ -16,6 +16,7 @@ import com.iformall.exception.MallinkException; | |||||
| import com.iformall.mapper.*; | import com.iformall.mapper.*; | ||||
| import com.iformall.service.*; | import com.iformall.service.*; | ||||
| import com.iformall.utils.DateUtils; | import com.iformall.utils.DateUtils; | ||||
| import com.iformall.utils.PressUtils; | |||||
| import com.iformall.utils.RedisLock; | import com.iformall.utils.RedisLock; | ||||
| import com.iformall.utils.Utility; | import com.iformall.utils.Utility; | ||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
| @@ -98,6 +99,9 @@ public class WxOrderServiceImpl implements WxOrderService { | |||||
| @Autowired | @Autowired | ||||
| WxCouponSendService wxCouponSendService; | WxCouponSendService wxCouponSendService; | ||||
| @Autowired | |||||
| WxOrderPressMapper wxOrderPressMapper; | |||||
| @Override | @Override | ||||
| public PageInfo<WxOrder> listAsPage(WxOrder record, Integer pageIndex, Integer pageSize) { | public PageInfo<WxOrder> listAsPage(WxOrder record, Integer pageIndex, Integer pageSize) { | ||||
| return PageHelper.startPage(pageIndex, pageSize).doSelectPageInfo(() -> wxOrderMapper.findList(record)); | return PageHelper.startPage(pageIndex, pageSize).doSelectPageInfo(() -> wxOrderMapper.findList(record)); | ||||
| @@ -338,9 +342,18 @@ public class WxOrderServiceImpl implements WxOrderService { | |||||
| // 减库存操作 | // 减库存操作 | ||||
| stockReduce(user, coupon, couponIdStr); | stockReduce(user, coupon, couponIdStr); | ||||
| boolean isPress = false; | |||||
| if (coupon.getType().equals(EnumCouponType.COUPON_PRESS.getCode())) { | |||||
| isPress = true; | |||||
| } | |||||
| Date curr = new Date(); | Date curr = new Date(); | ||||
| int payment = coupon.getSalePrice(); | int payment = coupon.getSalePrice(); | ||||
| if (isPress) { | |||||
| // 砍价券 | |||||
| payment = coupon.getPrice(); | |||||
| } | |||||
| final IdWorker idWorker = IdWorker.get(); | final IdWorker idWorker = IdWorker.get(); | ||||
| Long orderNumber = idWorker.nextId(); | Long orderNumber = idWorker.nextId(); | ||||
| @@ -370,6 +383,30 @@ public class WxOrderServiceImpl implements WxOrderService { | |||||
| record.setDetail(bodyStr); | record.setDetail(bodyStr); | ||||
| record.setCreateDate(curr); | record.setCreateDate(curr); | ||||
| record.setUpdateDate(curr); | record.setUpdateDate(curr); | ||||
| if(isPress) { | |||||
| record.setPressCurrentNum(1); | |||||
| record.setPressEndDate(DateUtils.getHourTimeAfter(coupon.getPressLimitHours(), curr)); | |||||
| // 添加 wx_order_press | |||||
| int total = coupon.getPrice() - coupon.getSalePrice(); | |||||
| int left_total = total; | |||||
| WxOrderPress orderPress = new WxOrderPress(); | |||||
| orderPress.setId(idWorker.nextId()); | |||||
| orderPress.setOrderId(orderNumber); | |||||
| orderPress.setUserId(user.getId()); | |||||
| orderPress.setCreateDate(curr); | |||||
| orderPress.setPressValue(PressUtils.stateLessPressValue(total, left_total, coupon.getPressLimitNum(), 0)); | |||||
| try { | |||||
| // 保存订单 | |||||
| wxOrderPressMapper.insertSelective(orderPress); | |||||
| } catch (RuntimeException e) { | |||||
| // 库存恢复 | |||||
| stockBack(record); | |||||
| logger.error("保存砍价记录Err:" + e.getMessage()); | |||||
| throw new MallinkException(ErrorCode.ORDER_SAVE_ERR); | |||||
| } | |||||
| } | |||||
| try { | try { | ||||
| // 保存订单 | // 保存订单 | ||||
| @@ -385,6 +422,8 @@ public class WxOrderServiceImpl implements WxOrderService { | |||||
| try { | try { | ||||
| wxCouponActionLogService.addOne(user.getTenantId(), couponId, orderNumber, EnumCouponSendSendType.ACTIVE.getCode(), couponChannelId); | wxCouponActionLogService.addOne(user.getTenantId(), couponId, orderNumber, EnumCouponSendSendType.ACTIVE.getCode(), couponChannelId); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| // 库存恢复 | |||||
| stockBack(record); | |||||
| logger.error("保存订单:" + e.getMessage()); | logger.error("保存订单:" + e.getMessage()); | ||||
| throw new MallinkException(ErrorCode.ORDER_SAVE_ERR); | throw new MallinkException(ErrorCode.ORDER_SAVE_ERR); | ||||
| } | } | ||||
| @@ -371,6 +371,21 @@ public class DateUtils { | |||||
| return mDate; | return mDate; | ||||
| } | } | ||||
| /** | |||||
| * 获得当前时间后几小时的时间 | |||||
| * | |||||
| * @param hour | |||||
| * 后几小时 | |||||
| * @return | |||||
| */ | |||||
| public static Date getHourTimeAfter(int hour, Date myDate) { | |||||
| Date testDate = myDate; | |||||
| SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |||||
| long myTime = (testDate.getTime() / 1000) + 60 * 60 * hour; | |||||
| testDate.setTime(myTime * 1000); | |||||
| return testDate; | |||||
| } | |||||
| /** | /** | ||||
| * 获得指定时间前几天,格式yyyy-MM-dd | * 获得指定时间前几天,格式yyyy-MM-dd | ||||
| * | * | ||||
| @@ -0,0 +1,52 @@ | |||||
| package com.iformall.utils; | |||||
| import com.iformall.utils.press.stateless.DefaultStatelessReduceRule; | |||||
| import com.iformall.utils.press.stateless.StatelessPingReduceRule; | |||||
| import com.iformall.utils.press.stateless.StatelessReduceRule; | |||||
| import java.math.BigDecimal; | |||||
| import java.math.RoundingMode; | |||||
| /** | |||||
| * 日期处理 | |||||
| * | |||||
| * @author stormeye.wu | |||||
| * @email wuguoqiang@iformall.com | |||||
| * @date 2019年1月5月 | |||||
| */ | |||||
| public class PressUtils { | |||||
| public final static int scale = 2; | |||||
| /** | |||||
| * randomPressValue | |||||
| * @param total 砍价总钱数(分) | |||||
| * @param left_total 已砍价总钱数(分) | |||||
| * @param limit_num 砍价限制次数 | |||||
| * @param limit_index 砍价index | |||||
| * @return | |||||
| */ | |||||
| public static int randomPressValue(int total, int left_total, int limit_num, int limit_index) { | |||||
| BigDecimal tt = BigDecimal.valueOf(total * 1.0D / 100).setScale(scale, RoundingMode.HALF_UP); | |||||
| BigDecimal lt = BigDecimal.valueOf(left_total* 1.0D / 100); | |||||
| StatelessReduceRule rule = new DefaultStatelessReduceRule(); | |||||
| BigDecimal current = rule.getReduce(tt, tt.subtract(lt), limit_num,limit_num - limit_index, scale); | |||||
| return current.intValue() * 100; | |||||
| } | |||||
| /** | |||||
| * stateLessPressValue 优先 1/3砍掉一半 | |||||
| * @param total 砍价总钱数(分) | |||||
| * @param left_total 已砍价总钱数(分) | |||||
| * @param limit_num 砍价限制次数 | |||||
| * @param limit_index 砍价index | |||||
| * @return | |||||
| */ | |||||
| public static int stateLessPressValue(int total, int left_total, int limit_num, int limit_index) { | |||||
| BigDecimal tt = BigDecimal.valueOf(total * 1.0D / 100).setScale(scale, RoundingMode.HALF_UP); | |||||
| BigDecimal lt = BigDecimal.valueOf(left_total* 1.0D / 100); | |||||
| StatelessReduceRule rule = new StatelessPingReduceRule(); | |||||
| BigDecimal current = rule.getReduce(tt, tt.subtract(lt), limit_num, limit_num - limit_index, scale); | |||||
| return current.intValue() * 100; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,63 @@ | |||||
| package com.iformall.utils.press; | |||||
| import java.math.BigDecimal; | |||||
| import java.util.List; | |||||
| /** | |||||
| * 仿拼多多砍价规则。 | |||||
| */ | |||||
| public class PingDuoDuoReduceRule implements ReduceRule { | |||||
| private final int percentOfFirstNReduce; | |||||
| private final int firstNReduce; | |||||
| private final SfReduceRule sfReduceRule = new SfReduceRule(); | |||||
| /** | |||||
| * new PingDuoDuoReduceRule(80, 10) 表示前10个人可砍掉总砍价金额的80%。 | |||||
| * | |||||
| * @param percentOfFirstNReduce 前N个人可砍掉的百分比,取值范围(0, 100] | |||||
| * @param firstNReduce 前N个砍价的人 | |||||
| */ | |||||
| public PingDuoDuoReduceRule(int percentOfFirstNReduce, int firstNReduce) { | |||||
| this.percentOfFirstNReduce = percentOfFirstNReduce; | |||||
| this.firstNReduce = firstNReduce; | |||||
| } | |||||
| /** | |||||
| * @param totalReduce 总共可砍价的金额 | |||||
| * @param totalReduceTimes 总共可砍价的次数 | |||||
| * @return 每次砍掉的金额组成的列表。 | |||||
| */ | |||||
| @Override | |||||
| public List<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes) { | |||||
| if (totalReduceTimes < firstNReduce) { | |||||
| throw new IllegalArgumentException("total reduce times is less than first n reduce times"); | |||||
| } | |||||
| if (percentOfFirstNReduce > 100 || percentOfFirstNReduce <= 0) { | |||||
| throw new IllegalArgumentException("reduce percent should in (0, 100]"); | |||||
| } | |||||
| BigDecimal firstNReduceTotal = BigDecimal.valueOf(totalReduce.doubleValue() * percentOfFirstNReduce / 100).setScale(2, BigDecimal.ROUND_HALF_EVEN); | |||||
| List<BigDecimal> ret = sfReduceRule.getReduceList(firstNReduceTotal, firstNReduce); | |||||
| BigDecimal leftReduceTotal = totalReduce.subtract(firstNReduceTotal).setScale(2, BigDecimal.ROUND_HALF_EVEN); | |||||
| if (totalReduceTimes - firstNReduce > 0) { | |||||
| ret.addAll(sfReduceRule.getReduceList(leftReduceTotal, totalReduceTimes - firstNReduce)); | |||||
| } | |||||
| return ret; | |||||
| } | |||||
| public static void main(String[] args) { | |||||
| PingDuoDuoReduceRule rule = new PingDuoDuoReduceRule(79, 3); | |||||
| List<BigDecimal> list = rule.getReduceList(new BigDecimal("100.21"), 11); | |||||
| BigDecimal total = BigDecimal.valueOf(0); | |||||
| int i = 0; | |||||
| for (BigDecimal val : list) { | |||||
| System.out.printf("第%d人砍掉了%s\n", ++i, val); | |||||
| total = total.add(val); | |||||
| } | |||||
| System.out.printf("总金额=%s\n", total); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| package com.iformall.utils.press; | |||||
| import java.math.BigDecimal; | |||||
| import java.math.RoundingMode; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import java.util.Random; | |||||
| /** | |||||
| * 砍价的几个因素: | |||||
| * 1. 可砍价的人数(N) | |||||
| * 2. 可砍掉的总金额(T) | |||||
| * 3. 每次砍价的最低砍价金额(M) | |||||
| * 4. 总砍价次数(K) | |||||
| * <p> | |||||
| * 本砍价规则将'每次砍价的最低砍价金额'默认为 := T / (K * 2)。 | |||||
| * | |||||
| * 本砍价规则只是{@link com.iformall.utils.RecursiveReduceRule}的递归版本。 | |||||
| */ | |||||
| public class RecursiveReduceRule implements ReduceRule { | |||||
| private final int scale; | |||||
| public RecursiveReduceRule(int scale) { | |||||
| this.scale = scale; | |||||
| } | |||||
| @Override | |||||
| public List<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes) { | |||||
| if (totalReduceTimes <= 0) { | |||||
| throw new IllegalArgumentException("total reduce times should gte 1"); | |||||
| } | |||||
| // 最低砍价金额默认为: 总砍价金额 / (砍价次数 * 2) | |||||
| BigDecimal minReduce = totalReduce.divide(BigDecimal.valueOf(totalReduceTimes).multiply(BigDecimal.valueOf(2)), BigDecimal.ROUND_HALF_EVEN); | |||||
| return getReduceList(totalReduce, totalReduceTimes, minReduce); | |||||
| } | |||||
| public List<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes, BigDecimal minReduce) { | |||||
| List<BigDecimal> ret = new ArrayList<>(); | |||||
| doReduce(totalReduce, totalReduceTimes, minReduce, ret); | |||||
| return ret; | |||||
| } | |||||
| private void doReduce(BigDecimal leftTotalReduce, int leftReduceTimes, BigDecimal minReduce, List<BigDecimal> vs) { | |||||
| if (leftReduceTimes <= 0) { | |||||
| return; | |||||
| } else if (leftReduceTimes == 1) { | |||||
| vs.add(leftTotalReduce); | |||||
| return; | |||||
| } | |||||
| double min = minReduce.doubleValue(); | |||||
| double max = leftTotalReduce.doubleValue() - min * leftReduceTimes; | |||||
| double adjustedMax = min + (leftReduceTimes > 10 ? max / 3 : max); | |||||
| double ratio = new Random().nextDouble(); | |||||
| BigDecimal current = BigDecimal.valueOf(min + (adjustedMax - min) * ratio).setScale(this.scale, RoundingMode.HALF_EVEN); | |||||
| vs.add(current); | |||||
| doReduce(leftTotalReduce.subtract(current), --leftReduceTimes, minReduce, vs); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| package com.iformall.utils.press; | |||||
| import java.math.BigDecimal; | |||||
| import java.util.List; | |||||
| public interface ReduceRule { | |||||
| /** | |||||
| * 获取砍价列表。 | |||||
| * | |||||
| * @param totalReduce 总共可砍价的金额 | |||||
| * @param totalReduceTimes 总共可砍价的次数 | |||||
| * @return 砍价列表 | |||||
| */ | |||||
| List<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes); | |||||
| } | |||||
| @@ -0,0 +1,68 @@ | |||||
| package com.iformall.utils.press; | |||||
| import java.math.BigDecimal; | |||||
| import java.math.RoundingMode; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import java.util.Random; | |||||
| /** | |||||
| * 砍价的几个因素: | |||||
| * 1. 可砍价的人数(N) | |||||
| * 2. 可砍掉的总金额(T) | |||||
| * 3. 每次砍价的最低砍价金额(M) | |||||
| * 4. 总砍价次数(K) | |||||
| * <p> | |||||
| * 本砍价规则将'每次砍价的最低砍价金额'默认为 := T / (K * 2)。 | |||||
| */ | |||||
| public class SfReduceRule implements ReduceRule { | |||||
| @Override | |||||
| public List<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes) { | |||||
| if (totalReduceTimes <= 0) { | |||||
| throw new IllegalArgumentException("total reduce times should gte 1"); | |||||
| } | |||||
| // 最低砍价金额默认为: 总砍价金额 / (砍价次数 * 2) | |||||
| BigDecimal minReduce = totalReduce.divide(BigDecimal.valueOf(totalReduceTimes).multiply(BigDecimal.valueOf(2)), BigDecimal.ROUND_HALF_EVEN); | |||||
| return getReduceList(totalReduce, totalReduceTimes, minReduce); | |||||
| } | |||||
| public List<BigDecimal> getReduceList(BigDecimal totalReduce, int totalReduceTimes, BigDecimal minReduce) { | |||||
| List<BigDecimal> ret = new ArrayList<>(); | |||||
| Random rand = new Random(); | |||||
| double[] vs = new double[totalReduceTimes]; | |||||
| double total = 0d; | |||||
| for (int i = 0; i < totalReduceTimes; i++) { | |||||
| double v = rand.nextDouble() * totalReduce.doubleValue(); | |||||
| vs[i] = v; | |||||
| total += v; | |||||
| } | |||||
| double minReduceVal = minReduce.doubleValue(); | |||||
| BigDecimal t = BigDecimal.valueOf(0d); | |||||
| for (int i = 0; i < totalReduceTimes - 1; i++) { | |||||
| double vss = vs[i] / total * (totalReduce.doubleValue() - minReduceVal * totalReduceTimes) + minReduceVal; | |||||
| BigDecimal reduce = BigDecimal.valueOf(vss).setScale(2, RoundingMode.HALF_EVEN); | |||||
| t = t.add(reduce); | |||||
| ret.add(reduce); | |||||
| } | |||||
| ret.add(totalReduce.subtract(t).setScale(2, RoundingMode.HALF_EVEN)); | |||||
| return ret; | |||||
| } | |||||
| public static void main(String[] args) { | |||||
| int t = new Random().nextInt(20) + 1; | |||||
| int i = 0; | |||||
| BigDecimal tt = BigDecimal.valueOf(new Random().nextDouble() * 10000 + 1).setScale(2, RoundingMode.HALF_UP); | |||||
| List<BigDecimal> reduceList = new SfReduceRule().getReduceList(tt, t); | |||||
| BigDecimal total = new BigDecimal(0d); | |||||
| for (BigDecimal d : reduceList) { | |||||
| total = total.add(d); | |||||
| System.out.printf("第%d个人砍掉了%s\n", ++i, d); | |||||
| } | |||||
| total = total.setScale(2, RoundingMode.HALF_UP); | |||||
| System.out.println("砍价总金额=" + tt + ",合计总金额=" + total); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,26 @@ | |||||
| package com.iformall.utils.press.stateless; | |||||
| import java.math.BigDecimal; | |||||
| import java.math.RoundingMode; | |||||
| import java.util.Random; | |||||
| public class DefaultStatelessReduceRule implements StatelessReduceRule { | |||||
| @Override | |||||
| public BigDecimal getReduce(BigDecimal totalReduce, BigDecimal leftReduce, int totalReduceTimes, int leftReduceTimes, int scale) { | |||||
| if (leftReduceTimes <= 0) { | |||||
| throw new IllegalArgumentException("leftReduceTimes should gte 1"); | |||||
| } | |||||
| if (leftReduceTimes == 1) { | |||||
| return leftReduce; | |||||
| } | |||||
| BigDecimal minReduce = totalReduce.divide(BigDecimal.valueOf(totalReduceTimes).multiply(BigDecimal.valueOf(2)), RoundingMode.HALF_EVEN); | |||||
| double min = minReduce.doubleValue(); | |||||
| double max = leftReduce.doubleValue() - min * leftReduceTimes; | |||||
| double adjustedMax = min + (leftReduceTimes > 10 ? max / 3 : max); | |||||
| double ratio = new Random().nextDouble(); | |||||
| return BigDecimal.valueOf(min + (adjustedMax - min) * ratio).setScale(scale, RoundingMode.HALF_EVEN); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| package com.iformall.utils.press.stateless; | |||||
| import java.math.BigDecimal; | |||||
| import java.math.RoundingMode; | |||||
| /** | |||||
| * 本砍价规则默认为:前1/3的人砍掉总砍价金额的一半,剩余的2/3的人瓜分另外一半总砍价金额。 | |||||
| */ | |||||
| public class StatelessPingReduceRule implements StatelessReduceRule { | |||||
| private DefaultStatelessReduceRule rule; | |||||
| public StatelessPingReduceRule() { | |||||
| this.rule = new DefaultStatelessReduceRule(); | |||||
| } | |||||
| public BigDecimal getReduce(BigDecimal totalReduce, BigDecimal leftTotalReduce, int totalReduceTimes, int leftReduceTimes, int scale) { | |||||
| if (totalReduceTimes == 1) { | |||||
| return totalReduce; | |||||
| } | |||||
| int firstN = Math.max(totalReduceTimes / 3, 1); // 前1/3的人 | |||||
| BigDecimal halfReduce = totalReduce.divide(BigDecimal.valueOf(2), RoundingMode.HALF_EVEN).setScale(scale, RoundingMode.HALF_EVEN); | |||||
| int leftN = totalReduceTimes - firstN; | |||||
| BigDecimal theOtherHalf = totalReduce.subtract(halfReduce); | |||||
| if (totalReduceTimes - leftReduceTimes < firstN) { | |||||
| // 前1/3的人砍掉一半 | |||||
| BigDecimal leftTotal = halfReduce.subtract(totalReduce.subtract(leftTotalReduce)); | |||||
| return rule.getReduce(halfReduce, leftTotal, firstN, firstN - (totalReduceTimes - leftReduceTimes), scale); | |||||
| } else { | |||||
| // 剩下的人砍掉一半 | |||||
| return rule.getReduce(theOtherHalf, leftTotalReduce, leftN, leftReduceTimes, scale); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| package com.iformall.utils.press.stateless; | |||||
| import java.math.BigDecimal; | |||||
| /** | |||||
| * 该接口表示无状态的砍价规则,每次调用只生成当次的砍价金额。 | |||||
| * 记录总砍价次数、剩余的可砍价次数、总砍价金额以及剩余的可砍价金额是应用应该考虑的问题。 | |||||
| */ | |||||
| public interface StatelessReduceRule { | |||||
| /** | |||||
| * 获取砍价列表。 | |||||
| * | |||||
| * @param totalReduce 总共可砍价的金额 | |||||
| * @param leftReduce 剩下的可砍价金额,首次调用时 totalReduce 应该等于 leftReduce | |||||
| * @param totalReduceTimes 总共可砍价的次数 | |||||
| * @param leftReduceTimes 当前剩下的砍价次数,首次调用时 totalReduceTimes 应该等于 leftReduceTimes | |||||
| * @return 砍价列表 | |||||
| */ | |||||
| BigDecimal getReduce(BigDecimal totalReduce, BigDecimal leftReduce, int totalReduceTimes, int leftReduceTimes, int scale); | |||||
| } | |||||