Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DataAquarium.Project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ Created On Date = CreatedDate</auditing>
<layout name="Classic" menuStyle="MultiLevel" />
<theme name="Aquarium" />
<publish mode="FileSystem">
<fileSystem external="false">{"path":"C:\\Users\\adub\\Documents\\Code OnTime\\Publish\\LearnHub\\V08920250904","previewUrl":"","webConfigCustomizations":{"DebugMode":"false","SMTPDelivery":"network","SMTPFrom":"b.adu@dekakis.com","SMTPDefaultCredentials":"false","SMTPNetworkUserName":"b.adu@dekakis.com","SMTPNetworkPassword":"nanakillo,1970","SMTPEnableSSL":"true","SMTPNetworkPort":"465","SMTPClientDomain":"mail.ntchosting.com"}}</fileSystem>
<fileSystem external="false">{"path":"C:\\Users\\adub\\Documents\\Code OnTime\\Publish\\LearnHub\\V08920251018","previewUrl":"","webConfigCustomizations":{"DebugMode":"false","CustomErrors":"Off","SMTPDelivery":"network","SMTPFrom":"b.adu@dekakis.com","SMTPDefaultCredentials":"false","SMTPNetworkUserName":"b.adu@dekakis.com","SMTPNetworkPassword":"nanakillo,1970","SMTPEnableSSL":"true","SMTPNetworkPort":"465","SMTPClientDomain":"mail.ntchosting.com"}}</fileSystem>
</publish>
</project>
2 changes: 1 addition & 1 deletion DataAquarium.Version.xml
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"/>
608 changes: 606 additions & 2 deletions Sync.LT13085-Adub.xml

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions app/App_Code/Helpers/QueueWorker.cs
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
}
}
}
51 changes: 51 additions & 0 deletions app/App_Code/Helpers/SyncQueueHelper.cs
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
});
}
}

}
18 changes: 18 additions & 0 deletions app/App_Code/Models/fee_collection_transaction_extra.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum fee_collection_transaction_extraDataField

TransDate,

DueDate,

FeeID,

Status,
Expand Down Expand Up @@ -105,6 +107,9 @@ public partial class fee_collection_transaction_extraModel : BusinessRulesObject
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
private DateTime? _transDate;

[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
private DateTime? _dueDate;

[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
private int? _feeID;

Expand Down Expand Up @@ -263,6 +268,19 @@ public DateTime? TransDate
}
}

public DateTime? DueDate
{
get
{
return _dueDate;
}
set
{
_dueDate = value;
UpdateFieldValue("DueDate", value);
}
}

public int? FeeID
{
get
Expand Down
2 changes: 1 addition & 1 deletion app/App_Code/Services/ApplicationServices.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public virtual void Initialize()
{
ApplicationServices.FrameworkAppName = "LearnHub";
ApplicationServices.FrameworkSiteContentControllerName = "SiteContent";
ApplicationServices.Version = "8.9.23.504";
ApplicationServices.Version = "8.9.23.517";
ApplicationServices.HostVersion = "1.2.5.0";
var compilation = ((CompilationSection)(WebConfigurationManager.GetSection("system.web/compilation")));
var releaseMode = !compilation.Debug;
Expand Down
109 changes: 102 additions & 7 deletions app/App_Code/custom/Rules/SharedBusinessRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand All @@ -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"))
{
Expand All @@ -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.");
}
}
Copy link

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.

Fix in Cursor Fix in Web


base.BeforeSqlAction(args, result);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Base BeforeSqlAction Called Twice, Validation Order Incorrect

The 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.

Fix in Cursor Fix in Web


if (args.Controller == "account_general_ledger" && args.CommandName == "Delete")
{
if (!UserIsInRole("Administrators, HeadTeachers"))
Expand Down Expand Up @@ -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.");
//}
}
}

}


Expand All @@ -210,7 +248,7 @@ protected override void AfterSqlAction(ActionArgs args, ActionResult result)
}
}


[ControllerAction("StockTransactions", "Insert, Update, Calculate", ActionPhase.After)]
public void AddOrUpdateStockTransaction()
{
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class fee_collection_transaction_extraBusinessRules : zLearnHub.R
public void r104Implementation(fee_collection_transaction_extraModel instance)
{
// This is the placeholder for method implementation.
instance.Description = instance.FeeDescription + '-' + "test";
instance.Description = instance.FeeDescription;
}
}
}
Loading