进销存管理系统的设计与实现

 

 

摘要

【摘要】

        随着国家的政治经济政策的优化改革,使得国内中小企业得以蓬勃发展。可是随着企业的发展,客户数量的的为断增多,财务工作繁杂化,库存产品零乱等一系列问题也接踵而至,给企业经营者运作带来了很大的困扰。信息技术的迅猛发展,科学经营管理观念的迅速传播,使中小企业经营者逐渐认识到转变企业经营管理理念,改善企业经营管理模式是企业适应时代的发展急需解决的问题。现代管理,应该建立在真实、有效的数据信息基础之上,应该是一种科学的、理性的管理体制,而这种管理与经营的联系与互助,又必须依赖于计算机的强大的数据统计功能,计算机管理系统便是实际的企业进销问题与计算机强大数据统计功能相结合的产物。管理系统解决的了商业企业商品的进、销、存、退、换、盘、损、残、调、借、赠等一系列操作流程中的数据信息的处理问题,提供一系列的数据分析,帮助经营者合理的调整各个经营运作环节的运作节奏,并对商品流转过程进行了全程跟踪管理、相应款项流通的全程记录管理和票据信息统计的管理。因此进销存管理系统的全面应用,规范业务流程、提高了企业的管理水平,提高资金流动的透明度,加快商品资金周转速度,进而全面提高了企业的经营水平、进而全面提升了企业的经济效益。

【关键词】进销存管理系引言

企业经营如逆水行舟不进则退。每一个努力发展的企业都明白,先进管理的重要作用,引进先进管理系统使企业经营变得规范、合理。进销存管理系统无论是在功能设计还是业务流程上都尽可能做到满足经营管理运作流程的需求,并且操作方便、功能强大,即使操作者对计算机知识一窍不通也能一用就会。它强化库存管理,规范业务流程,提高资金管理的透明度,加快商品资金周转,是企业经营管理中数据分析中必不可少的管理工具。

总体设计

根据市场的需求,要求系统具有以下功能:

        实现商品入库、入库退货操作。

        实现商品销售、销售退货操作。

        实现库存管理(调货、盘点)操作。

        实现信息的查询、打印功能。

        准确地进行账款记录、账款查询。

 

2.1  项目规划

根据需求分析,设计系统框架。进销存管理系统由等8部分组成。设计各部分具体功能如下:

       基础信息模块

基础信息模块包括药品信息、员工信息、供应商信息和客户信息4部分。

 

       销售模块

销售模块由销售登记、销售退货、销售查询、销售退货查询4部分。 

 入库模块

入库模块主要由入库登记、入库退货、入库查询、入库退货查询4组成。

        调货模块

调货模块包含调货登记和调货查询两部分。

        库存模块

库存模块由库存盘点、库存查询、仓库管理3部分组成。   

结账模块

结账模块主要包括销售结账、销售退货结账、入库结账、入库退货结账。

        财务模块

财务模块由日结、月结、供应商往来账、客户往来账4部分组成。

        系统管理模块

系统管理模块由修改用户密码、修改用户权限、修改用户3部分组成。

  

系统设计

3.1  开发及运行环境

1.硬件要求

CPU:300MHz以上的处理器。

内存:128MB,推荐256MB。

硬盘:150MB以上剩余空间。

显示像素:最低800*600,最佳效果1024*768。

2.软件要求

操作系统:Windows2000/NT/XP/CE。

数据库:SQL Server2000。

3.2  数据库设计

1.数据库概要说明

本系统采用SQL Server 2000 数据库,系统数据库名为ypgl,中共包含46个表,其中作为临时表的有20个,作为数据存储表的有26个。

临时表:lsdhinfo0、lsdhinfo1、lsgys0、lsgys1、lskcquery0、lskcquery1、lskh0、lskh1、lsrkquery0、lsrkquery1、lsrkthinfo0、lsrkthinfo1、lsxsquery0、lsxsquery1、lsxsthinfo0、lsxsthinfo1、lsyg0、lsyg1、lsypinfo0、lsypinfo1。

数据存储表:tabbf、tabck、tabdhdj、tabdhph、tabgys、tabjsfs、tabkc、tabkcpddj、tabkcpdph、tabkh、tabpurview、tabpurviewctrl、tabrkdj、tabrkjz、tabrkph、tabrkthdj、tabrkthjz、tabrkthph、tabxsdj、tabxsjz、tabxsph、tabxsthdj、tabxsthjz、tabxsthph、tabyginfo、tabypinfo。

图2所示的即为本系统中数据库的数据表结构图,该数据表结构图包含系统所有数据表。可以清晰地反应数据库信息。

 

2.主要数据表的结构

数据库中的数据表请参见附录B。

功能模块设计

4.1  主窗口设计

进销存管理系统主窗口由菜单、工具栏、客户区域和状态栏四部分组成,效果如图3所示。

 

图3  进销存管理系统主窗口

1.菜单设计

(1)在工具栏中单击按钮 ,或者从菜单中选择“View”/“Workspace”项,这时会弹出如图4所示的工作区窗口(Workspace窗口)。在工作区窗口中,能看到该程序所使用的资源,且每种资源都有一个资源符号,主窗体也使用了一个资源符号IDD_A1_DIALOG,这是VC缺省提供的。可以在这里添加或者删除各种资源。

 

图4  Workspace窗口

(2)在工作区窗口(Workspace窗口)右键单击“a1 resources”选项,在弹出菜单中选择“Insert...”选项,将弹出“Insert Resource”对话框。在该对话框中选择“Menu”选项,然后单击“New”按钮,将生成如图5所示的菜单资源。

 

图5  编辑菜单资源

(3)右键双击菜单资源编辑器的虚线空白框,在弹出的菜单中选择“Properties”选项,将弹出“Menu Item Properties”对话框,在“Menu Item Properties”对话框的“caption”(标题)编辑框中键入:“基础信息(&I)”(符号&可以使字母I有一个下划线,而且可以通过“Alt+I”访问该菜单项。此时关闭“Menu Item Properties”对话框,将在菜单编辑器中生成主菜单“基础信息”。双击“基础信息”菜单下的虚线空白框,在弹出的“Menu Item Properties”对话框中设计“药品信息(&M)”、“员工信息(&Y)”等菜单项。

(4)同上,可以设计其他主菜单及菜单项。最后得到如图6所示的菜单界面。

 

图6  菜单界面

2.工具栏设计

在应用程序中要经常使用工具栏,它是最常用的界面元素,对应着应用程序的最常用功能。主窗口共有9个工具栏按钮,分别是“销售登记”、“销售退货”、“销售结账”、“入库登记”、“入库退货”、“入库结账”、“调货登记”、“库存登记”、“退出”工具栏按钮。创建工具栏可使用MFC类库中的CToolBarCtrl类,该类用来生成工具条。本系统主窗体的工具栏将引用MSDN提供的类CStandardBar,该类派生自CToolBarCtrl。

操作步骤如下:

(1)从基类CToolBarCtrl中派生需要的类CstandardBar。选择“Insert”/“New Class...”菜单项,在弹出来的“New Class”对话框中设置“Class Type”为“MFC Class”,在“Class Infomation”中的Name编辑框中键入“CstandardBar”,然后在“Base Class”下拉列表框中选择“CtoolBarCtrl”,最后单击“OK”按钮。

(2)需要9个按钮,每个按钮有相应的文本和图片。所以,需要添加如图7所示的图片资源,资源长为288像素,高为32像素,资源符号为:IDR_STANDARDBAR。 

 

(3)添加字符串资源(String Table),如表1所示。

表1                                  字符串资源

资源符号

字符串资源

IDSTR_XSDJ

102

销售登记

IDSTR_XSTH

103

销售退货

IDSTR_XSJZ

104

销售结账

IDSTR_RKDJ

105

入库登记

IDSTR_RKTH

106

入库退货

IDSTR_RKJZ

107

入库结账

IDSTR_DHDJ

108

调货登记

IDSTR_KCPD

109

库存盘点

IDSTR_OUT

110

退出

(4)程序中引入资源,创建工具栏按钮。

创建工具栏按钮需要重写Create函数,该函数创建工具栏的步骤如下:

① 先创建工具栏窗口,然后为工具栏类添加图片资源。相关函数是:

SetBitmapSize(CSize(32,32));     //设置单个位图的大小

VERIFY(AddBitmap(m_nButtonCount,IDR_STANDARDBAR) != -1);     //添加位图

m_nButtonCount是指按钮图片的个数,IDR_STANDARDBAR对应着相应的图片。

② 创建相应数量的按钮,并为每个按钮分配相应图片资源和文本资源,设置相关属性。

m_pTBButtons = new TBBUTTON[m_nButtonCount];     //用来加入到工具栏里的按钮

……

m_pTBButtons[nIndex].iString = AddStrings(pString);

m_pTBButtons[nIndex].fsState = TBSTATE_ENABLED;

m_pTBButtons[nIndex].fsStyle = TBSTYLE_BUTTON;

m_pTBButtons[nIndex].dwData = 0;

m_pTBButtons[nIndex].iBitmap = nIndex;      //控制按钮的相关图片

m_pTBButtons[nIndex].idCommand = nIndex + IDSTR_XSDJ;     //用于命令消息传递

在步骤②中,要注意如下事项:

① AddStrings(pString)返回一个字符串的基于0的编号,该值用来连接字符串到按钮上,其中的字符串参数pString需要两个结束符来表示结尾,必须将字符串写成如下形式:pString= "Only one string to add\0";CString类不能提供这样的功能,因为不可能在CString中保存超过一个结束符的字符串。所以,将CString中的字符串取出,以char定义的字符串保存,再对该字符串添加一个结束符,做法如下:

CString string;

string.LoadString(nIndex + IDSTR_XSDJ);      //装载字符串资源

……

//取得字符串的长度为了添加一个结束符,给长度加1

int nStringLength = string.GetLength() + 1;

……

TCHAR * pString = string.GetBufferSetLength(nStringLength);     //按增加后的长度返回字符串

pString[nStringLength] = 0;

函数GetBufferSetLength的过程分配了nStringLength+1长度的内存空间,并在加上结束符'\0'之后,复制原字符串到这个新的内存空间中,同时将原字符串的结束符也复制到新的位置,于是,该函数结束后,字符串pString已经有两个结束符了,最后一个语句略显多余或不足。但为了保证该字符串确实有两个结束符,不能省略这两个结束符。

pString[nStringLength] = 0;

pString[nStringLength-1] = 0;

② fsState确定按钮的状态,fsStyle确定按钮的风格。若给fsStyle赋值TBSTYLE_SEP,则该按钮表现为一个间隔。dwData可以是用户自定义的数据,可以将一个指针或句柄传递给它,可以在某些消息响应函数中使用。iBitmap是表示基于0的图像列表的编号。

③ idCommand为与按钮连接的命令标识,当这个按钮被按下时,这个值将被放到WM_COMMAND中发送到父窗体。如果fsStyle被设置为TBSTYLE_SEP,该值必须为0。

用Create函数创建工具栏的代码如下:

BOOL CStandardBar::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

{

        BOOL bRet = CToolBarCtrl::Create(dwStyle, rect, pParentWnd, nID);     //记录基类的返回值

        m_nButtonCount = IDSTR_OUT - IDSTR_XSDJ + 1;

        SetBitmapSize(CSize(32,32));        //设置单个位图的大小

        VERIFY(AddBitmap(m_nButtonCount,IDR_STANDARDBAR) != -1);     //添加位图

        m_pTBButtons = new TBBUTTON[m_nButtonCount];     //用来加入到工具栏里的按钮

        for (int nIndex = 0; nIndex < m_nButtonCount; nIndex++)     //循环设定按钮属性

        {

                CString string;

                string.LoadString(nIndex + IDSTR_XSDJ);      //装载字符串资源

                //为每一个字符串再加一个'\0',用于向工具栏里加字符串

      int nStringLength = string.GetLength() + 1;

      TCHAR * pString = string.GetBufferSetLength(nStringLength);

                pString[nStringLength] = 0;

                pString[nStringLength-1] = 0;

                VERIFY((m_pTBButtons[nIndex].iString = AddStrings(pString)) != -1);     //返回字符串的编号

      string.ReleaseBuffer();

                m_pTBButtons[nIndex].fsState = TBSTATE_ENABLED;

                m_pTBButtons[nIndex].fsStyle = TBSTYLE_BUTTON;

                m_pTBButtons[nIndex].dwData = 0;

                m_pTBButtons[nIndex].iBitmap = nIndex;             //控制按钮的相关图片

                m_pTBButtons[nIndex].idCommand = nIndex + IDSTR_XSDJ;      //用于命令消息传递

        }

                m_pTBButtons[m_nButtonCount-1].idCommand=IDOK; //用来响应退出消息

        TBBUTTON sepButton;            //用于分隔的按钮

        sepButton.idCommand = 0;

        sepButton.fsStyle = TBSTYLE_SEP;

        sepButton.fsState = TBSTATE_ENABLED;

        sepButton.iString = 0;

        sepButton.iBitmap = 0;

        sepButton.dwData = 0;

        for (nIndex = 0; nIndex < m_nButtonCount; nIndex++)

        {

                VERIFY(AddButtons(1,&m_pTBButtons[nIndex]));     //循环添加按钮

                if (!((nIndex +1) % 3))

                {

                        VERIFY(AddButtons(1,&sepButton));     //每3个按钮为一组,两组间有一个分隔按钮

                }

        }

        return bRet;     //返回CToolBarCtrl::Create的返回值

}

(5)调用工具栏类。先在类CA1Dlg中实例化CStandardBar的对象。

CStandardBar m_StandardBar;

别忘了在这个文件里包含CStandardBar类声明所在的头文件。

#include "StandardBar.h"

(6)增加消息WM_CREATE的响应函数,为CStandardBar对象创建相应窗口。

int CA1Dlg::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

        if (CDialog::OnCreate(lpCreateStruct) == -1)

                return -1;

                m_StandardBar.Create(WS_BORDER | WS_VISIBLE | WS_CHILD

                        | TBSTYLE_WRAPABLE| CCS_TOP| CCS_ADJUSTABLE, 

                CRect(0,0,0,0),this, IDR_STANDARDBAR1);

        m_StandardBar.AutoSize();       //重新计算控件的大小

        return 0;

}

工具栏到这里就创建成功了。

 

3.为菜单和工具栏添加消息处理函数

在此之前,定义了菜单和工具栏界面,单击他们并没有实质的内容,现在为他们添加消息处理函数。

(1)单击菜单和工具栏按钮的两种消息都是命令消息,所以,只要让他们传递相同的消息,就能执行相同的消息处理函数。从代码中可以看到对于工具栏的按钮来说,按钮的命令消息值与字符串资源符号的值相同,而且是顺序的,而对于相应的菜单项来说,消息值是随机的顺序值。为了将两者对应起来,要修改菜单项的资源符号,将其改为相应的按钮的字符串资源符号。例如:将销售登记菜单项的资源符号改为IDSTR_XSDJ,并给它定义消息响应函数:void CA1Dlg::OnXsdj()。这样,无论是单击“销售登记”菜单项,还是单击“销售登记”按钮都会执行这个函数。同理,完成其他的菜单项与按钮的对应。

(2)还有一个问题:别忘了,工具栏中有一个“退出”按钮。这个退出按钮与谁对应呢?当用户按下〈Enter〉键或〈Esc〉键时,对话框就会退出,这里触发的两个消息分别是IDOK和IDCANCEL。如果给“退出”按钮的命令消息值赋值为IDOK,那么单击该按钮时,对话框就会退出。代码如下:

m_pTBButtons[m_nButtonCount-1].idCommand=IDOK;     //用来响应退出消息

4.状态栏设计

为使应用程序操作界面更加友好,可以使用状态条显示程序当前程序的状态信息或提示信息。在VC中提供了CStatusBarCtrl类显示状态栏。在本程序中利用状态条显示操作者、日期、时间等信息。其实还有CStatusBar类可以显示状态栏,但是这个类只能用于主框架(CFrameWnd)上。

分析功能:显示操作员名字、公司名称及时间。其中,公司名称是常量字符串,可以将其加为字符串资源;操作员名字,是登录的用户名,将其放在应用程序类中,这样,就可以在程序的任何地方都可以访问了;时间,需要设置OnTimer时间来处理当时间改变时,刷新显示。

(1)解决创建状态栏的相关问题

① 访问应用程序对象。

CA1App* app=( CA1App *)::AfxGetApp();

//app是应用程序对象指针,可以访问应用程序对象的成员变量,例如:

MessageBox(app->m_sUserName);

② 访问资源字符串。

先在“Workspace”中的“Resource View”选项中建立字符串资源,定义符号IDS_COMPANY,对应资源为“明日腾龙科技有限责任公司(www.mingrisoft.com)”。

在程序中使用如下代码:

CString str;

str.LoadString(IDS_COMPANY);

str保存相应的字符串资源。

③ 将访问时间转化为字符串。

CTime t=CTime::GetCurrentTime();

        CString s=t.Format("%H:%M:%S");

         s="当前系统时间:"+s;

 

s会显示为“当前系统时间:20:09:25”形式的字符串。

④ OnTimer消息响应函数。

为实现每隔1秒刷新一次状态栏的显示内容,可以使用WM_TIMER的消息响应函数OnTimer。要创建主窗体的OnTimer函数,首先在“Workspace”工作区“Class View”选项卡中右键单击“CA1Dlg”选项,在弹出菜单中选择“Add Windows Message Handle...”菜单项,将弹出“New Windows Message and event handles for class CAIDlg”对话框。在该对话框中,可以选择要进行处理的消息句柄,并为其添加消息响应函数。步骤是从左边的列表框中双击

“WM_TIMER”,将其添加到右上边的列表框中,双击该列表框中的“WM_TIMER”项,可以看到VC++创建的函数OnTimer,可以设置这个函数每隔一定的时间响应一次。函数SetTimer来设置OnTimer的消息响应频率。

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );

参数说明:

nIDEvent:用来标识是哪一个Timer事件。

nElapse:设置该Timer事件每隔多长时间发生一次,单位:毫秒。

lpfnTimer:设置回调函数,用来响应事件的发生,相当于OnTimer函数。如果将其设为NULL,那么,WM_TIMER事件由窗口类来处理,即由OnTimer函数处理。

在本程序中,设定OnTimer函数1000毫秒响应一次。

SetTimer(12,1000,NULL);

⑤状态栏类CStatusBarCtrl的使用。

CRect rect;

this->GetClientRect(&rect);

int indicators[3];

indicators[0]=rect.Width()/2;

indicators[1]=rect.Width()*3/4;

indicators[2]=rect.Width();

m_StatusBarCtrl.SetParts(3,indicators); 

以上代码用来初始化状态栏,函数SetParts用来设定该状态栏由几个面板组成,每个面板的宽度。实际上,该函数用整型数组作参数,数组元素的值代表面板的宽度。

函数SetText用来设定每个面板上显示的数据,代码如下:

lBOOL SetText( LPCTSTR lpszText, int nPane, int nType );

参数说明:

lpszText:是该面板的字符串。

nPane:是面板编号(基于0)。

nType:是面板风格,该参数一般为0。

(2)创建状态栏。

创建状态栏的操作步骤如下:

①在CA1Dlg的声明中定义CStatusBarCtrl的对象:CStatusBarCtrl*m_StatusBarCtrl。

②在CA1Dlg的初始化函数中初始化状态栏对象,代码如下:

BOOL CA1Dlg::OnInitDialog()

{

        CDialog::OnInitDialog();

        SetIcon(m_hIcon, TRUE);              //Set big icon

        SetIcon(m_hIcon, FALSE);            //Set small icon

       

        //取得系统时间

        CTime t=CTime::GetCurrentTime();

        CString s=t.Format("%H:%M:%S");

        s="当前系统时间:"+s;    

//为状态栏创建窗体

        CRect rect;

        this->GetClientRect(&rect);

        m_StatusBarCtrl.Create(WS_CHILD|WS_VISIBLE|CCS_BOTTOM,

                rect,this,ID_STATUS_BAR_CTRL);

                //设置状态栏的显示区间数,及相应宽度

        int indicators[3];

        indicators[0]=rect.Width()/2;

        indicators[1]=rect.Width()*3/4;

        indicators[2]=rect.Width();

    m_StatusBarCtrl.SetParts(3,indicators);

        //显示各区间文本

        str.LoadString(IDS_COMPANY);

        m_StatusBarCtrl.SetText(str,0,0);

      CA1App * app=( CA1App *)::AfxGetApp();

    m_StatusBarCtrl.SetText("当前操作员:"+app->m_sUserName,1,0);

        m_StatusBarCtrl.SetText(s,2,0);

        //令CA1Dlg每一秒钟触发一次OnTimer事件,修改系统时间

    this->SetTimer(12,1000,NULL); 

        return TRUE;     //return TRUE  unless you set the focus to a control

}

③处理WM_TIMER消息的消息响应函数OnTimer,代码如下:

void CA1Dlg::OnTimer(UINT nIDEvent)

{

        CTime t=CTime::GetCurrentTime();

        CString s=t.Format("%H:%M:%S");

        s="当前系统时间:"+s;

        this->m_StatusBarCtrl.SetText(s,2,0);  

        CDialog::OnTimer(nIDEvent);

}

5.客户区设计

一个优秀的商业管理系统,不但要有实用的功能,还要有漂亮友好的界面。在本例中,设置窗体背景只需加入一个Bitmap资源,运行效果如图3.3所示,其操作步骤如下:

(1)在资源对话框上放一个Picture控件。

(2)然后加入一个需要的Bitmap资源,如果图片包含的颜色超过256种,那么它会提示该图片不能在资源编辑器里编辑,不用管它,除非真的想编辑它,加进来的图片的资源符号缺省为IDB_BITMAP1。

(3)设置Picture控件属性类型为Bitmap,图像为IDB_BITMAP1。

4.2  系统登录管理

1.实现目标

程序启动后,首先进入系统登录程序验证用户密码。系统登录程序主要实现如下功能。

q         输入密码的控件采用文本框。密码如果输入正确,取得用户权限并进入系统,否则,将提示错误,并返回密码输入框。

q         记录错误次数,录入密码错误3次将自动退出系统。

q         用户按下〈Enter〉键,控制焦点的移动。

系统登录程序运行结果如图8所示。

 

图8  系统登录窗口

2.设计步骤

(1)增加对话框资源,设计窗体资源符号为IDD_DIALOG_LOGIN。

(2)为该窗口连接相关的类,在资源对话框上右键单击,选择“ClassWizard...”选项,会弹出“Add a class”对话框,提示IDD_DIALOG_LOGIN是一个新的资源,需要为这个资源创建一个类,或为它连接一个现有类。在此,选择新建一个派生于CDialog的类来连接该资源,该类命名为CDlgLogin。

(3)向窗口中添加图片、编辑框、静态文本、按钮等资源,设置主要资源属性,如表2所示。

表2                                相关资源设置

资源名称

资源符号

资源对应的变量

资源属性

Bitmap

JIEMIAN

来自于文件jiemian.bmp

Picture

IDC_STATIC

类型Bitmap,图像JIEMIAN

Button

IDOK

缺省

Button

IDCANCEL

缺省

Static Box

IDC_STATIC

标题为:请输入用户名和密码:

Static Box

IDC_STATIC

标题为:用户名:

Static Box

IDC_STATIC

标题为:密码:

Edit Box

IDC_EDIT_NAME

CEdit m_editUserName

取消Border,选上【Static edge”

Edit Box

IDC_EDIT_PASSWORD

CEdit m_editPassWord

取消Border,选上【Static edge”和【PassWord”

3.程序相关代码

系统登录程序要在显示主窗体之前显示,系统登录程序代码如下:

BOOL CMedApp::InitInstance()

{

......

        LRunSql::InitConnectPtr();     //初始化COM环境,进行数据库连接

        CDlgLogin dlg;     //创建登录窗口对象

        if(dlg.DoModal()==IDOK)     //显示登录窗口

        {

                        CA1Dlg *pdlg=new CA1Dlg;     //创建主窗体对象

                        m_pMainWnd = pdlg;

                        pdlg->DoModal();      //显示主窗体

                        delete pdlg;

                        pdlg=NULL;

        }

        LRunSql::Close();           //断开数据库连接

return FALSE;

}

当用户单击“确定”按钮时,进行密码判断和次数判断。处理“确定“按钮的消息响应函数如下:

void CDlgLogOn::OnOK()

{

        BOOL bLogOn=FALSE;

        CString sUserPassWord, sPurview,sUserName,sInputPassWord;

        LRunSql m_runsql;

        CString sql;

        _variant_t value;

        CString sError;

        //更新数据变量

        this->m_editPassWord.GetWindowText(sInputPassWord);

        this->m_editUserName.GetWindowText(sUserName);

        CA1App* App=(CA1App *)AfxGetApp();

        sql.Format(

"select admi_password,admi_purview from tabpurview where admi_name='%s'",

                sUserName);

        if(m_runsql.CheckSQLResult(sql))

        {

                value=m_runsql.m_recordset->GetCollect("admi_password");

                if(value.vt!=VT_NULL)

                        sUserPassWord=(char*)(_bstr_t)value;

              value=m_runsql.m_recordset->GetCollect("admi_purview");

                if(value.vt!=VT_NULL)

                        sPurview=(char*)(_bstr_t)value;

                if(sUserPassWord==CCrypt::Encrypt(sInputPassWord,123))

                {

                        App->m_sUserName=sUserName;

                        App->m_sPurview=sPurview;

                        bLogOn=TRUE;

                }

                else

                {

                        sError="请重新输入密码。\n注意大小写!","密码错误";

                        this->m_editPassWord.SetFocus();

                }

        }

        else

        {

                sError="请确认用户名大小写是否正确!","无此用户";

                this->m_editUserName.SetFocus();

        }

        if(bLogOn) EndDialog(IDOK);

        else

        {     

                        m_iLogOnCount++;

                        if(m_iLogOnCount>=3)

                                this->EndDialog(0);

                        else

                                MessageBox(sError);

        }

}

进行焦点控制。定义控制焦点的函数,该函数定义了当用户单击〈Enter〉键时,焦点改变的顺序,当需要改变焦点时,调用此函数。

bool CDlgLogOn::SetTheFocus()

{

        HWND hwnd=::GetFocus();

     UINT id=::GetDlgCtrlID(hwnd);

        switch(id)

        {

        case IDC_EDIT_NAME:

                this->m_editPassWord.SetFocus();

      ::PostMessage(m_editPassWord.GetSafeHwnd(),WM_KEYDOWN,VK_END,0);

                return true;

        case IDC_EDIT_PASSWORD:

      this->GetDlgItem(IDOK)->SetFocus();

                return true;

        case IDOK:

                this->OnOK();

                return true;

        case IDCANCEL:

                this->OnCancel();

                return true;

        }

        return false;

}

在消息预处理函数中调用此函数。

BOOL CDlgLogOn::PreTranslateMessage(MSG* pMsg)

{

        if(pMsg->message==WM_KEYDOWN&&pMsg->wParam==13)

        {

                if(this->SetTheFocus())

                        return true;

        }

        return CDialog::PreTranslateMessage(pMsg);

}

//该函数返回True,表示该消息已被处理

4.3  商品销售管理

1.实现目标

q         自动生成销售日期及销售票号。

q         确定用户输入数据完毕后,可提交数据给数据库。

q         自动核算销售金额。

q         支持打印功能。

销售登记模块运行结果如图9所示。

 

图9   销售登记模块运行结果

2.设计步骤

(1)新建一个窗口类,名称为CDlgXSDJ,对话框资源ID为IDD_DIALOG_XSDJ。

(2)在窗口中添加编辑框、组合框、静态文本、按钮、时间控件等资源。

(3)设置主要资源属性,如表3所示。

表3                                                       对话框及相关资源设置

资源名称

资源符号

资源对应的变量

资源属性

Dialog

IDD_DIALOG_XSDJ

CDlgXSDJ dlg(true)

标题:销售登记

字体名称:楷体_GB2312

字体大小:12

Combo Box

IDC_COMBO1

CComboBox m_comboJsfs

Type:DropList

Combo Box

IDC_COMBO2

CComboBox m_comboJsr

Type:DropList

Edit Box

IDC_EDIT_KHID

CGeneralEdit m_editKhId

取消Border

Edit Box

IDC_EDIT_KHNAME

CGeneralEdit m_editKhName

取消Border

Edit Box

IDC_EDIT1

CString m_strPh

CEdit m_editPh

取消Border,选上Read Only

Edit Box

IDC_EDIT_SS

 

 

Picture

IDC_STATIC

 

Type:Frame

Color:Black

Static Box

IDC_STATIC_SS

 

标题为:实收:

Static Box

IDC_STATIC

 

标题为:总金额:

Static Box

IDC_STATIC_ZJE

 

标题为:0.00

Static Box

IDC_STATIC_PH

标题为:销售登记票号:

Date Time Picker

IDC_DATETIMEPICKER1

CDateTimeCtrl  m_tcRq

格式:Short Date

选上:Static edge

Button

IDC_BUTTON1

标题为:删除

Button

IDC_BUTTON_OK

CButton m_btnOk

标题为:确定

Button

IDC_BUTTON_PRINT

标题为:打印...

Button

IDCANCEL

标题为:退出

3.程序相关代码

(1)创建左上角的标题信息。

① 创建白色背景。

以背景作父窗体,让其他的控件作它的子窗体。这里需要在CDlgXSDJ的头文件中声明CWhiteFrame对象。

CWhiteFrame    m_whiteframe;

在OnCreate的成员函数中创建该对象对应的窗口。

int CDlgXSDJ::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

        if (CDialog::OnCreate(lpCreateStruct) == -1)

                return -1;

        CRect rect;

        this->GetClientRect(&rect);

        rect.left+=10;

        rect.right-=250;

        rect.top+=10;

        rect.bottom=200;

        m_whiteframe.Create("",WS_CHILD|WS_TABSTOP|WS_VISIBLE,rect,this,700);

        return 0;

}

② 创建标题部分的控件。

该部分的控件都以上述的白色背景窗口为父窗口,要注意以下几点:

               在这个背景上创建静态文本,且文本背景是白色。对此,同样使用CWhiteFrame类,并用该类定义一组静态文本对象,要注意创建文本的位置,使其与相应的控件相配合。例如:确保静态文本“客户编号”与相应的编辑框在同一个水平高度;日期与日期控件在同一个高度。

               由于入库与销售大部分内容是相同的,所以,用一个窗体来执行这两部分功能。为此,用Bool型的变量m_bXs来表示这两种区别,并在代码中要适时修改一些属性。

               改变设计时添加的控件的父窗体要用到其成员函数SetParent。方式如下:

m_tcRq.SetParent(&m_whiteframe);

               对于结算方式下拉列表框和经手人下拉列表框,需要从数据库中取得相应的数据来初始化这两个窗口,并在最后为这两个下拉列表框确定初值。

               初始化时间。

CTime m_dateRq = CTime::GetCurrentTime();      //初始化时间

        this->m_tcRq.SetTime(&m_dateRq);

创建和初始化标题部分的控件的代码如下:

bool CDlgXSDJ::InitBaseInfo()

{

        CRect rect;CString str;

        wf=new CWhiteFrame[5];

        CString  strs[5];        //该字符串保存静态文本

        if(m_bXs)            //判断当前是销售登记,还是入库登记

        {

                strs[0]="客户编号:";

                strs[1]="客户名称:";

        }

        else

        {

                this->GetDlgItem(IDC_STATIC_PH)->SetWindowText("入库登记票号:");

                this->SetWindowText("入库登记:");

                strs[0]="供应商编号:";

                strs[1]="供应商名称:";

        }

        strs[2]="结算方式:";

        strs[3]="经手人:";

        strs[4]="日期:";

        rect=CRect(10,40,80,55);      //用来确定静态文本的位置

        for(int i=0;i<5;i++)

        {      //创建静态文本

                wf[i].Create(strs[i],WS_CHILD|WS_VISIBLE,rect,&m_whiteframe);

                wf[i].Invalidate();

                rect.OffsetRect(0,28);

        }

        m_tcRq.SetParent(&m_whiteframe);      //改变标题控件的父窗口

        m_comboJsr.SetParent(&m_whiteframe);

        m_editKhName.SetParent(&m_whiteframe);

        m_editKhId.SetParent(&m_whiteframe);

        m_comboJsfs.SetParent(&m_whiteframe);

        //init time;

        CTime m_dateRq = CTime::GetCurrentTime();      //初始化时间

        this->m_tcRq.SetTime(&m_dateRq);

        //init jsr;       //初始化经手人下拉列表框

        CString sql;

        sql.Format("select yg_name from tabyginfo");

        if(!m_runsql.CheckSQLResult(sql))return false;

        _variant_t value;

        while(!m_runsql.m_recordset->adoEOF)       //从数据库取值初始化员工下拉列表框

        {

                value=m_runsql.m_recordset->GetCollect("yg_name");

if(value.vt!=VT_NULL)

                this->m_comboJsr.AddString((char*)(_bstr_t)value);

                m_runsql.m_recordset->MoveNext();

        }

        //init jsfs;

        sql.Format("select jsfs from tabjsfs");

        this->m_runsql.RunSQL(sql);

        while(!m_runsql.m_recordset->adoEOF)      //从数据库取值初始化经手人下拉列表框

        {

                value=m_runsql.m_recordset->GetCollect("jsfs");

                if(value.vt!=VT_NULL)

                this->m_comboJsfs.AddString((char*)(_bstr_t)value);

                m_runsql.m_recordset->MoveNext();

        }

        this->m_comboJsfs.SetCurSel(0);

        this->m_comboJsr.SetCurSel(0);

       

return true;

}

③ 为客户编号和客户名称编辑框连接自动提示窗口。

对于客户信息和供应商信息,这两者信息格式大部分是相同的,并且每次是用到其中的一组数据时都需要用到自动提示窗口,所以专门作出一个类CGeneralEdit来对应着两组信息,并包含对自动提示窗口的使用。该类派生自CEdit(参看类图2.12)。在此之前,请参看关键技术中的5.2.2关于显示自动提示窗口的介绍。

功能分析:

               界面上看,需要有下划线和白色背景。

               当把焦点移到某个编辑框上时原有弹出的自动提示窗口消失。

               当在编辑框中进行编辑时引起EN_CHANGE事件时,弹出自动提示窗口。

               焦点移走时,让自动提示窗口消失;但是如果焦点移动到了自动提示窗口上,则自动提示窗口不消失。

               当父窗体移动时,自动提示窗口也跟着移动。

               当在编辑框中进行编辑时,按〈↑〉、〈↓〉、〈Page Up〉、〈Page Down〉键,自动提示窗口会响应这些消息,且此时焦点还在编辑框中。

当用户在其中一个编辑框中按〈Enter〉键,表示确定了选择某一个客户(编号或名称),

 

               那么另一个编辑框中的内容也随之改变。

               当用户在其中一个编辑框中单击〈Esc〉键,表示用户放弃进行编辑,取消自动弹出对话框。

为了实现功能2,写消息WM_SETFOCUS的消息响应函数OnSetFocus。

void CGeneralEdit::OnSetFocus(CWnd* pOldWnd)

{

        ASSERT(this->m_poplist!=NULL);

        m_poplist->ShowWindow(SW_HIDE);

        CEdit::OnSetFocus(pOldWnd);

}

对于功能3,要写EN_CHANGE消息响应函数OnChange。

在编写EN_CHANGE消息响应函数OnChange时,需要注意:

               客户编号编辑框和客户名称编辑框都是由一个类定义出来的,使用资源符号来区分这两个对象,如IDC_EDIT_KHID对应着客户编号编辑框,而IDC_EDIT_KHNAME则对应着客户名称编辑框对象。

               当编辑编号时,直接用当前编辑框的字符串来刷新自动提示窗口,当编辑名称时,还需要参考编号编辑框的值,来显示自动提示窗口的内容。

               如果自动提示窗口已经显示了,就没有必要重复计算显示。

代码如下:

void CGeneralEdit::OnChange()

{

        if(m_bSetValue) return;

        ASSERT(this->m_poplist!=NULL);

        CString str;

        this->GetWindowText(str);       //取得当前编辑框的字符串

        int iID=this->GetDlgCtrlID();       //取得当前编辑框对象的资源符号的值

                if(iID==IDC_EDIT_KHID)      //判断是编号对象,还是名称对象

                {

                this->m_poplist->RefreshListCtrlView(0,str);      //刷新自动提示窗口数据

                }

                else if(iID==IDC_EDIT_KHNAME)

                {

                        CString id;

                        CWnd * editid;

                        editid=this->GetParent()->GetDlgItem(IDC_EDIT_KHID);

                        editid->GetWindowText(id);

                        m_poplist->RefreshListCtrlView(1,str,id,0);     //根据两个字符串刷新自动提示窗口

                }

        if(!m_poplist->IsWindowVisible())                   //如果自动提示窗口已显示,就不用再设定位置、显示

        {

                this->GetWindowRect(&m_rectWindow);

                this->m_poplist->SetShowPosition(this->m_rectWindow,this);

                m_poplist->ShowListPop();

        }

}

对于功能4、6、7、8,需要编写预解释函数PreTranslateMessage(MSG* pMsg)和 WM_KILLFOCUS的消息响应函数OnKillFocus,功能分析如下:

               编辑框失去焦点的时候,会调用函数OnKillFocus(CWnd*pNewWnd),此时,需要判断是否焦点转移到了自动提示窗口上,pNewWnd就是焦点转移到的窗口指针,判断这个指针是谁就行了。

               当用户按〈Enter〉键,缺省情况下,对话框处理这个消息并最终执行OnOk函数,关闭对话框。需要在预解释函数中处理这个消息,并把它解释为WM_KILLFOCUS,并设定一个开关变量标识这个消息,在OnKillFocus函数中收到并处理这个消息。

               当用户按〈Esc〉键时,需要让自动提示窗口消失,同时将〈Enter〉键解释为无关紧要的键如〈Ctrl〉键。

               当用户按〈↑〉、〈↓〉、〈Page Up〉、〈Page Down〉键时,将这个消息原封不动地发送给自动提示窗口,自动提示窗口会响应这些消息(此时焦点还在编辑框中)。

BOOL CGeneralEdit::PreTranslateMessage(MSG* pMsg)

{

        //TODO: Add your specialized code here and/or call the base class

        if(pMsg->message==WM_KEYDOWN)                   //捕捉键盘某个键按下事件

        {

                switch(pMsg->wParam)

                {

                case 13:          //捕捉〈Enter〉键

                {

                        pMsg->message=WM_KILLFOCUS;             //将该消息改为WM_KILLFOCUS消息

                        m_Kill=RETURNKILL;              //设置开关变量,将在OnKillFocus中使用

                        break;

                }

                case 27:        //捕捉〈Esc〉键

                {

                        if(m_poplist->IsWindowVisible())      //如果自动提示窗口处于显示状态,隐藏它

                        {

                        this->m_poplist->ShowWindow(false);

                        pMsg->wParam=VK_CONTROL;      //将该消息改为〈Ctrl〉键

                        }

                        break;

                }

                case VK_UP:      //捕捉〈↑〉、〈↓〉、〈Page Up〉、〈Page Down〉键

                case VK_DOWN:

                case VK_NEXT:

                case VK_PRIOR:

                        {      //将这些消息由自动提示窗口处理

                                this->m_poplist->SendTheUDNPMessage(pMsg->wParam);

                                pMsg->wParam=VK_CONTROL;      //并将该消息解释为无用的键

                                break;

                        }

                }

        }

        return CEdit::PreTranslateMessage(pMsg);

}

下面看看自动提示窗口是如何处理〈↑〉、〈↓〉、〈Page Up〉、〈Page Down〉键的。

void CListCtrlPop::SendTheUDNPMessage(UINT KEY)

{

        ::SendMessage(this->m_listctrl->GetSafeHwnd(),WM_KEYDOWN,KEY,0);

        m_listctrl->SetHotItem(m_listctrl->GetSelectionMark());

}

自动提示窗口将这些消息原封不动地发送给了其中的列表控件。

调用完消息与解释函数后,消息被预解释了,可以处理这些解释后的消息。

void CGeneralEdit::OnKillFocus(CWnd* pNewWnd)

{     

        CEdit::OnKillFocus(pNewWnd);

        int iID=this->GetDlgCtrlID();         //取得当前编辑框对象的资源符号值

        switch(m_Kill)                //判断是不是经过处理的〈Enter〉键按下的消息

        {

        case RETURNKILL:              //〈Enter〉键按下后

                {

                        ASSERT(this->m_poplist!=NULL);      //判断自动提示窗口对象指针有效

                        if(m_poplist->IsWindowVisible()&&m_poplist->GetSelectedMark()>=0)

                        {    //判断自动提示窗口是可视的,并且有某个记录被选择

                        //接下来,进行从提示对话框中取值赋值给编辑框

                                this->m_poplist->ShowWindow(SW_HIDE);

                                CGeneralEdit* edit;

                                //根据资源编号取得对象指针

                                edit=(CGeneralEdit* )this->GetParent()->GetDlgItem(IDC_EDIT_KHNAME);

                                edit->SetValueUnOnChange(m_poplist->GetListCtrlSel(1));

                                edit->Invalidate();      //刷新编辑框

                                edit=(CGeneralEdit* )this->GetParent()->GetDlgItem(IDC_EDIT_KHID);

                                edit->SetValueUnOnChange(m_poplist->GetListCtrlSel(0));

                                edit->Invalidate();

                        }

                        this->m_poplist->ShowWindow(SW_HIDE);      //隐藏窗口

                        //让对话框窗口设定焦点该由谁获得

::PostMessage(this->GetParent()->GetParent()->GetSafeHwnd(),

MYMESSAGE_XSDJ_SETTHEFOCUS,0,0);

                        break;

                }

        default:

//此时,需要判断是否焦点转移到了自动提示窗口或自动提示窗口中的列表上,如果是,就不隐藏

//自动提示窗口,否则,隐藏自动提示窗口

                if(pNewWnd!=NULL)

                if(pNewWnd->GetDlgCtrlID()==m_poplist->GetDlgCtrlID()||

                        pNewWnd->GetDlgCtrlID()==ID_POP_LISTCTRL);

                else this->m_poplist->ShowWindow(SW_HIDE);

                break;

        }

        m_Kill=NORMALKILL;      //恢复开关变量

        //TODO: Add your message handler code here

}

(2)自动产生销售编号。

CString CDlgXSDJ::ChanShengXSDJPH()

{

        this->UpdateData();

        CString sql;

        _bstr_t sql_;

        _variant_t value;

        CString s_value;

        CString s_date;

        CString ph;

//开始取得时间,并将其加入票号中

        CTime m_tRq;

        this->m_tcRq.GetTime(m_tRq);

        this->m_tcRq.GetWindowText(s_date);

        CString y_date,m_date,d_date;

        y_date=m_tRq.Format("%Y");

        m_date.Format("%02d",m_tRq.GetMonth());

        d_date.Format("%02d",m_tRq.GetDay());

       

ph=y_date+"-"+m_date+"-"+d_date;

        if(this->m_bXs)      //判断是销售登记还是入库登记

        {

                ph=ph+"xsd";

//从数据库中取得最大的编号

                sql.Format("select xs_ph from tabxsph where rq=#%s# order by xs_ph desc",s_date);

                if(m_runsql.CheckSQLResult(sql))

                {

                        value=m_runsql.m_recordset->GetCollect("xs_ph");

                        if(value.vt!=NULL)

                        {

                                svalue=(char*)(_bstr_t)value;

                                svalue=s_value.Mid(s_value.GetLength()-4,4);

                        }

                }

                else

                {

                svalue="0";

                }

        }

        else

        {

                ph=ph+"rkd";

                sql.Format("select rk_ph from tabrkph where rq=#%s# order by rk_ph desc",s_date);

                if(m_runsql.CheckSQLResult(sql))

                {

                        value=m_runsql.m_recordset->GetCollect("rk_ph");

                        if(value.vt!=NULL)

                        {

                                svalue=(char*)(_bstr_t)value;

                                svalue=s_value.Mid(s_value.GetLength()-4,4);

                        }

                }

                else

                {

                svalue="0";

                }

        }

//格式化产生的新编号

        long lvalue=atoi(s_value)+1;

        s_value.Format("%04d",l_value);

        ph=ph+s_value;

        return ph;       

}

(3)创建列表控件。

初始化列表控件,设置列表控件的大小、风格、列标题、可编辑的列号。读者可参看本章5.7节专题技术,其中详细介绍了功能强大的列表控件。

bool CDlgXSDJ::InitnListXSDJ()

{

        CRect rect_nlist(10,300,700,650);

        this->GetClientRect(&rect_nlist);

        rect_nlist.top=210;

        rect_nlist.bottom=480;

        this->m_plistXSDJ=new CListXSDJ(m_bXs);

        m_plistXSDJ->Create(WS_CHILD|WS_VISIBLE|LVS_REPORT|WS_BORDER,

rect_nlist,this,ID_NEWLISTLSXSDJ);

        this->m_plistXSDJ->ModifyStyle(LVS_EDITLABELS, 0L);      //禁止标题编辑

        m_plistXSDJ->ModifyStyle(0L, LVS_REPORT);        //设为Report类型

        m_plistXSDJ->ModifyStyle(0L, LVS_SHOWSELALWAYS);             //始终高亮显示被选中的项

        m_plistXSDJ->ModifyStyle(0L, LVS_NOSORTHEADER);

        m_plistXSDJ->ModifyStyle( LVS_OWNERDRAWFIXED,0L);

        m_plistXSDJ->SetExtendedStyle( LVS_EX_FULLROWSELECT |         //允许整行选中

                LVS_EX_GRIDLINES |      //画出网格线

                LVS_EX_FLATSB         //扁平风格的滚动条

                );     

        CString str;

                //1、先产生numTitle列

        int i=0;

        m_plistXSDJ->InsertColumn( i, "药品编号" );

        m_plistXSDJ->InsertColumn( 1, "药品名称" );

        m_plistXSDJ->InsertColumn( 2, "仓库名称" );

        m_plistXSDJ->InsertColumn( 4, "数量" );

        m_plistXSDJ->InsertColumn( 5, "总金额" );

        if(m_bXs)

        {

        m_plistXSDJ->InsertColumn( 3, "单价" );

        m_plistXSDJ->InsertColumn( 6, "实收" );

        }

        else

        {

        m_plistXSDJ->InsertColumn( 3, "进价" );

        m_plistXSDJ->InsertColumn( 6, "实付" );

                }

//2、插入1行

                m_plistXSDJ->InsertItem(0,"");

        //3、调整列宽

        RECT rect2;

        m_plistXSDJ->GetWindowRect(&rect2);

        int wid = rect2.right - rect2.left;

        for( i=0; i<7; i++ )

        {

                m_plistXSDJ->SetItemText(0,i,"");

                m_plistXSDJ->SetColumnWidth( i, wid/7 );

        }

        m_plistXSDJ->SetSubItemCanEdited(0);

        m_plistXSDJ->SetSubItemCanEdited(1);

        m_plistXSDJ->SetSubItemCanEdited(2);

        m_plistXSDJ->SetSubItemCanEdited(3);

        m_plistXSDJ->SetSubItemCanEdited(4);

        m_plistXSDJ->SetSubItemCanEdited(5);

        m_plistXSDJ->SetSubItemCanEdited(6);

        m_plistXSDJ->Invalidate(false);

        return true;

}

4.4  商品入库管理

1.实现目标

q         系统自动生成入库时间及入库票号。

q         确定用户输入数据完毕后,可提交给数据库。

q         自动核算入库金额,支持打印功能。

q         删除当前指针所对应的记录信息。

入库登记模块运行结果如图10所示。

 

图10  入库登记模块运行结果

2.设计步骤

(1)新建一个对话框,对话框资源ID为IDD_DIALOG_RKDJ。

(2)在窗口中添加编辑框、组合框、静态文本、按钮、时间控件等资源。

(3)设置对话框标题属性为“入库登记”,其他控件属性保持默认设置。

3.代码分析

(1)设计对话框及相关资源。

对话框及相关资源设置与销售模块相同。

定义bool类型的变量m_bXs,在该类的构造函数中对这两个模块加以区分。

CDlgXSDJ::CDlgXSDJ(bool isxs,CWnd* pParent /*=NULL*/)

        : CDialog(CDlgXSDJ::IDD, pParent)

   {

……

           this->m_bXs=isxs;

……

   }

(2)创建左上角的标题信息。

① 显示各种文本内容。

bool CDlgXSDJ::InitBaseInfo()

{

        CRect rect;CString str;

        wf=new CWhiteFrame[5];

        CString  strs[5];  //该字符串保存静态文本

        if(m_bXs)               //判断当前是销售登记,还是入库登记

        {

                strs[0]="客户编号:";

                strs[1]="客户名称:";

        }

        else

        {

                this->GetDlgItem(IDC_STATIC_PH)->SetWindowText("入库登记票号:");

                this->SetWindowText("入库登记:");

                strs[0]="供应商编号:";

                strs[1]="供应商名称:";

        }

……

}

② 为供应商编号和供应商名称编辑框连接自动提示窗口。

在这里的区别是:提示的内容来源不同,所以要进行不同的数据初始化。

bool CDlgXSDJ::InitStringLists()

{

         CString sql;

        _variant_t value;

        m_strlistKh=new CStringList[2];

       

        if(m_bXs)

        {

                m_strlistKh[0].AddTail("客户编号");

                m_strlistKh[1].AddTail("客户全称");

                sql.Format("select DISTINCT kh_id,kh_name from tabkh order by kh_id asc");

                if(!this->m_runsql.CheckSQLResult(sql))return false;

                while(!m_runsql.m_recordset->adoEOF)

                {

                         value=m_runsql.m_recordset->GetCollect("kh_id");

                        if(value.vt!=VT_NULL)

                        {

                                this->m_strlistKh[0].AddTail((char*)(_bstr_t)value);

                        }

                        value=m_runsql.m_recordset->GetCollect("kh_name");

                        if(value.vt!=VT_NULL)

                        {

                                this->m_strlistKh[1].AddTail((char*)(_bstr_t)value);

                        }

                        m_runsql.m_recordset->MoveNext();

                }

        }

        else

        {

                m_strlistKh[0].AddTail("供应商编号");

                m_strlistKh[1].AddTail("供应商全称");

                sql.Format("select DISTINCT gys_id,gys_name from tabgys order by gys_id asc");

                if(!this->m_runsql.CheckSQLResult(sql))return false;

                while(!m_runsql.m_recordset->adoEOF)

                {

                        value=m_runsql.m_recordset->GetCollect("gys_id");

                        if(value.vt!=VT_NULL)

                        {

                                this->m_strlistKh[0].AddTail((char*)(_bstr_t)value);

                        }

                        value=m_runsql.m_recordset->GetCollect("gys_name");

                        if(value.vt!=VT_NULL)

                        {

                                this->m_strlistKh[1].AddTail((char*)(_bstr_t)value);

                        }

                        m_runsql.m_recordset->MoveNext();

                }

        }

        this->m_popList.Create(this);

        m_popList.SelectStringList(2,this->m_strlistKh);

        this->m_editKhId.SelectPopList(&m_popList);

        this->m_editKhName.SelectPopList(&m_popList);

        return true;

}

(3)自动产生入库票号。

CString CDlgXSDJ::ChanShengXSDJPH()

{

        this->UpdateData();

        CString sql;

        _bstr_t sql_;

        _variant_t value;

        CString s_value;

        CString s_date;

        CString ph;

        CTime m_tRq;       //开始取得时间,并将其加入票号中

        this->m_tcRq.GetTime(m_tRq);

        this->m_tcRq.GetWindowText(s_date);

        CString y_date,m_date,d_date;

        y_date=m_tRq.Format("%Y");

        m_date.Format("%02d",m_tRq.GetMonth());

        d_date.Format("%02d",m_tRq.GetDay());

        ph=y_date+"-"+m_date+"-"+d_date;

        if(this->m_bXs)     //判断是销售登记还是入库登记

        {

                ph=ph+"xsd";

//从数据库中取得最大的编号

                sql.Format("select xs_ph from tabxsph where rq=’%s’ order by xs_ph desc",s_date);

                if(m_runsql.CheckSQLResult(sql))

                {

                        value=m_runsql.m_recordset->GetCollect("xs_ph");

                        if(value.vt!=NULL)

                        {

                                svalue=(char*)(_bstr_t)value;

                                svalue=s_value.Mid(s_value.GetLength()-4,4);

                        }

                }

                else

                {

                svalue="0";

                }

        }

        else

        {

                ph=ph+"rkd";

                sql.Format("select rk_ph from tabrkph where rq=’%s’order by rk_ph desc",s_date);

                if(m_runsql.CheckSQLResult(sql))

                {

                        value=m_runsql.m_recordset->GetCollect("rk_ph");

                        if(value.vt!=NULL)

                        {

                                svalue=(char*)(_bstr_t)value;

                                svalue=s_value.Mid(s_value.GetLength()-4,4);

                        }

                }

                else

                {

                svalue="0";

                }

        }  

        long lvalue=atoi(s_value)+1;     //格式化产生的新编号

        s_value.Format("%04d",l_value);

        ph=ph+s_value;

        return ph;       

}

(4)创建列表控件。

对于列表控件,需要注意第3列标题不同。

bool CDlgXSDJ::InitnListXSDJ()

{

……

        if(m_bXs)

        {

        m_plistXSDJ->InsertColumn( 3, "单价" );

        m_plistXSDJ->InsertColumn( 6, "实收" );

        }

        else

        {

        m_plistXSDJ->InsertColumn( 3, "进价" );

        m_plistXSDJ->InsertColumn( 6, "实付" );

       

        }

……

        return true;

}

(5)在对话框的初始化函数中进行数据初始化。

BOOL CDlgXSDJ::OnInitDialog()

{

        CDialog::OnInitDialog();

        CRect rect;

        InitStringLists();

        InitnListXSDJ();

        if(!this->InitBaseInfo())

        {

                MessageBox("初始化基本数据失败");

                return true;

        }

        this->m_sXsPh=m_strPh=this->ChanShengXSDJPH();

        this->UpdateData(false);

        m_editKhId.SetFocus();

        return FALSE;     //return TRUE unless you set the focus to a control

                        //EXCEPTION: OCX Property Pages should return FALSE

}     //返回值是FALSE表示焦点已被确定

(6)实现按钮功能。

入库登记模块有4个功能按钮:确定、打印、删除、退出。其中,打印、退出按钮与销售模块相同,这里不再重复。

“删除”按钮将会调用列表控件的成员函数DeleteSelected,该函数找到被选的行记录,取消该临时库存的相关信息,再删除该行,最后刷新列表。修改临时库存时,与销售登记的算法不同。

bool CListXSDJ::DeleteSelected()

{

        if(this->m_poplist->IsWindowVisible())      //隐藏不必要的窗口

                this->m_poplist->ShowWindow(SW_HIDE);

    if(this->m_edit.IsWindowVisible())

        m_edit.ShowWindow(SW_HIDE);

        LVITEM* item=new LVITEM;

        int number=this->GetSelectedCount();

        for(int i=this->GetItemCount()-1;i>=0;i--)      //从最后一行开始判断是否被选上

        {

                        this->m_nItem=i;

                        item->iItem=i;

                        item->mask=LVIF_STATE ;

                        item->stateMask=LVIS_SELECTED ;

                        GetItem(item);

                if(item->state==LVIS_SELECTED)

                {      //在临时库存中取消相关信息

                        CString s_sl=this->GetItemText(i,4);

                        long l_sl=atoi(s_sl);

                        if(m_iYpKcSelected[i]<0||l_sl==0)continue;

                        long kc_number

=atoi(m_pstrlistKc[3].GetAt(this->m_pstrlistKc[3].FindIndex(this->m_iYpKcSelected[i])));

                        CString left;

                        if(m_bXs)

                        left.Format("%d",kc_number+l_sl);

                        else

                        left.Format("%d",kc_number-l_sl);

                        this->m_pstrlistKc[3].SetAt(this->m_pstrlistKc[3].FindIndex(this->m_iYpKcSelected[i]),left);

                        DeleteItem(i);

                        this->m_iTheItemState[i]=NONE;

                }

        }

                if(GetItemCount()==0)

                        InsertItem(0,"");

                this->m_nItem=0;

                Invalidate();

        return true;

}

实现“”确定按钮,该按钮完成两个内容:检查用户输入数据是否有效及以事务形式提交数据库。提交数据库时,刷新的数据表不同,且刷新库存表时,算法不同。

void CDlgXSDJ::OnButtonOk()

{

        if(!CheckKh())

        {

                this->m_editKhId.SetFocus();

                if(m_bXs)

                MessageBox("请输入客户信息","注意");

                else

                MessageBox("请输入供应商信息","注意");

                return;

        }

        else if(!this->m_plistXSDJ->CheckAllItem())

        {

                if(m_bXs)

                        MessageBox("请输入完整客户登记信息","注意");

                else

                        MessageBox("请输入完整供应商登记信息","注意");

                this->m_plistXSDJ->m_edit.SetFocus();

                return;

        }

                CA1App * app=(CA1App*)AfxGetApp();

      this->BeginWaitCursor();

                CString xs_ph=this->m_sXsPh;int i=0;

                CString kh_id;this->m_editKhId.GetWindowText(kh_id);

                CString kh_name;this->m_editKhName.GetWindowText(kh_name);

                CString czy=app->m_sUserName;

                CString jsr;this->m_comboJsr.GetWindowText(jsr);

                CString rq;this->m_tcRq.GetWindowText(rq);

                CString jsfs;this->m_comboJsfs.GetWindowText(jsfs);

                CString yp_id,yp_name,s_sl,ck_name,s_dj,s_je,sql;

               

                long l_sl=0;

                double d_dj=0,d_je=0,ys=0,ss=0,ws=0;

                long pzs=0;

                bool is_jq=false;

                CStringList slist;

                int n=m_plistXSDJ->GetItemCount();

                if(n<=0)

                {

                        MessageBox("请输入登记信息","注意");

                        this->m_plistXSDJ->m_iEditedIndex=-1;

                        m_plistXSDJ->InsertItem(0,"");

         ::PostMessage(m_plistXSDJ->GetSafeHwnd(),WM_SETFOCUS,0,0);

                        ::PostMessage(m_plistXSDJ->GetSafeHwnd(),SETNEXTITEMPHFOCUS,0,0);

                        return ;

                }

                try{

                m_runsql.m_recordset->Close();

                LRunSql::BeginTrans();

                }

                catch(_com_error e)

                {

                        MessageBox(e.ErrorMessage());

                }

                for( i=0;i<n;i++)

                {

                yp_id=this->m_plistXSDJ->GetItemText(i,0);

                yp_name=this->m_plistXSDJ->GetItemText(i,1);

                ck_name=this->m_plistXSDJ->GetItemText(i,2);

                s_dj=this->m_plistXSDJ->GetItemText(i,3);

                d_dj=atof(s_dj);

                s_sl=this->m_plistXSDJ->GetItemText(i,4);

                l_sl=atoi(s_sl);

                s_je=this->m_plistXSDJ->GetItemText(i,5);

                d_je=atof(s_je);

                ys=d_je+ys;

                ss=ss+atof(m_plistXSDJ->GetItemText(i,6));

                //save dj;

      if(slist.Find(yp_id)==NULL)slist.AddTail(yp_id);

                if(m_bXs)

                {

                        double jinjia=0;double profit=0;

                        m_plistXSDJ->m_mapYpToJj.Lookup(yp_id,jinjia);profit=(d_dj-jinjia)*l_sl;

                        sql.Format("INSERT INTO tabxsdj(xs_ph,yp_id,ck_name,sl,dj,je,profit)\

                                        VALUES('%s','%s','%s',%d,%f,%f,%f)"

                        ,xs_ph,yp_id,ck_name,l_sl,d_dj,d_je,profit);

                }

                else

                sql.Format("INSERT INTO tabrkdj(rk_ph,yp_id,ck_name,sl,dj,je)\

                                        VALUES('%s','%s','%s',%d,%f,%f)"

                        ,xs_ph,yp_id,ck_name,l_sl,d_dj,d_je);

                this->m_runsql.RunSQL(sql);

                if(m_bXs)

                sql.Format("update tabkc set kc_number = kc_number - %d where yp_id='%s' and ck_name =\

                                '%s'",l_sl,yp_id,ck_name);

                else

                sql.Format("update tabkc set kc_number = kc_number + %d where yp_id='%s' and ck_name =\

                                '%s'",l_sl,yp_id,ck_name);

                this->m_runsql.RunSQL(sql);

                }

                ws=ys-ss;

                if(ws<=0.0)

                {

                        ws=0.0;

                        is_jq=true;

                }

                pzs=slist.GetCount();

                if(!m_bXs)

                sql.Format("INSERT INTO tabrkph(rk_ph,gys_id,pzs,yf,sf,wf,is_jq,rq,czy,jsr,jsfs)\

                                        VALUES('%s','%s',%d,%f,%f,%f,%d,#%s#,'%s','%s','%s')"

                        ,xs_ph,kh_id,pzs,ys,ss,ws,is_jq,rq,czy,jsr,jsfs);

                else

                sql.Format("INSERT INTO tabxsph(xs_ph,kh_id,pzs,ys,ss,ws,is_jq,rq,czy,jsr,jsfs)\

                                        VALUES('%s','%s',%d,%f,%f,%f,%d,#%s#,'%s','%s','%s')"

                        ,xs_ph,kh_id,pzs,ys,ss,ws,is_jq,rq,czy,jsr,jsfs);

                this->m_runsql.RunSQL(sql);

      this->EndWaitCursor();

                if(!LRunSql::CommitTrans())

                {

                MessageBox("数据库事务提交错误","医药管理系统");

                }

                else

                MessageBox("OK","医药管理系统");

       this->EndDialog(0);

}

 

(7)实现弹出提示窗口。

入库登记模块与销售登记模块实现弹出提示窗口的不同点是:修改临时库存的算法不同。主要表现在编辑框的EN_CHANGE消息处理函数上,当用户修改数量编辑框中数据时,要对临时数据库进行修改。

void CListXSDJ::OnNewEditChange(const int& item,const int& sub_item)

{

……

switch(sub_item)

{……

case 4:     //数量

if(m_bXs)

l_left=m_edit.kc_number-atoi(str)+m_edit.m_dSetFocusValue;//- cur show number + setfocus value= left

else

l_left=m_edit.kc_number+atoi(str)-m_edit.m_dSetFocusValue;//+ cur show number - setfocus value= left

……

}

……

}

附录A  参考文献

1.《Visual Basic精彩编程200例》机械工业出版社 赛奎春、高春艳等

2003年1月

2.《Visual Basic数据库开发实例解析》机械工业出版社  刘志铭、高春艳等

2003年8月

3.《Visual FoxPro数据库开发实例解析》机械工业出版社                       王晶莹、王国辉等

2003年9月

4.《Power Builder数据库开发实例解析》机械工业出版社                       华传铭、张振坤等

2003年9月

5.《Delphi数据库开发实例解析》机械工业出版社 赛奎春、郑骁鹏等

2004年2月

6.《PowerBuilder 精彩编程200例》机械工业出版社 张振坤、李文立等

2004年9月

7.《Visual FoxPro 精彩编程200例》机械工业出版社 王国辉、董韶华等

2004年9月

8.《ASP数据库开发实例解析》机械工业出版社 李严、于亚芳、王国辉                              2004年12月

9.《Delphi工程应用与项目实践》机械工业出版社 宋坤、赵智勇等

2005年1月

10.《Visual Basic工程应用与项目实践》机械工业出版社 高春艳、李俊民等

2005年1月

11.《Visual C++工程应用与项目实践》机械工业出版社 张雨、阮伟良等

2005年1月

12.《JSP工程应用与项目实践》机械工业出版社 陈威、白伟明、李楠

2005年2月

13.《ASP工程应用与项目实践》机械工业出版社 王国辉、牛强、李南南

2005年4月

14.《Visual Basic 信息系统开发实例精选》机械工业出版社 高春艳、李俊民、张耀庭等     

2005年7月

15.《ASP 信息系统开发实例精选》机械工业出版社 王国辉、牛强、李南南等

2005年7月

16.《Delphi 信息系统开发实例精选》机械工业出版社 宋坤、赵智勇、刘强等

2005年7月

17.《Visual foxpro数据库开发关键技术与实例应用》人民邮电出版社 周桓、张雨、王国辉

2004年5月

18.《Power Builder数据库开发关键技术与实例应用》人民邮电出版社 刘志铭、张振坤、冯文萃 2004年5月

19.《Delphi数据库开发关键技术与实例应用》人民邮电出版社 赛奎春、陈紫鸿、宋昆

2004年5月

20.《Visual basic数据库开发关键技术与实例应用》人民邮电出版社 高春艳、李艳

2004年5月

21.《Visual C++ 管理信息系统完整项目实例剖析》人民邮电出版社 明日科技

2005年7月                                                      

22.《Visual Basic 管理信息系统完整项目实例剖析》人民邮电出版社 明日科技

2005年7月

23.《PowerBuilder 管理信息系统完整项目实例剖析》人民邮电出版社 明日科技

2005年7月

24.《Visual FoxPro管理信息系统完整项目实例剖析》人民邮电出版社 明日科技

2005年7月

25.《SQL Server数据库开发实例解析》机械工业出版社 宋昆、李严等

2006年1月

26.《Access数据库开发实例解析》机械工业出版社 李俊民、高春燕等

2006年1月

27.《Visual Basic数据库系统开发完全手册》人民邮电出版社 明日科技王春才、高春艳、

李俊民 2006年3月

28.《Visual C++ 数据库系统开发完全手册》人民邮电出版社 明日科技 王端、于速、张雨

2006年3月

29.《Delphi数据库系统开发完全手册》人民邮电出版社 明日科技 宋坤、邹天思

2006年3月

30.《JSP数据库系统开发完全手册》人民邮电出版社 明日科技 王国辉、李文立、杨亮

2006年3月

31.《Visual Basic数据库系统开发案例精选》人民邮电出版社 明日科技 高春艳、李俊民、

刘彬彬 2006年5月

32.《Delphi 数据库系统开发案例精选》人民邮电出版社 明日科技 李文立、刘强、梁冰

2006年5月

33.《ASP数据库系统开发案例精选》人民邮电出版社 明日科技 孙明丽、邹天思、盖天宇

2006年5月

34.《JSP数据库系统开发案例精选》人民邮电出版社 明日科技 王国辉、王易

2006年5月

 

附录B  数据表

由于篇幅有限,只给出部分数据表。

下面分别介绍以下各个表的结构。(临时表是为了实现在查询结果中进行查询,在这里不做具体介绍)。

q         tabdhdj表

tabdhdj表用于保存调货登记信息。tabdhdj表的结构如表4所示。

表4                                  tabdhdj表的结构

 

 

字段名称

数据类型

字段大小

可否为空

说明

dh_ph

文本

20

Not Null

调货票号(主键)

out_ck

文本

20

Not Null

调出的库(外健)

in_ck

文本

20

Not Null

调入的库(外健)

yp_id

文本

10

Not Null

药品编号(外健)

sl

长整型

 

Null

数量

 

q         tabpurview表

tabpurview表用于保存权限信息。tabpurview表的结构如表5所示。

表5                               tabpurview表的结构

字段名称

数据类型

字段大小

可否为空

说明

admi_name

文本

20

Not Null

用户名(主键)

admi_password

文本

255

Null

用户密码

admi_purview

长整型

 

Not Null

用户权限(外键)

q         tabrkdj表

tabrkdj表用于保存入库登记的相关信息。tabrkdj表的结构如表6所示。

表6                                tabrkdj表的结构

字段名称

数据类型

字段大小

可否为空

说明

rk_ph

文本

20

Not Null

入库票号(外键)

yp_id

文本

10

Not Null

药品编号(外键)

ck_name

文本

30

Not Null

仓库名称(外键)

sl

双精度型

 

Null

数量

dj

双精度型

 

Null

单价

je

双精度型

 

Null

金额

q         tabrkjz表

tabrkjz表用于保存入库结账的相关信息。tabrkjz表的结构如表7所示。

表7                              tabrkjz表的结构

字段名称

数据类型

字段大小

可否为空

说明

fk_ph

文本

20

Not Null

收款票号(主键)

rk_ph

文本

20

Not Null

销售票号(外键)

je

双精度型

 

Null

收款金额

fs

文本

10

Null

收款方式(外键)

rq

日期/时间

 

Not Null

结款日期

czy

文本

20

Not Null

操作员(外键)

jsr

文本

20

Not Null

经手人

bz

文本

100

Null

备注

q         tabrkph表

tabrkph表用于保存入库票号。tabrkph表的结构如表8所示。

表8                               tabrkph表的结构

字段名称

数据类型

字段大小

可否为空

说明

rk_ph

文本

20

Not Null

入库票号(主键)

gys_id

文本

10

Not Null

供应商编号(外键)

pzs

双精度型

 

Null

品种数

yf

双精度型

 

Null

应付

sf

双精度型

 

Null

实付

wf

双精度型

 

Null

未付

rq

日期/时间

 

Not Null

日期

jsfs

文本

10

Not Null

结算方式(外键)

is_jq

布尔型

 

Not Null

是否结清

czy

文本

20

Not Null

操作员(外键)

jsr

文本

20

Not Null

经手人

 
posted @ 2009-11-19 21:42  龙大源  阅读(4285)  评论(0编辑  收藏  举报