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"); } }
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)); }
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)); }
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); }
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()); }
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()); } }
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); }
/* * 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(); }
private void OnNext(JobActivity activity) { // TODO: Just saving it on the database for now // Log activity here dbcontext.JobActivityCollection.InsertOne(activity); }
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)); }
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)); }