diff --git a/__init__.py b/__init__.py index 852ae23..d0ee2ad 100644 --- a/__init__.py +++ b/__init__.py @@ -11,6 +11,7 @@ from .cron import Cron from .onlinesource import OnlineSource from .update_wiz import UpdateSoureWizard from .diagram import GraphDef, ChartPoint +from .import_wiz import ImportWizard, ImportWizardStart def register(): @@ -21,6 +22,7 @@ def register(): Rate, Identifier, Cron, + ImportWizardStart, module='investment', type_='model') Pool.register( GraphDef, @@ -28,5 +30,6 @@ def register(): module='investment', type_='model', depends=['diagram']) Pool.register( UpdateSoureWizard, + ImportWizard, module='investment', type_='wizard') diff --git a/import_wiz.py b/import_wiz.py new file mode 100644 index 0000000..d33d954 --- /dev/null +++ b/import_wiz.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +# This file is part of the investment-module from m-ds for Tryton. +# The COPYRIGHT file at the top level of this repository contains the +# full copyright notices and license terms. + +from io import StringIO +from datetime import datetime, date +from decimal import Decimal +import csv +from trytond.pool import Pool +from trytond.model import ModelView, fields +from trytond.wizard import Wizard, StateTransition, StateView, Button +from trytond.transaction import Transaction +from trytond.exceptions import UserError +from trytond.i18n import gettext + + +sel_dec_divider = [ + (',', ','), + ('.', '.'), + ] + +sel_date_fmt = [ + ('%d.%m.%Y', 'dd.mm.yyyy'), + ('%Y-%m-%d', 'yyyy-mm-dd'), + ('%m/%d/%Y', 'mm/dd/yyyy'), + ] + +sel_field_delimiter = [ + (';', ';'), + (',', ','), + ] + + +class ImportWizardStart(ModelView): + 'Import CSV-File' + __name__ = 'investment.imp_wiz.start' + + asset = fields.Many2One(string='Asset', readonly=True, + model_name='investment.asset') + file_ = fields.Binary(string="CSV-File", required=True) + dec_divider = fields.Selection(string='Decimal divider', + required=True, selection=sel_dec_divider) + date_fmt = fields.Selection(string='Date format', + required=True, selection=sel_date_fmt) + field_delimiter = fields.Selection(string='Field delimiter', + required=True, selection=sel_field_delimiter) + +# end ImportWizardStart + + +class ImportWizard(Wizard): + 'Import CSV-File' + __name__ = 'investment.imp_wiz' + + start_state = 'start' + start = StateView(model_name='investment.imp_wiz.start', \ + view='investment.imp_wiz_start_form', \ + buttons=[ + Button(string='Cancel', state='end', icon='tryton-cancel'), + Button(string='Import File', state='importf', icon='tryton-import', default=True), + ]) + importf = StateTransition() + + def default_start(self, fields): + """ show asset + """ + context = Transaction().context + + values = { + 'dec_divider': ',', + 'date_fmt': '%d.%m.%Y', + 'field_delimiter': ';', + } + values['asset'] = context.get('active_id', None) + return values + + def transition_importf(self): + """ read file, import + """ + pool = Pool() + ImportWiz = pool.get('investment.imp_wiz', type='wizard') + + if self.start.file_ is not None: + (lines, max_date, min_date) = ImportWiz.read_csv_file( + self.start.file_.decode('utf8'), + dec_divider = self.start.dec_divider, + date_fmt = self.start.date_fmt, + delimiter = self.start.field_delimiter) + + if len(lines) > 0: + ImportWiz.upload_rates( + self.start.asset, + lines, min_date, max_date) + return 'end' + + @classmethod + def upload_rates(cls, asset, rates_list, min_date, max_date): + """ upload new rates to asset + """ + Rate = Pool().get('investment.rate') + + # get rate in date-range + rates = Rate.search([ + ('asset.id', '=', asset.id), + ('date', '>=', min_date), + ('date', '<=', max_date), + ]) + existing_dates = [x.date for x in rates] + done_dates = [] + + to_create = [] + for rate in rates_list: + if rate['date'] in existing_dates: + continue + if rate['date'] in done_dates: + continue + + to_create.append({ + 'asset': asset.id, + 'date': rate['date'], + 'rate': rate['rate'], + }) + done_dates.append(rate['date']) + + if len(to_create) > 0: + print('-- to_create:', to_create) + Rate.create(to_create) + + @classmethod + def read_csv_file(cls, file_content, dec_divider, date_fmt, delimiter): + """ read file-content from csv + """ + result = [] + + del_chars = ['.', ','] + del_chars.remove(dec_divider) + min_date = None + max_date = None + min_rate = None + max_rate = None + + with StringIO(file_content) as fhdl: + csv_lines = csv.DictReader(fhdl, + fieldnames = ['date', 'rate'], + dialect='excel', + delimiter=delimiter) + + for line in csv_lines: + # skip first line + if line.get('date', '') == 'date': + continue + + try : + date_val = datetime.strptime(line.get('date', None).strip(), date_fmt).date() + except : + raise UserError(gettext( + 'investment.msg_import_err_date', + datefmt = date_fmt, + colnr = '1', + )) + try : + rate_val = line.get('rate', None).replace(del_chars[0], '').strip() + rate_val = Decimal(rate_val.replace(dec_divider, '.')) + except : + raise UserError(gettext( + 'investment.msg_import_err_date', + datefmt = 'dd%sdd' % dec_divider, + colnr = '2', + )) + + if isinstance(date_val, date) and isinstance(rate_val, Decimal): + result.append({'date': date_val, 'rate': rate_val}) + + # date range + if max_date is None: + max_date = date_val + else : + if max_date < date_val: + max_date = date_val + + if min_date is None: + min_date = date_val + else : + if min_date > date_val: + min_date = date_val + + # rate range + if max_rate is None: + max_rate = rate_val + else : + if max_rate < rate_val: + max_rate = rate_val + + if min_rate is None: + min_rate = rate_val + else : + if min_rate > rate_val: + min_rate = rate_val + else : + raise UserError(gettext( + 'investment.msg_err_unknown_content', + linetxt = line, + )) + + # ~ print('- found %d records' % len(result)) + # ~ print('- dates from %s to %s' % ( + # ~ min_date.isoformat() if min_date is not None else '-', + # ~ max_date.isoformat() if max_date is not None else '-', + # ~ )) + # ~ print('- rates from %s to %s' % ( + # ~ str(min_rate) if min_rate is not None else '-', + # ~ str(max_rate) if max_rate is not None else '-', + # ~ )) + return (result, max_date, min_date) + +# end ImportWizard + diff --git a/import_wiz.xml b/import_wiz.xml new file mode 100644 index 0000000..6a852ca --- /dev/null +++ b/import_wiz.xml @@ -0,0 +1,31 @@ + + + + + + + investment.imp_wiz.start + form + import_start_form + + + + Import CSV-File + investment.imp_wiz + + + + + + + + form_action + investment.asset,-1 + + + + + diff --git a/locale/de.po b/locale/de.po index a3858a1..99f1889 100644 --- a/locale/de.po +++ b/locale/de.po @@ -34,6 +34,14 @@ msgctxt "model:ir.message,text:msg_currency_rate_positive" msgid "A asset rate must be positive." msgstr "Der Kurs des Vermögenswertes muss positiv sein." +msgctxt "model:ir.message,text:msg_import_err_date" +msgid "failed to read column %(colnr)s of file, expected date (format: %(datefmt)s)" +msgstr "Fehler beim Lesen von Spalte %(colnr)s der Datei, erwartetes Datum (Format: %(datefmt)s)" + +msgctxt "model:ir.message,text:msg_err_unknown_content" +msgid "failed to identify row content: %(linetxt)s" +msgstr "Zeileninhalt konnte nicht identifiziert werden: %(linetxt)s" + ############## # ir.ui.menu # @@ -66,6 +74,10 @@ msgctxt "model:ir.action,name:updt_source_wiz" msgid "Update Source" msgstr "Quelle aktualisieren" +msgctxt "model:ir.action,name:act_import_wizard" +msgid "Import CSV-File" +msgstr "CSV-Datei importieren" + ############################### # ir.action.act_window.domain # @@ -83,6 +95,50 @@ msgid "All" msgstr "Alle" +############################ +# investment.imp_wiz.start # +############################ +msgctxt "model:investment.imp_wiz.start,name:" +msgid "Import CSV-File" +msgstr "CSV-Datei importieren" + +msgctxt "field:investment.imp_wiz.start,asset:" +msgid "Asset" +msgstr "Vermögenswert" + +msgctxt "field:investment.imp_wiz.start,file_:" +msgid "CSV-File" +msgstr "CSV-Datei" + +msgctxt "field:investment.imp_wiz.start,dec_divider:" +msgid "Decimal divider" +msgstr "Dezimaltrenner" + +msgctxt "field:investment.imp_wiz.start,date_fmt:" +msgid "Date format" +msgstr "Datumsformat" + +msgctxt "field:investment.imp_wiz.start,field_delimiter:" +msgid "Field delimiter" +msgstr "Feldtrenner" + + +###################### +# investment.imp_wiz # +###################### +msgctxt "model:investment.imp_wiz,name:" +msgid "Import CSV-File" +msgstr "CSV-Datei importieren" + +msgctxt "wizard_button:investment.imp_wiz,start,end:" +msgid "Cancel" +msgstr "Abbruch" + +msgctxt "wizard_button:investment.imp_wiz,start,importf:" +msgid "Import File" +msgstr "Datei importieren" + + ############################ # investment.source_update # ############################ diff --git a/locale/en.po b/locale/en.po index e3466ac..809e7ee 100644 --- a/locale/en.po +++ b/locale/en.po @@ -22,6 +22,14 @@ msgctxt "model:ir.message,text:msg_currency_rate_positive" msgid "A asset rate must be positive." msgstr "A asset rate must be positive." +msgctxt "model:ir.message,text:msg_import_err_date" +msgid "failed to read column %(colnr)s of file, expected date (format: %(datefmt)s)" +msgstr "failed to read column %(colnr)s of file, expected date (format: %(datefmt)s)" + +msgctxt "model:ir.message,text:msg_err_unknown_content" +msgid "failed to identify row content: %(linetxt)s" +msgstr "failed to identify row content: %(linetxt)s" + msgctxt "model:ir.ui.menu,name:menu_investment" msgid "Investment" msgstr "Investment" @@ -46,6 +54,10 @@ msgctxt "model:ir.action,name:updt_source_wiz" msgid "Update Source" msgstr "Update Source" +msgctxt "model:ir.action,name:act_import_wizard" +msgid "Import CSV-File" +msgstr "Import CSV-File" + msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_current" msgid "Current" msgstr "Current" @@ -58,6 +70,42 @@ msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_all" msgid "All" msgstr "All" +msgctxt "model:investment.imp_wiz.start,name:" +msgid "Import CSV-File" +msgstr "Import CSV-File" + +msgctxt "field:investment.imp_wiz.start,asset:" +msgid "Asset" +msgstr "Asset" + +msgctxt "field:investment.imp_wiz.start,file_:" +msgid "CSV-File" +msgstr "CSV-File" + +msgctxt "field:investment.imp_wiz.start,dec_divider:" +msgid "Decimal divider" +msgstr "Decimal divider" + +msgctxt "field:investment.imp_wiz.start,date_fmt:" +msgid "Date format" +msgstr "Date format" + +msgctxt "field:investment.imp_wiz.start,field_delimiter:" +msgid "Field delimiter" +msgstr "Field delimiter" + +msgctxt "model:investment.imp_wiz,name:" +msgid "Import CSV-File" +msgstr "Import CSV-File" + +msgctxt "wizard_button:investment.imp_wiz,start,end:" +msgid "Cancel" +msgstr "Cancel" + +msgctxt "wizard_button:investment.imp_wiz,start,importf:" +msgid "Import File" +msgstr "Import File" + msgctxt "model:investment.source_update,name:" msgid "Update Source" msgstr "Update Source" diff --git a/message.xml b/message.xml index a17315c..2907923 100644 --- a/message.xml +++ b/message.xml @@ -11,6 +11,12 @@ full copyright notices and license terms. --> A asset rate must be positive. + + failed to read column %(colnr)s of file, expected date (format: %(datefmt)s) + + + failed to identify row content: %(linetxt)s + diff --git a/tryton.cfg b/tryton.cfg index 707e2d3..25c87d9 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -18,5 +18,6 @@ xml: update_wiz.xml rate.xml diagram.xml + import_wiz.xml menu.xml cron.xml diff --git a/view/import_start_form.xml b/view/import_start_form.xml new file mode 100644 index 0000000..f787153 --- /dev/null +++ b/view/import_start_form.xml @@ -0,0 +1,19 @@ + + +
+