如何計算balance? |
尚未結案
|
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
我要拿的資料: date doc_no match_doc descrip type debit credit balance
08/02/2004 INV 1001 INV1001 PURCHASE INV 45000 45000
10/03/2004 HP1001 INV1001 PAY HP (1000) 44000
10/04/2004 HP1002 INV1001 PAY HP (1000) 43000
15/04/2004 HP1001 INV1001 VOID CHQ CHQ 1000 44000
10/05/2004 HP1004 INV1001 PAY HP (1000) 43000
15/02/2004 INV 1002 INV1002 PURCHASE INV 30000 73000
10/03/2004 HP1003 INV1002 PAY HP (2000) 71000
12/04/2004 HP1005 INV1002 PAY HP (2000) 69000 我用Calc field 來做 debit, credit 和 balance, 但是怎樣都算不到balance, credit 和 debit是從資料庫里的Amount里拿出(正數為Debit,負數為Credit),balance的計算則要很靈活, 每一行列什麼(如credit (1000), balance 為減(1000)),用戶是能選要如和排列顯示資料的, 如order by date 等.
不知有那位大大能教我如何計算balance? 我用Report builder,謝謝!
|
Fishman
尊榮會員 發表:120 回覆:1949 積分:2163 註冊:2006-10-28 發送簡訊給我 |
Hi chyap99, 這篇文章你參可看看
http://delphi.ktop.com.tw/topic.php?topic_id=43818
----------------------------------
小弟才疏學淺,若有謬誤尚請不吝指教
----------------------------------
------
Fishman |
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
謝謝大大回覆,但我的好像有點不一樣, 我的debit,credit 和balance都是Calculated Field,debit 和 credit 都是拿database 里的Amount, 如Amount是正數為Debit,負數為Credit,balance的計算則要很靈活,看是加debit還是減credit.
我的計算如下:
procedure form1.sqlCalcFields(DataSet: TDataSet);
Var
bal : Currency;
begin
if plHpstt['Amount'] > 0 then
begin
sqlDebit.AsCurrency := sqlAmount.AsCurrency ;
end;
if plHpstt['Amount'] < 0 then
begin
sqlHpSttCredit.AsCurrency := sqlHpSttAmount.AsCurrency ;
end;
end;
那balance 要怎樣算呢? 我放
Bal := sqlBalance.AsCurrency + sqlAmount.AsCurrency;
sqlBalance.AsCurrency := Bal;
但不行. 發表人 - chyap99 於 2004/05/27 19:43:26
|
yachanga
資深會員 發表:24 回覆:335 積分:296 註冊:2003-09-27 發送簡訊給我 |
Hi chyap99 您好:
不知道下面這張表是不是符合您的需求..
假設Amount 欄位資料表的資料..
若資料為正數..則放在Debit..
負數則放在Credit...
然後在作Balance...
這三個欄位都是Calculate Field
ABS(Field)可過濾欄位負號
先設定一個全域變數..
然後試試下面程式碼..
Var balance: Real; procedure TForm1.Query1CalcFields(DataSet: TDataSet); Var amount:real; begin amount:=Query1.FieldByName('AMOUNT').AsFloat; if Query1amount.Value>0 then begin Query1debit.Value:=amount; balance:=balance+amount; end else begin Query1credit.Value:=abs(amount); balance:=balance-abs(amount); end; Query1balance.Value:=balance; end;發表人 - yachanga 於 2004/05/27 22:56:00 |
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
privete
Var
Bal : real;
procedure Form1.queryCalcFields(DataSet: TDataSet);
Var
amount : real;
begin
amount:=query.FieldByName('Amount').AsCurrency;
if queryAmount.Value > 0 then begin
queryDebit.Value := amount;
Bal := Bal + amount;
end
else begin
queryCredit.Value := abs(amount);
Bal := Bal + amount; {47600(+)-794= 47600-794}
end;
queryBalance.Value := Bal;
showmessage(currtostr(Bal)); if (queryDocument_Type.Value = 'INV') then
begin
queryStatus.Value := 'Hire Purchase';
end; if (queryDocument_Type.Value = 'HP') then
begin
queryStatus.Value := 'Payment';
end; showmessage :
1.47600 2.95200 3.142800 4.190400 5.189606 6.237206 7.236412
8.235618 9.283218 10.282424 11.320024 12.329230 13.328436 我只試了兩個record,加了個showmessage,當run時, show了13個message如上,而且每一次preview,bal 會不斷增加.為什么會這樣呢? 大大試時沒問題嗎?
|
yachanga
資深會員 發表:24 回覆:335 積分:296 註冊:2003-09-27 發送簡訊給我 |
Hi chyap99:
1. 在Query Open之前記得將Bal reset 為 0
2. 確定Amount 數字有負值 我將您的程式修改如下
Var Bal : real; procedure TForm1.Button1Click(Sender: TObject); begin Ba1:=0; //Reset 總合 query.Close; query.Open; end; procedure Form1.queryCalcFields(DataSet: TDataSet); Var amount : real; begin amount:=query.FieldByName('Amount').AsCurrency; if queryAmount.Value > 0 then begin queryDebit.Value := amount; Bal := Bal amount; end else begin queryCredit.Value := abs(amount); Bal := Bal - abs(amount); {47600- |-794|= 47600-794} //這樣比較保險 end; queryBalance.Value := Bal; showmessage(currtostr(Bal)); if (queryDocument_Type.Value = 'INV') then begin queryStatus.Value := 'Hire Purchase'; end; if (queryDocument_Type.Value = 'HP') then begin queryStatus.Value := 'Payment'; end; end;~悠遊法國號~ |
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
Var
Bal : real; procedure Form1.btnViewClick(Sender: TObject);
begin
Bal:=0; //Reset
Query.Close;
Query.Open;
Report.template.DatabaseSettings.Name := 'Account' ;
Report.template.LoadFromDatabase;
report.print;
end; procedure Form1.queryCalcFields(DataSet: TDataSet);
Var
amount : real;
begin amount:=query.FieldByName('Amount').AsCurrency;
if queryAmount.Value > 0 then begin
queryDebit.Value := amount;
Bal := Bal amount;
end
else begin
queryCredit.Value := abs(amount);
Bal := Bal - abs(amount);
//這樣比較保險
end; queryBalance.Value := Bal;
showmessage(currtostr(Bal)); if (queryDocument_Type.Value = 'INV') then
begin
queryStatus.Value := 'Hire Purchase';
end; if (queryDocument_Type.Value = 'HP') then
begin
queryStatus.Value := 'Payment';
end;
end; 謝謝大大指點,其實我明白大大要講的,但是我一按view button,在顯示時我用showmessage看, Bal就一直跳到283218了,我放了Bal:=0;都沒用,到底怎樣能讓它不一直加呢?在database里我的amount為$47600.00 和 ($794.00),大大用同樣coding沒問題嗎?我不明白那里出錯了.
|
yachanga
資深會員 發表:24 回覆:335 積分:296 註冊:2003-09-27 發送簡訊給我 |
|
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
|
yachanga
資深會員 發表:24 回覆:335 積分:296 註冊:2003-09-27 發送簡訊給我 |
引言: 謝謝yachanga大大的回复, 我看了大大給的source code, 然后在我的program加了個DBGrid, 用DBGrid顯示時并無錯誤, 但是用report builder 的report.print來看, balance就不對了!我想是因為當程序進入report時會loop很多次,導致它的balance變得很大,但是我該怎樣做才能在report里拿到正確的balance呢?我還是做不到...Hi chyap99您好: 原來您是在Report Builder 上使用會有問題, 下次要早說喔 小弟只用過 class="code"> procedure TForm1.Button2Click(Sender: TObject); begin Query1.Close; Query1.Open; quickrep1.Preview; // Preview 報表 end; procedure TForm1.QuickRep1BeforePrint(Sender: TCustomQuickRep; var PrintReport: Boolean); begin balance:=0; //加上此句 就OK end; 您可參考小弟執行檔說明 要有裝QuickReport V3.6.2 才可以Compile喔~~ http://delphi.ktop.com.tw/topic.php?TOPIC_ID=50922 ~悠遊法國號~ |
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
引言: 您可以在報表元件的BeforePrint 事件中將balance reset 為0..即可解決
以QuickReport為例
-------------------------------------------------------------------- 謝謝yachanga大大的回复,
我在ReportBuilder元件的BeforePrint事件中將balance reset為0, 還是拿不到對的balance, 我想是因為ReportBuilder的loop法不同吧,我在ReportBuilde web site拿到的解釋如下(不翻譯因為我也看不太懂)
引言:
OnCalcFields will fire each time the record position changes in the dataset.
You should only use this event to perform a calculation that depends on data field values in the same record. Never use this event to perform calculations across records. If you build an examples that scrolls back and forth thru the dataset, you will see that the calculations break.
ReportBuilder does not simply traverse the dataset from front to back
---------------------------------------------------------------------
不知大大有什麼意見呢?我想如OnCalcFieldsz再做不到就試用variable的onCalc了, 但這方法對我來說不太好因為怕user把variable delete掉...而且也不知能不能.
|
Chance36
版主 發表:31 回覆:1033 積分:792 註冊:2002-12-31 發送簡訊給我 |
引言: OnCalcFields will fire each time the record position changes in the dataset. OnCalcFields會在資料集的記錄指標移動後發 You should only use this event to perform a calculation that depends on data field values in the same record. Never use this event to perform calculations across records. If you build an examples that scrolls back and forth thru the dataset, you will see that the calculations break. 在OnCalcFields事件只可以針對相同記錄中的欄位值做運算,不可作跨記錄的運算,你可以用個dbGrid然後往前往後移動指標看看,就會看到計算欄位的值被破壞了(以你的例子就會看到Balance一直的在累加(或累減)) ReportBuilder does not simply traverse the dataset from front to back ReportBuilder 並不是很單純的從資料集的第一筆到最後一筆跑一遍而已chyap99 你好 對於這個問題,使用ReportBuilder中的variable也不見得適合,同樣會遇到記錄指標來回的問題。 個人覺得(也是我目前在用的方法),對於報表的輸出,一律使用TClientDataSet,TClientDataSet可自定結構,承接其他資料集的結構,然後從其他資料集複製資料或直接下SQL取得資料(有連結TDataSetProvider),可作動態索引,及複雜資料運算,靈活度與速度皆可兼得。 承接的例子修改如下: cds : TClientDataSet ; // 放在Form上面,就不用說明動態建立的方式 // 建立Procedure 供呼叫用 Procedure TForm1.ReCala ; Var Bal ,Amount : Real; bm : TBookMark; Begin With CDS DO Begin mb := GetBookMark; // 記錄目前指標 First; Bal := 0 ; While Not Eof Do Begin Edit; Bal := Bal FieldByName('Debit').AsFloat-FieldByName('Credit').AsFloat; FieldByName('Balance').AsFloat := Bal; Post; Next; End; GotoBookMark(bm); // 還原指標 End; End; // 從Query.Open後開始交給TClientDataSet來處理 query.Open; Bal := 0 ; With cds Do Begin FieldDefs := Query.FieldDefs ; // 繼承資料結構 CreateDataSet; // 建立記憶體資料表 While Not Query.Eof Do Begin Append; For i:=0 To FieldsCount-1 Do Begin Fields[i].Value := Query.Fields[i].Value; // 因為結構相同所以 End; if QueryAmount.Value > 0 then begin FieldByname('Debit').AsFloat := QueryAmount.Value ; end else if QueryAmount.Value < 0 then begin queryCredit.Value := abs(QueryAmount.Value ); End; Post; Query.Next; End; First; End; ReCala ; // 呼叫ReCala計算一次 另外如果有確定排序欄位後可以如下方式重新計算 // 建立動態索引,若需要降冪排序,可以cds.AddIndex方法建立(具體做法可在本站找到範例) cds.IndexFieldNames := 'Date;Doc_No'; // 依Date Doc_No排序或是'Doc_No;Date'亦可,隨你。 ReCala ; // 呼叫ReCala重新計算一次 最後交給ReportBuilder輸出時,已是最後的結果,ReportBuilder只要照資料輸出即可。發表人 - chance36 於 2004/06/02 03:13:12 |
yachanga
資深會員 發表:24 回覆:335 積分:296 註冊:2003-09-27 發送簡訊給我 |
引言: 引言: 謝謝yachanga大大的回复, 我在ReportBuilder元件的BeforePrint事件中將balance reset為0, 還是拿不到對的balance, 我想是因為ReportBuilder的loop法不同吧,我在ReportBuilde web site拿到的解釋如下(不翻譯因為我也看不太懂) 不知大大有什麼意見呢?我想如OnCalcFieldsz再做不到就試用variable的onCalc了, 但這方法對我來說不太好因為怕user把variable delete掉...而且也不知能不能.Hi chyap99您好: 我剛剛試了一下 ReportBuilder 7.0 trial 版, 發現此報表loop做法和Quick Report 不同..所以若您先在DataSet 用CalcField 會有您說的現象產生.. 以下是小弟試的結果 直接在Report Builder 計算 Debit, Credit, Balance 三個欄位 即可 1. 拉一個 TppDBText 為ppDBdebit, DateField 為amount 2. 拉一個 TppDBText 為ppDBcredit, DateField 為amount 3. 拉一個 TppDBCalc 為ppDBbalance, DateField 為amount 完全不需程式碼, 就自己累加囉 加少許程式碼如下 procedure TForm1.ppDBdebitGetText(Sender: TObject; var Text: String); begin if StrToFloat(text)<0 then text:=''; end; procedure TForm1.ppDBcreditGetText(Sender: TObject; var Text: String); begin if StrToFloat(text)>=0 then text:='' else text:= FloatToStr(abs(StrToFloat(text))); end;詳細原始碼請參考小弟範例 http://delphi.ktop.com.tw/topic.php?TOPIC_ID=50922 Balnace 計算 QuickReport ReportBuilder 發表人 - yachanga 於 2004/06/02 09:43:52 |
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
謝謝yachanga大大和版主Chance36, 因為小弟的Report做法是在run time時才把格式load進來(用戶可Load不同格式,不同TppDBText名for不同格式), 所以 引言:
---------------------------------------------------------------------
如直接在Report Builder 計算 Debit, Credit, Balance 三個欄位 即可
1. 拉一個 TppDBText 為ppDBdebit, DateField 為amount.....
......
---------------------------------------------------------------------
如直接用ppDBdebitGetText等來做, 可能會有loading格式里并無ppDBdebit(相同名), 用戶把ppDBdebit刪除等問題, 因為小弟用的report builder不支援RAP. 在report builder的page得到兩個意見: 引言:
--------------------------------------------------------------------
Try using a summary dataset. Either use SQL to create a summary query that contains the calculated results or perhaps pre-process the data to build a temp table that contains the summarized calcs. Then use DBText inside the report.
--------------------------------------------------------------------- 小弟試過用temp table, 但資料太大時讀取會變慢,要insert,select再delete.另一個方法應該將像版主所提的.. 因為沒接觸過TClientDataSet,小弟在嘗試時遇到了好几個run time error. 試過在(buttonOnclick event 和 form create)
sqlHpStt.Open;
Bal := 0 ;
With cds Do Begin
FieldDefs := sqlHpStt.FieldDefs ;
CreateDataSet;
While Not sqlHpStt.Eof Do Begin
Append;
For i:=0 To FieldCount-1 Do Begin
Fields[i].Value := sqlHpStt.Fields[i].Value;
//showmessage(Fields[i].Value);
End;
if sqlHpSttAmount.Value > 0 then begin
sqlHpSttDebit.Value := sqlHpSttAmount.Value ;
end else if sqlHpSttAmount.Value < 0 then begin
sqlHpSttCredit.Value := abs(sqlHpSttAmount.Value );
End;
Post;
sqlHpStt.Next;
End;
First;
End;
ReCala ;
Report.template.DatabaseSettings.Name := lsName ; {lsName是用戶選擇的report格式.}
Report.template.LoadFromDatabase;
report.print; 放在新的procedure,
Procedure TrepAcHpSttRp.ReCala ;
Var
Bal ,Amount : Currency;
bm : TBookMark;
Begin
With CDS DO Begin
bm := GetBookMark;
First;
Bal := 0 ;
While Not Eof Do Begin
Edit;
Bal := Bal FieldByName('Debit').AsCurrency-FieldByName('Credit').AsCurrency;
FieldByName('Balance').AsCurrency := Bal;
Post;
Next;
End;
GotoBookMark(bm);
End;
End; run時會有missing data provider or data packet(小弟不知如何set provider), Invalid value for 'rate'(rate的格式是number,如1.0) ,invalid variant type conversion (如某個field里并無record,就會有此error). 公司目前有兩個system,目前小弟須用的是給舊的,用delphi 3, 因為如此, 可能也會造成很多麻煩.希望大大們能多加指點!
|
Chance36
版主 發表:31 回覆:1033 積分:792 註冊:2002-12-31 發送簡訊給我 |
引言: run時會有missing data provider or data packet(小弟不知如何set provider), Invalid value for 'rate'(rate的格式是number,如1.0) ,invalid variant type conversion (如某個field里并無record,就會有此error). 公司目前有兩個system,目前小弟須用的是給舊的,用delphi 3, 因為如此, 可能也會造成很多麻煩.希望大大們能多加指點!chyap99 你好 1.會有上述錯誤訊息,應該是對於ClientDataSet使用Open的方法才產生的,在此只是要建立記憶體資料表,故於欄位結構確定後,要呼叫CreateDataSet方法才對。(除非Delphi3的CLientDataset不一樣??這我就不得而知了) 2.經測試,在Query中建立的計算欄位,並不會在FieldDefs的指定中帶到ClientDataSet,所以必須自行建立。 最後完整的程式碼如下所示: Var i : Integer ; fn : String ; begin sqlHpStt.Open; With cds Do Begin Close; // 先行關閉,免得第二次執行時未關閉會觸發錯誤 FieldDefs := sqlHpStt.FieldDefs ; // 繼承資料結構(不含計算欄位) FieldDefs.Add('Debit' ,ftFloat,0,False); // 此三個計算欄位自行加進去 FieldDefs.Add('Credit' ,ftFloat,0,False); FieldDefs.Add('Balance',ftFloat,0,False); // Open; // 千萬不要用Open (因為要建立記憶體資料表) CreateDataSet; // 使用這個建立記憶體資料表 While Not sqlHpStt.Eof Do Begin Append; For i:=0 To sqlHpStt.FieldCount-1 Do Begin fn := sqlHpStt.Fields[i].FieldName ; // 為免那欄位結構不同所以加以檢查,有相同欄位名稱者才複製資料 If FindField(fn) <> nil Then FieldByName(fn).Value := sqlHpStt.Fields[i].Value; End; //一般個人的習慣是存取欄位位值使用.Asxxxx而不用.Value if sqlHpSttAmount.Value > 0 then begin FieldByname('Debit').AsFloat := sqlHpSttAmount.Value ; end else if sqlHpSttAmount.Value < 0 then begin FieldByname('Credit').AsFloat:= abs(sqlHpSttAmount.Value ); End; Post; sqlHpStt.Next; End; First; End; sqlHpStt.Close; // 沒用了可以關閉 ReCalc; // 最後計算一次Balance end; |
yachanga
資深會員 發表:24 回覆:335 積分:296 註冊:2003-09-27 發送簡訊給我 |
引言: 謝謝yachanga大大和版主Chance36, 在report builder的page得到兩個意見: -------------------------------------------------------------------- Try using a summary dataset. Either use SQL to create a summary query that contains the calculated results or perhaps pre-process the data to build a temp table that contains the summarized calcs. Then use DBText inside the report. --------------------------------------------------------------------- 小弟試過用temp table, 但資料太大時讀取會變慢,要insert,select再delete.另一個方法應該將像版主所提的..Hi chyap99 您好: 依據您的需求 , 的確用ClientDataSet會比較好.. 可參照Chance36版大的觀念.. 部分資料因應您的需求可稍微修改..Chance36版大別生氣喔 < class="code"> procedure TForm1.OpenDataSet; Var i : Integer ; fn : String ; amount,balance: Real; begin balance:=0; sqlHpStt.Open; With cds Do Begin Close; FieldDefs := sqlHpStt.FieldDefs ; FieldDefs.Add('Debit' ,ftFloat,0,False); FieldDefs.Add('Credit' ,ftFloat,0,False); FieldDefs.Add('Balance',ftFloat,0,False); CreateDataSet; While Not sqlHpStt.Eof Do Begin Append; For i:=0 To sqlHpStt.FieldCount-1 Do Begin fn := sqlHpStt.Fields[i].FieldName ; If FindField(fn) <> nil Then FieldByName(fn).Value := sqlHpStt.Fields[i].Value; End; amount:=sqlHpStt.FieldByName('Amount').Value; balance:=balance amount; if amount > 0 then FieldByname('Debit').AsFloat := amount else if amount < 0 then FieldByname('Credit').AsFloat:= abs(amount); FieldByname('Balance').AsFloat :=balance; //直接在裡面計算, 不用另外再呼叫一個函數~~ Post; sqlHpStt.Next; End; End; sqlHpStt.Close; end; 根據您的問題的詳細原始碼請參考小弟的範例 ClientDataSet DBGrid QuickReport ReportBuilder http://delphi.ktop.com.tw/topic.php?TOPIC_ID=50922 有關ClientDataSet 更多範例可參考JustMade版大黃金文章 【發表】ClientDataSet 簡單範例 http://delphi.ktop.com.tw/topic.php?topic_id=28513 發表人 - yachanga 於 2004/06/03 09:56:28 |
Chance36
版主 發表:31 回覆:1033 積分:792 註冊:2002-12-31 發送簡訊給我 |
|
chyap99
一般會員 發表:20 回覆:51 積分:24 註冊:2004-03-31 發送簡訊給我 |
謝謝yachanga大大和Chance36版主,
引言:為什麼計算Balance的部份要另寫一個函數處理呢?因為會重複使用,如chyap99 兄所言,在取得資料後,使用者還可以決定排序的欄位,然後再依排序後的結果,產生一次Balance的值,所以才會加個函數來處理。測試過後我想版主Chance36所給的確實比較適合我,因為一切都須以使用者的需求為先,而balance的問題也解決了. 當然這期間真的非常謝謝各位大大的幫助,Fishman大大的拋磚引玉, yachanga大大不厭其煩的指點(還特地下載report builder測試等)以及Chance36版主的教導(知道了能用TClientDataSet等),小弟真是學到不少!如果能讓多過一人得分就好了. 因為是新手,對于TClientDataSet其實還有些不太懂的,如現在小弟的做法(cds->TDataSource->Pipeline)不知是不是最有效(正確)的方法,cds.AddIndex還不會用等,而我的Report也還沒真正完成,可能之后還會有很多難題須要各位加以指點!在此先謝過了!謝謝! |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |