HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Employee wage/salary calculation MVP solution - follow-up

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
salarycalculationemployeewagemvpfollowsolution

Problem

This is a follow-up to this post.

In this project (C# Win forms) I'm supposed to calculate wages of employees.

These are the steps:

-
Calculate earnings from Attendance data. Here all regular earnings like BasicSalary, OverTimeAmount etc will be calculated.

-
Then these earning details will be shown in a grid where user can enter values for blank columns ( non fixed earning figures like SpecialAllowance which is not attendance based ) (See class Earning)

-
Then user can save these earning details on a table called SalaryTrans. (Each record on the grid has a Save button.)

-
Calculate deduction. For all the records (employees) which is in SalaryTrans, deductions will be calculated. (see class Deduction)

-
Then these deduction details will be shown in a grid where user can enter non fixed / random deductions like OtherDeductions etc.

-
Then user can save these deduction details on the same table SalaryTrans. (Actually, in this case it updates deduction related columns of current records existing in the table.)

-
Then user can balance wages [BalanceWage = Earnings-Deduction].

-
After balancing, Net wage should be calculated deducting CarriedForwardAmount from BalanceWage. This carried forward amount is the less than 100 fraction of BalanceWage. (ex: if BalanceWage is 7,526.50 then CF amount is 26.50 and NetWage should be 7500.00)

The following diagram shows a portion of class relationships with regard to the above scenario.

The following code shows the solution I've implemented for the above case. This is the MVP pattern (for this demonstration I've removed interface usage and only the concrete classes are shown).

WageInfo has a one-to-many relationship with Earning, Deduction and WageBalance. Hence WageInfo maintains three list List, List and List as this is performed in 3 steps by the user.

Calculate Earnings Code Explanation

When requested, DataService returns a DataTable with all earings. T

Solution

I don't know which variant of MVP you are attempting to use (Supervising Controller or Passive View), but this:

class WagesPresenter : BasePresenter
{
    frmWages _WageView;
}


...is incorrect.

In MVP, the Presenter interacts with the View via an interface. This allows the Presenter to be unit-tested more easily and also allows the implementation of the View to change independently of its behavior. So what you'll want is something like this:

interface IWagesView
{
    BindingSource EarningDetails { get; set; }
}

class frmWages : IWagesView
{
    public frmWages()
    {
        Presenter = new WagesPresenter(new WagesManager(), new WageInfo(), this);
    }

    public WagesPresenter Presenter { get; private set; }

    public BindingSource EarningDetails { get; set; }
}

class WagesPresenter : BasePresenter
{
    WageInfo _wageInfo;
    IWagesView _view;
    WageManager _wageManager;

    public WagesPresenter(WageManager wageManger, WageInfo wageInfo, IWagesView view)
    {
        _wageManager = wageManger;
        _wageInfo = wageInfo;
        _view = view;
    }
}


Also, use var wherever appropriate (at least in my opinion) to reduce code redundancy:

class DataService
{
    public DataTable GetEarnings(DateTime fromDate, DateTime toDate)
    {
        using (var sqlConnection = new SqlConnection(db.GetConnectionString))
        using (var sqlCommand = new SqlCommand("dbo.sp_Earnings", sqlConnection))
        {
            sqlCommand.CommandType = CommandType.StoredProcedure;
            sqlCommand.Parameters.Add("@fromDate", SqlDbType.DateTime).Value = fromDate;
            sqlCommand.Parameters.Add("@toDate", SqlDbType.DateTime).Value = toDate;

            using (var sqlAdapter = new SqlDataAdapter(sqlCommand))
            using (var dataSet = new DataSet())
            using (var dataTable = new DataTable())
            {
                sqlConnection.Open();
                dataSet.Tables.Add(dataTable);
                sqlAdapter.Fill(dataTable);

                return dataTable;
            }
        }
    }
}


EDIT: I would also refactor your Earning property assignments to use helper methods to reduce the amount of repetition you have when converting possibly-null DB values:

static class DbConvert
{
    static decimal ToDecimal(object value)
    {
        return value == DBNull.Value ? 0 : Convert.ToDecimal(value);
    }

    static int ToInt32(object value)
    {
        return value == DBNull.Value ? 0 : Convert.ToInt32(value);
    }

    // and other appropriate methods...
}

return new Earning
{
    EmployeeID = DbConvert.ToInt32(row["Emp_ID"]),
    ExtraShiftAmount = DbConvert.ToDecimal(row["Extra_Shift_Amount"])
    // and so on...
};

Code Snippets

class WagesPresenter : BasePresenter
{
    frmWages _WageView;
}
interface IWagesView
{
    BindingSource EarningDetails { get; set; }
}

class frmWages : IWagesView
{
    public frmWages()
    {
        Presenter = new WagesPresenter(new WagesManager(), new WageInfo(), this);
    }

    public WagesPresenter Presenter { get; private set; }

    public BindingSource EarningDetails { get; set; }
}

class WagesPresenter : BasePresenter
{
    WageInfo _wageInfo;
    IWagesView _view;
    WageManager _wageManager;

    public WagesPresenter(WageManager wageManger, WageInfo wageInfo, IWagesView view)
    {
        _wageManager = wageManger;
        _wageInfo = wageInfo;
        _view = view;
    }
}
class DataService
{
    public DataTable GetEarnings(DateTime fromDate, DateTime toDate)
    {
        using (var sqlConnection = new SqlConnection(db.GetConnectionString))
        using (var sqlCommand = new SqlCommand("dbo.sp_Earnings", sqlConnection))
        {
            sqlCommand.CommandType = CommandType.StoredProcedure;
            sqlCommand.Parameters.Add("@fromDate", SqlDbType.DateTime).Value = fromDate;
            sqlCommand.Parameters.Add("@toDate", SqlDbType.DateTime).Value = toDate;

            using (var sqlAdapter = new SqlDataAdapter(sqlCommand))
            using (var dataSet = new DataSet())
            using (var dataTable = new DataTable())
            {
                sqlConnection.Open();
                dataSet.Tables.Add(dataTable);
                sqlAdapter.Fill(dataTable);

                return dataTable;
            }
        }
    }
}
static class DbConvert
{
    static decimal ToDecimal(object value)
    {
        return value == DBNull.Value ? 0 : Convert.ToDecimal(value);
    }

    static int ToInt32(object value)
    {
        return value == DBNull.Value ? 0 : Convert.ToInt32(value);
    }

    // and other appropriate methods...
}

return new Earning
{
    EmployeeID = DbConvert.ToInt32(row["Emp_ID"]),
    ExtraShiftAmount = DbConvert.ToDecimal(row["Extra_Shift_Amount"])
    // and so on...
};

Context

StackExchange Code Review Q#51803, answer score: 4

Revisions (0)

No revisions yet.