[render] pycompta-render to html, json or csv (closes #268913)

authorNicolas Chauvat <nicolas.chauvat@logilab.fr>
changeset21f227c3477f
branchdefault
phasepublic
hiddenno
parent revision#770f22a7a535 rapports configurables (closes #257021)
child revision#aa7d88c58d0a [render_*] ajoute write_historique_compte à render_mod()
files modified by this revision
bin/pycompta-render
lib/render_csv.py
lib/render_html.py
lib/render_json.py
main.py
# HG changeset patch
# User Nicolas Chauvat <nicolas.chauvat@logilab.fr>
# Date 1414008850 -7200
# Wed Oct 22 22:14:10 2014 +0200
# Node ID 21f227c3477fcfe0ef118193e85fc95f752bbd18
# Parent 770f22a7a535d109a07465e6f7994336a682168e
[render] pycompta-render to html, json or csv (closes #268913)

diff --git a/bin/pycompta-render b/bin/pycompta-render
@@ -1,44 +1,36 @@
1  #!/usr/bin/python
2 
3  import sys
4  import os.path as osp
5 -import glob
6 -import codecs
7 -import xml.etree.ElementTree as ET
8 -from pycompta.lib import render_html
9 +import optparse
10 +
11 +from pycompta.main import render_mod
12 
13 -target = sys.argv[1]
14 -if not target.endswith('/'): target += '/'
15 -basedir = osp.dirname(target)
16 -planc = {}
17 -for compte in ET.parse(osp.join(target, 'plan-comptable.xml')).findall('.//compte'):
18 -    planc[compte.get('numero')] = compte.get('nom')
19 -cr_precedent = ET.parse(osp.join(target, 'compte-resultat_precedent.xml'))
20 -bilan_precedent = ET.parse(osp.join(target, 'bilan_precedent.xml'))
21 +def mk_optparser():
22 +    usage = "usage: %prog [options] <source> <target>"
23 +    parser = optparse.OptionParser(usage)
24 +    parser.add_option('--debug', action='store_true', default=False,
25 +                      help=u"affiche messages de debug")
26 +    return parser
27 +
28 +def run(frmat, target, options):
29 +
30 +    if not target.endswith('/'): target += '/'
31 +    basedir = osp.dirname(target)
32 
33 -for path in glob.glob(osp.join(basedir,'*.xml')):
34 -    basename = osp.basename(path)
35 -    if basename.startswith('journal'):
36 -        dest = path.replace('journal', 'journal2').replace('.xml', '.html')
37 -        print 'rendering journal', dest
38 -        render_html.write_journal(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
39 -    elif basename.startswith('grand-livre'):
40 -        dest = path.replace('grand-livre', 'balance2').replace('.xml', '.html')
41 -        print 'rendering balance', dest, 'from', path
42 -        render_html.write_balance(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
43 -        dest = path.replace('grand-livre', 'grand-livre2').replace('.xml', '.html')
44 -        print 'rendering grand-livre', dest, 'from', path
45 -        render_html.write_grandlivre(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
46 -        dest = path.replace('grand-livre', 'tva2').replace('.xml', '.html')
47 -        print 'rendering tva', dest, 'from', path
48 -        render_html.write_tva(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
49 -    elif basename.startswith('compte-resultat'):
50 -        dest = path.replace('compte-resultat', 'compte-resultat2').replace('.xml', '.html')
51 -        print 'rendering compte-resultat', dest, 'from', path
52 -        render_html.write_compte_resultat(codecs.open(dest,'w','utf-8'), ET.parse(path), planc, cr_precedent)
53 -    elif basename.startswith('bilan-immo'):
54 -        pass
55 -    elif basename.startswith('bilan') and not basename.startswith('bilan_precedent'):
56 -        dest = path.replace('bilan', 'bilan2').replace('.xml', '.html')
57 -        print 'rendering bilan', dest, 'from', path
58 -        render_html.write_bilan(codecs.open(dest,'w','utf-8'), ET.parse(path), planc, bilan_precedent)
59 +    if frmat == 'html':
60 +        from pycompta.lib import render_html as mod
61 +    if frmat == 'json':
62 +        from pycompta.lib import render_json as mod
63 +
64 +    render_mod(mod, basedir)
65 +
66 +if __name__ == '__main__':
67 +    parser = mk_optparser()
68 +    options, args = parser.parse_args(sys.argv)
69 +
70 +    if options.debug:
71 +        import logging
72 +        logging.getLogger().setLevel(level=logging.DEBUG)
73 +
74 +    run(args[1], args[2], options)
diff --git a/lib/render_csv.py b/lib/render_csv.py
@@ -0,0 +1,43 @@
75 +# -*- coding: utf-8 -*-
76 +
77 +from pycompta.lib.render_html import fmc, format_montant
78 +
79 +SOCIETE = 'SOCIETE'
80 +EXTENSION = '.csv'
81 +
82 +def div100(string):
83 +    if len(string) > 2:
84 +        return string[:-2]+','+string[-2:]
85 +    return string
86 +
87 +def write_journal(output, journal, planc):
88 +    pass
89 +
90 +def write_grandlivre(*args):
91 +    pass
92 +
93 +def write_balance(out, glivre, planc):
94 +    glnode = glivre.getroot()
95 +    out.write('# Comptabilite %s -- Balance du %s au %s\r\n' % (
96 +            SOCIETE, glnode.get('debut'), glnode.get('fin')))
97 +    out.write('compte;libelle;report-debit;report-credit;var-debit;var-credit;debit;credit\r\n')
98 +    for compte in glivre.findall('compte'):
99 +        total = int(compte.get('solde'))
100 +        out.write(';'.join([compte.get('num'), planc.get(compte.get('num'), u''),
101 +                            div100(compte.get('report-debit')),
102 +                            div100(compte.get('report-credit')),
103 +                            div100(compte.get('var-debit')),
104 +                            div100(compte.get('var-credit')),
105 +                            div100(str(max(-total,0))),
106 +                            div100(str(max(total,0)))
107 +                            ]))
108 +        out.write('\r\n')
109 +
110 +def write_compte_resultat(*args):
111 +    pass
112 +
113 +def write_bilan(*args):
114 +    pass
115 +
116 +def write_tva(*args):
117 +    pass
diff --git a/lib/render_html.py b/lib/render_html.py
@@ -22,10 +22,12 @@
118  TRHIGHLIGHT = u'''<tr onmouseover="addElementClass(this, 'highlighted');" ''' \
119      '''onmouseout="removeElementClass(this, 'highlighted')">'''
120 
121  SOCIETE = '%soc%'
122 
123 +EXTENSION = '.html'
124 +
125  # FIXME: move javascript to lib ?
126 
127  def fmc(compte, key, empty_if_zero=True):
128      return format_montant(compte.get(key), empty_if_zero)
129 
diff --git a/lib/render_json.py b/lib/render_json.py
@@ -0,0 +1,60 @@
130 +# -*- coding: utf-8 -*-
131 +
132 +import json
133 +
134 +SOCIETE = 'SOCIETE'
135 +EXTENSION = '.json'
136 +
137 +def write_journal(output, journal, planc):
138 +    jnl = journal.getroot()
139 +    out = {}
140 +    out['title'] = u'Comptabilité %s -- Journal du %s au %s' % (SOCIETE, jnl.get('debut'), jnl.get('fin'))
141 +    out['debit'] = jnl.get('debit')
142 +    out['credit'] = jnl.get('credit')
143 +    out['ecritures'] = []
144 +    ecritures = sorted((ecr for ecr in journal.findall('ecriture')), key=lambda x: x.get('date'))
145 +    for ecr in ecritures:
146 +        item = {'num': ecr.get('e_num'),
147 +                'date': ecr.get('date'),
148 +                'label': ecr.findtext('libelle'),
149 +                'debits': [],
150 +                'credits': [],
151 +                }
152 +        for part in sorted((deb for deb in ecr.findall('debit')), key=lambda x: x.get('compte')):
153 +            item['debits'].append({'compte':part.get('compte'), 'montant': part.get('montant')})
154 +        for part in sorted((deb for deb in ecr.findall('credit')), key=lambda x: x.get('compte')):
155 +            item['credits'].append({'compte':part.get('compte'), 'montant': part.get('montant')})
156 +        txt = u''
157 +        if ecr.get('ref'):
158 +            txt += u'Cf %s <br />' % ecr.get('ref')
159 +        for reg in ecr.findall('reglement'):
160 +            txt += u'Réglement %s %s <br />' % (reg.get('type',''), reg.text)
161 +        out['ecritures'].append(item)
162 +    json.dump(out, output)
163 +
164 +def write_grandlivre(*args):
165 +    pass
166 +
167 +def write_balance(*args):
168 +    pass
169 +
170 +def write_compte_resultat(output, resultat, planc, crprecedent):
171 +    crnode = resultat.getroot()
172 +    out = {}
173 +    out['title'] = u'Comptabilité %s -- Compte résulat du %s au %s' % (
174 +        SOCIETE, crnode.get('debut'), crnode.get('fin'))
175 +    out['debut'] = crnode.get('debut')
176 +    out['fin'] = crnode.get('fin')
177 +    pnode = crnode.find('produits')
178 +    #out['produits'] = dict([ (poste.get('id'), poste) for poste in pnode.findall('poste') ])
179 +    cnode = crnode.find('charges')
180 +    #out['charges'] = dict([ (poste.get('id'), poste) for poste in cnode.findall('poste') ])
181 +    print out
182 +    json.dump(out, output)
183 +    return
184 +
185 +def write_bilan(*args):
186 +    pass
187 +
188 +def write_tva(*args):
189 +    pass
diff --git a/main.py b/main.py
@@ -10,10 +10,13 @@
190  import sys
191  import os
192  import os.path as osp
193  import optparse
194  import logging
195 +import glob
196 +import codecs
197 +import xml.etree.ElementTree as ET
198 
199  if sys.stdout.isatty():
200      from logilab.common.logging_ext import set_color_formatter
201      set_color_formatter()
202 
@@ -134,10 +137,54 @@
203      else :
204          compta_prev = None
205 
206      return compta, compta_prev
207 
208 +def planc2dict(filename):
209 +    planc = {}
210 +    for compte in ET.parse(filename).findall('.//compte'):
211 +        planc[compte.get('numero')] = compte.get('nom')
212 +    return planc
213 +
214 +def render_mod(mod, basedir, filenames=None):
215 +    planc = planc2dict(osp.join(basedir, 'plan-comptable.xml'))
216 +    #societe = ET.parse(osp.join(basedir, 'societe.xml'))
217 +
218 +    ext = mod.EXTENSION
219 +
220 +    if filenames is None:
221 +        filenames = glob.glob(osp.join(basedir,'*.xml'))
222 +
223 +    for path in filenames:
224 +        basename = osp.basename(path)
225 +        if basename.startswith('journal'):
226 +            dest = path.replace('.xml', ext)
227 +            logging.debug('rendering journal %s', dest)
228 +            mod.write_journal(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
229 +        elif basename.startswith('grand-livre'):
230 +            dest = path.replace('grand-livre', 'balance').replace('.xml', ext)
231 +            logging.debug('rendering balance %s from %s', dest, path)
232 +            mod.write_balance(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
233 +            dest = path.replace('.xml', ext)
234 +            logging.debug('rendering grand-livre %s from %s', dest, path)
235 +            mod.write_grandlivre(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
236 +            dest = path.replace('grand-livre', 'tva').replace('.xml', ext)
237 +            logging.debug('rendering tva %s from %s', dest, path)
238 +            mod.write_tva(codecs.open(dest,'w','utf-8'), ET.parse(path), planc)
239 +        elif basename.startswith('compte-resultat'):
240 +            cr_precedent = ET.parse(osp.join(basedir, 'compte-resultat_precedent.xml'))
241 +            dest = path.replace('.xml', ext)
242 +            logging.debug('rendering compte-resultat %s from %s', dest, path)
243 +            mod.write_compte_resultat(codecs.open(dest,'w','utf-8'), ET.parse(path), planc, cr_precedent)
244 +        elif basename.startswith('bilan-immo'):
245 +            pass
246 +        elif basename.startswith('bilan') and not basename.startswith('bilan_precedent'):
247 +            bilan_precedent = ET.parse(osp.join(basedir, 'bilan_precedent.xml'))
248 +            dest = path.replace('.xml', ext)
249 +            logging.debug('rendering bilan %s from %s', dest, path)
250 +            mod.write_bilan(codecs.open(dest,'w','utf-8'), ET.parse(path), planc, bilan_precedent)
251 +
252  ## MAIN ########################################################################
253 
254  def mk_optparser():
255      usage = "usage: %prog [options] <source> <target>"
256      parser = optparse.OptionParser(usage)
@@ -181,10 +228,11 @@
257      if options.fo or (options.pdf and options.fo is None) :
258          render.render_fo(*job)
259      if options.pdf :
260          render.render_pdf(*job)
261 
262 +# XXX move to bin/pycompta
263  def run(args):
264      """
265      Programme principal
266      """
267      # parse command-line
@@ -239,6 +287,5 @@
268          stats = stats.load('stones.prof')
269          stats.sort_stats('time', 'calls')
270          stats.print_stats(30)
271      else:
272          return run_standard(config, debut, fin, prev, options)
273 -