123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- 'use strict';
- const { CrudService } = require('naf-framework-mongoose-free/lib/service');
- const { BusinessError, ErrorCode } = require('naf-core').Error;
- const _ = require('lodash');
- const assert = require('assert');
- const moment = require('moment');
- const Transaction = require('mongoose-transactions');
- //
- class LessonStudentService extends CrudService {
- constructor(ctx) {
- super(ctx, 'lessonstudent');
- this.model = this.ctx.model.Business.LessonStudent;
- this.rscModel = this.ctx.model.Relation.RelationStudentCoach;
- this.lessonCoachModel = this.ctx.model.Business.LessonCoach;
- this.lessonModel = this.ctx.model.Business.Lesson;
- this.rcsModel = this.ctx.model.Relation.RelationCoachSchool;
- this.rssModel = this.ctx.model.Relation.RelationStudentSchool;
- this.billModel = this.ctx.model.Business.Bill;
- this.payOrderModel = this.ctx.model.Business.PayOrder;
- this.studentModel = this.ctx.model.User.Student;
- this.payService = this.ctx.service.wxpay;
- this.tran = new Transaction();
- }
- async checkCanUse({ school_id, student_id, lesson_id }) {
- const num = await this.model.count({ school_id, student_id, lesson_id, $or: [{ is_try: '0', is_pay: { $and: [{ $ne: '-3' }, { $ne: '-1' }] } }, { is_try: '1' }] });
- if (num > 0) throw new BusinessError(ErrorCode.DATA_EXISTED, '已存在有效的报名信息');
- return true;
- }
- async beforeCreate(body) {
- const { lesson_id, school_id, student_id } = body;
- // 检查是否已经有数据: 支付失败和已退款不算
- const data = await this.model.findOne({ lesson_id, school_id, student_id, $or: [{ is_try: '0', is_pay: { $nin: [ '-1', '-3' ] } }, { is_try: '1' }] });
- // 数据已存在
- if (data) throw new BusinessError(ErrorCode.DATA_EXISTED, '您已报名');
- // 数据不存在
- const { is_try } = body;
- // 检查是否使用试听名额
- if (is_try === '1') {
- const num = await this.model.count({ student_id, is_try: '1' });
- if (num > 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, '您已使用过免费试听的机会');
- }
- return body;
- }
- async toRePay({ id }) {
- const data = await this.model.findById(id);
- if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到报名信息');
- if (data.is_pay === '1') throw new BusinessError(ErrorCode.SERVICE_FAULT, '该课程已支付成功');
- const payOrder = await this.payOrderModel.findById(data.pay_id);
- if (!payOrder) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到课程缴费信息');
- const { openid, money, school_id, payer_id: student_id, order_no } = payOrder;
- try {
- // 关闭订单
- await this.payService.close(order_no);
- // 删了重做
- this.tran.remove('LessonStudent', id);
- this.tran.remove('PayOrder', data.pay_id);
- await this.tran.run();
- this.tran.clean();
- const result = await this.create({ openid, money, school_id, student_id, lesson_id: data.lesson_id });
- return result;
- } catch (error) {
- await this.tran.rollback();
- } finally {
- this.tran.clean();
- }
- }
- async create(data) {
- // 检查是否能报名上课
- data = await this.beforeCreate(data);
- // 能报名上课,就直接创建数据
- try {
- // 创建数据
- const id = this.tran.insert('LessonStudent', data);
- const { school_id, student_id: payer_id, money, openid } = data;
- if (!openid) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户的微信信息');
- const payData = {
- openid,
- school_id,
- payer_id,
- payer_role: 'Student',
- pay_for: 'LessonStudent',
- from_id: id,
- time: moment().format('YYYY-MM-DD HH:mm:ss'),
- order_no: this.ctx.service.business.payOrder.getOrderNo(),
- desc: '课程费',
- money,
- };
- // 计算付款单支付金额
- // 有余额且余额够,就直接扣余额;余额不足,就直接去支付
- const relation = await this.rssModel.findOne({ student_id: payer_id, school_id });
- if (!relation) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到在校记录');
- const { money: surplus } = relation;
- let billId;
- if (this.ctx.minus(surplus, money) >= 0) {
- // 余额够,生成账单
- const billData = _.pick(payData, [ 'school_id', 'payer_role', 'payer_id', 'pay_for', 'from_id' ]);
- billData.type = '-2';
- billData.is_pay = '1';
- billData.time = moment().format('YYYY-MM-DD HH:mm:ss');
- billData.money = money;
- billId = this.tran.insert('Bill', billData);
- // 减少余额
- const rs = this.ctx.minus(surplus, data.money);
- this.tran.update('RelationStudentSchool', relation._id, { money: rs });
- // 生成付款单数据,但是不需要支付
- payData.status = '1';
- payData.config = {
- useSurplus: true,
- bill: billId,
- money,
- };
- // 修改课程信息的支付状态
- this.tran.update('LessonStudent', id, { is_pay: '1' });
- }
- const pay_id = this.tran.insert('PayOrder', payData);
- const result = { pay_id };
- // 如果生成账单id,则说明是全余额付的.修改账单的数据,将pay_id改了
- if (billId) this.tran.update('Bill', billId, { pay_id });
- else {
- // 需要微信支付签名(填充回调函数,回调函数处理)
- const wxSign = await this.payService.create({ ...payData, notice_url: this.app.config.payReturn });
- result.wxSign = wxSign;
- }
- await this.tran.run();
- this.tran.clean();
- // 然后将pay_id赋给课程
- this.tran.update('LessonStudent', id, { pay_id });
- await this.tran.run();
- return result;
- } catch (error) {
- await this.tran.rollback();
- throw new Error(error);
- } finally {
- this.tran.clean();
- }
- }
- // 计算价格
- async toComputed({ lesson_id, student_id }) {
- // 通过课程id找到该课的所有教练
- const lesson = await this.lessonModel.findById(lesson_id);
- if (!lesson) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到数据');
- const data = JSON.parse(JSON.stringify(lesson));
- const lessonMoney = _.get(data, 'money');
- const lessonType = _.get(data, 'type');
- if (!lessonMoney) throw new BusinessError(ErrorCode.DATA_INVALID, '课程设置错误,缺少教练费');
- // lesson中的money: 是学生应缴费用;
- // lessonStudent中的money:是学生应缴费用(公开课不计算,私教课需要查下有没有教师的优惠);
- // lessonCoach中的money: 是教师公开课每人应收的费用
- // 学生应缴计算方式: 公开课:lesson中的money; 私教课 lesson中的money - 和该教练的优惠 (可以没有)
- // 教练应收计算方式: 公开课: lessonCoach中设置的单价*人数; 私教课: (lesson的money - 对该学生的优惠) 之和
- if (lessonType === '0') {
- // 公开课
- data.real_money = lessonMoney;
- } else {
- // 找教练和学生的设置
- // 虽然用find,但实际上只有一个教练,但凡有2个,就应该不是私教课了
- const lessonCoachs = await this.lessonCoachModel.find({ lesson_id });
- if (lessonCoachs.length <= 0) return data;
- const coach_ids = lessonCoachs.map(i => i.coach_id);
- const rsc = await this.rscModel.findOne({ coach_id: coach_ids, student_id });
- if (rsc) {
- const discountType = _.get(rsc, 'config.discount_type'); // fixed;subtract;discount
- const number = _.get(rsc, 'config.number');
- if (discountType && number) {
- // 优惠目前设置3种: 固定金额; ${num}(num>1):减num元; x折; 打x折(money * (x/100))
- if (discountType === 'fixed') {
- // 固定金额
- data.real_money = number >= 0 ? number : 0;
- } else if (discountType === 'subtract') {
- data.real_money = lessonMoney - number >= 0 ? lessonMoney - number : 0;
- } else if (discountType === 'discount') {
- data.real_money = _.round(_.multiply(lessonMoney, _.divide(number, 10)), 2);
- }
- data.config = _.get(rsc, 'config');
- }
- }
- }
- return data;
- }
- // 退课,将钱退至余额
- async toRefund({ lesson_id, student_id }) {
- const data = await this.model.findOne({ lesson_id, student_id });
- if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到数据');
- const lesson = await this.lessonModel.findById(lesson_id);
- if (!lesson) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到课程数据');
- if (!moment().isBefore(lesson.refund_hour)) throw new BusinessError(ErrorCode.DATA_INVALID, '已经超过退课时间,无法退课');
- if (data.is_pay === '-3') throw new BusinessError(ErrorCode.DATA_INVALID, '已经完成退课,无法再次退课');
- if (data.is_try === '1') {
- // 试课 修改退款状态, 而不需要退钱,同时也浪费了试课机会
- data.is_pay = '-3';
- await data.save();
- return;
- }
- try {
- // 正常交钱的课,将 pay_id 的status 修改为 -3, 同时触发修改这条数据,但是没有退款;
- console.log(data);
- const payOrder = await this.payOrderModel.findById(data.pay_id);
- if (!payOrder) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到付款单信息');
- // 再生成账单记录
- const obj = _.pick(payOrder, [ 'school_id', 'payer_role', 'payer_id', 'pay_for', 'from_id' ]);
- obj.money = data.money;
- obj.type = '2';
- obj.is_pay = '1';
- obj.time = moment().format('YYYY-MM-DD HH:mm:ss');
- // 再将钱返回给余额
- let relation,
- model;
- const { payer_role, payer_id, school_id } = payOrder;
- if (payer_role === 'Student') {
- relation = await this.rssModel.findOne({ student_id: payer_id, school_id });
- model = 'RelationStudentSchool';
- } else if (payer_role === 'Coach') {
- relation = await this.rcsModel.findOne({ coach_id: payer_id, school_id });
- model = 'RelationCoachSchool';
- }
- const newMoney = this.ctx.plus(relation.money, obj.money);
- // 退至余额
- this.tran.update(model, relation._id, { money: newMoney });
- // 生成退款账单
- this.tran.insert('Bill', obj);
- // 付款单状态修改
- this.tran.update('PayOrder', data.pay_id, { status: '-3' });
- // 课程-学生状态修改
- this.tran.update('LessonStudent', data._id, { is_pay: '-3' });
- await this.tran.run();
- return;
- } catch (error) {
- await this.tran.rollback();
- console.log(error);
- throw Error(error);
- } finally {
- this.tran.clean();
- }
- }
- }
- module.exports = LessonStudentService;
|