设计背景
小区物业费用缴纳,公寓租金收取,出租屋租金收取等场景,大部分都是靠人工收取,而广大业主和住户可能时间或者工作繁忙,不能到现场付款,那么设计一个这样的小程序,方便物业公司进行账务收款,账务统计,节省人力物力成本,不论在家中或者外地,都可以通过小程序直接缴费,不受时间、地点约束,操作简单,方便快捷
概要设计
数据字典
SheetModel.DB_STRUCTURE = {
_pid: 'string|true',
SHEET_ID: 'string|true',
SHEET_TITLE: 'string|true|comment=标题',
SHEET_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中',
SHEET_CATE_ID: 'string|true|default=0|comment=分类',
SHEET_CATE_NAME: 'string|false|comment=分类冗余',
SHEET_ORDER: 'int|true|default=9999',
SHEET_VOUCH: 'int|true|default=0',
SHEET_FORMS: 'array|true|default=[]',
SHEET_OBJ: 'object|true|default={}',
SHEET_QR: 'string|false',
SHEET_VIEW_CNT: 'int|true|default=0',
SHEET_CNT: 'int|true|default=0',
SHEET_PAY_CNT: 'int|true|default=0',
SHEET_WAIT_CNT: 'int|true|default=0',
SHEET_NO_CNT: 'int|true|default=0',
SHEET_FEE: 'int|true|default=0',
SHEET_PAY_FEE: 'int|true|default=0',
SHEET_WAIT_FEE: 'int|true|default=0',
SHEET_ADD_TIME: 'int|true',
SHEET_EDIT_TIME: 'int|true',
SHEET_ADD_IP: 'string|false',
SHEET_EDIT_IP: 'string|false',
};
SheetDataModel.DB_STRUCTURE = {
_pid: 'string|true',
SHEET_DATA_ID: 'string|true',
SHEET_DATA_SHEET_ID: 'string|true|comment=FK',
SHEET_DATA_SHEET_TITLE: 'string|false',
SHEET_DATA_PAY_TRADE_NO: 'string|false|comment=商家订单号 32位',
SHEET_DATA_PAY_STATUS: 'int|true|default=0|comment=支付状态 0=未支付 1=已支付 99=无需支付',
SHEET_DATA_PAY_FEE: 'int|true|default=0|comment=已支付费用 分',
SHEET_DATA_PAY_TIME: 'int|true|default=0|comment=支付时间',
SHEET_DATA_NAME: 'string|false|姓名',
SHEET_DATA_MOBILE: 'string|false|手机号',
SHEET_DATA_FEE: 'int|true|default=0|comment=需支付费用 分',
SHEET_DATA_FORMS: 'array|true|default=[]',
SHEET_DATA_OBJ: 'object|true|default={}',
SHEET_DATA_ADD_TIME: 'int|true',
SHEET_DATA_EDIT_TIME: 'int|true',
SHEET_DATA_ADD_IP: 'string|false',
SHEET_DATA_EDIT_IP: 'string|false',
};
关键实现
async getMySheetChartList(userId, time = 180) {
let where = {};
if (!config.IS_DEMO) {
let user = await UserModel.getOne({ USER_MINI_OPENID: userId });
if (!user) return null;
where = {
SHEET_DATA_PAY_STATUS: 1,
SHEET_DATA_NAME: user.USER_NAME,
SHEET_DATA_MOBILE: user.USER_MOBILE,
'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
};
}
else {
where = {
SHEET_DATA_PAY_STATUS: 1,
SHEET_DATA_NAME: 'Tom',
SHEET_DATA_MOBILE: '14600000000',
'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
};
}
time = Number(time);
time = this._timestamp - time * 86400 * 1000;
where.SHEET_DATA_PAY_TIME = ['>=', time];
let fields = 'SHEET_DATA_PAY_TIME,SHEET_DATA_FORMS,SHEET_DATA_FEE,sheet.SHEET_TITLE ';
let orderBy = {
'SHEET_DATA_PAY_TIME': 'desc',
'SHEET_DATA_ADD_TIME': 'desc'
};
let joinParams = {
from: SheetModel.CL,
localField: 'SHEET_DATA_SHEET_ID',
foreignField: '_id',
as: 'sheet',
};
let list = await SheetDataModel.getListJoin(joinParams, where, fields, orderBy, 1, 200, false);
list = list.list;
if (list.length == 0) return null;
let categories = [];
let data = [];
for (let k = 0; k < list.length; k++) {
data.push(Number(list[k].SHEET_DATA_FEE / 100));
categories.push(list[k].sheet.SHEET_TITLE.substr(0, 15));
list[k].SHEET_DATA_PAY_TIME = timeUtil.timestamp2Time(list[k].SHEET_DATA_PAY_TIME);
}
return { categories, data, list: list.reverse() };
}
// 取得账单详情
async getMySheetDataDetail(userId, sheetDataId) {
let where = {};
if (!config.IS_DEMO) {
let user = await UserModel.getOne({ USER_MINI_OPENID: userId, USER_STATUS: UserModel.STATUS.COMM });
if (!user) this.AppError('用户不存在或者状态异常');
where = {
_id: sheetDataId,
SHEET_DATA_NAME: user.USER_NAME,
SHEET_DATA_MOBILE: user.USER_MOBILE,
}
}
else {
where = {
_id: sheetDataId,
SHEET_DATA_NAME: 'Tom',
SHEET_DATA_MOBILE: '14600000000',
}
}
let sheetData = await SheetDataModel.getOne(where);
if (!sheetData) return null;
let sheet = await SheetModel.getOne({ _id: sheetData.SHEET_DATA_SHEET_ID, SHEET_STATUS: SheetModel.STATUS.COMM });
if (!sheet) this.AppError('支付项目不存在');
sheetData.sheet = sheet;
return sheetData;
}
/** 取得我的分页列表 */
async getMySheetDataList(userId, {
search, // 搜索条件
sortType, // 搜索菜单
sortVal, // 搜索菜单
orderBy, // 排序
page,
size = 30,
isTotal = true,
oldTotal
}) {
orderBy = orderBy || {
'SHEET_DATA_ADD_TIME': 'desc'
};
let fields = 'SHEET_DATA_PAY_TIME,SHEET_DATA_ADD_TIME,SHEET_DATA_FEE,SHEET_DATA_FORMS,SHEET_DATA_PAY_FEE,SHEET_DATA_PAY_STATUS,sheet.SHEET_TITLE,sheet.SHEET_OBJ';
let where = {};
if (!config.IS_DEMO) {
let user = await UserModel.getOne({ USER_MINI_OPENID: userId });
if (!user) return null;
where = {
SHEET_DATA_NAME: user.USER_NAME,
SHEET_DATA_MOBILE: user.USER_MOBILE,
'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
};
}
else {
where = {
SHEET_DATA_NAME: 'Tom',
SHEET_DATA_MOBILE: '14600000000',
'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
};
}
if (util.isDefined(search) && search) {
where['SHEET_DATA_SHEET_TITLE'] = {
$regex: '.*' + search,
$options: 'i'
};
} else if (sortType) {
// 搜索菜单
switch (sortType) {
case 'status': {
where['SHEET_DATA_PAY_STATUS'] = Number(sortVal);
break;
}
case 'sort': { //按时间倒序
orderBy = this.fmtOrderBySort(sortVal, 'SHEET_DATA_ADD_TIME');
break;
}
}
}
let joinParams = {
from: SheetModel.CL,
localField: 'SHEET_DATA_SHEET_ID',
foreignField: '_id',
as: 'sheet',
};
let result = await SheetDataModel.getListJoin(joinParams, where, fields, orderBy, page, size, isTotal, oldTotal);
return result;
}
async statSheetData(sheetId) {
let where = {
SHEET_DATA_SHEET_ID: sheetId
}
// 总数
let cnt = await SheetDataModel.count(where);
// 总费用
let fee = await SheetDataModel.sum(where, 'SHEET_DATA_FEE');
// 已支付记录
let wherePayCnt = {
SHEET_DATA_SHEET_ID: sheetId,
SHEET_DATA_PAY_STATUS: 1,
}
let payCnt = await SheetDataModel.count(wherePayCnt);
// 无须支付
let whereNoCnt = {
SHEET_DATA_SHEET_ID: sheetId,
SHEET_DATA_PAY_STATUS: 99,
}
let noCnt = await SheetDataModel.count(whereNoCnt);
// 已支付金额
let wherePayFee = {
SHEET_DATA_SHEET_ID: sheetId,
SHEET_DATA_PAY_STATUS: 1,
}
let payFee = await SheetDataModel.sum(wherePayFee, 'SHEET_DATA_PAY_FEE');
let waitCnt = cnt - payCnt - noCnt;
let waitFee = fee - payFee;
let data = {
SHEET_CNT: cnt,
SHEET_PAY_CNT: payCnt,
SHEET_WAIT_CNT: waitCnt,
SHEET_NO_CNT: noCnt,
SHEET_FEE: fee,
SHEET_PAY_FEE: payFee,
SHEET_WAIT_FEE: waitFee
}
await SheetModel.edit(sheetId, data);
return { cnt, payCnt, waitCnt, noCnt, fee, payFee, waitFee };
}
// 修正本地订单状态
async fixSheetDataPay(tradeNo, sheetId) {
if (!tradeNo) {
// 无支付号空单
let data = {
SHEET_DATA_PAY_STATUS: 0,
SHEET_DATA_PAY_TRADE_NO: '',
SHEET_DATA_PAY_FEE: 0,
SHEET_DATA_PAY_TIME: 0,
}
await SheetDataModel.edit({ SHEET_DATA_PAY_TRADE_NO: tradeNo }, data);
// 重新统计
this.statSheetData(sheetId);
return false;
}
let payService = new PayService();
if (!await payService.fixPayResult(tradeNo)) {
// 关闭未支付单
payService.closePay(tradeNo);
// 未支付
let data = {
SHEET_DATA_PAY_STATUS: 0,
SHEET_DATA_PAY_TRADE_NO: '',
SHEET_DATA_PAY_FEE: 0,
SHEET_DATA_PAY_TIME: 0,
}
await SheetDataModel.edit({ SHEET_DATA_PAY_TRADE_NO: tradeNo }, data);
// 重新统计
this.statSheetData(sheetId);
return false;
}
// 已支付
let pay = await PayModel.getOne({ PAY_TRADE_NO: tradeNo });
if (!pay) this.AppError('支付流水异常,请核查');
// 更新支付信息
let data = {
SHEET_DATA_PAY_STATUS: 1,
SHEET_DATA_PAY_TRADE_NO: tradeNo,
SHEET_DATA_PAY_FEE: pay.PAY_TOTAL_FEE,
SHEET_DATA_PAY_TIME: pay.PAY_END_TIME,
}
await SheetDataModel.edit({ SHEET_DATA_PAY_TRADE_NO: tradeNo }, data);
// 重新统计
this.statSheetData(sheetId);
return true;
}
// 预支付
async prepay(userId, sheetDataId) {
this.AppError('[物业缴费]该功能暂不开放,如有需要请加作者微信:cclinux0730');
}
// 查询支付结果
async queryPayResult(sheetDataId) {
let sheetData = await SheetDataModel.getOne(sheetDataId);
if (!sheetData) return { status: 0 };
return {
status: sheetData.SHEET_DATA_PAY_STATUS
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。