Code cleanup
[account.git] / account.cpp
1
2 #include "account.h"
3
4 #include <sstream>
5 #include <fstream>
6
7 #include <iostream>
8 #include <iomanip>
9
10 acc_nr::acc_nr(String &s)
11 {
12    String s_nr;
13
14    std::cerr << "Account number from " << s;
15    group = s.dec();
16    s_nr    = s(4,2);
17    nr      = s_nr.dec();
18    std::cerr << " : " << *this << "\n";
19 }
20
21 acc_nr::operator String()
22 {
23    char _buf[8];
24    sprintf(_buf, "%03d.%02d", group, nr);
25
26    String s(_buf);
27    return s;
28 }
29
30 std::istream &operator>>(std::istream &s, acc_nr &a)
31 {
32    char c;
33
34    s >> a.group >> c >> a.nr;
35    return s;
36 }
37
38 std::ostream &operator<<(std::ostream &s, acc_nr a)
39 {
40    s << String(a);
41    return s;
42 }
43
44 std::istream &operator>>(std::istream &s, amount &a)
45 {
46    long   hundreds;
47    char   c;
48
49    s >> hundreds >> c >> a.value;
50    a.value += hundreds * 100;
51
52    return s;
53 }
54
55 std::ostream &operator<<(std::ostream &s, amount a)
56 {
57    if (a == 0)
58       s << "           ";
59    else
60    {
61       s << std::setw(8) << std::setfill(' ') << a / 100 << ".";
62       s << std::setw(2) << std::setfill('0') << a % 100;
63       s << std::setw(0) << std::setfill(' ');
64    }
65
66    return s;
67 }
68
69 String amount::to_string()
70 {
71    char _buf[20];
72    sprintf(_buf, "%8lu.%02lu", value / 100, value % 100);
73
74    if (value != 0)
75    {
76       String s(_buf);
77       return s;
78    }
79    else
80    {
81       return String("           ");
82    }
83 }
84 std::ostream &operator<<(std::ostream &s, balance b)
85 {
86    s << b.Debit << "  " << b.Credit;
87
88    return s;
89 }
90
91 std::istream &operator>>(std::istream &s, Booking &b)
92 {
93    s >> b.Bnumber >> b.booked;
94    s >> b.comment;
95
96    return s;
97 }
98
99 std::ostream &operator<<(std::ostream &s, Booking &b)
100 {
101    s << "#" << std::setw(4) << std::setfill('0') << b.Bnumber;
102    s << std::setw(0) << std::setfill(' ');
103    s << " " << b.booked << " ";
104    s << std::setfill(' ') << std::setw(58) << std::left << b.comment;
105    s << std::setw(0) << std::right ;
106
107    return s;
108 }
109
110 std::istream &operator>>(std::istream &s, Mutation &m)
111 {
112    int   nr_blanks = 0;
113    char  c;
114
115    while (s.get(c), c == ' ')
116       nr_blanks++;
117
118    s.putback(c);
119    s >> m.account;
120
121    amount  a;
122
123    String amnt;
124    int    p;
125
126    s >> amnt;
127    p = ~amnt - 1;
128    while (p > 0 && amnt[p] == ' ')
129    {
130       p--;
131    }
132    while (p > 0 && amnt[p] != ' ')
133    {
134       p--;
135    }
136
137    amnt = amnt(p, ~amnt - p);
138    int dot = amnt.index('.');
139    String cents = amnt(dot+1, 2);
140    a = long(amnt) * 100 + cents.dec();
141
142    if (nr_blanks < 7)
143       m.bal = balance(a, 0);
144    else
145       m.bal = balance(0, a);
146
147    return s;
148 }
149
150 std::ostream &operator<<(std::ostream &s, Mutation &m)
151 {
152    s << "#" << std::setw(4) << std::setfill('0') << m.booking_nr;
153    s << std::setw(0) << std::setfill(' ');
154    s << " " << m.account << m.bal;
155    return s;
156 }
157
158
159
160 std::istream &operator>>(std::istream &s, Account &acc)
161 {
162    char kind;
163
164    s >> acc.Anumber >> kind >> std::ws;
165    if (kind == 'B')
166       acc.kind = BALANCE;
167    else
168       acc.kind = COST;
169
170    s >> acc.name;
171
172    return s;
173 }
174
175 std::ostream &operator<<(std::ostream &s, Account &acc)
176 {
177    s << acc.Anumber << " " << std::setw(44) << std::left << acc.name;
178    s << std::setw(0) << std::right << " ";
179    s << acc.Bal;
180
181    return s;
182 }
183
184 std::ostream& operator<<(std::ostream &s, Ledger &l)
185 {
186    int  i;
187    balance total, saldi;
188    balance EndBalance, Result;
189
190    s << l.nr_accs << " accounts in ledger:\n\n";
191    for (i=0; i<l.nr_accs; i++)
192    {
193       total += l.accs[i];
194       saldi += ~balance(l.accs[i]);
195       s << l.accs[i] << ~balance(l.accs[i]);
196       if (l.accs[i].BAL())
197       {
198          s << "                       " << ~balance(l.accs[i]);
199          EndBalance += ~balance(l.accs[i]);
200       }
201       else
202       {
203          s << ~balance(l.accs[i]);
204          Result += ~balance(l.accs[i]);
205       }
206       s << "\n";
207    }
208
209    balance profit;
210    profit = ~Result;
211
212    s << "\n       Result                                       ";
213    s << "                                                ";
214    s << -profit << profit << "\n";
215
216    Result += -profit;
217    EndBalance += profit;
218
219    s << "\n       Total                                        ";
220    s << total << saldi << Result << EndBalance << "\n";
221
222    return s;
223 }
224
225 void PostScript_balans_layout(PostScript &ps, String header)
226 {
227    ps.Line(16, 595, 755, 595);
228    ps.Line(16, 580, 755, 580);
229    ps.Line(16, 40, 755, 40);
230    ps.Text(200, 582, header);
231
232    ps.Line( 16, 595,  16, 40);
233    ps.Line(755, 595, 755, 40);
234
235    ps.LineAttributes(0.2, 1.0, 0.0);
236    ps.Line(195, 580, 195, 40);
237    ps.Line(335, 580, 335, 40);
238    ps.Line(475, 580, 475, 40);
239    ps.Line(615, 580, 615, 40);
240
241    ps.LineAttributes(0.2, 2.0, 2.0);
242    ps.Line(265, 580, 265, 40);
243    ps.Line(405, 580, 405, 40);
244    ps.Line(545, 580, 545, 40);
245    ps.Line(685, 580, 685, 40);
246 }
247
248 void Ledger::accounts_report(const char * filename)
249 {
250    try
251    {
252       PostScript ps(filename);
253       int        i;
254
255       for (i=0; i<nr_accs; i++)
256       {
257          accs[i].Postscript_sheet(ps);
258       }
259    }
260    catch (psexception &e)
261    {
262       std::cerr << "Error creating postscript output: " << e.what() << "\n";
263    }
264 }
265
266 void Ledger::saldi_report(const char * filename, date period_begin, date period_end)
267 {
268    try
269    {
270       PostScript ps(filename);
271       balance total, saldi;
272       balance EndBalance, Result;
273       int        i;
274       float      y = 572;
275       int        row = 0;
276
277       String     header("Saldibalans");
278
279       header += " " + companyname;
280       header += " van " + period_begin.format("%e %B %Y") + " tot " + period_end.format("%e %B %Y");
281
282       ps.NewPage(842, 595);
283       ps.FontSize(6);
284
285
286       for (i=0; i<nr_accs; i++)
287       {
288          balance saldo(accs[i]);
289
290          if (saldo.debit() != 0 || saldo.credit() != 0)
291          {
292             if (row == 64)
293             {
294                ps.FontSize(9);
295                PostScript_balans_layout(ps, header);
296                ps.NewPage(842, 595);
297                ps.FontSize(6);
298                y = 572;
299                row = 0;
300             }
301
302             if (row % 2 == 1)
303             {
304                ps.Rectangle(16, y-2, 739, 7);
305             }
306
307             total += accs[i];
308             saldi += ~balance(accs[i]);
309
310             ps.Text(20, y, String(accs[i].Number()));
311             ps.Text(50, y, accs[i].Name());
312
313             balance saldo(accs[i]);
314
315             ps.Text(200, y, saldo.debit().to_string());
316             ps.Text(270, y, saldo.credit().to_string());
317             saldo = ~saldo;
318             ps.Text(340, y, saldo.debit().to_string());
319             ps.Text(410, y, saldo.credit().to_string());
320
321             if (accs[i].BAL())
322             {
323                EndBalance += ~balance(accs[i]);
324
325                ps.Text(620, y, saldo.debit().to_string());
326                ps.Text(690, y, saldo.credit().to_string());
327             }
328             else
329             {
330                Result += ~balance(accs[i]);
331
332                ps.Text(480, y, saldo.debit().to_string());
333                ps.Text(550, y, saldo.credit().to_string());
334             }
335
336             y -= 7.0;
337             row ++;
338          }
339       }
340
341       ps.FontSize(9);
342       PostScript_balans_layout(ps, header);
343
344       balance profit;
345       profit = ~Result;
346       Result += -profit;
347       EndBalance += profit;
348
349       ps.LineAttributes(0.5, 1.0, 0.0);
350       ps.Line(16, 60, 755, 60);
351       y = 52;
352       ps.Text(50, y, "Resultaat");
353       ps.Text(480, y, (-profit).debit().to_string());
354       ps.Text(550, y, (-profit).credit().to_string());
355       ps.Text(620, y, profit.debit().to_string());
356       ps.Text(690, y, profit.credit().to_string());
357
358       ps.Line(16, 50, 755, 50);
359       y = 42;
360       ps.Text(50, y, "Totaal");
361       ps.Text(200, y, total.debit().to_string());
362       ps.Text(270, y, total.credit().to_string());
363       ps.Text(340, y, saldi.debit().to_string());
364       ps.Text(410, y, saldi.credit().to_string());
365       ps.Text(480, y, Result.debit().to_string());
366       ps.Text(550, y, Result.credit().to_string());
367       ps.Text(620, y, EndBalance.debit().to_string());
368       ps.Text(690, y, EndBalance.credit().to_string());
369    }
370    catch (psexception &e)
371    {
372       std::cerr << "Error creating postscript output: " << e.what() << "\n";
373    }
374 }
375
376 void Ledger::XML_saldi(std::ostream &s, date period_begin, date period_end)
377 {
378    int  i;
379
380    s << "<?xml version='1.0'?>\n";
381    s << "<accounting companyname='" << companyname << "'>\n";
382    s << "<balance-sheet begin='" << period_begin << "' end='" << period_end << "'>\n";
383    for (i=0; i<nr_accs; i++)
384    {
385       balance saldo = ~balance(accs[i]);
386
387       if (saldo.debit() != 0 || saldo.credit() != 0)
388       {
389          s << "  <account>\n";
390          s << "    <number>" << accs[i].Number() << "</number>\n";
391          s << "    <name>" << accs[i].Name() << "</name>\n";
392          if (accs[i].BAL())
393          {
394             s << "    <type>BALANCE</type>\n";
395          }
396          else
397          {
398             s << "    <type>COST</type>\n";
399          }
400          if (saldo.debit() == 0)
401          {
402             s << "    <balance>CREDIT</balance>\n";
403             s << "    <amount>" << saldo.credit() << "</amount>\n";
404          }
405          else
406          {
407             s << "    <balance>DEBIT</balance>\n";
408             s << "    <amount>" << saldo.debit() << "</amount>\n";
409          }
410          s << "  </account>\n";
411          s << "\n";
412       }
413    }
414    s << "</balance-sheet>\n";
415    s << "</accounting>\n";
416 }
417
418 Account & Ledger::operator[](acc_nr Anr)
419 {
420    int i;
421
422    for (i=0; accs[i] != Anr && i<nr_accs; i++);
423    if (i == nr_accs)
424       std::cerr << "Account " << Anr << " not found.\n";
425    return accs[i];
426 }
427
428 extern int n_m;
429 extern Mutation m[];
430 extern Booking b[];
431
432 void PostScript_account_layout(PostScript &ps)
433 {
434    ps.LineAttributes(1.0, 1.0, 0.0);
435    ps.Line(30, 770, 580, 770);
436    ps.Line(30, 740, 580, 740);
437    ps.Line(30,  20, 580,  20);
438
439    ps.Line(30, 770, 30, 20);
440    ps.Line(580, 770, 580, 20);
441
442    ps.LineAttributes(0.2, 1.0, 0.0);
443    ps.Line(400, 740, 400, 20);
444    ps.LineAttributes(0.2, 2.0, 2.0);
445    ps.Line(480, 740, 480, 20);
446 }
447
448 void Account::Postscript_sheet(PostScript &ps)
449 {
450    int      M, B;
451    balance  total;
452    balance  saldo;
453    int      row;
454    float    y;
455    bool     page_started = false;
456
457    row = 68;
458
459    /*  Go through all mutations, selecting for each account  */
460
461    for (M = 0; M < n_m; M++)
462    {
463       std::ostringstream   bookstr;
464
465       if (acc_nr(m[M]) == Anumber)
466       {
467          if (row == 68)
468          {
469             if (page_started)
470             {
471                PostScript_account_layout(ps);
472             }
473             page_started = true;
474             ps.NewPage(596, 842);
475             ps.FontSize(12);
476
477             ps.Text(40, 750, String(Anumber));
478             ps.Text(100, 750, name);
479
480             ps.FontSize(8);
481             y = 730;
482             row = 0;
483          }
484
485          saldo = m[M];
486          total += m[M];
487
488          if (row % 2 == 1)
489          {
490             ps.Rectangle(30, y-2, 550, 9);
491          }
492             /*  Search the booking date and description */
493
494          for (B = 0; b[B].number() != m[M].number(); B++);
495          bookstr << b[B];
496          ps.Text(40, y, bookstr.str().c_str());
497
498          ps.Text(400, y, saldo.debit().to_string());
499          ps.Text(480, y, saldo.credit().to_string());
500
501          y -= 10;
502          row++;
503       }
504    }
505
506    /*  Print the footer with the totals  */
507
508
509    if (page_started)
510    {
511       ps.Text(100, 50, "SALDO");
512       saldo = -~total;
513          ps.Text(400, 50, saldo.debit().to_string());
514          ps.Text(480, 50, saldo.credit().to_string());
515       ps.Line(30,  40, 580,  40);
516       total += -~total;
517       ps.Text(100, 30, "TOTAAL");
518          ps.Text(400, 30, total.debit().to_string());
519          ps.Text(480, 30, total.credit().to_string());
520       PostScript_account_layout(ps);
521    }
522 }
523
524 void Account::sheet(std::ostream &s)
525 {
526    int      M, B;
527    balance  total;
528
529    s << "\n\n             " << Anumber << "  " << name << "\n";
530    s << "================================================================";
531    s << "===========+=============+===========+\n";
532
533    /*  Go through all mutations, selecting for each account  */
534
535    for (M = 0; M < n_m; M++)
536    {
537       if (acc_nr(m[M]) == Anumber)
538       {
539          total += m[M];
540
541             /*  Search the booking date and description */
542
543          for (B = 0; b[B].number() != m[M].number(); B++);
544          s << b[B] << "|" << balance(m[M]) << " |\n";
545       }
546    }
547
548    /*  Print the footer with the totals  */
549
550    s << "                 SALDO                                      ";
551    s << "               |" << -~total << " |\n";
552    s << "----------------------------------------------------------------";
553    s << "-----------+-------------+-----------+\n";
554    total += -~total;
555    s << "                 TOTAAL                                       ";
556    s << "             |" << total << " |\n";
557    s << "----------------------------------------------------------------";
558    s << "-----------+-------------+-----------+\n";
559 }
560
561 void Ledger::read(char *filename)
562 {
563    std::ifstream   in(filename);
564    Account   A;
565    char      line_begin;
566
567    if (!in)
568    {
569       std::cerr << "Cannot read ledger file" << filename << "\n";
570       return;
571    }
572
573    nr_accs = 0;
574    while (in)
575    {
576       line_begin = '\n';
577       in.get(line_begin);
578       if (isdigit(line_begin))
579       {
580          in.putback(line_begin);
581          in >> A;
582          accs[nr_accs++] = A;
583       }
584       else if (line_begin == '$')
585       {
586          // Optional attributes
587          String  attribute;
588          String  a_name, a_value;
589
590          in >> attribute;
591
592          int  separator = attribute.index('=');
593          do
594          {
595             separator--;
596          }
597          while (isspace(attribute[separator]));
598          a_name = attribute(0, separator+1);
599
600          separator = attribute.index('=');
601          do
602          {
603             separator++;
604          }
605          while (isspace(attribute[separator]));
606          a_value = attribute;
607          a_value(0, separator) = "";
608
609          if (a_name == "companyname")
610          {
611             companyname = a_value;
612          }
613          else
614          {
615             std::cerr << "Unrecognized attribute in Ledger: " << a_name << "\n";
616          }
617       }
618       else if (line_begin == '\n')
619       {
620          //  Ignore empty lines
621       }
622       else
623       {
624          //  Anything else is comment: eat it.
625          String comment;
626
627          in >> comment;
628       }
629    }
630    accs[nr_accs].Name() = String("Rekening bestaat niet");
631
632 }
633