c#开发的工资计算程序有个不解的问题
近期开发了个带公式编辑的工资计算程序,遇到了个很疑惑的问题,希望高人指点。开发环境:vs 2008(C#)+ sql server 2005
问题现象如下:
用户选中一个地区(大概3900人,工资由20个需要计算的项目组成),开始计算。计算完成后发现个别人员工资某一项有误,如扣缺勤的计算公式为:扣缺勤=[底薪]/[当月标准工作小时]*[未上班工作小时],一开始认为在计算的时候是公式解析错误导致,就在每次计算的时候记下了公式解析情况。发现计算错误查看日志,发现公式在解析的时候没错,日志如下:
---------2010-07-08 14:21:20---------
李丹 扣缺勤(底薪/当月标准工作小时*未上班工作小时)。
选中计算有误的员工,再次计算一次,就又都正确了。如果选择重新计算全部,计算完后还有错,错误的员工和工资项目每次都不一样,公式解析都没有问题。
--------------------编程问答-------------------- 核心计算代码:
using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Data;
using Common;
using System.Data.Common;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
namespace DataContent
{
//实现薪资计算的类
class Compute
{
private int _total;
private int _complyte;
private DataTable dt_formula; //计算公式
private string viewName = "view_1"; //视图名称
private DataSet ds_employees;
private string _SysDate;
private bool _hasRollBack = false;
private string errLogName;
public bool hasRollBack
{
get { return _hasRollBack; }
}
public System.Windows.Forms.ProgressBar progressBar1;
public System.Windows.Forms.Label lb_process;
public Frm_cmpt parent;
public Compute()
{
_Compute("");
}
public Compute(string common)
{
_Compute(common);
}
private void _Compute(string common)
{
errLogName = "数据计算错误" + DateTime.Now.ToString("yyyy-MM-dd HHmm") +".txt";
try
{
parent = new Frm_dataContent();
DataSet ds = GetFormula();
if (ds == null || ds.Tables[0].Rows.Count < 1)
{
throw new Exception("没有需要计算的项目!");
}
dt_formula = ds.Tables[0];
if (common == "")
{
common = "1=1";
}
_SysDate = GetDate(common);
ds_employees = GetViewData(viewName, common);
if (ds_employees == null || ds_employees.Tables[0].Rows.Count < 1)
{
throw new Exception("获取员工信息失败!");
}
_total = ds_employees.Tables[0].Rows.Count * dt_formula.Rows.Count + 1; //+1是导入垫付扣还累计表数据
Assay.dtFormula = dt_formula;
}
catch(Exception ep)
{
CommonFunc com = new CommonFunc();
com.WriteErrLog(ep);
com.WriteLog(ep.Message, "err");
throw ep;
}
}
//开始计算
public void StartCompute()
{
try
{
foreach (DataRow dr_formula in dt_formula.Rows)
{
if (dr_formula["已计算"].ToString() != "1")
{
string itemName = dr_formula["字段"].ToString();
string itemFormula = dr_formula["计算公式"].ToString();
string Num = dr_formula["小数位数"].ToString();
foreach (DataRow dr_employees in ds_employees.Tables[0].Rows)
{
ComputeItem(itemName, itemFormula, Num, dr_employees);
}
RefreshMTable(dr_formula, "已计算", "1");
}
}
//处理垫付扣还累计数据
ImportData(_SysDate);
RefreshProgress(); //刷新进度
parent.Complyte();
if(_hasRollBack)
{
MessageShow("计算已完成 但是有错误");
System.Diagnostics.Process.Start(Application.StartupPath + "\\Log\\" + errLogName);
}
else
{
MessageShow("计算已完成!");
}
}
catch (System.Threading.ThreadAbortException abortException)
{
MessageShow("计算已被用户取消!");
}
catch (Exception ep)
{
CommonFunc com = new CommonFunc();
com.WriteErrLog(ep);
com.WriteLog(ep.Message, "err");
MessageShow("计算失败!");
}
}
//提取str中包含的日期
//str中允许的时间格式yyyy-mm-dd
protected string GetDate(string str)
{
string tmp = @"[0-9]{4,4}-[0-9]{1,2}-[0-9]{1,2}";
Regex r = new Regex(tmp);
Match m = r.Match(str);
if (m.Success) return m.Groups[0].Value;
throw new Exception("无法获取有效的系统期间");
}
//获取计算进度
private int GetProgress(int compl,int total)
{
float tmp = ((float)compl / (float)total) * 100;
int i = (int)tmp;
if (i > 100) return 100;
return i;
}
private void RefreshProgress()
{
++_complyte;
int ProgressValue = GetProgress(_complyte,_total);
progressBar1.Value = ProgressValue;
lb_process.Text = ProgressValue.ToString() + "%";
}
/// <summary>
///
/// </summary>
/// <param name="itemName"></param>
/// <param name="formula">计算公式,如果计算公式为""空字符串,将字段值存为""空字符串。</param>
/// <param name="dr_employees"></param>
private void ComputeItem(string itemName, string formula,string num, DataRow dr_employees)
{
Assay ass = null; //公式解析
string comm = "系统期间='" + dr_employees["系统期间"].ToString() + "' and 员工工号='" + dr_employees["员工工号"].ToString() + "' and 员工姓名='" + dr_employees["员工姓名"].ToString() + "'";
string value = "";
if (formula != "")
{
try
{
string str_formula = null; //解析后的最终公式
ass = new Assay(dr_employees, formula);
str_formula = ass.GetResultEx();
if (str_formula != "")
{
value = ComputeItem(str_formula, viewName, comm);
if (value == "") value = "0";
int n = num == "" ? 0 : int.Parse(num);
value = ValueFormat(value, n); //将计算结果保留指定小数位
if (!Save(viewName, itemName, value, comm))
{
string err = dr_employees["员工姓名"].ToString()
+"( "
+ dr_employees["员工工号"].ToString()
+") "
+ itemName + " 结果保存失败!";
throw new Exception(err); //存入数据库
}
RefreshMTable(dr_employees, itemName, value); //存入内存表
RefreshProgress(); //刷新进度
}
CommonFunc com = new CommonFunc();
string msg = dr_employees["员工姓名"].ToString()
+ "( "
+ dr_employees["员工工号"].ToString()
+ ") "
+ itemName
+ "("
+ str_formula
+ ")";
com.WriteLog(msg, "公式");
}
catch (System.Threading.ThreadAbortException abortException)
{
throw abortException;
}
catch (Exception ep)
{
if (ep.Message == "IF_ERR_NULL")
{
string err = dr_employees["员工姓名"].ToString()
+ " ("
+ dr_employees["员工工号"].ToString()
+ ") "
+ ass.errorItem[0].ToString() + " 字段值未初始化!";
throw new Exception(err);//null值字段不是前置字段,但是其值为null,无法参与计算
}
else if (ep.Message == "IF_BEFORE")
{
for (int index = 0; index < ass.beforeItem.Count; ++index)
{
string tmpItemName = ass.beforeItem[index].ToString(); //前置字段名称
DataRow tmpFormula = GetFormulaByName(tmpItemName);
if (tmpFormula.IsNull("计算公式") || tmpFormula["计算公式"].ToString() == "") throw new Exception(tmpItemName + "字段计算公式为空!");
ComputeItem(tmpFormula); //前置字段
}
ComputeItem(itemName, formula,num, dr_employees);
}
else
{
string err = dr_employees["员工姓名"].ToString()
+ " ("
+ dr_employees["员工工号"].ToString()
+ ") "
+ itemName + ep.Message;
throw new Exception(err);
}
}
}
} --------------------编程问答-------------------- 接上
private string ValueFormat(string value, int num)
{
decimal dValue = decimal.Parse(value);
string re = "0.";
for (int i = 0; i < num; ++i)
{
re += "0";
}
return dValue.ToString(re);
}
//为所有员工计算前置字段
private bool ComputeItem(DataRow dr_formula)
{
try
{
string itemName = dr_formula["字段"].ToString();
string formula = dr_formula["计算公式"].ToString();
string Num = dr_formula["小数位数"].ToString();
foreach (DataRow dr_employees in ds_employees.Tables[0].Rows)
{
ComputeItem(itemName, formula,Num, dr_employees);
}
RefreshMTable(dr_formula, "已计算", "1");
return true;
}
catch(Exception ep)
{
throw new Exception(ep.Message);
}
}
//将值更新到内存表中
private void RefreshMTable(DataRow dr, string itemName, string value)
{
string type = dr.Table.Columns[itemName].DataType.ToString();
switch (type)
{
case "System.Int32":
dr[itemName] = value == "" ? 0 : int.Parse(value);
break;
case "System.Decimal":
dr[itemName] = value == "" ? 0 : decimal.Parse(value);
break;
case "System.String":
dr[itemName] = value;
break;
default:
dr[itemName] = value;
break;
}
}
//从dt_formula读取指定字段名的计算公式
private DataRow GetFormulaByName(string item)
{
DataRow[] result = dt_formula.Select("字段 = '" + item + "'");
if (result == null || result.Length < 1) return null; //字段不存在计算公式表中
return result[0];
}
//异常提示
private void MessageShow(string message)
{
System.Windows.Forms.MessageBox.Show(message);
}
//获取视图数据
private DataSet GetViewData(string ViewName, string comm)
{
string sql = @"select * from " + ViewName + " where " + comm;
return base_db.dbConn.GetDataSet(sql);
}
//获取公式
private DataSet GetFormula()
{
string sql = @"select * from 计算公式 ";
return base_db.dbConn.GetDataSet(sql);
}
/// <summary>
/// 获取计算结果
/// </summary>
/// <param name="formula"></param>
/// <param name="comm">sql条件</param>
private string ComputeItem(string formula, string view, string comm)
{
string sql = @"select cast(" + formula + " as decimal(18,4)) from " + view + " where " + comm;
DataSet ds = base_db.dbConn.GetDataSet(sql);
if (ds == null || ds.Tables[0].Rows.Count < 1) return "";
return ds.Tables[0].Rows[0][0].ToString();
}
/// <summary>
/// 保存值到数据库
/// </summary>
/// <param name="view">视图名称</param>
/// <param name="item">字段名</param>
/// <param name="value">字段值</param>
/// <param name="comm">sql查询条件</param>
/// <returns></returns>
private bool Save(string view, string item, string value, string comm)
{
base_db.dbConn.BeginTransaction(); //在连接上启用事务
string sql = @"update " + view + " set " + item + " = " + value + " where " + comm;
try
{
base_db.dbConn.ExecuteSQL(sql);
base_db.dbConn.Commit();
return true;
}
catch(Exception ep)
{
base_db.dbConn.RollBack();
string num = GetNum(sql);
CommonFunc com = new CommonFunc();
try
{
com.WriteErrLog(ep);
System.IO.StreamWriter sw = System.IO.File.AppendText(Application.StartupPath + "\\Log\\" + errLogName);
//sw.WriteLine("---------" + string.Format("{0:yyyy-MM-dd HH:mm:ss}", System.DateTime.Now) + "---------");
//sw.WriteLine("reqcont:");
sw.WriteLine(num);
//sw.WriteLine("");
//sw.WriteLine("");
sw.Flush();
sw.Close();
_hasRollBack = true;
}
catch
{ }
return true;
}
}
// 获取员工工号
private string GetNum(string sql)
{
string tmp = @"员工工号='[^']+'";
Regex r = new Regex(tmp);
Match m = r.Match(sql);
if (m.Success)
{
string str = m.Groups[0].Value.Replace("员工工号='", "").Replace("'", "");
return str;
}
return "";
}
/// <summary>
/// 处理垫付扣还数据。分三步1.将上月数据拷贝一份,不过垫付、扣还都为0;
/// 2.对比view_1更新当月垫付、扣还值
/// 3.计算当月剩余值
/// </summary>
/// <param name="SysDate"></param>
private void ImportData(string SysDate)
{
DateTime dtime = DateTime.Parse(SysDate);
CopyData(dtime);
UpdateExsitData(dtime);
InsertNewData(dtime);
UpdateResult(dtime);
}
/// <summary>
/// 拷贝上月数据,清空垫付、扣还
/// </summary>
/// <param name="dtime">当月日期</param>
private void CopyData(DateTime dtime)
{
string thisDate = dtime.ToString("yyyy-MM-dd");
dtime = dtime.AddMonths(-1);
string lastDate = dtime.ToString("yyyy-MM-dd");
string sql = @"delete from 垫付扣还累计 where 系统期间='" + thisDate + @"'";
if (!base_db.dbConn.ExecuteSQL(sql)) throw new Exception("删除垫付扣还数据失败");
sql = @"
insert into 垫付扣还累计
select 姓名,身份证,部门,0 AS 垫付,0 AS 扣还,公司垫付余额,'" + thisDate + @"' AS 系统期间
from 垫付扣还累计
where 系统期间='" + lastDate + @"'
";
if (!base_db.dbConn.ExecuteSQL(sql)) throw new Exception("垫付扣还数据复制失败");
}
//更新上月已经存在人员的垫付扣还数据
private void UpdateExsitData(DateTime dtime)
{
string thisDate = dtime.ToString("yyyy-MM-dd");
string sql = @"
DECLARE @tmptable table
(
员工姓名 nvarchar(200),
员工工号 nvarchar(200),
部门名称 nvarchar(200),
当月公司垫付 money,
当月公司扣还 money,
系统期间 datetime,
姓名 nvarchar(200),
身份证 nvarchar(200),
垫付 money,
扣还 money,
公司垫付余额 money
)
insert into @tmptable select *
from
(
select view_1.员工姓名,view_1.员工工号,view_1.部门名称,view_1.公司垫付_最终,view_1.当月公司扣还,
view_1.系统期间,A.姓名,A.身份证,A.垫付,A.扣还,A.公司垫付余额
from view_1
left join 垫付扣还累计 as A on A.身份证=员工工号 and a.系统期间=view_1.系统期间
where view_1.系统期间='" + thisDate + @"' and (view_1.公司垫付_最终<> 0 or view_1.当月公司扣还<>0)
) as a
WHERE 身份证 IS NOT NULL
update 垫付扣还累计 set 垫付=
(
select 当月公司垫付 from @tmptable where 身份证=垫付扣还累计.身份证
),
扣还=
(
select 当月公司扣还 from @tmptable where 身份证=垫付扣还累计.身份证
)
where 系统期间='" + thisDate + @"' and 身份证 in (select 身份证 from @tmptable)
";
if (!base_db.dbConn.ExecuteSQL(sql)) throw new Exception("垫付扣还数据跟新失败");
}
//向垫付扣还累计插入新员工垫付扣还信息
private void InsertNewData(DateTime dtime)
{
string thisDate = dtime.ToString("yyyy-MM-dd");
string sql = @"
insert into 垫付扣还累计
select 员工姓名,员工工号,部门名称,公司垫付,当月公司扣还,0 as 公司垫付余额, 系统期间
from
(
select view_1.员工姓名,view_1.员工工号,view_1.部门名称,view_1.公司垫付,view_1.当月公司扣还,
view_1.系统期间,A.姓名,A.身份证,A.垫付,A.扣还,A.公司垫付余额
from view_1
left join 垫付扣还累计 as A on A.身份证=员工工号 and a.系统期间=view_1.系统期间
where view_1.系统期间='" + thisDate + @"' and (view_1.公司垫付<> 0 or view_1.当月公司扣还<>0)
) as a
WHERE 身份证 IS NULL
";
if (!base_db.dbConn.ExecuteSQL(sql)) throw new Exception("垫付扣还数据插入失败");
}
//计算余额
private void UpdateResult(DateTime dtime)
{
string thisDate = dtime.ToString("yyyy-MM-dd");
string sql = @"update 垫付扣还累计 set 公司垫付余额=公司垫付余额+垫付-扣还 where 系统期间='" + thisDate + @"'";
if (!base_db.dbConn.ExecuteSQL(sql)) throw new Exception("垫付扣还数据计算失败");
}
}
} --------------------编程问答-------------------- 太多
还是单步跟踪检查计算值
--------------------编程问答-------------------- 错误差多少?不是精度问题吗? --------------------编程问答-------------------- 先Debug吧 --------------------编程问答-------------------- 估计是你哪个变量在计算下一个员工前没清空吧 --------------------编程问答-------------------- 根据我的经验 有可能是 某个或某些变量初始化的时间导致的, 第一次运行时候 错误,第二次又正确了 变量,控制变量可能有问题 ,建议跟踪全部变量.第一次运行 全部变量 输出以下,第二次运行 全部变量再输出以下 ,对比一下 两次变量值 不相同的变量可能就是导致出问题的因素 --------------------编程问答-------------------- debug --------------------编程问答-------------------- 楼上的太厉害了 洋洋洒洒就写了好几百行啊 --------------------编程问答-------------------- 这样的问题还是要靠自己debug --------------------编程问答--------------------
+1 我也觉得不是你计算的问题 ,毕竟无复杂性可言 看看你变量的初始化位置 --------------------编程问答-------------------- 自己一部部调试吧。。。 --------------------编程问答-------------------- 好长啊~~~~ --------------------编程问答-------------------- 哥们,你调试一下好不?
先找到出问题的地方,再贴出代码吧。
你这样会让人头晕的
补充:.NET技术 , C#