diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4fa52e0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,57 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +CEL Payroll is a .NET Framework 4.5 WinForms desktop payroll/HR application (C#) backed by SQL Server. Solution: `CEL Payroll.sln` (VS 2019 format; folder name refers to the original VS2015 port). There are no test projects and no linter. + +## Build + +Build from Visual Studio, or with MSBuild from a Developer Command Prompt: + +```bash +msbuild "CEL Payroll.sln" /p:Configuration=Debug +``` + +Startup project is `Payroll.UI` (WinExe, entry `Payroll.UI/Payroll.UI/Program.cs` → `frmMain`). Third-party and in-house framework assemblies (`Ease.CoreV35`, `Ease.Controls`, `Ease.Report`, ReportViewer, NPOI, ClosedXML, iTextSharp, etc.) are checked in as binaries under `Library/` and referenced by HintPath, not NuGet. Crystal Reports 10.5 runtime must be installed for `Payroll.Report` to build. + +## Architecture + +Layered, with project dependencies flowing downward: + +- **Payroll.UI** — WinForms screens, grouped by module folder (Salary, Tax, Bonus, Leave, PF, Gratuity, FinalSettlement, …). +- **Payroll.Report** — report classes (`Class/rpt*.cs`), the report viewer (`UI/fReportViewer.cs`), typed datasets (`PayrollDataSet`), and `.rdlc` files under `RDLC/` (plus some Crystal reports). +- **Ease.UICreator** — generic, config-driven master/detail form generator used for simple setup screens. +- **Payroll.Controls** — shared custom WinForms controls. +- **Payroll.Service** — service implementations + data access (SQL). +- **Payroll.BO** — business objects and the service interfaces. +- **Ease.SvcHost** — Windows service exposing `Payroll.Service` over .NET Remoting (TCP) for a 3-tier deployment. +- **Payroll.NotifProcessInterface** — console exe that runs notification/mail processing. + +### BO / Service / DA pattern + +Each entity spans three files that share a module folder name across projects, e.g. Bonus: + +1. `Payroll.BO/Bonus/Bonus.cs` — class deriving from `BasicBaseObject` (or similar Ease.CoreV35 base), properties call `OnPropertyChange`, static `Get(...)`/instance `Save()`/`Delete()` methods that use a per-type `Cache` and delegate to `Service`. The **service interface (`IBonusService`) is declared at the bottom of the same BO file**. `Service` is resolved via `Services.Factory.CreateService(...)`. +2. `Payroll.Service/Bonus/Service/BonusService.cs` — `ServiceTemplate` subclass implementing the interface. Every method wraps work in `TransactionContext.Begin()` / `tc.End()`, with `tc.HandleError()` + `ExceptionLog.Write(e)` + `throw new ServiceException(...)` in the catch. `MapObject` maps `DataReader` columns to BO properties and sets `ObjectState.Saved`. +3. `Payroll.Service/Bonus/DA/BonusDA.cs` — `internal static` class of raw SQL. Queries use Ease's placeholder formatting, not SqlParameters: `%n` number, `%s` string, `%d` date (e.g. `tc.ExecuteNonQuery("UPDATE Bonus SET code=%s WHERE BonusID=%n", ...)`). + +Adding a method to an entity means touching all three: interface + BO wrapper, service implementation, DA query. IDs use the Ease `ID` type (`.Integer` for the int value). Many queries are scoped by `SystemInformation.CurrentSysInfo.PayrollTypeID`. + +Service location is controlled by `serviceHandler` in `Payroll.UI/Payroll.UI/App.config`: `Payroll.Service.dll/conn` loads the services in-process using the `conn` entry in ``; a `host:port/Payroll.Service` value goes through `Ease.SvcHost` remoting instead. + +### Menus and forms + +The main menu is driven by `Payroll.UI/Payroll.UI/Menu.xml`. Each `` names a `` and ``; `frmMain` resolves the form type by reflection (`Payroll.UI.` unless the name is assembly-qualified) and invokes the parameterless show method. Simple setup screens use `BasicUIForms` (`CreateUI.cs`), whose `show*` methods build forms through `Ease.UICreator`. Adding a screen to the menu means adding a Menu.xml entry whose names match exactly. + +### Reports + +RDLC files are compiled as **EmbeddedResource** in `Payroll.Report.csproj` and loaded by manifest name, e.g. `"Payroll.Report.RDLC.BankAdvice.rdlc"` (see `Class/rptBankAdvice.cs`, `UI/fReportViewer.cs`). A new `.rdlc` must be added to the csproj as an EmbeddedResource, and its dataset fields must match the `DataTable` passed in (typed datasets live in `PayrollDataSet`). + +## Repository conventions + +- Branching: work on `dev_shahadat`, PRs into `devqc` (Gitea-hosted). +- Commit messages are prefixed with the ticket ID in the form `YYYYMMDD-NNNN `, e.g. `20260929-3216 Bank advice Salary Report Modification`. +- Release commits typically bump `AssemblyVersion`/`AssemblyFileVersion` in `Payroll.UI/Payroll.UI/Properties/AssemblyInfo.cs` to a date-based `YY.M.DD.0` value. +- `App.config` contains live DB connection strings and is frequently changed per environment; don't commit connection-string changes unless intended. diff --git a/Payroll.BO/Basic/Enums.cs b/Payroll.BO/Basic/Enums.cs index afe2780..faea663 100644 --- a/Payroll.BO/Basic/Enums.cs +++ b/Payroll.BO/Basic/Enums.cs @@ -1248,6 +1248,7 @@ namespace Payroll.BO Memorandum = 52, SalarySMS = 53, SalarySheetSummary = 54, + SalarySheetSummaryWithLWP = 541, SalarySheetArrear = 57, SalaryBankSummary = 55, SalaryByMonthAndYear = 111111, diff --git a/Payroll.BO/Salary/SalaryCalculator.cs b/Payroll.BO/Salary/SalaryCalculator.cs index 6c662ae..0df9b8a 100644 --- a/Payroll.BO/Salary/SalaryCalculator.cs +++ b/Payroll.BO/Salary/SalaryCalculator.cs @@ -1437,6 +1437,11 @@ namespace Payroll.BO item.BasicCurrentDeduct = nTotalUnAuthorizedLeave - item.BasicPaidDays; item.BasicPaidDays += item.BasicCurrentDeduct; item.BasicUnpaidDays = 0; + + // Allowances set in the parameter are cut for the same days as Basic + item.OtherCurrentDeduct = nTotalUnAuthorizedLeave - item.OtherPaidDays; + item.OtherPaidDays += item.OtherCurrentDeduct; + item.OtherUnpaidDays = 0; } foreach (UnAuthorizeLeaveParamDetail detail in Param.Details) @@ -1497,13 +1502,16 @@ namespace Payroll.BO detail.Type == EnumSalaryComponent.Basic ? (int)EnumSalaryItemCode.Basic_Salary : detail.AllowanceID.Integer, salaryDetail.SupportID.Integer, salaryDetail.Description, GlobalFunctions.Round(nAmount)); - this.AddDetail(salary, EnumSalaryGroup.Miscellaneous, EnumSalaryItemCode.Tot_UnauthLeave_Days, Param.UnAhuthorizeLeaveID.Integer, 0, "UnAuthorized Leave Days", nThisMonthUnAuthorizedLeave); //if(nUnpaidDays>0) //{ // } } + // Days deducted this month, added once (not per parameter item); used by salary sheet, payslip and arrear + if (Param.Details.Count > 0) + this.AddDetail(salary, EnumSalaryGroup.Miscellaneous, EnumSalaryItemCode.Tot_UnauthLeave_Days, Param.UnAhuthorizeLeaveID.Integer, 0, "UnAuthorized Leave Days", item.BasicCurrentDeduct); + //} //item.currentDeduct = nUnpaidDays; diff --git a/Payroll.Report/Class/rptSalarySheet.cs b/Payroll.Report/Class/rptSalarySheet.cs index a9e60ca..4197b79 100644 --- a/Payroll.Report/Class/rptSalarySheet.cs +++ b/Payroll.Report/Class/rptSalarySheet.cs @@ -931,14 +931,53 @@ namespace Payroll.Report return null; } public void ShowReport() + { + ShowSalarySheet(false); + } + + /// + /// Salary Sheet with LWP (ReportID 541): items are shown at full amount and the + /// unauthorized leave (LWP) deduction is shown as a separate deduction column. + /// + public void ShowReportWithLWP() + { + ShowSalarySheet(true); + } + + private void ShowSalarySheet(bool withLWP) { fReportViewer form = new fReportViewer(); _SalaryMonth = _selectedParameter.FromDate.Value; - ID nReportID = _selectedParameter.ReportItem.ReportID; + // Signatures/authorization are shared with the normal Salary Sheet (54) + ID nReportID = withLWP ? ID.FromInteger((int)EnumReportType.SalarySheetSummary) : _selectedParameter.ReportItem.ReportID; string sEmpID = _selectedParameter.ReportItem.INSQL; DataRow oDR = null; DataSet oSalarySheets = null; oSalarySheets = SalaryMonthly.GetEmpSalarySheet(GlobalFunctions.LastDateOfMonth(_SalaryMonth), sEmpID); + + // One "LWP" row per employee = sum of all unauthorized leave deductions + DataTable dtLWP = oSalarySheets.Tables["UnAuthorizedLeave"].Clone(); + dtLWP.Columns["Description"].ReadOnly = false; + if (withLWP) + { + foreach (DataRow src in oSalarySheets.Tables["UnAuthorizedLeave"].Rows) + { + DataRow lwp = GetItem3(dtLWP, src["EMPLOYEENO"].ToString()); + if (lwp == null) + { + lwp = dtLWP.NewRow(); + lwp.ItemArray = src.ItemArray; + lwp["ITEMCODE"] = 0; + lwp["ITEMID"] = 0; + lwp["Description"] = "LWP"; + dtLWP.Rows.Add(lwp); + } + else + { + lwp["Amount"] = Convert.ToDouble(lwp["Amount"]) + Convert.ToDouble(src["Amount"]); + } + } + } PayrollDataSet.PayrollDataSet.SalarySheetDataTable dTSalarySheet = new Payroll.Report.PayrollDataSet.PayrollDataSet.SalarySheetDataTable(); List components = new List() { "Basic", "Allowance", "OT", "Deduction", "Loan" }; @@ -1056,6 +1095,12 @@ namespace Payroll.Report } + seq++; + foreach (DataRow dr in dtLWP.Rows) + { + dr["SequenceNo"] = seq; + } + seq++; foreach (DataRow dr in oSalarySheets.Tables["TotalDeduction"].Rows) { @@ -1083,7 +1128,8 @@ namespace Payroll.Report oDR = dTSalarySheet.NewRow(); this.CopyNonMngtForGross(oDR, source); - if (source["ITEMGROUP"].ToString() == "1") + // With LWP the items stay at full amount; LWP is shown as a separate deduction + if (!withLWP && source["ITEMGROUP"].ToString() == "1") { DataRow dr = this.GetItem2(oSalarySheets.Tables["UnAuthorizedLeave"], source["EMPLOYEENO"].ToString(), EnumSalaryGroup.UnauthLeave, (EnumSalaryItemCode)Convert.ToInt16(source["ITEMCODE"]), Convert.ToInt16(source["ITEMID"])); if (dr != null) @@ -1106,6 +1152,15 @@ namespace Payroll.Report { oDR["Amount"] = Convert.ToDouble(dr2["Amount"]) + Convert.ToDouble(dr["Amount"]); } + if (withLWP && dr2 != null) + { + // Gross before LWP = Net Pay + Total Deduction + LWP + DataRow drLWP = this.GetItem3(dtLWP, source["EMPLOYEENO"].ToString()); + double nGross = Convert.ToDouble(dr2["Amount"]); + if (dr != null) nGross += Convert.ToDouble(dr["Amount"]); + if (drLWP != null) nGross += Convert.ToDouble(drLWP["Amount"]); + oDR["Amount"] = nGross; + } dTSalarySheet.Rows.Add(oDR); } @@ -1116,10 +1171,35 @@ namespace Payroll.Report dTSalarySheet.Rows.Add(oDR); } + foreach (DataRow source in dtLWP.Rows) + { + oDR = dTSalarySheet.NewRow(); + this.CopyNonMngtForDeduct(oDR, source); + dTSalarySheet.Rows.Add(oDR); + } + foreach (DataRow source in oSalarySheets.Tables["TotalDeduction"].Rows) { oDR = dTSalarySheet.NewRow(); this.CopyNonManagement(oDR, source); + if (withLWP) + { + DataRow drLWP = this.GetItem3(dtLWP, source["EMPLOYEENO"].ToString()); + if (drLWP != null) + oDR["Amount"] = Convert.ToDouble(oDR["Amount"]) + Convert.ToDouble(drLWP["Amount"]); + } + dTSalarySheet.Rows.Add(oDR); + } + + // Employee with LWP but no regular deduction has no Total Deduction row + foreach (DataRow source in dtLWP.Rows) + { + if (this.GetItem3(oSalarySheets.Tables["TotalDeduction"], source["EMPLOYEENO"].ToString()) != null) continue; + oDR = dTSalarySheet.NewRow(); + this.CopyNonManagement(oDR, source); + oDR["Description"] = "Total Deduction"; + oDR["SequenceNo"] = oSalarySheets.Tables["TotalDeduction"].Rows.Count > 0 + ? oSalarySheets.Tables["TotalDeduction"].Rows[0]["SequenceNo"] : Convert.ToInt32(source["SequenceNo"]) + 1; dTSalarySheet.Rows.Add(oDR); } @@ -1130,7 +1210,7 @@ namespace Payroll.Report dTSalarySheet.Rows.Add(oDR); } - + #endregion //return dTSalarySheet; diff --git a/Payroll.Report/RDLC/BankAdvice.rdlc b/Payroll.Report/RDLC/BankAdvice.rdlc index 2df63fe..7100cbf 100644 --- a/Payroll.Report/RDLC/BankAdvice.rdlc +++ b/Payroll.Report/RDLC/BankAdvice.rdlc @@ -1332,7 +1332,6 @@ 7 - true @@ -2288,89 +2287,43 @@ - 0.99542in + 1.49653in - 1.0775in + 1.05985in - 2.24918in + 0.21025in - 1.40167in + 0.20985in - 1.54625in + 0.7936in + + + 1.05985in + + + 1.05985in + + + 0.20985in + + + 0.20985in + + + 0.71608in + + + 0.20985in 0.25in - - - - true - true - - - - - SL NO - - - - - - Silver - 2pt - 2pt - 2pt - 2pt - - - - - - - - true - true - - - - - Employee ID - - - - - - Silver - 2pt - 2pt - 2pt - 2pt - - - - @@ -2380,7 +2333,7 @@ - Employee Name + Beneficiary Account Name @@ -2405,14 +2358,14 @@ - + true true - Account No + Beneficiary Account Name @@ -2421,7 +2374,71 @@ + + Silver + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + Transaction Type(IFT/EFT/RTGS) + + + + + + Silver + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + Routing Number + + + + @@ -2469,69 +2486,203 @@ + + + + true + true + + + + + Debit Narration + + + + + + Silver + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + E-mail 01 + + + + + + Silver + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + E-mail 02 + + + + + + Silver + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + Payment Execution Date (DD-MM-YYYY) + + + + + + Silver + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + Employee Id + + + + + + Silver + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + Customer Reference + + + + + + Silver + 2pt + 2pt + 2pt + 2pt + + + + 0.25in - - - - true - true - - - - - =Fields!SLNo.Value - - - 2pt - 2pt - 2pt - 2pt - - - - - - - - true - true - - - - - =Fields!EmpNo.Value - - - 2pt - 2pt - 2pt - 2pt - - - - @@ -2563,7 +2714,7 @@ - + true true @@ -2577,7 +2728,65 @@ + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + @@ -2623,6 +2832,192 @@ + + + + true + true + + + + + ="Salary month of " & Format(Fields!SalaryMonth.Value, "MMMM, yyyy") + + + + SalaryMonth + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + =Fields!Email.Value + + + + Email + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + Textbox42 + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + Textbox45 + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + =Fields!EmpNo.Value + + + + EmpNo + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + Textbox90 + + + 2pt + 2pt + 2pt + 2pt + + + + @@ -2630,7 +3025,7 @@ - + true true @@ -2646,7 +3041,100 @@ + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + @@ -2657,12 +3145,8 @@ 2pt - 4 - - - @@ -2697,6 +3181,204 @@ + + + + true + true + + + + + + + + + + + + Textbox35 + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + + + + Textbox39 + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + + + + Textbox43 + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + + + + Textbox56 + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + + + + Textbox60 + + + 2pt + 2pt + 2pt + 2pt + + + + + + + + true + true + + + + + + + + + + + + Textbox91 + + + 2pt + 2pt + 2pt + 2pt + + + + @@ -2708,6 +3390,12 @@ + + + + + + @@ -2730,7 +3418,7 @@ dsCompany_BankAdvice 6.36333in 0.75in - 7.27002in + 7.2354in 1