diff --git a/cli/commands/update.js b/cli/commands/update.js index d998603ded3..40b9143d33b 100644 --- a/cli/commands/update.js +++ b/cli/commands/update.js @@ -1,15 +1,14 @@ const fse = require("fs-extra"); -const execa = require("execa"); const start = require('./start'); const { filePath, log, execCommand } = require('./utils'); +const { execSync } = require('child_process'); module.exports = async function() { try { log('Stopping pm2 processes ...'); - // stop services try { - await execa("pm2", ["delete", 'all']); + execSync("pm2 delete all", { stdio: 'inherit' }); } catch (e) { console.log(e.message); } diff --git a/cli/commands/utils.js b/cli/commands/utils.js index 7861a283223..cfcaa12c83f 100644 --- a/cli/commands/utils.js +++ b/cli/commands/utils.js @@ -1,5 +1,4 @@ const chalk = require('chalk'); -const execa = require('execa'); const fs = require('fs'); const cliProgress = require('cli-progress'); const request = require('request'); @@ -7,6 +6,7 @@ const fse = require('fs-extra'); const { resolve } = require('path'); const exec = require('child_process').exec; const colors = require('colors'); +const { execSync } = require('child_process'); const filePath = pathName => { if (pathName) { @@ -145,14 +145,6 @@ module.exports.downloadLatesVersion = async configs => { await fse.copy(filePath('build.tar.gz'), filePath('build-backup.tar.gz')); }; -const runCommand = (command, args, pipe) => { - if (pipe) { - return execa(command, args).stdout.pipe(process.stdout); - } - - return execa(command, args); -}; - module.exports.startServices = async configs => { log('Starting services using pm2 ...'); @@ -482,7 +474,8 @@ module.exports.startServices = async configs => { )}` ); - return runCommand('pm2', ['start', filePath('ecosystem.config.js')], false); + const ecosystemPath = filePath('ecosystem.config.js'); + return execSync(`pm2 start ${ecosystemPath}`); }; const generateNginxConf = async ({ @@ -555,6 +548,5 @@ const generateNginxConf = async ({ ); }; -module.exports.runCommand = runCommand; module.exports.downloadFile = downloadFile; module.exports.execCurl = execCurl; diff --git a/cli/installer/index.js b/cli/installer/index.js index 942d14822c3..47cc385316b 100644 --- a/cli/installer/index.js +++ b/cli/installer/index.js @@ -1,18 +1,9 @@ var amqplib = require('amqplib'); -var shell = require('shelljs'); var open = amqplib.connect(process.env.RABBITMQ_HOST); var queueName = 'managePluginInstall'; - -var runCommand = (command, method='exec') => { - return new Promise((resolve) => { - setTimeout(() => { - shell[method](command); - resolve('done'); - }, 500) - }); -} +const { execSync } = require('child_process'); var sleep = ms => { return new Promise(resolve => { @@ -50,43 +41,43 @@ open sendMessage(ch, 'started'); - await runCommand('..', 'cd'); + execSync('cd ..'); // Update configs.json - await runCommand(`npm run erxes installer-update-configs ${data.type} ${data.name}`); + execSync(`npm run erxes installer-update-configs ${data.type} ${data.name}`); if (data.type === 'install') { sendMessage(ch, 'Running up ....'); - await runCommand(`npm run erxes up -- --fromInstaller`); + execSync(`npm run erxes up -- --fromInstaller`); sendMessage(ch, 'Syncing ui ....'); - await runCommand(`npm run erxes syncui ${data.name}`); + execSync(`npm run erxes syncui ${data.name}`); sendMessage(ch, 'Restarting coreui ....'); - await runCommand(`npm run erxes restart coreui`); + execSync(`npm run erxes restart coreui`); sendMessage(ch, 'Waiting for 10 seconds for plugin api....'); await sleep(10000); sendMessage(ch, 'Restarting gateway ...'); - await runCommand(`npm run erxes restart gateway`); + execSync(`npm run erxes restart gateway`); } if (data.type === 'uninstall') { sendMessage(ch, 'Running up'); - await runCommand(`npm run erxes up -- --fromInstaller`); + execSync(`npm run erxes up -- --fromInstaller`); sendMessage(ch, `Removing ${data.name} service ....`); - await runCommand(`npm run erxes remove-service erxes_plugin_${data.name}_api`); + execSync(`npm run erxes remove-service erxes_plugin_${data.name}_api`); sendMessage(ch, `Restarting coreui ....`); - await runCommand(`npm run erxes restart coreui`); + execSync(`npm run erxes restart coreui`); sendMessage(ch, `Restarting gateway ....`); - await runCommand(`npm run erxes restart gateway`); + execSync(`npm run erxes restart gateway`); } - await runCommand('installer', 'cd'); + execSync('cd installer'); sendMessage(ch, `done`); diff --git a/cli/package.json b/cli/package.json index 581a4b75a28..c5d2faab56f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "erxes", - "version": "0.4.24", + "version": "0.4.25", "description": "Free and open fair-code licensed all-in-one growth marketing & management software", "homepage": "https://erxes.io", "repository": "https://github.com/erxes/erxes", @@ -25,7 +25,6 @@ "colors": "^1.4.0", "commander": "^6.2.0", "dotenv": "^16.0.3", - "execa": "^4.1.0", "figlet": "^1.5.0", "fs-extra": "^9.0.1", "jsonwebtoken": "^8.5.1", @@ -33,7 +32,6 @@ "lodash": "^4.17.21", "pm2": "^5.2.2", "request": "^2.88.2", - "shelljs": "^0.8.5", "yaml": "^1.10.2" } } diff --git a/cypress/tests/ui/auth.cy.js b/cypress/tests/auth.cy.js similarity index 100% rename from cypress/tests/ui/auth.cy.js rename to cypress/tests/auth.cy.js diff --git a/cypress/tests/api/demo.cy.js b/cypress/tests/contacts/api/demo.cy.js similarity index 100% rename from cypress/tests/api/demo.cy.js rename to cypress/tests/contacts/api/demo.cy.js diff --git a/cypress/tests/ui/contacts/company.cy.js b/cypress/tests/contacts/ui/company.cy.js similarity index 100% rename from cypress/tests/ui/contacts/company.cy.js rename to cypress/tests/contacts/ui/company.cy.js diff --git a/cypress/tests/ui/contacts/customer.cy.js b/cypress/tests/contacts/ui/customer.cy.js similarity index 100% rename from cypress/tests/ui/contacts/customer.cy.js rename to cypress/tests/contacts/ui/customer.cy.js diff --git a/cypress/tests/ui/contacts/lead.cy.js b/cypress/tests/contacts/ui/lead.cy.js similarity index 100% rename from cypress/tests/ui/contacts/lead.cy.js rename to cypress/tests/contacts/ui/lead.cy.js diff --git a/packages/plugin-products-api/src/dataloaders/resolvers/queries/products.ts b/packages/plugin-products-api/src/dataloaders/resolvers/queries/products.ts index db5b86c50d9..a6ffaccf3a3 100644 --- a/packages/plugin-products-api/src/dataloaders/resolvers/queries/products.ts +++ b/packages/plugin-products-api/src/dataloaders/resolvers/queries/products.ts @@ -7,7 +7,10 @@ import { PRODUCT_STATUSES } from '../../../models/definitions/products'; import { escapeRegExp } from '@erxes/api-utils/src/core'; import { IContext, IModels } from '../../../connectionResolver'; import messageBroker, { sendTagsMessage } from '../../../messageBroker'; -import { getSimilaritiesProducts } from '../../../maskUtils'; +import { + getSimilaritiesProducts, + getSimilaritiesProductsCount +} from '../../../maskUtils'; import { Builder, countBySegment, countByTag } from '../../../utils'; interface IQueryParams { @@ -236,6 +239,10 @@ const productQueries = { params ); + if (params.groupedSimilarity) { + return await getSimilaritiesProductsCount(models, filter, params); + } + return models.Products.find(filter).count(); }, diff --git a/packages/plugin-products-api/src/maskUtils.ts b/packages/plugin-products-api/src/maskUtils.ts index c71b7cddf32..e5f9d2a6c8b 100644 --- a/packages/plugin-products-api/src/maskUtils.ts +++ b/packages/plugin-products-api/src/maskUtils.ts @@ -171,8 +171,8 @@ export const checkSameMaskConfig = async (models: IModels, doc: IProduct) => { return undefined; }; -export const groupBySameMasksAggregator = () => { - return [ +export const groupBySameMasksAggregator = (isCount = false) => { + const sameArr = [ { $addFields: { sameMasksLen: { @@ -197,7 +197,28 @@ export const groupBySameMasksAggregator = () => { }, { $unwind: '$sameMasks' - }, + } + ]; + + if (isCount) { + return [ + ...sameArr, + { + $group: { + _id: { sameMasks: '$sameMasks' }, + product: { $first: '$code' } + } + }, + { + $group: { + _id: { code: '$product' } + } + } + ]; + } + + return [ + ...sameArr, { $sort: { 'product.code': 1 } }, { $group: { @@ -217,8 +238,8 @@ export const groupBySameMasksAggregator = () => { ]; }; -export const groupByCategoryAggregator = () => { - return [ +export const groupByCategoryAggregator = (isCount = false) => { + const sameArr = [ { $lookup: { from: 'product_categories', @@ -248,7 +269,22 @@ export const groupByCategoryAggregator = () => { } } } - }, + } + ]; + + if (isCount) { + return [ + ...sameArr, + { + $group: { + _id: { same: '$same' } + } + } + ]; + } + + return [ + ...sameArr, { $group: { _id: { same: '$same' }, @@ -281,3 +317,17 @@ export const getSimilaritiesProducts = async (models, filter, params) => { hasSimilarity: gd.count > 1 })); }; + +export const getSimilaritiesProductsCount = async (models, filter, params) => { + const aggregates = + params.groupedSimilarity === 'config' + ? groupBySameMasksAggregator(true) + : groupByCategoryAggregator(true); + const groupedData = await models.Products.aggregate([ + { $match: filter }, + ...aggregates, + { $group: { _id: {}, count: { $sum: 1 } } } + ]); + + return ((groupedData || [])[0] || {}).count || 0; +}; diff --git a/packages/plugin-timeclock-api/src/graphql/resolvers/utils.ts b/packages/plugin-timeclock-api/src/graphql/resolvers/utils.ts index 568d7e66f1f..7ecb825390a 100644 --- a/packages/plugin-timeclock-api/src/graphql/resolvers/utils.ts +++ b/packages/plugin-timeclock-api/src/graphql/resolvers/utils.ts @@ -259,6 +259,20 @@ export const timeclockReportByUser = async ( ] }); + const requestsOfSelectedMonth = await models.Absences.find({ + $and: [ + { userId }, + { solved: true }, + { status: 'Approved' }, + { + startTime: { + $gte: startOfSelectedMonth, + $lte: startOfNextMonth + } + } + ] + }); + let scheduledShiftStartSelectedDay; let scheduledShiftEndSelectedDay; @@ -465,6 +479,7 @@ export const timeclockReportByUser = async ( totalHoursBreakScheduled, totalHoursBreakSelecteDay, + requests: requestsOfSelectedMonth, scheduledShifts: scheduleShiftsSelectedMonth, timeclocks: timeclocksOfSelectedMonth, diff --git a/packages/plugin-timeclock-api/src/graphql/schema.ts b/packages/plugin-timeclock-api/src/graphql/schema.ts index accbd8bcd8d..1bb25de6f85 100644 --- a/packages/plugin-timeclock-api/src/graphql/schema.ts +++ b/packages/plugin-timeclock-api/src/graphql/schema.ts @@ -181,7 +181,8 @@ export const types = ` scheduledShifts: [Shift] timeclocks: [Timeclock] - + requests: [Absence] + totalHoursWorkedSelectedDay: Float totalHoursScheduledSelectedDay: Float totalMinsLateSelectedDay: Float diff --git a/packages/plugin-timeclock-ui/src/components/schedule/ScheduleForm.tsx b/packages/plugin-timeclock-ui/src/components/schedule/ScheduleForm.tsx index 1ebc7e18636..e0fbc4e16a7 100644 --- a/packages/plugin-timeclock-ui/src/components/schedule/ScheduleForm.tsx +++ b/packages/plugin-timeclock-ui/src/components/schedule/ScheduleForm.tsx @@ -17,7 +17,6 @@ import { FlexRow, MarginX, MarginY, - CustomBoxWrapper, SortItem, CustomContainer } from '../../styles'; @@ -37,7 +36,6 @@ import Datetime from '@nateradebaugh/react-datetime'; import { dateFormat, timeFormat } from '../../constants'; import { IUser } from '@erxes/ui/src/auth/types'; import { FormControl } from '@erxes/ui/src/components/form'; -import Box from '@erxes/ui/src/components/Box'; import * as icons from 'react-bootstrap-icons'; diff --git a/packages/plugin-timeclock-ui/src/components/schedule/ScheduleList.tsx b/packages/plugin-timeclock-ui/src/components/schedule/ScheduleList.tsx index 643678e8b6b..8584e77aebb 100644 --- a/packages/plugin-timeclock-ui/src/components/schedule/ScheduleList.tsx +++ b/packages/plugin-timeclock-ui/src/components/schedule/ScheduleList.tsx @@ -24,6 +24,7 @@ import Icon from '@erxes/ui/src/components/Icon'; import Select from 'react-select-plus'; import { Title } from '@erxes/ui-settings/src/styles'; import { ControlLabel, FormGroup } from '@erxes/ui/src/components/form'; +import { confirm } from '@erxes/ui/src/utils'; type Props = { currentUser: IUser; @@ -76,7 +77,8 @@ function ScheduleList(props: Props) { getActionBar, showSideBar, getPagination, - isCurrentUserSupervisor + isCurrentUserSupervisor, + checkDuplicateScheduleShifts } = props; const [selectedScheduleStatus, setScheduleStatus] = useState( @@ -184,6 +186,27 @@ function ScheduleList(props: Props) { router.setParams(history, { scheduleStatus: e.value }); }; + const checkAndApproveSchedule = async ( + scheduleOfMember: ISchedule + ): Promise => { + const checkDuplicateShifts = await checkDuplicateScheduleShifts({ + userIds: [scheduleOfMember.user._id], + shifts: scheduleOfMember.shifts.map(shift => ({ + shiftStart: shift.shiftStart, + shiftEnd: shift.shiftEnd, + scheduleConfigId: shift.scheduleConfigId, + lunchBreakInMins: shift.lunchBreakInMins + })), + userType: 'admin', + checkOnly: true, + status: 'Approved' + }); + + if (!checkDuplicateShifts.length) { + solveSchedule(scheduleOfMember._id, 'Approved'); + } + }; + const actionBarLeft = ( setShowRemoveBtn(false)} style={{ textAlign: 'center' }} > - {showRemoveBtn && ( - - diff --git a/packages/plugin-timeclock-ui/src/components/sidebar/SideBar.tsx b/packages/plugin-timeclock-ui/src/components/sidebar/SideBar.tsx index a93902ed836..33d9b5b3bfa 100644 --- a/packages/plugin-timeclock-ui/src/components/sidebar/SideBar.tsx +++ b/packages/plugin-timeclock-ui/src/components/sidebar/SideBar.tsx @@ -2,7 +2,12 @@ import { router, __ } from '@erxes/ui/src/utils'; import Sidebar from '@erxes/ui/src/layout/components/Sidebar'; import React, { useState } from 'react'; import SelectTeamMembers from '@erxes/ui/src/team/containers/SelectTeamMembers'; -import { FlexColumnCustom, SidebarActions, SidebarHeader } from '../../styles'; +import { + FlexColumnCustom, + FlexRow, + SidebarActions, + SidebarHeader +} from '../../styles'; import { CustomRangeContainer } from '../../styles'; import DateControl from '@erxes/ui/src/components/form/DateControl'; import Button from '@erxes/ui/src/components/Button'; @@ -21,8 +26,9 @@ type Props = { branches: IBranch[]; departments: IDepartment[]; }; -// get 1st of the next Month + const NOW = new Date(); +// get 1st of the next Month const startOfNextMonth = new Date(NOW.getFullYear(), NOW.getMonth() + 1, 1); // get 1st of this month const startOfThisMonth = new Date(NOW.getFullYear(), NOW.getMonth(), 1); @@ -198,9 +204,84 @@ const LeftSideBar = (props: Props) => { return {renderSidebarActions()}; }; + const onDateButtonClick = (type: string) => { + const startOfLastMonth = new Date(NOW.getFullYear(), NOW.getMonth() - 1, 1); + + if (type === 'today') { + setStartDate(NOW); + setEndDate(NOW); + setParams('startDate', NOW); + setParams('endDate', NOW); + } + + if (type === 'last month') { + const endOfLastMonth = new Date(NOW); + + setStartDate(startOfLastMonth); + setParams('startDate', startOfLastMonth); + + // set 1st of current month + endOfLastMonth.setDate(1); + // Subtract 1 day to go back to the last day of the previous month + endOfLastMonth.setDate(endOfLastMonth.getDate() - 1); + + setEndDate(endOfLastMonth); + setParams('endDate', endOfLastMonth); + } + + if (type === 'last week') { + const startOfLastWeek = new Date(NOW); + const endOfLastWeek = new Date(NOW); + + // Set the date to the beginning of the current week (Sunday) + startOfLastWeek.setDate(NOW.getDate() - NOW.getDay()); + + // Subtract 7 days to get to the start of the last week + startOfLastWeek.setDate(startOfLastWeek.getDate() - 6); + + // Set the date to the end of the week (Sunday) + endOfLastWeek.setDate(NOW.getDate() - NOW.getDay() + 7); + + // Subtract 7 days to get to the end of the last week + endOfLastWeek.setDate(endOfLastWeek.getDate() - 7); + + setStartDate(startOfLastWeek); + setParams('startDate', startOfLastWeek); + + setEndDate(endOfLastWeek); + setParams('endDate', endOfLastWeek); + } + }; + return ( + + + + +
Departments