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