const CartItem = require('../models/CartItem') const Order = require('../models/Order') const User = require('../models/User') const Food = require('../models/Food') const Payment = require('../models/Payment') const DiscountCode = require('../models/DiscountCode') const StoreInfo = require('../models/StoreInfo') const {body, validationResult} = require('express-validator') const {res404, res500, checkValidations} = require('../plugins/controllersHelperFunctions') const {_sr} = require('../plugins/serverResponses') const axios = require('axios') const zarinpal = require('./../zarinpal') const {sendSingle, sendMulti} = require('./../plugins/pushNotif').notification const {sendNotifyOrder} = require('./../socket/adminNotify') const moment = require('moment-jalaali'); const {sms} = require('./../SMSModule') const CronJob = require('cron').CronJob /////////// variables const apiKey = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImRiYjIwMmRhYzAxYzFjNzgwYjgxNWQyZDgyMWNmNmY1Njk0MThhMGE3N2Q1M2Y0OTZkYzQzYTgxMmNmN2U3ZWJjNzI5MjY5YjkxMjA4MTYyIn0.eyJhdWQiOiIxNDk3OSIsImp0aSI6ImRiYjIwMmRhYzAxYzFjNzgwYjgxNWQyZDgyMWNmNmY1Njk0MThhMGE3N2Q1M2Y0OTZkYzQzYTgxMmNmN2U3ZWJjNzI5MjY5YjkxMjA4MTYyIiwiaWF0IjoxNjI3MzI2Mzc0LCJuYmYiOjE2MjczMjYzNzQsImV4cCI6MTYyOTkxODM3NCwic3ViIjoiIiwic2NvcGVzIjpbImJhc2ljIl19.hyKNSy4fmPps6yIdm33Wof-mES9ujh5lmI3VFWyecbkNCm9ATPwsZ9s53A3a1wPJmHfLBXpCcob5NGL-GjZlVfqfRBiReRMr_iniCjUOnqENfFwVEJutxI-xrzVe7PZzOgx1Vr6WwSUHvQ36eJSS1lBlHlqCgwRbkG0hs4YdZsKhzhfYdlAluNw9djjUAMbRWZVL0EluP9CYko4w8tB0KHUZMZYGQcevCfb_3OU9ygHo7B--Z5aTPfm7IK4eP0gHsZ_t857B3tu3ZJB1mkgxc2R9gZUflKqj1ZqkYiR0uMGP-571enXh6pYQLXL2k-gOxLIedCaE6XytxVXhENnotA'; const limit = 20 const zarinUrl = 'https://www.zarinpal.com/pg/StartPay' const timeToStart = 1800000 // const timeToStart = 1800000 ////////// functions const deg2rad = (deg) => { return deg * (Math.PI / 180) } const _computeShippingCost = async (addresses, location) => { try { if (addresses.length && location) { const address = addresses[0] const R = 6371; // Radius of the earth in km const dLat = deg2rad(address.location.coordinates[1] - location.coordinates[1]); // deg2rad below const dLon = deg2rad(address.location.coordinates[0] - location.coordinates[0]); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(deg2rad(address.location.coordinates[1])) * Math.cos(deg2rad(location.coordinates[1])) * Math.sin(dLon / 2) * Math.sin(dLon / 2) ; const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // Distance in km return R * c; } else { return 0 } } catch (error) { return 0 } } const _computeShippingCostByRoad = async (addresses, storeLocation) => { try { if (addresses.length && storeLocation) { const address = addresses.find(item2 => item2.used === true) const lng = address.location.coordinates[0] const lat = address.location.coordinates[1] const storeLng = storeLocation.coordinates[0] const storeLat = storeLocation.coordinates[1] const getDistance = await axios.get(`https://map.ir/routes/route/v1/driving/${lng},${lat};${storeLng},${storeLat}`, { headers: { 'x-api-key': apiKey } }); if (getDistance.data.code !== 'Ok') return new Error(_sr['fa'].response.unknownError); //@todo if distance under 1000 meter equal to 0 return getDistance.data.routes[0].distance <= 1000 ? 0 : Math.round(getDistance.data.routes[0].distance / 1000); } else { return 0 } } catch (error) { return 0 } } const getMinutes = (str) => { var time = str.split(':'); return time[0] * 60 + time[1] * 1; } const getMinutesNow = (timezone) => { var timeNow = new Date(); return timeNow.getUTCHours() * 60 + timeNow.getUTCMinutes(); } const _computeHavePointPrice = async (items) => { try { let total = 0 for (const item of items) { if (item.havePoint) { let priceItem = item.price - (item.price * item.static_discount || 0 / 100) let totalPrice = priceItem * item.quantity total += totalPrice } } return total } catch (error) { return 0 } } //////////////////////////////////////////////////////////////////public ////////////////////////////////////// cart module.exports.addToCart = [ //// check start time and end time // async (req, res, next) => { // try { // const getTime = await StoreInfo.findOne() // // if (getTime) { // if (getTime?.open24){ // return next() // } else if (getTime?.utcStartTime && getTime?.utcEndTime) { // let start = getMinutes(getTime.utcStartTime) // let end = getMinutes(getTime.utcEndTime) // let now = getMinutesNow(getTime.timezone) // // if ( // (start < now && end > now) || (start > end && (now < end || now > start)) // ) { // return next() // } else if (start === end) { // return res.status(503).json({message: `در حال حاظر پذیرش سفارش نداریم`}) // }else { // return res.status(503).json({message: `پذیرش سفارش از ساعت ${getTime?.startTime} تا ${getTime?.endTime} می باشد`}) // } // // } else { // return next() // } // } else { // return next() // } // // } catch (error) { // return res500(res, _sr['fa'].response.unknownError) // } // }, [ body('product_id') .custom((value, {req}) => { return Food.findById(value) .then(product => { if (product) return true else return Promise.reject(_sr['fa'].not_found.item_id) }) .catch(err => { return Promise.reject(_sr['fa'].not_found.item_id) }) }), body('type') .notEmpty().withMessage(`نوع عملی که باید انجام شود را بفرستید`), body('branchId') .notEmpty().withMessage(_sr['fa'].required.field) .bail() .custom((value, {req}) => { return StoreInfo.findOne({_id: value}) .then(store => { if (!store) return Promise.reject(_sr['fa'].not_found.item_id) else return true }) }) ], checkValidations(validationResult), async (req, res) => { try { const {product_id, type, branchId} = req.body; const checkCart = await CartItem.findOne({branchId, product_id , user_id: req.user._id}).populate('product_id'); if (checkCart) { if (Number(checkCart.quantity) > 0) { if (type === 'positive') { checkCart.quantity = Number(checkCart.quantity) + 1; } else if (type === 'negative') { checkCart.quantity = Number(checkCart.quantity) - 1; } await checkCart.save(); if (Number(checkCart.quantity) === 0 || checkCart.product_id.stock === 0) { await checkCart.deleteOne() } } return res.json({message: _sr['fa'].response.success_save}) } else { const addCart = await CartItem.create({ branchId, product_id, user_id: req.user._id, quantity: 1 }) if (!addCart) return res500(res, _sr['fa'].response.unknownError); return res.json({message: _sr['fa'].response.success_save}) } } catch (error) { return res500(res, _sr['fa'].response.unknownError); } } ] module.exports.getCartItems = [ async (req, res) => { try { const branchId = req?.params?.branchId const allCartItems = await CartItem.find({branchId, user_id: req.user._id}).populate({ path: 'user_id', select: '-password -token', sort: {useRate: -1} }).populate('product_id') return res.json(allCartItems); } catch (error) { return res500(res, _sr['fa'].response.unknownError); } } ] module.exports.updateCart = [ [ body('quantity') .notEmpty() .withMessage((value, {req}) => { return _sr['fa'].required.quantity }) .bail() .isNumeric() .withMessage((value, {req}) => { return _sr['fa'].format.number }) ], checkValidations(validationResult), (req, res) => { CartItem.findById(req.params.item_id) .then(item => { if (!item) return res404(res, _sr['fa'].not_found.item_id) item.quantity = Number(req.body.quantity) item.save() return res.json({message: _sr['fa'].response.success_save}) }) .catch(err => { return res404(res, err) }) } ] module.exports.deleteAllCartItem = [ (req, res) => { CartItem.deleteMany({user_id: req.user._id}, (err, result) => { if (err) return res500(res, _sr['fa'].response.unknownError) else return res.json({message: _sr['fa'].response.success_remove}) }) } ] /////////////////////////////////// reorder module.exports.reOrder = [ //////////////////// validation step // (req, res, next) => { // CartItem.find({user_id: req.user._id}) // .then(items => { // // check if cart is not empty // if (items.length) { // // check product stock // let outOfRun = [] // items.forEach(async (item, index) => { // await Food.findById(item.product_id) // .then(product => { // if (product.stock < item.quantity) { // outOfRun.push({ // product_id: product._id, // category_id: product.category, // stock: product.stock // }) // item.remove() // } // if (index + 1 === items.length) { // if (!outOfRun.length) return next() // else return res.status(422).json(outOfRun) // } // }) // }) // } else { // return res.status(404).json({message: _sr['fa'].response.cart_empty}) // } // }) // }, async (req, res) => { try { const orderId = req.params?.orderId const checkOrder = await Order.findOne({'status': 'delivered', user_id: req.user._id, _id: orderId}).populate('product_id') if (!checkOrder) return res404(res, `سفارش کاربر یافت نشد`); await CartItem.deleteMany({user_id: req.user._id}) for (const food of checkOrder.order_items) { if (food.product_id && !food.product_id?.deleted) { await CartItem.create({ product_id: food.product_id._id, user_id: req.user._id, quantity: food.quantity }) } } return res.json({message: _sr['fa'].response.success_save}) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] ////////////////////////////////////// order module.exports.addOrder = [ [ body('description') .optional().isString().withMessage(`توضیحات سفارش باید به صورت رشته فرستاده شود`), body('bag') .optional().isBoolean().withMessage(_sr['fa'].format.boolean), body('score') .optional().isBoolean().withMessage(_sr['fa'].format.boolean), body('discountCode') .if((value, {req}) => { return value; }) .custom((value, {req}) => { return DiscountCode.findOne({discount_code: value}).then(discount => { if (!discount) return Promise.reject(`کد تخفیف وارد شده صحیح نمیباشد`); if (Number(discount.discount_expire_date) > Date.now()) return Promise.reject(`تاریخ مصرف کد تخفیف به اتمام رشیده است`) if (discount.used_by.includes(req?.user?._id)) return Promise.reject(`کد تخفیف قبلا استفاده شده است`); if (!discount.isPublic) { if (discount?.discount_user_id?.toString() !== req?.user?._id.toString()) return Promise.reject(`کد تخفیف وارد شده صحیح نمیباشد`) } else return true }).catch(error => { return Promise.reject(`کد تخفیف وارد شده صحیح نمیباشد`); }) }), body('branchId') .notEmpty().withMessage(_sr['fa'].required.field) .bail() .custom((value, {req}) => { return StoreInfo.findOne({_id: value}) .then(store => { if (!store) return Promise.reject(_sr['fa'].not_found.item_id) else return true }) }) ], checkValidations(validationResult), //////////////////// validation step (req, res, next) => { CartItem.find({branchId: req?.body?.branchId, user_id: req.user._id}) .then(items => { // check if cart is not empty if (items.length) { // check product stock let outOfRun = [] items.forEach(async (item, index) => { await Food.findById(item.product_id) .then(product => { if (product.stock < item.quantity) { outOfRun.push({ name: product.name, product_id: product._id, category_id: product.category, stock: product.stock }) item.remove() } if (index + 1 === items.length) { if (!outOfRun.length) return next() else return res.status(422).json(outOfRun) } }) }) } else { return res.status(404).json({message: _sr['fa'].response.cart_empty}) } }) }, //////////////////// save order async (req, res) => { try { const {branchId , description, bag, score, discountCode} = req.body; const cartItems = await CartItem.find({branchId ,user_id: req.user._id}).populate('product_id'); const getUserInfo = await User.findById(req.user._id); const getDiscountCode = await DiscountCode.findOne({branchId ,discount_code: discountCode}); if (!getUserInfo) return res404(res, `اطلاعات کاربر یافت نشد`); if (!cartItems.length) return res.status(404).json({message: `هیچ محصولی در سبد خرید شما وجود ندارد`}) await Order.deleteMany({branchId, status: 'incomplete', user_id: req.user._id}); /// check order exist or not // const checkOrder = await Order.findOne({'status': 'incomplete', user_id: req.user._id}); // // if (checkOrder) { // await checkOrder.remove() // } const orderItems = []; for (const item of cartItems) { orderItems.push({ product_id: item.product_id._id, name: item.product_id.name, quantity: item.quantity, price: item.product_id.price, static_discount: item.product_id.static_discount, havePoint: item.product_id.havePoint }); } if (score && bag) return res.status(422).json({message: 'استفاده همزمان از کیف پول و امتیاز برای خرید مجاز نمی باشد'}) const orderData = { branchId, user_id: req.user._id, number: Date.now(), order_items: orderItems, statuses: [{ status: 'incomplete' }], description, bag, score, discount_code: getDiscountCode?.discount_code, discount_amount: getDiscountCode?.discount_amount, discount_menuType: getDiscountCode?.menuTypeId }; await Order.create(orderData); return res.json({message: _sr['fa'].response.success_save}) } catch (error) { console.log(error) return res500(res, _sr['fa'].response.unknownError); } } ] module.exports.orderPay = [ [ body('branchId') .notEmpty().withMessage(_sr['fa'].required.field) .bail() .custom((value, {req}) => { return StoreInfo.findOne({_id: value}) .then(store => { if (!store) return Promise.reject(_sr['fa'].not_found.item_id) else return true }) }) ], checkValidations(validationResult), (req, res, next) => { CartItem.find({branchId: req?.body?.branchId, user_id: req.user._id}) .then(items => { // check if cart is not empty if (items.length) { // check product stock let outOfRun = [] items.forEach(async (item, index) => { await Food.findById(item.product_id) .then(product => { if (product.stock < item.quantity) { outOfRun.push({ product_id: product._id, category_id: product.category, stock: product.stock }) item.remove() } if (index + 1 === items.length) { if (!outOfRun.length) return next() else return res.status(422).json(outOfRun) } }) }) } else { return res.status(404).json({message: _sr['fa'].response.cart_empty}) } }) }, async (req, res) => { try { const branchId = req?.body?.branchId const cartItems = await CartItem.find({branchId , user_id: req.user._id}).populate('product_id'); const checkOrder = await Order.findOne({branchId , 'status': 'incomplete', user_id: req.user._id}) const getUserInfo = await User.findById(req.user._id); const getStoreInfo = await StoreInfo.findOne({_id : branchId}) if (!checkOrder) return res404(res, 'لطفا از مرحله سبد خرید دوباره ادامه دهید') let pricePoint = Math.floor(getStoreInfo?.priceForChange/getStoreInfo?.pointForChange) || 0 let totalPricePoint = getStoreInfo?.totalPricePoint || 0 let pointForBuy = getStoreInfo?.pointForBuy || 0 let discount_percent = checkOrder?.discount_amount || 0; let discount_menuType = checkOrder?.discount_menuType; let specialDiscountMenuType = false; let routePrice = 0; let sum = 0; let sumFoodByPercent = 0; let sumFoodByPercentForDiscount = 0; let total = 0 let firstTotal = 0 let percent = 0; let tax = 0; if (!getUserInfo) return res404(res, `اطلاعات کاربر یافت نشد`); if (!cartItems.length) return res.status(404).json({message: `هیچ محصولی در سبد خرید شما وجود ندارد`}) const orderItems = []; for (const item of cartItems) { orderItems.push({ product_id: item.product_id._id, name: item.product_id.name, quantity: item.quantity, price: item.product_id.price, static_discount: item.product_id.static_discount, havePoint: item.product_id.havePoint }); const itemPercent = item.product_id.price * item.product_id.static_discount / 100; const sumPercent = itemPercent * item.quantity; const price = item.product_id.price; const sumPriceItem = price * item.quantity; let sumByPercentForDiscount = sumPriceItem - sumPercent if (Boolean(discount_percent) && discount_menuType) { specialDiscountMenuType = true if (item?.product_id?.category.type?.toString() === discount_menuType?.toString()) { sumFoodByPercentForDiscount += sumByPercentForDiscount } } sum = sum + sumPriceItem; /// sum food price without percent percent = percent + sumPercent; /// sum all food percent } /// sum food price with percent sumFoodByPercent = sum - percent /// compute shipping cost if (getStoreInfo) { if (getStoreInfo?.shippingType) { routePrice = getStoreInfo?.shippingCost || 0; } else { routePrice = await _computeShippingCostByRoad(getUserInfo?.addresses, getStoreInfo?.location) * (getStoreInfo?.shippingCost || 0) || 0; } /// compute tax of order if (getStoreInfo?.tax) { tax = sumFoodByPercent * 9 / 100 } } /// compute discount code if (Boolean(discount_percent)) { let discountPercent = (specialDiscountMenuType ? sumFoodByPercentForDiscount : sumFoodByPercent) * discount_percent / 100 percent = percent + discountPercent /// compute sum food with discount percent sumFoodByPercent = sumFoodByPercent - discountPercent } /// compute total of order total = sumFoodByPercent + tax + routePrice; checkOrder.order_items = orderItems checkOrder.createdOn = Date.now() checkOrder.address = getUserInfo.addresses.find(item => item.used) checkOrder.sum = Math.ceil(sum) checkOrder.routePrice = Math.ceil(routePrice) checkOrder.tax = Math.ceil(tax) checkOrder.percent = Math.ceil(percent) checkOrder.total = Math.ceil(total) checkOrder.pricePoint = pricePoint firstTotal = Math.ceil(total) if (checkOrder.bag) { const payPrice = total - getUserInfo?.bagAmount total = payPrice <= 0 ? 0 : payPrice } if (checkOrder.score && pricePoint > 0) { const payScore = total - (getUserInfo?.score * pricePoint) total = payScore <= 0 ? 0 : payScore } checkOrder.payTotal = Math.ceil(total) console.log('total', total) console.log('payTotal', checkOrder?.payTotal) /// if total pay is zero dont move to pay method if (checkOrder?.payTotal <= 0) { /// if bag true use the bagAmount of user if (checkOrder?.bag) { getUserInfo.bagAmount -= firstTotal // add point for user only for available product const computeAvaPro = await _computeHavePointPrice(checkOrder.order_items) /// add score for order if (computeAvaPro >= totalPricePoint && totalPricePoint > 0) { const point = (Math.floor(computeAvaPro / totalPricePoint) * pointForBuy) if (point > 0) { getUserInfo.score = getUserInfo.score + point getUserInfo.scoreLogs = [ { score: point, order_id: getUserInfo._id, forWhat: 'buy', action: 'increase' } ] } } await getUserInfo.save() if (Boolean(discount_percent)) { const updateDiscount = await DiscountCode.findOne({discount_code: checkOrder.discount_code}) if (!updateDiscount || (Number(updateDiscount.discount_expire_date) > Date.now())) return res.status(403).json({message: 'مشکلی در پرداخت و ثبت سفارش پیش آمده است لطفا با پشتیبانی تماس بگیرید'}) updateDiscount.used_by.push(getUserInfo._id) await updateDiscount.save() } const addPayment = await Payment.create({ user_id: getUserInfo._id, order_id: checkOrder._id, transactionId: Date.now(), refId: Date.now(), getPayment: true, action: 'decreaseBag', status: 'paid', cost: firstTotal, }) checkOrder.payment.push(addPayment._id) checkOrder.bagCost = firstTotal } /// it was ok! /// if score true use the score of user if (checkOrder?.score && pricePoint > 0) { const point = Math.ceil(firstTotal / pricePoint) checkOrder.scoreCost = point * pricePoint checkOrder.scorePoint = point getUserInfo.score -= point getUserInfo.scoreLogs = [ { score: point, order_id: checkOrder._id, forWhat: 'buy', action: 'decrease' } ] await getUserInfo.save() } checkOrder.statuses.push({ status: 'processing' }) checkOrder.status = 'processing' checkOrder.paymentStatus = 'paid' await checkOrder.save() for (const food of checkOrder.order_items) { const getFood = await Food.findById(food.product_id) if (getFood) { getFood.stock = getFood.stock - food.quantity // getFood.canReview.push(req.user._id)///add user id for review getFood.save() } } /// remove cart item await CartItem.deleteMany({user_id: getUserInfo._id}) //----------------------- send notif for admins ----------------- const fcmTokens = [] const allAdmins = await User.find({scope: 'admin'}) for (const admin of allAdmins) { if (admin.fcmToken) fcmTokens.push(admin.fcmToken) } const objNotif = { title: 'سفارش جدید!', body: 'یک سفارش جدید در پنل برگ من ثبت گردید' } const payload = { notification: objNotif, }; const input = { registrationTokens: fcmTokens, payload } await sendMulti(input) sendNotifyOrder(checkOrder?.branchId) //---------------------------------------------------------------- //--------------------- send notif to user -------------------------- const messageUser = { title: 'ثبت سفارش', body: 'با تشکر از اعتماد شما سفارش شما با موفقیت ثبت شد' } const payloadUser = { notification: messageUser, }; const inputUser = { registrationTokens: getUserInfo.fcmToken, payload: payloadUser } await sendSingle(inputUser) //------------------------------------------------------------------- //------------------------------- send sms -------------------------- const smsTemplate = `سفارش شما با موفقیت ثبت شد با تشکر از اعتماد شما برگ من لغو11` const smsResult = await sms(getUserInfo.mobile_number, smsTemplate) if (!smsResult || !smsResult.data.IsSuccessful) { return res500(res, `مشکلی در ارسال پیامک پیش آمده است`) } //------------------------------------------------------------------- return res.json({message: _sr['fa'].response.success_save}) } const request = await zarinpal.request(checkOrder?.payTotal, checkOrder?.number?.toString(), req.user) if (!request.status) { console.log(request) return res500(res, _sr['fa'].response.unknownError) } // add payment to order const addPayment = await Payment.create({ user_id: getUserInfo._id, order_id: checkOrder._id, transactionId: request.response.authority, cost: checkOrder.payTotal, }) if (addPayment) { checkOrder.payment = addPayment._id await checkOrder.save(); } else { return res500(res, _sr['fa'].response.unknownError) } /// remove cart item await CartItem.deleteMany({branchId , user_id: req.user._id}) return res.json({paymentUrl: `${zarinUrl}/${request.response.authority}`}) } catch (error) { console.log(error.message) return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.getPaymentResponse = [ async (req, res) => { try { const checkPayment = await Payment.findOne({transactionId: req?.query?.Authority}).populate('order_id') const user = await User.findById(checkPayment.user_id) ///check duplicate transaction if (!checkPayment || checkPayment.getPayment || checkPayment.action === 'decreaseBag') { return res.json({ status: false, fromWhere : user?.fromWhere || 'app', message: 'پرداخت ناموفق بود' }) } /// verify transaction const verify = await zarinpal.verify(checkPayment.action === 'increaseBag' ? checkPayment.cost : checkPayment.order_id.payTotal, checkPayment.transactionId) console.log(verify) if (verify.status) { if (verify.code === 101) { console.log(verify) return res.json({ status: false, message: 'پرداخت ناموفق بود' }) } checkPayment.status = 'paid' checkPayment.refId = verify?.response?.ref_id checkPayment.getPayment = true checkPayment.fee_type = verify?.response?.fee_type checkPayment.card_hash = verify?.response?.card_hash checkPayment.card_pan = verify?.response?.card_pan checkPayment.fee = verify?.response?.fee checkPayment.created_at = Date.now() checkPayment.createdOn = Date.now() await checkPayment.save() /// only for buy food execute this action if (checkPayment.action === 'payOrder') { const getOrder = await Order.findById(checkPayment.order_id._id) if (!getOrder) return res404(res, 'سفارش پیدا نشد') for (const food of getOrder.order_items) { const getFood = await Food.findById(food.product_id) if (getFood) { getFood.stock = getFood.stock - food.quantity // getFood.canReview.push(req.user._id)///add user id for review getFood.save() } } if (getOrder.discount_code) { const discount = await DiscountCode.findOne({discount_code: getOrder.discount_code}) if (discount) { discount.used_by.push(getOrder.user_id) await discount.save() } } if (user) { /// decrease score if (getOrder?.score) { getOrder.scorePoint = user.score getOrder.scoreCost = user.score * getOrder?.pricePoint user.scoreLogs = [ { score: user.score, order_id: getOrder._id, forWhat: 'buy', action: 'decrease' } ] user.score = 0 await user.save() } /// decrease bag if (getOrder.bag) { const addPayment = await Payment.create({ user_id: user._id, order_id: getOrder._id, transactionId: Date.now(), refId: Date.now(), getPayment: true, action: 'decreaseBag', status: 'paid', cost: user.bagAmount, }) getOrder.payment.push(addPayment._id) getOrder.bagCost = user.bagAmount user.bagAmount = 0 await user.save() } if (!getOrder.score || !getOrder?.discount_code) { // add point for user only for available product const computeAvaPro = await _computeHavePointPrice(getOrder?.order_items) /// add score for order if (computeAvaPro >= getOrder?.totalPricePoint && getOrder?.totalPricePoint > 0) { const point = (Math.floor(computeAvaPro / getOrder?.totalPricePoint) * getOrder?.pointForBuy) if (point > 0) { user.score = user.score + point user.scoreLogs = [ { score: point, order_id: getOrder._id, forWhat: 'buy', action: 'increase' } ] await user.save() } } } } getOrder.statuses.push({ status: 'processing' }) getOrder.status = 'processing' getOrder.paymentStatus = 'paid' await getOrder.save() //----------------------- send notif for admins ----------------- const fcmTokens = [] const allAdmins = await User.find({scope: 'admin'}) for (const admin of allAdmins) { if (admin.fcmToken) fcmTokens.push(admin.fcmToken) } const objNotif = { title: 'سفارش جدید!', body: 'یک سفارش جدید در پنل برگ من ثبت گردید' } const payload = { notification: objNotif, }; const input = { registrationTokens: fcmTokens, payload } await sendMulti(input) sendNotifyOrder(getOrder?.branchId) //---------------------------------------------------------------- //--------------------- send notif to user -------------------------- const messageUser = { title: 'ثبت سفارش', body: 'با تشکر از اعتماد شما سفارش شما با موفقیت ثبت شد' } const payloadUser = { notification: messageUser, }; const inputUser = { registrationTokens: user.fcmToken, payload: payloadUser } await sendSingle(inputUser) //------------------------------------------------------------------- //------------------------------- send sms -------------------------- const smsTemplate = `سفارش شما با موفقیت ثبت شد با تشکر از اعتماد شما برگ من لغو11` const smsResult = await sms(user.mobile_number, smsTemplate) if (!smsResult || !smsResult.data.IsSuccessful) { return res500(res, `مشکلی در ارسال پیامک پیش آمده است`) } //------------------------------------------------------------------- } else if (checkPayment.action === 'increaseBag') { if (!user) return res.json({ status: false, message: 'مشکلی در افزایش موجودی کیف پول شما پیش آمده است لطفا با پشتیبانی تماس بگیرید' }) user.bagAmount = user.bagAmount + checkPayment?.cost await user.save() //--------------------- send notif to user -------------------------- const messageUser = { title: 'افزایش موجودی', body: 'با تشکر از اعتماد شما کیف پول شما با موفقیت شارژ شد برگ من' } const payloadUser = { notification: messageUser, }; const inputUser = { registrationTokens: user.fcmToken, payload: payloadUser } await sendSingle(inputUser) //------------------------------------------------------------------- //------------------------------- send sms -------------------------- const smsTemplate = `با تشکر از اعتماد شما کیف پول شما با موفقیت شارژ شد برگ من لغو11` const smsResult = await sms(user.mobile_number, smsTemplate) if (!smsResult || !smsResult.data.IsSuccessful) { return res500(res, `مشکلی در ارسال پیامک پیش آمده است`) } //------------------------------------------------------------------- } return res.json({ status: true, refId: verify?.response?.ref_id, fromWhere : user?.fromWhere || 'app', message: 'پرداخت با موفقیت انجام شد' }) } else { /// only for buy food execute this action if (checkPayment.action === 'payOrder') { let getOrder = await Order.findById(checkPayment.order_id._id) if (getOrder) { getOrder.statuses.push({ status: 'void' }) getOrder.status = 'void' getOrder.paymentStatus = 'unpaid' await getOrder.save() } checkPayment.getPayment = true checkPayment.created_at = new Date() await checkPayment.save() } return res.json({ status: false, fromWhere : user?.fromWhere || 'app', message: 'پرداخت ناموفق بود' }) } } catch (error) { console.log(error.message) return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.addAddressToOrder = [ (req, res) => { Order.findById(req.params.order_id) .then(order => { if (!order.address) { User.findOne({'addresses._id': req.body.address_id}) .then(user => { order.address = user.addresses.id(req.body.address_id) // add processing status to order order.statuses.push({ status: 'processing' }) order.save() return res.json(order) }) .catch(err => { return res500(res, err) }) } else { return res.status(500).json({message: v_m[req.body.locale].response.already_has_address}) } }) .catch(err => { return res404(res, err) }) } ] module.exports.addStatusToOrder = [ async (req, res) => { try { const order = await Order.findById(req.params.order_id).populate('user_id') if (!order) return res404(res, 'سفارش مورد نظر یافت نشد') order.statuses.push({ status: req.body.status }) order.status = req.body.status order.save() if (req.body.status === 'delivered') { //--------------------- send notif to user -------------------------- const messageUser = { title: 'ارسال سفارش', body: 'سفارش شما ارسال گردید با تشکر برگ من' } const payloadUser = { notification: messageUser, }; const inputUser = { registrationTokens: order?.user_id?.fcmToken, payload: payloadUser } await sendSingle(inputUser) //------------------------------------------------------------------- setTimeout(async () => { let smsTemplate = '' if (order.user_id.fromWhere === 'app') smsTemplate = `لطفا با ثبت نظر برای سفارش خود به ما برای خدمات بهتر کمک کنید با تشکر برگ من` else smsTemplate = `لطفا با ثبت نظر برای سفارش خود به ما برای خدمات بهتر کمک کنید با تشکر برگ من لغو11 لینک : https://app.bargrestaurant.com/rate/${req.params.order_id}` const smsResult = await sms(order?.user_id?.mobile_number, smsTemplate) //--------------------- send notif to user -------------------------- const messageUser = { title: 'ثبت نظر', body: 'لطفا با ثبت نظر برای سفارش خود به ما برای خدمات بهتر کمک کنید با تشکر' } const payloadUser = { notification: messageUser, }; const inputUser = { registrationTokens: order?.user_id?.fcmToken, payload: payloadUser } await sendSingle(inputUser) //------------------------------------------------------------------- if (!smsResult || !smsResult.data.IsSuccessful) { console.log('order sms', smsResult) } }, timeToStart) } return res.json(order) } catch (error) { console.log(error.message) return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.changePayStatus = [ [ body('status') .notEmpty().withMessage(_sr['fa'].required.field) .bail() .isIn(['refund', 'unpaid', 'paid']).withMessage('وضعیت ارسال شده صحیح نمی باشد') ], checkValidations(validationResult), async (req, res) => { try { const targetOrder = await Order.findById(req.params.id) if (!targetOrder) return res404(res, 'سفارش مورد نظر پیدا نشد'); const targetPayment = await Payment.findById(targetOrder.payment) if (targetPayment) { targetPayment.status = req.body.status await targetPayment.save() } targetOrder.paymentStatus = req.body.status await targetOrder.save() return res.json({message: _sr['fa'].response.success_save}) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.getOrders = [ (req, res) => { const branchId = req?.params?.branchId Order.find({ branchId, user_id: req.user._id, status: {$in: ['canceled' , 'processing', 'sent', 'delivered']}, paymentStatus: 'paid' }).populate({ path: 'order_items.product_id', select: 'name canReview ratings' }).sort({_id: -1}) .then(orders => { const allOrders = [] for (const item of orders) { let clone = JSON.parse(JSON.stringify(item.order_items)) clone.map(food => { // food.review = food?.product_id?.canReview?.includes(req.user._id.toString()) // food.rating = food?.product_id?.ratings?.find(rate => rate.user_id.toString() === req.user._id.toString() && rate?.order_id?.toString() === item?._id?.toString())?.rate || 0 food.rating = food?.product_id?.ratings?.find(rate => rate?.user_id?.toString() === req?.user?._id.toString())?.rate || 0 }) allOrders.push({ _id: item._id, address: item.address, status: item.status, items: clone, orderType: item.orderType, review: item.rating.rate, created_at: item.createdOn, total: item.total }) } return res.json(allOrders) }) .catch(err => { console.log(err.message) return res500(res, _sr['fa'].response.unknownError) }) } ] module.exports.getOrdersForReport = [ async (req, res) => { try { const branchId = req?.params?.branchId const orders = await Order.find({ branchId, status: {$in: ['canceled' ,'processing', 'sent', 'delivered']}, paymentStatus: 'paid' }) return res.json(orders) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.getOneOrderByUser = [ async (req, res) => { try { const targetOrder = await Order.findOne({_id: req.params.id, user_id: req.user._id}).select('status') if (!targetOrder) return res404(res, 'سفارش مورد نظر پیدا نشد') return res.json(targetOrder) } catch (error) { console.log('get one order by user |', error.message) return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.deleteOrder = [ async (req, res) => { try { const targetOrder = await Order.findById(req.params.id); if (!targetOrder) return res404(res, 'سفارش مورد نظر پیدا نشد') /// @todo maybe delete payment too await targetOrder.deleteOne() return res.json({message: _sr['fa'].response.success_remove}) } catch (error) { console.log('delete order: ', error.message) return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.increaseBag = [ [ body('price') .notEmpty().withMessage(`لطفا یک مقدار مشخص کنید`) ], checkValidations(validationResult), async (req, res) => { try { const cost = req.body.price; const user = await User.findById(req.user._id) if (!user) return res404(res, `اطلاعات کاربر پیدا نشد`) const request = await zarinpal.request(cost, 'کیف پول', req.user) if (!request.status) { console.log(request) return res500(res, _sr['fa'].response.unknownError) } // add payment to order await Payment.create({ user_id: user._id, order_id: null, transactionId: request.response.authority, cost: cost, action: 'increaseBag' }) return res.json({paymentUrl: `${zarinUrl}/${request.response.authority}`}) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] /////// admin panel module.exports.getOneOrder = [ (req, res) => { Order.findById(req.params.id).populate({ path: 'user_id', select: 'first_name last_name scoreLogs' }).populate('order_items.product_id') .populate('payment') .then(order => { return res.json(order) }) .catch(err => { return res404(res, err) }) } ] module.exports.search = [ async (req, res) => { try { let { branchId, number, orderStatus, paymentStatus, userId, date } = req.body; let findQuery = { branchId, status: {$nin: ['incomplete', 'void']} }; findQuery['$and'] = []; findQuery['$or'] = []; /** search by number */ if (number && !_.isEmpty(number)) { findQuery['$and'].push({ number }); } /** search by orderStatus */ if (orderStatus && !_.isEmpty(orderStatus)) { findQuery['$and'].push({ status: { $in: orderStatus } }); } /** search by paymentStatus */ if (paymentStatus && !_.isEmpty(paymentStatus)) { findQuery['$and'].push({ paymentStatus: { $in: paymentStatus } }); } /** search by user_id */ if (userId && !_.isEmpty(userId)) { findQuery['$and'].push({ user_id: userId }); } /** search by date */ if (date && !_.isEmpty(date)) { if (date.length === 1) { findQuery['$and'].push({ createdOn: { $gte: moment(date[0]).startOf('days'), $lt: moment(date[0]).endOf('days') } }); } else { findQuery['$and'].push({ createdOn: { $gte: new Date(date[0]), $lt: new Date(date[1]) } }); } } _.isEmpty(findQuery['$and']) && delete findQuery['$and']; _.isEmpty(findQuery['$or']) && delete findQuery['$or']; /** get news */ const orders = await Order.paginate(findQuery, { page: req.query.page, limit, populate: [{path: 'user_id', select: 'first_name last_name'}], sort: { _id: -1 } }); /** show result */ return res.json(orders); } catch (error) { console.log(error.message) return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.reports = [ // [ // body('startDate') // .notEmpty().withMessage(_sr['fa'].required.date), // // body('endDate') // .notEmpty().withMessage(_sr['fa'].required.date), // ], // checkValidations(validationResult), async (req, res) => { try { const branchId = req?.params?.branchId let findQuery = { branchId, status: {$nin: ['incomplete', 'void']} }; let {startDate,endDate} = req.body; // if (!startDate || !endDate){ // findQuery.createdOn = { // createdOn: { // $gte: moment().startOf('days'), // $lt: moment().endOf('days') // } // } // }else { if (startDate && endDate) { findQuery.createdOn = { $gte: new Date(startDate), $lt: new Date(endDate) } }else if (startDate){ findQuery.createdOn = { $gte: moment(startDate), $lt: new Date() } }else if (endDate){ findQuery.createdOn = { // $gte: '', $lt: moment(endDate) } } // } /** search by date */ // if (startDate || endDate && !_.isEmpty(date)) { // if (date.length === 1) { // findQuery.createdOn = { // $gte: moment(startDate), // $lt: moment(startDate) // } // } else { // findQuery.createdOn = { // $gte: new Date(date[0]), // $lt: new Date(date[1]) // } // } // } else { // // } /** get news */ const orders = await Order.find(findQuery).populate({path: 'user_id', select: 'first_name last_name'}).sort({_id: -1}); let result = { count: orders?.length || 0, total: orders?.reduce((total, item) => { return total + item?.total }, 0) || 0, payOnline: orders?.reduce((total, item) => { return total + item?.payTotal }, 0) || 0, routePrice: orders?.reduce((total, item) => { return total + item?.routePrice }, 0) || 0, bagCost: orders?.reduce((total, item) => { return total + item?.bagCost }, 0) || 0, scorePoint: orders?.reduce((total, item) => { return total + item?.scorePoint }, 0) || 0, scoreCost: orders?.reduce((total, item) => { return total + item?.scoreCost }, 0) || 0, } /** show result */ return res.json(result); } catch (error) { console.log(error.message) return res500(res, _sr['fa'].response.unknownError) } } ] //////////////////////////////////// compute price module.exports.computePrice = [ //////////////////// validation step (req, res, next) => { const branchId = req?.params?.branchId CartItem.find({branchId , user_id: req.user._id}) .then(items => { // check if cart is not empty if (items.length) { // check product stock let outOfRun = [] items.forEach(async (item, index) => { await Food.findById(item.product_id) .then(product => { if (product.stock < item.quantity) { outOfRun.push({ product_id: product._id, category_id: product.category, stock: product.stock }) item.remove() } if (index + 1 === items.length) { if (!outOfRun.length) return next() else return res.status(422).json(outOfRun) } }) }) } else { return res.status(404).json({message: _sr['fa'].response.cart_empty}) } }) }, async (req, res) => { try { const branchId = req?.params?.branchId const cartItems = await CartItem.find({branchId, user_id: req.user._id}).populate({ path: 'product_id', populate: { path: 'category', model: 'FoodCategory' } }); const checkOrder = await Order.findOne({branchId , 'status': 'incomplete', user_id: req.user._id}) const getUserInfo = await User.findById(req.user._id); const getStoreInfo = await StoreInfo.findOne({_id : branchId}) const product = []; const userAddress = getUserInfo.addresses.find(item => item.used === true) || null let specialDiscountMenuType = false; let discount_percent = checkOrder?.discount_amount || 0; let discount_menuType = checkOrder?.discount_menuType; let routePrice = 0; let sum = 0; let sumFoodByPercent = 0; let sumFoodByPercentForDiscount = 0; let total = 0; let percent = 0; let tax = 0; if (!getUserInfo) return res404(res, `اطلاعات کاربر یافت نشد`); if (!cartItems.length) return res.status(404).json({message: `هیچ محصولی در سبد خرید شما وجود ندارد`}) if (checkOrder) { const orderItems = []; for (const item of cartItems) { orderItems.push({ product_id: item.product_id._id, name: item.product_id.name, quantity: item.quantity, price: item.product_id.price, static_discount: item.product_id.static_discount, havePoint: item.product_id.havePoint }); product.push({ product: item.product_id, quantity: item.quantity }) const itemPercent = item.product_id.price * item.product_id.static_discount / 100; const sumPercent = itemPercent * item.quantity; const price = item.product_id.price; const sumPriceItem = price * item.quantity; let sumByPercentForDiscount = sumPriceItem - sumPercent if (Boolean(discount_percent) && discount_menuType) { specialDiscountMenuType = true if (item?.product_id?.category.type?.toString() === discount_menuType?.toString()) { sumFoodByPercentForDiscount += sumByPercentForDiscount } } sum = sum + sumPriceItem; /// sum food price without percent percent = percent + sumPercent; /// sum all food percent } /// sum food price with percent sumFoodByPercent = sum - percent /// compute shipping cost if (getStoreInfo) { if (getStoreInfo?.shippingType) { routePrice = getStoreInfo?.shippingCost || 0; } else { routePrice = await _computeShippingCostByRoad(getUserInfo?.addresses, getStoreInfo?.location) * (getStoreInfo?.shippingCost || 0) || 0; } /// compute tax of order if (getStoreInfo?.tax) { tax = sumFoodByPercent * 9 / 100 } } /// compute discount code if (Boolean(discount_percent)) { let discountPercent = (specialDiscountMenuType ? sumFoodByPercentForDiscount : sumFoodByPercent) * discount_percent / 100 percent = percent + discountPercent /// compute sum food with discount percent sumFoodByPercent = sumFoodByPercent - discountPercent } /// compute total of order total = sumFoodByPercent + tax + routePrice; checkOrder.order_items = orderItems checkOrder.createdOn = Date.now() checkOrder.sum = Math.ceil(sum) checkOrder.routePrice = Math.ceil(routePrice) checkOrder.tax = Math.ceil(tax) checkOrder.percent = Math.ceil(percent) checkOrder.total = Math.ceil(total) checkOrder.pricePoint = getStoreInfo?.pricePoint if (checkOrder.bag) { const payPrice = total - getUserInfo?.bagAmount total = payPrice <= 0 ? 0 : payPrice } if (checkOrder.score && getStoreInfo?.pricePoint > 0) { const payScore = total - (getUserInfo?.score * getStoreInfo?.pricePoint) total = payScore <= 0 ? 0 : payScore } checkOrder.payTotal = Math.ceil(total) checkOrder.save(); } return res.json({ usePoint: getStoreInfo?.usePointWhenPay, product, userAddress, sum: Math.ceil(sum), routePrice: Math.ceil(routePrice), tax: Math.ceil(tax), percent: Math.ceil(percent), total: Math.ceil(checkOrder.payTotal) }) } catch (error) { console.log(error.message) return res500(res, _sr['fa'].response.unknownError) } } ] ////////////////////////////////////// get list of all orders for admin module.exports.getAllOrders = [ async (req, res) => { try { const branchId = req?.params?.branchId const orders = await Order.paginate({branchId, status: {$nin: ['incomplete', 'void']}}, { page: req.query.page || 1, limit, populate: [ {path: 'user_id', select: 'first_name last_name'} ], sort: {_id: -1} }) return res.json(orders) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] ///////////////////////////////////// get payment list module.exports.getAllPaymentByUser = [ async (req, res) => { try { const getAll = await Payment.find({user_id: req.user._id}).sort({_id: -1}) return res.json(getAll) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] module.exports.getAllPaymentByAdmin = [ async (req, res) => { try { const getAll = await Payment.paginate({user_id: req.params.id}, { page: req.query.page || 1, limit, sort: {_id: -1} }) return res.json(getAll) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] //////////////////////////////////// get all foods for rates module.exports.getAllRateFood = [ async (req, res) => { try { const getOrder = await Order.findOne({ _id: req.params.order_id, user_id: req.user._id, status: 'delivered', paymentStatus: 'paid' }).populate({ path: 'order_items.product_id', select: 'name ratings' }) if (!getOrder) return res404(res, `سفارش مورد نظر پیدا نشد`) return res.json(getOrder) } catch (e) { return res500(res, _sr['fa'].response.unknownError) } } ] /////////////////////////////////// rate order module.exports.rateOrderAndFood = [ [ body('rateOrder') .notEmpty().withMessage(_sr['fa'].required.field) ], checkValidations(validationResult), async (req, res) => { try { const orderId = req?.params?.order_id const {rateOrder, reason, foods} = req.body const getOrder = await Order.findOne({ _id: orderId, user_id: req.user._id, status: 'delivered', paymentStatus: 'paid' }) if (!getOrder) return res404(res, `سفارش مورد نظر پیدا نشد`) //// add rate for all foods for (const food of foods) { const data = { rate: food.rate, comment: food.comment, order_id: getOrder._id, user_id: getOrder.user_id } //// check food exist or not const getFood = await Food.findById(food?.product_id?._id) if (getFood && getOrder?.order_items.some(item => item?.product_id?._id?.toString() === getFood?._id?.toString())) { getFood.ratings.push(data) let rateSUM = 0 getFood?.ratings?.forEach(item => rateSUM += Number(item.rate)) getFood.rate = (rateSUM / getFood?.ratings?.length).toFixed(1) await getFood.save() } } //// add rate to order getOrder.rating = { rate: rateOrder, reasonForBad: reason } await getOrder.save() return res.json({message: _sr['fa'].response.success_save}) } catch (e) { return res500(res, _sr['fa'].response.unknownError) } } ] ////////////////////////////////// buy club module.exports.buyClub = [ [ body('clubId') .notEmpty().withMessage(_sr['fa'].required.field) ], checkValidations(validationResult), async (req, res) => { try { const clubId = req.body?.clubId const user = await User.findById(req.user._id) if (!user) return res404(res, 'اطلاعات کاربر یافت نشد') const club = await Food.findOne({_id: clubId, club: true}) if (!club) return res404(res, 'کلاب مورد نظر پیدا نشد') if (user.score < club.point) return res.status(422).json({message: 'امتیاز شما برای خرید این محصول کافی نمی باشد'}) const data = { user_id: user._id, score: true, scorePoint: club.point, scoreCost: club.price, orderType: 'club', number: Date.now(), address: user?.addresses.find(item => item.used), createdOn: Date.now(), paymentStatus: 'paid', statuses: [{ status: 'processing' }], status: 'processing', order_items: [{ product_id: club._id, name: club.name, quantity: 1, price: club.price, point: club.point, havePoint: true }], sum: club.point, routePrice: 0, tax: 0, percent: 0, total: club.price } const order = await Order.create(data) if (!order) return res500(res, _sr['fa'].response.unknownError) user.score -= club.point user.scoreLogs = [ { score: club.point, order_id: order._id, forWhat: 'buy', action: 'decrease' } ] await user.save() club.stock -= club?.clubQuantity || 1 await club.save() return res.json({message: _sr['fa'].response.success_save}) } catch (error) { return res500(res, _sr['fa'].response.unknownError) } } ] ////////////////////////////////// test notif module.exports.testNotif = [ async (req, res) => { try { sendNotifyOrder('6126313206f2593a6a4f71db') return res.json({}) } catch (error) { return res500(res, error.message) } } ] module.exports.chromeNotif = [ async (req, res) => { try { const fcmTokens = [] const allAdmins = await User.find({scope: 'admin'}) for (const admin of allAdmins) { if (admin.fcmToken) fcmTokens.push(admin.fcmToken) } const objNotif = { title: "سفارش جدید!", body: "یک سفارش جدید در پنل برگ من ثبت گردید" } const payload = { notification: objNotif, data: { routeName: 'test', type: "bazaar", routeType: "viewBazaar", }, }; console.log(fcmTokens) const input = { registrationTokens: fcmTokens, payload } console.log(input) const test = await sendMulti(input) return res.json(test) // const payload = JSON.stringify({ // title: 'Push notifications with Service Workers', // }); // // webPush.sendNotification('test', payload) // .catch(error => console.error(error)); } catch (error) { } } ]