从0到1:理发店预约剪发小程序开发笔记(上)

背景

理发师可以在小程序上设置自己的可预约时间,价格,自我介绍,顾客可以根据理发师的日程安排选择合适的时间进行预约和支付。这样可以提高预约的效率,减少沟通成本,方便双方的安排。

功能规划

  • 首页展示:展示最新的公告通知、理发师推荐等内容,吸引用户关注。
  • 理发师列表:列出所有在班的理发师信息,包括个人简介、星级、理发价格等,方便用户选择合适的理发师。
  • 预约功能:用户可以根据自己的需求选择理发师,进行预约并支付费用。
  • 预约管理:用户可以查看自己的预约记录,包括已完成的预约和待核销的预约,也可以取消预约(所支付费用原路退回)。

功能图

数据库设计

复制代码
MeetModel.DB_STRUCTURE = {
	_pid: 'string|true',
	MEET_ID: 'string|true',
	MEET_ADMIN_ID: 'string|true|comment=添加的管理员',
	MEET_TITLE: 'string|true|comment=标题',

	MEET_JOIN_FORMS: 'array|true|default=[]|comment=表单字段设置',
	MEET_DAYS: 'array|true|default=[]|comment=最近一次修改保存的可用日期',

	MEET_CATE_ID: 'string|true|comment=分类编号',
	MEET_CATE_NAME: 'string|true|comment=分类冗余',

	MEET_FORMS: 'array|true|default=[]',
	MEET_OBJ: 'object|true|default={}',

	MEET_CANCEL_SET: 'int|true|default=1|comment=取消设置 0=不允,1=允许,2=仅开始前可取消',

	MEET_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中,9=停止预约,10=已关闭',
	MEET_ORDER: 'int|true|default=9999',
	MEET_VOUCH: 'int|true|default=0',

	MEET_QR: 'string|false',


	MEET_FEE: 'int|false|comment=支付金额 分',
	MEET_PAY_CNT: 'int|true|default=0|comment=支付数',
	MEET_PAY_FEE: 'int|true|default=0|comment=支付额',
	MEET_JOIN_CNT: 'int|true|default=0|comment=预约数',

	MEET_PHONE: 'string|false|comment=登录手机',
	MEET_PASSWORD: 'string|false|comment=登录密码',
	MEET_TOKEN: 'string|false|comment=当前登录token',
	MEET_TOKEN_TIME: 'int|true|default=0|comment=当前登录token time',
	MEET_MINI_OPENID: 'string|false|comment=小程序openid',
	MEET_LOGIN_CNT: 'int|true|default=0|comment=登陆次数',
	MEET_LOGIN_TIME: 'int|false|comment=最近登录时间',

	MEET_VIEW_CNT: 'int|true|default=0',

	MEET_ADD_TIME: 'int|true',
	MEET_EDIT_TIME: 'int|true',
	MEET_ADD_IP: 'string|false',
	MEET_EDIT_IP: 'string|false',
};

ProductModel.DB_STRUCTURE = {
	_pid: 'string|true',
	PRODUCT_ID: 'string|true',

	PRODUCT_TITLE: 'string|true|comment=标题', 
	PRODUCT_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中',

	PRODUCT_CATE_ID: 'string|true|default=0|comment=分类',
	PRODUCT_CATE_NAME: 'string|false|comment=分类冗余',

	PRODUCT_ORDER: 'int|true|default=9999',
	PRODUCT_VOUCH: 'int|true|default=0',

	PRODUCT_FORMS: 'array|true|default=[]',
	PRODUCT_OBJ: 'object|true|default={}',

	PRODUCT_QR: 'string|false',
	PRODUCT_VIEW_CNT: 'int|true|default=0',

	PRODUCT_ADD_TIME: 'int|true',
	PRODUCT_EDIT_TIME: 'int|true',
	PRODUCT_ADD_IP: 'string|false',
	PRODUCT_EDIT_IP: 'string|false',
};

核心实现

复制代码
class MeetService extends BaseProjectService {

	constructor() {
		super();
		this._log = new LogUtil(projectConfig.MEET_LOG_LEVEL);
	}

	/**
	 * 抛出异常
	 * @param {*} msg 
	 * @param {*} code 
	 */
	AppError(msg) {
		this._log.error(msg);
		super.AppError(msg);
	}

	_meetLog(meet, func = '', msg = '') {
		let str = '';
		str = `[MEET=${meet.MEET_TITLE}][${func}] ${msg}`;
		this._log.debug(str);
	}

	async minuteJob() {
		console.log('### minuteJob >>>>>');


		// 未支付的成功订单取消  
		let time = this._timestamp - 6 * 60 * 1000;
		console.log('###### Begin>>> 未支付订单6分钟后取消, time<=' + time + ', ' + timeUtil.timestamp2Time(time));


		let where = {
			JOIN_STATUS: ['in', [JoinModel.STATUS.SUCC]],
			JOIN_PAY_STATUS: 0,
			JOIN_ADD_TIME: ['<=', time],
		}
		let rows = await JoinModel.getAll(where, '*', {}, 3000, false);
		console.log('未支付订单6分钟后取消, count=', rows.length);

		for (let k in rows) {
			let join = rows[k];

			let tradeNo = join.JOIN_PAY_TRADE_NO;

			if (!await this.fixMeetJoinPay(tradeNo, join.JOIN_MEET_ID, join.JOIN_MEET_TIME_MARK)) {
				console.log('该报名记录未支付,已取消并删除!', join);
			}

		}

		console.log('###### END. 未支付订单6分钟后取消');
	}


	/** 统一获取Meet(某天) */
	async getMeetOneDay(meetId, day, where, fields = '*') {

		let meet = await MeetModel.getOne(where, fields);
		if (!meet) return meet;

		meet.MEET_DAYS_SET = await this.getDaysSet(meetId, day, day);
		return meet;
	}

	/** 获取日期设置 */
	async getDaysSet(meetId, startDay, endDay = null) {
		let where = {
			DAY_MEET_ID: meetId
		}
		if (startDay && endDay && endDay == startDay)
			where.day = startDay;
		else if (startDay && endDay)
			where.day = ['between', startDay, endDay];
		else if (!startDay && endDay)
			where.day = ['<=', endDay];
		else if (startDay && !endDay)
			where.day = ['>=', startDay];

		let orderBy = {
			'day': 'asc'
		}
		let list = await DayModel.getAllBig(where, 'day,dayDesc,times', orderBy, 1000);

		for (let k = 0; k < list.length; k++) {
			delete list[k]._id;
		}

		return list;
	}

	// 按时段统计某时段报名情况
	async statJoinCnt(meetId, timeMark) {
		let whereDay = {
			DAY_MEET_ID: meetId,
			day: this.getDayByTimeMark(timeMark)
		};
		let day = await DayModel.getOne(whereDay, 'times');
		if (!day) return;

		let whereJoin = {
			JOIN_MEET_TIME_MARK: timeMark,
			JOIN_MEET_ID: meetId
		};
		let ret = await JoinModel.groupCount(whereJoin, 'JOIN_STATUS');

		let stat = { //统计数据
			succCnt: ret['JOIN_STATUS_1'] || 0, //1=预约成功,
			cancelCnt: ret['JOIN_STATUS_10'] || 0, //10=已取消, 
			adminCancelCnt: ret['JOIN_STATUS_99'] || 0, //99=后台取消
		};

		let times = day.times;
		for (let j in times) {
			if (times[j].mark === timeMark) {
				let data = {
					['times.' + j + '.stat']: stat
				}
				await DayModel.edit(whereDay, data);
				return;
			}
		}

	}


	// 预约前检测
	async beforeJoin(userId, meetId, timeMark) {
		await this.checkMeetRules(userId, meetId, timeMark);
	}

	// 修正某用户所有未支付的成功订单状态,无须支付的不用处理
	async fixUserMeetJoinPayRecord(userId) {
		let where = {
			JOIN_USER_ID: userId,
			JOIN_PAY_STATUS: 0,
			JOIN_STATUS: ['in', [JoinModel.STATUS.SUCC]],
		}
		let list = await JoinModel.getAll(where);

		for (let k = 0; k < list.length; k++) {
			await this.fixMeetJoinPay(list[k].JOIN_PAY_TRADE_NO, list[k].JOIN_MEET_ID, list[k].JOIN_MEET_TIME_MARK);
		}
	}

	// 修正某订单状态 (仅需支付订单)
	async fixMeetJoinPay(tradeNo, meetId, timeMark) {

		if (!tradeNo) {
			// 无支付号空单 删除
			await JoinModel.del({ JOIN_PAY_TRADE_NO: tradeNo });

			// 重新统计
			this.statJoinCnt(meetId, timeMark);

			return false;
		}

		let payService = new PayService();
		if (!await payService.fixPayResult(tradeNo)) {
			// 关闭未支付单
			payService.closePay(tradeNo);

			// 未支付 
			await JoinModel.del({ JOIN_PAY_TRADE_NO: tradeNo });

			// 重新统计
			this.statJoinCnt(meetId, timeMark);

			return false;
		}

		// 已支付
		let pay = await PayModel.getOne({ PAY_TRADE_NO: tradeNo });
		if (!pay) this.AppError('支付流水异常,请核查');

		// 更新支付信息
		let data = {
			JOIN_PAY_STATUS: 1,
			JOIN_PAY_TRADE_NO: tradeNo,
			JOIN_PAY_FEE: pay.PAY_TOTAL_FEE,
			JOIN_PAY_TIME: pay.PAY_END_TIME,
		}
		await JoinModel.edit({ JOIN_PAY_TRADE_NO: tradeNo }, data);


		// 重新统计
		this.statJoinCnt(meetId, timeMark);
		return true;
	}

	// 用户预约逻辑
	async prepay(userId, meetId, timeMark, formsList) {
		 
	}



	// 根据日期获取其所在天设置
	getDaySetByDay(meet, day) {
		for (let k = 0; k < meet.MEET_DAYS_SET.length; k++) {
			if (meet.MEET_DAYS_SET[k].day == day)
				return dataUtil.deepClone(meet.MEET_DAYS_SET[k]);
		}
		return null;
	}

	// 根据时段标识获取其所在天 
	getDayByTimeMark(timeMark) {
		return timeMark.substr(1, 4) + '-' + timeMark.substr(5, 2) + '-' + timeMark.substr(7, 2);
	}

	// 根据时段标识获取其所在天设置
	getDaySetByTimeMark(meet, timeMark) {
		let day = this.getDayByTimeMark(timeMark);

		for (let k = 0; k < meet.MEET_DAYS_SET.length; k++) {
			if (meet.MEET_DAYS_SET[k].day == day)
				return dataUtil.deepClone(meet.MEET_DAYS_SET[k]);
		}
		return null;
	}

	// 根据时段标识获取其所在时段设置
	getTimeSetByTimeMark(meet, timeMark) {
		let day = this.getDayByTimeMark(timeMark);

		for (let k = 0; k < meet.MEET_DAYS_SET.length; k++) {
			if (meet.MEET_DAYS_SET[k].day != day) continue;

			for (let j in meet.MEET_DAYS_SET[k].times) {
				if (meet.MEET_DAYS_SET[k].times[j].mark == timeMark)
					return dataUtil.deepClone(meet.MEET_DAYS_SET[k].times[j]);
			}
		}
		return null;
	}

	// 预约时段人数和状态控制校验
	async checkMeetTimeControll(meet, timeMark, meetPeopleCnt = 1) {
		if (!meet) this.AppError('预约时段设置错误, 预约项目不存在');

		let daySet = this.getDaySetByTimeMark(meet, timeMark); // 当天设置
		let timeSet = this.getTimeSetByTimeMark(meet, timeMark); // 预约时段设置

		if (!daySet || !timeSet) this.AppError('预约时段设置错误day&time');

		let statusDesc = timeSet.status == 1 ? '开启' : '关闭';
		let limitDesc = '';
		if (timeSet.isLimit) {
			limitDesc = '人数上限MAX=' + timeSet.limit;
		} else
			limitDesc = '人数不限制NO';

		this._meetLog(meet, `------------------------------`);
		this._meetLog(meet, `#预约时段控制,预约日期=<${daySet.day}>`, `预约时段=[${timeSet.start}-${timeSet.end}],状态=${statusDesc}, ${limitDesc} 当前预约成功人数=${timeSet.stat.succCnt}`);

		if (timeSet.status == 0) this.AppError('该时段预约已经关闭,请选择其他');

		// 时段总人数限制
		if (timeSet.isLimit) {
			if (timeSet.stat.succCnt >= timeSet.limit) {
				this.AppError('该时段预约人员已满,请选择其他');
			}

			let maxCnt = timeSet.limit - timeSet.stat.succCnt;

			if (maxCnt < meetPeopleCnt) {
				this.AppError('本时段最多还可以预约' + (maxCnt) + '人,您当前提交了' + meetPeopleCnt + '人,请调整后再提交');
			}
		}

	}


	/** 报名规则校验 */
	async checkMeetRules(userId, meetId, timeMark, formsList = null) {

		// 预约时段是否存在
		let meetWhere = {
			_id: meetId
		};
		let day = this.getDayByTimeMark(timeMark);
		let meet = await this.getMeetOneDay(meetId, day, meetWhere);
		if (!meet) {
			this.AppError('预约时段选择错误,请重新选择');
		}

		// 预约时段人数和状态控制校验
		let meetPeopleCnt = formsList ? formsList.length : 1;

		await this.checkMeetTimeControll(meet, timeMark, meetPeopleCnt);

		// 截止规则  
		await this.checkMeetEndSet(meet, timeMark);


		// 针对用户的次数限制
		await this.checkMeetLimitSet(userId, meet, timeMark, meetPeopleCnt);

	}


	// 预约次数限制校验
	async checkMeetLimitSet(userId, meet, timeMark, nowCnt) {
		if (!meet) this.AppError('预约次数规则错误, 预约项目不存在');
		let meetId = meet._id;

		let daySet = this.getDaySetByTimeMark(meet, timeMark); // 当天设置
		let timeSet = this.getTimeSetByTimeMark(meet, timeMark); // 预约时段设置

		this._meetLog(meet, `------------------------------`);
		this._meetLog(meet, `#预约次数规则,预约日期=<${daySet.day}>`, `预约时段=[${timeSet.start}~${timeSet.end}]`);

		let where = {
			JOIN_MEET_ID: meetId,
			JOIN_MEET_TIME_MARK: timeMark,
			JOIN_USER_ID: userId,
			JOIN_STATUS: JoinModel.STATUS.SUCC
		}
		let cnt = await JoinModel.count(where);
		let maxCnt = projectConfig.MEET_MAX_JOIN_CNT;
		this._meetLog(meet, `预约次数规则,mode=本时段可预约${maxCnt}次`, `当前已预约=${cnt}次`);

		if (cnt >= maxCnt)
			this.AppError(`您本时段已经预约,不能继续预约`);

	}



	// 预约截止设置校验
	async checkMeetEndSet(meet, timeMark) {
		if (!meet) this.AppError('预约截止规则错误, 预约项目不存在');


		this._meetLog(meet, `------------------------------`);
		let daySet = this.getDaySetByTimeMark(meet, timeMark); // 当天设置
		let timeSet = this.getTimeSetByTimeMark(meet, timeMark); // 预约时段设置

		this._meetLog(meet, `#预约截止规则,预约日期=<${daySet.day}>`, `预约时段=[${timeSet.start}-${timeSet.end}]`);

		let nowTime = timeUtil.time('Y-M-D h:m:s');

		/*
		let startTime = daySet.day + ' ' + timeSet.start + ':00';
		this._meetLog(meet, `预约开始规则,mode=<时段过期判定>`, `预约开始时段=${startTime},当前时段=${nowTime}`);
		if (nowTime > startTime) {
			this.AppError('该时段已开始,无法预约,请选择其他');
		}*/

		let endTime = daySet.day + ' ' + timeSet.end + ':59';
		this._meetLog(meet, `预约开始规则,mode=<时段过期判定>`, `预约结束时段=${endTime},当前时段=${nowTime}`);
		if (nowTime > endTime) {
			this.AppError('该时段已结束,无法预约,请选择其他');
		}

	}


	/**  预约详情 */
	async viewMeet(userId, meetId) {
		await this.fixUserMeetJoinPayRecord(userId);

		let fields = '*';

		let where = {
			_id: meetId,
			MEET_STATUS: ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]
		}
		let meet = await MeetModel.getOne(where, fields);
		if (!meet) return null;

		MeetModel.inc(meetId, 'MEET_VIEW_CNT', 1);

		let getDaysSet = [];
		meet.MEET_DAYS_SET = await this.getDaysSet(meetId, timeUtil.time('Y-M-D')); //今天及以后
		let daysSet = meet.MEET_DAYS_SET;

		let now = timeUtil.time('Y-M-D');
		for (let k = 0; k < daysSet.length; k++) {
			let dayNode = daysSet[k];

			if (dayNode.day < now) continue; // 排除过期

			let getTimes = [];

			for (let j in dayNode.times) {
				let timeNode = dayNode.times[j];

				// 排除状态关闭的时段
				if (timeNode.status != 1) continue;

				// 判断数量是否已满
				if (timeNode.isLimit && timeNode.stat.succCnt >= timeNode.limit)
					timeNode.error = '预约已满';

				// 截止规则
				if (!timeNode.error) {
					try {
						await this.checkMeetEndSet(meet, timeNode.mark);
					} catch (ex) {
						if (ex.name == 'AppError')
							timeNode.error = '预约结束';
						else
							throw ex;
					}
				}

				getTimes.push(timeNode);
			}
			dayNode.times = getTimes;

			getDaysSet.push(dayNode);
		}


		// 预约人数
		let whereJoin = {
			JOIN_MEET_ID: meetId,
			JOIN_STATUS: JoinModel.STATUS.SUCC
		}
		let cntJoin = await JoinModel.count(whereJoin);

		// 收藏人数
		let FavModel = require('../model/fav_model.js');
		let whereFav = {
			FAV_OID: meetId,
		}
		let cntFav = await FavModel.count(whereFav);

		// 只返回需要的字段
		let ret = {};

		ret.cntJoin = cntJoin;
		ret.cntFav = cntFav;

		ret.MEET_DAYS_SET = getDaysSet;

		ret.MEET_FEE = meet.MEET_FEE;
		ret.MEET_QR = meet.MEET_QR;
		ret.MEET_TITLE = meet.MEET_TITLE;
		ret.MEET_CATE_NAME = meet.MEET_CATE_NAME;
		ret.MEET_CATE_ID = meet.MEET_CATE_ID;
		ret.MEET_OBJ = meet.MEET_OBJ;
		ret.MEET_VIEW_CNT = meet.MEET_VIEW_CNT;
		ret.cover = await cloudUtil.getTempFileURLOne(meet.MEET_OBJ.cover[0]);
 
		return ret;
	}


	/**  预约前获取关键信息 */
	async detailForJoin(userId, meetId, timeMark) {

		await this.fixUserMeetJoinPayRecord(userId);

		let fields = 'MEET_FEE,MEET_DAYS_SET,MEET_JOIN_FORMS, MEET_TITLE';

		let where = {
			_id: meetId,
			MEET_STATUS: ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]
		}
		let day = this.getDayByTimeMark(timeMark);
		let meet = await this.getMeetOneDay(meetId, day, where, fields);
		if (!meet) return null;

		let dayDesc = timeUtil.fmtDateCHN(this.getDaySetByTimeMark(meet, timeMark).day);

		let timeSet = this.getTimeSetByTimeMark(meet, timeMark);
		let timeDesc = timeSet.start + '~' + timeSet.end;
		meet.dayDesc = dayDesc + ' ' + timeDesc;

		// 取出本人最近一次本时段填写表单
		let whereMy = {
			JOIN_USER_ID: userId,
		}
		let orderByMy = {
			JOIN_ADD_TIME: 'desc'
		}
		let joinMy = await JoinModel.getOne(whereMy, 'JOIN_FORMS', orderByMy);


		if (joinMy)
			meet.myForms = joinMy.JOIN_FORMS;
		else
			meet.myForms = [];

		meet.MEET_FEE = Number(dataUtil.fmtMoney(meet.MEET_FEE / 100));

		return meet;
	}

	/** 获取某天可用时段 */
	async getUsefulTimesByDaysSet(meetId, day) {
		let where = {
			DAY_MEET_ID: meetId,
			day
		}
		let daysSet = await DayModel.getAll(where, 'day,times');
		let usefulTimes = [];
		for (let k = 0; k < daysSet.length; k++) {
			if (daysSet[k].day != day)
				continue;

			let times = daysSet[k].times;
			for (let j in times) {
				if (times[j].status != 1) continue;
				usefulTimes.push(times[j]);
			}
			break;

		}
		return usefulTimes;
	}

	/** 按天获取预约项目 */
	async getMeetListByDay(day) {
		let where = {
			MEET_STATUS: MeetModel.STATUS.COMM,
		};

		let orderBy = {
			'MEET_ORDER': 'asc',
			'MEET_ADD_TIME': 'desc'
		};

		let fields = 'MEET_TITLE,MEET_DAYS_SET,MEET_OBJ.cover';

		let list = await MeetModel.getAll(where, fields, orderBy);

		let retList = [];

		for (let k = 0; k < list.length; k++) {
			let usefulTimes = await this.getUsefulTimesByDaysSet(list[k]._id, day);

			if (usefulTimes.length == 0) continue;

			let node = {};
			node.timeDesc = usefulTimes.length > 1 ? usefulTimes.length + '个时段' : usefulTimes[0].start;
			node.title = list[k].MEET_TITLE;
			node.pic = list[k].MEET_OBJ.cover;
			node._id = list[k]._id;
			retList.push(node);

		}
		return retList;
	}

	/** 获取从某天开始可预约的日期 */
	async getHasDaysFromDay(day) {
		let where = {
			day: ['>=', day],
		};

		let fields = 'times,day';
		let list = await DayModel.getAllBig(where, fields);

		let retList = [];
		for (let k = 0; k < list.length; k++) {
			for (let n in list[k].times) {
				if (list[k].times[n].status == 1) {
					retList.push(list[k].day);
					break;
				}
			}
		}
		return retList;
	}

	/** 取得预约分页列表 */
	async getMeetList({
		search, // 搜索条件
		sortType, // 搜索菜单
		sortVal, // 搜索菜单
		orderBy, // 排序 
		cateId, //分类查询条件
		page,
		size,
		isTotal = true,
		oldTotal
	}) {

		orderBy = orderBy || {
			'MEET_ORDER': 'asc',
			'MEET_ADD_TIME': 'desc'
		};
		let fields = 'MEET_TITLE,MEET_OBJ,MEET_DAYS,MEET_CATE_NAME,MEET_CATE_ID,MEET_FEE';

		let where = {};
		where.and = {
			_pid: this.getProjectId() //复杂的查询在此处标注PID
		};

		if (cateId && cateId !== '0') where.and.MEET_CATE_ID = cateId;

		where.and.MEET_STATUS = ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]; // 状态  

		if (util.isDefined(search) && search) {
			where.or = [
				{ MEET_TITLE: ['like', search] },
			];

		} else if (sortType && util.isDefined(sortVal)) {
			// 搜索菜单
			switch (sortType) {
				case 'sort': {
					orderBy = this.fmtOrderBySort(sortVal, 'NEWS_ADD_TIME');
					break;
				}
				case 'cateId': {
					if (sortVal) where.and.MEET_CATE_ID = String(sortVal);
					break;
				}
			}
		}
		let result = await MeetModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);

		return result;
	}



	/** 取消我的预约 只有成功可以取消 */
	async cancelMyJoin(userId, joinId) {

		let where = {
			JOIN_USER_ID: userId,
			_id: joinId,
			JOIN_IS_CHECKIN: 0, // 核销不能取消
			JOIN_STATUS: JoinModel.STATUS.SUCC
		};
		let join = await JoinModel.getOne(where);

		if (!join) {
			this.AppError('未找到可取消的预约记录');
		}

		// 取消规则判定
		let whereMeet = {
			_id: join.JOIN_MEET_ID,
			MEET_STATUS: ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]
		}
		let meet = await this.getMeetOneDay(join.JOIN_MEET_ID, join.JOIN_MEET_DAY, whereMeet);
		if (!meet) this.AppError('预约项目不存在或者已关闭');

		let daySet = this.getDaySetByTimeMark(meet, join.JOIN_MEET_TIME_MARK);
		let timeSet = this.getTimeSetByTimeMark(meet, join.JOIN_MEET_TIME_MARK);
		if (!timeSet) this.AppError('被取消的时段不存在');


		if (meet.MEET_CANCEL_SET == 0)
			this.AppError('该预约不能取消');


		let startT = daySet.day + ' ' + timeSet.start + ':00';
		let startTime = timeUtil.time2Timestamp(startT);
		let now = timeUtil.time();
		if (meet.MEET_CANCEL_SET == 2 && now > startTime)
			this.AppError('该预约时段已经开始,无法取消');

		// TODO 已过期不能取消

		if (join.JOIN_PAY_STATUS == 99) {
			// 无须支付
			// 更新记录 
			let data = {
				JOIN_STATUS: JoinModel.STATUS.CANCEL,
				JOIN_CANCEL_TIME: this._timestamp,
			}
			await JoinModel.edit(joinId, data);
		}
		else {
			let tradeNo = join.JOIN_PAY_TRADE_NO;
			if (!await this.fixMeetJoinPay(tradeNo, join.JOIN_MEET_ID, join.JOIN_MEET_TIME_MARK)) {
				this.AppError('该预约记录未支付,已取消并删除!');
			}
			let payService = new PayService();
			await payService.refundPay(tradeNo, '用户取消预约');

			// 更新记录 
			let data = {
				JOIN_STATUS: JoinModel.STATUS.CANCEL,
				JOIN_CANCEL_TIME: this._timestamp,
				JOIN_PAY_STATUS: 8,
			}
			await JoinModel.edit(joinId, data);
		}

		// await JoinModel.del(where); 以前是删除,现在改为取消


		// 统计
		this.statJoinCnt(join.JOIN_MEET_ID, join.JOIN_MEET_TIME_MARK);

	}

	/** 取得我的预约详情 */
	async getMyJoinDetail(userId, joinId) {

		let fields = 'JOIN_PAY_STATUS,JOIN_PAY_FEE,JOIN_COMPLETE_END_TIME,JOIN_IS_CHECKIN,JOIN_CHECKIN_TIME,JOIN_REASON,JOIN_MEET_ID,JOIN_MEET_TITLE,JOIN_MEET_DAY,JOIN_MEET_TIME_START,JOIN_MEET_TIME_END,JOIN_STATUS,JOIN_ADD_TIME,JOIN_CODE,JOIN_FORMS';

		let where = {
			_id: joinId,
			JOIN_USER_ID: userId
		};
		return await JoinModel.getOne(where, fields);
	}

	/** 取得我的预约分页列表 */
	async getMyJoinList(userId, {
		search, // 搜索条件
		sortType, // 搜索菜单
		sortVal, // 搜索菜单
		orderBy, // 排序 
		page,
		size,
		isTotal = true,
		oldTotal
	}) {

		await this.fixUserMeetJoinPayRecord(userId);

		orderBy = orderBy || {
			//	'JOIN_MEET_DAY': 'desc',
			//	'JOIN_MEET_TIME_START': 'desc',
			'JOIN_ADD_TIME': 'desc'
		};
		let fields = 'JOIN_COMPLETE_END_TIME,JOIN_IS_CHECKIN,JOIN_REASON,JOIN_MEET_ID,JOIN_MEET_TITLE,JOIN_MEET_DAY,JOIN_MEET_TIME_START,JOIN_MEET_TIME_END,JOIN_STATUS,JOIN_ADD_TIME,JOIN_OBJ';

		let where = {
			JOIN_USER_ID: userId
		};
		//where.MEET_STATUS = ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]; // 状态  

		if (util.isDefined(search) && search) {
			where['JOIN_MEET_TITLE'] = {
				$regex: '.*' + search,
				$options: 'i'
			};
		} else if (sortType) {
			// 搜索菜单
			switch (sortType) {

				case 'cateId': {
					if (sortVal) where.JOIN_MEET_CATE_ID = String(sortVal);
					break;
				}
				case 'all': { //所有 
					break;
				}
				case 'use': { //可用未过期
					where.JOIN_STATUS = JoinModel.STATUS.SUCC;
					where.JOIN_COMPLETE_END_TIME = ['>=', timeUtil.time('Y-M-D h:m')];
					break;
				}
				case 'check': { //已核销
					where.JOIN_STATUS = JoinModel.STATUS.SUCC;
					where.JOIN_IS_CHECKIN = 1;
					break;
				}
				case 'timeout': { //已过期未核销
					where.JOIN_STATUS = JoinModel.STATUS.SUCC;
					where.JOIN_IS_CHECKIN = 0;
					where.JOIN_COMPLETE_END_TIME = ['<', timeUtil.time('Y-M-D h:m')];
					break;
				}
				case 'succ': { //预约成功
					where.JOIN_STATUS = JoinModel.STATUS.SUCC;
					//where.JOIN_MEET_DAY = ['>=', timeUtil.time('Y-M-D h:m')];
					//where.JOIN_MEET_TIME_START = ['>=', timeUtil.time('h:m')];
					break;
				}
				case 'cancel': { //系统已取消
					where.JOIN_STATUS = ['in', [JoinModel.STATUS.ADMIN_CANCEL]];
					break;
				}
				case 'usercancel': { //用户已取消
					where.JOIN_STATUS = ['in', [JoinModel.STATUS.CANCEL]];
					break;
				}
			}
		}
		let result = await JoinModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);

		return result;
	}

	/** 取得我的某日预约列表 */
	async getMyJoinSomeday(userId, day) {

		let fields = 'JOIN_IS_CHECKIN,JOIN_MEET_ID,JOIN_MEET_TITLE,JOIN_MEET_DAY,JOIN_MEET_TIME_START,JOIN_MEET_TIME_END,JOIN_STATUS,JOIN_ADD_TIME';

		let where = {
			JOIN_USER_ID: userId,
			JOIN_MEET_DAY: day
		};
		//where.MEET_STATUS = ['in', [MeetModel.STATUS.COMM, MeetModel.STATUS.OVER]]; // 状态  

		let orderBy = {
			'JOIN_MEET_TIME_START': 'asc',
			'JOIN_ADD_TIME': 'desc'
		}

		return await JoinModel.getAll(where, fields, orderBy);


	}
}

UI设计







理发师平台




Git代码分享

git代码下载

相关推荐
一只侯子4 小时前
Face AE Tuning
图像处理·笔记·学习·算法·计算机视觉
wasp5204 小时前
Apache Hudi 项目总体分析
数据挖掘·apache·hudi·数据湖仓
whale fall6 小时前
【剑雅14】笔记
笔记
星空的资源小屋7 小时前
跨平台下载神器ArrowDL,一网打尽所有资源
javascript·笔记·django
Xudde.8 小时前
Quick2靶机渗透
笔记·学习·安全·web安全·php
AA陈超9 小时前
Git常用命令大全及使用指南
笔记·git·学习
愚戏师9 小时前
Python3 Socket 网络编程复习笔记
网络·笔记
降临-max10 小时前
JavaSE---网络编程
java·开发语言·网络·笔记·学习
大白的编程日记.11 小时前
【计算网络学习笔记】MySql的多版本控制MVCC和Read View
网络·笔记·学习·mysql
IMPYLH12 小时前
Lua 的 require 函数
java·开发语言·笔记·后端·junit·lua