예제 #1
0
        private async void OnNext(JobActivity activity)
        {
            List <string> eligibleActivity = new List <string>()
            {
                JobActivityOperationNames.Cancel,
                JobActivityOperationNames.Delete,
                JobActivityOperationNames.Restore,
                JobActivityOperationNames.Update
            };

            if (activity.ReferenceId != null && eligibleActivity.Any(x => x == activity.Operation))
            {
                var jobUpdatedMessage = new TaskCatMessage()
                {
                    JobID          = activity.JobId,
                    JobHRID        = activity.HRID,
                    ReferenceId    = activity.ReferenceId,
                    RemoteJobStage = RemoteJobStage.UPDATE,
                    RemoteJobState = activity.Value.ToString()
                };

                logger.Info($"Sending update {activity.Value.ToString()} back to polling service for job {activity.HRID}");
                var pushMessageBody = new Message(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(jobUpdatedMessage)));
                await this.pushQueueClient.SendAsync(pushMessageBody);

                logger.Info($"Sent message back to polling service");
            }
        }
예제 #2
0
        public async Task <IHttpActionResult> RestoreJob(string jobId)
        {
            if (string.IsNullOrWhiteSpace(jobId))
            {
                logger.Error("Null or WhiteSpace in JobID: {0}", jobId);
                throw new ArgumentException(nameof(jobId));
            }

            var currentUser = new ReferenceUser(this.User.Identity.GetUserId(), this.User.Identity.GetUserName())
            {
                Name = this.User.Identity.GetUserFullName()
            };

            var job = await repository.GetJob(jobId);

            var result = await repository.RestoreJob(job);

            Task.Factory.StartNew(() =>
            {
                var activity = new JobActivity(job, JobActivityOperationNames.Restore, currentUser);
                activitySubject.OnNext(activity);
            });

            return(Ok(result));
        }
예제 #3
0
        public async Task <IHttpActionResult> CancelJob([FromBody] JobCancellationRequest request)
        {
            if (request == null)
            {
                return(BadRequest("null request encountered"));
            }
            if (!ModelState.IsValid)
            {
                return(BadRequest(ModelState));
            }

            var currentUser = new ReferenceUser(this.User.Identity.GetUserId(), this.User.Identity.GetUserName())
            {
                Name = this.User.Identity.GetUserFullName()
            };

            var job = await repository.GetJob(request.JobId);

            var result = await repository.CancelJob(job, request.Reason);

            Task.Factory.StartNew(() =>
            {
                var activity = new JobActivity(job, JobActivityOperationNames.Cancel, currentUser);
                activitySubject.OnNext(activity);
            });
            return(Ok(result));
        }
예제 #4
0
        public async Task <IHttpActionResult> UpdateOrder([FromUri] string jobId, [FromBody] OrderModel orderModel, [FromUri] string mode = JobUpdateMode.force)
        {
            if (!JobUpdateMode.IsValidUpdateMode(mode))
            {
                throw new ArgumentException(nameof(mode));
            }

            if (orderModel == null)
            {
                return(BadRequest("Null order payload provided"));
            }
            if (!ModelState.IsValid)
            {
                return(BadRequest(ModelState));
            }

            var currentUserId = this.User.Identity.GetUserId();
            var currentUser   = new ReferenceUser(currentUserId, this.User.Identity.GetUserName())
            {
                Name = this.User.Identity.GetUserFullName()
            };

            var job = await repository.GetJob(jobId);

            if (!this.User.IsInRole(RoleNames.ROLE_ADMINISTRATOR) &&
                !this.User.IsInRole(RoleNames.ROLE_BACKOFFICEADMIN) && !this.User.IsInRole(RoleNames.ROLE_ASSET))
            {
                if (orderModel.UserId != null && orderModel.UserId != currentUserId)
                {
                    logger.Error("Invalid Operation: Updating user id {0} is not authorized against user id {1}",
                                 orderModel.UserId, this.User.Identity.GetUserId());

                    throw new InvalidOperationException(string.Format(
                                                            "Updating user id {0} is not authorized against user id {1}",
                                                            orderModel.UserId, this.User.Identity.GetUserId()));
                }
            }
            else if (this.User.IsInRole(RoleNames.ROLE_ASSET) &&
                     !this.User.IsInRole(RoleNames.ROLE_ADMINISTRATOR) &&
                     !this.User.IsInRole(RoleNames.ROLE_BACKOFFICEADMIN))
            {
                if (!job.Assets.Any(x => x.Key == currentUserId))
                {
                    logger.Error("{0} is not an associated asset with this job", currentUserId);
                    throw new UnauthorizedAccessException($"{currentUserId} is not an associated asset with this job");
                }
            }

            var result = await repository.UpdateOrder(job, orderModel, mode);

            Task.Factory.StartNew(() =>
            {
                var activity = new JobActivity(job, JobActivityOperationNames.Update, nameof(Job.Order), currentUser);
                activitySubject.OnNext(activity);
            });

            return(Ok(result));
        }
        public static JobActivity OpenActivityInReviewMode(string sessionId, string jobId, short nodeId, short epc)
        {
            JobActivity activity = new JobActivity();

            activity.Identity        = new KTA_ActivityServices.JobActivityIdentity();
            activity.Identity.JobId  = jobId;
            activity.Identity.NodeId = -1;
            activity.Identity.EmbeddedProcessCount = 0;

            return(activity);
        }
예제 #6
0
        public async Task <IActionResult> Cancel([FromBody] JobActivity activity, string jobID, short NodeID, short EPC)
        {
            if (activity == null)
            {
                activity = await KtaService.TakeActivityAsync(await getUserSession(), jobID, NodeID, EPC);
            }

            await KtaService.CancelActivityAsync(await getUserSession(), activity.Identity);

            return(Ok());
        }
예제 #7
0
        public async Task <IHttpActionResult> Process(string jobid)
        {
            var job = await jobRepository.GetJob(jobid);

            var paymentMethod = service.GetPaymentMethodByKey(job.PaymentMethod);
            var result        = paymentMethod.ProcessPayment(new ProcessPaymentRequest()
            {
                JobId = jobid
            });

            var currentUser = new ReferenceUser(this.User.Identity.GetUserId(), this.User.Identity.GetUserName())
            {
                Name = this.User.Identity.GetUserFullName()
            };

            if (result.Errors != null && result.Errors.Count > 0)
            {
                return(Content(System.Net.HttpStatusCode.BadRequest, result.Errors));
            }

            if (result.Success)
            {
                job.PaymentStatus = result.NewPaymentStatus;
                var jobUpdateResult = await jobRepository.UpdateJob(job);

                Task.Factory.StartNew(() =>
                {
                    var activity = new JobActivity(job, JobActivityOperationNames.Update, nameof(Job.PaymentStatus), currentUser)
                    {
                        Value = result.NewPaymentStatus.ToString()
                    };
                    activitySubject.OnNext(activity);
                });

                if (jobUpdateResult.ModifiedCount > 0)
                {
                    return(Ok());
                }
                else
                {
                    logger.Error("Job update failed for JobId: {0}", jobid);
                    throw new ServerErrorException("job update failed");
                }
            }
            else
            {
                logger.Error("Internal Server Error");
                return(InternalServerError());
            }
        }
예제 #8
0
        public async Task <JobActivity> TakeActivityAsync(string sessionId, string jobId, short nodeId, short epc)
        {
            var _actService = new KTA_ActivityServices.ActivityServiceClient();
            // Create an activity service i.e. can call all methods in the activity service e.g. TakeActivity, Complete, Cancel etc

            // As we are going to take an activity we need to build up the identity of the activity i.e. jobid, node id, embedded process count
            var actIdentity = new KTA_ActivityServices.JobActivityIdentity();

            actIdentity.JobId  = jobId;
            actIdentity.NodeId = nodeId;
            actIdentity.EmbeddedProcessCount = epc;

            var jobAct = new JobActivity();

            var result = await _actService.TakeActivityAsync(sessionId, actIdentity);

            await _actService.CloseAsync();

            return(result);
        }
        public static JobActivity TakeActivity(string sessionId, string jobId, short nodeId, short epc)
        {
            // Create an activity service i.e. can call all methods in the activity service e.g. TakeActivity, Complete, Cancel etc

            // As we are going to take an activity we need to build up the identity of the activity i.e. jobid, node id, embedded process count
            var actIdentity = new KTA_ActivityServices.JobActivityIdentity();

            actIdentity.JobId  = jobId;
            actIdentity.NodeId = nodeId;
            actIdentity.EmbeddedProcessCount = epc;

            var jobAct = new JobActivity();

            jobAct = actService.TakeActivityAsync(sessionId, actIdentity).GetAwaiter().GetResult();


            //didn't take activity successfully...e.g. maybe someone else just got it
            //so we'd want to advise the user by providing a message and then returning to workqueue


            return(jobAct);
        }
예제 #10
0
        /*
         * This is a simple prototype showing how you can loop through all jobs/activities and do something useful with them
         * Unfortunately, there's no way to go straight to jobs or activities - you have to start with accounts, then get jobs, then get activities
         * That makes the JT API only useful for "batch" functionality (something that's run on a regular basis like hourly or daily)
         *
         * This example specifically looks for all activities of a certain type (like Install) that are scheduled
         * (estimated or confirmed or auto-scheduled) for tomorrow.
         *
         * It then sends an SMS reminder (using Twilio.com) and stores the details of that reminder in another JobTracker activity on the job
         * That 2nd activity is useful to let humans know that a reminder was sent AND so this utility can avoid double-sending the reminder
         *
         * THIS IS JUST A PROTOTYPE!!!! Though you might be able to make it useful with relatively little work, there is still work to do to
         * "operationalize" it for your needs. Making your utility operational is beyond the scope of what we can support. If you need help,
         * we recommend reaching out to one of our partners (like Data Bridge) at moraware.com/partners
         *
         */

        //Potential improvements for the reader to implement
        // - add a custom List of Values field in JT that must be a certain value in order for a reminder to be sent
        // - add twilio error handling (requires a web page to call back to)
        //      - collect together and send email?
        // - strip phone numbers of non-numbers and verify phone numbers exactly 10 digits, then add +1 (unless already starts with 1, then check to be 11 digits)
        // - instead of hard-coding rules, put them in a database
        // - operationalize - make it so the code can be run on a regular schedule, perhaps in Windows Azure
        // - put credentials/settings in a better place (typically App.config or Web.config, but it's clearer this way for a prototype)
        //

        static void Main(string[] args)
        {
            // ALL OF THESE DATA NEED TO BE SET FOR YOUR SPECIFIC SITUATION
            ///////////
            var DB          = "";                                 // this should be the part before moraware.net - so if your Moraware database is at patrick.moraware.net, enter "patrick"
            var JTURL       = "https://" + DB + ".moraware.net/";
            var UID         = "";                                 // enter a Moraware user id with API access
            var PWD         = "";                                 // the password for that user id
            var OUTPUTFILE  = "c:\\" + DB + "-SmsPrototype.html"; // decide where you want this
            var INSTALLTYPE = 124;                                // JobActivityTypeId for Install on my test DB - YOURS WILL BE DIFFERENT
                                                                  // To find it, click on the Activity Type (under job > edit settings) and choose View Change Log
                                                                  // this number will be the last part of the URL, id=124 in my sample database
            var REMINDERTYPE = 1283;                              // JobActivityTypeId for the activity you create in JobTracker to show you sent a text
                                                                  // - mine was 1283 but YOURS WILL BE DIFFERENT ...
                                                                  // to find it, first create the Activity Type under Job > Edit Settings ... I called mine "Automatic Reminder"
                                                                  // then click on the Activity Type (still under job settings) and choose View Change Log for Automatic Reminder
                                                                  // this number will be the last part of the URL, id=1283 in my sample database
            var REMINDERSTATUS = 3;                               // JobActivityStatusId for the activity. Complete was 3 for me. It's probably 3 for you as well, but it's not guaranteed

            // Find your Account Sid and Auth Token at twilio.com/user/account
            string ACCOUNTSID = "";
            string AUTHTOKEN  = "";
            var    TWILIOFROM = "";                                   // Comes from your Twilio account. Make it look like this: +16162005000

            var twilio = new TwilioRestClient(ACCOUNTSID, AUTHTOKEN); // consider if this needs to be disposed

            StreamWriter w = File.AppendText(OUTPUTFILE);             // should be wrapped in a using block for production

            w.AutoFlush = true;
            w.WriteLine("*********************<br />");
            w.WriteLine("JT SMS Prototype Start {0}<br />", DateTime.Now);
            w.WriteLine("*********************<br />");

            Connection conn = new Connection(JTURL + "api.aspx", UID, PWD);

            conn.Connect();

            var      tomorrow = DateTime.Now.AddDays(1);
            DateTime startDate;

            var af       = new AccountFilter(Moraware.JobTrackerAPI4.Account.AccountStatusType_Enum.Active);
            var accounts = conn.GetAccounts(af, new PagingOptions(0, 9999, true)); // if you have a huge number of accounts (> 9999), you'll have to page

            foreach (var account in accounts)
            {
                var jobs = conn.GetJobsForAccount(account.AccountId, true);
                foreach (var job in jobs)
                {
                    Console.WriteLine("Checking {0}: {1}", job.JobId, job.JobName);
                    if (job.Address == null)
                    {
                        continue;
                    }
                    if (String.IsNullOrEmpty(job.Address.Cell))
                    {
                        continue;
                    }
                    var installsToConfirm = new List <JobActivity>();
                    var confirmations     = new Dictionary <int, JobActivity>();

                    var activities = conn.GetJobActivities(job.JobId, true, false);
                    foreach (var activity in activities)
                    {
                        if (activity.StartDate == null)
                        {
                            continue;
                        }
                        else
                        {
                            startDate = (DateTime)activity.StartDate;
                        }

                        // CHECK RULES HERE - there's no magic formula ... your rules can be completely different
                        if (activity.JobActivityTypeId == INSTALLTYPE &&                                                                     // Install
                            (activity.JobActivityStatusId == 1 || activity.JobActivityStatusId == 4 || activity.JobActivityStatusId == 2) && // Estimate or Auto-Schedule or Confirmed
                            // these status id's are not guaranteed to be the same for your database - use id's that are appropriate for you
                            startDate.Date == tomorrow.Date)                                                                                 // Starts tomorrow
                        {
                            installsToConfirm.Add(activity);
                        }

                        // CHECK FOR EXISTING REMINDERS HERE (don't double send)
                        if (activity.JobActivityTypeId == REMINDERTYPE)
                        {
                            var match = Regex.Match(activity.Notes, @"Reference activity: (\d+)");
                            if (match.Success)
                            {
                                int refId;
                                try
                                {
                                    refId = Int32.Parse(match.Groups[1].Value);
                                    confirmations.Add(refId, activity);
                                }
                                catch (Exception)
                                {
                                    // send error message somewhere?
                                }
                            }
                        }
                    }

                    foreach (var activity in installsToConfirm)
                    {
                        if (confirmations.ContainsKey(activity.JobActivityId))
                        {
                            //we've already created a notification for this activity, so don't resend it
                            continue;
                        }

                        Console.WriteLine("Create a notification for job id {0}, activity id {1}, type id {2}, status id {3}",
                                          activity.JobId, activity.JobActivityId, activity.JobActivityTypeId, activity.JobActivityStatusId);

                        // Here's the message - edit to suit your needs!
                        String msg = String.Format("Reminder, you have a countertop installation scheduled for {0:M/dd} at {1:H:mm tt}. If this information is incorrect, please call xxx-xxx-xxxx as soon as possible. Thank you!",
                                                   activity.StartDate, activity.ScheduledTime);

                        var cell = job.Address.Cell; // NOTE - you should strip out text and validate this

                        String notes = String.Format("Type: SMS\nNumber: {0}\nReference activity: {1}\nMessage: {2}",
                                                     cell, activity.JobActivityId, msg);

                        Console.WriteLine(notes);

                        var reminder = new JobActivity(activity.JobId, REMINDERTYPE, REMINDERSTATUS);
                        foreach (var phase in activity.JobPhases)
                        {
                            reminder.JobPhases.Add(phase);
                        }
                        reminder.Notes = notes;
                        conn.CreateJobActivity(reminder); // once this is added, the notification won't be sent again ... so if twilio fails, this won't retry

                        // FINALLY, actually send the text
                        var message = twilio.SendMessage(TWILIOFROM, cell, msg, ""); // last param is a callback to handle errors/status ... needs to call a web service (not implemented here)
                    }
                }
            }

            Console.WriteLine("Press any key to continue ...");
            Console.ReadKey();

            w.Close();
            conn.Disconnect();
        }
예제 #11
0
 private void OnNext(JobActivity activity)
 {
     // TODO: Just saving it on the database for now
     // Log activity here
     dbcontext.JobActivityCollection.InsertOne(activity);
 }
예제 #12
0
        public async Task <IHttpActionResult> Tag([FromUri] string jobId, [FromBody] JsonPatchDocument <Job> tagPatch)
        {
            if (tagPatch == null)
            {
                throw new ArgumentException($"request body is null");
            }

            if (!(User.IsInRole(RoleNames.ROLE_BACKOFFICEADMIN) || User.IsInRole(RoleNames.ROLE_ADMINISTRATOR)))
            {
                logger.Error("User {0} is not authorized to do this operation", User.Identity.Name);
                throw new UnauthorizedAccessException($"User {User.Identity.Name} is not authorized to do this operation");
            }

            var unsupportedOp = tagPatch.Operations.Where(x =>
                                                          x.OperationType == OperationType.Move ||
                                                          x.OperationType == OperationType.Test ||
                                                          x.OperationType == OperationType.Copy);


            if (unsupportedOp.Count() > 0)
            {
                logger.Error($"Unsupported operation type {unsupportedOp.First()}");
                throw new NotSupportedException($"Unsupported operation type {unsupportedOp.First()}");
            }

            if (!tagPatch.Operations.All(x => x.path.StartsWith("/Tags/")))
            {
                logger.Error("Patch operation not supported any fields except Tags");
                throw new NotSupportedException("Patch operation not supported any fields except Tags");
            }

            // Check tags meant for addition and replacements.
            var tagsToCheckFor = tagPatch.Operations.Where(
                x => x.OperationType == OperationType.Add || x.OperationType == OperationType.Replace)
                                 .Select(x => x.value.ToString());

            // The question here might say why I didn't put this method in the repository
            // We don't really need this method to be reused, or at least don't see a potential
            // use case yet. When we do, we would make it a reusable method

            var nonExistentTags =
                tagsToCheckFor.Except(
                    dataTagService.Collection.Find(
                        Builders <DataTag> .Filter.Or(tagsToCheckFor.Select(x => Builders <DataTag> .Filter.Eq(y => y.Value, x))))
                    .ToList()
                    .Select(x => x.Value));

            if (nonExistentTags.Count() > 0)
            {
                throw new NotSupportedException($"tag {nonExistentTags.First()} is invalid");
            }

            // get current user
            var currentUser = new ReferenceUser(this.User.Identity.GetUserId(), this.User.Identity.GetUserName())
            {
                Name = this.User.Identity.GetUserFullName()
            };

            var job = await repository.GetJob(jobId);

            job.Tags = job.Tags == null ? new List <string>() : job.Tags;
            tagPatch.ApplyTo(job);
            job.Tags         = job.Tags.Distinct().ToList();
            job.Tags         = job.Tags.Count == 0 ? null : job.Tags;
            job.ModifiedTime = DateTime.UtcNow;

            // update job with tag
            var jobUpdateresult = await repository.UpdateJob(job);

            var result = new UpdateResult <Job>(jobUpdateresult.MatchedCount, jobUpdateresult.ModifiedCount, job);

            // log job activity
            var activity = new JobActivity(job, JobActivityOperationNames.Update, nameof(Job.Tags), currentUser);

            activitySubject.OnNext(activity);

            return(Ok(result));
        }
예제 #13
0
        public async Task <IHttpActionResult> Update([FromUri] string jobId, [FromUri] string taskId, [FromBody] JsonPatchDocument <JobTask> taskPatch, [FromUri] bool updatedValue = false)
        {
            if (taskPatch == null)
            {
                logger.Error($"{nameof(taskPatch)} is null");
                throw new ArgumentNullException(nameof(taskPatch));
            }

            if (taskPatch.Operations.Any(x => x.OperationType != Marvin.JsonPatch.Operations.OperationType.Replace))
            {
                logger.Debug(taskPatch.Operations.ToString());
                logger.Error("Operations except replace is not supported");
                throw new NotSupportedException("Operations except replace is not supported");
            }

            // INFO: This is ghetto, need to do it in a better way, may be write extension methods for JsonPatchDocument
            List <string> allowedPaths = new List <string>();

            allowedPaths.Add(nameof(JobTask.AssetRef));
            allowedPaths.Add(nameof(JobTask.State));

            if (!taskPatch.Operations.All(x => allowedPaths.Any(a => x.path.EndsWith(a))))
            {
                logger.Error("Patch operation not supported on one or more paths");
                throw new NotSupportedException("Patch operation not supported on one or more paths");
            }

            var currentUser = new ReferenceUser(this.User.Identity.GetUserId(), this.User.Identity.GetUserName())
            {
                Name = this.User.Identity.GetUserFullName()
            };

            var job = await repository.GetJob(jobId);

            var activities = new List <JobActivity>();

            job.PropertyChanged += (sender, eventArgs) =>
            {
                JobActivity jobChangeActivity = null;
                switch (eventArgs.PropertyName)
                {
                case nameof(Job.State):
                    jobChangeActivity = new JobActivity(job, JobActivityOperationNames.Update, nameof(Job.State), currentUser)
                    {
                        Value = (sender as Job).State.ToString()
                    };
                    activities.Add(jobChangeActivity);
                    break;
                }
            };

            var result = await repository.UpdateJobTaskWithPatch(job, taskId, taskPatch);

            result.SerializeUpdatedValue = updatedValue;

            var updatedTask = result.UpdatedValue.Tasks.First(x => x.id == taskId);

            var taskUpdates = new List <JobActivity>();

            foreach (var op in taskPatch.Operations)
            {
                var taskActivity = new JobActivity(
                    result.UpdatedValue,
                    JobActivityOperationNames.Update,
                    op.path.Substring(1),
                    currentUser,
                    new ReferenceActivity(taskId, updatedTask.Type))
                {
                    Value = op.value.ToString()
                };
                taskUpdates.Add(taskActivity);
            }

            activities.InsertRange(0, taskUpdates);

            Task.Factory.StartNew(() =>
            {
                foreach (var activity in activities)
                {
                    activitySubject.OnNext(activity);
                }
            });

            return(Ok(result));
        }