├── .gitignore
├── MANIFEST.in
├── designer_plugins
├── plugin_date_range_selector.py
├── plugin_db_lookup_combobox.py
├── plugin_log_viewer.py
├── plugin_tableview_with_footer.py
└── plugin_treeview_with_footer.py
├── docs
├── FAQ.md
├── FAQ.ru.md
├── LICENSE
├── README.md
├── README.ru.md
├── error_description.md
├── error_description.ru.md
├── img
│ ├── fns_auth_dialog.png
│ ├── investment_portfolio_holdings.png
│ ├── main_linux.png
│ ├── main_windows.png
│ ├── one_account_view.png
│ ├── report_deals.png
│ ├── report_income_spending.png
│ ├── report_profit_loss.png
│ ├── slip_import.png
│ └── stocks_and_investment_account.png
├── ru-tax-3ndfl
│ ├── 3ndfl_tax_report.xlsx
│ ├── IBKR_flex_sample.xml
│ ├── corporate_actions.md
│ ├── corporate_actions.odg
│ ├── cost_basis
│ │ ├── 20190416_Form8937-NVS_ALC_SpinOff.pdf
│ │ ├── 20201130_Form8937_PFE_VTRS_SpinOff.pdf
│ │ ├── 20220411_Form8937_T_WBD_SpinOff.pdf
│ │ ├── 20230103_Form8937_GE_GEHC_SpinOff.pdf
│ │ └── 20240401_Form8937_GE_GEV_SpinOff.pdf
│ ├── declaration.dc0
│ ├── declaration_empty.dc0
│ ├── img
│ │ ├── account_selection.png
│ │ ├── bank_account.png
│ │ ├── corporate_actions.png
│ │ ├── declaration_1.png
│ │ ├── declaration_2.png
│ │ ├── declaration_3.png
│ │ ├── ibkr_account.png
│ │ ├── ibkr_selection_example.png
│ │ ├── import_log.png
│ │ ├── main_window.png
│ │ ├── menu_selection.png
│ │ ├── report_1.png
│ │ ├── report_2.png
│ │ ├── report_3.png
│ │ ├── report_4.png
│ │ ├── report_5.png
│ │ ├── report_params.png
│ │ └── update_quotes.png
│ └── taxes.md
└── ru-tax-currency
│ ├── CBR2TAX_currency.ipynb
│ └── CurrencyRates.xml
├── jal
├── __init__.py
├── compile_ui
├── constants.py
├── create_pro
├── data_export
│ ├── __init__.py
│ ├── dlsg.py
│ ├── tax_reports
│ │ ├── __init__.py
│ │ ├── portugal.json
│ │ ├── portugal.py
│ │ ├── russia.json
│ │ └── russia.py
│ ├── taxes.py
│ ├── taxes_flow.py
│ ├── templates
│ │ ├── __init__.py
│ │ ├── tax_prt_dividends.json
│ │ ├── tax_prt_interests.json
│ │ ├── tax_prt_shares.json
│ │ ├── tax_rus_bonds.json
│ │ ├── tax_rus_crypto.json
│ │ ├── tax_rus_derivatives.json
│ │ ├── tax_rus_dividends.json
│ │ ├── tax_rus_fees.json
│ │ ├── tax_rus_flow.json
│ │ ├── tax_rus_interests.json
│ │ └── tax_rus_trades.json
│ └── xlsx.py
├── data_import
│ ├── __init__.py
│ ├── broker_statements
│ │ ├── __init__.py
│ │ ├── ibkr.py
│ │ ├── just2trade.py
│ │ ├── kit.py
│ │ ├── open_portfolio.py
│ │ ├── psb.py
│ │ ├── tvoy.py
│ │ └── vtb.py
│ ├── category_recognizer.py
│ ├── import_schema.json
│ ├── receipt_api
│ │ ├── __init__.py
│ │ ├── eu_lidl_plus.py
│ │ ├── pt_pingo_doce.py
│ │ ├── receipt_api.py
│ │ ├── receipts.py
│ │ └── ru_fns.py
│ ├── shop_receipt.py
│ ├── statement.py
│ ├── statement_xls.py
│ ├── statement_xml.py
│ └── statements.py
├── db
│ ├── __init__.py
│ ├── account.py
│ ├── asset.py
│ ├── backup_restore.py
│ ├── balances_model.py
│ ├── category.py
│ ├── closed_trade.py
│ ├── country.py
│ ├── db.py
│ ├── deposit.py
│ ├── helpers.py
│ ├── holdings_model.py
│ ├── ledger.py
│ ├── operations.py
│ ├── operations_model.py
│ ├── peer.py
│ ├── reference_models.py
│ ├── settings.py
│ ├── tag.py
│ ├── tax_estimator.py
│ ├── trades_model.py
│ ├── tree_model.py
│ └── view_model.py
├── img
│ ├── aux_ibkr.png
│ ├── aux_j2t.png
│ ├── aux_kit.png
│ ├── aux_pof.png
│ ├── aux_psb.ico
│ ├── aux_tvoy.png
│ ├── aux_vtb.ico
│ ├── flag_en.png
│ ├── flag_pt.png
│ ├── flag_ru.png
│ ├── tag_bank.ico
│ ├── tag_card.ico
│ ├── tag_cash.ico
│ ├── tag_investing.ico
│ ├── ui_add.ico
│ ├── ui_add_child.ico
│ ├── ui_amortization.ico
│ ├── ui_buy.ico
│ ├── ui_cancel.ico
│ ├── ui_chart.ico
│ ├── ui_clean.ico
│ ├── ui_copy.ico
│ ├── ui_coupon.ico
│ ├── ui_delisting.ico
│ ├── ui_deposit_account.ico
│ ├── ui_deposit_close.ico
│ ├── ui_deposit_open.ico
│ ├── ui_details.ico
│ ├── ui_dividend.ico
│ ├── ui_dividend_stock.ico
│ ├── ui_fee.ico
│ ├── ui_interest.ico
│ ├── ui_jal.png
│ ├── ui_list.ico
│ ├── ui_merger.ico
│ ├── ui_minus.ico
│ ├── ui_ok.ico
│ ├── ui_plus.ico
│ ├── ui_remove.ico
│ ├── ui_renaming.ico
│ ├── ui_sell.ico
│ ├── ui_spinoff.ico
│ ├── ui_split.ico
│ ├── ui_tag.ico
│ ├── ui_tax.ico
│ ├── ui_total.ico
│ ├── ui_transfer_asset_in.ico
│ ├── ui_transfer_asset_out.ico
│ ├── ui_transfer_in.ico
│ ├── ui_transfer_out.ico
│ ├── ui_vesting.ico
│ └── ui_with_credit.ico
├── jal.pro
├── jal.py
├── jal_init.sql
├── languages
│ ├── en.qm
│ ├── en.ts
│ ├── ru.qm
│ └── ru.ts
├── net
│ ├── __init__.py
│ ├── downloader.py
│ ├── moex.py
│ └── web_request.py
├── pypi_description.md
├── reports
│ ├── __init__.py
│ ├── account_balance.py
│ ├── assets_payments.py
│ ├── category.py
│ ├── deals.py
│ ├── income_spending.py
│ ├── operations_base.py
│ ├── peer.py
│ ├── portfolio.py
│ ├── profit_loss.py
│ ├── reports.py
│ ├── tag.py
│ └── term_deposits.py
├── run_designer
├── ui
│ ├── __init__.py
│ ├── asset_dlg.ui
│ ├── flow_export_widget.ui
│ ├── login_fns_dlg.ui
│ ├── login_lidl_plus_dlg.ui
│ ├── login_pingo_doce_dlg.ui
│ ├── main_window.ui
│ ├── operations_widget.ui
│ ├── quotes_update.ui
│ ├── rebuild_window.ui
│ ├── receipt_import_dlg.ui
│ ├── reference_data_dlg.ui
│ ├── reports
│ │ ├── __init__.py
│ │ ├── account_balance_report.ui
│ │ ├── assets_payments_report.ui
│ │ ├── category_report.ui
│ │ ├── deals_report.ui
│ │ ├── income_spending_report.ui
│ │ ├── peer_report.ui
│ │ ├── portfolio_report.ui
│ │ ├── profit_loss_report.ui
│ │ ├── tag_report.ui
│ │ ├── tax_estimation.ui
│ │ ├── term_deposits_report.ui
│ │ ├── ui_account_balance_report.py
│ │ ├── ui_assets_payments_report.py
│ │ ├── ui_category_report.py
│ │ ├── ui_deals_report.py
│ │ ├── ui_income_spending_report.py
│ │ ├── ui_peer_report.py
│ │ ├── ui_portfolio_report.py
│ │ ├── ui_profit_loss_report.py
│ │ ├── ui_tag_report.py
│ │ ├── ui_tax_estimation.py
│ │ └── ui_term_deposits_report.py
│ ├── select_account_dlg.ui
│ ├── select_reference_dlg.ui
│ ├── tax_export_widget.ui
│ ├── ui_asset_dlg.py
│ ├── ui_flow_export_widget.py
│ ├── ui_login_fns_dlg.py
│ ├── ui_login_lidl_plus_dlg.py
│ ├── ui_login_pingo_doce_dlg.py
│ ├── ui_main_window.py
│ ├── ui_operations_widget.py
│ ├── ui_rebuild_window.py
│ ├── ui_receipt_import_dlg.py
│ ├── ui_reference_data_dlg.py
│ ├── ui_select_account_dlg.py
│ ├── ui_select_reference_dlg.py
│ ├── ui_tax_export_widget.py
│ ├── ui_update_quotes_window.py
│ └── widgets
│ │ ├── __init__.py
│ │ ├── asset_payment_operation.ui
│ │ ├── corporate_action_operation.ui
│ │ ├── income_spending_operation.ui
│ │ ├── term_deposit_operation.ui
│ │ ├── trade_operation.ui
│ │ ├── transfer_operation.ui
│ │ ├── ui_asset_payment_operation.py
│ │ ├── ui_corporate_action_operation.py
│ │ ├── ui_income_spending_operation.py
│ │ ├── ui_term_deposit_operation.py
│ │ ├── ui_trade_operation.py
│ │ └── ui_transfer_operation.py
├── universal_cache.py
├── updates
│ ├── __init__.py
│ ├── jal_delta_40.sql
│ ├── jal_delta_41.sql
│ ├── jal_delta_42.sql
│ ├── jal_delta_43.sql
│ ├── jal_delta_44.sql
│ ├── jal_delta_45.sql
│ ├── jal_delta_46.sql
│ ├── jal_delta_47.sql
│ ├── jal_delta_48.sql
│ ├── jal_delta_49.sql
│ ├── jal_delta_50.sql
│ ├── jal_delta_51.sql
│ ├── jal_delta_52.sql
│ ├── jal_delta_53.sql
│ ├── jal_delta_54.sql
│ ├── jal_delta_55.sql
│ ├── jal_delta_56.sql
│ ├── jal_delta_57.sql
│ ├── jal_delta_58.sql
│ └── jal_delta_59.sql
└── widgets
│ ├── __init__.py
│ ├── abstract_operation_details.py
│ ├── account_select.py
│ ├── asset_dialog.py
│ ├── asset_payment_widget.py
│ ├── corporate_action_widget.py
│ ├── custom
│ ├── __init__.py
│ ├── date_range_selector.py
│ ├── db_lookup_combobox.py
│ ├── log_viewer.py
│ ├── table_footer.py
│ ├── tableview_with_footer.py
│ └── treeview_with_footer.py
│ ├── delegates.py
│ ├── helpers.py
│ ├── icons.py
│ ├── income_spending_widget.py
│ ├── main_window.py
│ ├── mdi.py
│ ├── operations_tabs.py
│ ├── operations_widget.py
│ ├── price_chart.py
│ ├── qr_scanner.py
│ ├── reference_data.py
│ ├── reference_dialogs.py
│ ├── reference_selector.py
│ ├── selection_dialog.py
│ ├── tax_widget.py
│ ├── term_deposit_widget.py
│ ├── trade_widget.py
│ └── transfer_widget.py
├── register_designer_plugins.py
├── requirements.txt
├── run.py
├── setup.py
└── tests
├── __init__.py
├── conftest.py
├── fixtures.py
├── helpers.py
├── pytest.ini
├── test_corp_actions.py
├── test_data
├── 3ndfl_2020.dc0
├── 3ndfl_2020_empty.dc0
├── 3ndfl_2021.dc1
├── 3ndfl_2021_empty.dc1
├── 3ndfl_2022.dc2
├── 3ndfl_2022_empty.dc2
├── deals_set.tgz
├── ibkr.json
├── ibkr.xml
├── ibkr_bond.json
├── ibkr_bond.xml
├── ibkr_cfd.json
├── ibkr_cfd.xml
├── ibkr_corp_actions.json
├── ibkr_corp_actions.xml
├── ibkr_dividends.json
├── ibkr_dividends.xml
├── ibkr_merger_complex.json
├── ibkr_merger_complex.xml
├── ibkr_merger_spinoff.json
├── ibkr_merger_spinoff.xml
├── ibkr_rights.json
├── ibkr_rights.xml
├── ibkr_spinoff.json
├── ibkr_spinoff.xml
├── ibkr_warrants.json
├── ibkr_warrants.xml
├── ibkr_year0.xml
├── ibkr_year1.xml
├── invalid_backup.tgz
├── j2t.json
├── j2t.xlsx
├── kit.json
├── kit.xlsx
├── matched.json
├── pof.json
├── pof_converted.json
├── taxes_bond_rus.json
├── taxes_cfd_rus.json
├── taxes_flow.json
├── taxes_merger_complex_rus.json
├── taxes_merger_spinoff_rus.json
├── taxes_over_years_rus.json
├── taxes_rus.json
├── taxes_spinoff_rus.json
├── taxes_vesting_rus.json
├── tvoy.json
├── tvoy.zip
├── vtb.json
└── vtb.xls
├── test_dlsg.py
├── test_downloaders.py
├── test_ibkr.py
├── test_json_import.py
├── test_ledger.py
├── test_main.py
├── test_statements.py
└── test_taxes_rus.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | /venv
3 | /.idea
4 | /.vscode
5 |
6 | # unit testing
7 | *.prof
8 | .coverage
9 |
10 | # generated by app
11 | /jal/jal.sqlite
12 | /jal.egg-info
13 | *.log
14 | jal_faults.log
15 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include jal/languages/*.png jal/languages/*.qm
2 | include jal/img/*.png jal/img/*.ico
3 | include jal/*.sql jal/updates/*.sql
4 | include jal/pypi_description.md
5 | recursive-exclude tests *
--------------------------------------------------------------------------------
/designer_plugins/plugin_date_range_selector.py:
--------------------------------------------------------------------------------
1 | # Here reference goes from PYSIDE_DESIGNER_PLUGINS directory
2 | from jal.widgets.custom.date_range_selector import DateRangeSelector
3 |
4 | from PySide6.QtGui import QIcon
5 | from PySide6.QtDesigner import (QDesignerCustomWidgetInterface)
6 |
7 |
8 | DOM_XML = """
9 |
10 |
11 |
12 |
13 | 0
14 | 0
15 | 300
16 | 32
17 |
18 |
19 |
20 |
21 | """
22 |
23 |
24 | class DateRangeSelectorPlugin(QDesignerCustomWidgetInterface):
25 | def __init__(self):
26 | super().__init__()
27 | self._initialized = False
28 |
29 | def createWidget(self, parent):
30 | t = DateRangeSelector(parent)
31 | return t
32 |
33 | def domXml(self):
34 | return DOM_XML
35 |
36 | def group(self):
37 | return ''
38 |
39 | def icon(self):
40 | return QIcon()
41 |
42 | def includeFile(self):
43 | return 'jal/widgets/custom/date_range_selector.h'
44 |
45 | def initialize(self, form_editor):
46 | if self._initialized:
47 | return
48 | self._initialized = True
49 |
50 | def isContainer(self):
51 | return False
52 |
53 | def isInitialized(self):
54 | return self._initialized
55 |
56 | def name(self):
57 | return 'DateRangeSelector'
58 |
59 | def toolTip(self):
60 | return 'Widget that allows selection of dates interval'
61 |
62 | def whatsThis(self):
63 | return self.toolTip()
64 |
--------------------------------------------------------------------------------
/designer_plugins/plugin_db_lookup_combobox.py:
--------------------------------------------------------------------------------
1 | # Here reference goes from PYSIDE_DESIGNER_PLUGINS directory
2 | from jal.widgets.custom.db_lookup_combobox import DbLookupComboBox
3 |
4 | from PySide6.QtGui import QIcon
5 | from PySide6.QtDesigner import (QDesignerCustomWidgetInterface)
6 |
7 |
8 | DOM_XML = """
9 |
10 |
11 |
12 |
13 | 0
14 | 0
15 | 300
16 | 32
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | """
31 |
32 |
33 | class DbLookupComboBoxPlugin(QDesignerCustomWidgetInterface):
34 | def __init__(self):
35 | super().__init__()
36 | self._initialized = False
37 |
38 | def createWidget(self, parent):
39 | t = DbLookupComboBox(parent)
40 | return t
41 |
42 | def domXml(self):
43 | return DOM_XML
44 |
45 | def group(self):
46 | return ''
47 |
48 | def icon(self):
49 | return QIcon()
50 |
51 | def includeFile(self):
52 | return 'jal/widgets/custom/db_lookup_combobox.h'
53 |
54 | def initialize(self, form_editor):
55 | if self._initialized:
56 | return
57 | self._initialized = True
58 |
59 | def isContainer(self):
60 | return False
61 |
62 | def isInitialized(self):
63 | return self._initialized
64 |
65 | def name(self):
66 | return 'DbLookupComboBox'
67 |
68 | def toolTip(self):
69 | return 'ComboBox to select available values from database lookup table'
70 |
71 | def whatsThis(self):
72 | return self.toolTip()
73 |
--------------------------------------------------------------------------------
/designer_plugins/plugin_log_viewer.py:
--------------------------------------------------------------------------------
1 | # Here reference goes from PYSIDE_DESIGNER_PLUGINS directory
2 | from jal.widgets.custom.log_viewer import LogViewer
3 |
4 | from PySide6.QtGui import QIcon
5 | from PySide6.QtDesigner import (QDesignerCustomWidgetInterface)
6 |
7 |
8 | DOM_XML = """
9 |
10 |
11 |
12 |
13 | 0
14 | 0
15 | 300
16 | 200
17 |
18 |
19 |
20 |
21 | """
22 |
23 |
24 | class LogViewerPlugin(QDesignerCustomWidgetInterface):
25 | def __init__(self):
26 | super().__init__()
27 | self._initialized = False
28 |
29 | def createWidget(self, parent):
30 | t = LogViewer(parent)
31 | return t
32 |
33 | def domXml(self):
34 | return DOM_XML
35 |
36 | def group(self):
37 | return ''
38 |
39 | def icon(self):
40 | return QIcon()
41 |
42 | def includeFile(self):
43 | return 'jal/widgets/custom/log_viewer.h'
44 |
45 | def initialize(self, form_editor):
46 | if self._initialized:
47 | return
48 | self._initialized = True
49 |
50 | def isContainer(self):
51 | return False
52 |
53 | def isInitialized(self):
54 | return self._initialized
55 |
56 | def name(self):
57 | return 'LogViewer'
58 |
59 | def toolTip(self):
60 | return 'Widget to display python logger messages in UI'
61 |
62 | def whatsThis(self):
63 | return self.toolTip()
64 |
--------------------------------------------------------------------------------
/designer_plugins/plugin_tableview_with_footer.py:
--------------------------------------------------------------------------------
1 | from jal.widgets.custom.tableview_with_footer import TableViewWithFooter
2 |
3 | from PySide6.QtGui import QIcon
4 | from PySide6.QtDesigner import (QDesignerCustomWidgetInterface)
5 |
6 |
7 | DOM_XML = """
8 |
9 |
19 |
20 | """
21 |
22 |
23 | class TableViewWithFooterPlugin(QDesignerCustomWidgetInterface):
24 | def __init__(self):
25 | super().__init__()
26 | self._initialized = False
27 |
28 | def createWidget(self, parent):
29 | t = TableViewWithFooter(parent)
30 | return t
31 |
32 | def domXml(self):
33 | return DOM_XML
34 |
35 | def group(self):
36 | return ''
37 |
38 | def icon(self):
39 | return QIcon()
40 |
41 | def includeFile(self):
42 | return 'jal/widgets/custom/tableview_with_footer.h'
43 |
44 | def initialize(self, form_editor):
45 | if self._initialized:
46 | return
47 | self._initialized = True
48 |
49 | def isContainer(self):
50 | return False
51 |
52 | def isInitialized(self):
53 | return self._initialized
54 |
55 | def name(self):
56 | return 'TableViewWithFooter'
57 |
58 | def toolTip(self):
59 | return 'QTableView that has a footer'
60 |
61 | def whatsThis(self):
62 | return self.toolTip()
63 |
--------------------------------------------------------------------------------
/designer_plugins/plugin_treeview_with_footer.py:
--------------------------------------------------------------------------------
1 | from jal.widgets.custom.treeview_with_footer import TreeViewWithFooter
2 |
3 | from PySide6.QtGui import QIcon
4 | from PySide6.QtDesigner import (QDesignerCustomWidgetInterface)
5 |
6 |
7 | DOM_XML = """
8 |
9 |
19 |
20 | """
21 |
22 |
23 | class TreeViewWithFooterPlugin(QDesignerCustomWidgetInterface):
24 | def __init__(self):
25 | super().__init__()
26 | self._initialized = False
27 |
28 | def createWidget(self, parent):
29 | t = TreeViewWithFooter(parent)
30 | return t
31 |
32 | def domXml(self):
33 | return DOM_XML
34 |
35 | def group(self):
36 | return ''
37 |
38 | def icon(self):
39 | return QIcon()
40 |
41 | def includeFile(self):
42 | return 'jal/widgets/custom/treeview_with_footer.h'
43 |
44 | def initialize(self, form_editor):
45 | if self._initialized:
46 | return
47 | self._initialized = True
48 |
49 | def isContainer(self):
50 | return False
51 |
52 | def isInitialized(self):
53 | return self._initialized
54 |
55 | def name(self):
56 | return 'TreeViewWithFooter'
57 |
58 | def toolTip(self):
59 | return 'QTreeView that has a footer'
60 |
61 | def whatsThis(self):
62 | return self.toolTip()
63 |
--------------------------------------------------------------------------------
/docs/FAQ.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | 1. *Which platforms/operation systems are supported by JAL*?
4 | JAL was created as a cross-platform appication based on *python* and GUI library *[Qt](https://www.qt.io/)*.
5 | Thus, theoretically, it should work on any platform that supports python and Qt installation.
6 | Installation and usage were tested on Linux, Windows and MacOS systems.
7 | But there are some problematic enviroments sometimes, like Windows with ARM (M1) CPU (*JAL* can't be installed due to *numpy* library compilation failure).
8 |
9 | 2. *Application does not start from python! We're write in it `>>>pip install jal`, and got SyntaxError*.
10 | JAL must run not from interactive python shell but, as any other python script, by passing a name of running program to python interpreter as an argument. It's shown in examples in Readme.
11 |
12 | 3. *How to start from begining, with clean database*?
13 | You may use menu _Data -> Clean All_ to start from scratch.
14 | Or you may delete JAL database file manually (don't forget to make backup if needed!). Database file is named `jal/jal.sqlite`. If you delete it (while programm is closed, sure), next run JAL creates new, clean database.
15 |
--------------------------------------------------------------------------------
/docs/FAQ.ru.md:
--------------------------------------------------------------------------------
1 | # Часто задаваемые вопросы
2 |
3 | 1. *На каких платформах/операционных системах работает JAL*?
4 | JAL создавался как кросс-платформенное приложение на базе *python* и графической библиотеки *[Qt](https://www.qt.io/)*,
5 | поэтому теоретически запуск возможен на любой платформе, которая поддерживает их установку.
6 | Установка и использование были успешно проверены на Linux, Windows и MacOS.
7 | Иногда бывают проблемы с отдельными конфигурациями устройствами. Например, не поддерживается установка *JAL* на Windows с процессором ARM (M1), т.к. в репозитории PyPi нет бинарной сборки библиотеки *numpy* и сборщик пакета пытается собрать ее из исходного кода, а для этого нужен компилятор и целый ворох дополнительных библиотек.
8 |
9 | 2. *Приложение не запускается из python! Ввели в строке `>>>pip install jal` , получили ошибку SyntaxError*.
10 | JAL должен запускаться не из интерактивного интерпретатора python, а, как и любая программа на python, путем передачи имени запускаемого файла интерпретатору в качестве аргумента. Об этом достаточно подробно написано в Readme.
11 |
12 | 3. *Как очистить базу данных?*
13 | Вы можете использовать пункт меню _Данные -> Очистить Всё_ для того, чтобы начать всё сначала.
14 | Либо можно просто удалить файл базы данных вручную (не забудьте сделать резервную копию из самой программы или просто скопировав файл, если это необходимо!). Файл базы находится зесь: `jal/jal.sqlite`. Убедитесь, что программа не запущена, удалите файл и при следующем запуске JAL будет создана новая чистая база.
15 |
--------------------------------------------------------------------------------
/docs/error_description.md:
--------------------------------------------------------------------------------
1 | # Error messages
2 |
3 | ### INFO
4 | Informational messages that may help to understand what JAL does but doesn't require any special attention.
5 |
6 | 1. *Tax adjustment for dividend: X.XX -> Y.YY (divided description)*
7 | Some brokers (e.g. Interactive Brokers) change dividend tax retrospectively sometimes. This message indicates when such an event happens during broker statement import.
8 |
9 | ### WARNING
10 | These are warnings that indicate something unexpected have happened. It doesn't necessarily a problem but good practice is to check and understand the reason.
11 |
12 | ### ERROR
13 | It indicates a problem that JAL was not able to cope with. You need to check and correct it.
14 |
15 | 1. *Results value of corporate action doesn't match 100% of initial asset value. Date: ...*
16 | If security undergo a corporate action then its cost basis should be fully distributed across results of corporate action according to Company's financial statement (e.g. Form 8937 for US securities).
17 | I.e. if stock *A* is converted into stocks *B* and *C* as result of corporate action then we need to declare X% of *A's* cost basis is allocated to *B* and Y% is allocated to *C*. While having X + Y equal to 100%.
18 | In order to make it you should locate corporate action in a list of operations and set shares of assets in results table.
19 |
--------------------------------------------------------------------------------
/docs/img/fns_auth_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/fns_auth_dialog.png
--------------------------------------------------------------------------------
/docs/img/investment_portfolio_holdings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/investment_portfolio_holdings.png
--------------------------------------------------------------------------------
/docs/img/main_linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/main_linux.png
--------------------------------------------------------------------------------
/docs/img/main_windows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/main_windows.png
--------------------------------------------------------------------------------
/docs/img/one_account_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/one_account_view.png
--------------------------------------------------------------------------------
/docs/img/report_deals.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/report_deals.png
--------------------------------------------------------------------------------
/docs/img/report_income_spending.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/report_income_spending.png
--------------------------------------------------------------------------------
/docs/img/report_profit_loss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/report_profit_loss.png
--------------------------------------------------------------------------------
/docs/img/slip_import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/slip_import.png
--------------------------------------------------------------------------------
/docs/img/stocks_and_investment_account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/img/stocks_and_investment_account.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/3ndfl_tax_report.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/3ndfl_tax_report.xlsx
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/corporate_actions.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/corporate_actions.odg
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/cost_basis/20190416_Form8937-NVS_ALC_SpinOff.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/cost_basis/20190416_Form8937-NVS_ALC_SpinOff.pdf
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/cost_basis/20201130_Form8937_PFE_VTRS_SpinOff.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/cost_basis/20201130_Form8937_PFE_VTRS_SpinOff.pdf
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/cost_basis/20220411_Form8937_T_WBD_SpinOff.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/cost_basis/20220411_Form8937_T_WBD_SpinOff.pdf
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/cost_basis/20230103_Form8937_GE_GEHC_SpinOff.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/cost_basis/20230103_Form8937_GE_GEHC_SpinOff.pdf
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/cost_basis/20240401_Form8937_GE_GEV_SpinOff.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/cost_basis/20240401_Form8937_GE_GEV_SpinOff.pdf
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/declaration.dc0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/declaration.dc0
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/declaration_empty.dc0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/declaration_empty.dc0
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/account_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/account_selection.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/bank_account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/bank_account.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/corporate_actions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/corporate_actions.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/declaration_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/declaration_1.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/declaration_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/declaration_2.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/declaration_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/declaration_3.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/ibkr_account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/ibkr_account.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/ibkr_selection_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/ibkr_selection_example.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/import_log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/import_log.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/main_window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/main_window.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/menu_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/menu_selection.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/report_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/report_1.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/report_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/report_2.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/report_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/report_3.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/report_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/report_4.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/report_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/report_5.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/report_params.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/report_params.png
--------------------------------------------------------------------------------
/docs/ru-tax-3ndfl/img/update_quotes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-3ndfl/img/update_quotes.png
--------------------------------------------------------------------------------
/docs/ru-tax-currency/CurrencyRates.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/docs/ru-tax-currency/CurrencyRates.xml
--------------------------------------------------------------------------------
/jal/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "2025.2.3"
--------------------------------------------------------------------------------
/jal/compile_ui:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | /usr/lib/qt6/uic -g python -o ./ui/ui_main_window.py ./ui/main_window.ui
3 | /usr/lib/qt6/uic -g python -o ./ui/ui_operations_widget.py ./ui/operations_widget.ui
4 | /usr/lib/qt6/uic -g python -o ./ui/ui_rebuild_window.py ./ui/rebuild_window.ui
5 | /usr/lib/qt6/uic -g python -o ./ui/ui_update_quotes_window.py ./ui/quotes_update.ui
6 | /usr/lib/qt6/uic -g python -o ./ui/ui_reference_data_dlg.py ./ui/reference_data_dlg.ui
7 | /usr/lib/qt6/uic -g python -o ./ui/ui_tax_export_widget.py ./ui/tax_export_widget.ui
8 | /usr/lib/qt6/uic -g python -o ./ui/ui_flow_export_widget.py ./ui/flow_export_widget.ui
9 | /usr/lib/qt6/uic -g python -o ./ui/ui_asset_dlg.py ./ui/asset_dlg.ui
10 | /usr/lib/qt6/uic -g python -o ./ui/ui_select_account_dlg.py ./ui/select_account_dlg.ui
11 | /usr/lib/qt6/uic -g python -o ./ui/ui_select_reference_dlg.py ./ui/select_reference_dlg.ui
12 | /usr/lib/qt6/uic -g python -o ./ui/ui_receipt_import_dlg.py ./ui/receipt_import_dlg.ui
13 | /usr/lib/qt6/uic -g python -o ./ui/ui_login_fns_dlg.py ./ui/login_fns_dlg.ui
14 | /usr/lib/qt6/uic -g python -o ./ui/ui_login_lidl_plus_dlg.py ./ui/login_lidl_plus_dlg.ui
15 | /usr/lib/qt6/uic -g python -o ./ui/ui_login_pingo_doce_dlg.py ./ui/login_pingo_doce_dlg.ui
16 | /usr/lib/qt6/uic -g python -o ./ui/widgets/ui_income_spending_operation.py ./ui/widgets/income_spending_operation.ui
17 | /usr/lib/qt6/uic -g python -o ./ui/widgets/ui_asset_payment_operation.py ./ui/widgets/asset_payment_operation.ui
18 | /usr/lib/qt6/uic -g python -o ./ui/widgets/ui_trade_operation.py ./ui/widgets/trade_operation.ui
19 | /usr/lib/qt6/uic -g python -o ./ui/widgets/ui_transfer_operation.py ./ui/widgets/transfer_operation.ui
20 | /usr/lib/qt6/uic -g python -o ./ui/widgets/ui_corporate_action_operation.py ./ui/widgets/corporate_action_operation.ui
21 | /usr/lib/qt6/uic -g python -o ./ui/widgets/ui_term_deposit_operation.py ./ui/widgets/term_deposit_operation.ui
22 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_portfolio_report.py ./ui/reports/portfolio_report.ui
23 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_tax_estimation.py ./ui/reports/tax_estimation.ui
24 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_income_spending_report.py ./ui/reports/income_spending_report.ui
25 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_assets_payments_report.py ./ui/reports/assets_payments_report.ui
26 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_term_deposits_report.py ./ui/reports/term_deposits_report.ui
27 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_account_balance_report.py ./ui/reports/account_balance_report.ui
28 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_category_report.py ./ui/reports/category_report.ui
29 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_tag_report.py ./ui/reports/tag_report.ui
30 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_peer_report.py ./ui/reports/peer_report.ui
31 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_deals_report.py ./ui/reports/deals_report.ui
32 | /usr/lib/qt6/uic -g python -o ./ui/reports/ui_profit_loss_report.py ./ui/reports/profit_loss_report.ui
33 |
34 | /usr/lib/qt6/bin/lupdate -no-obsolete jal.pro
35 |
--------------------------------------------------------------------------------
/jal/create_pro:
--------------------------------------------------------------------------------
1 | echo "SOURCES = "$(find . -type f -name "*.py" -not -path "./ui/*") > jal.pro
2 | echo "FORMS = "$(find ./ui -type f -name "*.ui") >> jal.pro
3 | echo "TRANSLATIONS = languages/en.ts languages/ru.ts" >> jal.pro
--------------------------------------------------------------------------------
/jal/data_export/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/data_export/tax_reports/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/data_export/tax_reports/portugal.json:
--------------------------------------------------------------------------------
1 | {
2 | "2022": {
3 | "tax_treaty": ["ad", "ae","ao","at","bb", "be", "bg","bh", "br", "ca", "ch", "ci", "cl", "co", "cn", "cv", "cu", "cy", "cz", "de", "dk", "dz", "ee", "es", "et", "fr", "gb", "ge", "gr", "gw", "hk", "hr", "hu", "id", "ie", "il", "in", "is", "it", "jp", "ke", "kr", "kw", "lv", "lt", "lu", "ma", "md", "me", "mo", "mt", "mx", "mz", "nl", "no", "om", "pa", "pe", "pk", "pl", "qa", "ro", "ru", "sa", "se", "sg", "si", "sk", "sm", "sn", "st", "tl", "tn", "tr", "ua", "us", "uy", "ve", "vn", "za"]
4 | },
5 | "2023": {
6 | "tax_treaty": ["ad", "ae","ao","at","bb", "be", "bg","bh", "br", "ca", "ch", "ci", "cl", "co", "cn", "cv", "cu", "cy", "cz", "de", "dk", "dz", "ee", "es", "et", "fr", "gb", "ge", "gr", "gw", "hk", "hr", "hu", "id", "ie", "il", "in", "is", "it", "jp", "ke", "kr", "kw", "lv", "lt", "lu", "ma", "md", "me", "mo", "mt", "mx", "mz", "nl", "no", "om", "pa", "pe", "pk", "pl", "qa", "ro", "ru", "sa", "se", "sg", "si", "sk", "sm", "sn", "st", "tl", "tn", "tr", "ua", "us", "uy", "ve", "vn", "za"]
7 | },
8 | "2024": {
9 | "tax_treaty": ["ad", "ae","ao","at","bb", "be", "bg","bh", "br", "ca", "ch", "ci", "cl", "co", "cn", "cv", "cu", "cy", "cz", "de", "dk", "dz", "ee", "es", "et", "fr", "gb", "ge", "gr", "gw", "hk", "hr", "hu", "id", "ie", "il", "in", "is", "it", "jp", "ke", "kr", "kw", "lv", "lt", "lu", "ma", "md", "me", "mo", "mt", "mx", "mz", "nl", "no", "om", "pa", "pe", "pk", "pl", "qa", "ro", "ru", "sa", "se", "sg", "si", "sk", "sm", "sn", "st", "tl", "tn", "tr", "ua", "us", "uy", "ve", "vn", "za"]
10 | }
11 | }
--------------------------------------------------------------------------------
/jal/data_export/tax_reports/russia.json:
--------------------------------------------------------------------------------
1 | {
2 | "2020": {
3 | "tax_treaty": ["al", "am", "ar", "at", "au", "az", "be", "bg", "bw", "by", "ca", "ch", "cl", "cn", "cu", "cy", "cz", "de", "dk", "dz", "eg", "es", "fi", "fr", "gb", "gr", "hr", "hu", "id", "ie", "il", "in", "ir", "is", "it", "jp", "kg", "kp", "kr", "kw", "kz", "lb", "lk", "lt", "lu", "lv", "ma", "md", "me", "mk", "ml", "mn", "mt", "mx", "my", "na", "nl", "no", "nz", "ph", "pl", "pt", "qa", "rs", "sa", "se", "sg", "si", "sk", "sy", "th", "tj", "tm", "tr", "ua", "us", "uz", "ve", "vn", "za"]
4 | },
5 | "2021": {
6 | "tax_treaty": ["al", "am", "ar", "at", "au", "az", "be", "bg", "bw", "by", "ca", "ch", "cl", "cn", "cu", "cy", "cz", "de", "dk", "dz", "eg", "es", "fi", "fr", "gb", "gr", "hr", "hu", "id", "ie", "il", "in", "ir", "is", "it", "jp", "kg", "kp", "kr", "kw", "kz", "lb", "lk", "lt", "lu", "lv", "ma", "md", "me", "mk", "ml", "mn", "mt", "mx", "my", "na", "nl", "no", "nz", "ph", "pl", "pt", "qa", "rs", "sa", "se", "sg", "si", "sk", "sy", "th", "tj", "tm", "tr", "ua", "us", "uz", "ve", "vn", "za"]
7 | },
8 | "2022": {
9 | "tax_treaty": ["al", "am", "ar", "at", "au", "az", "be", "bg", "bw", "by", "ca", "ch", "cl", "cn", "cu", "cy", "cz", "de", "dk", "dz", "eg", "es", "fi", "fr", "gb", "gr", "hr", "hu", "id", "ie", "il", "in", "ir", "is", "it", "jp", "kg", "kp", "kr", "kw", "kz", "lb", "lk", "lt", "lu", "ma", "md", "me", "mk", "ml", "mn", "mt", "mx", "my", "na", "no", "nz", "ph", "pl", "pt", "qa", "rs", "sa", "se", "sg", "si", "sk", "sy", "th", "tj", "tm", "tr", "us", "uz", "ve", "vn", "za"]
10 | },
11 | "2023": {
12 | "tax_treaty": ["am", "ar", "az", "bw", "by", "cl", "cn", "cu", "dz", "eg", "id", "il", "in", "ir", "kg", "kp", "kw", "kz", "lb", "lk", "ma", "md", "ml", "mn", "mx", "my", "na", "ph", "qa", "rs", "sa", "sy", "th", "tj", "tm", "tr", "uz", "ve", "vn", "za"]
13 | },
14 | "2024": {
15 | "tax_treaty": ["am", "ar", "az", "bw", "by", "cl", "cn", "cu", "dz", "eg", "id", "il", "in", "ir", "kg", "kp", "kw", "kz", "lb", "lk", "ma", "md", "ml", "mn", "mx", "my", "na", "ph", "qa", "rs", "sa", "sy", "th", "tj", "tm", "tr", "uz", "ve", "vn", "za"]
16 | }
17 | }
--------------------------------------------------------------------------------
/jal/data_export/templates/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/data_export/templates/__init__.py
--------------------------------------------------------------------------------
/jal/data_export/templates/tax_prt_dividends.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": "Dividends",
3 | "title": "Dividends report",
4 | "headers": [
5 | "Period: {parameters[period]}",
6 | "Account owner: ",
7 | "Account: {parameters[account]}"
8 | ],
9 | "columns": [
10 | {"name": "Payment date", "width": 10},
11 | {"name": "Stock symbol", "width": 8},
12 | {"name": "ISIN", "width": 11},
13 | {"name": "Payer name", "width": 40},
14 | {"name": "EUR/{parameters[currency]} rate for payment date", "width": 16},
15 | {"name": "Amount, {parameters[currency]}", "width": 12},
16 | {"name": "Amount, EUR", "width": 12},
17 | {"name": "Tax withheld, {parameters[currency]}", "width": 12},
18 | {"name": "Tax withheld, EUR", "width": 12},
19 | {"name": "Country", "width": 20},
20 | {"name": "Tax treaty", "width": 7}
21 | ],
22 | "columns_numbered": true,
23 | "dividend": {
24 | "rows": [
25 | ["payment_date", "symbol", "isin", "full_name", "rate", "amount", "amount_eur", "tax", "tax_eur", "country", "tax_treaty", "note"]
26 | ],
27 | "formats": [
28 | ["D", "T", "T", "T", "N:4", "N:2", "N:2", "N:2", "N:2", "T", "T", "-"]
29 | ]
30 | },
31 | "totals": {
32 | "rows": [
33 | [null, null, null, null, "TOTAL", "amount", "amount_eur", "tax", "tax_eur", null, null]
34 | ],
35 | "formats": [
36 | [null, null, null, null, "F", "F", "F", "F", "F", null, null]
37 | ]
38 | },
39 | "footers": []
40 | }
--------------------------------------------------------------------------------
/jal/data_export/templates/tax_prt_interests.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": "Interest",
3 | "title": "Interests report",
4 | "headers": [
5 | "Period: {parameters[period]}",
6 | "Account owner: ",
7 | "Account: {parameters[account]}"
8 | ],
9 | "columns": [
10 | {"name": "Description", "width": 50},
11 | {"name": "Payment date", "width": 10},
12 | {"name": "EUR/{parameters[currency]} rate for payment date", "width": 10},
13 | {"name": "Amount, {parameters[currency]}", "width": 10},
14 | {"name": "Amount, EUR", "width": 10}
15 | ],
16 | "columns_numbered": true,
17 | "interest": {
18 | "rows": [
19 | ["note", "payment_date", "rate", "amount", "amount_eur"]
20 | ],
21 | "formats": [
22 | ["T", "D", "N:4", "N:2", "N:2"]
23 | ]
24 | },
25 | "totals": {
26 | "rows": [
27 | [null, null, "ИТОГО", "amount", "amount_eur"]
28 | ],
29 | "formats": [
30 | [null, null, "F", "F", "F"]
31 | ]
32 | },
33 | "footers": []
34 | }
--------------------------------------------------------------------------------
/jal/data_export/templates/tax_prt_shares.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": "Shares",
3 | "title": "Shares sales report",
4 | "headers": [
5 | "Period: {parameters[period]}",
6 | "Account owner: ",
7 | "Account: {parameters[account]}"
8 | ],
9 | "columns": [
10 | {"name": "Symbol", "width": 8},
11 | {"name": "ISIN", "width": 11},
12 | {"name": "Operation", "width": 8},
13 | {"name": "Operation #", "width": 10},
14 | {"name": "Operation date", "width": 10},
15 | {"name": "Qty.", "width": 8},
16 | {"name": "Price, {parameters[currency]}", "width": 12},
17 | {"name": "Value, {parameters[currency]}", "width": 12},
18 | {"name": "Fee, {parameters[currency]}", "width": 12},
19 | {"name": "Profit/Loss, {parameters[currency]}", "width": 10},
20 | {"name": "EUR/{parameters[currency]} rate", "width": 9},
21 | {"name": "Value, EUR", "width": 12},
22 | {"name": "Fee, EUR", "width": 12},
23 | {"name": "Profit/Loss, EUR", "width": 10},
24 | {"name": "Note", "width": 25}
25 | ],
26 | "columns_numbered": true,
27 | "trade": {
28 | "rows": [
29 | ["symbol", "isin", "o_type", "o_number", "o_date", "qty", "o_price", "o_amount", "o_fee", "profit", "rate", "o_amount_eur", "o_fee_eur", "profit_eur", "note"],
30 | [null, null, "c_type", "c_number", "c_date", null, "c_price", "c_amount", "c_fee", null, null, "c_amount_eur", "c_fee_eur", null, null ]
31 | ],
32 | "formats": [
33 | ["T", "N:0", "T", "T", "D", "T", "N:6", "N:2", "N:6", "N:2", "N:4", "N:2", "N:2", "N:2", "T" ],
34 | [null, null, "T", "T", "D", null, "N:6", "N:2", "N:6", null, null, "N:2", "N:2", null, null]
35 | ],
36 | "span": [
37 | [{"h": 0, "v": 1}, {"h": 0, "v": 1}, null, null, null, {"h": 0, "v": 1}, null, null, null, {"h": 0, "v": 1}, {"h": 0, "v": 1}, null, null, {"h": 0, "v": 1}, {"h": 0, "v": 1}],
38 | [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null ]
39 | ]
40 | },
41 | "totals": {
42 | "rows": [
43 | [null, null, null, null, null, null, null, null, "TOTAL", "profit", " ", " ", "profit_eur", null, null]
44 | ],
45 | "formats": [
46 | [null, null, null, null, null, null, null, null, "F", "F", "F", "F", "F", null, null]
47 | ]
48 | },
49 | "footers": []
50 | }
--------------------------------------------------------------------------------
/jal/data_export/templates/tax_rus_dividends.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": "Дивиденды",
3 | "title": "Отчет по дивидендам, полученным в отчетном периоде",
4 | "headers": [
5 | "Документ-основание: ",
6 | "Период: {parameters[period]}",
7 | "ФИО: ",
8 | "Номер счета: {parameters[account]}"
9 | ],
10 | "columns": [
11 | {"name": "Дата выплаты", "width": 10},
12 | {"name": "Ценная бумага", "width": 8},
13 | {"name": "ISIN", "width": 11},
14 | {"name": "Полное наименование", "width": 40},
15 | {"name": "Курс {parameters[currency]}/RUB на дату выплаты", "width": 16},
16 | {"name": "Доход, {parameters[currency]}", "width": 12},
17 | {"name": "Доход, RUB (код 1010)", "width": 12},
18 | {"name": "Налог упл., {parameters[currency]}", "width": 12},
19 | {"name": "Налог упл., RUB", "width": 12},
20 | {"name": "Налог к уплате, RUB", "width": 12},
21 | {"name": "Страна", "width": 20},
22 | {"name": "СОИДН", "width": 7}
23 | ],
24 | "columns_numbered": true,
25 | "dividend": {
26 | "rows": [
27 | ["payment_date", "symbol", "isin", "full_name", "rate", "amount", "amount_rub", "tax", "tax_rub", "tax2pay", "country", "tax_treaty", "note"]
28 | ],
29 | "formats": [
30 | ["D", "T", "T", "T", "N:4", "N:2", "N:2", "N:2", "N:2", "N:2", "T", "T", "-"]
31 | ]
32 | },
33 | "totals": {
34 | "rows": [
35 | [null, null, null, null, "ИТОГО", "amount", "amount_rub", "tax", "tax_rub", "tax2pay", null, null]
36 | ],
37 | "formats": [
38 | [null, null, null, null, "F", "F", "F", "F", "F", "F", null, null]
39 | ]
40 | },
41 | "footers": [
42 | "Описание данных в столбцах таблицы",
43 | "1 - Дата, в которую дивиденд был зачислен на счет согласно отчету брокера",
44 | "2 - Краткое наименование ценной бумаги",
45 | "3 - Международный идентификационный код ценной бумаги",
46 | "4 - Полное наименование ценной бумаги",
47 | "5 - Официальный курс валюты выплаты, установленный ЦБ РФ на дату выплаты дивиденда",
48 | "6 - Сумма выплаченного дивиденда в валюте счета",
49 | "7 - Сумма выплаченного дивиденда в рублях по курсу ЦБ РФ на дату выплаты (= Столбец 5 x Столбец 6)",
50 | "8 - Сумма налога, удержанная эмитентом, в валюте счета",
51 | "9 - Сумма налога, удержанная эмитентом, в рублях по курсу ЦБ РФ на дату удержания (= Столбец 5 x Столбец 8)",
52 | "10 - Сумма налога, подлежащая уплате в РФ (= 13% от Столбца 7 - Столбец 9)",
53 | "11 - Страна регистрации эмитента ценной бумаги ",
54 | "12 - Наличие у Российской Федерации договора об избежании двойного налогообложения со страной эмитента"
55 | ]
56 | }
--------------------------------------------------------------------------------
/jal/data_export/templates/tax_rus_fees.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": "Комиссии",
3 | "title": "Отчет по комиссиям и прочим платежам в отчетном периоде",
4 | "headers": [
5 | "Документ-основание: ",
6 | "Период: {parameters[period]}",
7 | "ФИО: ",
8 | "Номер счета: {parameters[account]}"
9 | ],
10 | "columns": [
11 | {"name": "Описание", "width": 50},
12 | {"name": "Дата оплаты", "width": 10},
13 | {"name": "Курс {parameters[currency]}/RUB на дату оплаты", "width": 10},
14 | {"name": "Сумма, {parameters[currency]}", "width": 10},
15 | {"name": "Сумма, RUB", "width": 10}
16 | ],
17 | "columns_numbered": true,
18 | "fee": {
19 | "rows": [
20 | ["note", "payment_date", "rate", "amount", "amount_rub"]
21 | ],
22 | "formats": [
23 | ["T", "D", "N:4", "N:2", "N:2"]
24 | ]
25 | },
26 | "totals": {
27 | "rows": [
28 | [null, null, "ИТОГО", "amount", "amount_rub"]
29 | ],
30 | "formats": [
31 | [null, null, "F", "F", "F"]
32 | ]
33 | },
34 | "footers": [
35 | "Описание данных в столбцах таблицы",
36 | "1 - Описание платежа",
37 | "2 - Дата платежа",
38 | "3 - Официальный курс валюты, установленный ЦБ РФ на дату платежа",
39 | "4 - Сумма платежа в валюте счёта",
40 | "5 - Сумма платежа в рублях (= Столбец 2 * Столбец 4)"
41 | ]
42 | }
--------------------------------------------------------------------------------
/jal/data_export/templates/tax_rus_flow.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": "ОоДДС",
3 | "title": "Отчет о движении средств и стоимости иных финансовых активов",
4 | "headers": [
5 | "Период: {parameters[period]}"
6 | ],
7 | "columns": [
8 | {"name": "Номер счёта", "width": 20},
9 | {"name": "Название счёта", "width": 20},
10 | {"name": "Валюта", "width": 20},
11 | {"name": "Описание", "width": 20},
12 | {"name": "Остаток начало", "width": 10},
13 | {"name": "Зачислено", "width": 10},
14 | {"name": "Списано", "width": 10},
15 | {"name": "Остаток конец", "width": 10}
16 | ],
17 | "columns_numbered": true,
18 | "account_lines": {
19 | "rows": [
20 | ["account", "account_name","currency", "money", "money_begin", "money_in", "money_out", "money_end"],
21 | [null, null, null, "assets", "assets_begin", "assets_in", "assets_out", "assets_end"]
22 | ],
23 | "formats": [
24 | ["T", "T", "T", "T", "N:3", "N:3", "N:3", "N:3"],
25 | [null, null, null, "T", "N:3", "N:3", "N:3", "N:3"]
26 | ],
27 | "span": [
28 | [{"h": 0, "v": 1}, {"h": 0, "v": 1}, {"h": 0, "v": 1}, null, null, null, null, null],
29 | [null, null, null, null, null, null, null, null]
30 | ]
31 | },
32 | "footers": [
33 | ]
34 | }
--------------------------------------------------------------------------------
/jal/data_export/templates/tax_rus_interests.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": "Проценты",
3 | "title": "Отчет по процентам и прочим выплатам, выплаченным в отчетном периоде",
4 | "headers": [
5 | "Документ-основание: ",
6 | "Период: {parameters[period]}",
7 | "ФИО: ",
8 | "Номер счета: {parameters[account]}"
9 | ],
10 | "columns": [
11 | {"name": "Описание", "width": 50},
12 | {"name": "Дата выплаты", "width": 10},
13 | {"name": "Курс {parameters[currency]}/RUB на дату выплаты", "width": 10},
14 | {"name": "Сумма, {parameters[currency]}", "width": 10},
15 | {"name": "Доход, RUB (код 1011)", "width": 10},
16 | {"name": "Налог, RUB", "width": 10}
17 | ],
18 | "columns_numbered": true,
19 | "interest": {
20 | "rows": [
21 | ["note", "payment_date", "rate", "amount", "amount_rub", "tax_rub"]
22 | ],
23 | "formats": [
24 | ["T", "D", "N:4", "N:2", "N:2", "N:2"]
25 | ]
26 | },
27 | "totals": {
28 | "rows": [
29 | [null, null, "ИТОГО", "amount", "amount_rub", "tax_rub"]
30 | ],
31 | "formats": [
32 | [null, null, "F", "F", "F", "F"]
33 | ]
34 | },
35 | "footers": [
36 | "Описание данных в столбцах таблицы",
37 | "1 - Описание платежа",
38 | "2 - Дата платежа",
39 | "3 - Официальный курс валюты, установленный ЦБ РФ на дату платежа",
40 | "4 - Сумма платежа в валюте счёта",
41 | "5 - Сумма дохода в рублях (= Столбец 3 * Столбец 4)",
42 | "6 - Сумма налога к уплате (13% от столбца 5)"
43 | ]
44 | }
--------------------------------------------------------------------------------
/jal/data_import/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/data_import/broker_statements/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/data_import/broker_statements/__init__.py
--------------------------------------------------------------------------------
/jal/data_import/import_schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "object",
3 | "properties": {
4 | "period": {
5 | "type": "array",
6 | "minItems": 2,
7 | "maxItems": 2
8 | },
9 | "accounts": {
10 | "type": "array"
11 | },
12 | "assets": {
13 | "type": "array"
14 | },
15 | "trades": {
16 | "type": "array"
17 | },
18 | "income_spending": {
19 | "type": "array"
20 | },
21 | "transfers": {
22 | "type": "array"
23 | },
24 | "corporate_actions": {
25 | "type": "array"
26 | },
27 | "asset_payments": {
28 | "type": "array"
29 | }
30 | },
31 | "required": [
32 | "period",
33 | "accounts",
34 | "assets"
35 | ]
36 | }
--------------------------------------------------------------------------------
/jal/data_import/receipt_api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/data_import/receipt_api/__init__.py
--------------------------------------------------------------------------------
/jal/data_import/receipt_api/receipt_api.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import QObject, Signal, QDateTime
2 | from PySide6.QtWidgets import QApplication
3 |
4 |
5 | #-----------------------------------------------------------------------------------------------------------------------
6 | # Base parent class for various API used by JAL for downloading information about purchases (slips)
7 | class ReceiptAPI(QObject):
8 | slip_load_failed = Signal()
9 | slip_load_ok = Signal()
10 |
11 | def __init__(self):
12 | super().__init__()
13 |
14 | def tr(self, text):
15 | return QApplication.translate("ReceiptAPI", text)
16 |
17 | # Provides a list of parameters required for slip query if manual input is in use in form of dictionary
18 | # { "parameter_name" : "parameter_type" }
19 | @staticmethod
20 | def parameters_list() -> dict:
21 | raise NotImplementedError(f"parameters_list() shouldn't be called for ReceiptAPI class")
22 |
23 | # Method performs required actions to have active API session that may be used for queries
24 | # Returns True after successful activation and False otherwise
25 | def activate_session(self) -> bool:
26 | raise NotImplementedError(f"activate_session() method is not implemented in {type(self).__name__}")
27 |
28 | # Request slip data via API
29 | def query_slip(self):
30 | raise NotImplementedError(f"query_slip() method is not implemented in {type(self).__name__}")
31 |
32 | # Returns a list of purchased items (as dictionaries with name, price, amount, etc)
33 | def slip_lines(self) -> list:
34 | raise NotImplementedError(f"slip_lines() method is not implemented in {type(self).__name__}")
35 |
36 | # Returns a shop name where purchase was done (if it is possible to get)
37 | def shop_name(self) -> str:
38 | raise NotImplementedError(f"shop_name() method is not implemented in {type(self).__name__}")
39 |
40 | # Returns data/time of the operation from the receipt
41 | def datetime(self) -> QDateTime:
42 | raise NotImplementedError(f"datetime() method is not implemented in {type(self).__name__}")
43 |
--------------------------------------------------------------------------------
/jal/data_import/receipt_api/receipts.py:
--------------------------------------------------------------------------------
1 | import re
2 | from PySide6.QtCore import QObject
3 | from jal.data_import.receipt_api.ru_fns import ReceiptRuFNS
4 | from jal.data_import.receipt_api.eu_lidl_plus import ReceiptEuLidlPlus
5 | from jal.data_import.receipt_api.pt_pingo_doce import ReceiptPtPingoDoce
6 | from jal.widgets.qr_scanner import ScanDialog, QRScanner
7 |
8 |
9 | # ----------------------------------------------------------------------------------------------------------------------
10 | # Possible values that may be used by factory
11 | RU_FNS_API = 'RU_FNS'
12 | EU_LIDL_PLUS_API = 'EU_LIDL_PLUS'
13 | PT_PINGO_DOCE_API = 'PT_PINGO_DOCE'
14 |
15 |
16 | # ----------------------------------------------------------------------------------------------------------------------
17 | class ReceiptAPIFactory(QObject):
18 | def __init__(self):
19 | super().__init__()
20 | self._apis = {
21 | RU_FNS_API: ReceiptRuFNS,
22 | EU_LIDL_PLUS_API: ReceiptEuLidlPlus,
23 | PT_PINGO_DOCE_API: ReceiptPtPingoDoce
24 | }
25 | self.supported_names = {
26 | RU_FNS_API: self.tr("Russian receipt"),
27 | EU_LIDL_PLUS_API: self.tr("European Lidl receipt"),
28 | PT_PINGO_DOCE_API: self.tr("Portuguese Pingo Doce receipt")
29 | }
30 | self._pt_nifs = {
31 | '503340855': EU_LIDL_PLUS_API,
32 | '500829993': PT_PINGO_DOCE_API
33 | }
34 |
35 | def get_api_parameters(self, api_type):
36 | api = self._apis.get(api_type)
37 | return api.parameters_list()
38 |
39 | # Selects required API class based on QR data pattern
40 | def get_api_for_qr(self, qr_text):
41 | extra_data=''
42 | api_type = self._detect_api_id_by_qr(qr_text)
43 | if api_type == EU_LIDL_PLUS_API:
44 | extra_data = ScanDialog.execute_scan(code_type=QRScanner.TYPE_ITF,
45 | message=self.tr("Please scan flat barcode from the receipt"))
46 | if extra_data is None:
47 | extra_data = '' # keep empty value to allow manual input
48 | api = self._apis.get(api_type)
49 | return api(qr_text=qr_text, aux_data=extra_data)
50 |
51 | def _detect_api_id_by_qr(self, qr_text):
52 | ru_fns_keys = ["i=", "n=", "s=", "t=", "fn=", "fp="]
53 | pt_at_pattern = r"A:(?P.{1,9})\*B:.{1,30}\*C:PT\*D:FS\*E:N\*F:\d{8}\*G:.{1,30}\*H:.{1,70}\*I1:PT\*.*"
54 | if all([x in qr_text for x in ru_fns_keys]):
55 | return RU_FNS_API
56 | parts = re.match(pt_at_pattern, qr_text)
57 | if parts is not None:
58 | NIF = parts.groupdict()['NIF']
59 | try:
60 | return self._pt_nifs[NIF]
61 | except KeyError:
62 | raise ValueError(self.tr("Portuguese QR recognized but shop isn't supported, NIF: ") + f"{NIF}")
63 | raise ValueError(self.tr("No API found for QR data: ") + f"'{qr_text}'")
64 |
65 | def get_api_with_params(self, api_type, params):
66 | api = self._apis.get(api_type)
67 | return api(params=params)
68 |
--------------------------------------------------------------------------------
/jal/db/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/db/country.py:
--------------------------------------------------------------------------------
1 | from jal.db.db import JalDB
2 | from jal.db.settings import JalSettings
3 |
4 |
5 | class JalCountry(JalDB):
6 | db_cache = []
7 |
8 | def __init__(self, country_id: int = 0, data: dict = None, search=False) -> None:
9 | super().__init__(cached=True)
10 | if not JalCountry.db_cache:
11 | self._fetch_data()
12 | self._id = country_id
13 | if self._valid_data(data):
14 | if search:
15 | self._id = self._find_country(data)
16 | try:
17 | self._data = [x for x in self.db_cache if x['id'] == self._id][0]
18 | except IndexError:
19 | self._data = None
20 | self._name = self._data['name'] if self._data is not None else None
21 | self._code = self._data['code'] if self._data is not None else None
22 | self._iso_code = self._data['iso_code'] if self._data is not None else None
23 |
24 | def invalidate_cache(self):
25 | self._fetch_data()
26 |
27 | # JalCountry maintains single cache available for all instances
28 | @classmethod
29 | def class_cache(cls) -> True:
30 | return True
31 |
32 | def _fetch_data(self):
33 | JalCountry.db_cache = []
34 | query = self._exec("SELECT * FROM countries_ext ORDER BY id")
35 | while query.next():
36 | JalCountry.db_cache.append(self._read_record(query, named=True))
37 |
38 | def id(self) -> int:
39 | return self._id
40 |
41 | # Returns country name in given language or in current interface language if no argument is given
42 | def name(self, language: str='') -> str:
43 | if not language:
44 | language = JalSettings().getLanguage()
45 | return self._read("SELECT c.name FROM country_names c LEFT JOIN languages l ON c.language_id=l.id "
46 | "WHERE country_id=:country_id AND l.language=:language",
47 | [(":country_id", self._id), (":language", language)])
48 |
49 | def code(self) -> str:
50 | return self._code
51 |
52 | def iso_code(self) -> str:
53 | return self._iso_code
54 |
55 | def _valid_data(self, data: dict) -> bool:
56 | if data is None:
57 | return False
58 | if 'code' not in data:
59 | return False
60 | return True
61 |
62 | def _find_country(self, data: dict) -> int:
63 | country_id = self._read("SELECT id FROM countries WHERE code=:code",
64 | [(":code", data['code'])], check_unique=True)
65 | if country_id is None:
66 | return 0
67 | else:
68 | return country_id
--------------------------------------------------------------------------------
/jal/db/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | from enum import auto
3 | from jal.db.db import JalDB
4 | from PySide6.QtCore import QStandardPaths, QFileInfo
5 | from jal.constants import Setup
6 |
7 | class FolderFor:
8 | Statement = auto()
9 | Report = auto()
10 |
11 | class JalSettings(JalDB):
12 | __RECENT_PREFIX = "RecentFolder_"
13 | __folders = {
14 | FolderFor.Statement: "Statement",
15 | FolderFor.Report: "Report"
16 | }
17 |
18 | def __init__(self):
19 | super().__init__()
20 |
21 | @staticmethod
22 | def path(path_type) -> str:
23 | app_path = JalDB.get_app_path()
24 | if path_type == JalDB.PATH_APP:
25 | return app_path
26 | if path_type == JalDB.PATH_DB_FILE:
27 | return JalDB.get_db_path()
28 | if path_type == JalDB.PATH_LANG:
29 | return app_path + Setup.LANG_PATH + os.sep
30 | if path_type == JalDB.PATH_ICONS:
31 | return app_path + Setup.ICONS_PATH + os.sep
32 | if path_type == JalDB.PATH_LANG_FILE:
33 | return app_path + Setup.LANG_PATH + os.sep + JalSettings().getLanguage() + '.qm'
34 | if path_type == JalDB.PATH_TAX_REPORT_TEMPLATE:
35 | return app_path + Setup.EXPORT_PATH + os.sep + Setup.TAX_REPORT_PATH + os.sep
36 | if path_type == JalDB.PATH_TEMPLATES:
37 | return app_path + Setup.EXPORT_PATH + os.sep + Setup.TEMPLATE_PATH + os.sep
38 |
39 | def getValue(self, key, default=None):
40 | value = self._read("SELECT value FROM settings WHERE name=:key", [(":key", key)])
41 | if value is None:
42 | value = default
43 | try:
44 | return int(value) # Try to provide integer if conversion is possible
45 | except ValueError:
46 | return value
47 |
48 | def setValue(self, key, value):
49 | self._exec("INSERT OR REPLACE INTO settings(name, value) VALUES(:key, :value)",
50 | [(":key", key), (":value", value)], commit=True)
51 |
52 | # Returns 2-letter language code that corresponds to current 'Language' settings in DB
53 | def getLanguage(self):
54 | lang_id = self.getValue('Language', default=1)
55 | return self._read("SELECT language FROM languages WHERE id = :language_id", [(':language_id', lang_id)])
56 |
57 | # Set 'Language' setting in DB that corresponds to given 2-letter language code
58 | def setLanguage(self, language_code):
59 | lang_id = self._read("SELECT id FROM languages WHERE language = :language_code",
60 | [(':language_code', language_code)])
61 | self.setValue('Language', lang_id)
62 |
63 | def getRecentFolder(self, folder_type: int, default: str=''):
64 | folder = self.getValue(self.__RECENT_PREFIX + self.__folders[folder_type])
65 | if not folder:
66 | folder = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
67 | if not folder:
68 | folder = default
69 | return folder
70 |
71 | def setRecentFolder(self, folder_type: int, folder: str):
72 | file_info = QFileInfo(folder)
73 | if file_info.isDir():
74 | path = file_info.absoluteFilePath()
75 | else:
76 | path = file_info.absolutePath()
77 | self.setValue(self.__RECENT_PREFIX + self.__folders[folder_type], path)
78 |
--------------------------------------------------------------------------------
/jal/db/tag.py:
--------------------------------------------------------------------------------
1 | from jal.db.db import JalDB
2 | from jal.constants import AssetData
3 |
4 | class JalTag(JalDB):
5 | db_cache = []
6 |
7 | def __init__(self, tag_id: int = 0) -> None:
8 | super().__init__(cached=True)
9 | if not JalTag.db_cache:
10 | self._fetch_data()
11 | self._id = tag_id
12 | try:
13 | self._data = [x for x in self.db_cache if x['id'] == self._id][0]
14 | except IndexError:
15 | self._data = None
16 | self._name = self._data['tag'] if self._data is not None else ''
17 | self._iconfile = self._data['icon_file'] if self._data is not None else ''
18 |
19 | def invalidate_cache(self):
20 | self._fetch_data()
21 |
22 | # JalCountry maintains single cache available for all instances
23 | @classmethod
24 | def class_cache(cls) -> True:
25 | return True
26 |
27 | # Returns a dict {tag_id: 'icon_filename'} of all tags that have icons assigned
28 | @classmethod
29 | def icon_files(cls) -> dict:
30 | icons = {}
31 | query = cls._exec("SELECT id, icon_file FROM tags WHERE icon_file!=''")
32 | while query.next():
33 | tag_id, filename = cls._read_record(query)
34 | icons[tag_id] = filename
35 | return icons
36 |
37 | def _fetch_data(self):
38 | JalTag.db_cache = []
39 | query = self._exec("SELECT * FROM tags ORDER BY id")
40 | while query.next():
41 | JalTag.db_cache.append(self._read_record(query, named=True))
42 |
43 | def id(self) -> int:
44 | return self._id
45 |
46 | # Returns country name in given language or in current interface language if no argument is given
47 | def name(self) -> str:
48 | return self._name
49 |
50 | # Returns the name of icon file that is assigned to the tag
51 | def icon(self) -> str:
52 | return self._iconfile
53 |
54 | def replace_with(self, new_id):
55 | self._exec("UPDATE action_details SET tag_id=:new_id WHERE tag_id=:old_id",
56 | [(":new_id", new_id), (":old_id", self._id)])
57 | self._exec("UPDATE asset_data SET value=:new_id WHERE datatype=:tag AND value=:old_id",
58 | [(":tag", AssetData.Tag), (":new_id", str(new_id)), (":old_id", self._id)])
59 | self._exec("DELETE FROM tags WHERE id=:old_id", [(":old_id", self._id)], commit=True)
60 | JalDB().invalidate_cache() # Full DB as it impacts JalAsset cache also
61 | self._id = 0
62 |
--------------------------------------------------------------------------------
/jal/db/view_model.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import Qt
2 | from PySide6.QtGui import QFont
3 | from jal.db.db import JalModel
4 |
5 |
6 | class JalViewModel(JalModel):
7 | def __init__(self, parent_view, table_name):
8 | super().__init__(parent_view, table_name)
9 | self._key_field = 'id' # this is assumed to be an auto-increment field that us used to track rows existence
10 | self._columns = []
11 | self.deleted = []
12 | self._view = parent_view
13 |
14 | def headerData(self, section, orientation, role=Qt.DisplayRole):
15 | if orientation == Qt.Horizontal and role == Qt.DisplayRole:
16 | return self._columns[section]
17 | return None
18 |
19 | def footerData(self, section, role=Qt.DisplayRole):
20 | return None
21 |
22 | def removeRow(self, row, parent=None):
23 | if self.record(row).value(self._key_field): # New rows have 0 in index key field - we may not track this rows
24 | self.deleted.append(row) # as Qt will remove them completely on its own
25 | super().removeRow(row)
26 |
27 | def submitAll(self):
28 | result = super().submitAll()
29 | if result:
30 | self.deleted = []
31 | return result
32 |
33 | def revertAll(self):
34 | self.deleted = []
35 | super().revertAll()
36 |
37 | def data(self, index, role=Qt.DisplayRole):
38 | if not index.isValid():
39 | return None
40 | if role == Qt.FontRole and (index.row() in self.deleted):
41 | font = QFont()
42 | font.setStrikeOut(True)
43 | return font
44 | return super().data(index, role)
45 |
46 | def row_is_deleted(self, row):
47 | if row in self.deleted:
48 | return True
49 | else:
50 | return False
51 |
--------------------------------------------------------------------------------
/jal/img/aux_ibkr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/aux_ibkr.png
--------------------------------------------------------------------------------
/jal/img/aux_j2t.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/aux_j2t.png
--------------------------------------------------------------------------------
/jal/img/aux_kit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/aux_kit.png
--------------------------------------------------------------------------------
/jal/img/aux_pof.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/aux_pof.png
--------------------------------------------------------------------------------
/jal/img/aux_psb.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/aux_psb.ico
--------------------------------------------------------------------------------
/jal/img/aux_tvoy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/aux_tvoy.png
--------------------------------------------------------------------------------
/jal/img/aux_vtb.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/aux_vtb.ico
--------------------------------------------------------------------------------
/jal/img/flag_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/flag_en.png
--------------------------------------------------------------------------------
/jal/img/flag_pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/flag_pt.png
--------------------------------------------------------------------------------
/jal/img/flag_ru.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/flag_ru.png
--------------------------------------------------------------------------------
/jal/img/tag_bank.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/tag_bank.ico
--------------------------------------------------------------------------------
/jal/img/tag_card.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/tag_card.ico
--------------------------------------------------------------------------------
/jal/img/tag_cash.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/tag_cash.ico
--------------------------------------------------------------------------------
/jal/img/tag_investing.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/tag_investing.ico
--------------------------------------------------------------------------------
/jal/img/ui_add.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_add.ico
--------------------------------------------------------------------------------
/jal/img/ui_add_child.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_add_child.ico
--------------------------------------------------------------------------------
/jal/img/ui_amortization.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_amortization.ico
--------------------------------------------------------------------------------
/jal/img/ui_buy.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_buy.ico
--------------------------------------------------------------------------------
/jal/img/ui_cancel.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_cancel.ico
--------------------------------------------------------------------------------
/jal/img/ui_chart.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_chart.ico
--------------------------------------------------------------------------------
/jal/img/ui_clean.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_clean.ico
--------------------------------------------------------------------------------
/jal/img/ui_copy.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_copy.ico
--------------------------------------------------------------------------------
/jal/img/ui_coupon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_coupon.ico
--------------------------------------------------------------------------------
/jal/img/ui_delisting.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_delisting.ico
--------------------------------------------------------------------------------
/jal/img/ui_deposit_account.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_deposit_account.ico
--------------------------------------------------------------------------------
/jal/img/ui_deposit_close.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_deposit_close.ico
--------------------------------------------------------------------------------
/jal/img/ui_deposit_open.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_deposit_open.ico
--------------------------------------------------------------------------------
/jal/img/ui_details.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_details.ico
--------------------------------------------------------------------------------
/jal/img/ui_dividend.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_dividend.ico
--------------------------------------------------------------------------------
/jal/img/ui_dividend_stock.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_dividend_stock.ico
--------------------------------------------------------------------------------
/jal/img/ui_fee.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_fee.ico
--------------------------------------------------------------------------------
/jal/img/ui_interest.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_interest.ico
--------------------------------------------------------------------------------
/jal/img/ui_jal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_jal.png
--------------------------------------------------------------------------------
/jal/img/ui_list.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_list.ico
--------------------------------------------------------------------------------
/jal/img/ui_merger.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_merger.ico
--------------------------------------------------------------------------------
/jal/img/ui_minus.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_minus.ico
--------------------------------------------------------------------------------
/jal/img/ui_ok.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_ok.ico
--------------------------------------------------------------------------------
/jal/img/ui_plus.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_plus.ico
--------------------------------------------------------------------------------
/jal/img/ui_remove.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_remove.ico
--------------------------------------------------------------------------------
/jal/img/ui_renaming.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_renaming.ico
--------------------------------------------------------------------------------
/jal/img/ui_sell.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_sell.ico
--------------------------------------------------------------------------------
/jal/img/ui_spinoff.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_spinoff.ico
--------------------------------------------------------------------------------
/jal/img/ui_split.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_split.ico
--------------------------------------------------------------------------------
/jal/img/ui_tag.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_tag.ico
--------------------------------------------------------------------------------
/jal/img/ui_tax.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_tax.ico
--------------------------------------------------------------------------------
/jal/img/ui_total.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_total.ico
--------------------------------------------------------------------------------
/jal/img/ui_transfer_asset_in.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_transfer_asset_in.ico
--------------------------------------------------------------------------------
/jal/img/ui_transfer_asset_out.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_transfer_asset_out.ico
--------------------------------------------------------------------------------
/jal/img/ui_transfer_in.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_transfer_in.ico
--------------------------------------------------------------------------------
/jal/img/ui_transfer_out.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_transfer_out.ico
--------------------------------------------------------------------------------
/jal/img/ui_vesting.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_vesting.ico
--------------------------------------------------------------------------------
/jal/img/ui_with_credit.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/img/ui_with_credit.ico
--------------------------------------------------------------------------------
/jal/jal.pro:
--------------------------------------------------------------------------------
1 | SOURCES = ./jal.py ./widgets/delegates.py ./widgets/qr_scanner.py ./widgets/mdi.py ./widgets/income_spending_widget.py ./widgets/reference_data.py ./widgets/term_deposit_widget.py ./widgets/tax_widget.py ./widgets/asset_dialog.py ./widgets/helpers.py ./widgets/asset_payment_widget.py ./widgets/__init__.py ./widgets/abstract_operation_details.py ./widgets/custom/log_viewer.py ./widgets/custom/treeview_with_footer.py ./widgets/custom/date_range_selector.py ./widgets/custom/db_lookup_combobox.py ./widgets/custom/__init__.py ./widgets/custom/table_footer.py ./widgets/custom/tableview_with_footer.py ./widgets/selection_dialog.py ./widgets/operations_widget.py ./widgets/operations_tabs.py ./widgets/corporate_action_widget.py ./widgets/reference_dialogs.py ./widgets/price_chart.py ./widgets/account_select.py ./widgets/trade_widget.py ./widgets/transfer_widget.py ./widgets/icons.py ./widgets/main_window.py ./widgets/reference_selector.py ./db/closed_trade.py ./db/deposit.py ./db/asset.py ./db/ledger.py ./db/settings.py ./db/account.py ./db/tax_estimator.py ./db/category.py ./db/backup_restore.py ./db/tag.py ./db/trades_model.py ./db/helpers.py ./db/__init__.py ./db/db.py ./db/country.py ./db/balances_model.py ./db/view_model.py ./db/tree_model.py ./db/operations.py ./db/reference_models.py ./db/operations_model.py ./db/holdings_model.py ./db/peer.py ./constants.py ./__init__.py ./updates/__init__.py ./data_export/templates/__init__.py ./data_export/taxes.py ./data_export/__init__.py ./data_export/tax_reports/russia.py ./data_export/tax_reports/portugal.py ./data_export/tax_reports/__init__.py ./data_export/dlsg.py ./data_export/taxes_flow.py ./data_export/xlsx.py ./net/web_request.py ./net/__init__.py ./net/downloader.py ./net/moex.py ./reports/deals.py ./reports/profit_loss.py ./reports/account_balance.py ./reports/category.py ./reports/tag.py ./reports/operations_base.py ./reports/__init__.py ./reports/portfolio.py ./reports/income_spending.py ./reports/assets_payments.py ./reports/term_deposits.py ./reports/reports.py ./reports/peer.py ./universal_cache.py ./data_import/broker_statements/ibkr.py ./data_import/broker_statements/tvoy.py ./data_import/broker_statements/__init__.py ./data_import/broker_statements/just2trade.py ./data_import/broker_statements/psb.py ./data_import/broker_statements/vtb.py ./data_import/broker_statements/open_portfolio.py ./data_import/broker_statements/kit.py ./data_import/receipt_api/pt_pingo_doce.py ./data_import/receipt_api/receipt_api.py ./data_import/receipt_api/eu_lidl_plus.py ./data_import/receipt_api/__init__.py ./data_import/receipt_api/receipts.py ./data_import/receipt_api/ru_fns.py ./data_import/statements.py ./data_import/statement_xml.py ./data_import/statement.py ./data_import/__init__.py ./data_import/shop_receipt.py ./data_import/category_recognizer.py ./data_import/statement_xls.py
2 | FORMS = ./ui/rebuild_window.ui ./ui/quotes_update.ui ./ui/widgets/trade_operation.ui ./ui/widgets/asset_payment_operation.ui ./ui/widgets/term_deposit_operation.ui ./ui/widgets/corporate_action_operation.ui ./ui/widgets/transfer_operation.ui ./ui/widgets/income_spending_operation.ui ./ui/reference_data_dlg.ui ./ui/login_pingo_doce_dlg.ui ./ui/asset_dlg.ui ./ui/login_lidl_plus_dlg.ui ./ui/flow_export_widget.ui ./ui/reports/tag_report.ui ./ui/reports/income_spending_report.ui ./ui/reports/term_deposits_report.ui ./ui/reports/account_balance_report.ui ./ui/reports/peer_report.ui ./ui/reports/assets_payments_report.ui ./ui/reports/category_report.ui ./ui/reports/tax_estimation.ui ./ui/reports/portfolio_report.ui ./ui/reports/deals_report.ui ./ui/reports/profit_loss_report.ui ./ui/select_account_dlg.ui ./ui/main_window.ui ./ui/operations_widget.ui ./ui/tax_export_widget.ui ./ui/select_reference_dlg.ui ./ui/login_fns_dlg.ui ./ui/receipt_import_dlg.ui
3 | TRANSLATIONS = languages/en.ts languages/ru.ts
4 |
--------------------------------------------------------------------------------
/jal/jal.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import logging
4 | import traceback
5 | from PySide6.QtCore import Qt, QTranslator, qInstallMessageHandler, QtMsgType, qDebug
6 | from PySide6.QtWidgets import QApplication, QMessageBox
7 | from jal.widgets.main_window import MainWindow
8 | from jal.db.db import JalDB, JalDBError
9 | from jal.db.settings import JalSettings
10 |
11 | #-----------------------------------------------------------------------------------------------------------------------
12 | def exception_logger(exctype, value, tb):
13 | info = traceback.format_exception(exctype, value, tb)
14 | logging.fatal(f"EXCEPTION: {info}")
15 | sys.__excepthook__(exctype, value, tb)
16 |
17 |
18 | def make_error_window(error: JalDBError):
19 | window = QMessageBox()
20 | window.setAttribute(Qt.WA_DeleteOnClose)
21 | window.setWindowTitle("JAL: Start-up aborted")
22 | window.setIcon(QMessageBox.Critical)
23 | window.setText(error.message)
24 | window.setInformativeText(error.details)
25 | return window
26 |
27 | def setup_root_logging():
28 | root_logger = logging.getLogger()
29 | log_level = os.environ.get('LOGLEVEL', 'INFO').upper()
30 | root_logger.setLevel(log_level)
31 | qInstallMessageHandler(systemWideQtLogHandler)
32 |
33 | def systemWideQtLogHandler(level, context, message):
34 | # Mapping Qt message levels to Python logging levels
35 | level_map = {
36 | QtMsgType.QtDebugMsg: logging.DEBUG,
37 | QtMsgType.QtInfoMsg: logging.INFO,
38 | QtMsgType.QtWarningMsg: logging.WARNING,
39 | QtMsgType.QtCriticalMsg: logging.ERROR,
40 | QtMsgType.QtFatalMsg: logging.CRITICAL,
41 | }
42 |
43 | category = context.category if hasattr(context, 'file') else ""
44 | ctx_str = f"{str(context)}:{category}"
45 | logging.log(
46 | level_map.get(level, logging.ERROR),
47 | f"[QT] {message} | {ctx_str}"[:250]
48 | )
49 |
50 | #-----------------------------------------------------------------------------------------------------------------------
51 | def main():
52 | setup_root_logging()
53 | translator_installed = False
54 | sys.excepthook = exception_logger
55 | app = QApplication([])
56 |
57 | error = JalDB().init_db()
58 | translator = QTranslator(app)
59 | if translator.load(JalSettings.path(JalDB.PATH_LANG_FILE)):
60 | if app.installTranslator(translator):
61 | translator_installed = True
62 | if error.code == JalDBError.OutdatedDbSchema:
63 | error = JalDB().update_db_schema() # this call isn't a part of JalDB.init_db() intentionally - to provide translation of UI message
64 | if error.code != JalDBError.NoError:
65 | window = make_error_window(error)
66 | else:
67 | window = MainWindow(translator)
68 | window.show()
69 | app.exec()
70 | if translator_installed:
71 | app.removeTranslator(translator)
72 |
73 |
74 | #-----------------------------------------------------------------------------------------------------------------------
75 | if __name__ == "__main__":
76 | main()
77 |
--------------------------------------------------------------------------------
/jal/languages/en.qm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/languages/en.qm
--------------------------------------------------------------------------------
/jal/languages/ru.qm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/languages/ru.qm
--------------------------------------------------------------------------------
/jal/net/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/net/web_request.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import platform
3 | from enum import auto
4 | import requests
5 | from requests.exceptions import ConnectTimeout, ConnectionError
6 | from PySide6.QtCore import QThread, QMutex
7 | from jal import __version__
8 |
9 |
10 | # Class that executes web-requests in a separate thread
11 | # Request parameters are given in constructor and execution starts immediately after object creation
12 | # Result of execution is available via data() method after thread completion
13 | class WebRequest(QThread):
14 | GET = auto() # Execute HTTP GET method
15 | POST = auto() # Execute HTTP POST method with application/x-www-form-urlencoded
16 | POST_JSON = auto() # Execute HTTP POST with JSON
17 |
18 | def __init__(self, operation, url, params=None, headers=None, binary=False):
19 | super().__init__()
20 | self._mutex = QMutex()
21 | self._data = ''
22 | self._operation = operation
23 | self._url = url
24 | self._params = params
25 | self._headers = headers
26 | self._binary = binary
27 | if not self.isRunning():
28 | self.start()
29 |
30 | def run(self):
31 | self._mutex.lock()
32 | url = self._url
33 | operation = self._operation
34 | params = self._params
35 | headers = self._headers
36 | binary = self._binary
37 | self._mutex.unlock()
38 | result = self._request(operation, url, params=params, headers=headers, binary=binary)
39 | self._mutex.lock()
40 | self._data = result
41 | self._mutex.unlock()
42 |
43 | def data(self):
44 | self._mutex.lock()
45 | data = self._data
46 | self._mutex.unlock()
47 | return data
48 |
49 | def _request(self, operation, url, params=None, headers=None, binary=False):
50 | session = requests.Session()
51 | session.headers['User-Agent'] = f"JAL/{__version__} ({platform.system()} {platform.release()})"
52 | if headers is not None:
53 | session.headers.update(headers)
54 | try:
55 | if operation == WebRequest.GET:
56 | response = session.get(url, params=params)
57 | elif operation == WebRequest.POST:
58 | response = session.post(url, data=params)
59 | elif operation == WebRequest.POST_JSON:
60 | response = session.post(url, json=params)
61 | else:
62 | assert False
63 | except ConnectTimeout:
64 | logging.error(self.tr("Timeout") + " URL {url}")
65 | return ''
66 | except ConnectionError as e:
67 | logging.error(self.tr("Error") + ", URL {url}\n{e}")
68 | return ''
69 | if response.status_code == 200:
70 | if binary:
71 | return response.content
72 | else:
73 | return response.text
74 | else:
75 | logging.error(self.tr("Failed") + f" [{response.status_code}] URL {url}\n{response.text}")
76 | return ''
77 |
--------------------------------------------------------------------------------
/jal/pypi_description.md:
--------------------------------------------------------------------------------
1 | # JAL
2 | Just Another Ledger is a project for personal finance tracking.
3 |
4 | It was designed to keep records of personal incomes/spendings and investments with up-to-date information about account's balances and portfolio value.
5 |
6 | ### Main features
7 | - multiple accounts with different currencies (base currency is russian rouble but might be changed in future versions)
8 | - 5 types of transactions:
9 | 1. Generic income/spending operations that may be split into several categories
10 | 2. Asset and money transfers between accounts (with currency conversion if required)
11 | 3. Buy/Sell operation for securities (jal supports stocks, ETFs, options, partial support of bonds and futures)
12 | 4. Dividend for stocks and Interest payments for bonds
13 | 5. Corporate actions for stocks (Split, Symbol change, Merger, Spin-Off, Stock dividend)
14 | 6. Term deposits.
15 | - basic reports:
16 | 1. Daily history of account balance.
17 | 2. Portfolio asset allocation for a given date.
18 | 3. Monthly income/expenditure by category.
19 | 4. Investment profit/loss and history of payments for an assets.
20 | 5. Closed deals summary.
21 | - stock/ETF quotes updates for US (Yahoo), EU (Euronext), CA (TSX) and RU (MOEX) exchanges traded stocks
22 | - Broker statement import:
23 | 1. Russian: Uralsib broker (zipped xls), KIT Finance (xlsx), PSB broker (xls), Open broker (xml).
24 | 2. International: Interactive Brokers Flex statement (xml), Just2Trade (xls).
25 | - Investments report for tax declaration preparation for Russia and Portugal.
26 | Russian tax estimation for open positions.
27 | - *experimental* Download russian electronic slips from russian tax authority (FNS). This function requires authorization and `pyzbar` package installation for QR recognition.
28 | You may authorize via SMS, FNS personal account or ESIA/Gosuslugi. QR code may be scanned from camera, clipboard image or image file on disk.
29 |
30 | Full description is available at Github - *[English](https://github.com/titov-vv/jal/blob/master/docs/README.md), [Русский](https://github.com/titov-vv/jal/blob/master/docs/README.ru.md)*
31 |
32 | Support: [jal@gmx.ru](mailto:jal@gmx.ru?subject=%5BJAL%5D%20Help) or [Telegram](https://t.me/jal_support)
--------------------------------------------------------------------------------
/jal/reports/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/reports/deals.py:
--------------------------------------------------------------------------------
1 | from functools import partial
2 |
3 | from PySide6.QtCore import Slot, QObject
4 | from jal.ui.reports.ui_deals_report import Ui_DealsReportWidget
5 | from jal.reports.reports import Reports
6 | from jal.widgets.mdi import MdiWidget
7 | from jal.db.trades_model import ClosedTradesModel
8 |
9 | JAL_REPORT_CLASS = "DealsReport"
10 |
11 |
12 | # ----------------------------------------------------------------------------------------------------------------------
13 | class DealsReport(QObject):
14 | def __init__(self):
15 | super().__init__()
16 | self.name = self.name = self.tr("Deals by Account")
17 | self.window_class = "DealsReportWindow"
18 |
19 |
20 | # ----------------------------------------------------------------------------------------------------------------------
21 | class DealsReportWindow(MdiWidget):
22 | def __init__(self, parent: Reports, settings: dict = None):
23 | super().__init__(parent.mdi_area())
24 | self.ui = Ui_DealsReportWidget()
25 | self.ui.setupUi(self)
26 | self._parent = parent
27 | self.name = self.tr("Deals")
28 |
29 | # Add available groupings
30 | self.ui.GroupCombo.addItem(self.tr(""), "")
31 | self.ui.GroupCombo.addItem(self.tr("Asset"), "symbol")
32 | self.ui.GroupCombo.addItem(self.tr("Close"), "close_date")
33 | self.ui.GroupCombo.addItem(self.tr("Asset - Open - Close"), "symbol;open_date;close_date")
34 | self.ui.GroupCombo.addItem(self.tr("Open - Close"), "open_date;close_date")
35 | self.ui.GroupCombo.addItem(self.tr("Close - Open"), "close_date;open_date")
36 |
37 | self.trades_model = ClosedTradesModel(self.ui.ReportTreeView)
38 | self.ui.ReportTreeView.setModel(self.trades_model)
39 |
40 | self.connect_signals_and_slots()
41 |
42 | def connect_signals_and_slots(self):
43 | self.ui.ReportAccountButton.changed.connect(self.updateReport)
44 | self.ui.ReportRange.changed.connect(self.updateReport)
45 | self.ui.GroupCombo.currentIndexChanged.connect(self.updateReport)
46 | self.ui.SaveButton.pressed.connect(partial(self._parent.save_report, self.name, self.ui.ReportTreeView.model()))
47 |
48 | @Slot()
49 | def updateReport(self):
50 | self.ui.ReportTreeView.model().updateView(account_id=self.ui.ReportAccountButton.account_id,
51 | dates=self.ui.ReportRange.getRange(),
52 | grouping=self.ui.GroupCombo.currentData())
53 |
--------------------------------------------------------------------------------
/jal/run_designer:
--------------------------------------------------------------------------------
1 | export PYSIDE_DESIGNER_PLUGINS=..
2 | pyside6-designer
3 |
--------------------------------------------------------------------------------
/jal/ui/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/ui/flow_export_widget.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MoneyFlowWidget
4 |
5 |
6 |
7 | 0
8 | 0
9 | 458
10 | 158
11 |
12 |
13 |
14 | Money Flow
15 |
16 |
17 | -
18 |
19 |
20 |
21 | 0
22 | 0
23 |
24 |
25 |
26 | Select file
27 |
28 |
29 | ...
30 |
31 |
32 |
33 | -
34 |
35 |
36 | Save Report
37 |
38 |
39 |
40 | -
41 |
42 |
43 | Excel file:
44 |
45 |
46 |
47 | -
48 |
49 |
50 | Year:
51 |
52 |
53 |
54 | -
55 |
56 |
57 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
58 |
59 |
60 |
61 |
62 |
63 | 2010
64 |
65 |
66 | 2030
67 |
68 |
69 | 2020
70 |
71 |
72 |
73 | -
74 |
75 |
76 |
77 | 0
78 | 0
79 |
80 |
81 |
82 | File where to store tax report in Excel format
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/jal/ui/login_lidl_plus_dlg.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | LoginLidlPlusDialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 500
11 |
12 |
13 |
14 | Authorization Lidl Plus
15 |
16 |
17 |
18 | 6
19 |
20 |
21 | 2
22 |
23 |
24 | 2
25 |
26 |
27 | 2
28 |
29 |
30 | 2
31 |
32 | -
33 |
34 |
35 |
36 | 0
37 | 0
38 |
39 |
40 |
41 |
42 | about:blank
43 |
44 |
45 |
46 |
47 | -
48 |
49 |
50 | QFrame::NoFrame
51 |
52 |
53 | QFrame::Plain
54 |
55 |
56 |
57 | 0
58 |
59 |
60 | 0
61 |
62 |
63 | 0
64 |
65 |
66 | 6
67 |
68 |
-
69 |
70 |
71 | Qt::Horizontal
72 |
73 |
74 |
75 | -
76 |
77 |
78 |
79 | 0
80 | 0
81 |
82 |
83 |
84 | Close
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | QWebEngineView
96 | QWidget
97 | QtWebEngineWidgets/QWebEngineView
98 |
99 |
100 |
101 |
102 |
103 | CloseBtn
104 | clicked()
105 | LoginLidlPlusDialog
106 | close()
107 |
108 |
109 | 199
110 | 477
111 |
112 |
113 | 199
114 | 249
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/jal/ui/quotes_update.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | UpdateQuotesDlg
4 |
5 |
6 | Qt::ApplicationModal
7 |
8 |
9 |
10 | 0
11 | 0
12 | 256
13 | 308
14 |
15 |
16 |
17 | Update asset's quotes
18 |
19 |
20 | -
21 |
22 |
23 | dd/MM/yyyy
24 |
25 |
26 | true
27 |
28 |
29 | Qt::UTC
30 |
31 |
32 |
33 | -
34 |
35 |
36 | Qt::Horizontal
37 |
38 |
39 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
40 |
41 |
42 |
43 | -
44 |
45 |
46 | dd/MM/yyyy
47 |
48 |
49 | true
50 |
51 |
52 | Qt::UTC
53 |
54 |
55 |
56 | -
57 |
58 |
59 | End date
60 |
61 |
62 |
63 | -
64 |
65 |
66 | Start date
67 |
68 |
69 |
70 | -
71 |
72 |
73 | QAbstractItemView::EditKeyPressed
74 |
75 |
76 | true
77 |
78 |
79 | true
80 |
81 |
82 |
83 | -
84 |
85 |
86 | Sources
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | buttonBox
96 | accepted()
97 | UpdateQuotesDlg
98 | accept()
99 |
100 |
101 | 248
102 | 254
103 |
104 |
105 | 157
106 | 274
107 |
108 |
109 |
110 |
111 | buttonBox
112 | rejected()
113 | UpdateQuotesDlg
114 | reject()
115 |
116 |
117 | 316
118 | 260
119 |
120 |
121 | 286
122 | 274
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/jal/ui/reports/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/ui/reports/__init__.py
--------------------------------------------------------------------------------
/jal/ui/reports/account_balance_report.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | AccountBalanceHistoryReportWidget
4 |
5 |
6 |
7 | 0
8 | 0
9 | 769
10 | 345
11 |
12 |
13 |
14 | Account balance history chart
15 |
16 |
17 |
18 | 0
19 |
20 |
21 | 0
22 |
23 |
24 | 0
25 |
26 |
27 | 0
28 |
29 |
30 | 0
31 |
32 | -
33 |
34 |
35 | QFrame::Panel
36 |
37 |
38 | QFrame::Sunken
39 |
40 |
41 |
42 | 2
43 |
44 |
45 | 2
46 |
47 |
48 | 2
49 |
50 |
51 | 2
52 |
53 |
54 | 6
55 |
56 |
-
57 |
58 |
59 | QTD;YTD;this_year;last_year
60 |
61 |
62 |
63 | -
64 |
65 |
66 | Qt::Horizontal
67 |
68 |
69 |
70 | 40
71 | 20
72 |
73 |
74 |
75 |
76 | -
77 |
78 |
79 | Account:
80 |
81 |
82 |
83 | -
84 |
85 |
86 |
87 |
88 |
89 |
90 | -
91 |
92 |
93 | Save...
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | DateRangeSelector
105 | QWidget
106 | jal/widgets/custom/date_range_selector.h
107 | 1
108 |
109 |
110 | AccountButton
111 | QPushButton
112 | jal/widgets/account_select.h
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/jal/ui/select_account_dlg.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | SelectAccountDlg
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 141
11 |
12 |
13 |
14 | Please select account
15 |
16 |
17 | -
18 |
19 |
20 | TextLabel
21 |
22 |
23 |
24 | -
25 |
26 |
27 | -
28 |
29 |
30 | Use the same account for given currency
31 |
32 |
33 |
34 | -
35 |
36 |
37 | Qt::Horizontal
38 |
39 |
40 | QDialogButtonBox::Ok
41 |
42 |
43 |
44 | -
45 |
46 |
47 | Qt::Vertical
48 |
49 |
50 |
51 | 20
52 | 40
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | AccountSelector
62 | QWidget
63 | jal/widgets/reference_selector.h
64 | 1
65 |
66 |
67 |
68 |
69 |
70 | buttonBox
71 | accepted()
72 | SelectAccountDlg
73 | close()
74 |
75 |
76 | 199
77 | 60
78 |
79 |
80 | 199
81 | 52
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/jal/ui/select_reference_dlg.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | SelectReferenceDlg
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 97
11 |
12 |
13 |
14 |
15 | 0
16 | 0
17 |
18 |
19 |
20 | Title
21 |
22 |
23 | -
24 |
25 |
26 | Description
27 |
28 |
29 |
30 | -
31 |
32 |
33 | QFrame::NoFrame
34 |
35 |
36 | QFrame::Plain
37 |
38 |
39 | 0
40 |
41 |
42 | 0
43 |
44 |
45 |
46 | 0
47 |
48 |
49 | 0
50 |
51 |
52 | 0
53 |
54 |
55 | 0
56 |
57 |
58 | 0
59 |
60 |
61 |
62 |
63 | -
64 |
65 |
66 | Qt::Horizontal
67 |
68 |
69 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
70 |
71 |
72 |
73 | -
74 |
75 |
76 | Qt::Vertical
77 |
78 |
79 |
80 | 20
81 | 40
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | buttonBox
92 | accepted()
93 | SelectReferenceDlg
94 | close()
95 |
96 |
97 | 199
98 | 60
99 |
100 |
101 | 199
102 | 52
103 |
104 |
105 |
106 |
107 | buttonBox
108 | rejected()
109 | SelectReferenceDlg
110 | reject()
111 |
112 |
113 | 199
114 | 51
115 |
116 |
117 | 199
118 | 48
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/jal/ui/ui_select_account_dlg.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'select_account_dlg.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 6.9.1
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
12 | QMetaObject, QObject, QPoint, QRect,
13 | QSize, QTime, QUrl, Qt)
14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
15 | QFont, QFontDatabase, QGradient, QIcon,
16 | QImage, QKeySequence, QLinearGradient, QPainter,
17 | QPalette, QPixmap, QRadialGradient, QTransform)
18 | from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
19 | QDialogButtonBox, QLabel, QSizePolicy, QSpacerItem,
20 | QVBoxLayout, QWidget)
21 |
22 | from jal.widgets.reference_selector import AccountSelector
23 |
24 | class Ui_SelectAccountDlg(object):
25 | def setupUi(self, SelectAccountDlg):
26 | if not SelectAccountDlg.objectName():
27 | SelectAccountDlg.setObjectName(u"SelectAccountDlg")
28 | SelectAccountDlg.resize(400, 141)
29 | self.verticalLayout = QVBoxLayout(SelectAccountDlg)
30 | self.verticalLayout.setObjectName(u"verticalLayout")
31 | self.DescriptionLbl = QLabel(SelectAccountDlg)
32 | self.DescriptionLbl.setObjectName(u"DescriptionLbl")
33 |
34 | self.verticalLayout.addWidget(self.DescriptionLbl)
35 |
36 | self.AccountWidget = AccountSelector(SelectAccountDlg)
37 | self.AccountWidget.setObjectName(u"AccountWidget")
38 |
39 | self.verticalLayout.addWidget(self.AccountWidget)
40 |
41 | self.ReuseAccount = QCheckBox(SelectAccountDlg)
42 | self.ReuseAccount.setObjectName(u"ReuseAccount")
43 |
44 | self.verticalLayout.addWidget(self.ReuseAccount)
45 |
46 | self.buttonBox = QDialogButtonBox(SelectAccountDlg)
47 | self.buttonBox.setObjectName(u"buttonBox")
48 | self.buttonBox.setOrientation(Qt.Horizontal)
49 | self.buttonBox.setStandardButtons(QDialogButtonBox.Ok)
50 |
51 | self.verticalLayout.addWidget(self.buttonBox)
52 |
53 | self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
54 |
55 | self.verticalLayout.addItem(self.verticalSpacer)
56 |
57 |
58 | self.retranslateUi(SelectAccountDlg)
59 | self.buttonBox.accepted.connect(SelectAccountDlg.close)
60 |
61 | QMetaObject.connectSlotsByName(SelectAccountDlg)
62 | # setupUi
63 |
64 | def retranslateUi(self, SelectAccountDlg):
65 | SelectAccountDlg.setWindowTitle(QCoreApplication.translate("SelectAccountDlg", u"Please select account", None))
66 | self.DescriptionLbl.setText(QCoreApplication.translate("SelectAccountDlg", u"TextLabel", None))
67 | self.ReuseAccount.setText(QCoreApplication.translate("SelectAccountDlg", u"Use the same account for given currency", None))
68 | # retranslateUi
69 |
70 |
--------------------------------------------------------------------------------
/jal/ui/ui_select_reference_dlg.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'select_reference_dlg.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 6.9.1
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
12 | QMetaObject, QObject, QPoint, QRect,
13 | QSize, QTime, QUrl, Qt)
14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
15 | QFont, QFontDatabase, QGradient, QIcon,
16 | QImage, QKeySequence, QLinearGradient, QPainter,
17 | QPalette, QPixmap, QRadialGradient, QTransform)
18 | from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
19 | QFrame, QLabel, QSizePolicy, QSpacerItem,
20 | QVBoxLayout, QWidget)
21 |
22 | class Ui_SelectReferenceDlg(object):
23 | def setupUi(self, SelectReferenceDlg):
24 | if not SelectReferenceDlg.objectName():
25 | SelectReferenceDlg.setObjectName(u"SelectReferenceDlg")
26 | SelectReferenceDlg.resize(400, 97)
27 | sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
28 | sizePolicy.setHorizontalStretch(0)
29 | sizePolicy.setVerticalStretch(0)
30 | sizePolicy.setHeightForWidth(SelectReferenceDlg.sizePolicy().hasHeightForWidth())
31 | SelectReferenceDlg.setSizePolicy(sizePolicy)
32 | SelectReferenceDlg.setWindowTitle(u"Title")
33 | self.WindowLayout = QVBoxLayout(SelectReferenceDlg)
34 | self.WindowLayout.setObjectName(u"WindowLayout")
35 | self.DescriptionLabel = QLabel(SelectReferenceDlg)
36 | self.DescriptionLabel.setObjectName(u"DescriptionLabel")
37 | self.DescriptionLabel.setText(u"Description")
38 |
39 | self.WindowLayout.addWidget(self.DescriptionLabel)
40 |
41 | self.SelectorFrame = QFrame(SelectReferenceDlg)
42 | self.SelectorFrame.setObjectName(u"SelectorFrame")
43 | self.SelectorFrame.setFrameShape(QFrame.NoFrame)
44 | self.SelectorFrame.setFrameShadow(QFrame.Plain)
45 | self.SelectorFrame.setLineWidth(0)
46 | self.SelectorFrame.setMidLineWidth(0)
47 | self.FrameLayout = QVBoxLayout(self.SelectorFrame)
48 | self.FrameLayout.setSpacing(0)
49 | self.FrameLayout.setObjectName(u"FrameLayout")
50 | self.FrameLayout.setContentsMargins(0, 0, 0, 0)
51 |
52 | self.WindowLayout.addWidget(self.SelectorFrame)
53 |
54 | self.buttonBox = QDialogButtonBox(SelectReferenceDlg)
55 | self.buttonBox.setObjectName(u"buttonBox")
56 | self.buttonBox.setOrientation(Qt.Horizontal)
57 | self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
58 |
59 | self.WindowLayout.addWidget(self.buttonBox)
60 |
61 | self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
62 |
63 | self.WindowLayout.addItem(self.verticalSpacer)
64 |
65 |
66 | self.retranslateUi(SelectReferenceDlg)
67 | self.buttonBox.accepted.connect(SelectReferenceDlg.close)
68 | self.buttonBox.rejected.connect(SelectReferenceDlg.reject)
69 |
70 | QMetaObject.connectSlotsByName(SelectReferenceDlg)
71 | # setupUi
72 |
73 | def retranslateUi(self, SelectReferenceDlg):
74 | pass
75 | # retranslateUi
76 |
77 |
--------------------------------------------------------------------------------
/jal/ui/widgets/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/ui/widgets/__init__.py
--------------------------------------------------------------------------------
/jal/universal_cache.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | from typing import Any, Callable, Hashable, Tuple
3 |
4 | class UniversalCache:
5 | """
6 | Universal cache for functions with hashable arguments.
7 |
8 | Features:
9 | - Cache key: tuple (function, hashable arguments)
10 | - Autovalidation of arguments (must be hashable)
11 | - Invalidate specific cache entries (by key)
12 | - Guaranteed unique cache key for each function and its arguments
13 | """
14 |
15 | def __init__(self):
16 | self._cache = {}
17 |
18 | def get_data(
19 | self,
20 | func: Callable,
21 | args: Tuple[Hashable, ...] = ()
22 | ) -> Any:
23 | """
24 | Get data from cache or execute function if not cached.
25 |
26 | :param func: Function to cache/execute
27 | :param args: Function arguments (must be hashable)
28 | :return: Function result from cache or execution
29 | """
30 | key = self._create_key(func, args)
31 |
32 | if key not in self._cache:
33 | self._cache[key] = func(*args)
34 |
35 | return self._cache[key]
36 |
37 | def update_data(
38 | self,
39 | func: Callable,
40 | args: Tuple[Hashable, ...] = ()
41 | ) -> Any:
42 | """
43 | Invalidates old cache entry and executes function to have new data cached.
44 |
45 | :param func: Function to cache/execute
46 | :param args: Function arguments (must be hashable)
47 | :return: Function result from new execution
48 | """
49 | self.invalidate(func, args)
50 | return self.get_data(func, args)
51 |
52 | def invalidate(self, func: Callable, args: Tuple[Hashable, ...] = ()) -> None:
53 | """Delete a specific cache entry"""
54 | key = self._create_key(func, args)
55 | self._cache.pop(key, None)
56 |
57 | def clear_cache(self) -> None:
58 | """Clear the entire cache"""
59 | self._cache.clear()
60 |
61 | def _create_key(self, func: Callable, args: Tuple) -> Tuple:
62 | """Create a unique key for the cache"""
63 | self._validate_args_hashable(args)
64 | return (
65 | func.__module__,
66 | func.__name__,
67 | args
68 | )
69 |
70 | @staticmethod
71 | def _validate_args_hashable(args: Tuple) -> None:
72 | """Validate that all arguments are hashable"""
73 | try:
74 | hash(args)
75 | except TypeError:
76 | raise ValueError("All elements must be hashable")
77 |
--------------------------------------------------------------------------------
/jal/updates/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/updates/jal_delta_40.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | -- Change db structure for base currency
6 | DELETE FROM settings WHERE id=2 AND name='BaseCurrency';
7 |
8 | DROP TABLE IF EXISTS base_currency;
9 | CREATE TABLE base_currency (
10 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
11 | since_timestamp INTEGER NOT NULL UNIQUE,
12 | currency_id INTEGER NOT NULL REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE
13 | );
14 | --------------------------------------------------------------------------------
15 | -- Ensure compatibility with previous behavior
16 | INSERT INTO base_currency(id, since_timestamp, currency_id) VALUES (1, 946684800, 1);
17 | -- Update data source name
18 | UPDATE data_sources SET name='Central banks' WHERE id=0;
19 | --------------------------------------------------------------------------------
20 | -- Change db structure for currency handling
21 | CREATE TABLE temp_tickers AS SELECT * FROM asset_tickers;
22 | DROP TABLE asset_tickers;
23 | CREATE TABLE asset_tickers (
24 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
25 | asset_id INTEGER REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
26 | symbol TEXT NOT NULL,
27 | currency_id INTEGER REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE,
28 | description TEXT NOT NULL DEFAULT (''),
29 | quote_source INTEGER REFERENCES data_sources (id) ON DELETE SET NULL ON UPDATE CASCADE DEFAULT ( -1),
30 | active INTEGER NOT NULL DEFAULT (1)
31 | );
32 | INSERT INTO asset_tickers (id, asset_id, symbol, currency_id, description, quote_source, active)
33 | SELECT id, asset_id, symbol, currency_id, description, quote_source, active FROM temp_tickers;
34 | DROP TABLE temp_tickers;
35 | -- Set reference currency to NULL for currency symbols
36 | UPDATE asset_tickers SET currency_id=NULL WHERE asset_id IN (SELECT id FROM assets WHERE type_id=1);
37 | -- Index to prevent duplicates
38 | DROP INDEX IF EXISTS uniq_symbols;
39 | CREATE UNIQUE INDEX uniq_symbols ON asset_tickers (asset_id, symbol COLLATE NOCASE, currency_id);
40 |
41 | DROP TRIGGER IF EXISTS validate_ticker_currency_insert;
42 | CREATE TRIGGER validate_ticker_currency_insert
43 | BEFORE INSERT ON asset_tickers
44 | FOR EACH ROW
45 | WHEN IIF(NEW.currency_id IS NULL, 0, 1) = (SELECT IIF(type_id=1, 1, 0) FROM assets WHERE id=NEW.asset_id)
46 | BEGIN
47 | SELECT RAISE(ABORT, "JAL_SQL_MSG_0003");
48 | END;
49 |
50 | DROP TRIGGER IF EXISTS validate_ticker_currency_update;
51 | CREATE TRIGGER validate_ticker_currency_update
52 | AFTER UPDATE OF currency_id ON asset_tickers
53 | FOR EACH ROW
54 | WHEN IIF(NEW.currency_id IS NULL, 0, 1) = (SELECT IIF(type_id=1, 1, 0) FROM assets WHERE id=NEW.asset_id)
55 | BEGIN
56 | SELECT RAISE(ABORT, "JAL_SQL_MSG_0003");
57 | END;
58 | --------------------------------------------------------------------------------
59 | PRAGMA foreign_keys = 1;
60 | --------------------------------------------------------------------------------
61 | -- Set new DB schema version
62 | UPDATE settings SET value=40 WHERE name='SchemaVersion';
63 | COMMIT;
64 |
--------------------------------------------------------------------------------
/jal/updates/jal_delta_42.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | CREATE TABLE old_actions_table AS SELECT * FROM actions;
6 | DROP TABLE actions;
7 | CREATE TABLE actions (
8 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
9 | op_type INTEGER NOT NULL DEFAULT (1),
10 | timestamp INTEGER NOT NULL,
11 | account_id INTEGER REFERENCES accounts (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
12 | peer_id INTEGER REFERENCES agents (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
13 | alt_currency_id INTEGER REFERENCES assets (id) ON DELETE RESTRICT ON UPDATE CASCADE,
14 | note TEXT
15 | );
16 | INSERT INTO actions (id, op_type, timestamp, account_id, peer_id, alt_currency_id)
17 | SELECT id, op_type, timestamp, account_id, peer_id, alt_currency_id FROM old_actions_table;
18 | DROP TABLE old_actions_table;
19 | --------------------------------------------------------------------------------
20 | -- Restore triggers
21 | CREATE TRIGGER actions_after_delete
22 | AFTER DELETE ON actions
23 | FOR EACH ROW
24 | WHEN (SELECT value FROM settings WHERE id = 1)
25 | BEGIN
26 | DELETE FROM action_details WHERE pid = OLD.id;
27 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp;
28 | END;
29 |
30 | CREATE TRIGGER actions_after_insert
31 | AFTER INSERT ON actions
32 | FOR EACH ROW
33 | WHEN (SELECT value FROM settings WHERE id = 1)
34 | BEGIN
35 | DELETE FROM ledger WHERE timestamp >= NEW.timestamp;
36 | END;
37 |
38 | CREATE TRIGGER actions_after_update
39 | AFTER UPDATE OF timestamp, account_id, peer_id ON actions
40 | FOR EACH ROW
41 | WHEN (SELECT value FROM settings WHERE id = 1)
42 | BEGIN
43 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp OR timestamp >= NEW.timestamp;
44 | END;
45 | --------------------------------------------------------------------------------
46 | PRAGMA foreign_keys = 1;
47 | --------------------------------------------------------------------------------
48 | -- Set new DB schema version
49 | UPDATE settings SET value=42 WHERE name='SchemaVersion';
50 | COMMIT;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_43.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | -- Fix old data
6 | UPDATE dividends SET ex_date=0 WHERE ex_date IS NULL;
7 | UPDATE dividends SET number='' WHERE number IS NULL;
8 | UPDATE dividends SET tax='0' WHERE tax IS NULL;
9 | --------------------------------------------------------------------------------
10 | -- Correct table structure
11 | CREATE TABLE dividends_old AS SELECT * FROM dividends;
12 | DROP TABLE dividends;
13 | CREATE TABLE dividends (
14 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
15 | op_type INTEGER NOT NULL DEFAULT (2),
16 | timestamp INTEGER NOT NULL,
17 | ex_date INTEGER NOT NULL DEFAULT (0),
18 | number TEXT DEFAULT ('') NOT NULL,
19 | type INTEGER NOT NULL,
20 | account_id INTEGER REFERENCES accounts (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
21 | asset_id INTEGER REFERENCES assets (id) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL,
22 | amount TEXT NOT NULL DEFAULT ('0'),
23 | tax TEXT NOT NULL DEFAULT ('0'),
24 | note TEXT
25 | );
26 | INSERT INTO dividends (id, op_type, timestamp, ex_date, number, type, account_id, asset_id, amount, tax, note)
27 | SELECT id, op_type, timestamp, ex_date, number, type, account_id, asset_id, amount, tax, note FROM dividends_old;
28 | DROP TABLE dividends_old;
29 |
30 | DROP TRIGGER IF EXISTS dividends_after_delete;
31 | CREATE TRIGGER dividends_after_delete
32 | AFTER DELETE ON dividends
33 | FOR EACH ROW
34 | WHEN (SELECT value FROM settings WHERE id = 1)
35 | BEGIN
36 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp;
37 | END;
38 |
39 | DROP TRIGGER IF EXISTS dividends_after_insert;
40 | CREATE TRIGGER dividends_after_insert
41 | AFTER INSERT ON dividends
42 | FOR EACH ROW
43 | WHEN (SELECT value FROM settings WHERE id = 1)
44 | BEGIN
45 | DELETE FROM ledger WHERE timestamp >= NEW.timestamp;
46 | END;
47 |
48 | DROP TRIGGER IF EXISTS dividends_after_update;
49 | CREATE TRIGGER dividends_after_update
50 | AFTER UPDATE OF timestamp, account_id, asset_id, amount, tax ON dividends
51 | FOR EACH ROW
52 | WHEN (SELECT value FROM settings WHERE id = 1)
53 | BEGIN
54 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp OR timestamp >= NEW.timestamp;
55 | END;
56 | --------------------------------------------------------------------------------
57 | PRAGMA foreign_keys = 1;
58 | --------------------------------------------------------------------------------
59 | -- Set new DB schema version
60 | UPDATE settings SET value=43 WHERE name='SchemaVersion';
61 | COMMIT;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_44.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | -- Add new 'number' field into transfers table
6 | CREATE TABLE transfers_old AS SELECT * FROM transfers;
7 | DROP TABLE IF EXISTS transfers;
8 | CREATE TABLE transfers (
9 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
10 | op_type INTEGER NOT NULL DEFAULT (4),
11 | withdrawal_timestamp INTEGER NOT NULL,
12 | withdrawal_account INTEGER NOT NULL REFERENCES accounts (id) ON DELETE CASCADE ON UPDATE CASCADE,
13 | withdrawal TEXT NOT NULL,
14 | deposit_timestamp INTEGER NOT NULL,
15 | deposit_account INTEGER NOT NULL REFERENCES accounts (id) ON DELETE CASCADE ON UPDATE CASCADE,
16 | deposit TEXT NOT NULL,
17 | fee_account INTEGER REFERENCES accounts (id) ON DELETE CASCADE ON UPDATE CASCADE,
18 | fee TEXT,
19 | number TEXT NOT NULL DEFAULT (''),
20 | asset INTEGER REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE,
21 | note TEXT
22 | );
23 | INSERT INTO transfers (id, op_type, withdrawal_timestamp, withdrawal_account, withdrawal, deposit_timestamp, deposit_account, deposit, fee_account, fee, asset, note)
24 | SELECT id, op_type, withdrawal_timestamp, withdrawal_account, withdrawal, deposit_timestamp, deposit_account, deposit, fee_account, fee, asset, note FROM transfers_old;
25 | DROP TABLE transfers_old;
26 |
27 | DROP TRIGGER IF EXISTS transfers_after_delete;
28 | CREATE TRIGGER transfers_after_delete
29 | AFTER DELETE ON transfers
30 | FOR EACH ROW
31 | WHEN (SELECT value FROM settings WHERE id = 1)
32 | BEGIN
33 | DELETE FROM ledger WHERE timestamp >= OLD.withdrawal_timestamp OR timestamp >= OLD.deposit_timestamp;
34 | END;
35 |
36 | DROP TRIGGER IF EXISTS transfers_after_insert;
37 | CREATE TRIGGER transfers_after_insert
38 | AFTER INSERT ON transfers
39 | FOR EACH ROW
40 | WHEN (SELECT value FROM settings WHERE id = 1)
41 | BEGIN
42 | DELETE FROM ledger WHERE timestamp >= NEW.withdrawal_timestamp OR timestamp >= NEW.deposit_timestamp;
43 | END;
44 |
45 | DROP TRIGGER IF EXISTS transfers_after_update;
46 | CREATE TRIGGER transfers_after_update
47 | AFTER UPDATE OF withdrawal_timestamp, deposit_timestamp, withdrawal_account, deposit_account, fee_account,
48 | withdrawal, deposit, fee, asset ON transfers
49 | FOR EACH ROW
50 | WHEN (SELECT value FROM settings WHERE id = 1)
51 | BEGIN
52 | DELETE FROM ledger WHERE timestamp >= OLD.withdrawal_timestamp OR timestamp >= OLD.deposit_timestamp OR
53 | timestamp >= NEW.withdrawal_timestamp OR timestamp >= NEW.deposit_timestamp;
54 | END;
55 | --------------------------------------------------------------------------------
56 | PRAGMA foreign_keys = 1;
57 | --------------------------------------------------------------------------------
58 | -- Set new DB schema version
59 | UPDATE settings SET value=44 WHERE name='SchemaVersion';
60 | COMMIT;
61 |
--------------------------------------------------------------------------------
/jal/updates/jal_delta_45.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | DELETE FROM settings WHERE id>10;
4 | INSERT INTO settings(id, name, value) VALUES (11, 'RecentFolder_Statement', '.');
5 | INSERT INTO settings(id, name, value) VALUES (12, 'RecentFolder_Report', '.');
6 | INSERT INTO settings(id, name, value) VALUES (13, 'CleanDB', 0);
7 | --------------------------------------------------------------------------------
8 | -- Set new DB schema version
9 | UPDATE settings SET value=45 WHERE name='SchemaVersion';
10 | COMMIT;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_46.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | -- Add tree-hierarchy to 'tags' table
6 | CREATE TABLE temp_tags AS SELECT * FROM tags;
7 | DROP TABLE tags;
8 | CREATE TABLE tags (
9 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
10 | pid INTEGER NOT NULL DEFAULT (0),
11 | tag TEXT (64) NOT NULL UNIQUE
12 | );
13 | INSERT INTO tags (id, tag) SELECT id, tag FROM temp_tags;
14 | DROP TABLE temp_tags;
15 | --------------------------------------------------------------------------------
16 | PRAGMA foreign_keys = 1;
17 | --------------------------------------------------------------------------------
18 | -- Set new DB schema version
19 | UPDATE settings SET value=46 WHERE name='SchemaVersion';
20 | COMMIT;
21 | -- Reduce file size
22 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_47.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | INSERT INTO settings(id, name, value) VALUES (14, 'EuLidlClientSecret', 'TGlkbFBsdXNOYXRpdmVDbGllbnQ6c2VjcmV0');
4 | INSERT INTO settings(id, name, value) VALUES (15, 'EuLidlAccessToken', '');
5 | INSERT INTO settings(id, name, value) VALUES (16, 'EuLidlRefreshToken', '');
6 | INSERT INTO settings(id, name, value) VALUES (17, 'PtPingoDoceAccessToken', '');
7 | INSERT INTO settings(id, name, value) VALUES (18, 'PtPingoDoceRefreshToken', '');
8 | INSERT INTO settings(id, name, value) VALUES (19, 'PtPingoDoceUserProfile', '{}');
9 | --------------------------------------------------------------------------------
10 | -- Set new DB schema version
11 | UPDATE settings SET value=47 WHERE name='SchemaVersion';
12 | COMMIT;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_48.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | CREATE TABLE temp_tickers AS SELECT * FROM asset_tickers;
6 | DROP TABLE asset_tickers;
7 | CREATE TABLE asset_tickers (
8 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
9 | asset_id INTEGER REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
10 | symbol TEXT NOT NULL,
11 | currency_id INTEGER REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE,
12 | description TEXT NOT NULL DEFAULT (''),
13 | quote_source INTEGER DEFAULT ( -1) NOT NULL,
14 | active INTEGER NOT NULL DEFAULT (1)
15 | );
16 | INSERT INTO asset_tickers (id, asset_id, symbol, currency_id, description, quote_source, active)
17 | SELECT id, asset_id, symbol, currency_id, description, quote_source, active FROM temp_tickers;
18 | DROP TABLE temp_tickers;
19 | -- Index to prevent duplicates
20 | DROP INDEX IF EXISTS uniq_symbols;
21 | CREATE UNIQUE INDEX uniq_symbols ON asset_tickers (asset_id, symbol COLLATE NOCASE, currency_id);
22 | -- Create triggers to keep currency_id NULL for currencies and NOT NULL for other assets
23 | DROP TRIGGER IF EXISTS validate_ticker_currency_insert;
24 | CREATE TRIGGER validate_ticker_currency_insert
25 | BEFORE INSERT ON asset_tickers
26 | FOR EACH ROW
27 | WHEN IIF(NEW.currency_id IS NULL, 0, 1) = (SELECT IIF(type_id=1, 1, 0) FROM assets WHERE id=NEW.asset_id)
28 | BEGIN
29 | SELECT RAISE(ABORT, "JAL_SQL_MSG_0003");
30 | END;
31 |
32 | DROP TRIGGER IF EXISTS validate_ticker_currency_update;
33 | CREATE TRIGGER validate_ticker_currency_update
34 | AFTER UPDATE OF currency_id ON asset_tickers
35 | FOR EACH ROW
36 | WHEN IIF(NEW.currency_id IS NULL, 0, 1) = (SELECT IIF(type_id=1, 1, 0) FROM assets WHERE id=NEW.asset_id)
37 | BEGIN
38 | SELECT RAISE(ABORT, "JAL_SQL_MSG_0003");
39 | END;
40 | -- drop table with data sources
41 | DROP TABLE IF EXISTS data_sources;
42 | --------------------------------------------------------------------------------
43 | PRAGMA foreign_keys = 1;
44 | --------------------------------------------------------------------------------
45 | -- Set new DB schema version
46 | UPDATE settings SET value=48 WHERE name='SchemaVersion';
47 | COMMIT;
48 | -- Reduce file size
49 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_49.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | UPDATE transfers SET deposit=(deposit-fee), fee_account=NULL, fee=NULL WHERE id IN
6 | (SELECT t.id FROM transfers t LEFT JOIN accounts fa ON fa.id = t.fee_account WHERE fa.organization_id IS NULL AND t.fee IS NOT NULL AND t.fee_account==t.deposit_account);
7 | UPDATE transfers SET withdrawal=(withdrawal+fee), fee_account=NULL, fee=NULL WHERE id IN
8 | (SELECT t.id FROM transfers t LEFT JOIN accounts fa ON fa.id = t.fee_account WHERE fa.organization_id IS NULL AND t.fee IS NOT NULL AND t.fee_account==t.withdrawal_account);
9 | --------------------------------------------------------------------------------
10 | PRAGMA foreign_keys = 1;
11 | --------------------------------------------------------------------------------
12 | -- Set new DB schema version
13 | UPDATE settings SET value=49 WHERE name='SchemaVersion';
14 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (7, 'RebuildDB', 1);
15 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (10, 'MessageOnce',
16 | '{"en": "Database version was updated.\nTransfers handling was changed - fee account should now have organization assigned.\nYou may assign it in the Data->Accounts menu if a related error will appear in the log.",
17 | "ru": "Версия базы данных обновлена.\nОбработка переводов была изменена - счёт комиссии теперь должен иметь организацию-владельца.\nВы можете указать её в меню Данные->Счета, если будут в логе будет ошибка об этом."}');
18 | COMMIT;
19 | -- Reduce file size
20 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_50.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | UPDATE transfers SET deposit='0' WHERE asset IS NOT NULL;
6 | --------------------------------------------------------------------------------
7 | PRAGMA foreign_keys = 1;
8 | --------------------------------------------------------------------------------
9 | -- Set new DB schema version
10 | UPDATE settings SET value=50 WHERE name='SchemaVersion';
11 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (7, 'RebuildDB', 1);
12 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (10, 'MessageOnce',
13 | '{"en": "Database version was updated.\nAssets transfer handling was changed.\nSet a non-zero cost basis if you need to see a relevant open price for tranferred positions in another currency.",
14 | "ru": "Версия базы данных обновлена.\nОбработка переводов ЦБ была изменена.\nУкажите ненулевую стоимость позиции, если вы хотите видеть правильную цену покупки переведённых бумаг в другой валюте."}');
15 | COMMIT;
16 | -- Reduce file size
17 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_51.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | DROP TRIGGER IF EXISTS on_closed_trade_delete;
6 |
7 | DROP INDEX IF EXISTS open_trades_by_operation_idx;
8 | CREATE INDEX open_trades_by_operation_idx ON trades_opened (timestamp, op_type, operation_id);
9 |
10 | DROP TRIGGER dividends_after_delete;
11 |
12 | DROP TRIGGER IF EXISTS dividends_after_delete;
13 | CREATE TRIGGER dividends_after_delete
14 | AFTER DELETE ON dividends
15 | FOR EACH ROW
16 | WHEN (SELECT value FROM settings WHERE id = 1)
17 | BEGIN
18 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp;
19 | DELETE FROM trades_opened WHERE timestamp >= OLD.timestamp;
20 | END;
21 |
22 | DROP TRIGGER IF EXISTS dividends_after_insert;
23 | CREATE TRIGGER dividends_after_insert
24 | AFTER INSERT ON dividends
25 | FOR EACH ROW
26 | WHEN (SELECT value FROM settings WHERE id = 1)
27 | BEGIN
28 | DELETE FROM ledger WHERE timestamp >= NEW.timestamp;
29 | DELETE FROM trades_opened WHERE timestamp >= NEW.timestamp;
30 | END;
31 |
32 | DROP TRIGGER IF EXISTS dividends_after_update;
33 | CREATE TRIGGER dividends_after_update
34 | AFTER UPDATE OF timestamp, type, account_id, asset_id, amount, tax ON dividends
35 | FOR EACH ROW
36 | WHEN (SELECT value FROM settings WHERE id = 1)
37 | BEGIN
38 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp OR timestamp >= NEW.timestamp;
39 | DELETE FROM trades_opened WHERE timestamp >= OLD.timestamp OR timestamp >= NEW.timestamp;
40 | END;
41 | --------------------------------------------------------------------------------
42 | PRAGMA foreign_keys = 1;
43 | --------------------------------------------------------------------------------
44 | -- Set new DB schema version
45 | UPDATE settings SET value=51 WHERE name='SchemaVersion';
46 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (7, 'RebuildDB', 1);
47 | COMMIT;
48 | -- Reduce file size
49 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_52.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = 0;
4 | --------------------------------------------------------------------------------
5 | DROP TABLE IF EXISTS term_deposits;
6 | CREATE TABLE term_deposits (
7 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
8 | op_type INTEGER NOT NULL DEFAULT (6),
9 | account_id INTEGER NOT NULL REFERENCES accounts (id) ON DELETE CASCADE ON UPDATE CASCADE,
10 | note TEXT
11 | );
12 | DROP TABLE IF EXISTS deposit_actions;
13 | CREATE TABLE deposit_actions (
14 | id INTEGER PRIMARY KEY UNIQUE NOT NULL,
15 | deposit_id INTEGER REFERENCES term_deposits (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
16 | timestamp INTEGER NOT NULL,
17 | action_type INTEGER NOT NULL,
18 | amount TEXT NOT NULL
19 | );
20 | DROP INDEX IF EXISTS deposit_actions_idx;
21 | CREATE UNIQUE INDEX deposit_actions_idx ON deposit_actions (deposit_id, timestamp, action_type);
22 |
23 |
24 | DROP TRIGGER IF EXISTS deposit_action_after_delete;
25 | CREATE TRIGGER deposit_action_after_delete
26 | AFTER DELETE ON deposit_actions
27 | FOR EACH ROW
28 | WHEN (SELECT value FROM settings WHERE id = 1)
29 | BEGIN
30 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp;
31 | END;
32 |
33 | DROP TRIGGER IF EXISTS deposit_action_after_insert;
34 | CREATE TRIGGER deposit_action_after_insert
35 | AFTER INSERT ON deposit_actions
36 | FOR EACH ROW
37 | WHEN (SELECT value FROM settings WHERE id = 1)
38 | BEGIN
39 | DELETE FROM ledger WHERE timestamp >= NEW.timestamp;
40 | END;
41 |
42 | DROP TRIGGER IF EXISTS deposit_action_after_update;
43 | CREATE TRIGGER deposit_action_after_update
44 | AFTER UPDATE OF timestamp, account_id, type, asset_id, qty ON deposit_actions
45 | FOR EACH ROW
46 | WHEN (SELECT value FROM settings WHERE id = 1)
47 | BEGIN
48 | DELETE FROM ledger WHERE timestamp >= OLD.timestamp OR timestamp >= NEW.timestamp;
49 | END;
50 |
51 |
52 | DROP VIEW IF EXISTS operation_sequence;
53 | CREATE VIEW operation_sequence AS
54 | SELECT m.op_type, m.id, m.timestamp, m.account_id, subtype
55 | FROM
56 | (
57 | SELECT op_type, 1 AS seq, id, timestamp, account_id, 0 AS subtype FROM actions
58 | UNION ALL
59 | SELECT op_type, 2 AS seq, id, timestamp, account_id, type AS subtype FROM dividends
60 | UNION ALL
61 | SELECT op_type, 3 AS seq, id, timestamp, account_id, type AS subtype FROM asset_actions
62 | UNION ALL
63 | SELECT op_type, 4 AS seq, id, timestamp, account_id, 0 AS subtype FROM trades
64 | UNION ALL
65 | SELECT op_type, 5 AS seq, id, withdrawal_timestamp AS timestamp, withdrawal_account AS account_id, -1 AS subtype FROM transfers
66 | UNION ALL
67 | SELECT op_type, 5 AS seq, id, withdrawal_timestamp AS timestamp, fee_account AS account_id, 0 AS subtype FROM transfers WHERE NOT fee IS NULL
68 | UNION ALL
69 | SELECT op_type, 5 AS seq, id, deposit_timestamp AS timestamp, deposit_account AS account_id, 1 AS subtype FROM transfers
70 | UNION ALL
71 | SELECT td.op_type, 6 AS seq, td.id, da.timestamp, td.account_id, da.id AS subtype FROM deposit_actions AS da LEFT JOIN term_deposits AS td ON da.deposit_id=td.id WHERE da.action_type<=100
72 | ) AS m
73 | ORDER BY m.timestamp, m.seq, m.subtype, m.id;
74 | --------------------------------------------------------------------------------
75 | PRAGMA foreign_keys = 1;
76 | --------------------------------------------------------------------------------
77 | -- Set new DB schema version
78 | UPDATE settings SET value=52 WHERE name='SchemaVersion';
79 | COMMIT;
80 | -- Reduce file size
81 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_54.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | DELETE FROM quotes WHERE quote='NaN';
4 | --------------------------------------------------------------------------------
5 | -- Set new DB schema version
6 | UPDATE settings SET value=54 WHERE name='SchemaVersion';
7 | COMMIT;
8 | -- Reduce file size
9 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_55.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (20, 'DlgGeometry_Accounts', '');
4 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (21, 'DlgViewState_Accounts', '');
5 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (22, 'DlgGeometry_Assets', '');
6 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (23, 'DlgViewState_Assets', '');
7 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (24, 'DlgGeometry_Peers', '');
8 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (25, 'DlgViewState_Peers', '');
9 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (26, 'DlgGeometry_Categories', '');
10 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (27, 'DlgViewState_Categories', '');
11 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (28, 'DlgGeometry_Tags', '');
12 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (29, 'DlgViewState_Tags', '');
13 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (30, 'DlgGeometry_Quotes', '');
14 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (31, 'DlgViewState_Quotes', '');
15 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (32, 'DlgGeometry_Base currency', '');
16 | INSERT OR REPLACE INTO settings(id, name, value) VALUES (33, 'DlgViewState_Base currency', '');
17 | --------------------------------------------------------------------------------
18 | -- Set new DB schema version
19 | UPDATE settings SET value=55 WHERE name='SchemaVersion';
20 | COMMIT;
21 | -- Reduce file size
22 | VACUUM;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_58.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | PRAGMA foreign_keys = OFF;
4 | --------------------------------------------------------------------------------
5 | UPDATE trades SET number='' WHERE number IS NULL;
6 | UPDATE trades SET note='' WHERE note IS NULL;
7 | -- Forbid NULL settlement and fee
8 | CREATE TABLE temp_trades AS SELECT * FROM trades;
9 | DROP TABLE IF EXISTS trades;
10 | CREATE TABLE trades (
11 | oid INTEGER PRIMARY KEY UNIQUE NOT NULL,
12 | otype INTEGER NOT NULL DEFAULT (3),
13 | timestamp INTEGER NOT NULL,
14 | settlement INTEGER NOT NULL DEFAULT (0),
15 | number TEXT NOT NULL DEFAULT (''),
16 | account_id INTEGER REFERENCES accounts (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
17 | asset_id INTEGER REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL,
18 | qty TEXT NOT NULL DEFAULT ('0'),
19 | price TEXT NOT NULL DEFAULT ('0'),
20 | fee TEXT NOT NULL DEFAULT ('0'),
21 | note TEXT NOT NULL DEFAULT ('')
22 | );
23 | INSERT INTO trades (oid, otype, timestamp, settlement, number, account_id, asset_id, qty, price, fee, note)
24 | SELECT oid, otype, timestamp, settlement, number, account_id, asset_id, qty, price, fee, note FROM temp_trades;
25 | DROP TABLE temp_trades;
26 | CREATE TRIGGER trades_after_delete AFTER DELETE ON trades FOR EACH ROW BEGIN DELETE FROM ledger WHERE timestamp >= OLD.timestamp; DELETE FROM trades_opened WHERE timestamp >= OLD.timestamp; END;
27 | CREATE TRIGGER trades_after_insert AFTER INSERT ON trades FOR EACH ROW BEGIN DELETE FROM ledger WHERE timestamp >= NEW.timestamp; DELETE FROM trades_opened WHERE timestamp >= NEW.timestamp; END;
28 | CREATE TRIGGER trades_after_update AFTER UPDATE OF timestamp, account_id, asset_id, qty, price, fee ON trades FOR EACH ROW BEGIN DELETE FROM ledger WHERE timestamp >= OLD.timestamp OR timestamp >= NEW.timestamp; DELETE FROM trades_opened WHERE timestamp >= OLD.timestamp OR timestamp >= NEW.timestamp; END;
29 | --------------------------------------------------------------------------------
30 | DROP INDEX IF EXISTS ledger_by_operation;
31 | CREATE INDEX ledger_by_operation ON ledger (otype, oid, opart, book_account);
32 | DROP INDEX IF EXISTS ledger_by_time;
33 | CREATE INDEX ledger_by_time ON ledger (timestamp, asset_id, account_id);
34 | --------------------------------------------------------------------------------
35 | -- Set new DB schema version
36 | UPDATE settings SET value=58 WHERE name='SchemaVersion';
37 | INSERT OR REPLACE INTO settings(name, value) VALUES ('RebuildDB', 1);
38 | COMMIT;
--------------------------------------------------------------------------------
/jal/updates/jal_delta_59.sql:
--------------------------------------------------------------------------------
1 | BEGIN TRANSACTION;
2 | --------------------------------------------------------------------------------
3 | ALTER TABLE accounts ADD COLUMN credit TEXT DEFAULT ('0') NOT NULL;
4 | --------------------------------------------------------------------------------
5 | INSERT OR REPLACE INTO settings(name, value) VALUES('ShowInactiveAccountBalances', 0);
6 | INSERT OR REPLACE INTO settings(name, value) VALUES('UseAccountCreditLimit', 1);
7 | --------------------------------------------------------------------------------
8 | -- Set new DB schema version
9 | UPDATE settings SET value=59 WHERE name='SchemaVersion';
10 | COMMIT;
--------------------------------------------------------------------------------
/jal/widgets/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by find_packages() in setup.py
--------------------------------------------------------------------------------
/jal/widgets/custom/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/jal/widgets/custom/__init__.py
--------------------------------------------------------------------------------
/jal/widgets/custom/db_lookup_combobox.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import Qt, Property
2 | from PySide6.QtWidgets import QComboBox
3 | from jal.db.db import JalModel
4 |
5 |
6 | # Combobox to lookup in db tables:
7 | # It is mandatory to set up 'table', 'key_field' and 'field' properties at design time
8 | class DbLookupComboBox(QComboBox):
9 | def __init__(self, parent=None):
10 | super().__init__(parent=parent)
11 | self._model = None
12 | self._table = ''
13 | self._key_field = ''
14 | self._field = ''
15 | self._selected_id = -1
16 |
17 | def getKey(self):
18 | if self._model is None:
19 | return 0
20 | return self._model.get_value(self._key_field, self._field, self.currentText())
21 |
22 | def setKey(self, selected_id):
23 | if self._selected_id == selected_id:
24 | return
25 | self._selected_id = selected_id
26 | value = self._model.get_value(self._field, self._key_field, selected_id)
27 | self.setCurrentIndex(self.findText(value))
28 |
29 | def getTable(self):
30 | return self._table
31 |
32 | def setTable(self, table):
33 | if self._table == table:
34 | return
35 | self._table = table
36 | self.setupDb()
37 |
38 | def getKeyField(self):
39 | return self._key_field
40 |
41 | def setKeyField(self, field_name):
42 | if self._key_field == field_name:
43 | return
44 | self._key_field = field_name
45 |
46 | def getField(self):
47 | return self._field
48 |
49 | def setField(self, field_name):
50 | if self._field == field_name:
51 | return
52 | self._field = field_name
53 | self.setupDb()
54 |
55 | key = Property(int, getKey, setKey, user=True)
56 | db_table = Property(str, getTable, setTable)
57 | key_field = Property(str, getKeyField, setKeyField)
58 | db_field = Property(str, getField, setField)
59 |
60 | def setupDb(self):
61 | if not self._table or not self._field:
62 | return
63 | self._model = JalModel(self, self._table)
64 | field_idx = self._model.fieldIndex(self._field)
65 | self._model.setSort(field_idx, Qt.AscendingOrder)
66 | self._model.select()
67 | self.setModel(self._model)
68 | self.setModelColumn(field_idx)
69 |
--------------------------------------------------------------------------------
/jal/widgets/custom/tableview_with_footer.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import QAbstractItemModel
2 | from PySide6.QtWidgets import QTableView
3 | from PySide6.QtGui import QResizeEvent
4 | from .table_footer import FooterView
5 |
6 | # ----------------------------------------------------------------------------------------------------------------------
7 | # File implements TableViewWithFooter class that is a descendant of QTableView class with footer.
8 | # Underlying model should support footerData(section, role) method in order to provide data for the footer.
9 | # The footer is implemented as FooterView class that is derived from QHeaderView.
10 | # ----------------------------------------------------------------------------------------------------------------------
11 | class TableViewWithFooter(QTableView):
12 | def __init__(self, parent_view):
13 | self._parent_view = parent_view
14 | super().__init__(parent_view)
15 | self._footer = FooterView(self, self.horizontalHeader())
16 |
17 | def footer(self) -> FooterView:
18 | return self._footer
19 |
20 | # Create a bottom margin for footer placement (mirror of a top header margin)
21 | def resizeEvent(self, event: QResizeEvent) -> None:
22 | super().resizeEvent(event)
23 | margins = self.viewportMargins()
24 | self.setViewportMargins(margins.left(), margins.top(), margins.right(), margins.top())
25 | self._footer.on_header_geometry()
26 |
27 | def setModel(self, model: QAbstractItemModel) -> None:
28 | super().setModel(model)
29 | self._footer.setModel(model)
30 |
31 | def setColumnHidden(self, idx: int, hidden: bool) -> None:
32 | super().setColumnHidden(idx, hidden)
33 | self._footer.setSectionHidden(idx, hidden)
34 |
--------------------------------------------------------------------------------
/jal/widgets/custom/treeview_with_footer.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import QAbstractItemModel
2 | from PySide6.QtWidgets import QTreeView
3 | from PySide6.QtGui import QResizeEvent
4 | from .table_footer import FooterView
5 |
6 | # ----------------------------------------------------------------------------------------------------------------------
7 | # File implements TableViewWithFooter class that is a descendant of QTableView class with footer.
8 | # Underlying model should support footerData(section, role) method in order to provide data for the footer.
9 | # The footer is implemented as FooterView class that is derived from QHeaderView.
10 | # ----------------------------------------------------------------------------------------------------------------------
11 | class TreeViewWithFooter(QTreeView):
12 | def __init__(self, parent_view):
13 | self._parent_view = parent_view
14 | super().__init__(parent_view)
15 | self._footer = FooterView(self, self.header())
16 |
17 | def footer(self) -> FooterView:
18 | return self._footer
19 |
20 | # Create a bottom margin for footer placement (mirror of a top header margin)
21 | def resizeEvent(self, event: QResizeEvent) -> None:
22 | super().resizeEvent(event)
23 | margins = self.viewportMargins()
24 | self.setViewportMargins(margins.left(), margins.top(), margins.right(), margins.top())
25 | self._footer.on_header_geometry()
26 |
27 | def setModel(self, model: QAbstractItemModel) -> None:
28 | super().setModel(model)
29 | self._footer.setModel(model)
30 |
31 | def setColumnHidden(self, idx: int, hidden: bool) -> None:
32 | super().setColumnHidden(idx, hidden)
33 | self._footer.setSectionHidden(idx, hidden)
34 |
--------------------------------------------------------------------------------
/jal/widgets/mdi.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import Slot, Signal
2 | from PySide6.QtWidgets import QWidget, QMdiArea, QTabBar, QVBoxLayout
3 |
4 |
5 | # ----------------------------------------------------------------------------------------------------------------------
6 | # Base class that is used for any other widget which should be displayed inside JAL MainWindow MDI
7 | # implemented as TabbedMdiArea() class (below)
8 | class MdiWidget(QWidget):
9 | onClose = Signal(QWidget)
10 |
11 | def __init__(self, parent=None):
12 | super().__init__(parent)
13 |
14 | @Slot()
15 | def closeEvent(self, event):
16 | self.onClose.emit(self.parent())
17 | super().closeEvent(event)
18 |
19 | def refresh(self):
20 | pass
21 |
22 |
23 | # ----------------------------------------------------------------------------------------------------------------------
24 | # Class that acts as QMdiArea in SubWindowView mode but has Tabs at the same time
25 | # Child windows should be derived from MdiWidget class for correct operation
26 | class TabbedMdiArea(QWidget):
27 | def __init__(self, parent=None):
28 | super().__init__(parent=parent)
29 |
30 | self.layout = QVBoxLayout(self)
31 | self.layout.setContentsMargins(0, 0, 0, 0)
32 | self.layout.setSpacing(0)
33 | self.setLayout(self.layout)
34 |
35 | self.mdi = QMdiArea(self)
36 | self.mdi.setOption(QMdiArea.DontMaximizeSubWindowOnActivation)
37 | self.layout.addWidget(self.mdi)
38 |
39 | self.tabs = QTabBar(self)
40 | self.tabs.setShape(QTabBar.RoundedSouth)
41 | self.tabs.setExpanding(False)
42 | self.tabs.setTabsClosable(True)
43 | self.layout.addWidget(self.tabs)
44 |
45 | self.mdi.subWindowActivated.connect(self.subWindowActivated)
46 | self.tabs.currentChanged.connect(self.tabClicked)
47 | self.tabs.tabCloseRequested.connect(self.tabClose)
48 |
49 | def subWindowList(self, order=QMdiArea.CreationOrder):
50 | return self.mdi.subWindowList(order)
51 |
52 | def addSubWindow(self, widget, maximized=False, size=None):
53 | sub_window = self.mdi.addSubWindow(widget)
54 | widget.onClose.connect(self.subWindowClosed)
55 | self.tabs.addTab(sub_window.windowTitle().replace('&', '&&')) # & -> && to prevent shortcut creation
56 | if maximized:
57 | sub_window.showMaximized()
58 | else: # show centered otherwise
59 | if size is None:
60 | w = sub_window.width()
61 | h = sub_window.height()
62 | else:
63 | w = size[0]
64 | h = size[1]
65 | x = self.mdi.x() + self.mdi.width() / 2 - w / 2
66 | y = self.mdi.y() + self.mdi.height() / 2 - h / 2
67 | sub_window.setGeometry(x, y, w, h)
68 | sub_window.show()
69 | return sub_window
70 |
71 | @Slot()
72 | def subWindowActivated(self, window):
73 | if window is not None:
74 | self.tabs.setCurrentIndex(self.mdi.subWindowList().index(window))
75 |
76 | @Slot()
77 | def subWindowClosed(self, window):
78 | index = self.subWindowList().index(window)
79 | self.tabs.removeTab(index)
80 |
81 | @Slot()
82 | def tabClicked(self, index):
83 | try:
84 | sub_window = self.subWindowList()[index]
85 | except IndexError:
86 | return
87 | self.mdi.setActiveSubWindow(sub_window)
88 |
89 | @Slot()
90 | def tabClose(self, index):
91 | try:
92 | sub_window = self.subWindowList()[index]
93 | except KeyError:
94 | return
95 | self.mdi.removeSubWindow(sub_window)
96 | self.tabs.removeTab(index)
97 |
--------------------------------------------------------------------------------
/jal/widgets/operations_tabs.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import Signal, Slot
2 | from PySide6.QtWidgets import QWidget, QStackedWidget, QMessageBox
3 |
4 | from jal.widgets.corporate_action_widget import CorporateActionWidget
5 | from jal.widgets.asset_payment_widget import AssetPaymentWidget
6 | from jal.widgets.income_spending_widget import IncomeSpendingWidget
7 | from jal.widgets.trade_widget import TradeWidget
8 | from jal.widgets.transfer_widget import TransferWidget
9 | from jal.widgets.term_deposit_widget import TermDepositWidget
10 | from jal.db.operations import LedgerTransaction
11 |
12 |
13 | class JalOperationsTabs(QStackedWidget):
14 | dbUpdated = Signal()
15 |
16 | def __init__(self, parent):
17 | super().__init__(parent)
18 | self.widgets = {LedgerTransaction.NA: QWidget(self),
19 | LedgerTransaction.IncomeSpending: IncomeSpendingWidget(self),
20 | LedgerTransaction.AssetPayment: AssetPaymentWidget(self),
21 | LedgerTransaction.Trade: TradeWidget(self),
22 | LedgerTransaction.Transfer: TransferWidget(self),
23 | LedgerTransaction.CorporateAction: CorporateActionWidget(self),
24 | LedgerTransaction.TermDeposit: TermDepositWidget(self)}
25 | for key, widget in self.widgets.items():
26 | if key != LedgerTransaction.NA:
27 | widget.dbUpdated.connect(self.dbUpdated)
28 | self.addWidget(widget)
29 | self.setCurrentIndex(0)
30 |
31 | # Returns a dictionary of {type, name} of operations that widget is able to handle
32 | def get_operations_list(self) -> dict:
33 | operations = {}
34 | for key, widget in self.widgets.items():
35 | if hasattr(widget, "name"):
36 | operations[key] = widget.name
37 | return operations
38 |
39 | def _check_for_changes(self):
40 | for key, widget in self.widgets.items():
41 | if key == LedgerTransaction.NA:
42 | continue
43 | if widget.modified:
44 | reply = QMessageBox().warning(self, self.tr("You have unsaved changes"),
45 | widget.name +
46 | self.tr(" has uncommitted changes,\ndo you want to save it?"),
47 | QMessageBox.Yes, QMessageBox.No)
48 | if reply == QMessageBox.Yes:
49 | widget.saveChanges()
50 | else:
51 | widget.revertChanges()
52 |
53 | def show_operation(self, otype, oid):
54 | self._check_for_changes()
55 | self.setCurrentIndex(otype)
56 | if otype != LedgerTransaction.NA:
57 | self.widgets[otype].set_id(oid)
58 |
59 | def new_operation(self, otype, account_id):
60 | self._check_for_changes()
61 | self.widgets[otype].createNew(account_id=account_id)
62 | self.setCurrentIndex(otype)
63 |
64 | @Slot()
65 | def copy_operation(self):
66 | otype = self.currentIndex()
67 | if otype == LedgerTransaction.NA:
68 | return
69 | self._check_for_changes()
70 | self.widgets[otype].copyNew()
71 |
--------------------------------------------------------------------------------
/jal/widgets/selection_dialog.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import Slot
2 | from PySide6.QtWidgets import QDialog, QMessageBox
3 | from jal.ui.ui_select_reference_dlg import Ui_SelectReferenceDlg
4 | from jal.widgets.helpers import center_window
5 | from jal.widgets.reference_selector import PeerSelector
6 | from jal.widgets.reference_selector import CategorySelector
7 | from jal.widgets.reference_selector import TagSelector
8 |
9 |
10 | #-----------------------------------------------------------------------------------------------------------------------
11 | # Common base GUI dialog class for selector dialogs. Takes window title and label comment to describe selection
12 | class SelectReferenceDialog(QDialog):
13 | def __init__(self, parent=None, title='', description=''):
14 | super().__init__(parent=parent)
15 | self.ui = Ui_SelectReferenceDlg()
16 | self.ui.setupUi(self)
17 | self.selected_id = 0
18 | self.setWindowTitle(title)
19 | self.ui.DescriptionLabel.setText(description)
20 | center_window(self)
21 |
22 | @Slot()
23 | def closeEvent(self, event):
24 | if self.selected_id == 0:
25 | QMessageBox().warning(None, self.tr("No selection"), self.tr("You should select something"), QMessageBox.Ok)
26 | event.ignore()
27 | return
28 | self.setResult(QDialog.Accepted)
29 | event.accept()
30 |
31 |
32 | #-----------------------------------------------------------------------------------------------------------------------
33 | # Dialog for peer selection
34 | # Constructor takes description to show and default_peer for initial choice
35 | class SelectPeerDialog(SelectReferenceDialog):
36 | def __init__(self, description, default_peer=0):
37 | super().__init__(title=self.tr("Please select peer"), description=description)
38 | self.PeerWidget = PeerSelector(self.ui.SelectorFrame)
39 | self.ui.FrameLayout.addWidget(self.PeerWidget)
40 | self.PeerWidget.selected_id = self.selected_id = default_peer
41 |
42 | @Slot()
43 | def closeEvent(self, event):
44 | self.selected_id = self.PeerWidget.selected_id
45 | super().closeEvent(event)
46 |
47 |
48 | #-----------------------------------------------------------------------------------------------------------------------
49 | # Dialog for category selection
50 | # Constructor takes description to show and default_category for initial choice
51 | class SelectCategoryDialog(SelectReferenceDialog):
52 | def __init__(self, parent=None, description='', default_category=0):
53 | super().__init__(parent, title=self.tr("Please select category"), description=description)
54 | self.CategoryWidget = CategorySelector(self.ui.SelectorFrame)
55 | self.ui.FrameLayout.addWidget(self.CategoryWidget)
56 | self.CategoryWidget.selected_id = self.selected_id = default_category
57 |
58 | @Slot()
59 | def closeEvent(self, event):
60 | self.selected_id = self.CategoryWidget.selected_id
61 | super().closeEvent(event)
62 |
63 |
64 | #-----------------------------------------------------------------------------------------------------------------------
65 | # Dialog for tag selection
66 | # Constructor takes description to show and default_tag for initial choice
67 | class SelectTagDialog(SelectReferenceDialog):
68 | def __init__(self, parent=None, description='', default_tag=0):
69 | super().__init__(parent, title=self.tr("Please select tag"), description=description)
70 | self.TagWidget = TagSelector(self.ui.SelectorFrame)
71 | self.ui.FrameLayout.addWidget(self.TagWidget)
72 | self.TagWidget.selected_id = self.selected_id = default_tag
73 |
74 | @Slot()
75 | def closeEvent(self, event):
76 | self.selected_id = self.TagWidget.selected_id
77 | super().closeEvent(event)
78 |
--------------------------------------------------------------------------------
/jal/widgets/trade_widget.py:
--------------------------------------------------------------------------------
1 | from jal.ui.widgets.ui_trade_operation import Ui_TradeOperation
2 | from jal.widgets.abstract_operation_details import AbstractOperationDetails
3 | from jal.widgets.delegates import WidgetMapperDelegateBase
4 | from jal.db.operations import LedgerTransaction
5 | from jal.db.helpers import now_ts
6 |
7 |
8 | # ----------------------------------------------------------------------------------------------------------------------
9 | class TradeWidgetDelegate(WidgetMapperDelegateBase):
10 | def __init__(self, parent=None):
11 | super().__init__(parent=parent)
12 | self.delegates = {'timestamp': self.timestamp_delegate,
13 | 'settlement': self.timestamp_delegate,
14 | 'asset_id': self.symbol_delegate,
15 | 'qty': self.decimal_long_delegate,
16 | 'price': self.decimal_long_delegate,
17 | 'fee': self.decimal_long_delegate}
18 |
19 |
20 | # ----------------------------------------------------------------------------------------------------------------------
21 | class TradeWidget(AbstractOperationDetails):
22 | def __init__(self, parent=None):
23 | super().__init__(parent=parent, ui_class=Ui_TradeOperation)
24 | self.operation_type = LedgerTransaction.Trade
25 | super()._init_db("trades")
26 | self.ui.timestamp_editor.setFixedWidth(self.ui.timestamp_editor.fontMetrics().horizontalAdvance("00/00/0000 00:00:00") * 1.25)
27 | self.ui.settlement_editor.setFixedWidth(self.ui.settlement_editor.fontMetrics().horizontalAdvance("00/00/0000") * 1.5)
28 |
29 | self.mapper.setItemDelegate(TradeWidgetDelegate(self.mapper))
30 |
31 | self.ui.account_widget.changed.connect(self.mapper.submit)
32 | self.ui.asset_widget.changed.connect(self.mapper.submit)
33 |
34 | self.mapper.addMapping(self.ui.timestamp_editor, self.model.fieldIndex("timestamp"))
35 | self.mapper.addMapping(self.ui.settlement_editor, self.model.fieldIndex("settlement"))
36 | self.mapper.addMapping(self.ui.account_widget, self.model.fieldIndex("account_id"))
37 | self.mapper.addMapping(self.ui.currency_price, self.model.fieldIndex("account_id"))
38 | self.mapper.addMapping(self.ui.currency_fee, self.model.fieldIndex("account_id"))
39 | self.mapper.addMapping(self.ui.asset_widget, self.model.fieldIndex("asset_id"))
40 | self.mapper.addMapping(self.ui.number, self.model.fieldIndex("number"))
41 | self.mapper.addMapping(self.ui.qty_edit, self.model.fieldIndex("qty"))
42 | self.mapper.addMapping(self.ui.price_edit, self.model.fieldIndex("price"))
43 | self.mapper.addMapping(self.ui.fee_edit, self.model.fieldIndex("fee"))
44 | self.mapper.addMapping(self.ui.note, self.model.fieldIndex("note"))
45 |
46 | self.model.select()
47 |
48 | def prepareNew(self, account_id):
49 | new_record = super().prepareNew(account_id)
50 | new_record.setValue("timestamp", now_ts())
51 | new_record.setValue("settlement", now_ts())
52 | new_record.setValue("number", '')
53 | new_record.setValue("account_id", account_id)
54 | new_record.setValue("asset_id", 0)
55 | new_record.setValue("qty", '0')
56 | new_record.setValue("price", '0')
57 | new_record.setValue("fee", '0')
58 | new_record.setValue("note", None)
59 | return new_record
60 |
61 | def copyToNew(self, row):
62 | new_record = self.model.record(row)
63 | new_record.setNull("oid")
64 | new_record.setValue("timestamp", now_ts())
65 | new_record.setValue("settlement", now_ts())
66 | new_record.setValue("number", '')
67 | return new_record
--------------------------------------------------------------------------------
/register_designer_plugins.py:
--------------------------------------------------------------------------------
1 | # Environment variable PYSIDE_DESIGNER_PLUGINS should contain path to this file
2 |
3 | from designer_plugins.plugin_date_range_selector import DateRangeSelectorPlugin
4 | from designer_plugins.plugin_db_lookup_combobox import DbLookupComboBoxPlugin
5 | from designer_plugins.plugin_log_viewer import LogViewerPlugin
6 | from designer_plugins.plugin_tableview_with_footer import TableViewWithFooterPlugin
7 | from designer_plugins.plugin_treeview_with_footer import TreeViewWithFooterPlugin
8 |
9 | from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
10 |
11 | if __name__ == '__main__':
12 | QPyDesignerCustomWidgetCollection.addCustomWidget(DateRangeSelectorPlugin())
13 | QPyDesignerCustomWidgetCollection.addCustomWidget(DbLookupComboBoxPlugin())
14 | QPyDesignerCustomWidgetCollection.addCustomWidget(LogViewerPlugin())
15 | QPyDesignerCustomWidgetCollection.addCustomWidget(TableViewWithFooterPlugin())
16 | QPyDesignerCustomWidgetCollection.addCustomWidget(TreeViewWithFooterPlugin())
17 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | lxml>=4.5.0
2 | pandas>=1.1.1
3 | PySide6>=6.5.1
4 | requests>=2.24.0
5 | XlsxWriter>=1.3.3
6 | pytest
7 | jsonschema
8 | sqlparse
9 | oauthlib
10 | requests-oauthlib
11 | setuptools
12 | packaging
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import faulthandler
3 | from jal.jal import main
4 |
5 | if __name__ == "__main__":
6 | log_file_fd = open("jal_faults.log", "a")
7 | faulthandler.enable(log_file_fd)
8 | main()
9 |
10 | # Below code is the same but initiates application via entry point defined in already installed package
11 | # from importlib.metadata import distribution
12 | #
13 | # main_entry = distribution('jal').entry_points[0].load()
14 | #
15 | # main_entry()
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | from setuptools import setup, find_packages
3 |
4 |
5 | def read(rel_path: str) -> str:
6 | here = os.path.abspath(os.path.dirname(__file__))
7 | with open(os.path.join(here, rel_path), 'r', encoding='utf-8') as fp:
8 | return fp.read()
9 |
10 |
11 | def get_version(rel_path: str) -> str:
12 | for line in read(rel_path).splitlines():
13 | if line.startswith('__version__'):
14 | quote_char = '"' if '"' in line else "'"
15 | return line.split(quote_char)[1]
16 | raise RuntimeError("Unable to find version string.")
17 |
18 |
19 | setup(
20 | name="jal",
21 | version=get_version("jal/__init__.py"),
22 | author_email="jal@gmx.ru",
23 | description="Just Another Ledger - project to track personal financial records",
24 | long_description_content_type='text/markdown',
25 | long_description=read('jal/pypi_description.md'),
26 | packages=find_packages(),
27 | package_dir={'jal': 'jal'},
28 | python_requires=">=3.8.1",
29 | url="https://github.com/titov-vv/jal",
30 | classifiers=[
31 | "Development Status :: 3 - Alpha",
32 | "Topic :: Office/Business",
33 | "Topic :: Office/Business :: Financial",
34 | "Topic :: Office/Business :: Financial :: Accounting",
35 | "Topic :: Office/Business :: Financial :: Investment",
36 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
37 | "Operating System :: OS Independent",
38 | "Programming Language :: Python"
39 | ],
40 | install_requires=["lxml", "pandas", "PySide6>=6.5.1", "requests>=2.24", "XlsxWriter>=1.3.3", "jsonschema", "sqlparse", "oauthlib", "requests-oauthlib", "setuptools"],
41 | entry_points={
42 | 'console_scripts': ['jal=jal.jal:main', ]
43 | },
44 | include_package_data=True,
45 | package_data={
46 | '': ['*.sql', '*.json', 'languages/*.qm', 'languages/*.png', 'pypi_description.md', 'img/*.ico', 'img/*.png']
47 | }
48 | )
49 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # Dummy __init__.py file to facilitate package recognition by pytest
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #
2 | # conftest.py
3 | import sys
4 | from os.path import dirname
5 | from os.path import abspath, join
6 | root_dir = dirname(dirname(abspath(__file__)))
7 | root_dir = join(root_dir, 'jal')
8 | sys.path.append(root_dir)
--------------------------------------------------------------------------------
/tests/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | log_cli=true
3 | qt_api=pyside6
4 |
--------------------------------------------------------------------------------
/tests/test_data/3ndfl_2020.dc0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/3ndfl_2020.dc0
--------------------------------------------------------------------------------
/tests/test_data/3ndfl_2020_empty.dc0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/3ndfl_2020_empty.dc0
--------------------------------------------------------------------------------
/tests/test_data/3ndfl_2021.dc1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/3ndfl_2021.dc1
--------------------------------------------------------------------------------
/tests/test_data/3ndfl_2021_empty.dc1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/3ndfl_2021_empty.dc1
--------------------------------------------------------------------------------
/tests/test_data/3ndfl_2022.dc2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/3ndfl_2022.dc2
--------------------------------------------------------------------------------
/tests/test_data/3ndfl_2022_empty.dc2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/3ndfl_2022_empty.dc2
--------------------------------------------------------------------------------
/tests/test_data/deals_set.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/deals_set.tgz
--------------------------------------------------------------------------------
/tests/test_data/ibkr_bond.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1609459200, 1640995199],
3 | "accounts": [
4 | {"id": 1, "number": "U7654321", "currency": 1, "precision": 10, "cash_begin": 12.345, "cash_end": 1234567.249966388, "cash_end_settled": 1234567.89}
5 | ],
6 | "assets": [
7 | {"id": 1, "type": "money", "name": "USD"},
8 | {"id": 2, "type": "bond", "name": "X 6 1/4 03/15/26", "isin": "US912909AN84"},
9 | {"id": 3, "type": "bond", "name": "X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26", "isin": "US912CALAN84"},
10 | {"id": 4, "type": "stock", "name": "APPLE INC", "isin": "US0378331005"}
11 | ],
12 | "symbols": [
13 | {"id": 1, "asset": 1, "symbol": "USD"},
14 | {"id": 2, "asset": 2, "symbol": "X 6 1/4 03/15/26", "currency": 1},
15 | {"id": 3, "asset": 3, "symbol": "X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26", "currency": 1},
16 | {"id": 4, "asset": 4, "symbol": "AAPL", "currency": 1,"note": "NASDAQ"}
17 | ],
18 | "assets_data": [
19 | {"id": 1, "asset": 2, "reg_number": "912909AN8", "expiry": 1773532800, "principal": 1000},
20 | {"id": 2, "asset": 3, "reg_number": "912CALAN8", "expiry": 1773532800, "principal": 1000},
21 | {"id": 3, "asset": 4, "reg_number": "037833100"}
22 | ],
23 | "trades": [
24 | {"id": 1, "number": "2882737839", "timestamp": 1622131065, "settlement": 1622246400, "account": 1, "asset": 2, "quantity": 2.0, "price": 637.09, "fee": 2.0},
25 | {"id": 2, "number": "TEST_ID", "timestamp": 1631709966, "settlement": 1631836800, "account": 1, "asset": 4, "quantity": 1.0, "price": 123.45, "fee": -0.12},
26 | {"id": 3, "number": "", "timestamp": 1640781240, "settlement": 1640908800, "account": 1, "asset": 2, "quantity": 2.0, "price": 637.09, "fee": 2.0},
27 | {"id": 4, "number": "17849517124", "timestamp": 1632515100, "settlement": 1632515100, "account": 1, "asset": 3, "quantity": -2.0, "price": 1031.25, "fee": 0.0, "note": "(US912CALAN84) BOND MATURITY FOR USD 1.03125 PER BOND (X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26, X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26, US912CALAN84)"}
28 | ],
29 | "corporate_actions": [
30 | {"id": 1, "type": "merger", "account": 1, "timestamp": 1630007100, "number": "17569476329", "asset": 2, "quantity": 2.0,
31 | "outcome": [{"asset": 3, "quantity": 2.0, "share": 0.0}],"description": "X 6 1/4 03/15/26(US912909AN84) TENDERED TO US912CALAN84 1 FOR 1 (X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26, X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26, US912CALAN84)"}
32 | ],
33 | "asset_payments": [
34 | {"id": 1, "type": "interest", "account": 1, "timestamp": 1622131065, "number": "2882737839", "asset": 2, "amount": -25.69, "description": "PURCHASE ACCRUED INT X 6 1/4 03/15/26"},
35 | {"id": 2, "type": "interest", "account": 1, "timestamp": 1631664000, "number": "", "asset": 2, "amount": 62.0, "description": "BOND COUPON PAYMENT (X 6 1/4 03/15/26)"},
36 | {"id": 3, "type": "interest", "account": 1, "timestamp": 1631664000, "number": "TEST_ID", "asset": 2, "amount": 0.5, "description": "BOND COUPON PAYMENT (X 6 1/4 03/15/26)"},
37 | {"id": 4, "type": "interest", "account": 1, "timestamp": 1632700800, "number": "", "asset": 3, "amount": 3.82, "description": "BOND COUPON PAYMENT (X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26)"}
38 | ],
39 | "income_spending": [],
40 | "transfers": []
41 | }
--------------------------------------------------------------------------------
/tests/test_data/ibkr_cfd.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1641168000, 1672703999],
3 | "accounts": [
4 | {"id": 1, "number": "U7654321F", "currency": 1, "precision": 10, "cash_begin": 12.345, "cash_end": 1234567.249966388, "cash_end_settled": 1234567.89}
5 | ],
6 | "assets": [
7 | {"id": 1, "name": "USD", "type": "money"},
8 | {"id": 2, "name": "USD TSLA", "type": "cfd"},
9 | {"id": 3, "name": "USD MU", "type": "cfd"}
10 | ],
11 | "symbols": [
12 | {"id": 1, "asset": 1, "symbol": "USD"},
13 | {"id": 2, "asset": 2, "currency": 1, "symbol": "TSLAn"},
14 | {"id": 3, "asset": 3, "currency": 1, "symbol": "MUn"}
15 | ],
16 | "assets_data": [
17 | {"id": 1, "asset": 2, "expiry": 2093212800},
18 | {"id": 2, "asset": 3, "expiry": 2093212800}
19 | ],
20 | "trades": [
21 | {"id": 1, "number": "200000523", "timestamp": 1645695065, "settlement": 1645833600, "account": 1, "asset": 2, "quantity": -1.0, "price": 901.4, "fee": 1.0},
22 | {"id": 2, "number": "211321719", "timestamp": 1657106512, "settlement": 1657238400, "account": 1, "asset": 3, "quantity": -37.0, "price": 60.074, "fee": 1.0},
23 | {"id": 3, "number": "211326559", "timestamp": 1657110438, "settlement": 1657238400, "account": 1, "asset": 3, "quantity": -1.0, "price": 59.5, "fee": 1.0},
24 | {"id": 4, "number": "211380318", "timestamp": 1657186655, "settlement": 1657497600, "account": 1, "asset": 3, "quantity": 38.0, "price": 58.035, "fee": 1.0}
25 | ],
26 | "corporate_actions": [
27 | {"id": 1, "type": "split", "account": 1, "timestamp": 1661372700, "number": "1031017435", "asset": 2, "quantity": -1.0,
28 | "outcome": [{"asset": 2, "quantity": -3.0, "share": 1.0}], "description": "TSLAn(131067756) SPLIT 3 FOR 1 (TSLAn, USD TSLA, )"}
29 | ],
30 | "asset_payments": [
31 | {"id": 1, "type": "dividend", "account": 1, "timestamp": 1658866800, "ex_date": 1657238400, "number": "", "asset": 3, "amount": -4.37, "description": "MUn(120549262) PAYMENT IN LIEU OF DIVIDEND (ICFDUK Dividend)"}
32 | ],
33 | "income_spending": [
34 | {"id": 1, "timestamp": 1657152000, "account": 1, "peer": 0, "lines": [{"amount": -0.28, "category": -5, "description": "LONG CFD INTEREST FOR 07-JUL-2022"}]},
35 | {"id": 2, "timestamp": 1657065600, "account": 1, "peer": 0, "lines": [{"amount": -0.02, "category": -5, "description": "CFD BORROW FEE FOR MU for 06-JUL-2022"}]},
36 | {"id": 3, "timestamp": 1657065600, "account": 1, "peer": 0, "lines": [{"amount": -0.06, "category": -5, "description": "SHORT CFD INTEREST FOR 06-JUL-2022"}]}
37 | ],
38 | "transfers": []
39 | }
--------------------------------------------------------------------------------
/tests/test_data/ibkr_corp_actions.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1672531200, 1704067199],
3 | "accounts": [
4 | {"id": 1, "number": "U7654321", "currency": 1, "precision": 10, "cash_begin": 12.345, "cash_end": 1234567.249966388, "cash_end_settled": 1234567.89}],
5 | "assets": [
6 | {"id": 1, "type": "money", "name": "USD"},
7 | {"id": 2, "type": "stock", "name": "TWO HANDS CORP", "isin": "US90187E5015"},
8 | {"id": 3, "type": "stock", "name": "TWO HANDS CORP", "isin": "US90187E4026"},
9 | {"id": 4, "type": "bond", "name": "B 01/25/24", "isin": "US912796ZY88"}
10 | ],
11 | "symbols": [
12 | {"id": 1, "asset": 1, "symbol": "USD"},
13 | {"id": 2, "asset": 2, "symbol": "TWOHD", "currency": 1, "note": "PINK"},
14 | {"id": 3, "asset": 3, "symbol": "TWOHD", "currency": 1, "note": "PINK"},
15 | {"id": 4, "asset": 4, "symbol": "912796ZY8", "currency": 1}
16 | ],
17 | "assets_data": [
18 | {"id": 1, "asset": 2, "reg_number": "90187E501"},
19 | {"id": 2, "asset": 3, "reg_number": "90187E402"},
20 | {"id": 3, "asset": 4, "principal": 1000, "expiry": 1706140800, "reg_number": "912796ZY8"}
21 | ],
22 | "asset_payments": [],
23 | "corporate_actions": [
24 | {"id": 1, "type": "split", "account": 1, "timestamp": 1695932700, "number": "25164201871", "asset": 3, "quantity": 35.0,
25 | "outcome": [{"asset": 2, "quantity": 0.035, "share": 1.0}], "description": "TWOH(US90187E4026) SPLIT 1 FOR 1000 (TWOHD, TWO HANDS CORP, US90187E5015)"}
26 | ],
27 | "income_spending": [],
28 | "trades": [
29 | {"id": 1, "number": "26300000000", "timestamp": 1706127900, "settlement": 1706127900, "account": 1, "asset": 4, "quantity": -5.0, "price": 1000.0, "fee": 0.0, "note": "(US912796ZY88) TBILL MATURITY (912796ZY8, B 01/25/24, US912796ZY88)"}
30 | ],
31 | "transfers": []
32 | }
--------------------------------------------------------------------------------
/tests/test_data/ibkr_merger_complex.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1609459200, 1640995199],
3 | "accounts": [
4 | {"id": 1, "number": "U7654321", "currency": 1, "precision": 10, "cash_begin": 0.0, "cash_end": -935.800878369, "cash_end_settled": -935.800878369}
5 | ],
6 | "assets": [
7 | {"id": 1, "type": "money", "name": "USD"},
8 | {"id": 2, "type": "warrant", "name": "DNA 31DEC27 11.5 C", "isin": "US37611X1182"},
9 | {"id": 3, "type": "stock", "name": "GINKGO BIOWORKS HOLDINGS INC", "isin": "US37611X1000"},
10 | {"id": 4, "type": "stock", "name": "SOARING EAGLE ACQUISITION CO", "isin": "KYG8354H1002" }
11 | ],
12 | "symbols": [
13 | {"id": 1, "asset": 1, "symbol": "USD"},
14 | {"id": 2, "asset": 2, "currency": 1, "symbol": "DNA WS", "note": "NYSE"},
15 | {"id": 3, "asset": 3, "currency": 1, "symbol": "DNA", "note": "NYSE"},
16 | {"id": 4, "asset": 4, "currency": 1, "symbol": "SRNGU"}
17 | ],
18 | "assets_data": [
19 | {"id": 1, "asset": 2, "expiry": 1830211200},
20 | {"id": 2, "asset": 3, "reg_number": "37611X100"}
21 | ],
22 | "corporate_actions": [
23 | {"id": 1, "type": "merger", "account": 1, "timestamp": 1631823900, "number": "17747623336", "asset": 4, "quantity": 111.0,
24 | "outcome": [{"asset": 1, "quantity": 136.53, "share": 0.0}, {"asset": 2, "quantity": 22.0, "share": 0.0}, {"asset": 3, "quantity": 111.0, "share": 0.0}],
25 | "description": "SRNGU(G8354H100) CASH and STOCK MERGER (Acquisition) DNA WS 1 FOR 5, US37611X1000 1 FOR 1 AND USD 1.23 (DNA WS, DNA 01AUG26 11.5 C, US37611X1182)"}
26 | ],
27 | "trades": [
28 | {"id": 1, "account": 1, "timestamp": 1631008683, "settlement": 1631145600, "number": "4057396877", "asset": 4, "quantity": 61.0, "price": 10.388, "fee": 0.36245725},
29 | {"id": 2, "account": 1, "timestamp": 1631534345, "settlement": 1631664000, "number": "4072660868", "asset": 4, "quantity": 50.0, "price": 9.86, "fee": 0.36025725},
30 | {"id": 3, "account": 1, "timestamp": 1632483314, "settlement": 1632787200, "number": "4103255040", "asset": 2, "quantity": -22.0, "price": 3.94, "fee": 0.423717318},
31 | {"id": 4, "account": 1, "timestamp": 1633533335, "settlement": 1633651200, "number": "4131958719", "asset": 3, "quantity": -60.0, "price": 10.05, "fee": 0.20447255},
32 | {"id": 5, "account": 1, "timestamp": 1634655039, "settlement": 1634774400, "number": "4160926315", "asset": 3, "quantity": -51.0, "price": 14.35, "fee": 0.309058685}
33 | ],
34 | "income_spending": [],
35 | "asset_payments": [],
36 | "transfers": []
37 | }
--------------------------------------------------------------------------------
/tests/test_data/ibkr_rights.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1609459200, 1640995199],
3 | "accounts": [
4 | {"id": 1, "number": "U1234567", "currency": 1, "precision": 10, "cash_begin": 0.0, "cash_end": 10659.073152, "cash_end_settled": -81667.430698},
5 | {"id": 2, "number": "U1234567", "currency": 2, "precision": 10, "cash_begin": 0.0, "cash_end": -935.800878369, "cash_end_settled": -935.800878369}
6 | ],
7 | "assets": [{"id": 1, "name": "HKD", "type": "money"},
8 | {"id": 2, "name": "USD", "type": "money"},
9 | {"id": 3, "type": "stock", "name": "TONGDA GROUP HOLDINGS LTD", "isin": "KYG8917X1218"},
10 | {"id": 5, "type": "stock", "name": "INTERACTIVE BROKERS GRO-CL A", "isin": "US45841N1072" },
11 | {"id": 6, "type": "stock", "isin": "KYG8917X1RTS"}
12 | ],
13 | "assets_data": [
14 | {"asset": 5, "id": 1, "reg_number": "45841N107"}
15 | ],
16 | "symbols": [{"id": 1, "asset": 1, "symbol": "HKD"},
17 | {"id": 2, "asset": 2, "symbol": "USD"},
18 | {"id": 3, "asset": 3, "currency": 1, "symbol": "698", "note": "SEHK"},
19 | {"id": 5, "asset": 5, "currency": 2, "symbol": "IBKR", "note": "NASDAQ"},
20 | {"id": 6, "asset": 6, "currency": 1, "symbol": "2965", "note": "CORPACT"}
21 | ],
22 | "asset_payments": [
23 | ],
24 | "corporate_actions": [
25 | ],
26 | "income_spending": [],
27 | "trades": [],
28 | "transfers": []}
--------------------------------------------------------------------------------
/tests/test_data/ibkr_spinoff.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1609459200, 1640995199],
3 | "accounts": [
4 | {"id": 1, "number": "U7654321", "currency": 1, "precision": 10, "cash_begin": 0.0, "cash_end": -935.800878369, "cash_end_settled": -935.800878369}
5 | ],
6 | "assets": [
7 | {"id": 1, "type": "money", "name": "USD"},
8 | {"id": 2, "type": "stock", "name": "GENERAL ELECTRIC CO", "isin": "US3696041033"},
9 | {"id": 3, "type": "stock", "name": "WABTEC CORP"}
10 | ],
11 | "symbols": [
12 | {"id": 1, "asset": 1, "symbol": "USD"},
13 | {"id": 2, "asset": 2, "currency": 1, "symbol": "GE", "note": "NYSE"},
14 | {"id": 3, "asset": 3, "currency": 1, "symbol": "WAB", "note": "NYSE"}
15 | ],
16 | "assets_data": [
17 | {"id": 1, "asset": 2, "reg_number": "369604103"},
18 | {"id": 2, "asset": 3, "reg_number": "929740108"}
19 | ],
20 | "corporate_actions": [
21 | {"id": 1, "type": "spin-off", "account": 1, "timestamp": 1637353500, "number": "10302900848", "asset": 2, "quantity": 100,
22 | "outcome": [{"asset": 2, "quantity": 100, "share": 0.0}, {"asset": 3, "quantity": 0.5371, "share": 0.0}],
23 | "description": "GE(US3696041033) SPINOFF 5371 FOR 1000000 (WAB, WABTEC CORP, 929740108)"}
24 | ],
25 | "trades": [
26 | {"id": 1, "account": 1, "timestamp": 1635760683, "settlement": 1635897600, "number": "11111", "asset": 2, "quantity": 100.0, "price": 8.325, "fee": 0.35},
27 | {"id": 2, "account": 1, "timestamp": 1637679039, "settlement": 1637798400, "number": "22222", "asset": 3, "quantity": -0.5371, "price": 1.2, "fee": 0.0},
28 | {"id": 3, "account": 1, "timestamp": 1638370239, "settlement": 1638489600, "number": "33333", "asset": 2, "quantity": -100.0, "price": 7.95, "fee": 0.3}
29 | ],
30 | "income_spending": [],
31 | "asset_payments": [],
32 | "transfers": []
33 | }
--------------------------------------------------------------------------------
/tests/test_data/ibkr_warrants.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1577836800, 1609459199],
3 | "accounts": [
4 | {"id": 1, "number": "U7654321", "currency": 1, "precision": 10, "cash_begin": 12.345, "cash_end": 1234567.249966388, "cash_end_settled": 1234567.89}
5 | ],
6 | "assets": [
7 | {"id": 1, "name": "USD", "type": "money"},
8 | {"id": 2, "type": "warrant", "name": "DNA 31DEC27 11.5 C", "isin": "US37611X1182"},
9 | {"id": 3, "type": "stock", "name": "GINKGO BIOWORKS HOLDINGS INC", "isin": "US37611X1000"},
10 | {"id": 4, "type": "stock", "name": "GINKGO BIOWORKS HOLDINGS INC", "isin": "KYG8354H1002"}
11 | ],
12 | "symbols": [
13 | {"id": 1, "asset": 1, "symbol": "USD"},
14 | {"id": 2, "asset": 2, "currency": 1, "symbol": "DNA WS", "note": "NYSE"},
15 | {"id": 3, "asset": 3, "currency": 1, "symbol": "DNA", "note": "NYSE"},
16 | {"id": 4, "asset": 4, "currency": 1, "symbol": "SRNGU"}
17 | ],
18 | "assets_data": [
19 | {"id": 1, "asset": 2, "expiry": 1830211200},
20 | {"id": 2, "asset": 3, "reg_number": "37611X100"}
21 | ],
22 | "trades": [],
23 | "corporate_actions": [
24 | {"id": 1, "type": "merger", "account": 1, "timestamp": 1631823900, "number": "17747623336", "asset": 4, "quantity": 111.0,
25 | "outcome": [{"asset": 2, "quantity": 22.0, "share": 0.0}, {"asset": 3, "quantity": 111.0, "share": 0.0}],
26 | "description": "SRNGU(G8354H100) MERGED(Acquisition) WITH DNA WS 1 FOR 5, US37611X1000 1 FOR 1 (DNA WS, DNA 01AUG26 11.5 C, US37611X1182)"}
27 | ],
28 | "asset_payments": [],
29 | "income_spending": [],
30 | "transfers": []
31 | }
--------------------------------------------------------------------------------
/tests/test_data/invalid_backup.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/invalid_backup.tgz
--------------------------------------------------------------------------------
/tests/test_data/j2t.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/j2t.xlsx
--------------------------------------------------------------------------------
/tests/test_data/kit.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1617235200, 1621555199],
3 | "accounts": [
4 | {"id": 1, "currency": 1, "number": "12345"},
5 | {"id": 2, "currency": 2, "number": "12345"}
6 | ],
7 | "assets": [
8 | {"id": 1, "type": "money", "name": "RUB"},
9 | {"id": 2, "type": "money", "name": "USD"},
10 | {"id": 3, "type": "stock", "name": "Аэрофлот-росс.авиалин(ПАО)ао", "isin": "RU0009062285"},
11 | {"id": 4, "type": "etf", "name": "FinEx Gold ETF USD", "isin": "IE00B8XB7377"},
12 | {"id": 5, "type": "bond", "name": "АО \"Тинькофф Банк\" БО-07", "isin": "RU000A0JWM31"},
13 | {"id": 6, "type": "stock", "name": "Polymetal International plc", "isin": "JE00B6T5S470"}
14 | ],
15 | "symbols": [
16 | {"id": 1, "asset": 1, "symbol": "RUB"},
17 | {"id": 2, "asset": 2, "symbol": "USD"},
18 | {"id": 3, "asset": 3, "symbol": "AFLT", "currency": 1, "note": "MOEX"},
19 | {"id": 4, "asset": 4, "symbol": "FXGD", "currency": 1, "note": "MOEX"},
20 | {"id": 5, "asset": 5, "symbol": "ТинькоффБ7", "currency": 1, "note": "MOEX"},
21 | {"id": 6, "asset": 6, "symbol": "POLY", "currency": 1, "note": "MOEX"}
22 | ],
23 | "assets_data": [
24 | {"id": 1, "asset": 3, "reg_number": "1-01-00010-A", "principal": 1.0},
25 | {"id": 2, "asset": 5, "reg_number": "4B020702673B", "principal": 1000.0, "expiry": 1624492800},
26 | {"id": 3, "asset": 6, "principal": 0.03}
27 | ],
28 | "trades": [
29 | {"id": 1, "number": "3312348787", "timestamp": 1615647620, "settlement": 1615766400, "account": 1, "asset": 4, "quantity": 10, "price": 987.65, "fee": 6.9},
30 | {"id": 2, "number": "3720000652", "timestamp": 1615909088, "settlement": 1615939200, "account": 1, "asset": 5, "quantity": 1, "price": 1005.7, "fee": 0.5},
31 | {"id": 3, "number": "3725000743", "timestamp": 1615976923, "settlement": 1616025600, "account": 1, "asset": 5, "quantity": -1, "price": 1004.1, "fee": 0.5},
32 | {"id": 4, "number": "3720123978", "timestamp": 1616770797, "settlement": 1616889600, "account": 1, "asset": 6, "quantity": 10, "price": 1532.1, "fee": 8.66}
33 | ],
34 | "income_spending": [
35 | {"id": 1, "timestamp": 1618617600, "account": 1, "peer": 0, "lines": [{"amount": 43.21, "category": -8, "description": "Проценты по договору займа ЦБ ДЗ/16042021-1"}]},
36 | {"id": 2, "timestamp": 1622332800, "account": 1, "peer": 0, "lines": [{"amount": -17.6, "category": -5, "description": "Счет НКО АО НРД за хранение ЦБ №H000/02923468 от 28.05.2021 г."}]},
37 | {"id": 3, "timestamp": 1617148800, "account": 1, "peer": 0, "lines": [{"amount": -200, "category": -5, "description": "Абонентская плата за обслуживание счета 11249"}]}
38 | ],
39 | "transfers": [
40 | {"id": 1, "account": [0, 1, 0], "asset": [1, 1], "timestamp": 1615766400, "withdrawal": 123, "deposit": 123, "fee": 0.0, "description": "зачисление - неторговое поручение от 15.03.2021, НП/в/А/150321/74123"}
41 | ],
42 | "corporate_actions": [],
43 | "asset_payments": [
44 | {"id": 1, "type": "interest", "account": 1, "timestamp": 1615909088, "number": "3720000652", "asset": 5, "amount": -16.83, "description": "НКД"},
45 | {"id": 2, "type": "interest", "account": 1, "timestamp": 1615976923, "number": "3725000743", "asset": 5, "amount": 17.03, "description": "НКД"}
46 | ]
47 | }
--------------------------------------------------------------------------------
/tests/test_data/kit.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/kit.xlsx
--------------------------------------------------------------------------------
/tests/test_data/pof_converted.json:
--------------------------------------------------------------------------------
1 | {
2 | "period": [1528528500, 1656742002],
3 | "accounts": [
4 | {"id": 10, "currency": 0, "type": "investment", "number": "Imported #1"},
5 | {"id": 11, "currency": 69, "number": "Imported #1"},
6 | {"id": 12, "currency": 1, "number": "Imported #1"}
7 | ],
8 | "assets": [
9 | {"id": 0, "type": "money", "name": "Рубль"},
10 | {"id": 1, "type": "money", "name": "Доллар"},
11 | {"id": 44, "type": "stock", "name": "Сбербанк", "isin": "RU0009029540"},
12 | {"id": 45, "type": "stock", "name": "ГАЗПРОМ ао", "isin": "RU0007661625"},
13 | {"id": 46, "type": "bond", "name": "ВЭБ ПБО1Р9", "isin": "RU000A0JXU71"},
14 | {"id": 52, "type": "stock", "name": "Ross Stores Inc", "isin": "US7782961038"},
15 | {"id": 14, "type": "commodity", "name": "GLDRUB_TOM"},
16 | {"id": 69, "type": "money", "name": "HKD"}
17 | ],
18 | "symbols": [
19 | {"id": 1, "asset": 0, "note": "", "symbol": "RUB"},
20 | {"id": 2, "asset": 1, "note": "", "symbol": "USD"},
21 | {"id": 3, "asset": 44, "note": "MOEX", "symbol": "SBER", "currency": 0},
22 | {"id": 4, "asset": 45, "note": "MOEX", "symbol": "GAZP", "currency": 0},
23 | {"id": 5, "asset": 46, "note": "MOEX", "symbol": "RU000A0JXU71", "currency": 0},
24 | {"id": 6, "asset": 52, "note": "SPBEX", "symbol": "ROST", "currency": 0},
25 | {"id": 7, "asset": 14, "note": "MOEX", "symbol": "GLDRUB_TOM", "currency": 0},
26 | {"id": 10, "asset": 69, "symbol": "HKD"}
27 | ],
28 | "assets_data": [
29 |
30 | ],
31 | "trades": [
32 | {"id": 1, "number": "1313074529", "timestamp": 1539637200, "settlement": 0, "account": 10, "asset": 45, "quantity": 150, "price": 167.58000183105, "fee": 0,"note": "\"Газпром\" (ПАО) ао"},
33 | {"id": 2, "number": "3407729597", "timestamp": 1582578000, "settlement": 0, "account": 10, "asset": 44, "quantity": -50, "price": 245, "fee": 0, "note": "Сбербанк России ПАО ао"},
34 | {"id": 3, "number": "465e9b6f2c683de80b4d06480f49b1f1", "timestamp": 1593723600, "settlement": 0, "account": 10, "asset": 44, "quantity": 30, "price": 210.85, "fee": 0, "note": "Сбербанк России ПАО ао"},
35 | {"id": 4, "number": "38159322496", "timestamp": 1722866873, "settlement": 0, "account": 10, "asset": 14, "quantity": 1, "price": 6629.6, "fee": 59.67, "note": "GLD/RUB_TOM - GLD/РУБ"}
36 | ],
37 | "income_spending": [
38 | {"id": 1, "timestamp": 1548104400, "account": 10, "peer": 0, "lines": [{"amount": -149, "category": -5, "description": "Списание комиссий"}]},
39 | {"id": 2, "timestamp": 1562014800, "account": 10, "peer": 0, "lines": [{"amount": 13000, "category": -1, "description": "Налоговый вычет"}]}
40 | ],
41 | "transfers": [
42 | {"id": 1, "account": [10, 11, 10], "number": "PP230319000000069703", "asset": [0, 69], "timestamp": 1723219200, "withdrawal": 1692.18, "deposit": 152, "fee": 0, "description": "HKD/RUB_TOM - HKD/РУБ"},
43 | {"id": 2, "account": [10, 12, 10], "number": "33918678504", "asset": [0, 1], "timestamp": 1698306811, "withdrawal": 4670.5, "deposit": 50, "fee": 18.68, "description": "USDRUB_TOM - USD/РУБ"}
44 | ]
45 | }
--------------------------------------------------------------------------------
/tests/test_data/taxes_bond_rus.json:
--------------------------------------------------------------------------------
1 | {
2 | "Акции":[
3 |
4 | ],
5 | "Дивиденды":[
6 |
7 | ],
8 | "Комиссии":[
9 |
10 | ],
11 | "Криптовалюты":[
12 |
13 | ],
14 | "Облигации":[
15 | {
16 | "c_amount":2062.5,
17 | "c_amount_rub":149994.28,
18 | "c_date":1632515100,
19 | "c_fee":0.0,
20 | "c_fee_rub":0.0,
21 | "c_int":0.0,
22 | "c_int_rub":0.0,
23 | "c_isin":"US912CALAN84",
24 | "c_number":"17849517124",
25 | "c_price":103.125,
26 | "c_qty":2.0,
27 | "c_rate":72.7245,
28 | "c_symbol":"X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26",
29 | "c_type":"Продажа",
30 | "country_iso":"840",
31 | "cs_date":1632515100,
32 | "cs_rate":72.7245,
33 | "income":2062.5,
34 | "income_rub":149994.28,
35 | "o_amount":1274.18,
36 | "o_amount_rub":93618.72,
37 | "o_date":1622131065,
38 | "o_fee":2.0,
39 | "o_fee_rub":146.95,
40 | "o_int":25.69,
41 | "o_int_rub":1887.54,
42 | "o_isin":"US912909AN84",
43 | "o_number":"2882737839",
44 | "o_price":63.709,
45 | "o_qty":2.0,
46 | "o_rate":73.4737,
47 | "o_symbol":"X 6 1/4 03/15/26",
48 | "o_type":"Покупка",
49 | "os_date":1622246400,
50 | "os_rate":73.4737,
51 | "principal":1000.0,
52 | "profit":760.63,
53 | "profit_rub":54341.07,
54 | "report_template":"bond_trade",
55 | "spending_rub":95653.21
56 | },
57 | {
58 | "country_iso":"840",
59 | "empty":"",
60 | "income_rub":4508.46,
61 | "interest":62.0,
62 | "interest_rub":4508.46,
63 | "isin":"US912909AN84",
64 | "number":"",
65 | "o_date":1631664000,
66 | "profit":62.0,
67 | "profit_rub":4508.46,
68 | "rate":72.7171,
69 | "report_template":"bond_interest",
70 | "spending_rub":0.0,
71 | "symbol":"X 6 1/4 03/15/26",
72 | "type":"Купон"
73 | },
74 | {
75 | "country_iso":"840",
76 | "empty":"",
77 | "income_rub":36.36,
78 | "interest":0.5,
79 | "interest_rub":36.36,
80 | "isin":"US912909AN84",
81 | "number":"TEST_ID",
82 | "o_date":1631664000,
83 | "profit":0.5,
84 | "profit_rub":36.36,
85 | "rate":72.7171,
86 | "report_template":"bond_interest",
87 | "spending_rub":0.0,
88 | "symbol":"X 6 1/4 03/15/26",
89 | "type":"Купон"
90 | },
91 | {
92 | "country_iso":"840",
93 | "empty":"",
94 | "income_rub":278.89,
95 | "interest":3.82,
96 | "interest_rub":278.89,
97 | "isin":"US912CALAN84",
98 | "number":"",
99 | "o_date":1632700800,
100 | "profit":3.82,
101 | "profit_rub":278.89,
102 | "rate":73.0081,
103 | "report_template":"bond_interest",
104 | "spending_rub":0.0,
105 | "symbol":"X 6 1/4 03/15/26 - PARTIAL CALL RED DATE 9/26",
106 | "type":"Купон"
107 | },
108 | {
109 | "income_rub":154817.99,
110 | "profit":826.95,
111 | "profit_rub":59164.78,
112 | "report_template":"totals",
113 | "spending_rub":95653.21
114 | }
115 | ],
116 | "ПФИ":[
117 |
118 | ],
119 | "Проценты":[
120 |
121 | ]
122 | }
--------------------------------------------------------------------------------
/tests/test_data/taxes_cfd_rus.json:
--------------------------------------------------------------------------------
1 | {
2 | "Акции":[
3 |
4 | ],
5 | "Дивиденды":[
6 |
7 | ],
8 | "Комиссии":[
9 | {
10 | "amount":0.28,
11 | "amount_rub":17.62,
12 | "note":"LONG CFD INTEREST FOR 07-JUL-2022",
13 | "payment_date":1657152000,
14 | "rate":62.911,
15 | "report_template":"fee"
16 | },
17 | {
18 | "amount":0.02,
19 | "amount_rub":1.17,
20 | "note":"CFD BORROW FEE FOR MU for 06-JUL-2022",
21 | "payment_date":1657065600,
22 | "rate":58.5118,
23 | "report_template":"fee"
24 | },
25 | {
26 | "amount":0.06,
27 | "amount_rub":3.51,
28 | "note":"SHORT CFD INTEREST FOR 06-JUL-2022",
29 | "payment_date":1657065600,
30 | "rate":58.5118,
31 | "report_template":"fee"
32 | },
33 | {
34 | "amount":0.36,
35 | "amount_rub":22.3,
36 | "report_template":"totals"
37 | }
38 | ],
39 | "Криптовалюты":[
40 |
41 | ],
42 | "Облигации":[
43 |
44 | ],
45 | "ПФИ":[
46 | {
47 | "c_amount":2147.3,
48 | "c_amount_rub":131557.03,
49 | "c_date":1657186655,
50 | "c_fee":0.9736842105263158,
51 | "c_fee_rub":61.26,
52 | "c_isin":"",
53 | "c_number":"211380318",
54 | "c_price":58.035,
55 | "c_qty":-37.0,
56 | "c_rate":62.911,
57 | "c_symbol":"MUn",
58 | "c_type":"Покупка",
59 | "cost_basis":100.0,
60 | "country_iso":"000",
61 | "cs_date":1657497600,
62 | "cs_rate":61.2664,
63 | "income":2222.74,
64 | "income_rub":140349.68,
65 | "note":"Удержан дивиденд: 252.51 RUB (4.37 USD)\n",
66 | "o_amount":2222.74,
67 | "o_amount_rub":140349.68,
68 | "o_date":1657106512,
69 | "o_fee":1.0,
70 | "o_fee_rub":58.51,
71 | "o_isin":"",
72 | "o_number":"211321719",
73 | "o_price":60.074,
74 | "o_qty":-37.0,
75 | "o_rate":58.5118,
76 | "o_symbol":"MUn",
77 | "o_type":"Продажа",
78 | "os_date":1657238400,
79 | "os_rate":63.1427,
80 | "profit":69.1,
81 | "profit_rub":8420.372223,
82 | "report_template":"trade",
83 | "spending_rub":131929.307777
84 | },
85 | {
86 | "c_amount":58.04,
87 | "c_amount_rub":3555.6,
88 | "c_date":1657186655,
89 | "c_fee":0.02631578947368421,
90 | "c_fee_rub":1.66,
91 | "c_isin":"",
92 | "c_number":"211380318",
93 | "c_price":58.035,
94 | "c_qty":-1.0,
95 | "c_rate":62.911,
96 | "c_symbol":"MUn",
97 | "c_type":"Покупка",
98 | "cost_basis":100.0,
99 | "country_iso":"000",
100 | "cs_date":1657497600,
101 | "cs_rate":61.2664,
102 | "income":59.5,
103 | "income_rub":3756.99,
104 | "note":"",
105 | "o_amount":59.5,
106 | "o_amount_rub":3756.99,
107 | "o_date":1657110438,
108 | "o_fee":1.0,
109 | "o_fee_rub":58.51,
110 | "o_isin":"",
111 | "o_number":"211326559",
112 | "o_price":59.5,
113 | "o_qty":-1.0,
114 | "o_rate":58.5118,
115 | "o_symbol":"MUn",
116 | "o_type":"Продажа",
117 | "os_date":1657238400,
118 | "os_rate":63.1427,
119 | "profit":0.43,
120 | "profit_rub":141.22,
121 | "report_template":"trade",
122 | "spending_rub":3615.77
123 | },
124 | {
125 | "income_rub":144106.67,
126 | "profit":69.53,
127 | "profit_rub":8561.592223,
128 | "report_template":"totals",
129 | "spending_rub":135545.077777
130 | }
131 | ],
132 | "Проценты":[
133 |
134 | ]
135 | }
--------------------------------------------------------------------------------
/tests/test_data/taxes_flow.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "report_template": "account_lines",
4 | "account": "U7654321",
5 | "currency": "USD (840)",
6 | "money": "Денежные средства",
7 | "account_name": "Inv. Account",
8 | "assets": "Финансовые активы",
9 | "money_begin": -0.278304,
10 | "money_in": 3.336481,
11 | "money_out": 2.532967000000000098225427791,
12 | "money_end": 0.52521,
13 | "assets_begin": 0.0,
14 | "assets_in": 2.719063000000000101863406599,
15 | "assets_out": 2.779543000000000120053300634,
16 | "assets_end": 0.1068
17 | }
18 | ]
--------------------------------------------------------------------------------
/tests/test_data/taxes_merger_spinoff_rus.json:
--------------------------------------------------------------------------------
1 | {
2 | "Акции":[
3 | {
4 | "c_amount":3.12,
5 | "c_amount_rub":198.25,
6 | "c_date":1652905500,
7 | "c_fee":0.0,
8 | "c_fee_rub":0.0,
9 | "c_isin":"US87167T2015",
10 | "c_number":"",
11 | "c_price":6.24,
12 | "c_qty":0.5,
13 | "c_rate":63.5428,
14 | "c_symbol":"SNPXD",
15 | "c_type":"Продажа",
16 | "cost_basis":0.0,
17 | "country_iso":"840",
18 | "cs_date":1652905500,
19 | "cs_rate":63.5428,
20 | "income":3.12,
21 | "income_rub":198.25,
22 | "note":"Spin-off: NTRP(US64129T2078) SPINOFF 1 FOR 5 (SNPX, SYNAPTOGENIX INC, US87167T1025)\nSYNAPTOGENIX INC (0.0 %)\nMerger: NTRP(US64129T2078) MERGED(Acquisition) WITH US71678J1007 1 FOR 5 (PTPI, PETROS PHARMACEUTICALS INC, US71678J1007)\nPETROS PHARMACEUTICALS INC\nSplit: SNPX(US87167T1025) SPLIT 1 FOR 4 (SNPXD, SYNAPTOGENIX INC, US87167T2015)\nSYNAPTOGENIX INC\nSplit: PTPI(US71678J1007) SPLIT 1 FOR 10 (PTPI, PETROS PHARMACEUTICALS INC, US71678J2096)\nPETROS PHARMACEUTICALS INC\n",
23 | "o_amount":47.4,
24 | "o_amount_rub":3481.16,
25 | "o_date":1630844624,
26 | "o_fee":0.1,
27 | "o_fee_rub":7.29,
28 | "o_isin":"US64129T2078",
29 | "o_number":"2543065342",
30 | "o_price":4.74,
31 | "o_qty":10.0,
32 | "o_rate":72.8545,
33 | "o_symbol":"NTRP",
34 | "o_type":"Покупка",
35 | "os_date":1631145600,
36 | "os_rate":73.4421,
37 | "profit":3.12,
38 | "profit_rub":198.25,
39 | "report_template":"corporate_action",
40 | "spending_rub":0.0
41 | },
42 | {
43 | "c_amount":0.82,
44 | "c_amount_rub":50.22,
45 | "c_date":1669839900,
46 | "c_fee":0.0,
47 | "c_fee_rub":0.0,
48 | "c_isin":"US71678J2096",
49 | "c_number":"",
50 | "c_price":4.111,
51 | "c_qty":0.2,
52 | "c_rate":61.0742,
53 | "c_symbol":"PTPI",
54 | "c_type":"Продажа",
55 | "cost_basis":100.0,
56 | "country_iso":"840",
57 | "cs_date":1669839900,
58 | "cs_rate":61.0742,
59 | "income":0.82,
60 | "income_rub":50.22,
61 | "note":"Spin-off: NTRP(US64129T2078) SPINOFF 1 FOR 5 (SNPX, SYNAPTOGENIX INC, US87167T1025)\nSYNAPTOGENIX INC (0.0 %)\nMerger: NTRP(US64129T2078) MERGED(Acquisition) WITH US71678J1007 1 FOR 5 (PTPI, PETROS PHARMACEUTICALS INC, US71678J1007)\nPETROS PHARMACEUTICALS INC\nSplit: SNPX(US87167T1025) SPLIT 1 FOR 4 (SNPXD, SYNAPTOGENIX INC, US87167T2015)\nSYNAPTOGENIX INC\nSplit: PTPI(US71678J1007) SPLIT 1 FOR 10 (PTPI, PETROS PHARMACEUTICALS INC, US71678J2096)\nPETROS PHARMACEUTICALS INC\n",
62 | "o_amount":47.4,
63 | "o_amount_rub":3481.16,
64 | "o_date":1630844624,
65 | "o_fee":0.1,
66 | "o_fee_rub":7.29,
67 | "o_isin":"US64129T2078",
68 | "o_number":"2543065342",
69 | "o_price":4.74,
70 | "o_qty":10.0,
71 | "o_rate":72.8545,
72 | "o_symbol":"NTRP",
73 | "o_type":"Покупка",
74 | "os_date":1631145600,
75 | "os_rate":73.4421,
76 | "profit":-46.68,
77 | "profit_rub":-3438.23,
78 | "report_template":"corporate_action",
79 | "spending_rub":3488.45
80 | },
81 | {
82 | "income_rub":248.47,
83 | "profit":-43.56,
84 | "profit_rub":-3239.98,
85 | "report_template":"totals",
86 | "spending_rub":3488.45
87 | }
88 | ],
89 | "Дивиденды":[
90 |
91 | ],
92 | "Комиссии":[
93 |
94 | ],
95 | "Криптовалюты":[
96 |
97 | ],
98 | "Облигации":[
99 |
100 | ],
101 | "ПФИ":[
102 |
103 | ],
104 | "Проценты":[
105 |
106 | ]
107 | }
--------------------------------------------------------------------------------
/tests/test_data/taxes_spinoff_rus.json:
--------------------------------------------------------------------------------
1 | {
2 | "Акции": [
3 | {
4 | "c_isin": "",
5 | "c_symbol": "WAB",
6 | "c_qty": 0.5371,
7 | "c_amount": 0.64,
8 | "c_amount_rub": 46.87,
9 | "c_date": 1637679039,
10 | "c_fee": 0.0,
11 | "c_fee_rub": 0.0,
12 | "c_number": "22222",
13 | "c_price": 1.2,
14 | "c_rate": 72.76,
15 | "c_type": "Продажа",
16 | "cost_basis": 10.0,
17 | "country_iso": "840",
18 | "cs_date": 1637798400,
19 | "cs_rate": 72.7171,
20 | "income": 0.64,
21 | "income_rub": 46.87,
22 | "note": "Spin-off: GE(US3696041033) SPINOFF 5371 FOR 1000000 (WAB, WABTEC CORP, 929740108)\nWABTEC CORP (10.0 %)\n",
23 | "o_isin": "US3696041033",
24 | "o_symbol": "GE",
25 | "o_qty": 100.0,
26 | "o_amount": 832.50,
27 | "o_amount_rub": 61140.55,
28 | "o_date": 1635760683,
29 | "o_fee": 0.35,
30 | "o_fee_rub": 25.53,
31 | "o_number": "11111",
32 | "o_price": 8.325,
33 | "o_rate": 72.9538,
34 | "o_type": "Покупка",
35 | "os_date": 1635897600,
36 | "os_rate": 73.4421,
37 | "profit": -82.65,
38 | "profit_rub": -6069.73,
39 | "report_template": "corporate_action",
40 | "spending_rub": 6116.6
41 | },
42 | {
43 | "c_isin": "US3696041033",
44 | "c_symbol": "GE",
45 | "c_qty": 100.0,
46 | "c_amount": 795.0,
47 | "c_amount_rub": 57765.73,
48 | "c_date": 1638370239,
49 | "c_fee": 0.3,
50 | "c_fee_rub": 21.82,
51 | "c_number": "33333",
52 | "c_price": 7.95,
53 | "c_rate": 72.7245,
54 | "c_type": "Продажа",
55 | "cost_basis": 90.0,
56 | "country_iso": "840",
57 | "cs_date": 1638489600,
58 | "cs_rate": 72.6613,
59 | "income": 795.0,
60 | "income_rub": 57765.73,
61 | "note": "Spin-off: GE(US3696041033) SPINOFF 5371 FOR 1000000 (WAB, WABTEC CORP, 929740108)\nWABTEC CORP (10.0 %)\n",
62 | "o_symbol": "GE",
63 | "o_isin": "US3696041033",
64 | "o_qty": 100.0,
65 | "o_amount": 832.5,
66 | "o_amount_rub": 61140.55,
67 | "o_date": 1635760683,
68 | "o_fee": 0.35,
69 | "o_fee_rub": 25.53,
70 | "o_number": "11111",
71 | "o_price": 8.325,
72 | "o_rate": 72.9538,
73 | "o_type": "Покупка",
74 | "os_date": 1635897600,
75 | "os_rate": 73.4421,
76 | "profit": 45.13,
77 | "profit_rub": 2694.44,
78 | "report_template": "corporate_action",
79 | "spending_rub": 55071.29
80 | },
81 | {
82 | "income_rub": 57812.6,
83 | "profit": -37.52,
84 | "profit_rub": -3375.29,
85 | "report_template": "totals",
86 | "spending_rub": 61187.89
87 | }
88 | ],
89 | "Дивиденды": [],
90 | "Комиссии": [],
91 | "Криптовалюты": [],
92 | "Облигации": [],
93 | "ПФИ": [],
94 | "Проценты": []
95 | }
--------------------------------------------------------------------------------
/tests/test_data/taxes_vesting_rus.json:
--------------------------------------------------------------------------------
1 | {
2 | "Акции": [
3 | {
4 | "c_isin": "US92556V1061",
5 | "c_symbol": "VTRS",
6 | "c_qty": 11.0,
7 | "cost_basis": 100.0,
8 | "c_amount": 298.54,
9 | "c_amount_rub": 21950.63,
10 | "c_date": 1632528000,
11 | "c_fee": 1.0,
12 | "c_fee_rub": 73.4,
13 | "c_number": "",
14 | "c_price": 27.14,
15 | "c_rate": 73.3963,
16 | "c_type": "Продажа",
17 | "country_iso": "840",
18 | "cs_date": 1632700800,
19 | "cs_rate": 73.5266,
20 | "income": 298.54,
21 | "income_rub": 21950.63,
22 | "o_isin": "US92556V1061",
23 | "o_symbol": "VTRS",
24 | "o_qty": 11.0,
25 | "o_amount": 247.83,
26 | "o_amount_rub": 18235.41,
27 | "o_date": 1621641600,
28 | "o_fee": 0.0,
29 | "o_fee_rub": 0.0,
30 | "o_number": "",
31 | "o_price": 22.53,
32 | "o_rate": 73.5803,
33 | "o_type": "Покупка",
34 | "os_date": 1621641600,
35 | "os_rate": 73.5803,
36 | "profit": 49.71,
37 | "profit_rub": 3641.82,
38 | "report_template": "trade",
39 | "note": "Stock vesting\n\n",
40 | "spending_rub": 18308.81
41 | },
42 | {
43 | "income_rub": 21950.63,
44 | "profit": 49.71,
45 | "profit_rub": 3641.82,
46 | "report_template": "totals",
47 | "spending_rub": 18308.81
48 | }
49 | ],
50 | "Дивиденды": [
51 | {
52 | "amount": 247.83,
53 | "amount_rub": 18235.41,
54 | "country": "н/д",
55 | "country_iso": "000",
56 | "full_name": "VIATRIS INC",
57 | "isin": "US92556V1061",
58 | "note": "Доход получен в натуральной форме (ценными бумагами)",
59 | "payment_date": 1621641600,
60 | "rate": 73.5803,
61 | "report_template": "dividend",
62 | "symbol": "VTRS",
63 | "tax": 0.0,
64 | "tax2pay": 2370.60,
65 | "tax_rub": 0.0,
66 | "tax_treaty": "Нет"
67 | },
68 | {
69 | "amount": 247.83,
70 | "amount_rub": 18235.41,
71 | "report_template": "totals",
72 | "tax": 0.0,
73 | "tax2pay": 2370.60,
74 | "tax_rub": 0.0
75 | }
76 | ],
77 | "Комиссии": [],
78 | "Криптовалюты": [],
79 | "Облигации": [],
80 | "ПФИ": [],
81 | "Проценты": []
82 | }
--------------------------------------------------------------------------------
/tests/test_data/tvoy.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/tvoy.zip
--------------------------------------------------------------------------------
/tests/test_data/vtb.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titov-vv/jal/a3469c00f341083c7abe3114beceb7bd83b7dbb8/tests/test_data/vtb.xls
--------------------------------------------------------------------------------
/tests/test_dlsg.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import filecmp
4 | from jal.data_export.dlsg import DLSG
5 | from tests.fixtures import project_root, data_path
6 |
7 |
8 | def test_empty_dlsg(tmp_path, project_root, data_path):
9 | test_tax_files_empty = {
10 | 2020: "3ndfl_2020_empty.dc0",
11 | 2021: "3ndfl_2021_empty.dc1",
12 | 2022: "3ndfl_2022_empty.dc2"
13 | }
14 |
15 | for year in test_tax_files_empty:
16 | tax_form = DLSG(year)
17 | assert tax_form._year == year
18 |
19 | # Validate saving of empty tax form file
20 | tax_form.save(str(tmp_path) + os.sep + test_tax_files_empty[year])
21 | assert filecmp.cmp(data_path + test_tax_files_empty[year], str(tmp_path) + os.sep + test_tax_files_empty[year])
22 | os.remove(str(tmp_path) + os.sep + test_tax_files_empty[year])
23 |
24 |
25 | def test_full_dlsg(tmp_path, project_root, data_path):
26 | test_tax_files_full = {
27 | 2020: "3ndfl_2020.dc0",
28 | 2021: "3ndfl_2021.dc1",
29 | 2022: "3ndfl_2022.dc2"
30 | }
31 |
32 | for year in test_tax_files_full:
33 | tax_form = DLSG(year)
34 | assert tax_form._year == year
35 |
36 | with open(data_path + 'taxes_rus.json', 'r', encoding='utf-8') as json_file:
37 | tax_report = json.load(json_file)
38 | tax_form.update_taxes(tax_report, {"currency": "USD", "broker_name": "IBKR", "broker_iso_country": "840"})
39 |
40 | # Validate saving of tax form file with data from tax report
41 | tax_form.save(str(tmp_path) + os.sep + test_tax_files_full[year])
42 | assert filecmp.cmp(data_path + test_tax_files_full[year], str(tmp_path) + os.sep + test_tax_files_full[year])
43 | os.remove(str(tmp_path) + os.sep + test_tax_files_full[year])
44 |
--------------------------------------------------------------------------------