diff --git a/app/controllers/timeclock.js b/app/controllers/timeclock.js index a753218..f287b7b 100644 --- a/app/controllers/timeclock.js +++ b/app/controllers/timeclock.js @@ -6,6 +6,7 @@ var _ = require('lodash'); var Promise = require('bluebird'); var TimeClockSpan = mongoose.model('TimeClockSpan'); var TimeClockException = mongoose.model('TimeClockException'); +var TimeSheet = mongoose.model('TimeSheet'); var Workorder = mongoose.model('Workorder'); var email = require('../util/email'); var config = require('../../config/config')['prod']; @@ -33,6 +34,35 @@ function MultipleSpansError(spans) { } MultipleSpansError.prototype = Object.create(Error.prototype); +function hasTechApprovedPreviousWeek(user, day) { + var startOfWeek = day.clone().startOf('week').subtract(1, 'week').toDate(); + var endOfWeek = day.clone().endOf('week').subtract(1, 'week').toDate(); + + return Promise + .props({ + spans: TimeClockSpan.count({ + start: { + '$gte': startOfWeek, + '$lte': endOfWeek + }, + user: user + }), + timesheet: TimeSheet.findOne({ + week: startOfWeek, + user: user + }) + }) + .then((props) => { + console.log('Spans last week: ', props.spans); + console.log('Previous timesheet', props.timesheet); + + var approved = props.spans == 0 || (props.timesheet && props.timesheet.approved); + console.log('Status: ', approved); + + return approved; + }); +} + function findUserSpans(user, day) { var startOfDay = day.clone().startOf('day').toDate(); var endOfDay = day.clone().endOf('day').toDate(); @@ -523,6 +553,16 @@ function validateDate(req, field) { return Promise.resolve(date); } +function sendApprovalRequiredResponse(res, date) { + date = moment().startOf('week').subtract(1, 'week'); + res.json({ + tasks: [{ + type: 'approveTimesheet', + week: date.format('YYYY-MM-DD') + }] + }); +} + module.exports = function () { return { index: function (req, res) { @@ -531,12 +571,20 @@ module.exports = function () { var today = moment(); - var spans = findUserSpans(req.user.id, today); - var workorders = findUserWorkorders(req.user, today); + hasTechApprovedPreviousWeek(req.user.id, today) + .then(approved => { + console.log('Approved? ', approved); + if (!approved) { + return sendApprovalRequiredResponse(res, today); + } - Promise.join(spans, workorders, handleStatusRequest) - .then(responseHandler(res)) - .catch(errorHandler(res)); + var spans = findUserSpans(req.user.id, today); + var workorders = findUserWorkorders(req.user, today); + + Promise.join(spans, workorders, handleStatusRequest) + .then(responseHandler(res)) + .catch(errorHandler(res)); + }); }, clockIn: function (req, res) { @@ -545,34 +593,50 @@ module.exports = function () { var today = moment(); - var params = validateClockRequest(req); - var spans = findUserSpans(req.user.id, today); - var workorders = findUserWorkorders(req.user, today); + hasTechApprovedPreviousWeek(req.user.id, today) + .then(approved => { - Promise.join(params, req.user, spans, workorders, today, handleClockInRequest) - .then(responseHandler(res)) - .catch(errorHandler(res)); + if (!approved) { + return sendApprovalRequiredResponse(res, today); + } + + var params = validateClockRequest(req); + var spans = findUserSpans(req.user.id, today); + var workorders = findUserWorkorders(req.user, today); + + Promise.join(params, req.user, spans, workorders, today, handleClockInRequest) + .then(responseHandler(res)) + .catch(errorHandler(res)); + }); }, clockOut: function (req, res) { //TODO: Check to make sure user has a valid timesheet. - Promise - .props({ - id: req.body.id, - date: moment(), - notes: req.body.notes, - reason: req.body.reason, - type: req.body.type - }) - .then((params) => { - var spans = findUserSpans(req.user.id, params.date); - var workorders = findUserWorkorders(req.user, params.date); - return Promise.join(params, req.user, spans, workorders, params.date, handleClockOutRequest); - }) - .then(responseHandler(res)) - .catch(errorHandler(res)); + hasTechApprovedPreviousWeek(req.user.id, today) + .then(approved => { + + if (!approved) { + return sendApprovalRequiredResponse(res, today); + } + + Promise + .props({ + id: req.body.id, + date: moment(), + notes: req.body.notes, + reason: req.body.reason, + type: req.body.type + }) + .then((params) => { + var spans = findUserSpans(req.user.id, params.date); + var workorders = findUserWorkorders(req.user, params.date); + return Promise.join(params, req.user, spans, workorders, params.date, handleClockOutRequest); + }) + .then(responseHandler(res)) + .catch(errorHandler(res)); + }); }, spansForUser: function (req, res) { diff --git a/app/controllers/timesheet.js b/app/controllers/timesheet.js index 350994c..e9d27a6 100644 --- a/app/controllers/timesheet.js +++ b/app/controllers/timesheet.js @@ -6,6 +6,7 @@ var _ = require('lodash'); var Promise = require('bluebird'); var TimeClockSpan = mongoose.model('TimeClockSpan'); var Workorder = mongoose.model('Workorder'); +var TimeSheet = mongoose.model('TimeSheet'); var User = mongoose.model('User'); var db = require('./db'); @@ -140,21 +141,28 @@ function buildReport(spans) { .then(findWorkordersById) .then(indexById); - const usersById = spans - .then(extractIds('user')) + const userIds = spans + .then(extractIds('user')); + + const usersById = userIds .then(findUsersById) .then(indexById); - return Promise.join(spans, workordersById, usersById, generateSummary); + const timesheetsByUser = userIds + .then(findTimesheetsByUser) + .then(indexByUser); + + return Promise.join(spans, workordersById, usersById, timesheetsByUser, generateSummary); } -function generateSummary(spans, workordersById, usersById) { +function generateSummary(spans, workordersById, usersById, timesheetsByUser) { var results = {}; function fetchOrCreateUserRecord(userId) { var record = results[userId]; if (!record) { var user = usersById[userId]; + var timesheet = timesheetsByUser[userId]; record = results[userId] = { user: { @@ -162,6 +170,7 @@ function generateSummary(spans, workordersById, usersById) { name: user.name }, hasOpenSpans: false, + approved: !!(timesheet && timesheet.approved), workorders: {}, spans: {}, clockedTime: 0, @@ -264,6 +273,10 @@ function indexById(data) { return _.indexBy(data, 'id') } +function indexByUser(data) { + return _.indexBy(data, 'user') +} + function findWorkordersById(ids) { const query = { _id: { @@ -287,6 +300,16 @@ function findUsersById(ids) { return User.find(query).exec(); } +function findTimesheetsByUser(ids) { + const query = { + user: { + $in: ids + } + }; + + return TimeSheet.find(query).exec(); +} + function findAllSpansForWeek(week) { var startOfWeek = week.clone().startOf('week'); var endOfWeek = week.clone().endOf('week'); @@ -320,6 +343,33 @@ function findUserSpansForWeek(id, week) { return TimeClockSpan.find(query).exec(); } +function approvalHandler(req, res) { + return (params) => { + params.week = params.week.toDate(); + + var query = { + user: params.id, + week: params.week + }; + + req.db.TimeSheet.findOne(query) + .then(timesheet => { + + if (!timesheet) { + timesheet = new req.db.TimeSheet({ + user: params.id, + week: params.week + }); + } + + timesheet.approved = true; + timesheet.approvedOn = new Date(); + + res.promise(timesheet.save()); + }); + }; +} + module.exports = function () { return { daysWorked: function (req, res) { @@ -365,6 +415,15 @@ module.exports = function () { .then(userSummaryHandler) .then(responseHandler(res)) .catch(errorHandler(res)); + }, + approve: function(req, res) { + Promise + .props({ + id: validateUserId(req), + week: validateWeek(req) + }) + .then(approvalHandler(req, res)) + .catch(errorHandler(res)); } } }; diff --git a/app/model/timeSheet.js b/app/model/timeSheet.js new file mode 100644 index 0000000..717e534 --- /dev/null +++ b/app/model/timeSheet.js @@ -0,0 +1,42 @@ +"use strict"; + +var moment = require('moment'); +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var ObjectId = Schema.ObjectId; + +var schema = new Schema({ + user: { + type: ObjectId, + ref: 'User', + required: true + }, + + week: { + type: Date, + required: true + }, + + approved: { + type: Boolean, + default: false, + required: true + }, + + approvedOn: { + type: Date, + required: function() { + return this.approved === true; + } + } +}); + +schema.pre('save', function (next) { + if (!this.approved) { + this.approvedOn = undefined; + } + + next(); +}); + +module.exports = mongoose.model('TimeSheet', schema); diff --git a/app/routes/spans.js b/app/routes/spans.js index 019000e..60047b8 100644 --- a/app/routes/spans.js +++ b/app/routes/spans.js @@ -71,7 +71,7 @@ function index(req, res) { } /** - * POST /api/spans/:user_id + * POST /api/spans/:span_id */ function update(req, res) { req.check('id').isMongoId(); diff --git a/config/db.js b/config/db.js index 08e1e00..5c9992c 100644 --- a/config/db.js +++ b/config/db.js @@ -13,6 +13,7 @@ const models = [ 'TestRun', 'TimeClockException', 'TimeClockSpan', + 'TimeSheet', 'User', 'Workorder' ]; diff --git a/config/routes.js b/config/routes.js index 33e5f77..dfa144d 100644 --- a/config/routes.js +++ b/config/routes.js @@ -107,6 +107,7 @@ module.exports = function (app, auth, piler, calendar, directory, config) { app.get('/api/timesheet/summary', timesheet.summary); app.get('/api/timesheet/:user_id/daysWorked', timesheet.daysWorked); app.get('/api/timesheet/:user_id/summary', timesheet.userSummary); + app.post('/api/timesheet/:user_id/approve', timesheet.approve); var pms = require('../app/controllers/pms'); app.get('/api/pms', pms.index);