def prepareDealsReport(self, begin, end, account_id, group_dates): if account_id == 0: self.report_failure.emit( g_tr('Reports', "You should select account to create Deals report")) return False if group_dates == 1: self.query = executeSQL( self.db, "SELECT asset, " "strftime('%s', datetime(open_timestamp, 'unixepoch', 'start of day')) as open_timestamp, " "strftime('%s', datetime(close_timestamp, 'unixepoch', 'start of day')) as close_timestamp, " "SUM(open_price*qty)/SUM(qty) as open_price, SUM(close_price*qty)/SUM(qty) AS close_price, " "SUM(qty) as qty, SUM(fee) as fee, SUM(profit) as profit, " "coalesce(100*SUM(qty*(close_price-open_price)-fee)/SUM(qty*open_price), 0) AS rel_profit " "FROM deals_ext " "WHERE account_id=:account_id AND close_timestamp>=:begin AND close_timestamp<=:end " "GROUP BY asset, open_timestamp, close_timestamp " "ORDER BY close_timestamp, open_timestamp", [(":account_id", account_id), (":begin", begin), (":end", end)], forward_only=False) else: self.query = executeSQL( self.db, "SELECT asset, open_timestamp, close_timestamp, open_price, close_price, " "qty, fee, profit, rel_profit FROM deals_ext " "WHERE account_id=:account_id AND close_timestamp>=:begin AND close_timestamp<=:end", [(":account_id", account_id), (":begin", begin), (":end", end)], forward_only=False) return True
def addNewAsset(db, symbol, name, asset_type, isin, data_source=-1): _ = executeSQL( db, "INSERT INTO assets(name, type_id, full_name, isin, src_id) " "VALUES(:symbol, :type, :full_name, :isin, :data_src)", [(":symbol", symbol), (":type", asset_type), (":full_name", name), (":isin", isin), (":data_src", data_source)]) db.commit() asset_id = readSQL(db, "SELECT id FROM assets WHERE name=:symbol", [(":symbol", symbol)]) if asset_id is not None: logging.info( g_tr('', "New asset with id ") + f"{asset_id}" + g_tr('', " was added: ") + f"{symbol} - '{name}'") else: logging.error(g_tr('', "Failed to add new asset: "), + f"{symbol}") return asset_id
def deleteOperation(self): if QMessageBox().warning( None, g_tr('LedgerOperationsView', "Confirmation"), g_tr('LedgerOperationsView', "Are you sure to delete selected transacion?"), QMessageBox.Yes, QMessageBox.No) == QMessageBox.No: return index = self.table_view.currentIndex() operations_model = self.table_view.model() operation_type = operations_model.data( operations_model.index(index.row(), 0)) mapper = self.operations[operation_type][self.OP_MAPPER] mapper.model().removeRow(0) mapper.model().submitAll() self.stateIsCommitted.emit() operations_model.select()
def setBalancesCurrency(self, currency_id, currency_name): if self.balance_currency != currency_id: self.balance_currency = currency_id balances_model = self.balances_view.model() balances_model.setHeaderData( balances_model.fieldIndex("balance_adj"), Qt.Horizontal, g_tr('Ledger', "Balance, ") + currency_name) self.updateBalancesView()
def setHoldingsCurrency(self, currency_id, currency_name): if self.holdings_currency != currency_id: self.holdings_currency = currency_id holidings_model = self.holdings_view.model() holidings_model.setHeaderData( holidings_model.fieldIndex("value_adj"), Qt.Horizontal, g_tr('Ledger', "Value, ") + currency_name) self.updateHoldingsView()
def loadFileSlipJSON(self): json_file, _filter = \ QFileDialog.getOpenFileName(self, g_tr('ImportSlipDialog', "Select file with slip JSON data"), ".", "JSON files (*.json)") if json_file: with open(json_file) as f: self.slip_json = json.load(f) self.parseJSON()
def get_slip(self, timestamp, amount, fn, fd, fp, slip_type): date_time = datetime.fromtimestamp(timestamp).strftime('%Y%m%dT%H%M%S') session_id = self.get_ru_tax_session() if session_id == '': return None s = requests.Session() s.headers['ClientVersion'] = '2.9.0' s.headers['Device-Id'] = str(uuid.uuid1()) s.headers['Device-OS'] = 'Android' s.headers['sessionId'] = session_id s.headers['Content-Type'] = 'application/json; charset=UTF-8' s.headers['Accept-Encoding'] = 'gzip' s.headers['User-Agent'] = 'okhttp/4.2.2' payload = '{' + f'"qr": "t={date_time}&s={amount:.2f}&fn={fn}&i={fd}&fp={fp}&n={slip_type}"' + '}' response = s.post('https://irkkt-mobile.nalog.ru:8888/v2/ticket', data=payload) if response.status_code != 200: if response.status_code == 401 and response.text == "Session was not found": self.refresh_session() else: logging.error( g_tr('SlipsTaxAPI', "Get ticket id failed: ") + f"{response}/{response.text} for {payload}") return None logging.info(g_tr('SlipsTaxAPI', "Slip found: " + response.text)) json_content = json.loads(response.text) if json_content[ 'status'] != '2': # Valid slip status is 2, other statuses are not fully clear logging.warning( g_tr( 'ImportSlipDialog', "Operation might be pending on server side. Try again later." )) return None url = "https://irkkt-mobile.nalog.ru:8888/v2/tickets/" + json_content[ 'id'] response = s.get(url) if response.status_code != 200: logging.error( g_tr('SlipsTaxAPI', "Get ticket failed: ") + f"{response}/{response.text}") return None logging.info(g_tr('SlipsTaxAPI', "Slip loaded: " + response.text)) slip_json = json.loads(response.text) return slip_json
def storeIBAsset(self, IBasset): asset_id = readSQL(self.db, "SELECT id FROM assets WHERE name=:symbol", [(":symbol", IBasset.symbol)]) if asset_id is not None: return asset_id try: asset_type = IBKR.AssetType[IBasset.assetCategory] except: logging.error( g_tr('StatementLoader', "Asset type ") + f"{IBasset.assetCategory}" + g_tr('StatementLoader', " is not supported")) return None if IBasset.subCategory == "ETF": asset_type = PredefinedAsset.ETF return addNewAsset(IBasset.symbol, IBasset.description, asset_type, IBasset.isin)
def loadIBCorpAction(self, IBCorpAction): if IBCorpAction.code == Code.CANCEL: logging.warning( g_tr('StatementLoader', "*** MANUAL ACTION REQUIRED ***")) logging.warning( f"Corporate action cancelled {IBCorpAction.type} for account " f"{IBCorpAction.accountId} ({IBCorpAction.currency}): {IBCorpAction.actionDescription}" ) logging.warning( f"@{IBCorpAction.dateTime} for {IBCorpAction.symbol}: Qty {IBCorpAction.quantity}, " f"Value {IBCorpAction.value}, Type {IBCorpAction.type}, Code {IBCorpAction.code}" ) return if IBCorpAction.assetCategory == AssetClass.STOCK and ( IBCorpAction.type == Reorg.MERGER or IBCorpAction.type == Reorg.SPINOFF): account_id = self.findAccountID(IBCorpAction.accountId, IBCorpAction.currency) if account_id is None: logging.error( g_tr('StatementLoader', "Account ") + f"{IBCorpAction.accountId} ({IBCorpAction.currency})" + g_tr('StatementLoader', " not found. Skipping trade #") + f"{IBCorpAction.transactionID}") return asset_id = self.findAssetID(IBCorpAction.symbol) timestamp = int(IBCorpAction.dateTime.timestamp()) settlement = timestamp if IBCorpAction.transactionID: number = IBCorpAction.transactionID else: number = "" qty = IBCorpAction.quantity self.createTrade(account_id, asset_id, timestamp, settlement, number, qty, 0, 0) logging.warning( g_tr('StatementLoader', "*** MANUAL ACTION REQUIRED ***")) logging.warning( f"Corporate action {IBCorpAction.type} for account " f"{IBCorpAction.accountId} ({IBCorpAction.currency}): {IBCorpAction.actionDescription}" ) logging.warning( f"@{IBCorpAction.dateTime} for {IBCorpAction.symbol}: Qty {IBCorpAction.quantity}, " f"Value {IBCorpAction.value}, Type {IBCorpAction.type}, Code {IBCorpAction.code}" )
def loadIBFee(self, fee): account_id = self.findAccountID(fee.accountId, fee.currency) if account_id is None: logging.error(g_tr('StatementLoader', "Account ") + f"{fee.accountId} ({fee.currency})" + g_tr('StatementLoader', " not found. Skipping fee #") + f"{fee.transactionID}") return timestamp = int(fee.dateTime.timestamp()) amount = float(fee.amount) # value may be both positive and negative note = fee.description query = executeSQL(self.db,"INSERT INTO actions (timestamp, account_id, peer_id) VALUES " "(:timestamp, :account_id, (SELECT organization_id FROM accounts WHERE id=:account_id))", [(":timestamp", timestamp), (":account_id", account_id)]) pid = query.lastInsertId() _ = executeSQL(self.db, "INSERT INTO action_details (pid, category_id, sum, note) " "VALUES (:pid, :category_id, :sum, :note)", [(":pid", pid), (":category_id", PredefinedCategory.Fees), (":sum", amount), (":note", note)]) self.db.commit() logging.info(g_tr('StatementLoader', "Fee added: ") + f"{note}, {amount}")
def loadIBFlex(self, filename): try: report = parser.parse(filename) except Exception as e: logging.error(g_tr('StatementLoader', "Failed to parse Interactive Brokers flex-report") + f": {e}") return False for statement in report.FlexStatements: self.loadIBStatement(statement) return True
def interceptRequest(self, info): url = info.firstPartyUrl().url() # Get intercepted URL if str.startswith(url, "https://irkkt-mobile.nalog.ru:8888/"): info.block(True) params = dict(parse.parse_qsl(parse.urlsplit(url).query)) auth_code = params['code'] auth_state = params['state'] logging.info(g_tr('SlipsTaxAPI', "ESIA login completed")) self.response_intercepted.emit(auth_code, auth_state)
def onOperationContextMenu(self, pos): self.current_index = self.table_view.indexAt(pos) contextMenu = QMenu(self.table_view) actionReconcile = QAction(text=g_tr('LedgerOperationsView', "Reconcile"), parent=self) actionReconcile.triggered.connect(self.reconcileAtCurrentOperation) actionCopy = QAction(text=g_tr('LedgerOperationsView', "Copy"), parent=self) actionCopy.triggered.connect(self.copyOperation) actionDelete = QAction(text=g_tr('LedgerOperationsView', "Delete"), parent=self) actionDelete.triggered.connect(self.deleteOperation) contextMenu.addAction(actionReconcile) contextMenu.addSeparator() contextMenu.addAction(actionCopy) contextMenu.addAction(actionDelete) contextMenu.popup(self.table_view.viewport().mapToGlobal(pos))
def get_web_data(url): response = requests.get(url) if response.status_code == 200: return response.text else: logging.error(f"URL: {url}" + g_tr('QuotesUpdateDialog', " failed with response ") + f"{response}") return ''
def MakeBackup(db_file, backup_path): db = sqlite3.connect(db_file) for table in backup_list: data = pd.read_sql_query(f"SELECT * FROM {table}", db) data.to_csv(f"{backup_path}/{table}.csv", sep="|", header=True, index=False) db.close() logging.info(g_tr('', "Backup saved in: ") + backup_path)
def createDividend(self, timestamp, account_id, asset_id, amount, note): id = readSQL( self.db, "SELECT id FROM dividends WHERE timestamp=:timestamp " "AND account_id=:account_id AND asset_id=:asset_id AND note=:note", [(":timestamp", timestamp), (":account_id", account_id), (":asset_id", asset_id), (":note", note)]) if id: logging.warning( g_tr('StatementLoader', "Dividend already exists: ") + f"{note}") return _ = executeSQL( self.db, "INSERT INTO dividends (timestamp, account_id, asset_id, sum, note) " "VALUES (:timestamp, :account_id, :asset_id, :sum, :note)", [(":timestamp", timestamp), (":account_id", account_id), (":asset_id", asset_id), (":sum", amount), (":note", note)]) self.db.commit() logging.info(g_tr('StatementLoader', "Dividend added: ") + f"{note}")
def loadReport(self): report_file, active_filter = \ QFileDialog.getOpenFileName(None, g_tr('StatementLoader', "Select statement file to import"), ".", f"{ReportType.IBKR};;{ReportType.Quik}") if report_file: result = self.loaders[active_filter](report_file) if result: self.load_completed.emit() else: self.load_failed.emit()
def loadIBWithholdingTax(self, tax): if tax.assetCategory != AssetClass.STOCK: logging.error( g_tr('StatementLoader', "Withholding tax for ") + f"{tax.assetCategory}" + g_tr('StatementLoader', " not implemented")) return account_id = self.findAccountID(tax.accountId, tax.currency) if account_id is None: logging.error( g_tr('StatementLoader', "Account ") + f"{tax.accountId} ({tax.currency})" + g_tr('StatementLoader', " not found. Skipping tax #") + f"{tax.transactionID}") return asset_id = self.findAssetID(tax.symbol) timestamp = int(tax.dateTime.timestamp()) amount = float(tax.amount) note = tax.description self.addWithholdingTax(timestamp, account_id, asset_id, amount, note)
def loadIBDividend(self, dividend): if dividend.assetCategory != AssetClass.STOCK: logging.error( g_tr('StatementLoader', "Dividend for ") + f"{dividend.assetCategory}" + g_tr('StatementLoader', " not implemented")) return account_id = self.findAccountID(dividend.accountId, dividend.currency) if account_id is None: logging.error( g_tr('StatementLoader', "Account ") + f"{dividend.accountId} ({dividend.currency})" + g_tr('StatementLoader', " not found. Skipping dividend #") + f"{dividend.transactionID}") return asset_id = self.findAssetID(dividend.symbol) timestamp = int(dividend.dateTime.timestamp()) amount = float(dividend.amount) note = dividend.description self.createDividend(timestamp, account_id, asset_id, amount, note)
def parseQRdata(self, qr_data): self.QR_data = qr_data logging.info(g_tr('ImportSlipDialog', "QR: " + self.QR_data)) parts = re.match(self.QR_pattern, qr_data) if not parts: logging.warning( g_tr( 'ImportSlipDialog', "QR available but pattern isn't recognized: " + self.QR_data)) for timestamp_pattern in self.timestamp_patterns: datetime = QDateTime.fromString(parts.group(1), timestamp_pattern) if datetime.isValid(): self.SlipTimstamp.setDateTime(datetime) self.SlipAmount.setText(parts.group(2)) self.FN.setText(parts.group(3)) self.FD.setText(parts.group(4)) self.FP.setText(parts.group(5)) self.SlipType.setText(parts.group(6)) self.qr_data_validated.emit()
def loadIBTransactionTax(self, IBtax): account_id = self.findAccountID(IBtax.accountId, IBtax.currency) if account_id is None: logging.error(g_tr('StatementLoader', "Account ") + f"{IBtax.accountId} ({IBtax.currency})" + g_tr('StatementLoader', " not found. Tax #") + f"{IBtax.tradeID}" + g_tr('StatementLoader', " skipped")) return timestamp = int(datetime.combine(IBtax.date, datetime.min.time()).timestamp()) amount = float(IBtax.taxAmount) # value is negative already note = f"{IBtax.symbol} ({IBtax.description}) - {IBtax.taxDescription} (#{IBtax.tradeId})" id = readSQL(self.db, "SELECT id FROM all_operations WHERE type = :type " "AND timestamp=:timestamp AND account_id=:account_id AND amount=:amount", [(":timestamp", timestamp), (":type", TransactionType.Action), (":account_id", account_id), (":amount", amount)]) if id: logging.warning(g_tr('StatementLoader', "Tax transaction #") + f"{IBtax.tradeId}" + g_tr('StatementLoader', " already exists")) return query = executeSQL(self.db, "INSERT INTO actions (timestamp, account_id, peer_id) VALUES " "(:timestamp, :account_id, (SELECT organization_id FROM accounts WHERE id=:account_id))", [(":timestamp", timestamp), (":account_id", account_id)]) pid = query.lastInsertId() _ = executeSQL(self.db, "INSERT INTO action_details (pid, category_id, sum, note) " "VALUES (:pid, :category_id, :sum, :note)", [(":pid", pid), (":category_id", PredefinedCategory.Taxes), (":sum", amount), (":note", note)]) self.db.commit() logging.info(g_tr('StatementLoader', "Transaction tax added: ") + f"{note}, {amount}")
def refresh_session(self): session_id = self.get_ru_tax_session() client_secret = readSQL( self.db, "SELECT value FROM settings WHERE name='RuTaxClientSecret'") refresh_token = readSQL( self.db, "SELECT value FROM settings WHERE name='RuTaxRefreshToken'") s = requests.Session() s.headers['ClientVersion'] = '2.9.0' s.headers['Device-Id'] = str(uuid.uuid1()) s.headers['Device-OS'] = 'Android' s.headers['sessionId'] = session_id s.headers['Content-Type'] = 'application/json; charset=UTF-8' s.headers['Accept-Encoding'] = 'gzip' s.headers['User-Agent'] = 'okhttp/4.2.2' payload = '{' + f'"client_secret":"{client_secret}","refresh_token":"{refresh_token}"' + '}' response = s.post( 'https://irkkt-mobile.nalog.ru:8888/v2/mobile/users/refresh', data=payload) if response.status_code == 200: logging.info( g_tr('SlipsTaxAPI', "Session refreshed: ") + f"{response.text}") json_content = json.loads(response.text) new_session_id = json_content['sessionId'] new_refresh_token = json_content['refresh_token'] _ = executeSQL( self.db, "UPDATE settings SET value=:new_session WHERE name='RuTaxSessionId'", [(":new_session", new_session_id)]) _ = executeSQL( self.db, "UPDATE settings SET value=:new_refresh_token WHERE name='RuTaxRefreshToken'", [(":new_refresh_token", new_refresh_token)]) else: logging.error( g_tr('SlipsTaxAPI', "Can't refresh session, response: ") + f"{response}/{response.text}")
def loadFileQR(self): self.initUi() qr_file, _filter = \ QFileDialog.getOpenFileName(self, g_tr('ImportSlipDialog', "Select file with QR code"), ".", "JPEG images (*.jpg);;PNG images (*.png)") if qr_file: barcodes = pyzbar.decode(Image.open(qr_file), symbols=[pyzbar.ZBarSymbol.QRCODE]) if barcodes: self.qr_data_available.emit(barcodes[0].data.decode('utf-8')) else: logging.warning('ImportSlipDialog', "No QR codes were found in file")
def loadIBStatement(self, IBstatement): logging.info( g_tr('StatementLoader', "Load IB Flex-statement for account ") + f"{IBstatement.accountId} " + g_tr('StatementLoader', "from ") + f"{IBstatement.fromDate}" + g_tr('StatementLoader', " to ") + f"{IBstatement.toDate}") for asset in IBstatement.SecuritiesInfo: if self.storeIBAsset(asset) is None: return False for trade in IBstatement.Trades: try: self.ib_trade_loaders[trade.assetCategory](trade) except: logging.error( g_tr('StatementLoader', "Load of ") + f"{trade.assetCategory}" + g_tr('StatementLoader', " is not implemented. Skipping trade #") + f"{trade.tradeID}") for tax in IBstatement.TransactionTaxes: self.loadIBTransactionTax(tax) for corp_action in IBstatement.CorporateActions: self.loadIBCorpAction(corp_action) # 1st loop to load all dividends separately - to allow tax match in 2nd loop for cash_transaction in IBstatement.CashTransactions: if cash_transaction.type == CashAction.DIVIDEND: self.loadIBDividend(cash_transaction) for cash_transaction in IBstatement.CashTransactions: if cash_transaction.type == CashAction.WHTAX: self.loadIBWithholdingTax(cash_transaction) elif cash_transaction.type == CashAction.FEES: self.loadIBFee(cash_transaction) elif cash_transaction.type == CashAction.DEPOSITWITHDRAW: self.loadIBDepositWithdraw(cash_transaction)
def addWithholdingTax(self, timestamp, account_id, asset_id, amount, note): parts = re.match(IBKR.TaxNotePattern, note) if not parts: logging.warning(g_tr('StatementLoader', "*** MANUAL ENTRY REQUIRED ***")) logging.warning(g_tr('StatementLoader', "Unhandled tax pattern found: ") + f"{note}") return dividend_note = parts.group(1) + '%' country_code = parts.group(2) try: dividend_id, old_tax = readSQL(self.db, "SELECT id, sum_tax FROM dividends " "WHERE timestamp=:timestamp AND account_id=:account_id " "AND asset_id=:asset_id AND note LIKE :dividend_description", [(":timestamp", timestamp), (":account_id", account_id), (":asset_id", asset_id), (":dividend_description", dividend_note)]) except: logging.warning(g_tr('StatementLoader', "Dividend not found for withholding tax: ") + f"{note}") return _ = executeSQL(self.db, "UPDATE dividends SET sum_tax=:tax, note_tax=:note WHERE id=:dividend_id", [(":dividend_id", dividend_id), (":tax", old_tax + amount), (":note", country_code + " tax")]) self.db.commit() logging.info(g_tr('StatementLoader', "Withholding tax added: ") + f"{note}")
def setType(self, trade_type): # if (self.p_type == type): # return self.p_type = trade_type if self.p_type: self.label.setText(g_tr('TradeAction', "CORP.ACTION")) self.palette.setColor(self.label.foregroundRole(), CustomColor.DarkBlue) self.label.setPalette(self.palette) else: self.label.setText(g_tr('TradeAction', "TRADE")) self.palette.setColor(self.label.foregroundRole(), CustomColor.DarkGreen) self.label.setPalette(self.palette) # else: # self.label.setText("SELL") # self.palette.setColor(self.label.foregroundRole(), CustomColor.DarkRed) # self.label.setPalette(self.palette) # else: # self.label.setText("UNKNOWN") self.changed.emit()
def createTrade(self, account_id, asset_id, timestamp, settlement, number, qty, price, fee, coupon=0.0): trade_id = readSQL(self.db, "SELECT id FROM trades " "WHERE timestamp=:timestamp AND asset_id = :asset " "AND account_id = :account AND number = :number AND qty = :qty AND price = :price", [(":timestamp", timestamp), (":asset", asset_id), (":account", account_id), (":number", number), (":qty", qty), (":price", price)]) if trade_id: logging.info(g_tr('StatementLoader', "Trade #") + f"{number}" + g_tr('StatementLoader', " already exists in ledger. Skipped")) return _ = executeSQL(self.db, "INSERT INTO trades (timestamp, settlement, corp_action_id, number, account_id, " "asset_id, qty, price, fee, coupon) " "VALUES (:timestamp, :settlement, 0, :number, :account, " ":asset, :qty, :price, :fee, :coupon)", [(":timestamp", timestamp), (":settlement", settlement), (":number", number), (":account", account_id), (":asset", asset_id), (":qty", float(qty)), (":price", float(price)), (":fee", -float(fee)), (":coupon", float(coupon))]) self.db.commit() logging.info(f"Trade #{number} added for account {account_id} asset {asset_id} @{timestamp}: {qty}x{price}")
def login_fns(self): client_secret = readSQL( self.db, "SELECT value FROM settings WHERE name='RuTaxClientSecret'") inn = self.InnEdit.text() password = self.PasswordEdit.text() s = requests.Session() s.headers['ClientVersion'] = '2.9.0' s.headers['Device-Id'] = str(uuid.uuid1()) s.headers['Device-OS'] = 'Android' s.headers['Content-Type'] = 'application/json; charset=UTF-8' s.headers['Accept-Encoding'] = 'gzip' s.headers['User-Agent'] = 'okhttp/4.2.2' payload = '{' + f'"client_secret":"{client_secret}","inn":"{inn}","password":"******"' + '}' response = s.post( 'https://irkkt-mobile.nalog.ru:8888/v2/mobile/users/lkfl/auth', data=payload) if response.status_code != 200: logging.error( g_tr('SlipsTaxAPI', "FNS login failed: ") + f"{response}/{response.text}") return logging.info( g_tr('SlipsTaxAPI', "FNS login successful: ") + f"{response.text}") json_content = json.loads(response.text) new_session_id = json_content['sessionId'] new_refresh_token = json_content['refresh_token'] _ = executeSQL( self.db, "UPDATE settings SET value=:new_session WHERE name='RuTaxSessionId'", [(":new_session", new_session_id)]) _ = executeSQL( self.db, "UPDATE settings SET value=:new_refresh_token WHERE name='RuTaxRefreshToken'", [(":new_refresh_token", new_refresh_token)]) self.db.commit() self.accept()
def init_db(self, db): self.dialog = ReferenceDataDialog( db, "accounts", [("id", None, 0, None, None), ("name", g_tr('AccountSelector', "Name"), -1, Qt.AscendingOrder, None), ("type_id", None, 0, None, None), ("currency_id", "Currency", None, None, ReferenceLookupDelegate), ("active", "Act", 32, None, ReferenceBoolDelegate), ("number", "Account #", None, None, None), ("reconciled_on", "Reconciled @", self.fontMetrics().width("00/00/0000 00:00:00") * 1.1, None, ReferenceTimestampDelegate), ("organization_id", "Bank", None, None, ReferenceLookupDelegate)], title=g_tr('AccountSelector', "Accounts"), search_field="full_name", toggle=("active", "Show inactive"), relations=[("type_id", "account_types", "id", "name", "Account type:"), ("currency_id", "currencies", "id", "name", None), ("organization_id", "agents", "id", "name", None)]) super().init_db("accounts", "name")
def save2file(self, taxes_file, year, account_id): year_begin = int(time.mktime(datetime.strptime(f"{year}", "%Y").timetuple())) year_end = int(time.mktime(datetime.strptime(f"{year + 1}", "%Y").timetuple())) workbook = xlsxwriter.Workbook(filename=taxes_file) formats = xslxFormat(workbook) for report in self.reports: sheet = workbook.add_worksheet(name=report) self.reports[report](sheet, account_id, year_begin, year_end, formats) try: workbook.close() except: logging.error(g_tr('TaxesRus', "Can't write tax report into file ") + f"'{taxes_file}'")