def test_get_mint_updates_no_itemize_arg_three_items(self): i1 = item( title='Really cool watch', quantity=1, item_subtotal='$10.00', item_subtotal_tax='$1.00', item_total='$11.00') i2 = item( title='Organic water', quantity=1, item_subtotal='$6.00', item_subtotal_tax='$0.00', item_total='$6.00') o1 = order( subtotal='$16.00', tax_charged='$1.00', total_charged='$17.00') t1 = transaction(amount='$17.00') stats = Counter() updates, _ = tagger.get_mint_updates( [o1], [i1, i2], [], [t1], get_args(no_itemize=True), stats) self.assertEqual(len(updates), 1) orig_t, new_trans = updates[0] self.assertTrue(orig_t is t1) self.assertEqual(len(new_trans), 1) self.assertEqual(new_trans[0].merchant, 'Amazon.com: Really cool watch, Organic water') self.assertEqual(new_trans[0].category, 'Shopping') self.assertEqual(new_trans[0].amount, 17000000)
def test_get_mint_updates_verbose_itemize_arg(self): i1 = item() o1 = order(shipping_charge='$3.99', total_promotions='$3.99') t1 = transaction() stats = Counter() updates, _ = tagger.get_mint_updates( [o1], [i1], [], [t1], get_args(verbose_itemize=True), stats) self.assertEqual(len(updates), 1) orig_t, new_trans = updates[0] self.assertTrue(orig_t is t1) self.assertEqual(len(new_trans), 3) self.assertEqual(new_trans[0].merchant, 'Amazon.com: Promotion(s)') self.assertEqual(new_trans[0].category, 'Shipping') self.assertEqual(new_trans[0].amount, -3990000) self.assertFalse(new_trans[0].is_debit) self.assertEqual(new_trans[1].merchant, 'Amazon.com: Shipping') self.assertEqual(new_trans[1].category, 'Shipping') self.assertEqual(new_trans[1].amount, 3990000) self.assertEqual(new_trans[2].merchant, 'Amazon.com: 2x Duracell AAs') self.assertEqual(new_trans[2].category, 'Shopping') self.assertEqual(new_trans[2].amount, 11950000) self.assertEqual(stats['new_tag'], 1)
def test_get_mint_updates_multi_orders_trans_same_date_and_amount(self): i1 = item(order_id='A') o1 = order(order_id='A') i2 = item(order_id='B') o2 = order(order_id='B') t1 = transaction() t2 = transaction() stats = Counter() updates, _ = tagger.get_mint_updates([o1, o2], [i1, i2], [], [t1, t2], get_args(), stats) self.assertEqual(len(updates), 2) updates2, _ = tagger.get_mint_updates([o1, o2], [i1, i2], [], [t1, t2], get_args(num_updates=1), stats) self.assertEqual(len(updates2), 1)
def test_get_mint_updates_multi_domains_no_retag(self): i1 = item() o1 = order() t1 = transaction(merchant='Amazon.co.uk: already tagged') stats = Counter() updates, _ = tagger.get_mint_updates([o1], [i1], [], [t1], get_args(), stats) self.assertEqual(len(updates), 0)
def test_get_mint_updates_retag_arg(self): i1 = item() o1 = order() t1 = transaction(merchant='Amazon.com: already tagged') stats = Counter() updates, _ = tagger.get_mint_updates([o1], [i1], [], [t1], get_args(retag_changed=True), stats) self.assertEqual(len(updates), 1) self.assertEqual(stats['retag'], 1)
def test_get_mint_updates_refund_no_date(self): r1 = refund(title='Cool item2', refund_amount='$10.95', refund_tax_amount='$1.00', refund_date=None) t1 = transaction(amount='$11.95', is_debit=False, date='3/12/14') stats = Counter() updates, _ = tagger.get_mint_updates([], [], [r1], [t1], get_args(), stats) self.assertEqual(len(updates), 0) self.assertEqual(stats['new_tag'], 0)
def test_get_mint_updates_no_tag_categories_arg(self): i1 = item() o1 = order() t1 = transaction(merchant='Amazon.com: 2x Duracell AAs', note=o1.get_note() + '\nItem(s):\n - 2x Duracell AAs') stats = Counter() updates, _ = tagger.get_mint_updates([o1], [i1], [], [t1], get_args(no_tag_categories=True), stats) self.assertEqual(len(updates), 0) self.assertEqual(stats['already_up_to_date'], 1)
def test_get_mint_updates_skip_already_tagged(self): i1 = item() o1 = order() t1 = transaction(merchant='SomeRandoCustomPrefix: already tagged') stats = Counter() updates, _ = tagger.get_mint_updates( [o1], [i1], [], [t1], get_args(description_prefix_override='SomeRandoCustomPrefix: '), stats) self.assertEqual(len(updates), 0) self.assertEqual(stats['no_retag'], 1)
def test_get_mint_updates_no_update_for_identical(self): i1 = item() o1 = order() t1 = transaction(merchant='Amazon.com: 2x Duracell AAs', category='Electronics & Software', note=o1.get_note() + '\nItem(s):\n - 2x Duracell AAs') stats = Counter() updates, _ = tagger.get_mint_updates([o1], [i1], [], [t1], get_args(retag_changed=True), stats) self.assertEqual(len(updates), 0) self.assertEqual(stats['already_up_to_date'], 1)
def test_get_mint_updates_no_itemize_arg_single_item(self): i1 = item() o1 = order(total_charged='$15.94', shipping_charge='$3.99') t1 = transaction(amount='$15.94') stats = Counter() updates, _ = tagger.get_mint_updates([o1], [i1], [], [t1], get_args(no_itemize=True), stats) self.assertEqual(len(updates), 1) orig_t, new_trans = updates[0] self.assertTrue(orig_t is t1) self.assertEqual(len(new_trans), 1) self.assertEqual(new_trans[0].merchant, 'Amazon.com: 2x Duracell AAs') self.assertEqual(new_trans[0].category, 'Electronics & Software') self.assertEqual(new_trans[0].amount, 15940000)
def test_get_mint_updates_simple_match(self): i1 = item() o1 = order() t1 = transaction() stats = Counter() updates, _ = tagger.get_mint_updates([o1], [i1], [], [t1], get_args(), stats) self.assertEqual(len(updates), 1) orig_t, new_trans = updates[0] self.assertTrue(orig_t is t1) self.assertEqual(len(new_trans), 1) self.assertEqual(new_trans[0].merchant, 'Amazon.com: 2x Duracell AAs') self.assertEqual(new_trans[0].category, 'Electronics & Software') self.assertEqual(new_trans[0].amount, 11950000) self.assertTrue(new_trans[0].is_debit) self.assertFalse(new_trans[0].is_child) self.assertEqual(stats['new_tag'], 1)
def test_get_mint_updates_simple_match_refund(self): r1 = refund(title='Cool item', refund_amount='$10.95', refund_tax_amount='$1.00', refund_date='3/12/14') t1 = transaction(amount='$11.95', is_debit=False, date='3/12/14') stats = Counter() updates, _ = tagger.get_mint_updates([], [], [r1], [t1], get_args(), stats) self.assertEqual(len(updates), 1) orig_t, new_trans = updates[0] self.assertTrue(orig_t is t1) self.assertEqual(len(new_trans), 1) self.assertEqual(new_trans[0].merchant, 'Amazon.com: 2x Cool item') self.assertEqual(new_trans[0].category, 'Returned Purchase') self.assertEqual(new_trans[0].amount, -11950000) self.assertFalse(new_trans[0].is_debit) self.assertFalse(new_trans[0].is_child) self.assertEqual(stats['new_tag'], 1)
def main(): warn_if_outdated('mint-amazon-tagger', VERSION) is_outdated, latest_version = check_outdated('mint-amazon-tagger', VERSION) if is_outdated: print('Please update your version by running:\n' 'pip3 install mint-amazon-tagger --upgrade') parser = argparse.ArgumentParser( description='Tag Mint transactions based on itemized Amazon history.') define_args(parser) args = parser.parse_args() if args.version: print('mint-amazon-tagger {}\nBy: Jeff Prouty'.format(VERSION)) exit(0) session_path = args.session_path if session_path.lower() == 'none': session_path = None items_csv = args.items_csv orders_csv = args.orders_csv refunds_csv = args.refunds_csv start_date = None if not items_csv or not orders_csv: logger.info('Missing Items/Orders History csv. Attempting to fetch ' 'from Amazon.com.') start_date = args.order_history_start_date duration = datetime.timedelta(days=args.order_history_num_days) end_date = datetime.date.today() # If a start date is given, adjust the end date based on num_days, # ensuring not to go beyond today. if start_date: start_date = start_date.date() if start_date + duration < end_date: end_date = start_date + duration else: start_date = end_date - duration items_csv, orders_csv, refunds_csv = fetch_order_history( args.report_download_location, start_date, end_date, args.amazon_email, args.amazon_password, session_path, args.headless) if not items_csv or not orders_csv: # Refunds are optional logger.critical('Order history either not provided at command line or ' 'unable to fetch. Exiting.') exit(1) orders = amazon.Order.parse_from_csv(orders_csv, ProgressCounter('Parsing Orders - ')) items = amazon.Item.parse_from_csv(items_csv, ProgressCounter('Parsing Items - ')) refunds = ([] if not refunds_csv else amazon.Refund.parse_from_csv( refunds_csv, ProgressCounter('Parsing Refunds - '))) if args.dry_run: logger.info('\nDry Run; no modifications being sent to Mint.\n') # Initialize the stats. Explicitly initialize stats that might not be # accumulated (conditionals). stats = Counter( adjust_itemized_tax=0, already_up_to_date=0, misc_charge=0, new_tag=0, no_retag=0, retag=0, user_skipped_retag=0, personal_cat=0, ) mint_client = MintClient(args.mint_email, args.mint_password, session_path, args.headless, args.mint_mfa_method, args.wait_for_sync) if args.pickled_epoch: mint_trans, mint_category_name_to_id = ( get_trans_and_categories_from_pickle(args.pickled_epoch, args.mint_pickle_location)) else: # Get the date of the oldest Amazon order. if not start_date: start_date = min([o.order_date for o in orders]) if refunds: start_date = min(start_date, min([o.order_date for o in refunds])) # Double the length of transaction history to help aid in # personalized category tagging overrides. today = datetime.date.today() start_date = today - (today - start_date) * 2 mint_category_name_to_id = mint_client.get_categories() mint_transactions_json = mint_client.get_transactions(start_date) epoch = int(time.time()) mint_trans = mint.Transaction.parse_from_json(mint_transactions_json) dump_trans_and_categories(mint_trans, mint_category_name_to_id, epoch, args.mint_pickle_location) updates, unmatched_orders = tagger.get_mint_updates( orders, items, refunds, mint_trans, args, stats, mint_category_name_to_id) log_amazon_stats(items, orders, refunds) log_processing_stats(stats) if args.print_unmatched and unmatched_orders: logger.warning( 'The following were not matched to Mint transactions:\n') by_oid = defaultdict(list) for uo in unmatched_orders: by_oid[uo.order_id].append(uo) for unmatched_by_oid in by_oid.values(): orders = [o for o in unmatched_by_oid if o.is_debit] refunds = [o for o in unmatched_by_oid if not o.is_debit] if orders: print_unmatched(amazon.Order.merge(orders)) for r in amazon.Refund.merge(refunds): print_unmatched(r) if not updates: logger.info( 'All done; no new tags to be updated at this point in time!') exit(0) if args.dry_run: logger.info('Dry run. Following are proposed changes:') if args.skip_dry_print: logger.info('Dry run print results skipped!') else: tagger.print_dry_run(updates, ignore_category=args.no_tag_categories) else: mint_client.send_updates(updates, ignore_category=args.no_tag_categories)
def test_get_mint_updates_empty_input(self): updates, _ = tagger.get_mint_updates( [], [], [], [], get_args(), Counter()) self.assertEqual(len(updates), 0)