-
Notifications
You must be signed in to change notification settings - Fork 0
Capture amended and enhanced accounting #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| <?xml version="1.0" encoding="utf-8" ?> | ||
| <!-- The version is automatically increased when the application is published from the app generator --> | ||
| <app version="504"/> | ||
| <app version="517"/> |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| using System; | ||
| using System.Data.SqlClient; | ||
| using System.Threading; | ||
| using System.Configuration; | ||
| using System.Web.Hosting; | ||
|
|
||
| public static class QueueWorker | ||
| { | ||
| private static Timer _timer; | ||
| private static bool _isRunning; | ||
| private static bool _initialized; | ||
| private static readonly object _lock = new object(); | ||
|
|
||
| // How often to poll the queue | ||
| private static readonly TimeSpan _interval = TimeSpan.FromSeconds(30); | ||
|
|
||
| public static void Start() | ||
| { | ||
| // Prevent duplicate initialization | ||
| if (_initialized) | ||
| return; | ||
|
|
||
| lock (_lock) | ||
| { | ||
| if (_initialized) | ||
| return; | ||
|
|
||
| _initialized = true; | ||
|
|
||
| // Use HostingEnvironment.QueueBackgroundWorkItem to survive short recycles | ||
| HostingEnvironment.QueueBackgroundWorkItem(ct => | ||
| { | ||
| _timer = new Timer(ProcessQueue, null, TimeSpan.Zero, _interval); | ||
| }); | ||
|
|
||
| Log("QueueWorker started at " + DateTime.Now); | ||
| } | ||
| } | ||
|
|
||
| private static void ProcessQueue(object state) | ||
| { | ||
| if (_isRunning) return; | ||
| _isRunning = true; | ||
|
|
||
| try | ||
| { | ||
| string connStr = ConfigurationManager.ConnectionStrings["zLearnHub"].ConnectionString; | ||
| using (var conn = new SqlConnection(connStr)) | ||
| using (var cmd = new SqlCommand("EXEC usp_ProcessSyncQueue", conn)) | ||
| { | ||
| conn.Open(); | ||
| cmd.ExecuteNonQuery(); | ||
| } | ||
|
|
||
| Log("Processed SyncQueue at " + DateTime.Now); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Log("Error in QueueWorker: " + ex.Message); | ||
| } | ||
| finally | ||
| { | ||
| _isRunning = false; | ||
| } | ||
| } | ||
|
|
||
| private static void Log(string message) | ||
| { | ||
| try | ||
| { | ||
| string path = HostingEnvironment.MapPath("~/App_Data/queueworker.log"); | ||
| System.IO.File.AppendAllText(path, message + Environment.NewLine); | ||
| } | ||
| catch | ||
| { | ||
| // ignore logging errors | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Data.SqlClient; | ||
| using System.Linq; | ||
| using System.Web; | ||
| using System.Web.Script.Serialization; | ||
| using zLearnHub.Data; | ||
| using zLearnHub.Services; | ||
|
|
||
| namespace zLearnHub.Helpers | ||
| { | ||
|
|
||
|
|
||
|
|
||
| /// <summary> | ||
| /// Summary description for SyncQueueHelper | ||
| /// </summary> | ||
| public static class SyncQueueHelper | ||
| { | ||
| /// <summary> | ||
| /// Enqueue any entity into the SyncQueue table for background processing. | ||
| /// </summary> | ||
| /// <param name="entityName">The name of the entity/controller</param> | ||
| /// <param name="entityId">Primary key of the entity</param> | ||
| /// <param name="action">Action performed: Insert, Update, Delete</param> | ||
| /// <param name="entityValues">The row values to serialize as payload</param> | ||
| /// <param name="targetSystem">Destination system, e.g., LMS, ERP</param> | ||
| /// | ||
|
|
||
| public static void Enqueue(string entityName, int entityId, string action, string entityValues, string targetSystem) | ||
| { | ||
| // Serialize the entity values to JSON | ||
| string payload = new JavaScriptSerializer().Serialize(entityValues); | ||
|
|
||
| // Use SqlText helper provided by Code On Time | ||
| SqlText.ExecuteNonQuery(@" | ||
| INSERT INTO SyncQueue (EntityName, EntityID, Action, Payload, TargetSystem, CreatedAt, Status) | ||
| VALUES (@EntityName, @EntityID, @Action, @Payload, @TargetSystem, GETDATE(), 'Pending') | ||
| ", | ||
| new | ||
| { | ||
| EntityName = entityName, | ||
| EntityID = entityId, | ||
| Action = action, | ||
| Payload = payload, | ||
| TargetSystem = targetSystem | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -104,7 +104,7 @@ protected override void VirtualizeController(string controllerName) | |
| } | ||
|
|
||
| //detail access controll formulation | ||
| if ((controllerName == "fee_collection_transaction" || controllerName == "fee_collection_transaction_itemised" || controllerName == "znfee_collection_transaction_itemised_detail") && !UserIsInRole("Administrators")) | ||
| if ((controllerName == "fee_collection_transaction" || controllerName == "fee_collection_transaction_itemised" || controllerName == "znfee_collection_transaction_itemised_detail" || controllerName == "fee_collection_transaction_extra") && !UserIsInRole("Administrators")) | ||
| { | ||
| // make the controller read-only by removing editing actions | ||
| NodeSet("view[@id='grid1']").SelectActions("Delete").Delete(); | ||
|
|
@@ -119,6 +119,11 @@ protected override void VirtualizeController(string controllerName) | |
|
|
||
| } | ||
|
|
||
| else if (controllerName == "Contact" && UserIsInRole("AccountManagers")) | ||
| { | ||
| NodeSet("view[@id='grid1']").SelectView("grid1").SelectActions("New, Edit, Delete").Delete(); | ||
| } | ||
|
|
||
| if ((controllerName == "WorkBenchTheStock" || controllerName == "MovementMode" && !UserIsInRole("Administrators"))) | ||
| { | ||
| // make the controller read-only by removing editing actions | ||
|
|
@@ -133,16 +138,19 @@ protected override void VirtualizeController(string controllerName) | |
| NodeSet().SelectAction("Delete").Delete(); | ||
| } | ||
|
|
||
| if (ControllerName == "fee_collection_transaction_extra") | ||
| { | ||
| NodeSet().SelectAction("Delete").Delete(); | ||
|
|
||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
||
| protected override void BeforeSqlAction(ActionArgs args, ActionResult result) | ||
| { | ||
| base.BeforeSqlAction(args, result); | ||
| // Check if the request is trying to delete a payment in FeeCollections | ||
| if (args.Controller == "fee_collection_transaction" && args.CommandName == "Delete") | ||
| if (args.Controller == "fee_collection_transaction_extra" && args.CommandName == "Delete") | ||
| { | ||
| if (!UserIsInRole("Administrators, HeadTeachers")) | ||
| { | ||
|
|
@@ -153,6 +161,22 @@ protected override void BeforeSqlAction(ActionArgs args, ActionResult result) | |
| } | ||
| } | ||
|
|
||
| // Always call base AFTER your logic, not before — so your validation can cancel the action if needed | ||
| if (args.Controller == "fee_collection_transaction_extra" && args.CommandName.Equals("Delete", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| // Check if the current user has one of the allowed roles | ||
| if (!(UserIsInRole("Administrators") || UserIsInRole("HeadTeachers"))) | ||
| { | ||
| // Block the delete command | ||
| result.Canceled = true; | ||
|
|
||
| // Show a clear message to the user | ||
| result.ShowAlert("You do not have permission to delete payment records. Escalate to a higher authority."); | ||
| } | ||
| } | ||
|
|
||
| base.BeforeSqlAction(args, result); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Base BeforeSqlAction Called Twice, Validation Order IncorrectThe BeforeSqlAction method calls base.BeforeSqlAction twice (lines 151 and 178). The first call at line 151 happens before any validation logic, and the second call at line 178 happens after the validation logic for fee_collection_transaction_extra. This is incorrect because base.BeforeSqlAction should only be called once, and it should be called AFTER all custom validation logic has been executed. The comment at line 164 even states "Always call base AFTER your logic" but then the code violates this principle by calling base first at line 151. This could cause validation logic to be bypassed or executed in the wrong order. |
||
|
|
||
| if (args.Controller == "account_general_ledger" && args.CommandName == "Delete") | ||
| { | ||
| if (!UserIsInRole("Administrators, HeadTeachers")) | ||
|
|
@@ -181,11 +205,25 @@ protected override void BeforeSqlAction(ActionArgs args, ActionResult result) | |
| if (status == "Received") | ||
| { | ||
| //throw new Exception("Received purchase orders cannot be edited."); | ||
| result.ShowAlert("You do not have permission to edit financial records. Please discuss with administrative head."); | ||
| result.ShowAlert("You do not have permission to edit PO record in already received. Please discuss with administrative head."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (args.Controller == "Contact" && args.CommandName == "New") | ||
| { | ||
| if (UserIsInRole("AccountManagers")) | ||
| { | ||
|
|
||
| //var status = Convert.ToString(SelectFieldValue("Status")); | ||
| //if (status == "Received") | ||
| //{ | ||
| //throw new Exception("Received purchase orders cannot be edited."); | ||
| result.ShowAlert("You do not have permission to create new student record. Please discuss with administrative head."); | ||
| //} | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
|
|
||
|
|
@@ -210,7 +248,7 @@ protected override void AfterSqlAction(ActionArgs args, ActionResult result) | |
| } | ||
| } | ||
|
|
||
|
|
||
| [ControllerAction("StockTransactions", "Insert, Update, Calculate", ActionPhase.After)] | ||
| public void AddOrUpdateStockTransaction() | ||
| { | ||
|
|
@@ -382,30 +420,87 @@ public void process_fee_collection_transaction() | |
| } | ||
|
|
||
| [ControllerAction("fee_collection_transaction", "Custom, ProcessRefund", ActionPhase.After)] | ||
| public void ProcessFeeRefund(int OriginalTransactionID, decimal RefundAmountToProcess) | ||
| //public void ProcessFeeRefund(int OriginalTransactionID, decimal RefundAmountToProcess) | ||
| public void ProcessFeeRefund(int OriginalTransactionID, decimal RefundAmountToProcess, string RefundReason, string NameOfRecepient) | ||
| { | ||
|
|
||
| // Get connection string from configuration | ||
| string connectionString = ConfigurationManager.ConnectionStrings["zLearnHub"].ConnectionString; | ||
| string ActionedBy = Context.User.Identity.Name; | ||
|
|
||
| try { | ||
| using (SqlConnection connection = new SqlConnection(connectionString)) | ||
| { | ||
| connection.Open(); | ||
| using (SqlCommand command = new SqlCommand("usp_ops_p9_create_fee_refund_transaction_revised_2", connection)) | ||
| using (SqlCommand command = new SqlCommand("usp_ops_auto_p4_post_fee_transaction_refund", connection)) | ||
| { | ||
| command.CommandType = System.Data.CommandType.StoredProcedure; | ||
| command.Parameters.AddWithValue("@TransactionID", OriginalTransactionID); | ||
| command.Parameters.AddWithValue("@RefundAmountToProcess", RefundAmountToProcess); | ||
| command.Parameters.AddWithValue("@RefundReason", RefundReason); | ||
| command.Parameters.AddWithValue("@NameOfRecepient", NameOfRecepient); | ||
| // ADD this line to pass the user's name to the stored procedure | ||
| command.Parameters.AddWithValue("@ActionedBy", ActionedBy); | ||
| command.ExecuteNonQuery(); | ||
| } | ||
| } | ||
|
|
||
| // Optional: Add a user notification | ||
| Result.Canceled = true; | ||
| // 2. Refresh parent/related views (This should be placed AFTER setting Canceled=true or in a separate hook if needed, but often works here) | ||
| Result.Refresh(); | ||
| Result.RefreshChildren(); | ||
| Result.ShowAlert("Refund processed successfully."); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Result.Canceled = false; // Keep the screen open on error | ||
| Result.ShowAlert("ERROR processing refund: " + ex.Message); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| [ControllerAction("fee_collection_transaction_extra", "Custom, ProcessRefundTransaction", ActionPhase.After)] | ||
| //public void ProcessFeeRefund(int OriginalTransactionID, decimal RefundAmountToProcess) | ||
| public void ProcessMultiFeeRefund(int OriginalTransactionID, decimal RefundAmountToProcess, string RefundReason, string NameOfRecepient) | ||
| { | ||
|
|
||
| // Get connection string from configuration | ||
| string connectionString = ConfigurationManager.ConnectionStrings["zLearnHub"].ConnectionString; | ||
| string ActionedBy = Context.User.Identity.Name; | ||
|
|
||
| try | ||
| { | ||
| using (SqlConnection connection = new SqlConnection(connectionString)) | ||
| { | ||
| connection.Open(); | ||
| using (SqlCommand command = new SqlCommand("usp_ops_auto_p4_post_fee_transaction_refund", connection)) | ||
| { | ||
| command.CommandType = System.Data.CommandType.StoredProcedure; | ||
| command.Parameters.AddWithValue("@TransactionID", OriginalTransactionID); | ||
| command.Parameters.AddWithValue("@RefundAmountToProcess", RefundAmountToProcess); | ||
| command.Parameters.AddWithValue("@RefundReason", RefundReason); | ||
| command.Parameters.AddWithValue("@NameOfRecepient", NameOfRecepient); | ||
| // ADD this line to pass the user's name to the stored procedure | ||
| command.Parameters.AddWithValue("@ActionedBy", ActionedBy); | ||
| command.ExecuteNonQuery(); | ||
| } | ||
| } | ||
|
|
||
| // Optional: Add a user notification | ||
| Result.Canceled = true; | ||
| // 2. Refresh parent/related views (This should be placed AFTER setting Canceled=true or in a separate hook if needed, but often works here) | ||
| Result.Refresh(); | ||
| Result.RefreshChildren(); | ||
| Result.ShowAlert("Refund processed successfully."); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Result.Canceled = false; // Keep the screen open on error | ||
| Result.ShowAlert("ERROR processing refund: " + ex.Message); | ||
| } | ||
| } | ||
|
|
||
| [ControllerAction("ProcessPurchaseOrder", "Insert, Update, Calculate", ActionPhase.After)] | ||
| public void process_purchase_order_from_order_details(int purchaseOrderID) | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Redundant Delete Validation Blocks Cause Confusion
The delete validation for fee_collection_transaction_extra is duplicated with identical logic in two consecutive if blocks (lines 153-161 and lines 165-176). Both check the same condition (controller == "fee_collection_transaction_extra" && commandName == "Delete") and both check user roles. The first block only shows an alert without canceling the action, while the second block properly cancels the action with result.Canceled = true. This means the first block is redundant and could cause confusion. Only one of these blocks should exist, and it should properly cancel the action.