user.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941
  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. // 2023/8/25 更新登录时间
  321. const updateBean = await that.app.comoBean.instance({
  322. login_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  323. update_ttime: that.app.szjcomo.date('Y-m-d H:i:s'),
  324. }, { where: { user_id: user.user_id } });
  325. await that.service.base.update(updateBean, that.useModel, '微信用户登录时间更新失败,请重试');
  326. }
  327. return user;
  328. }
  329. /**
  330. * [writeWxUser 写入微信用户]
  331. * @param {Object} userInfo [description]
  332. * @return {[type]} [description]
  333. */
  334. async writeWxUser(userInfo = {}) {
  335. const that = this;
  336. const data = await that.ctx.validate(that.wxuserValidate, userInfo);
  337. const createBean = await that.app.comoBean.instance(data);
  338. const result = await that.service.base.create(createBean, that.useModel, '添加微信用户失败,请重试');
  339. return {
  340. user_id: result.dataValues.user_id,
  341. account_name: result.dataValues.account_name,
  342. nickname: result.dataValues.nickname,
  343. headimgurl: result.dataValues.headimgurl,
  344. openid: result.dataValues.openid,
  345. unionid: result.dataValues.unionid,
  346. };
  347. }
  348. /**
  349. * [userMoney 获取用户余额]
  350. * @return {[type]} [description]
  351. */
  352. async userMoney() {
  353. const that = this;
  354. try {
  355. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  356. const result = await that.service.shop.getUserMoney(data.user_id);
  357. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  358. } catch (err) {
  359. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  360. }
  361. }
  362. /**
  363. * [userMoney 获取用户账户余额]
  364. * @return {[type]} [description]
  365. */
  366. async userAccount() {
  367. const that = this;
  368. try {
  369. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  370. const result = await that.service.shop.getUserAccount(data.user_id);
  371. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  372. } catch (err) {
  373. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  374. }
  375. }
  376. /**
  377. * [userMoneyLog 用户资金明细]
  378. * @return {[type]} [description]
  379. */
  380. async userMoneyLog() {
  381. const that = this;
  382. try {
  383. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  384. const selectBean = await that.app.comoBean.instance(data, {
  385. offset: (data.page - 1) * data.limit,
  386. limit: data.limit,
  387. where: { user_id: data.user_id },
  388. order: [[ 'log_id', 'desc' ]],
  389. attributes: [ 'log_id', 'log_desc', 'change_time', 'money', 'type', 'inviter_id', 'inviter_name', 'inviter_img' ],
  390. });
  391. const result = await that.service.base.select(selectBean, that.app.model.UsersMoneyLogs, '查询资金明细失败,请稍候重试', true, true);
  392. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  393. } catch (err) {
  394. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  395. }
  396. }
  397. /**
  398. * [userMoneyLog 用户分佣明细]
  399. * @return {[type]} [description]
  400. */
  401. async userCommissionLog() {
  402. const that = this;
  403. try {
  404. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  405. const selectBean = await that.app.comoBean.instance(data, {
  406. // offset: (data.page - 1) * data.limit, limit: data.limit,
  407. where: { user_id: data.user_id },
  408. order: [[ 'log_id', 'desc' ]],
  409. attributes: [ 'log_desc', 'create_time', 'commission', 'type', 'inviter_id', 'inviter_name', 'inviter_img' ],
  410. });
  411. // 2022/11/28 直接查询所有数据 不分页
  412. const result = await that.service.base.select(selectBean, that.app.model.UsersCommissionLogs, '查询分佣明细失败,请稍候重试', false, true);
  413. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  414. } catch (err) {
  415. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  416. }
  417. }
  418. /**
  419. * [userMoneyLog 用户餐币明细]
  420. * @return {[type]} [description]
  421. */
  422. async coinDetail() {
  423. const that = this;
  424. try {
  425. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  426. const selectBean = await that.app.comoBean.instance(data, {
  427. where: { user_id: data.user_id },
  428. order: [[ 'log_id', 'desc' ]],
  429. });
  430. // 2022/11/28 直接查询所有数据 不分页
  431. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoinLogs, '查询分佣明细失败,请稍候重试', false, true);
  432. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  433. } catch (err) {
  434. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  435. }
  436. }
  437. // 2023/2/28 用户餐饮币账户列表
  438. async userDiningCoin() {
  439. const that = this;
  440. try {
  441. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  442. const selectBean = await that.app.comoBean.instance(data, {
  443. where: { user_id: data.user_id, expired: false },
  444. });
  445. // 2023/2/28 直接查询所有数据 不分页
  446. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoins, '查询餐饮币账户失败,请稍候重试', false, true);
  447. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  448. } catch (err) {
  449. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  450. }
  451. }
  452. /**
  453. * 用户指定商家餐币账户余额
  454. * @return {Promise<*>}
  455. */
  456. async userCouldTransferCoin() {
  457. const that = this;
  458. try {
  459. const data = await that.ctx.validate(that.userTransferValidate, await that.ctx.getParse());
  460. const res = await that.getCouldTransferCoin(data);
  461. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', { couldTransferCoins: res.account }, false));
  462. } catch (err) {
  463. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  464. }
  465. }
  466. /**
  467. * 用户可在指定商家消费的餐币账户余额
  468. * @param data
  469. */
  470. async getCouldTransferCoin(data) {
  471. const that = this;
  472. if (!data.diningCoinCode) {
  473. throw new Error('参数错误');
  474. }
  475. const userInfo = await that.app.model.Users.findOne({
  476. where: { openid: data.diningCoinCode },
  477. attributes: [ 'user_id' ],
  478. });
  479. if (!userInfo || userInfo.user_id < 0) {
  480. throw new Error('参数错误');
  481. }
  482. const businessInfo = await that.app.model.Users.findOne({
  483. where: { user_id: data.user_id },
  484. attributes: [ 'partner_id' ],
  485. });
  486. if (!businessInfo || businessInfo.partner_id < 1) {
  487. throw new Error('抱歉,您不是源森家具的合作商户,无权收款。');
  488. }
  489. const seq = that.app.Sequelize;
  490. const selectBean = await that.app.comoBean.instance({}, {
  491. where: {
  492. user_id: userInfo.user_id,
  493. // partner_id: businessInfo.partner_id,
  494. // 2023/4/11 补充通用餐币
  495. partner_id: { [seq.Op.in]: [ businessInfo.partner_id, 1 ] },
  496. expired: false,
  497. },
  498. attributes: [[ seq.fn('sum', seq.col('account')), 'account' ]],
  499. });
  500. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoins, '查询餐饮币账户余额失败,请稍候重试', false, false);
  501. const res = JSON.parse(JSON.stringify(result));
  502. res.customer_id = userInfo.user_id;
  503. res.partner_id = businessInfo.partner_id;
  504. res.business_id = data.user_id;
  505. return res;
  506. }
  507. /**
  508. * 商家可提现餐币金额
  509. * @return {Promise<*>}
  510. */
  511. async businessDiningCoinCouldCash() {
  512. const that = this;
  513. try {
  514. const data = await that.ctx.validate(that.businessCashCoinValidate, await that.ctx.getParse());
  515. const res = await that.getDiningCoinCouldCash(data);
  516. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', res, false));
  517. } catch (err) {
  518. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  519. }
  520. }
  521. /**
  522. * 商家可提现餐币金额
  523. * @param data
  524. */
  525. async getDiningCoinCouldCash(data) {
  526. const that = this;
  527. const businessInfo = await that.app.model.Users.findOne({
  528. where: { user_id: data.user_id },
  529. });
  530. if (!businessInfo || businessInfo.partner_id < 0) {
  531. throw new Error('您的账号还没有绑定的合作商铺哦');
  532. }
  533. const partnerInfo = await that.app.model.PartnerInfo.findOne({
  534. where: { id: businessInfo.partner_id },
  535. });
  536. if (!partnerInfo || partnerInfo.user_id !== data.user_id) {
  537. throw new Error('您的账号没有权限提现餐费哦');
  538. }
  539. const seq = that.app.Sequelize;
  540. const currentDateTime = that.app.szjcomo.date('Y-m-d H:i:s');
  541. const sevenDayTime = 24 * 60 * 60 * 1000; // 2023/3/1 24小时后可提现
  542. const endTimeStamp = Date.parse(currentDateTime) - sevenDayTime;
  543. const endDateTime = that.app.szjcomo.date('Y-m-d H:i:s', endTimeStamp / 1000);
  544. // 2023/3/1 查询24小时前的所得餐币总和 以及所有时间提现的总和 再求和 即为 可提现餐币金额
  545. // todo : 特殊情况 商家管理员有自家绑定的商铺 餐币消费支出
  546. const selectBean = await that.app.comoBean.instance({}, {
  547. where: {
  548. user_id: businessInfo.user_id,
  549. partner_id: businessInfo.partner_id,
  550. create_time: { [seq.Op.lte]: endDateTime },
  551. account: { [seq.Op.gte]: 0 },
  552. },
  553. attributes: [[ seq.fn('sum', seq.col('account')), 'account' ]],
  554. });
  555. const coinResult = await that.service.base.select(selectBean, that.app.model.DinnerCoinLogs, '查询商家可提现餐币金额失败,请稍候重试', false, false);
  556. const selectBean2 = await that.app.comoBean.instance({}, {
  557. where: {
  558. user_id: businessInfo.user_id,
  559. partner_id: businessInfo.partner_id,
  560. account: { [seq.Op.lte]: 0 },
  561. type: { [seq.Op.ne]: -1 },
  562. },
  563. attributes: [[ seq.fn('sum', seq.col('account')), 'account' ]],
  564. });
  565. const cashResult = await that.service.base.select(selectBean2, that.app.model.DinnerCoinLogs, '查询商家可提现餐币金额失败,请稍候重试', false, false);
  566. const coinRes = JSON.parse(JSON.stringify(coinResult));
  567. const cashRes = JSON.parse(JSON.stringify(cashResult));
  568. const couldCash = new Decimal(coinRes.account ? coinRes.account : 0)
  569. .add(new Decimal(cashRes.account ? cashRes.account : 0))
  570. .toNumber();
  571. coinRes.all_coin_could_cash = couldCash > 0 ? couldCash : 0;
  572. coinRes.all_cash = cashRes.account;
  573. coinRes.partner_id = businessInfo.partner_id;
  574. coinRes.partner_fees = partnerInfo.partner_fees;
  575. return coinRes;
  576. }
  577. /**
  578. * 商家申请核销餐币
  579. * @return {Promise<void>}
  580. */
  581. async coinTransfer() {
  582. const that = this;
  583. const seq = that.app.Sequelize;
  584. const transaction = await that.app.model.transaction();
  585. try {
  586. const data = await that.ctx.validate(that.coinTransferValidate, await that.ctx.postParse());
  587. // 2023/2/28 获取可核销餐币
  588. const intervalTime = Date.parse(that.app.szjcomo.date('Y-m-d H:i:s')) - data.time;
  589. if (intervalTime > 10 * 60 * 1000) {
  590. throw new Error('顾客付款码已超时失效,请重新扫码!');
  591. }
  592. const transferParams = await that.getCouldTransferCoin(data);
  593. if (data.coinAmount > 0 && data.coinAmount <= transferParams.account) {
  594. // 2023/2/28 发起核销收取餐币
  595. const partnerInfo = await that.app.model.PartnerInfo.findOne({
  596. where: { id: transferParams.partner_id },
  597. });
  598. // 2023/4/12 核销餐币 包含通用电子餐币
  599. const selectBean = await that.app.comoBean.instance({}, {
  600. where: {
  601. user_id: transferParams.customer_id,
  602. // partner_id: transferParams.partner_id,
  603. partner_id: { [seq.Op.in]: [ transferParams.partner_id, 1 ] },
  604. expired: false,
  605. },
  606. order: [[ 'partner_id', 'desc' ], [ 'ori_partner', 'desc' ]],
  607. });
  608. // 2023/2/28 查询所有指定商家餐币
  609. const result = await that.service.base.select(selectBean, that.app.model.DinnerCoins, '查询餐饮币账户失败,请稍候重试', false, true);
  610. const customerAccounts = [];
  611. let needAccount = 0;
  612. for (const resultElement of result) {
  613. needAccount += resultElement.account;
  614. customerAccounts.push(JSON.parse(JSON.stringify(resultElement)));
  615. if (needAccount >= data.coinAmount) {
  616. break;
  617. }
  618. }
  619. let firstCoinAmount;
  620. let secondCoinAmount;
  621. let thirdCoinAmount;
  622. let fourthCoinAmount;
  623. let incomeCoinAmount = 0;
  624. switch (customerAccounts.length) {
  625. case 1:
  626. firstCoinAmount = data.coinAmount;
  627. secondCoinAmount = 0;
  628. thirdCoinAmount = 0;
  629. fourthCoinAmount = 0;
  630. break;
  631. case 2:
  632. firstCoinAmount = customerAccounts[0].account;
  633. secondCoinAmount = data.coinAmount - firstCoinAmount;
  634. thirdCoinAmount = 0;
  635. fourthCoinAmount = 0;
  636. break;
  637. case 3:
  638. firstCoinAmount = customerAccounts[0].account;
  639. secondCoinAmount = customerAccounts[1].account;
  640. thirdCoinAmount = data.coinAmount - firstCoinAmount - secondCoinAmount;
  641. fourthCoinAmount = 0;
  642. break;
  643. case 4:
  644. firstCoinAmount = customerAccounts[0].account;
  645. secondCoinAmount = customerAccounts[1].account;
  646. thirdCoinAmount = customerAccounts[2].account;
  647. fourthCoinAmount = data.coinAmount - firstCoinAmount - secondCoinAmount - thirdCoinAmount;
  648. break;
  649. default:
  650. break;
  651. }
  652. // =========================================== 2023/3/1 第一个账户划账======================================
  653. await that.doTransferCoins(that, transferParams, firstCoinAmount, partnerInfo, transaction, customerAccounts, 0);
  654. incomeCoinAmount += firstCoinAmount * (customerAccounts[0].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  655. // =============================== 2023/3/1 需要第二个账户来继续划账 ==========================================
  656. if (customerAccounts.length > 1 && secondCoinAmount > 0) {
  657. await that.doTransferCoins(that, transferParams, secondCoinAmount, partnerInfo, transaction, customerAccounts, 1);
  658. incomeCoinAmount += secondCoinAmount * (customerAccounts[1].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  659. }
  660. // =============================== 2023/3/18 需要第三个账户来继续划账 ==========================================
  661. if (customerAccounts.length > 2 && thirdCoinAmount > 0) {
  662. await that.doTransferCoins(that, transferParams, thirdCoinAmount, partnerInfo, transaction, customerAccounts, 2);
  663. incomeCoinAmount += thirdCoinAmount * (customerAccounts[2].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  664. }
  665. // =============================== 2023/3/18 需要第四个账户来继续划账 ==========================================
  666. if (customerAccounts.length > 3 && fourthCoinAmount > 0) {
  667. await that.doTransferCoins(that, transferParams, fourthCoinAmount, partnerInfo, transaction, customerAccounts, 3);
  668. incomeCoinAmount += fourthCoinAmount * (customerAccounts[3].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  669. }
  670. await transaction.commit();
  671. incomeCoinAmount = incomeCoinAmount.toFixed(2);
  672. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', {
  673. incomeCoinAmount,
  674. transferCoinAmount: data.coinAmount,
  675. }, false));
  676. }
  677. throw new Error('输入的核销金额有误,请稍后重试');
  678. } catch (e) {
  679. if (transaction) transaction.rollback();
  680. return that.ctx.appJson(that.app.szjcomo.appResult(e.message));
  681. }
  682. }
  683. // 2023/3/1 餐币划账 具体逻辑和操作
  684. async doTransferCoins(that, transferParams, coinAmount, partnerInfo, transaction, customerAccounts, index) {
  685. // 2023/3/6 获取顾客信息
  686. const customerInfo = await that.app.model.Users.findOne({
  687. where: { user_id: transferParams.customer_id },
  688. transaction,
  689. raw: true,
  690. });
  691. // 2023/3/1 扣去顾客餐币
  692. await that.service.diningCoin.addDiningCoinChangeLog({
  693. user_id: transferParams.customer_id,
  694. order_id: -1,
  695. partner_id: customerAccounts[index].partner_id,
  696. type: 3,
  697. account: -coinAmount,
  698. log_desc: partnerInfo.name + ' 消费',
  699. }, transaction);
  700. // 2023/3/1 更新顾客账户
  701. const customerAccountBean = await that.app.comoBean.instance({
  702. account: customerAccounts[index].account - coinAmount,
  703. update_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  704. }, {
  705. where: {
  706. user_id: transferParams.customer_id,
  707. ori_partner: customerAccounts[index].ori_partner,
  708. partner_id: customerAccounts[index].partner_id,
  709. expired: false,
  710. }, transaction,
  711. });
  712. if (customerAccounts[index].account === coinAmount) {
  713. // 2023/3/1 删除0元账户
  714. await that.service.base.delete(customerAccountBean, that.app.model.DinnerCoins, '顾客餐饮币清零更新失败,请重试');
  715. } else {
  716. await that.service.base.update(customerAccountBean, that.app.model.DinnerCoins, '顾客餐饮币余额更新失败,请重试');
  717. }
  718. // 2023/3/1 划入商家账户记录
  719. const businessCoinAmount = coinAmount * (customerAccounts[index].ori_partner ? partnerInfo.rate_from_partner : partnerInfo.rate_default);
  720. await that.service.diningCoin.addDiningCoinChangeLog({
  721. user_id: transferParams.business_id,
  722. order_id: -1,
  723. partner_id: transferParams.partner_id,
  724. type: 4,
  725. account: businessCoinAmount,
  726. log_desc: '核销收取餐币',
  727. ori_partner: customerAccounts[index].ori_partner,
  728. customer_id: customerInfo.user_id,
  729. customer_name: customerInfo.nickname,
  730. customer_img: customerInfo.headimgurl,
  731. }, transaction);
  732. // 2023/2/28 查询商家餐饮币账户列表 添加 或 更新账户余额
  733. const info = await that.app.model.DinnerCoins.findOne({
  734. where: {
  735. user_id: transferParams.business_id,
  736. // ori_partner: customerAccounts[index].ori_partner,
  737. partner_id: transferParams.partner_id,
  738. expired: false,
  739. },
  740. transaction,
  741. raw: true,
  742. });
  743. if (!info) {
  744. // 2023/2/27 没有对应类型的餐饮币 则插入该类型的餐饮币账户
  745. const createData = {
  746. user_id: transferParams.business_id,
  747. account: businessCoinAmount,
  748. // ori_partner: customerAccounts[index].ori_partner,
  749. ori_partner: true,
  750. partner_id: transferParams.partner_id,
  751. create_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  752. expired: false,
  753. expired_time: that.app.szjcomo.date('Y-m-d H:i:s', parseInt(+new Date() + '') / 1000 + 90 * 24 * 60 * 60),
  754. };
  755. createData.partner_name = partnerInfo.name;
  756. createData.partner_address = partnerInfo.address;
  757. createData.partner_tel = partnerInfo.tel_num;
  758. createData.partner_opening_time = partnerInfo.opening_time;
  759. const createBean = await that.app.comoBean.instance(createData, { transaction });
  760. await that.service.base.create(createBean, that.app.model.DinnerCoins, '餐饮币发放失败,请重试');
  761. } else {
  762. const updateBean = await that.app.comoBean.instance({
  763. account: info.account + businessCoinAmount,
  764. update_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  765. expired: false,
  766. expired_time: that.app.szjcomo.date('Y-m-d H:i:s', parseInt(+new Date() + '') / 1000 + 90 * 24 * 60 * 60),
  767. }, {
  768. where: {
  769. user_id: transferParams.business_id,
  770. // ori_partner: customerAccounts[index].ori_partner,
  771. partner_id: transferParams.partner_id,
  772. }, transaction,
  773. });
  774. await that.service.base.update(updateBean, that.app.model.DinnerCoins, '餐饮币余额更新失败,请重试');
  775. }
  776. }
  777. /**
  778. * 用户可提现金额(求和)
  779. * @return {Promise<*>}
  780. */
  781. async userCommissionCouldCash() {
  782. const that = this;
  783. try {
  784. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  785. const result = await that.getCouldCashCommission(data.user_id);
  786. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  787. } catch (err) {
  788. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  789. }
  790. }
  791. /**
  792. * 获取用户可提现金额(求和)
  793. * @return {Promise<void>}
  794. */
  795. async getCouldCashCommission(user_id = -1) {
  796. if (user_id <= 0) {
  797. throw new Error('参数错误');
  798. }
  799. const that = this;
  800. const seq = that.app.Sequelize;
  801. const currentDateTime = that.app.szjcomo.date('Y-m-d H:i:s');
  802. const sevenDayTime = 7 * 24 * 60 * 60 * 1000;
  803. const endTimeStamp = Date.parse(currentDateTime) - sevenDayTime;
  804. const endDateTime = that.app.szjcomo.date('Y-m-d H:i:s', endTimeStamp / 1000);
  805. // 2022/11/28 查询7天前的所得分佣总和 以及所有时间得提现总和 再求和 即为 可提现金额
  806. const selectBean = await that.app.comoBean.instance({ user_id }, {
  807. where: { user_id, create_time: { [seq.Op.lte]: endDateTime }, commission: { [seq.Op.gte]: 0 } },
  808. attributes: [ 'user_id', [ seq.fn('sum', seq.col('commission')), 'all_commission' ]],
  809. });
  810. const commissionResult = await that.service.base.select(selectBean, that.app.model.UsersCommissionLogs, '查询佣金失败,请稍候重试', false, false);
  811. const selectBean2 = await that.app.comoBean.instance({ user_id }, {
  812. where: { user_id, commission: { [seq.Op.lte]: 0 }, type: { [seq.Op.ne]: -1 } }, // type-1提现失败类型
  813. attributes: [ 'user_id', [ seq.fn('sum', seq.col('commission')), 'all_cash' ]],
  814. });
  815. const cashResult = await that.service.base.select(selectBean2, that.app.model.UsersCommissionLogs, '查询佣金失败,请稍候重试', false, false);
  816. const commRes = JSON.parse(JSON.stringify(commissionResult));
  817. const cashRes = JSON.parse(JSON.stringify(cashResult));
  818. commRes.all_commission_could_cash = new Decimal(commRes.all_commission ? commRes.all_commission : 0)
  819. .add(new Decimal(cashRes.all_cash ? cashRes.all_cash : 0))
  820. .toNumber();
  821. commRes.all_cash = cashRes.all_cash;
  822. return commRes;
  823. }
  824. /**
  825. * 用户佣金提现
  826. * @return {Promise<void>}
  827. */
  828. async userCashOut() {
  829. const that = this;
  830. try {
  831. const data = await that.ctx.validate(that.cashOutValidate, await that.ctx.postParse());
  832. // 2023/1/17 获取可提现佣金额度
  833. const res = await that.getCouldCashCommission(data.user_id);
  834. if (data.cash_amount >= 0.1 && data.cash_amount <= res.all_commission_could_cash) {
  835. // 2023/1/31 发起提现
  836. // todo : 用户分佣提现 1.扣取税费;2.用户分佣转通用电子餐费;
  837. const result = await that.service.wxPay.transfer(data);
  838. // 2023/1/31 提现状态
  839. if (result.status != 200) {
  840. throw new Error('提现转账出现异常,请联系客服后再重试');
  841. }
  842. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  843. }
  844. throw new Error('输入提现金额有误,请稍后重试');
  845. } catch (e) {
  846. return that.ctx.appJson(that.app.szjcomo.appResult(e.message));
  847. }
  848. }
  849. /**
  850. * 用户佣金转电子餐费
  851. * @return {Promise<void>}
  852. */
  853. async commission2DiningCoin() {
  854. }
  855. /**
  856. * 商家餐币提现
  857. * @return {Promise<*>}
  858. */
  859. async userCoinCashOut() {
  860. const that = this;
  861. try {
  862. const data = await that.ctx.validate(that.cashOutValidate, await that.ctx.postParse());
  863. // 2023/1/17 获取可提现佣金额度
  864. const res = await that.getDiningCoinCouldCash(data);
  865. data.partner_id = res.partner_id;
  866. if (data.cash_amount >= 0.1 && data.cash_amount <= (res.all_coin_could_cash - res.partner_fees)) {
  867. // 2023/1/31 发起提现
  868. const result = await that.service.businessPayService.transfer(data);
  869. // 2023/1/31 提现状态
  870. if (result.status != 200) {
  871. throw new Error('提现转账出现异常,请联系客服后再重试');
  872. }
  873. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  874. }
  875. throw new Error('输入提现金额有误,请稍后重试');
  876. } catch (e) {
  877. return that.ctx.appJson(that.app.szjcomo.appResult(e.message));
  878. }
  879. }
  880. /**
  881. * [newUserBenefits 查询新用户福利]
  882. * @return {[type]} [description]
  883. */
  884. async newUserBenefits() {
  885. const that = this;
  886. try {
  887. const data = await that.ctx.validate(that.userMoneyValidate, await that.ctx.getParse());
  888. const seq = that.app.Sequelize;
  889. const selectBean = await that.app.comoBean.instance(data, {
  890. offset: (data.page - 1) * data.limit, limit: data.limit, where: {
  891. user_id: data.user_id,
  892. type: { [seq.Op.in]: [ 1, 2 ] },
  893. },
  894. order: [[ 'type', 'asc' ]],
  895. });
  896. const result = await that.service.base.select(selectBean, that.app.model.UsersMoneyLogs, '新用户福利查询失败,请稍候重试', true, true);
  897. return that.ctx.appJson(that.app.szjcomo.appResult('SUCCESS', result, false));
  898. } catch (err) {
  899. return that.ctx.appJson(that.app.szjcomo.appResult(err.message));
  900. }
  901. }
  902. };