/* * Copyright (C) 2012-2016 The Android Money Manager Ex Project Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.money.manager.ex.transactions; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcelable; import android.preference.PreferenceManager; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.iconics.IconicsDrawable; import com.money.manager.ex.Constants; import com.money.manager.ex.MoneyManagerApplication; import com.money.manager.ex.R; import com.money.manager.ex.common.MmxBaseFragmentActivity; import com.money.manager.ex.common.events.AmountEnteredEvent; import com.money.manager.ex.core.MenuHelper; import com.money.manager.ex.core.UIHelper; import com.money.manager.ex.database.ISplitTransaction; import com.money.manager.ex.database.ITransactionEntity; import com.money.manager.ex.datalayer.PayeeRepository; import com.money.manager.ex.domainmodel.RecurringTransaction; import com.money.manager.ex.domainmodel.SplitCategory; import com.money.manager.ex.domainmodel.SplitRecurringCategory; import com.money.manager.ex.servicelayer.CategoryService; import com.money.manager.ex.servicelayer.PayeeService; import com.money.manager.ex.servicelayer.RecurringTransactionService; import com.money.manager.ex.core.Core; import com.money.manager.ex.core.TransactionTypes; import com.money.manager.ex.datalayer.AccountRepository; import com.money.manager.ex.datalayer.AccountTransactionRepository; import com.money.manager.ex.datalayer.RecurringTransactionRepository; import com.money.manager.ex.datalayer.SplitCategoriesRepository; import com.money.manager.ex.domainmodel.AccountTransaction; import com.money.manager.ex.domainmodel.Payee; import com.money.manager.ex.settings.AppSettings; import com.money.manager.ex.settings.PreferenceConstants; import com.money.manager.ex.transactions.events.DialogNegativeClickedEvent; import com.money.manager.ex.transactions.events.DialogPositiveClickedEvent; import com.money.manager.ex.utils.MmxDate; import com.squareup.sqlbrite.BriteDatabase; import org.greenrobot.eventbus.Subscribe; import org.parceler.Parcels; import java.util.ArrayList; import javax.inject.Inject; import icepick.State; import timber.log.Timber; /** * Activity for editing Checking Account Transaction */ public class CheckingTransactionEditActivity extends MmxBaseFragmentActivity { @State public String mIntentAction; // bill deposits public int mRecurringTransactionId = Constants.NOT_SET; @Inject BriteDatabase database; private EditTransactionCommonFunctions mCommon; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit_checking_account_transaction); MoneyManagerApplication.getApp().iocComponent.inject(this); ITransactionEntity model = AccountTransaction.create(); mCommon = new EditTransactionCommonFunctions(this, model, database); // showStandardToolbarActions(); mCommon.initializeToolbar(); // restore state, if any. if ((savedInstanceState != null)) { restoreInstanceState(savedInstanceState); } // Controls need to be at the beginning as they are referenced throughout the code. mCommon.findControls(this); // manage intent if (getIntent() != null) { boolean handled = handleIntent(savedInstanceState); if (!handled) { finish(); return; } } initializeInputControls(); // refresh user interface mCommon.onTransactionTypeChanged(mCommon.transactionEntity.getTransactionType()); mCommon.showPayeeName(); mCommon.displayCategoryName(); mCommon.setDirty(false); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); mCommon.onActivityResult(requestCode, resultCode, data); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuHelper helper = new MenuHelper(this, menu); helper.addSaveToolbarIcon(); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: return onActionCancelClick(); case MenuHelper.save: return onActionDoneClick(); default: return super.onOptionsItemSelected(item); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Save the whole transaction. outState.putParcelable(EditTransactionActivityConstants.KEY_TRANSACTION_ENTITY, Parcels.wrap(mCommon.transactionEntity)); // update the state interface outState.putString(EditTransactionActivityConstants.KEY_TO_ACCOUNT_NAME, mCommon.mToAccountName); outState.putString(EditTransactionActivityConstants.KEY_TRANS_CODE, mCommon.getTransactionType()); outState.putString(EditTransactionActivityConstants.KEY_PAYEE_NAME, mCommon.payeeName); outState.putString(EditTransactionActivityConstants.KEY_CATEGORY_NAME, mCommon.categoryName); outState.putString(EditTransactionActivityConstants.KEY_SUBCATEGORY_NAME, mCommon.subCategoryName); outState.putParcelable(EditTransactionActivityConstants.KEY_SPLIT_TRANSACTION, Parcels.wrap(mCommon.mSplitTransactions)); outState.putParcelable(EditTransactionActivityConstants.KEY_SPLIT_TRANSACTION_DELETED, Parcels.wrap(mCommon.mSplitTransactionsDeleted)); outState.putInt(EditTransactionActivityConstants.KEY_BDID_ID, mRecurringTransactionId); // outState.putString(EditTransactionActivityConstants.KEY_ACTION, mIntentAction); } @Override public boolean onActionCancelClick() { return mCommon.onActionCancelClick(); } @Override public void onBackPressed() { onActionCancelClick(); } @Override public boolean onActionDoneClick() { if (saveData()) { setResult(RESULT_OK); finish(); return true; } else { return false; } } // Events @Subscribe public void onEvent(AmountEnteredEvent event) { int id = Integer.parseInt(event.requestId); mCommon.onFinishedInputAmountDialog(id, event.amount); } /** * Handle user's confirmation to delete any Split Categories when switching to * Transfer transaction type. * @param event */ @Subscribe public void onEvent(DialogPositiveClickedEvent event) { mCommon.confirmDeletingCategories(); } @Subscribe public void onEvent(DialogNegativeClickedEvent event) { mCommon.cancelChangingTransactionToTransfer(); } /* // Private */ private boolean createSplitCategoriesFromRecurringTransaction() { // check if category and sub-category are not set. if(!(mCommon.transactionEntity.getCategoryId() <= 0 && mCommon.transactionEntity.getSubcategoryId() <= 0)) return false; // Adding transactions to the split list will set the Split checkbox and the category name. // create split transactions RecurringTransactionService recurringTransaction = new RecurringTransactionService(mRecurringTransactionId, this); ArrayList<ISplitTransaction> splitTemplates = recurringTransaction.loadSplitTransactions(); if(mCommon.mSplitTransactions == null) mCommon.mSplitTransactions = new ArrayList<>(); // For each of the templates, create a new record. for(int i = 0; i <= splitTemplates.size() - 1; i++) { SplitRecurringCategory record = (SplitRecurringCategory) splitTemplates.get(i); SplitCategory newSplit = new SplitCategory(); newSplit.setAmount(record.getAmount()); newSplit.setCategoryId(record.getCategoryId()); newSplit.setSubcategoryId(record.getSubcategoryId()); mCommon.mSplitTransactions.add(newSplit); } return true; } private void duplicateTransaction() { // Reset transaction id. To be inserted when the transaction is saved. mCommon.transactionEntity.setId(null); // Use today's date. mCommon.transactionEntity.setDate(new MmxDate().toDate()); // Remove transaction id in split categories. if (mCommon.mSplitTransactions != null) { // Reset ids so that the transactions get inserted on update. for (ISplitTransaction split : mCommon.mSplitTransactions) { split.setId(Constants.NOT_SET); } } } /** * Get any parameters, if sent, when intent was raised. This is used when called * from Tasker or any external caller. * @param intent The intent received. */ private void externalIntegration(Intent intent) { Uri data = intent.getData(); if (data == null) return; IntentDataParameters parameters = IntentDataParameters.parseData(this, data); // transaction type mCommon.transactionEntity.setTransactionType(parameters.transactionType); if (parameters.accountId > 0) { this.mCommon.transactionEntity.setAccountId(parameters.accountId); } mCommon.transactionEntity.setAmount(parameters.amount); // payee if (parameters.payeeId > 0) { this.mCommon.transactionEntity.setPayeeId(parameters.payeeId); this.mCommon.payeeName = parameters.payeeName; } else { // create payee if it does not exist if (parameters.payeeName != null) { PayeeService payeeService = new PayeeService(this); Payee payee = payeeService.createNew(parameters.payeeName); mCommon.transactionEntity.setPayeeId(payee.getId()); mCommon.payeeName = payee.getName(); } } // category if (parameters.categoryId > 0) { mCommon.transactionEntity.setCategoryId(parameters.categoryId); mCommon.categoryName = parameters.categoryName; } else { // No id sent. Create a category if it was sent but does not exist (id not found by the parser). if (parameters.categoryName != null) { CategoryService newCategory = new CategoryService(this); mCommon.transactionEntity.setCategoryId(newCategory.createNew(parameters.categoryName)); mCommon.categoryName = parameters.categoryName; } } } private void initializeInputControls() { // Transaction Type mCommon.initTransactionTypeSelector(); // status mCommon.initStatusSelector(); // Transaction date mCommon.initDateSelector(); // account(s) mCommon.initAccountSelectors(); // Payee mCommon.initPayeeControls(); // Category mCommon.initCategoryControls(SplitCategory.class.getSimpleName()); // Split Categories mCommon.initSplitCategories(); // mark checked if there are existing split categories. boolean hasSplit = mCommon.hasSplitCategories(); mCommon.setSplit(hasSplit); // Amount and total amount mCommon.initAmountSelectors(); // Transaction Number mCommon.initTransactionNumberControls(); // notes mCommon.initNotesControls(); } private boolean loadTransaction(int transId) { AccountTransactionRepository repo = new AccountTransactionRepository(this); AccountTransaction tx = repo.load(transId); if (tx == null) return false; mCommon.transactionEntity = tx; // Load Split Categories. if (mCommon.mSplitTransactions == null) { SplitCategoriesRepository splitRepo = new SplitCategoriesRepository(this); mCommon.mSplitTransactions = splitRepo.loadSplitCategoriesFor(transId); } AccountRepository accountRepository = new AccountRepository(this); mCommon.mToAccountName = accountRepository.loadName(mCommon.transactionEntity.getAccountToId()); mCommon.loadPayeeName(mCommon.transactionEntity.getPayeeId()); mCommon.loadCategoryName(); return true; } private boolean loadRecurringTransaction(int recurringTransactionId) { try { return loadRecurringTransactionInternal(recurringTransactionId); } catch (RuntimeException ex) { Timber.e(ex, "loading recurring transaction"); return false; } } /** * Loads a recurring transaction data when entering a recurring transaction. * @param recurringTransactionId Id of the recurring transaction. * @return A boolean indicating whether the operation was successful. */ private boolean loadRecurringTransactionInternal(int recurringTransactionId) { RecurringTransactionRepository repo = new RecurringTransactionRepository(this); RecurringTransaction recurringTx = repo.load(recurringTransactionId); if (recurringTx == null) return false; // Copy properties from recurring transaction mCommon.transactionEntity.setDate(recurringTx.getPaymentDate()); mCommon.transactionEntity.setAccountId(recurringTx.getAccountId()); mCommon.transactionEntity.setAccountToId(recurringTx.getToAccountId()); String transCode = recurringTx.getTransactionCode(); mCommon.transactionEntity.setTransactionType(TransactionTypes.valueOf(transCode)); mCommon.transactionEntity.setStatus(recurringTx.getStatus()); mCommon.transactionEntity.setAmount(recurringTx.getAmount()); mCommon.transactionEntity.setAmountTo(recurringTx.getAmountTo()); mCommon.transactionEntity.setPayeeId(recurringTx.getPayeeId()); mCommon.transactionEntity.setCategoryId(recurringTx.getCategoryId()); mCommon.transactionEntity.setSubcategoryId(recurringTx.getSubcategoryId()); mCommon.transactionEntity.setTransactionNumber(recurringTx.getTransactionNumber()); mCommon.transactionEntity.setNotes(recurringTx.getNotes()); AccountRepository accountRepository = new AccountRepository(this); mCommon.mToAccountName = accountRepository.loadName(mCommon.transactionEntity.getAccountToId()); mCommon.loadPayeeName(mCommon.transactionEntity.getPayeeId()); mCommon.loadCategoryName(); // e splits createSplitCategoriesFromRecurringTransaction(); return true; } /** * Get the parameters from the intent (parameters sent from the caller). * Also used for Tasker integration, for example. * @param savedInstanceState parameters */ private boolean handleIntent(Bundle savedInstanceState) { Intent intent = getIntent(); mIntentAction = intent.getAction(); if (mIntentAction == null) { Timber.w("no intent action passed to CheckingTransactionEditActivity e intent"); return false; } if (savedInstanceState == null) { int accountId = intent.getIntExtra(EditTransactionActivityConstants.KEY_ACCOUNT_ID, Constants.NOT_SET); if (accountId != Constants.NOT_SET) { mCommon.transactionEntity.setAccountId(accountId); } // Edit transaction. if (mIntentAction != null) { int transactionId = intent.getIntExtra(EditTransactionActivityConstants.KEY_TRANS_ID, Constants.NOT_SET); switch (mIntentAction) { case Intent.ACTION_EDIT: loadTransaction(transactionId); break; case Intent.ACTION_PASTE: // duplicate transaction loadTransaction(transactionId); duplicateTransaction(); break; case Intent.ACTION_INSERT: mRecurringTransactionId = intent.getIntExtra(EditTransactionActivityConstants.KEY_BDID_ID, Constants.NOT_SET); if (mRecurringTransactionId > Constants.NOT_SET) { loadRecurringTransaction(mRecurringTransactionId); } } } } // New transaction if (mIntentAction.equals(Intent.ACTION_INSERT)) { if (mCommon.transactionEntity.getStatus() == null) { String defaultStatus = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) .getString(getString(PreferenceConstants.PREF_DEFAULT_STATUS), ""); mCommon.transactionEntity.setStatus(defaultStatus); } if ("L".equals(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) .getString(getString(PreferenceConstants.PREF_DEFAULT_PAYEE), "N"))) { AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() { @Override protected Boolean doInBackground(Void... params) { try { Core core = new Core(getApplicationContext()); Payee payee = core.getLastPayeeUsed(); if (payee != null && mCommon.transactionEntity.getPayeeId() == Constants.NOT_SET) { // get id payee and category mCommon.transactionEntity.setPayeeId(payee.getId()); mCommon.payeeName = payee.getName(); mCommon.transactionEntity.setCategoryId(payee.getCategoryId()); mCommon.transactionEntity.setSubcategoryId(payee.getSubcategoryId()); // load category and subcategory name mCommon.loadCategoryName(); return Boolean.TRUE; } } catch (Exception e) { Timber.e(e, "loading default payee"); } return Boolean.FALSE; } @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); if (result) { try { // refresh field mCommon.showPayeeName(); mCommon.displayCategoryName(); } catch (Exception e) { Timber.e(e, "showing payee and category names"); } } } }; task.execute(); } externalIntegration(intent); // Select the default account if none set. Integer account = mCommon.transactionEntity.getAccountId(); if (account == null || account == Constants.NOT_SET) { AppSettings settings = new AppSettings(this); Integer defaultAccountId = settings.getGeneralSettings().getDefaultAccountId(); if (defaultAccountId == null) { // Show toast message. new UIHelper(this).showToast(getString(R.string.default_account_not_set)); return false; } else { mCommon.transactionEntity.setAccountId(defaultAccountId); } } } // set title if (getSupportActionBar() != null) { getSupportActionBar().setTitle(Intent.ACTION_INSERT.equals(mIntentAction) ? R.string.new_transaction : R.string.edit_transaction); } return true; } private void restoreInstanceState(Bundle savedInstanceState) { Parcelable parcelTx = savedInstanceState.getParcelable(EditTransactionActivityConstants.KEY_TRANSACTION_ENTITY); mCommon.transactionEntity = Parcels.unwrap(parcelTx); mCommon.mToAccountName = savedInstanceState.getString(EditTransactionActivityConstants.KEY_TO_ACCOUNT_NAME); mCommon.payeeName = savedInstanceState.getString(EditTransactionActivityConstants.KEY_PAYEE_NAME); mCommon.categoryName = savedInstanceState.getString(EditTransactionActivityConstants.KEY_CATEGORY_NAME); mCommon.subCategoryName = savedInstanceState.getString(EditTransactionActivityConstants.KEY_SUBCATEGORY_NAME); mCommon.mSplitTransactions = Parcels.unwrap(savedInstanceState.getParcelable(EditTransactionActivityConstants.KEY_SPLIT_TRANSACTION)); mCommon.mSplitTransactionsDeleted = Parcels.unwrap(savedInstanceState.getParcelable( EditTransactionActivityConstants.KEY_SPLIT_TRANSACTION_DELETED)); mRecurringTransactionId = savedInstanceState.getInt(EditTransactionActivityConstants.KEY_BDID_ID); // action // mIntentAction = savedInstanceState.getString(EditTransactionActivityConstants.KEY_ACTION); } /** * Save data to the database. * @return true if update data successful */ private boolean saveData() { if (!mCommon.validateData()) return false; boolean isTransfer = mCommon.transactionEntity.getTransactionType().equals(TransactionTypes.Transfer); if (!isTransfer) { mCommon.resetTransfer(); } // Transaction. Need the Id for split categories. if (!saveTransaction()) return false; // Split Categories if (mCommon.convertOneSplitIntoRegularTransaction()) { saveTransaction(); } if(!mCommon.isSplitSelected()) { // Delete any split categories if split is unchecked. mCommon.removeAllSplitCategories(); } if (!saveSplitCategories()) return false; // update category and subcategory for the default payee saveDefaultPayee(isTransfer); // Process recurring transaction. if (mRecurringTransactionId != Constants.NOT_SET) { RecurringTransactionService service = new RecurringTransactionService(mRecurringTransactionId, this); service.moveNextOccurrence(); } return true; } private void saveDefaultPayee(boolean isTransfer) { if ((isTransfer) || !mCommon.hasPayee() || mCommon.hasSplitCategories()) { return; } if (mCommon.transactionEntity == null) return; PayeeRepository payeeRepository = new PayeeRepository(this); Payee payee = payeeRepository.load(mCommon.transactionEntity.getPayeeId()); if (payee == null) return; payee.setCategoryId(mCommon.transactionEntity.getCategoryId()); payee.setSubcategoryId(mCommon.transactionEntity.getSubcategoryId()); boolean saved = payeeRepository.save(payee); if (!saved) { Toast.makeText(getApplicationContext(), R.string.db_payee_update_failed, Toast.LENGTH_SHORT).show(); Timber.w("Update Payee with Id=%d return <= 0", mCommon.transactionEntity.getPayeeId()); } } private boolean saveSplitCategories() { Integer transactionId = mCommon.transactionEntity.getId(); SplitCategoriesRepository splitRepo = new SplitCategoriesRepository(this); ArrayList<ISplitTransaction> deletedSplits = mCommon.getDeletedSplitCategories(); // deleted old split transaction if (!deletedSplits.isEmpty()) { if (!mCommon.deleteMarkedSplits(splitRepo)) return false; } // update split transaction boolean hasSplitCategories = mCommon.hasSplitCategories(); if (hasSplitCategories) { for (ISplitTransaction split : mCommon.mSplitTransactions) { SplitCategory entity = (SplitCategory) split; // do nothing if the split is marked for deletion. if(deletedSplits.contains(split)) { continue; } entity.setTransId(transactionId); if (entity.getId() == null || entity.getId() == Constants.NOT_SET) { // insert data if (!splitRepo.insert(entity)) { Toast.makeText(getApplicationContext(), R.string.db_checking_insert_failed, Toast.LENGTH_SHORT).show(); Timber.w("Insert new split transaction failed!"); return false; } } else { // update data if (!splitRepo.update(entity)) { Toast.makeText(getApplicationContext(), R.string.db_checking_update_failed, Toast.LENGTH_SHORT).show(); Timber.w("Update split transaction failed!"); return false; } } } } return true; } private boolean saveTransaction() { AccountTransactionRepository repo = new AccountTransactionRepository(this); if (!mCommon.transactionEntity.hasId()) { // insert mCommon.transactionEntity = repo.insert((AccountTransaction) mCommon.transactionEntity); if (!mCommon.transactionEntity.hasId()) { Toast.makeText(getApplicationContext(), R.string.db_checking_insert_failed, Toast.LENGTH_SHORT).show(); Timber.w("Insert new transaction failed!"); return false; } } else { // update boolean updated = repo.update((AccountTransaction) mCommon.transactionEntity); if (!updated) { Toast.makeText(getApplicationContext(), R.string.db_checking_update_failed, Toast.LENGTH_SHORT).show(); Timber.w("Update transaction failed!"); return false; } } return true; } }