Skip to content

Commit 0347f14

Browse files
author
djosg
committed
New reports working in gnucash 3 and python 3.
1 parent 3b7b80c commit 0347f14

4 files changed

Lines changed: 4115 additions & 0 deletions

File tree

.gitmetadata

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,11 @@
135135
{'st_mtime': '1531583860', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '91880'} ; reports/gains.py
136136
{'st_mtime': '1531006802', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '16226'} ; reports/hello_world.py
137137
{'st_mtime': '1530709022', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '25680'} ; reports/interest.py
138+
{'st_mtime': '1530718129', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '13376'} ; reports/multi_price_scatter.py
138139
{'st_mtime': '1531583846', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '16384'} ; reports/portfolio.py
140+
{'st_mtime': '1531063525', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '144443'} ; reports/portfolio_performance.py
139141
{'st_mtime': '1530718118', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '10022'} ; reports/price_scatter.py
142+
{'st_mtime': '1530709106', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '29802'} ; reports/transaction.py
140143
{'st_mtime': '1530622160', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '14469'} ; stylesheets/__init__.py
141144
{'st_mtime': '1409316809', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '8152'} ; stylesheets/plain.py
142145
{'st_mtime': '1531584144', 'st_mode': '33188', 'st_uid': '1000', 'st_gid': '1000', 'st_nlink': '1', 'st_size': '22914'} ; sw_app_utils.py

reports/multi_price_scatter.py

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
2+
3+
# how to do internationalization in python
4+
#import gettext
5+
#gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
6+
#gettext.textdomain('myapplication')
7+
#_ = gettext.gettext
8+
9+
# dummy function for internationalization
10+
def N_(msg):
11+
return msg
12+
13+
14+
import xml.etree.ElementTree as ET
15+
16+
17+
import sw_app_utils
18+
import sw_core_utils
19+
20+
import gnc_html_scatter
21+
22+
import gnc_html_document
23+
24+
25+
# this is an attempt at replicating the Price Scatter scheme report
26+
27+
from report_objects import ReportTemplate
28+
29+
# maybe store the class in ReportTemplate then dont need this import
30+
# yes we need a better way to handle this so dont need all these includes
31+
from report_objects import OptionsDB
32+
#from report_options import BooleanOption
33+
#from report_options import MultiChoiceOption
34+
# lets try importing all
35+
from report_options import *
36+
37+
class MultiPriceScatter(ReportTemplate):
38+
39+
def __init__ (self):
40+
41+
# need to set the base variables
42+
self.version = 1
43+
self.name = N_("MultiPrice")
44+
self.report_guid = "0C5629B16E374EE8A490213ABD8B6E86"
45+
self.menu_name = N_("Multi Price Scatterplot")
46+
# we have to define the tip
47+
self.menu_tip = N_("Multi Price Scatterplot")
48+
self.menu_path = N_("_Assets & Liabilities")
49+
50+
# we cant do this because at the moment this object is a ReportTemplate
51+
# which is only instantiated once - different reports instantiate the Report
52+
# class multiple times but using the same ReportTemplate instance - the Report
53+
# class contains the specific options and call this ReportTemplate options_generator
54+
# and render functions
55+
#self.stocks = []
56+
#self.markers = []
57+
#self.markercolors = []
58+
59+
60+
def options_generator (self):
61+
62+
# need to instantiate the options database
63+
self.options = OptionsDB()
64+
65+
self.options.add_date_interval('General', N_("Start Date"), N_("End Date"), "a")
66+
67+
self.options.add_interval_choice('General', N_("Step Size"), "b", 'MonthDelta')
68+
69+
self.options.add_currency('Price', N_("Report's currency"), "d")
70+
71+
self.options.register_option(CommodityOption('Price', N_("Price of Commodity"),"e",
72+
N_("Calculate the price of this commodity."),
73+
sw_core_utils.gnc_locale_default_iso_currency_code()))
74+
75+
# should the list be a dict or not??
76+
self.options.register_option(MultiChoiceOption('Price', N_("Price Source"),"f",
77+
N_("The source of price information."),
78+
'actual-transactions',
79+
[ \
80+
[ 'weighted-average', N_("Weighted Average."), N_("The weighted average of all currency transactions of the past.") ],
81+
[ 'actual-transactions', N_("Actual Transactions"), N_("The instantaneous price of actual currency transactions in the past.") ],
82+
[ 'pricedb', N_("Price Database"), N_("The recorded prices.") ],
83+
]))
84+
85+
self.options.register_option(SimpleBooleanOption('Price', N_("Invert prices"),"g",
86+
N_("Plot commodity per currency rather than currency per commodity."), False))
87+
88+
self.options.add_plot_size('Display', N_("Plot Width"), N_("Plot Height"), "c", 500, 400)
89+
90+
self.options.add_marker_choice('Display', N_("Marker"), "a", 'filledsquare')
91+
92+
self.options.register_option(ColorOption('Display', N_("Marker Color"), "b", N_("Color of the marker."),
93+
(0xb2, 0x22, 0x22, 0), 255, False))
94+
95+
96+
# we need to set the default option section - the one selected
97+
# gnc:options-set-default-section options "General"
98+
99+
return self.options
100+
101+
102+
def renderer (self, report):
103+
104+
# this actually implements the report look
105+
106+
#pdb.set_trace()
107+
108+
# new idea - we store the plot data in the specific report object
109+
# - which fortunately we pass
110+
if report.report_data == None:
111+
class ReportData(object):
112+
pass
113+
report.report_data = ReportData()
114+
report.report_data.stocks = []
115+
report.report_data.markers = []
116+
report.report_data.markercolors = []
117+
118+
stocks = report.report_data.stocks
119+
markers = report.report_data.markers
120+
markercolors = report.report_data.markercolors
121+
122+
# lots of stuff about getting option values
123+
124+
# now for html creation
125+
# where do we actually instantiate the Html object
126+
127+
# need to set a style
128+
# (gnc:html-document-set-style!
129+
# document "body"
130+
# 'attribute (list "bgcolor" (gnc:color-option->html bg-color-op))
131+
# 'font-color (gnc:color-option->html txt-color-op))
132+
133+
#document = gnc_html_document_full.HtmlDocument()
134+
document = gnc_html_document.HtmlDocument()
135+
136+
# this report does not set the title apparently deliberately
137+
#document.title = N_("Price Scatter")
138+
139+
chart = gnc_html_scatter.HtmlMultiScatter()
140+
141+
# Im seeing how this works in scheme report system
142+
# the scheme report system creates lists of entities to be added
143+
# now using one of the HTML/XML python modules to do this - why reinvent the wheel?
144+
# the only real issue is option replacement and links to
145+
# account pages
146+
147+
# note that price-scatter.scm defines an option getting function
148+
# equivalent to the following python - we just split out the python
149+
#def get_option (section, name):
150+
# optobj = self.options.lookup_name(section, name)
151+
# return optobj.value()
152+
153+
# the various plotting options
154+
155+
#pdb.set_trace()
156+
157+
optobj = self.options.lookup_name('General','Report name')
158+
chart.title = optobj.getter()
159+
160+
optobj = self.options.lookup_name('Display','Plot Width')
161+
chart.width = optobj.getter()
162+
163+
optobj = self.options.lookup_name('Display','Plot Height')
164+
chart.height = optobj.getter()
165+
166+
optobj = self.options.lookup_name('General','Step Size')
167+
optstp = optobj.getter()
168+
mapinterval = { \
169+
'DayDelta' : N_("Days"),
170+
'WeekDelta' : N_("Weeks"),
171+
'TwoWeekDelta' : N_("Double-Weeks"),
172+
'MonthDelta' : N_("Months"),
173+
'YearDelta' : N_("Years"),
174+
}
175+
chart.x_axis_label = mapinterval[optstp]
176+
177+
178+
optobj = self.options.lookup_name('General','Start Date')
179+
strtdt = optobj.get_option_value()
180+
181+
optobj = self.options.lookup_name('General','End Date')
182+
enddt = optobj.get_option_value()
183+
184+
185+
commod_table = sw_app_utils.get_current_commodities()
186+
187+
cur = commod_table.lookup('CURRENCY', 'USD')
188+
189+
optobj = self.options.lookup_name('Price','Price of Commodity')
190+
stock = optobj.getter()
191+
192+
optobj = self.options.lookup_name('Price','Invert prices')
193+
optinv = optobj.getter()
194+
# disable this optinv option - dont really understand it
195+
#if optinv:
196+
# chart.y_axis_label = stock.get_mnemonic()
197+
#else:
198+
# chart.y_axis_label = cur.get_mnemonic()
199+
chart.y_axis_label = cur.get_mnemonic()
200+
201+
202+
optobj = self.options.lookup_name('Display','Marker')
203+
#chart.marker = optobj.get_option_value()
204+
marker = optobj.get_option_value()
205+
206+
optobj = self.options.lookup_name('Display','Marker Color')
207+
optclr = optobj.getter()
208+
#chart.markercolor = "%2x%2x%2x"%(optclr[0],optclr[1],optclr[2])
209+
markercolor = "%2x%2x%2x"%(optclr[0],optclr[1],optclr[2])
210+
211+
212+
# update stock list if changed - otherwise assume plot options changing
213+
stock_updated = False
214+
if len(stocks) > 0 :
215+
216+
pdb.set_trace()
217+
218+
# ignore namespace for the moment??
219+
if stocks[-1].get_namespace() != stock.get_namespace() or \
220+
stocks[-1].get_mnemonic() != stock.get_mnemonic():
221+
222+
stocks.append(stock)
223+
markers.append(marker)
224+
markercolors.append(markercolor)
225+
stock_updated = True
226+
227+
else:
228+
229+
stocks.append(stock)
230+
markers.append(marker)
231+
markercolors.append(markercolor)
232+
stock_updated = True
233+
234+
235+
#chart.subtitle = "%s"%chart_sym + " - " + "%s to %s"%(strtdt.strftime("%m/%d/%Y"),enddt.strftime("%m/%d/%Y"))
236+
chart.subtitle = "%s to %s"%(strtdt.strftime("%m/%d/%Y"),enddt.strftime("%m/%d/%Y"))
237+
238+
doplot = True
239+
240+
if len(stocks) == 1:
241+
242+
# this tests the same namespace and mnemonic same
243+
#if gnc_commodity_eqiv(stock,cur):
244+
if stocks[0].get_namespace() == cur.get_namespace() and \
245+
stocks[0].get_mnemonic() == cur.get_mnemonic():
246+
247+
#docstr = "<h2>"+N_("Identical commodities")+"</h2>"
248+
#docstr += "<p>"+N_("Your selected commodity and the currency of the report \
249+
#are identical. It doesn't make sense to show prices for identical \
250+
#commodities.")+"</p>"
251+
#doclst = [docstr]
252+
docelm = ET.Element('div',attrib={'id' : 'scatter1'})
253+
docobj = ET.SubElement(docelm,'h2')
254+
docobj.text = N_("Identical commodities")
255+
newelm = ET.SubElement(docelm,'p')
256+
newelm.text = N_("Your selected commodity and the currency of the report \
257+
are identical. It doesn't make sense to show prices for identical \
258+
commodities.")
259+
260+
document.doc.docobj.append(docelm)
261+
262+
# need to reset any stored data!!
263+
report.report_data.stocks = []
264+
report.report_data.markers = []
265+
report.report_data.markercolors = []
266+
267+
doplot = False
268+
269+
if doplot:
270+
271+
# this is junky emulation of the gnc:deltasym-to-delta scheme function
272+
# this just converts the deltas to seconds - so have a simple linear x time scale
273+
# gnc:deltasym-to-delta converts to actual date time structure - so should
274+
# probably use datetime here
275+
mapxscale = { \
276+
'DayDelta' : 86400.0,
277+
'WeekDelta' : 604800.0,
278+
'TwoWeekDelta' : 1209600.0,
279+
'MonthDelta' : 2628000.0,
280+
'YearDelta' : 31536000.0,
281+
}
282+
283+
#
284+
book = sw_app_utils.get_current_book()
285+
prcdb = book.get_price_db()
286+
287+
for istock,stock in enumerate(stocks):
288+
289+
marker = markers[istock]
290+
markercolor = markercolors[istock]
291+
292+
# disable this - dont really understand this option yet
293+
#if optinv:
294+
# chart_sym = cur.get_mnemonic()
295+
#else:
296+
# chart_sym = stock.get_mnemonic()
297+
chart_sym = stock.get_mnemonic()
298+
299+
prclst = prcdb.get_prices(stock,cur)
300+
301+
if len(prclst) <= 0:
302+
print("Warning: no prices for stock",chart_sym)
303+
chart.add_data([], marker, markercolor, label=chart_sym)
304+
continue
305+
306+
#pdb.set_trace()
307+
308+
prcordr = []
309+
for prc in prclst:
310+
prcdt = prc.get_time64().date()
311+
if prcdt >= strtdt.date() and prcdt <= enddt.date():
312+
prcordr.append((float(prcdt.strftime("%s")),prc))
313+
prcordr.sort()
314+
315+
datalst = []
316+
317+
if len(prcordr) > 0:
318+
319+
prclst0 = prcordr[0][1]
320+
prcdt = prclst0.get_time64().date()
321+
prcdt0 = float(prcdt.strftime("%s"))
322+
323+
for prctm,prc in prcordr:
324+
prcobj = prc.get_value()
325+
#rlprc = float(prcobj.num)/prcobj.denom
326+
rlprc = prcobj.to_double()
327+
328+
# we need to convert the date to seconds
329+
cnvdt = (prctm-prcdt0)/mapxscale[optstp]
330+
331+
#chart.add_datapoint((cnvdt,rlprc))
332+
datalst.append((cnvdt,rlprc))
333+
334+
else:
335+
336+
print("Warning: no prices for stock in date range",chart_sym)
337+
338+
# for multi price scatter we add
339+
chart.add_data(datalst, marker, markercolor, label=chart_sym)
340+
341+
pdb.set_trace()
342+
343+
docelm = chart.render()
344+
345+
#document.doc.docobj = docelm
346+
document.doc.docobj.append(docelm)
347+
348+
stdelm = ET.Element('p')
349+
stdelm.text = "Start Date %s"%str(strtdt)
350+
docelm.append(stdelm)
351+
352+
endelm = ET.Element('p')
353+
endelm.text = "End Date %s"%str(enddt)
354+
docelm.append(endelm)
355+
356+
return document

0 commit comments

Comments
 (0)