Esempio n. 1
0
  @Test
  public void shouldRecursivelyDeleteAccount() {
    Account account = new Account("Parent");
    Account account2 = new Account("Child");
    account2.setParentUID(account.getUID());

    Transaction transaction = new Transaction("Random");
    account2.addTransaction(transaction);

    Split split = new Split(Money.getZeroInstance(), account.getUID());
    transaction.addSplit(split);
    transaction.addSplit(split.createPair(account2.getUID()));

    mAccountsDbAdapter.addRecord(account);
    mAccountsDbAdapter.addRecord(account2);

    assertThat(mAccountsDbAdapter.getRecordsCount()).isEqualTo(3);
    assertThat(mTransactionsDbAdapter.getRecordsCount()).isEqualTo(1);
    assertThat(mSplitsDbAdapter.getRecordsCount()).isEqualTo(2);

    boolean result =
        mAccountsDbAdapter.recursiveDeleteAccount(mAccountsDbAdapter.getID(account.getUID()));
    assertThat(result).isTrue();

    assertThat(mAccountsDbAdapter.getRecordsCount()).isEqualTo(1); // the root account
    assertThat(mTransactionsDbAdapter.getRecordsCount()).isZero();
    assertThat(mSplitsDbAdapter.getRecordsCount()).isZero();
  }
  @Test
  public void deletingTransactionsShouldDeleteSplits() {
    Transaction transaction = new Transaction("");
    Split split = new Split(Money.getZeroInstance(), alphaAccount.getUID());
    transaction.addSplit(split);
    mTransactionsDbAdapter.addRecord(transaction);

    assertThat(mSplitsDbAdapter.getSplitsForTransaction(transaction.getUID())).hasSize(1);

    mTransactionsDbAdapter.deleteRecord(transaction.getUID());
    assertThat(mSplitsDbAdapter.getSplitsForTransaction(transaction.getUID())).hasSize(0);
  }
Esempio n. 3
0
  private void init(@Nullable SQLiteDatabase db) {
    if (db == null) {
      mAccountsDbAdapter = AccountsDbAdapter.getInstance();
      mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
      mScheduledActionsDbAdapter = ScheduledActionDbAdapter.getInstance();
      mCommoditiesDbAdapter = CommoditiesDbAdapter.getInstance();
      mPricesDbAdapter = PricesDbAdapter.getInstance();
    } else {
      mTransactionsDbAdapter = new TransactionsDbAdapter(db, new SplitsDbAdapter(db));
      mAccountsDbAdapter = new AccountsDbAdapter(db, mTransactionsDbAdapter);
      mScheduledActionsDbAdapter = new ScheduledActionDbAdapter(db);
      mCommoditiesDbAdapter = new CommoditiesDbAdapter(db);
      mPricesDbAdapter = new PricesDbAdapter(db);
    }

    mContent = new StringBuilder();

    mAccountList = new ArrayList<>();
    mAccountMap = new HashMap<>();
    mTransactionList = new ArrayList<>();
    mScheduledActionsList = new ArrayList<>();

    mTemplatAccountList = new ArrayList<>();
    mTemplateTransactions = new ArrayList<>();
    mTemplateAccountToTransactionMap = new HashMap<>();

    mAutoBalanceSplits = new ArrayList<>();

    mPriceList = new ArrayList<>();
  }
Esempio n. 4
0
  @Before
  public void setUp() throws Exception {

    mSplitsDbAdapter = SplitsDbAdapter.getInstance();
    mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
    mAccountsDbAdapter = AccountsDbAdapter.getInstance();
  }
Esempio n. 5
0
  @Test
  public void shouldClearAllTablesWhenDeletingAllAccounts() {
    Account account = new Account("Test");
    Transaction transaction = new Transaction("Test description");
    Split split = new Split(Money.getZeroInstance(), account.getUID());
    transaction.addSplit(split);
    Account account2 = new Account("Transfer account");
    transaction.addSplit(split.createPair(account2.getUID()));

    mAccountsDbAdapter.addRecord(account);
    mAccountsDbAdapter.addRecord(account2);

    ScheduledAction scheduledAction = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
    scheduledAction.setActionUID("Test-uid");
    ScheduledActionDbAdapter scheduledActionDbAdapter = ScheduledActionDbAdapter.getInstance();

    scheduledActionDbAdapter.addRecord(scheduledAction);

    mAccountsDbAdapter.deleteAllRecords();

    assertThat(mAccountsDbAdapter.getRecordsCount()).isZero();
    assertThat(mTransactionsDbAdapter.getRecordsCount()).isZero();
    assertThat(mSplitsDbAdapter.getRecordsCount()).isZero();
    assertThat(scheduledActionDbAdapter.getRecordsCount()).isZero();
  }
Esempio n. 6
0
  @Test
  public void shouldAddAccountsToDatabase() {
    Account account1 = new Account("AlphaAccount");
    Account account2 = new Account("BetaAccount");
    Transaction transaction = new Transaction("MyTransaction");
    Split split = new Split(Money.getZeroInstance(), account1.getUID());
    transaction.addSplit(split);
    transaction.addSplit(split.createPair(account2.getUID()));
    account1.addTransaction(transaction);
    account2.addTransaction(transaction);

    mAccountsDbAdapter.addRecord(account1);
    mAccountsDbAdapter.addRecord(account2);

    Account firstAccount = mAccountsDbAdapter.getRecord(account1.getUID());
    assertThat(firstAccount).isNotNull();
    assertThat(firstAccount.getUID()).isEqualTo(account1.getUID());
    assertThat(firstAccount.getFullName()).isEqualTo(account1.getFullName());

    Account secondAccount = mAccountsDbAdapter.getRecord(account2.getUID());
    assertThat(secondAccount).isNotNull();
    assertThat(secondAccount.getUID()).isEqualTo(account2.getUID());

    assertThat(mTransactionsDbAdapter.getRecordsCount()).isEqualTo(1);
  }
  @Test
  public void shouldBalanceTransactionsOnSave() {
    Transaction transaction = new Transaction("Auto balance");
    Split split =
        new Split(
            new Money(BigDecimal.TEN, Currency.getInstance(Money.DEFAULT_CURRENCY_CODE)),
            alphaAccount.getUID());

    transaction.addSplit(split);

    mTransactionsDbAdapter.addRecord(transaction);

    Transaction trn = mTransactionsDbAdapter.getRecord(transaction.getUID());
    assertThat(trn.getSplits()).hasSize(2);

    String imbalanceAccountUID =
        mAccountsDbAdapter.getImbalanceAccountUID(
            Currency.getInstance(Money.DEFAULT_CURRENCY_CODE));
    assertThat(trn.getSplits()).extracting("mAccountUID").contains(imbalanceAccountUID);
  }
  @Test
  public void testComputeBalance() {
    Transaction transaction = new Transaction("Compute");
    Money firstSplitAmount = new Money("4.99", DEFAULT_CURRENCY.getCurrencyCode());
    Split split = new Split(firstSplitAmount, alphaAccount.getUID());
    transaction.addSplit(split);
    Money secondSplitAmount = new Money("3.50", DEFAULT_CURRENCY.getCurrencyCode());
    split = new Split(secondSplitAmount, bravoAccount.getUID());
    transaction.addSplit(split);

    mTransactionsDbAdapter.addRecord(transaction);

    // balance is negated because the CASH account has inverse normal balance
    transaction = mTransactionsDbAdapter.getRecord(transaction.getUID());
    Money savedBalance = transaction.getBalance(alphaAccount.getUID());
    assertThat(savedBalance).isEqualTo(firstSplitAmount.negate());

    savedBalance = transaction.getBalance(bravoAccount.getUID());
    assertThat(savedBalance.getNumerator()).isEqualTo(secondSplitAmount.negate().getNumerator());
    assertThat(savedBalance.getCurrency()).isEqualTo(secondSplitAmount.getCurrency());
  }
  @Test
  public void testTransactionsAreTimeSorted() {
    Transaction t1 = new Transaction("T800");
    t1.setTime(System.currentTimeMillis() - 10000);
    Split split = new Split(Money.getZeroInstance(), alphaAccount.getUID());
    t1.addSplit(split);
    t1.addSplit(split.createPair(bravoAccount.getUID()));

    Transaction t2 = new Transaction("T1000");
    t2.setTime(System.currentTimeMillis());
    Split split2 = new Split(new Money("23.50"), bravoAccount.getUID());
    t2.addSplit(split2);
    t2.addSplit(split2.createPair(alphaAccount.getUID()));

    mTransactionsDbAdapter.addRecord(t1);
    mTransactionsDbAdapter.addRecord(t2);

    List<Transaction> transactionsList =
        mTransactionsDbAdapter.getAllTransactionsForAccount(alphaAccount.getUID());
    assertThat(transactionsList).contains(t2, Index.atIndex(0));
    assertThat(transactionsList).contains(t1, Index.atIndex(1));
  }
  @Before
  public void setUp() throws Exception {
    mSplitsDbAdapter = SplitsDbAdapter.getInstance();
    mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
    mAccountsDbAdapter = AccountsDbAdapter.getInstance();

    alphaAccount = new Account(ALPHA_ACCOUNT_NAME);
    bravoAccount = new Account(BRAVO_ACCOUNT_NAME);

    mAccountsDbAdapter.addRecord(bravoAccount);
    mAccountsDbAdapter.addRecord(alphaAccount);

    mTestSplit = new Split(new Money(BigDecimal.TEN, DEFAULT_CURRENCY), alphaAccount.getUID());
  }
Esempio n. 11
0
  /** Tests the foreign key constraint "ON DELETE CASCADE" between accounts and splits */
  @Test
  public void shouldDeleteSplitsWhenAccountDeleted() {
    Account first = new Account(ALPHA_ACCOUNT_NAME);
    first.setUID(ALPHA_ACCOUNT_NAME);
    Account second = new Account(BRAVO_ACCOUNT_NAME);
    second.setUID(BRAVO_ACCOUNT_NAME);

    mAccountsDbAdapter.addRecord(second);
    mAccountsDbAdapter.addRecord(first);

    Transaction transaction = new Transaction("TestTrn");
    Split split = new Split(Money.getZeroInstance(), ALPHA_ACCOUNT_NAME);
    transaction.addSplit(split);
    transaction.addSplit(split.createPair(BRAVO_ACCOUNT_NAME));

    mTransactionsDbAdapter.addRecord(transaction);

    mAccountsDbAdapter.deleteRecord(ALPHA_ACCOUNT_NAME);

    Transaction trxn = mTransactionsDbAdapter.getRecord(transaction.getUID());
    assertThat(trxn.getSplits().size()).isEqualTo(1);
    assertThat(trxn.getSplits().get(0).getAccountUID()).isEqualTo(BRAVO_ACCOUNT_NAME);
  }
  @Override
  public void onReceive(Context context, Intent intent) {
    Log.i("TransactionRecorder", "Received transaction recording intent");
    Bundle args = intent.getExtras();
    String name = args.getString(Intent.EXTRA_TITLE);
    String note = args.getString(Intent.EXTRA_TEXT);
    BigDecimal amountBigDecimal = (BigDecimal) args.getSerializable(Transaction.EXTRA_AMOUNT);
    String currencyCode = args.getString(Account.EXTRA_CURRENCY_CODE);
    if (currencyCode == null) currencyCode = Money.DEFAULT_CURRENCY_CODE;

    String accountUID = args.getString(Transaction.EXTRA_ACCOUNT_UID);
    if (accountUID == null) // if no account was assigned, throw an exception
    throw new IllegalArgumentException("No account specified for the transaction");

    String doubleAccountUID = args.getString(Transaction.EXTRA_DOUBLE_ACCOUNT_UID);
    if (doubleAccountUID == null || doubleAccountUID.length() == 0)
      doubleAccountUID =
          QifHelper.getImbalanceAccountName(Currency.getInstance(Money.DEFAULT_CURRENCY_CODE));
    Transaction.TransactionType type =
        Transaction.TransactionType.valueOf(args.getString(Transaction.EXTRA_TRANSACTION_TYPE));

    Money amount = new Money(amountBigDecimal, Currency.getInstance(currencyCode));
    Transaction transaction = new Transaction(amount, name);
    transaction.setTime(System.currentTimeMillis());
    transaction.setDescription(note);
    transaction.setAccountUID(accountUID);
    transaction.setDoubleEntryAccountUID(doubleAccountUID);
    transaction.setTransactionType(type);

    TransactionsDbAdapter transacionsDbAdapter = new TransactionsDbAdapter(context);
    transacionsDbAdapter.addTransaction(transaction);

    WidgetConfigurationActivity.updateAllWidgets(context);

    transacionsDbAdapter.close();
  }
Esempio n. 13
0
  @Test
  public void shouldAddTransactionsAndSplitsWhenAddingAccounts() {
    Account account = new Account("Test");
    mAccountsDbAdapter.addRecord(account);

    Transaction transaction = new Transaction("Test description");
    Split split = new Split(Money.getZeroInstance(), account.getUID());
    transaction.addSplit(split);
    Account account1 = new Account("Transfer account");
    transaction.addSplit(split.createPair(account1.getUID()));
    account1.addTransaction(transaction);

    mAccountsDbAdapter.addRecord(account1);

    assertThat(mTransactionsDbAdapter.getRecordsCount()).isEqualTo(1);
    assertThat(mSplitsDbAdapter.getRecordsCount()).isEqualTo(2);
    assertThat(mAccountsDbAdapter.getRecordsCount())
        .isEqualTo(3); // ROOT account automatically added
  }
Esempio n. 14
0
  @Test
  public void shouldComputeAccountBalanceCorrectly() {
    Account account = new Account("Test", Commodity.USD);
    account.setAccountType(AccountType.ASSET); // debit normal account balance
    Account transferAcct = new Account("Transfer");

    mAccountsDbAdapter.addRecord(account);
    mAccountsDbAdapter.addRecord(transferAcct);

    Transaction transaction = new Transaction("Test description");
    mTransactionsDbAdapter.addRecord(transaction);
    Split split = new Split(new Money(BigDecimal.TEN, Commodity.USD), account.getUID());
    split.setTransactionUID(transaction.getUID());
    split.setType(TransactionType.DEBIT);
    mSplitsDbAdapter.addRecord(split);

    split = new Split(new Money("4.99", "USD"), account.getUID());
    split.setTransactionUID(transaction.getUID());
    split.setType(TransactionType.DEBIT);
    mSplitsDbAdapter.addRecord(split);

    split = new Split(new Money("1.19", "USD"), account.getUID());
    split.setTransactionUID(transaction.getUID());
    split.setType(TransactionType.CREDIT);
    mSplitsDbAdapter.addRecord(split);

    split = new Split(new Money("3.49", "EUR"), account.getUID());
    split.setTransactionUID(transaction.getUID());
    split.setType(TransactionType.DEBIT);
    mSplitsDbAdapter.addRecord(split);

    split = new Split(new Money("8.39", "USD"), transferAcct.getUID());
    split.setTransactionUID(transaction.getUID());
    mSplitsDbAdapter.addRecord(split);

    // balance computation ignores the currency of the split
    Money balance = mAccountsDbAdapter.getAccountBalance(account.getUID());
    Money expectedBalance = new Money("17.29", "USD"); // EUR splits should be ignored

    assertThat(balance).isEqualTo(expectedBalance);
  }
Esempio n. 15
0
  @Override
  public void endDocument() throws SAXException {
    super.endDocument();
    HashMap<String, String> mapFullName = new HashMap<>(mAccountList.size());
    HashMap<String, Account> mapImbalanceAccount = new HashMap<>();

    // The XML has no ROOT, create one
    if (mRootAccount == null) {
      mRootAccount = new Account("ROOT");
      mRootAccount.setAccountType(AccountType.ROOT);
      mAccountList.add(mRootAccount);
      mAccountMap.put(mRootAccount.getUID(), mRootAccount);
    }

    String imbalancePrefix = AccountsDbAdapter.getImbalanceAccountPrefix();

    // Add all account without a parent to ROOT, and collect top level imbalance accounts
    for (Account account : mAccountList) {
      mapFullName.put(account.getUID(), null);
      boolean topLevel = false;
      if (account.getParentUID() == null && account.getAccountType() != AccountType.ROOT) {
        account.setParentUID(mRootAccount.getUID());
        topLevel = true;
      }
      if (topLevel || (mRootAccount.getUID().equals(account.getParentUID()))) {
        if (account.getName().startsWith(imbalancePrefix)) {
          mapImbalanceAccount.put(account.getName().substring(imbalancePrefix.length()), account);
        }
      }
    }

    // Set the account for created balancing splits to correct imbalance accounts
    for (Split split : mAutoBalanceSplits) {
      String currencyCode = split.getAccountUID();
      Account imbAccount = mapImbalanceAccount.get(currencyCode);
      if (imbAccount == null) {
        imbAccount =
            new Account(imbalancePrefix + currencyCode, Currency.getInstance(currencyCode));
        imbAccount.setParentUID(mRootAccount.getUID());
        imbAccount.setAccountType(AccountType.BANK);
        mapImbalanceAccount.put(currencyCode, imbAccount);
        mAccountList.add(imbAccount);
      }
      split.setAccountUID(imbAccount.getUID());
    }

    java.util.Stack<Account> stack = new Stack<>();
    for (Account account : mAccountList) {
      if (mapFullName.get(account.getUID()) != null) {
        continue;
      }
      stack.push(account);
      String parentAccountFullName;
      while (!stack.isEmpty()) {
        Account acc = stack.peek();
        if (acc.getAccountType() == AccountType.ROOT) {
          // ROOT_ACCOUNT_FULL_NAME should ensure ROOT always sorts first
          mapFullName.put(acc.getUID(), AccountsDbAdapter.ROOT_ACCOUNT_FULL_NAME);
          stack.pop();
          continue;
        }
        String parentUID = acc.getParentUID();
        Account parentAccount = mAccountMap.get(parentUID);
        // ROOT account will be added if not exist, so now anly ROOT
        // has an empty parent
        if (parentAccount.getAccountType() == AccountType.ROOT) {
          // top level account, full name is the same as its name
          mapFullName.put(acc.getUID(), acc.getName());
          stack.pop();
          continue;
        }
        parentAccountFullName = mapFullName.get(parentUID);
        if (parentAccountFullName == null) {
          // non-top-level account, parent full name still unknown
          stack.push(parentAccount);
          continue;
        }
        mapFullName.put(
            acc.getUID(),
            parentAccountFullName + AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR + acc.getName());
        stack.pop();
      }
    }
    for (Account account : mAccountList) {
      account.setFullName(mapFullName.get(account.getUID()));
    }
    long startTime = System.nanoTime();
    mAccountsDbAdapter.beginTransaction();
    Log.d(getClass().getSimpleName(), "bulk insert starts");
    try {
      Log.d(getClass().getSimpleName(), "before clean up db");
      mAccountsDbAdapter.deleteAllRecords();
      Log.d(
          getClass().getSimpleName(),
          String.format("deb clean up done %d ns", System.nanoTime() - startTime));
      long nAccounts = mAccountsDbAdapter.bulkAddRecords(mAccountList);
      Log.d("Handler:", String.format("%d accounts inserted", nAccounts));
      // We need to add scheduled actions first because there is a foreign key constraint on
      // transactions
      // which are generated from scheduled actions (we do auto-create some transactions during
      // import)
      long nSchedActions = mScheduledActionsDbAdapter.bulkAddRecords(mScheduledActionsList);
      Log.d("Handler:", String.format("%d scheduled actions inserted", nSchedActions));

      long nTempTransactions = mTransactionsDbAdapter.bulkAddRecords(mTemplateTransactions);
      Log.d("Handler:", String.format("%d template transactions inserted", nTempTransactions));

      long nTransactions = mTransactionsDbAdapter.bulkAddRecords(mTransactionList);
      Log.d("Handler:", String.format("%d transactions inserted", nTransactions));

      long nPrices = mPricesDbAdapter.bulkAddRecords(mPriceList);
      Log.d(getClass().getSimpleName(), String.format("%d prices inserted", nPrices));

      long endTime = System.nanoTime();
      Log.d(getClass().getSimpleName(), String.format("bulk insert time: %d", endTime - startTime));

      mAccountsDbAdapter.setTransactionSuccessful();
    } finally {
      mAccountsDbAdapter.endTransaction();
    }
  }
  /**
   * Serializes {@link ScheduledAction}s from the database to XML
   *
   * @param xmlSerializer XML serializer
   * @throws IOException
   */
  private void exportScheduledTransactions(XmlSerializer xmlSerializer) throws IOException {
    // for now we will export only scheduled transactions to XML
    Cursor cursor =
        mScheduledActionDbAdapter.fetchAllRecords(
            ScheduledActionEntry.COLUMN_TYPE + "=?",
            new String[] {ScheduledAction.ActionType.TRANSACTION.name()});

    while (cursor.moveToNext()) {
      String actionUID =
          cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_ACTION_UID));
      Account accountUID = mTransactionToTemplateAccountMap.get(actionUID);

      if (accountUID
          == null) // if the action UID does not belong to a transaction we've seen before, skip it
      continue;

      xmlSerializer.startTag(null, GncXmlHelper.TAG_SCHEDULED_ACTION);
      xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.BOOK_VERSION);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ID);

      String nameUID = accountUID.getName();
      xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
      xmlSerializer.text(nameUID);
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ID);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_NAME);

      ScheduledAction.ActionType actionType =
          ScheduledAction.ActionType.valueOf(
              cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_TYPE)));
      if (actionType == ScheduledAction.ActionType.TRANSACTION) {
        String description =
            TransactionsDbAdapter.getInstance()
                .getAttribute(actionUID, TransactionEntry.COLUMN_DESCRIPTION);
        xmlSerializer.text(description);
      } else {
        xmlSerializer.text(actionType.name());
      }
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_NAME);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ENABLED);
      boolean enabled =
          cursor.getShort(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_ENABLED)) > 0;
      xmlSerializer.text(enabled ? "y" : "n");
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ENABLED);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE);
      xmlSerializer.text("n"); // we do not want transactions auto-created on the desktop.
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE_NOTIFY);
      xmlSerializer.text(
          "n"); // TODO: if we ever support notifying before creating a scheduled transaction, then
                // update this
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_AUTO_CREATE_NOTIFY);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ADVANCE_CREATE_DAYS);
      xmlSerializer.text("0");
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ADVANCE_CREATE_DAYS);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_ADVANCE_REMIND_DAYS);
      xmlSerializer.text("0");
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_ADVANCE_REMIND_DAYS);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_INSTANCE_COUNT);
      String scheduledActionUID =
          cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_UID));
      long instanceCount = mScheduledActionDbAdapter.getActionInstanceCount(scheduledActionUID);
      xmlSerializer.text(Long.toString(instanceCount));
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_INSTANCE_COUNT);

      // start date
      String createdTimestamp =
          cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_CREATED_AT));
      long scheduleStartTime = Timestamp.valueOf(createdTimestamp).getTime();
      serializeDate(xmlSerializer, GncXmlHelper.TAG_SX_START, scheduleStartTime);

      long lastRunTime =
          cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_LAST_RUN));
      if (lastRunTime > 0) {
        serializeDate(xmlSerializer, GncXmlHelper.TAG_SX_LAST, lastRunTime);
      }

      long endTime =
          cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_END_TIME));
      if (endTime > 0) {
        // end date
        serializeDate(xmlSerializer, GncXmlHelper.TAG_SX_END, endTime);
      } else { // add number of occurrences
        int totalFrequency =
            cursor.getInt(
                cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_TOTAL_FREQUENCY));
        xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_NUM_OCCUR);
        xmlSerializer.text(Integer.toString(totalFrequency));
        xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_NUM_OCCUR);

        // remaining occurrences
        int executionCount =
            cursor.getInt(
                cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_EXECUTION_COUNT));
        xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_REM_OCCUR);
        xmlSerializer.text(Integer.toString(totalFrequency - executionCount));
        xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_REM_OCCUR);
      }

      String tag = cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_TAG));
      if (tag != null && !tag.isEmpty()) {
        xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_TAG);
        xmlSerializer.text(tag);
        xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_TAG);
      }

      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_TEMPL_ACCOUNT);
      xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
      xmlSerializer.text(accountUID.getUID());
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_TEMPL_ACCOUNT);

      xmlSerializer.startTag(null, GncXmlHelper.TAG_SX_SCHEDULE);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_RECURRENCE);
      xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.RECURRENCE_VERSION);
      long period =
          cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_PERIOD));
      PeriodType periodType = ScheduledAction.getPeriodType(period);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_RX_MULT);
      xmlSerializer.text(String.valueOf(periodType.getMultiplier()));
      xmlSerializer.endTag(null, GncXmlHelper.TAG_RX_MULT);
      xmlSerializer.startTag(null, GncXmlHelper.TAG_RX_PERIOD_TYPE);
      xmlSerializer.text(periodType.name().toLowerCase());
      xmlSerializer.endTag(null, GncXmlHelper.TAG_RX_PERIOD_TYPE);

      long recurrenceStartTime =
          cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_START_TIME));
      serializeDate(xmlSerializer, GncXmlHelper.TAG_RX_START, recurrenceStartTime);

      xmlSerializer.endTag(null, GncXmlHelper.TAG_RECURRENCE);
      xmlSerializer.endTag(null, GncXmlHelper.TAG_SX_SCHEDULE);

      xmlSerializer.endTag(null, GncXmlHelper.TAG_SCHEDULED_ACTION);
    }
  }