user.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. /* eslint-disable eqeqeq */
  2. 'use strict';
  3. const shopController = require('./shop.js');
  4. const Decimal = require('decimal.js');
  5. // 用户控制器
  6. module.exports = class UserController extends shopController {
  7. // 使用模型
  8. get useModel() {
  9. const that = this;
  10. return that.app.model.Users;
  11. }
  12. /**
  13. * [registerValidate 用户注册验证器]
  14. * @return {[type]} [description]
  15. */
  16. get registerValidate() {
  17. const that = this;
  18. return {
  19. account_name: that.ctx.rules.name('账号')
  20. .required()
  21. .trim()
  22. .notEmpty(),
  23. password: that.ctx.rules.name('密码')
  24. .required()
  25. .trim()
  26. .notEmpty()
  27. .extend((field, value, row) => {
  28. row[field] = that.app.szjcomo.MD5(value);
  29. }),
  30. create_time: that.ctx.rules.default(that.app.szjcomo.date('Y-m-d H:i:s'))
  31. .required(),
  32. };
  33. }
  34. /**
  35. * [wxRegisterAdnLoginValidate 微信登录和注册验证器]
  36. * @return {[type]} [description]
  37. */
  38. get wxRegisterAdnLoginValidate() {
  39. const that = this;
  40. return {
  41. code: that.ctx.rules.name('微信授权code')
  42. .required()
  43. .notEmpty()
  44. .trim(),
  45. };
  46. }
  47. /**
  48. * [loginValidate 用户登录]
  49. * @return {[type]} [description]
  50. */
  51. get loginValidate() {
  52. const that = this;
  53. return {
  54. account_name: that.ctx.rules.name('用户账号')
  55. .required()
  56. .notEmpty()
  57. .trim(),
  58. password: that.ctx.rules.name('登录密码')
  59. .required()
  60. .notEmpty()
  61. .trim()
  62. .extend((field, value, row) => {
  63. row[field] = that.app.szjcomo.MD5(value);
  64. }),
  65. };
  66. }
  67. /**
  68. * [wxuserValidate 微信用户写入]
  69. * @return {[type]} [description]
  70. */
  71. get wxuserValidate() {
  72. const that = this;
  73. return {
  74. nickname: that.ctx.rules.name('用户昵称')
  75. .required()
  76. .notEmpty()
  77. .trim(),
  78. openid: that.ctx.rules.name('openid')
  79. .required()
  80. .notEmpty()
  81. .trim(),
  82. password: that.ctx.rules.default(that.app.szjcomo.MD5('123456'))
  83. .required(),
  84. account_name: that.ctx.rules.default(`${that.app.szjcomo.str_rand(6)}${that.app.szjcomo.date('His')}`)
  85. .required(),
  86. money: that.ctx.rules.default(0)
  87. .number(),
  88. intergral: that.ctx.rules.default(0)
  89. .number(),
  90. headimgurl: that.ctx.rules.default('')
  91. .required(),
  92. login_time: that.ctx.rules.default(that.app.szjcomo.date('Y-m-d H:i:s'))
  93. .required(),
  94. city: that.ctx.rules.default('')
  95. .required(),
  96. province: that.ctx.rules.default('')
  97. .required(),
  98. country: that.ctx.rules.default('')
  99. .required(),
  100. sex: that.ctx.rules.default(0)
  101. .number(),
  102. subscribe: that.ctx.rules.default(0)
  103. .number(),
  104. unionid: that.ctx.rules.default('')
  105. .required(),
  106. create_time: that.ctx.rules.default(that.app.szjcomo.date('Y-m-d H:i:s'))
  107. .required(),
  108. };
  109. }
  110. /**
  111. * [wxloginURLValidate 获取微信登录地址]
  112. * @return {[type]} [description]
  113. */
  114. get wxloginURLValidate() {
  115. const that = this;
  116. return {
  117. page_uri: that.ctx.rules.name('当前地址')
  118. .required()
  119. .notEmpty()
  120. .trim(),
  121. };
  122. }
  123. /**
  124. * [userMoneyValidate 获取用户余额]
  125. * @return {[type]} [description]
  126. */
  127. get userMoneyValidate() {
  128. const that = this;
  129. return {
  130. user_id: that.ctx.rules.default(that.service.shop.getWebUserId())
  131. .number(),
  132. page: that.ctx.rules.default(1)
  133. .number(),
  134. limit: that.ctx.rules.default(50)
  135. .number(),
  136. };
  137. }
  138. get userTransferValidate() {
  139. const that = this;
  140. return {
  141. user_id: that.ctx.rules.default(that.service.shop.getWebUserId())
  142. .number(),
  143. diningCoinCode: that.ctx.rules.default('')
  144. .required(),
  145. page: that.ctx.rules.default(1)
  146. .number(),
  147. limit: that.ctx.rules.default(50)
  148. .number(),
  149. };
  150. }
  151. get businessCashCoinValidate() {
  152. const that = this;
  153. return {
  154. user_id: that.ctx.rules.default(that.service.shop.getWebUserId())
  155. .number(),
  156. };
  157. }
  158. // 2023/1/13 提现验证器
  159. get cashOutValidate() {
  160. const that = this;
  161. return {
  162. user_id: that.ctx.rules.default(that.service.shop.getWebUserId())
  163. .number(),
  164. cash_amount: that.ctx.rules.name('提现金额')
  165. .required()
  166. .notEmpty()
  167. .number(),
  168. remark: that.ctx.rules.name('备注说明')
  169. .default('')
  170. .trim(),
  171. };
  172. }
  173. // 2023/2/28 餐币核销验证
  174. get coinTransferValidate() {
  175. const that = this;
  176. return {
  177. user_id: that.ctx.rules.default(that.service.shop.getWebUserId())
  178. .number(),
  179. coinAmount: that.ctx.rules.name('核销收取餐币')
  180. .required()
  181. .notEmpty()
  182. .number(),
  183. time: that.ctx.rules.name('餐币二维码刷新时间')
  184. .required()
  185. .notEmpty()
  186. .number(),
  187. diningCoinCode: that.ctx.rules.name('餐币二维码特征')
  188. .required()
  189. .notEmpty(),
  190. };
  191. }
  192. /**
  193. * [login 用户登录]
  194. * @return {[type]} [description]
  195. */
  196. async login() {
  197. const that = this;
  198. try {
  199. const data = await that.ctx.validate(that.loginValidate, await that.ctx.postParse());
  200. const user = await that.useModel.findOne({
  201. where: { account_name: data.account_name, password: data.password },
  202. // include: [
  203. // { model: that.app.model.ProxyApplyLogs, as: 'proxyApplyLogs', attributes: [ 'verify_status' ] },
  204. // ],
  205. attributes: [ 'user_id', 'account_name', 'nickname', 'headimgurl', 'openid', 'intergral', 'is_proxy', 'partner_id' ],
  206. raw: true,
  207. });
  208. if (!user) throw new Error('登录失败,请检查账号密码是否正确');
  209. user.isNew = false;
  210. const result = await that.loginHandle(user);
  211. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  212. } catch (err) {
  213. console.log(err);
  214. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  215. }
  216. }
  217. /**
  218. * [loginHandle 登录处理]
  219. * @param {Object} user [description]
  220. * @return {[type]} [description]
  221. */
  222. async loginHandle(user = {}) {
  223. const that = this;
  224. const token = that.app.jwt.sign(user, that.app.config.jwt.secret, { expiresIn: '24h' });
  225. const result = { token, user };
  226. return result;
  227. }
  228. /**
  229. * [register 用户注册]
  230. * @return {[type]} [description]
  231. */
  232. async register() {
  233. const that = this;
  234. try {
  235. const data = await that.ctx.validate(that.registerValidate, await that.ctx.postParse());
  236. const createBean = that.app.comoBean.instance(data, {});
  237. const result = await that.service.manager.create(createBean, that.useModel, '注册失败,请稍候重试');
  238. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  239. } catch (err) {
  240. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  241. }
  242. }
  243. /**
  244. * [wxRegisterAdnLogin 微信登录和注册]
  245. * @return {[type]} [description]
  246. */
  247. async wxRegisterAdnLogin() {
  248. const that = this;
  249. try {
  250. const appid = await that.service.configs.getConfigValue('wechat_account_appid');
  251. const secret = await that.service.configs.getConfigValue('wechat_account_secret');
  252. const data = await that.ctx.validate(that.wxRegisterAdnLoginValidate, await that.ctx.getParse());
  253. const result = await that.service.wechat.authLogin(appid, secret, data.code);
  254. if (result.errcode) throw new Error(`微信通讯失败,错误信息:${result.errmsg}`);
  255. const userInfo = await that.service.wechat.authUserInfo(result);
  256. const user = await that.wxRegisterAdnLoginHandle(userInfo, data.inviteCode);
  257. const loginRes = await that.loginHandle(user);
  258. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', loginRes, false));
  259. } catch (err) {
  260. await that.logs('user.js', err);
  261. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  262. }
  263. }
  264. /**
  265. * [wxloginURL 微信登录地址]
  266. * @return {[type]} [description]
  267. */
  268. async wxloginURL() {
  269. const that = this;
  270. try {
  271. const data = await that.ctx.validate(that.wxloginURLValidate, await that.ctx.postParse());
  272. const appid = await that.service.configs.getConfigValue('wechat_account_appid');
  273. const curarr = data.page_uri.split('#');
  274. const result = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${encodeURIComponent(curarr[0] + '#/shop/wxauth')}&response_type=code&scope=snsapi_userinfo&state=${that.app.szjcomo.base64_encode(data.page_uri)}#wechat_redirect`;
  275. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  276. } catch (err) {
  277. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  278. }
  279. }
  280. /**
  281. * [wxRegisterAdnLoginHandle 注册并登录]
  282. * @param {Object} userInfo [description]
  283. * @param {Number} inviteCode
  284. * @return {[type]} [description]
  285. */
  286. async wxRegisterAdnLoginHandle(userInfo = {}, inviteCode = -1) {
  287. const that = this;
  288. const inviter_id = inviteCode;
  289. // console.log('==========inviteCode========== : ' + inviter_id);
  290. const options = {
  291. where: { openid: userInfo.openid },
  292. attributes: [ 'user_id', 'account_name', 'nickname', 'headimgurl', 'openid', 'partner_id' ],
  293. raw: true,
  294. };
  295. let user = await that.useModel.findOne(options);
  296. if (!user) {
  297. // 2022/9/27 新用户注册 写入信息
  298. user = await that.writeWxUser(userInfo);
  299. user.isNew = true;
  300. // 2022/9/27: 新用户红包奖励8.8元
  301. await that.service.shop.userMoneyAdd(user.user_id, 8.8, null, '新用户注册奖励', 0, 1, -1);
  302. // 2022/9/29 受邀注册奖励 和 邀请新用户奖励
  303. if (inviter_id > 0) {
  304. try {
  305. const inviterInfo = await that.useModel.findOne({
  306. where: { user_id: inviter_id },
  307. attributes: [ 'user_id', 'nickname', 'headimgurl' ],
  308. raw: true,
  309. });
  310. await that.service.shop.userMoneyAdd(user.user_id, 1.68, null, '受邀注册奖励', 0, 2, inviter_id, inviterInfo.nickname, inviterInfo.headimgurl);
  311. await that.service.shop.userMoneyAdd(inviter_id, 1.68, null, '邀请新用户奖励', 0, 3, user.user_id, user.nickname, user.headimgurl);
  312. // 2022/11/17 邀请关系绑定
  313. await that.service.inviter.addRelUserInviter(user.user_id, inviter_id);
  314. } catch (e) {
  315. // 邀请人id不存在
  316. }
  317. }
  318. } else {
  319. user.isNew = false;
  320. }
  321. return user;
  322. }
  323. /**
  324. * [writeWxUser 写入微信用户]
  325. * @param {Object} userInfo [description]
  326. * @return {[type]} [description]
  327. */
  328. async writeWxUser(userInfo = {}) {
  329. const that = this;
  330. const data = await that.ctx.validate(that.wxuserValidate, userInfo);
  331. const createBean = await that.app.comoBean.instance(data);
  332. const result = await that.service.base.create(createBean, that.useModel, '添加微信用户失败,请重试');
  333. return {
  334. user_id: result.dataValues.user_id,
  335. account_name: result.dataValues.account_name,
  336. nickname: result.dataValues.nickname,
  337. headimgurl: result.dataValues.headimgurl,
  338. openid: result.dataValues.openid,
  339. unionid: result.dataValues.unionid,
  340. };
  341. }
  342. /**
  343. * [userMoney 获取用户余额]
  344. * @return {[type]} [description]
  345. */
  346. async userMoney() {
  347. const that = this;
  348. try {
  349. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  350. const result = await that.service.shop.getUserMoney(data.user_id);
  351. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  352. } catch (err) {
  353. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  354. }
  355. }
  356. /**
  357. * [userMoney 获取用户账户余额]
  358. * @return {[type]} [description]
  359. */
  360. async userAccount() {
  361. const that = this;
  362. try {
  363. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  364. const result = await that.service.shop.getUserAccount(data.user_id);
  365. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  366. } catch (err) {
  367. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  368. }
  369. }
  370. /**
  371. * [userMoneyLog 用户资金明细]
  372. * @return {[type]} [description]
  373. */
  374. async userMoneyLog() {
  375. const that = this;
  376. try {
  377. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  378. const selectBean = await that.app.comoBean.instance(data, {
  379. offset: (data.page - 1) * data.limit,
  380. limit: data.limit,
  381. where: { user_id: data.user_id },
  382. order: [[ 'log_id', 'desc' ]],
  383. attributes: [ 'log_id', 'log_desc', 'change_time', 'money', 'type', 'inviter_id', 'inviter_name', 'inviter_img' ],
  384. });
  385. const result = await that.service.base.select(selectBean, that.app.model.UsersMoneyLogs, '查询资金明细失败,请稍候重试', true, true);
  386. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  387. } catch (err) {
  388. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  389. }
  390. }
  391. /**
  392. * [userMoneyLog 用户分佣明细]
  393. * @return {[type]} [description]
  394. */
  395. async userCommissionLog() {
  396. const that = this;
  397. try {
  398. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  399. const selectBean = await that.app.comoBean.instance(data, {
  400. // offset: (data.page - 1) * data.limit, limit: data.limit,
  401. where: { user_id: data.user_id },
  402. order: [[ 'log_id', 'desc' ]],
  403. attributes: [ 'log_desc', 'create_time', 'commission', 'type', 'inviter_id', 'inviter_name', 'inviter_img' ],
  404. });
  405. // 2022/11/28 直接查询所有数据 不分页
  406. const result = await that.service.base.select(selectBean, that.app.model.UsersCommissionLogs, '查询分佣明细失败,请稍候重试', false, true);
  407. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  408. } catch (err) {
  409. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  410. }
  411. }
  412. /**
  413. * [userMoneyLog 用户餐币明细]
  414. * @return {[type]} [description]
  415. */
  416. async coinDetail() {
  417. const that = this;
  418. try {
  419. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  420. const selectBean = await that.app.comoBean.instance(data, {
  421. where: { user_id: data.user_id },
  422. order: [[ 'log_id', 'desc' ]],
  423. });
  424. // 2022/11/28 直接查询所有数据 不分页
  425. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoinLogs, '查询分佣明细失败,请稍候重试', false, true);
  426. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  427. } catch (err) {
  428. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  429. }
  430. }
  431. // 2023/2/28 用户餐饮币账户列表
  432. async userDiningCoin() {
  433. const that = this;
  434. try {
  435. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  436. const selectBean = await that.app.comoBean.instance(data, {
  437. where: { user_id: data.user_id, expired: false },
  438. });
  439. // 2023/2/28 直接查询所有数据 不分页
  440. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoins, '查询餐饮币账户失败,请稍候重试', false, true);
  441. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  442. } catch (err) {
  443. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  444. }
  445. }
  446. /**
  447. * 用户指定商家餐币账户余额
  448. * @return {Promise<*>}
  449. */
  450. async userCouldTransferCoin() {
  451. const that = this;
  452. try {
  453. const data = await that.ctx.validate(that.userTransferValidate, await that.ctx.getParse());
  454. const res = await that.getCouldTransferCoin(data);
  455. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', { couldTransferCoins: res.account }, false));
  456. } catch (err) {
  457. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  458. }
  459. }
  460. /**
  461. * 用户可在指定商家消费的餐币账户余额
  462. * @param data
  463. */
  464. async getCouldTransferCoin(data) {
  465. const that = this;
  466. if (!data.diningCoinCode) {
  467. throw new Error('参数错误');
  468. }
  469. const userInfo = await that.app.model.Users.findOne({
  470. where: { openid: data.diningCoinCode },
  471. attributes: [ 'user_id' ],
  472. });
  473. if (!userInfo || userInfo.user_id < 0) {
  474. throw new Error('参数错误');
  475. }
  476. const businessInfo = await that.app.model.Users.findOne({
  477. where: { user_id: data.user_id },
  478. attributes: [ 'partner_id' ],
  479. });
  480. if (!businessInfo || businessInfo.partner_id < 1) {
  481. throw new Error('抱歉,您不是茅酱酒业的合作商户,无权收款。');
  482. }
  483. const seq = that.app.Sequelize;
  484. const selectBean = await that.app.comoBean.instance({}, {
  485. where: {
  486. user_id: userInfo.user_id,
  487. // partner_id: businessInfo.partner_id,
  488. // 2023/4/11 补充通用餐币
  489. partner_id: { [seq.Op.in]: [ businessInfo.partner_id, 1 ] },
  490. expired: false,
  491. },
  492. attributes: [[ seq.fn('sum', seq.col('account')), 'account' ]],
  493. });
  494. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoins, '查询餐饮币账户余额失败,请稍候重试', false, false);
  495. const res = JSON.parse(JSON.stringify(result));
  496. res.customer_id = userInfo.user_id;
  497. res.partner_id = businessInfo.partner_id;
  498. res.business_id = data.user_id;
  499. return res;
  500. }
  501. /**
  502. * 商家可提现餐币金额
  503. * @return {Promise<*>}
  504. */
  505. async businessDiningCoinCouldCash() {
  506. const that = this;
  507. try {
  508. const data = await that.ctx.validate(that.businessCashCoinValidate, await that.ctx.getParse());
  509. const res = await that.getDiningCoinCouldCash(data);
  510. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', res, false));
  511. } catch (err) {
  512. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  513. }
  514. }
  515. /**
  516. * 商家可提现餐币金额
  517. * @param data
  518. */
  519. async getDiningCoinCouldCash(data) {
  520. const that = this;
  521. const businessInfo = await that.app.model.Users.findOne({
  522. where: { user_id: data.user_id },
  523. });
  524. if (!businessInfo || businessInfo.partner_id < 0) {
  525. throw new Error('您的账号还没有绑定的合作商铺哦');
  526. }
  527. const partnerInfo = await that.app.model.PartnerInfo.findOne({
  528. where: { id: businessInfo.partner_id },
  529. });
  530. if (!partnerInfo || partnerInfo.user_id !== data.user_id) {
  531. throw new Error('您的账号没有权限提现餐费哦');
  532. }
  533. const seq = that.app.Sequelize;
  534. const currentDateTime = that.app.szjcomo.date('Y-m-d H:i:s');
  535. const sevenDayTime = 24 * 60 * 60 * 1000; // 2023/3/1 24小时后可提现
  536. const endTimeStamp = Date.parse(currentDateTime) - sevenDayTime;
  537. const endDateTime = that.app.szjcomo.date('Y-m-d H:i:s', endTimeStamp / 1000);
  538. // 2023/3/1 查询24小时前的所得餐币总和 以及所有时间提现的总和 再求和 即为 可提现餐币金额
  539. // todo : 特殊情况 商家管理员有自家绑定的商铺 餐币消费支出
  540. const selectBean = await that.app.comoBean.instance({}, {
  541. where: {
  542. user_id: businessInfo.user_id,
  543. partner_id: businessInfo.partner_id,
  544. create_time: { [seq.Op.lte]: endDateTime },
  545. account: { [seq.Op.gte]: 0 },
  546. },
  547. attributes: [[ seq.fn('sum', seq.col('account')), 'account' ]],
  548. });
  549. const coinResult = await that.service.base.select(selectBean, that.app.model.DinnerCoinLogs, '查询商家可提现餐币金额失败,请稍候重试', false, false);
  550. const selectBean2 = await that.app.comoBean.instance({}, {
  551. where: {
  552. user_id: businessInfo.user_id,
  553. partner_id: businessInfo.partner_id,
  554. account: { [seq.Op.lte]: 0 },
  555. type: { [seq.Op.ne]: -1 },
  556. },
  557. attributes: [[ seq.fn('sum', seq.col('account')), 'account' ]],
  558. });
  559. const cashResult = await that.service.base.select(selectBean2, that.app.model.DinnerCoinLogs, '查询商家可提现餐币金额失败,请稍候重试', false, false);
  560. const coinRes = JSON.parse(JSON.stringify(coinResult));
  561. const cashRes = JSON.parse(JSON.stringify(cashResult));
  562. const couldCash = new Decimal(coinRes.account ? coinRes.account : 0)
  563. .add(new Decimal(cashRes.account ? cashRes.account : 0))
  564. .toNumber();
  565. coinRes.all_coin_could_cash = couldCash > 0 ? couldCash : 0;
  566. coinRes.all_cash = cashRes.account;
  567. coinRes.partner_id = businessInfo.partner_id;
  568. coinRes.partner_fees = partnerInfo.partner_fees;
  569. return coinRes;
  570. }
  571. /**
  572. * 商家申请核销餐币
  573. * @return {Promise<void>}
  574. */
  575. async coinTransfer() {
  576. const that = this;
  577. const seq = that.app.Sequelize;
  578. const transaction = await that.app.model.transaction();
  579. try {
  580. const data = await that.ctx.validate(that.coinTransferValidate, await that.ctx.postParse());
  581. // 2023/2/28 获取可核销餐币
  582. const intervalTime = Date.parse(that.app.szjcomo.date('Y-m-d H:i:s')) - data.time;
  583. if (intervalTime > 10 * 60 * 1000) {
  584. throw new Error('顾客付款码已超时失效,请重新扫码!');
  585. }
  586. const transferParams = await that.getCouldTransferCoin(data);
  587. if (data.coinAmount > 0 && data.coinAmount <= transferParams.account) {
  588. // 2023/2/28 发起核销收取餐币
  589. const partnerInfo = await that.app.model.PartnerInfo.findOne({
  590. where: { id: transferParams.partner_id },
  591. });
  592. // 2023/4/12 核销餐币 包含通用电子餐币
  593. const selectBean = await that.app.comoBean.instance({}, {
  594. where: {
  595. user_id: transferParams.customer_id,
  596. // partner_id: transferParams.partner_id,
  597. partner_id: { [seq.Op.in]: [ transferParams.partner_id, 1 ] },
  598. expired: false,
  599. },
  600. order: [[ 'partner_id', 'desc' ], [ 'ori_partner', 'desc' ]],
  601. });
  602. // 2023/2/28 查询所有指定商家餐币
  603. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoins, '查询餐饮币账户失败,请稍候重试', false, true);
  604. const customerAccounts = [];
  605. let needAccount = 0;
  606. for (const resultElement of result) {
  607. needAccount += resultElement.account;
  608. customerAccounts.push(JSON.parse(JSON.stringify(resultElement)));
  609. if (needAccount >= data.coinAmount) {
  610. break;
  611. }
  612. }
  613. let firstCoinAmount;
  614. let secondCoinAmount;
  615. let thirdCoinAmount;
  616. let fourthCoinAmount;
  617. let incomeCoinAmount = 0;
  618. switch (customerAccounts.length) {
  619. case 1:
  620. firstCoinAmount = data.coinAmount;
  621. secondCoinAmount = 0;
  622. thirdCoinAmount = 0;
  623. fourthCoinAmount = 0;
  624. break;
  625. case 2:
  626. firstCoinAmount = customerAccounts[0].account;
  627. secondCoinAmount = data.coinAmount - firstCoinAmount;
  628. thirdCoinAmount = 0;
  629. fourthCoinAmount = 0;
  630. break;
  631. case 3:
  632. firstCoinAmount = customerAccounts[0].account;
  633. secondCoinAmount = customerAccounts[1].account;
  634. thirdCoinAmount = data.coinAmount - firstCoinAmount - secondCoinAmount;
  635. fourthCoinAmount = 0;
  636. break;
  637. case 4:
  638. firstCoinAmount = customerAccounts[0].account;
  639. secondCoinAmount = customerAccounts[1].account;
  640. thirdCoinAmount = customerAccounts[2].account;
  641. fourthCoinAmount = data.coinAmount - firstCoinAmount - secondCoinAmount - thirdCoinAmount;
  642. break;
  643. default:
  644. break;
  645. }
  646. // =========================================== 2023/3/1 第一个账户划账======================================
  647. await that.doTransferCoins(that, transferParams, firstCoinAmount, partnerInfo, transaction, customerAccounts, 0);
  648. incomeCoinAmount += firstCoinAmount * (customerAccounts[0].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  649. // =============================== 2023/3/1 需要第二个账户来继续划账 ==========================================
  650. if (customerAccounts.length > 1 && secondCoinAmount > 0) {
  651. await that.doTransferCoins(that, transferParams, secondCoinAmount, partnerInfo, transaction, customerAccounts, 1);
  652. incomeCoinAmount += secondCoinAmount * (customerAccounts[1].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  653. }
  654. // =============================== 2023/3/18 需要第三个账户来继续划账 ==========================================
  655. if (customerAccounts.length > 2 && thirdCoinAmount > 0) {
  656. await that.doTransferCoins(that, transferParams, thirdCoinAmount, partnerInfo, transaction, customerAccounts, 2);
  657. incomeCoinAmount += thirdCoinAmount * (customerAccounts[2].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  658. }
  659. // =============================== 2023/3/18 需要第四个账户来继续划账 ==========================================
  660. if (customerAccounts.length > 3 && fourthCoinAmount > 0) {
  661. await that.doTransferCoins(that, transferParams, fourthCoinAmount, partnerInfo, transaction, customerAccounts, 3);
  662. incomeCoinAmount += fourthCoinAmount * (customerAccounts[3].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  663. }
  664. await transaction.commit();
  665. incomeCoinAmount = incomeCoinAmount.toFixed(2);
  666. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', {
  667. incomeCoinAmount,
  668. transferCoinAmount: data.coinAmount,
  669. }, false));
  670. }
  671. throw new Error('输入的核销金额有误,请稍后重试');
  672. } catch (e) {
  673. if (transaction) transaction.rollback();
  674. return that.ctx.appJson(that.app.szjcomo.appResult(e.message));
  675. }
  676. }
  677. // 2023/3/1 餐币划账 具体逻辑和操作
  678. async doTransferCoins(that, transferParams, coinAmount, partnerInfo, transaction, customerAccounts, index) {
  679. // 2023/3/6 获取顾客信息
  680. const customerInfo = await that.app.model.Users.findOne({
  681. where: { user_id: transferParams.customer_id },
  682. transaction,
  683. raw: true,
  684. });
  685. // 2023/3/1 扣去顾客餐币
  686. await that.service.diningCoin.addDiningCoinChangeLog({
  687. user_id: transferParams.customer_id,
  688. order_id: -1,
  689. partner_id: customerAccounts[index].partner_id,
  690. type: 3,
  691. account: -coinAmount,
  692. log_desc: partnerInfo.name + ' 消费',
  693. }, transaction);
  694. // 2023/3/1 更新顾客账户
  695. const customerAccountBean = await that.app.comoBean.instance({
  696. account: customerAccounts[index].account - coinAmount,
  697. update_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  698. }, {
  699. where: {
  700. user_id: transferParams.customer_id,
  701. ori_partner: customerAccounts[index].ori_partner,
  702. partner_id: customerAccounts[index].partner_id,
  703. expired: false,
  704. }, transaction,
  705. });
  706. if (customerAccounts[index].account === coinAmount) {
  707. // 2023/3/1 删除0元账户
  708. await that.service.base.delete(customerAccountBean, that.app.model.DinnerCoins, '顾客餐饮币清零更新失败,请重试');
  709. } else {
  710. await that.service.base.update(customerAccountBean, that.app.model.DinnerCoins, '顾客餐饮币余额更新失败,请重试');
  711. }
  712. // 2023/3/1 划入商家账户记录
  713. const businessCoinAmount = coinAmount * (customerAccounts[index].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  714. await that.service.diningCoin.addDiningCoinChangeLog({
  715. user_id: transferParams.business_id,
  716. order_id: -1,
  717. partner_id: transferParams.partner_id,
  718. type: 4,
  719. account: businessCoinAmount,
  720. log_desc: '核销收取餐币',
  721. ori_partner: customerAccounts[index].ori_partner,
  722. customer_id: customerInfo.user_id,
  723. customer_name: customerInfo.nickname,
  724. customer_img: customerInfo.headimgurl,
  725. }, transaction);
  726. // 2023/2/28 查询商家餐饮币账户列表 添加 或 更新账户余额
  727. const info = await that.app.model.DinnerCoins.findOne({
  728. where: {
  729. user_id: transferParams.business_id,
  730. // ori_partner: customerAccounts[index].ori_partner,
  731. partner_id: transferParams.partner_id,
  732. expired: false,
  733. },
  734. transaction,
  735. raw: true,
  736. });
  737. if (!info) {
  738. // 2023/2/27 没有对应类型的餐饮币 则插入该类型的餐饮币账户
  739. const createData = {
  740. user_id: transferParams.business_id,
  741. account: businessCoinAmount,
  742. // ori_partner: customerAccounts[index].ori_partner,
  743. ori_partner: true,
  744. partner_id: transferParams.partner_id,
  745. create_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  746. expired: false,
  747. expired_time: that.app.szjcomo.date('Y-m-d H:i:s', parseInt(+new Date() + '') / 1000 + 90 * 24 * 60 * 60),
  748. };
  749. createData.partner_name = partnerInfo.name;
  750. createData.partner_address = partnerInfo.address;
  751. createData.partner_tel = partnerInfo.tel_num;
  752. createData.partner_opening_time = partnerInfo.opening_time;
  753. const createBean = await that.app.comoBean.instance(createData, { transaction });
  754. await that.service.base.create(createBean, that.app.model.DinnerCoins, '餐饮币发放失败,请重试');
  755. } else {
  756. const updateBean = await that.app.comoBean.instance({
  757. account: info.account + businessCoinAmount,
  758. update_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  759. expired: false,
  760. expired_time: that.app.szjcomo.date('Y-m-d H:i:s', parseInt(+new Date() + '') / 1000 + 90 * 24 * 60 * 60),
  761. }, {
  762. where: {
  763. user_id: transferParams.business_id,
  764. // ori_partner: customerAccounts[index].ori_partner,
  765. partner_id: transferParams.partner_id,
  766. }, transaction,
  767. });
  768. await that.service.base.update(updateBean, that.app.model.DinnerCoins, '餐饮币余额更新失败,请重试');
  769. }
  770. }
  771. /**
  772. * 用户可提现金额(求和)
  773. * @return {Promise<*>}
  774. */
  775. async userCommissionCouldCash() {
  776. const that = this;
  777. try {
  778. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  779. const result = await that.getCouldCashCommission(data.user_id);
  780. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  781. } catch (err) {
  782. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  783. }
  784. }
  785. /**
  786. * 获取用户可提现金额(求和)
  787. * @return {Promise<void>}
  788. */
  789. async getCouldCashCommission(user_id = -1) {
  790. if (user_id <= 0) {
  791. throw new Error('参数错误');
  792. }
  793. const that = this;
  794. const seq = that.app.Sequelize;
  795. const currentDateTime = that.app.szjcomo.date('Y-m-d H:i:s');
  796. const sevenDayTime = 7 * 24 * 60 * 60 * 1000;
  797. const endTimeStamp = Date.parse(currentDateTime) - sevenDayTime;
  798. const endDateTime = that.app.szjcomo.date('Y-m-d H:i:s', endTimeStamp / 1000);
  799. // 2022/11/28 查询7天前的所得分佣总和 以及所有时间得提现总和 再求和 即为 可提现金额
  800. const selectBean = await that.app.comoBean.instance({ user_id }, {
  801. where: { user_id, create_time: { [seq.Op.lte]: endDateTime }, commission: { [seq.Op.gte]: 0 } },
  802. attributes: [ 'user_id', [ seq.fn('sum', seq.col('commission')), 'all_commission' ]],
  803. });
  804. const commissionResult = await that.service.base.select(selectBean, that.app.model.UsersCommissionLogs, '查询佣金失败,请稍候重试', false, false);
  805. const selectBean2 = await that.app.comoBean.instance({ user_id }, {
  806. where: { user_id, commission: { [seq.Op.lte]: 0 }, type: { [seq.Op.ne]: -1 } }, // type-1提现失败类型
  807. attributes: [ 'user_id', [ seq.fn('sum', seq.col('commission')), 'all_cash' ]],
  808. });
  809. const cashResult = await that.service.base.select(selectBean2, that.app.model.UsersCommissionLogs, '查询佣金失败,请稍候重试', false, false);
  810. const commRes = JSON.parse(JSON.stringify(commissionResult));
  811. const cashRes = JSON.parse(JSON.stringify(cashResult));
  812. commRes.all_commission_could_cash = new Decimal(commRes.all_commission ? commRes.all_commission : 0)
  813. .add(new Decimal(cashRes.all_cash ? cashRes.all_cash : 0))
  814. .toNumber();
  815. commRes.all_cash = cashRes.all_cash;
  816. return commRes;
  817. }
  818. /**
  819. * 用户佣金提现
  820. * @return {Promise<void>}
  821. */
  822. async userCashOut() {
  823. const that = this;
  824. try {
  825. const data = await that.ctx.validate(that.cashOutValidate, await that.ctx.postParse());
  826. // 2023/1/17 获取可提现佣金额度
  827. const res = await that.getCouldCashCommission(data.user_id);
  828. if (data.cash_amount >= 0.1 && data.cash_amount <= res.all_commission_could_cash) {
  829. // 2023/1/31 发起提现
  830. // todo : 用户分佣提现 1.扣取税费;2.用户分佣转通用电子餐费;
  831. const result = await that.service.wxPay.transfer(data);
  832. // 2023/1/31 提现状态
  833. if (result.status != 200) {
  834. throw new Error('提现转账出现异常,请联系客服后再重试');
  835. }
  836. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  837. }
  838. throw new Error('输入提现金额有误,请稍后重试');
  839. } catch (e) {
  840. return that.ctx.appJson(that.app.szjcomo.appResult(e.message));
  841. }
  842. }
  843. /**
  844. * 用户佣金转电子餐费
  845. * @return {Promise<void>}
  846. */
  847. async commission2DiningCoin() {
  848. }
  849. /**
  850. * 商家餐币提现
  851. * @return {Promise<*>}
  852. */
  853. async userCoinCashOut() {
  854. const that = this;
  855. try {
  856. const data = await that.ctx.validate(that.cashOutValidate, await that.ctx.postParse());
  857. // 2023/1/17 获取可提现佣金额度
  858. const res = await that.getDiningCoinCouldCash(data);
  859. data.partner_id = res.partner_id;
  860. if (data.cash_amount >= 0.1 && data.cash_amount <= (res.all_coin_could_cash - res.partner_fees)) {
  861. // 2023/1/31 发起提现
  862. const result = await that.service.businessPayService.transfer(data);
  863. // 2023/1/31 提现状态
  864. if (result.status != 200) {
  865. throw new Error('提现转账出现异常,请联系客服后再重试');
  866. }
  867. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  868. }
  869. throw new Error('输入提现金额有误,请稍后重试');
  870. } catch (e) {
  871. return that.ctx.appJson(that.app.szjcomo.appResult(e.message));
  872. }
  873. }
  874. /**
  875. * [newUserBenefits 查询新用户福利]
  876. * @return {[type]} [description]
  877. */
  878. async newUserBenefits() {
  879. const that = this;
  880. try {
  881. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  882. const seq = that.app.Sequelize;
  883. const selectBean = await that.app.comoBean.instance(data, {
  884. offset: (data.page - 1) * data.limit, limit: data.limit, where: {
  885. user_id: data.user_id,
  886. type: { [seq.Op.in]: [ 1, 2 ] },
  887. },
  888. order: [[ 'type', 'asc' ]],
  889. });
  890. const result = await that.service.base.select(selectBean, that.app.model.UsersMoneyLogs, '新用户福利查询失败,请稍候重试', true, true);
  891. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  892. } catch (err) {
  893. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  894. }
  895. }
  896. };