Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paul/bitwave csv #4397

Merged
merged 3 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions src/actions/TransactionExportActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ function makeCsvDateTime(date: number): { date: string; time: string } {
}
}

function makeBitwaveDateTime(date: number): string {
const d = new Date(date * 1000)
const yy = d.getUTCFullYear().toString().slice(-2)
const mm = padZero((d.getUTCMonth() + 1).toString())
const dd = padZero(d.getUTCDate().toString())
const hh = padZero(d.getUTCHours().toString())
const min = padZero(d.getUTCMinutes().toString())

return `${mm}/${dd}/${yy} ${hh}:${min}`
}

//
// Check if tx is
// 1. A transfer
Expand Down Expand Up @@ -379,3 +390,69 @@ export function exportTransactionsToCSVInner(
record_delimiter: '\n'
})
}

export async function exportTransactionsToBitwave(
wallet: EdgeCurrencyWallet,
accountId: string,
edgeTransactions: EdgeTransaction[],
currencyCode: string,
multiplier: string,
parentMultiplier: string
): Promise<string> {
const items: any[] = []
const parentCode = wallet.currencyInfo.currencyCode

for (const tx of edgeTransactions) {
edgeTxToCsv(tx)
}

function edgeTxToCsv(edgeTx: EdgeTransaction) {
const { date, isSend, metadata, nativeAmount, networkFee, parentNetworkFee, txid } = edgeTx
const amount: string = abs(div(nativeAmount, multiplier, DECIMAL_PRECISION))
const time = makeBitwaveDateTime(date)
let fee: string = ''
let feeTicker: string = ''
const { name = '', category = '', notes = '' } = metadata ?? {}

if (isSend) {
if (parentNetworkFee != null) {
feeTicker = parentCode
fee = div(parentNetworkFee, parentMultiplier, DECIMAL_PRECISION)
} else {
feeTicker = currencyCode
fee = div(networkFee, multiplier, DECIMAL_PRECISION)
}
}

items.push({
id: txid,
remoteContactId: '',
amount,
amountTicker: currencyCode,
cost: '',
costTicker: '',
fee,
feeTicker,
time,
blockchainId: txid,
memo: category,
transactionType: isSend ? 'withdrawal' : 'deposit',
accountId,
contactId: '',
categoryId: '',
taxExempt: 'FALSE',
tradeId: '',
description: name,
fromAddress: '',
toAddress: '',
groupId: '',
'metadata:myCustomMetadata1': notes
})
}

return csvStringify(items, {
header: true,
quoted_string: true,
record_delimiter: '\n'
})
}
156 changes: 135 additions & 21 deletions src/components/scenes/TransactionsExportScene.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { asBoolean, asObject, asString } from 'cleaners'
import { EdgeCurrencyWallet, EdgeTransaction } from 'edge-core-js'
import * as React from 'react'
import { Platform, ScrollView } from 'react-native'
import RNFS from 'react-native-fs'
import Share from 'react-native-share'
import EntypoIcon from 'react-native-vector-icons/Entypo'

import { exportTransactionsToCSV, exportTransactionsToQBO, updateTxsFiat } from '../../actions/TransactionExportActions'
import { exportTransactionsToBitwave, exportTransactionsToCSV, exportTransactionsToQBO, updateTxsFiat } from '../../actions/TransactionExportActions'
import { formatDate } from '../../locales/intl'
import { lstrings } from '../../locales/strings'
import { getDisplayDenomination } from '../../selectors/DenominationSelectors'
import { getDisplayDenomination, getExchangeDenomination } from '../../selectors/DenominationSelectors'
import { connect } from '../../types/reactRedux'
import { EdgeSceneProps } from '../../types/routerTypes'
import { getTokenId } from '../../util/CurrencyInfoHelpers'
import { getWalletName } from '../../util/CurrencyWalletHelpers'
import { SceneWrapper } from '../common/SceneWrapper'
import { DateModal } from '../modals/DateModal'
import { TextInputModal } from '../modals/TextInputModal'
import { Airship, showError } from '../services/AirshipInstance'
import { ThemeProps, withTheme } from '../services/ThemeContext'
import { SettingsHeaderRow } from '../settings/SettingsHeaderRow'
Expand All @@ -33,6 +36,9 @@ interface OwnProps extends EdgeSceneProps<'transactionsExport'> {}

interface StateProps {
multiplier: string
exchangeMultiplier: string
parentMultiplier: string
tokenId: string | undefined
}

interface DispatchProps {
Expand All @@ -46,8 +52,23 @@ interface State {
endDate: Date
isExportQbo: boolean
isExportCsv: boolean
isExportBitwave: boolean
}

const EXPORT_TX_INFO_FILE = 'exportTxInfo.json'

const asExportTxInfo = asObject({
bitwaveAccountId: asString,
isExportQbo: asBoolean,
isExportCsv: asBoolean,
isExportBitwave: asBoolean
})

const asExportTxInfoMap = asObject(asExportTxInfo)

type ExportTxInfoMap = ReturnType<typeof asExportTxInfoMap>
type ExportTxInfo = ReturnType<typeof asExportTxInfo>

class TransactionsExportSceneComponent extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props)
Expand All @@ -58,7 +79,8 @@ class TransactionsExportSceneComponent extends React.PureComponent<Props, State>
startDate: new Date(new Date().getFullYear() - lastYear, lastMonth.getMonth(), 1, 0, 0, 0),
endDate: new Date(new Date().getFullYear(), new Date().getMonth(), 1, 0, 0, 0),
isExportQbo: false,
isExportCsv: true
isExportCsv: true,
isExportBitwave: false
}
}

Expand All @@ -79,16 +101,34 @@ class TransactionsExportSceneComponent extends React.PureComponent<Props, State>
})
}

async componentDidMount(): Promise<void> {
try {
const { sourceWallet } = this.props.route.params
const { tokenId = sourceWallet.currencyInfo.currencyCode } = this.props
const { disklet } = sourceWallet
const result = await disklet.getText(EXPORT_TX_INFO_FILE)
const exportTxInfoMap = asExportTxInfoMap(JSON.parse(result))
const { isExportBitwave, isExportCsv, isExportQbo } = exportTxInfoMap[tokenId]
this.setState({
isExportBitwave,
isExportCsv,
isExportQbo
})
} catch (e) {
console.log(`Could not read ${EXPORT_TX_INFO_FILE} ${String(e)}. Failure is ok`)
}
}

render() {
const { startDate, endDate, isExportCsv, isExportQbo } = this.state
const { startDate, endDate, isExportBitwave, isExportCsv, isExportQbo } = this.state
const { theme, route } = this.props
const { sourceWallet, currencyCode } = route.params
const iconSize = theme.rem(1.25)

const walletName = `${getWalletName(sourceWallet)} (${currencyCode})`
const startDateString = formatDate(startDate)
const endDateString = formatDate(endDate)
const disabledExport = !isExportQbo && !isExportCsv
const disabledExport = !isExportQbo && !isExportCsv && !isExportBitwave

return (
<SceneWrapper background="theme">
Expand All @@ -108,21 +148,23 @@ class TransactionsExportSceneComponent extends React.PureComponent<Props, State>
}

renderAndroidSwitches() {
const { isExportCsv, isExportQbo } = this.state
const { isExportBitwave, isExportCsv, isExportQbo } = this.state
return (
<>
<SettingsRadioRow label={lstrings.export_transaction_quickbooks_qbo} value={isExportQbo} onPress={this.handleAndroidToggle} />
<SettingsRadioRow label={lstrings.export_transaction_csv} value={isExportCsv} onPress={this.handleAndroidToggle} />
<SettingsRadioRow label={lstrings.export_transaction_quickbooks_qbo} value={isExportQbo} onPress={this.handleQboToggle} />
<SettingsRadioRow label={lstrings.export_transaction_csv} value={isExportCsv} onPress={this.handleCsvToggle} />
<SettingsRadioRow label={lstrings.export_transaction_bitwave_csv} value={isExportBitwave} onPress={this.handleBitwaveToggle} />
</>
)
}

renderIosSwitches() {
const { isExportCsv, isExportQbo } = this.state
const { isExportBitwave, isExportCsv, isExportQbo } = this.state
return (
<>
<SettingsSwitchRow label={lstrings.export_transaction_quickbooks_qbo} value={isExportQbo} onPress={this.handleQboToggle} />
<SettingsSwitchRow label={lstrings.export_transaction_csv} value={isExportCsv} onPress={this.handleCsvToggle} />
<SettingsSwitchRow label={lstrings.export_transaction_bitwave_csv} value={isExportBitwave} onPress={this.handleBitwaveToggle} />
</>
)
}
Expand All @@ -139,25 +181,84 @@ class TransactionsExportSceneComponent extends React.PureComponent<Props, State>
this.setState({ endDate: date })
}

handleAndroidToggle = () => {
this.setState(state => ({
isExportCsv: !state.isExportCsv,
isExportQbo: !state.isExportQbo
}))
}

handleQboToggle = () => {
this.setState(state => ({ isExportQbo: !state.isExportQbo }))
if (Platform.OS === 'android') {
this.setState({ isExportQbo: true, isExportCsv: false, isExportBitwave: false })
} else {
this.setState(state => ({ isExportQbo: !state.isExportQbo }))
}
}

handleCsvToggle = () => {
this.setState(state => ({ isExportCsv: !state.isExportCsv }))
if (Platform.OS === 'android') {
this.setState({ isExportCsv: true, isExportBitwave: false, isExportQbo: false })
} else {
this.setState(state => ({ isExportCsv: !state.isExportCsv }))
}
}

handleBitwaveToggle = () => {
if (Platform.OS === 'android') {
this.setState({ isExportBitwave: true, isExportCsv: false, isExportQbo: false })
} else {
this.setState(state => ({ isExportBitwave: !state.isExportBitwave }))
}
}

handleSubmit = async (): Promise<void> => {
const { multiplier, route } = this.props
const { exchangeMultiplier, multiplier, parentMultiplier, route } = this.props
const { sourceWallet, currencyCode } = route.params
const { isExportQbo, isExportCsv, startDate, endDate } = this.state
const { isExportBitwave, isExportQbo, isExportCsv, startDate, endDate } = this.state
const { tokenId = sourceWallet.currencyInfo.currencyCode } = this.props

let exportTxInfo: ExportTxInfo | undefined
let exportTxInfoMap: ExportTxInfoMap | undefined
try {
const result = await sourceWallet.disklet.getText(EXPORT_TX_INFO_FILE)
exportTxInfoMap = asExportTxInfoMap(JSON.parse(result))
exportTxInfo = exportTxInfoMap[tokenId]
} catch (e) {
console.log(`Could not read ${EXPORT_TX_INFO_FILE} ${String(e)}. Failure is ok`)
}

let accountId = ''
const fileAccountId = exportTxInfo?.bitwaveAccountId ?? ''

if (isExportBitwave) {
accountId =
(await Airship.show<string | undefined>(bridge => (
<TextInputModal
autoFocus
autoCorrect={false}
bridge={bridge}
initialValue={fileAccountId}
inputLabel={lstrings.export_transaction_bitwave_accountid_modal_input_label}
message={lstrings.export_transaction_bitwave_accountid_modal_message}
returnKeyType="next"
submitLabel={lstrings.string_next_capitalized}
title={lstrings.export_transaction_bitwave_accountid_modal_title}
/>
))) ?? ''
}

if (
exportTxInfo?.bitwaveAccountId !== accountId ||
exportTxInfo?.isExportBitwave !== isExportBitwave ||
exportTxInfo?.isExportCsv !== isExportCsv ||
exportTxInfo?.isExportQbo !== isExportQbo
) {
if (exportTxInfoMap == null) {
exportTxInfoMap = {}
}
exportTxInfoMap[tokenId] = {
bitwaveAccountId: accountId,
isExportBitwave,
isExportQbo,
isExportCsv
}
await sourceWallet.disklet.setText(EXPORT_TX_INFO_FILE, JSON.stringify(exportTxInfoMap))
}

if (startDate.getTime() > endDate.getTime()) {
showError(lstrings.export_transaction_error)
return
Expand Down Expand Up @@ -222,6 +323,16 @@ class TransactionsExportSceneComponent extends React.PureComponent<Props, State>
formats.push('QBO')
}

if (isExportBitwave) {
const bitwaveFile = await exportTransactionsToBitwave(sourceWallet, accountId, txs, currencyCode, exchangeMultiplier, parentMultiplier)
files.push({
contents: bitwaveFile,
mimeType: 'text/comma-separated-values',
fileName: fileName + '.bitwave.csv'
})
formats.push('Bitwave CSV')
}

const title = 'Share Transactions ' + formats.join(', ')
if (Platform.OS === 'android') {
await this.shareAndroid(title, files[0])
Expand Down Expand Up @@ -268,7 +379,10 @@ class TransactionsExportSceneComponent extends React.PureComponent<Props, State>

export const TransactionsExportScene = connect<StateProps, DispatchProps, OwnProps>(
(state, { route: { params } }) => ({
multiplier: getDisplayDenomination(state, params.sourceWallet.currencyInfo.pluginId, params.currencyCode).multiplier
multiplier: getDisplayDenomination(state, params.sourceWallet.currencyInfo.pluginId, params.currencyCode).multiplier,
exchangeMultiplier: getExchangeDenomination(state, params.sourceWallet.currencyInfo.pluginId, params.currencyCode).multiplier,
parentMultiplier: getExchangeDenomination(state, params.sourceWallet.currencyInfo.pluginId, params.sourceWallet.currencyInfo.currencyCode).multiplier,
tokenId: getTokenId(state.core.account, params.sourceWallet.currencyInfo.pluginId, params.currencyCode)
}),
dispatch => ({
updateTxsFiatDispatch: async (wallet: EdgeCurrencyWallet, currencyCode: string, txs: EdgeTransaction[]) =>
Expand Down
4 changes: 4 additions & 0 deletions src/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,10 @@ const strings = {
string_end: 'End',
export_transaction_quickbooks_qbo: 'Quickbooks QBO',
export_transaction_csv: 'CSV',
export_transaction_bitwave_csv: 'Bitwave CSV',
export_transaction_bitwave_accountid_modal_title: 'Bitwave Account ID',
export_transaction_bitwave_accountid_modal_message: 'Please enter the Bitwave account ID for this wallet',
export_transaction_bitwave_accountid_modal_input_label: 'Account ID',
string_export: 'Export',
string_status: 'Status',
string_fee: 'Fee',
Expand Down
4 changes: 4 additions & 0 deletions src/locales/strings/enUS.json
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,10 @@
"string_end": "End",
"export_transaction_quickbooks_qbo": "Quickbooks QBO",
"export_transaction_csv": "CSV",
"export_transaction_bitwave_csv": "Bitwave CSV",
"export_transaction_bitwave_accountid_modal_title": "Bitwave Account ID",
"export_transaction_bitwave_accountid_modal_message": "Please enter the Bitwave account ID for this wallet",
"export_transaction_bitwave_accountid_modal_input_label": "Account ID",
"string_export": "Export",
"string_status": "Status",
"string_fee": "Fee",
Expand Down
Loading