summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2013-01-29 12:04:40 -0800
committerJohn Wiegley <johnw@newartisans.com>2013-01-29 12:04:40 -0800
commite1da55d1af6b2c1db355d18610aebab43b0ae01f (patch)
tree14ff3f43e44805847d8c04307e032699146c08c3 /contrib
parent6410f391f9f6b24c20f0146599199b5e868007a4 (diff)
parent2b7f7b7f90e182b52219a5fb824e3463a3b8a220 (diff)
downloadfork-ledger-e1da55d1af6b2c1db355d18610aebab43b0ae01f.tar.gz
fork-ledger-e1da55d1af6b2c1db355d18610aebab43b0ae01f.tar.bz2
fork-ledger-e1da55d1af6b2c1db355d18610aebab43b0ae01f.zip
Merge pull request #90 from rladams/next
Next
Diffstat (limited to 'contrib')
-rwxr-xr-xcontrib/raw/GenerateLatexExpeneseReport.pl429
-rw-r--r--contrib/raw/MetadataExample.dat111
-rw-r--r--contrib/raw/README5
-rwxr-xr-xcontrib/raw/VerifyImages.sh14
-rw-r--r--contrib/raw/dotemacs.el201
-rw-r--r--contrib/raw/ledger-matching.el342
-rw-r--r--contrib/raw/ledger-shell-environment-functions90
7 files changed, 1192 insertions, 0 deletions
diff --git a/contrib/raw/GenerateLatexExpeneseReport.pl b/contrib/raw/GenerateLatexExpeneseReport.pl
new file mode 100755
index 00000000..670d9f21
--- /dev/null
+++ b/contrib/raw/GenerateLatexExpeneseReport.pl
@@ -0,0 +1,429 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Getopt::Long; # Options processing
+use Smart::Comments -ENV, "###"; # Ignore my dividers, and use
+ # Smart_Comments=1 to activate
+use Cwd;
+use File::Basename;
+use 5.10.0;
+use POSIX qw(strftime);
+use Date::Calc qw(Add_Delta_Days);
+
+use Template;
+my $TT = Template->new( { POST_CHOMP => 1 } );
+
+######################################################################
+# TODO
+#
+# DONE Meal summaries are broken for multi-week reports
+
+######################################################################
+# Options
+
+# Is this an internal report?
+my $ExpenseReportCode = undef;
+my $Internal = undef;
+my $SuppressMeals = 0;
+my $ViewAfter = 0;
+my $ImageDir = ".";
+my $Anonymize = 0;
+my $Help = undef;
+
+GetOptions( 'c' => \$Internal,
+ 'm' => \$SuppressMeals,
+ 'v' => \$ViewAfter,
+ 'a' => \$Anonymize,
+ 'I' => \$ImageDir,
+ 'e:s' => \$ExpenseReportCode,
+ 'h|help' => \$Help
+ );
+
+# Help
+
+defined $Help && do {
+ print <<EOF;
+Usage: GenerateLatexExpenseReport.pl [OPTION] -e ERCode
+
+Options:
+ -c Internal report
+ -m Suppress meals
+ -v View PDF on completion
+ -a Anonymous, omit header/footer
+ -I Image directory
+ -e ER Code (AISER0001)
+
+EOF
+ exit -1;
+};
+
+die "Pass -e <ExpenseReportCode>" unless defined $ExpenseReportCode;
+
+######################################################################
+# Report items
+
+my @ItemizedExpenses;
+my $ItemizedTotal = 0.00;
+
+my @ItemizedReceipts;
+
+my @MealsReport;
+
+######################################################################
+# Gather required data about this expense report from the directory name
+# ie: ./AISER0015 20090419-20090425 AGIL2078 Shands HACMP/
+#
+# ExpenseReportCode = AISER0015
+# DateRange = 20090419-20090425
+# ProjectCode = AGIL2078
+# Description = Shands HACMP
+
+
+######################################################################
+# Remaining options
+
+# Where is the ledger binary?
+my $LedgerBin = "./ledger -f ./.ledger -V";
+
+# -E Show empty accounts
+# -S d Sort by date
+# -V Convert mileage to $
+my $LedgerOpts = "--no-color -S d";
+
+my $LedgerAcct = "^Dest:Projects";
+
+my $LedgerCriteria = "%" . "ER=$ExpenseReportCode";
+
+# Internal report?
+
+if ( $Internal ) {
+
+ # No mileage on an internal report
+ # $LedgerCriteria .= "&!/Mileage/"; # This shouldn't matter, just don't put metadata for ER on mileage
+ $LedgerAcct = "^Dest:Internal";
+
+}
+
+my $CmdLine = "$LedgerBin reg $LedgerOpts -E \"$LedgerCriteria\" and ^Stub "
+ . "--format \"%(tag('ER'))~%(tag('PROJECT'))~%(tag('NOTE'))\n\"";
+### $CmdLine
+
+my @TempLine = `$CmdLine`;
+
+# Match all remaining items
+$TempLine[0] =~ m,^(?<Er>.*?)~
+ (?<Project>.*?)~
+ (?<Note>.*?)\s*$,x;
+
+my $ProjectCode= $+{'Project'};
+my $Description= $+{'Note'};
+
+### $ExpenseReportCode
+### $ProjectCode
+### $Description
+### $LedgerAcct
+### $Internal
+### $Anonymize
+### $LedgerAcct
+### $LedgerOpts
+### $LedgerCriteria
+
+
+######################################################################
+# Pull main ledger report of line items for the expense report
+# Using ~ as a delimiter
+#
+# Example:
+# '2009/04/25~AR:Projects:AGIL2078:PersMealsLunch~:AISER0015: PILOT 00004259 MIDWAY, FL~ 8.68~Receipts/AGIL2078/20090425_Pilot_8_68_21204.jpg\n'
+#
+#./ledger --no-color reg %ER=AISER0040 and ^Projects -y "%Y/%m/%d" -V --format "%(date)~%(account)~%(payee)~%(amount)~%(tag('NOTE'))\n"
+
+$CmdLine = "$LedgerBin reg $LedgerOpts \"$LedgerCriteria\" and \"$LedgerAcct\" "
+ . "-y \"%Y/%m/%d\" "
+ . "--format \"%(date)~%(tag('CATEGORY'))~%(payee)~%(display_amount)~%(tag('NOTE'))~%(tag('RECEIPT'))\\n\"";
+### $CmdLine
+my @MainReport = `$CmdLine`;
+
+
+### MainReport: @MainReport
+
+# Remove any project codes and linefeeds
+#map { chomp(); s/(:AISER[0-9][0-9][0-9][0-9])+://g; } @MainReport; # No need, thats now metadata
+
+foreach my $line (@MainReport) { ### Processing Main Report... done
+
+ # Remove bad chars (#&)
+ $line =~ tr/#&//d;
+
+ # Match all remaining items
+ $line =~ m,^(?<Date>[0-9]{4}/[0-9]{2}/[0-9]{2})~
+ (?<Category>.*?)~
+ (?<Vendor>.*?)~
+ (?<Amount>.*?)~
+ (?<Note>.*?)~
+ (?<Receipts>.*?)\s*$,x;
+ my %Record = %+;
+
+ $Record{'Amount'}=~tr/$,//d;
+
+ foreach (keys %Record) {
+ $Record{$_} =~ s/^\s+//g;
+ }
+
+
+ # Grab images from <<file:///dir/filename.jpg>>
+ my $ImageList = $Record{'Receipts'};
+ $ImageList //= '';
+ my @Images = split( /,/, $ImageList );
+
+ # Cleanup
+ # Take last word of account name as category
+ $Record{'Category'} = ( split( /:/, $Record{'Category'} ) )[-1];
+
+ # If no images, italicise the line item.
+ $Record{'Italics'} = 1;
+
+ # Test images
+ foreach my $Image (@Images) {
+
+ # Turn off italics because there is an image
+ $Record{'Italics'} = 0;
+
+ if (! -r $ImageDir . "/" . $Image) {
+ print STDERR "Missing $ImageDir/$Image\n";
+ }
+ }
+
+ # Add to itemized expenses to be printed
+ push( @ItemizedExpenses, \%Record );
+ $ItemizedTotal += $Record{'Amount'};
+
+ # Add to itemized reciepts for printing
+ push( @ItemizedReceipts, { 'Vendor' => $Record{'Vendor'},
+ 'Amount' => $Record{'Amount'},
+ 'Date' => $Record{'Date'},
+ 'Images' => \@Images } )
+ if $ImageList;
+
+}
+
+### @ItemizedExpenses
+
+######################################################################
+# Meals report
+
+# Summarize total spent on meals by day
+$CmdLine = "$LedgerBin reg $LedgerOpts "
+ . "\"$LedgerCriteria\" and \"$LedgerAcct\" and \%CATEGORY=Meals "
+ . "-D -n "
+ . "--format \"%(account)~%(payee)~%(display_amount)~%(total)\n\" "
+ . "| grep -v '<None>'";
+
+### $CmdLine
+my @MealsOutput = `$CmdLine`;
+
+### @MealsOutput
+
+foreach my $line (@MealsOutput) {
+
+ # Match all remaining items
+ $line =~ m,^(?<Account>.*?)~
+ (?<DOW>.*?)~\$
+ (?<Amount>\s*[0-9]+\.[0-9]+)~\$
+ (?<RunningTotal>.*?)\s*$,x;
+ my %TRecord=%+;
+ $TRecord{'Account'}=~s/^Projects://g;
+ $TRecord{'DOW'}=~s/^- //g;
+
+ # Add to itemized expenses to be printed
+ push( @MealsReport, \%TRecord );
+
+}
+
+######################################################################
+# Total by category
+
+$CmdLine = "$LedgerBin bal $LedgerOpts "
+ . "\"$LedgerCriteria\" and \"$LedgerAcct\" '--account=tag(\"CATEGORY\")' "
+ . "--format \"%(account)~%(display_total)\\n\"";
+
+### $CmdLine
+my @CategoryOutput = `$CmdLine`;
+
+### @CategoryOutput
+
+my @CategoryReport;
+
+foreach my $line (@CategoryOutput) {
+
+ chomp $line;
+ $line =~ tr/\$,//d;
+
+ # Match all remaining items
+ my @Temp = split(/~/,$line);
+
+ my %TRecord= ( 'Category' => $Temp[0],
+ 'Amount' => $Temp[1]);
+
+ if ($TRecord{'Category'} eq '') {
+ $TRecord{'Category'} = '\\hline \\bf Total';
+ }
+
+ # Cleanup
+ # Take last word of account name as category
+ $TRecord{'Category'} = ( split( /:/, $TRecord{'Category'} ) )[0];
+
+ # Add to itemized expenses to be printed
+ push( @CategoryReport, \%TRecord );
+
+}
+
+### @CategoryReport
+
+
+######################################################################
+# Output
+######################################################################
+
+my $TTVars = {
+ 'Internal' => $Internal,
+ 'SuppressMeals' => $SuppressMeals,
+ 'ExpenseReportCode' => $ExpenseReportCode,
+ 'ProjectCode' => $ProjectCode,
+ 'Description' => $Description,
+ 'ItemizedExpenses' => \@ItemizedExpenses,
+ 'ItemizedTotal' => $ItemizedTotal,
+ 'MealsReport' => \@MealsReport,
+ 'CategoryReport' => \@CategoryReport,
+ 'ItemizedReceipts' => \@ItemizedReceipts,
+ 'ImageDir' => $ImageDir,
+ 'Anonymize' => $Anonymize
+};
+
+#### $TTVars
+
+my $LatexTemplate = <<EOF;
+[% USE format %][% ToDollars = format('\\\$%.2f') %]
+%%%%%%%%%%% Header
+
+\\documentclass[10pt,letterpaper]{article}
+\\usepackage[letterpaper,includeheadfoot,top=0.5in,bottom=0.5in,left=0.75in,right=0.75in]{geometry}
+\\usepackage[utf8]{inputenc}
+\\usepackage[T1]{fontenc}
+\\usepackage[scaled]{helvet}
+\\renewcommand*\\familydefault{\\sfdefault}
+\\usepackage{lastpage}
+\\usepackage{fancyhdr}
+\\usepackage{graphicx}
+\\usepackage{multicol}
+\\usepackage[colorlinks,linkcolor=blue]{hyperref}
+\\pagestyle{fancy}
+\\renewcommand{\\headrulewidth}{1pt}
+\\renewcommand{\\footrulewidth}{0.5pt}
+\\geometry{headheight=48pt}
+
+
+\\begin{document}
+
+%%%%%%%%%% Itemized table
+
+\\section{Itemized Expenses}
+[% FOREACH Expense IN ItemizedExpenses %]
+[% IF loop.first() or ( ( loop.count mod 20 ) == 0 ) %]
+\\begin{tabular}{|l|l|p{2in}|r|p{2in}|}
+\\hline
+\\hline
+\\bf Date & \\bf Category & \\bf Expense & \\bf Amount & \\bf Notes \\\\
+\\hline \\hline
+[% END %]
+[% IF Expense.Italics %]\\it[% END %] [% Expense.Date %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Category %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Vendor %] & [% IF Expense.Italics %]\\it[% END %] [% ToDollars(Expense.Amount) %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Note %] \\\\ \\hline
+[% IF loop.last() %]
+\\hline
+ & & \\bf Total & \\bf [% ToDollars(ItemizedTotal) %] & \\\\
+[% END %]
+[% IF ( ( (loop.count + 1) mod 20 ) == 0 ) or loop.last() %]
+\\hline
+\\hline
+\\end{tabular}
+[% IF ( ( (loop.count + 1) mod 20 ) == 0 ) %]\\newline {\\it Continued on next page...}[% END %]
+\\newpage
+[% END %]
+[% END %]
+
+[% IF ! Internal %][% IF ! SuppressMeals %]
+%%%%%%%%%% Meals summary table
+
+\\section{Meals Summary By Day}
+
+\\begin{tabular}{|l|l|p{2in}|p{2in}|}
+\\hline
+\\hline
+\\bf DOW & \\bf Daily Total & \\bf Running Total \\\\
+\\hline \\hline
+[% FOREACH Meal IN MealsReport %]
+[% Meal.DOW %] & [% ToDollars(Meal.Amount) %] & [% ToDollars(Meal.RunningTotal) %] \\\\ \\hline
+[% END %]
+\\hline
+\\hline
+\\end{tabular}
+[% END %][% END %]
+
+%%%%%%%%%% Category summary
+
+\\section{Expenses Summary}
+
+\\begin{tabular}{|l|l|}
+\\hline
+\\hline
+\\bf Category & \\bf Total \\\\
+\\hline \\hline
+[% FOREACH Category IN CategoryReport %]
+[% Category.Category %] & [% ToDollars(Category.Amount) %] \\\\ \\hline
+[% END %]
+\\hline
+\\hline
+\\end{tabular}
+
+
+%%%%%%%%%% Begin receipts
+
+\\section{Scanned Receipts}
+
+[% FOREACH Receipt IN ItemizedReceipts %]
+\\subsection{[% Receipt.Date %] [% Receipt.Vendor %]: [% ToDollars(Receipt.Amount) %]}
+[% FOREACH Image IN Receipt.Images %]
+\\includegraphics[angle=90,width=\\textwidth,keepaspectratio]{[% ImageDir %]/[% Image %]} \\\\
+[% END %]
+
+[% END %]
+
+
+%%%%%%%%%% Footer
+
+\\end{document}
+EOF
+
+my $LatexFN = $ExpenseReportCode . "-" . $ProjectCode . ".tex";
+### $LatexFN
+
+$TT->process( \$LatexTemplate, $TTVars, "./tmp/" . $LatexFN ) || do {
+ my $error = $TT->error();
+ print "error type: ", $error->type(), "\n";
+ print "error info: ", $error->info(), "\n";
+ die $error;
+};
+
+
+my $LatexOutput = `pdflatex -interaction batchmode -output-directory ./tmp "$LatexFN"`;
+### $LatexOutput
+
+ $LatexOutput = `pdflatex -interaction batchmode -output-directory ./tmp "$LatexFN"`;
+### $LatexOutput
+
+if ($ViewAfter) {
+ my $ViewFN = $LatexFN;
+ $ViewFN =~ s/\.tex$/.pdf/;
+ `acroread "./tmp/$ViewFN"`;
+}
+
diff --git a/contrib/raw/MetadataExample.dat b/contrib/raw/MetadataExample.dat
new file mode 100644
index 00000000..791eaf77
--- /dev/null
+++ b/contrib/raw/MetadataExample.dat
@@ -0,0 +1,111 @@
+; TAG key: value
+
+2009/09/27 * (09/28/2009) HUDSON NEWS HOUSTN HBB HOUSTON, TX
+ Source:Visa -$6.55
+ Projects:Meals
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+
+2009/09/27 * (09/28/2009) PEET'S COFFEE & TEA KINGWOOD, TX
+ Source:Visa -$2.44
+ Projects:Meals
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+
+2009/09/28 * (09/29/2009) FUSIA NEW YORK, NY
+ Source:Visa -$15.25
+ Projects:Meals
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+
+
+2009/09/29 * (09/30/2009) BALUCHI'S NEW YORK, NY
+ Source:Visa
+ Projects:Meals $20.00
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+ Internal:Travel $10.44
+ ; ER: AISER0036
+ ; PROJECT: Internal
+
+
+2009/10/01 * Reimbursing AISER0036
+ Bank:AISChecking
+ ; ER: AISER0036
+ ; PROJECT: Internal
+ Source:Visa $10.44
+
+
+----------
+
+$ ./ledger -Ef AISER0033.dat bal '--account=tag("ER")'
+ $-44.24
+ $44.24 AISER0033
+ 0 AISER0036
+--------------------
+ 0
+
+$ ./ledger -Ef AISER0033.dat bal
+ $-10.44 Bank:AISChecking
+ $10.44 Internal:Travel
+ $44.24 Projects:Meals
+ $-44.24 Source:Visa
+--------------------
+ 0
+
+$ ./ledger -Ef AISER0033.dat bal '--account=tag("PROJECT")'
+ $-44.24
+ 0 Internal
+ $44.24 PROJXXXX
+--------------------
+ 0
+
+$ ./ledger -f AISER0033.dat reg '--account=tag("PROJECT")'
+09-Sep-27 HUDSON NEWS HOUSTN .. $-6.55 $-6.55
+09-Sep-27 HUDSON NEWS HOUSTN .. PROJXXXX $6.55 0
+09-Sep-27 PEET'S COFFEE & TEA.. $-2.44 $-2.44
+09-Sep-27 PEET'S COFFEE & TEA.. PROJXXXX $2.44 0
+09-Sep-28 FUSIA NEW YORK, NY $-15.25 $-15.25
+09-Sep-28 FUSIA NEW YORK, NY PROJXXXX $15.25 0
+09-Sep-29 BALUCHI'S NEW YORK,.. $-30.44 $-30.44
+09-Sep-29 BALUCHI'S NEW YORK,.. PROJXXXX $20.00 $-10.44
+09-Sep-29 BALUCHI'S NEW YORK,.. Internal $10.44 0
+09-Oct-01 Reimbursing AISER0036 Internal $-10.44 $-10.44
+09-Oct-01 Reimbursing AISER0036 $10.44 0
+
+$ ./ledger -f AISER0033.dat reg '--account=tag("ER")'
+09-Sep-27 HUDSON NEWS HOUSTN .. $-6.55 $-6.55
+09-Sep-27 HUDSON NEWS HOUSTN .. AISER0033 $6.55 0
+09-Sep-27 PEET'S COFFEE & TEA.. $-2.44 $-2.44
+09-Sep-27 PEET'S COFFEE & TEA.. AISER0033 $2.44 0
+09-Sep-28 FUSIA NEW YORK, NY $-15.25 $-15.25
+09-Sep-28 FUSIA NEW YORK, NY AISER0033 $15.25 0
+09-Sep-29 BALUCHI'S NEW YORK,.. $-30.44 $-30.44
+09-Sep-29 BALUCHI'S NEW YORK,.. AISER0033 $20.00 $-10.44
+09-Sep-29 BALUCHI'S NEW YORK,.. AISER0036 $10.44 0
+09-Oct-01 Reimbursing AISER0036 AISER0036 $-10.44 $-10.44
+09-Oct-01 Reimbursing AISER0036 $10.44 0
+
+
+$ ./ledger -f AISER0033.dat reg %ER=AISER0033
+09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
+09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
+09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
+09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24
+
+
+$ ./ledger -f AISER0033.dat reg %ER=AISER0036
+09-Sep-29 BALUCHI'S NEW YORK,.. Internal:Travel $10.44 $10.44
+09-Oct-01 Reimbursing AISER0036 Bank:AISChecking $-10.44 0
+
+$ ./ledger -f AISER0033.dat reg %PROJECT=PROJXXXX
+09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
+09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
+09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
+09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24
+
+$ ./ledger -f AISER0033.dat --prepend-format='%(tag("IMG")) ' reg %ER=0033
+image1.jpg 09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
+image2.jpg 09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
+image3.jpg 09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
+image4.jpg 09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24
diff --git a/contrib/raw/README b/contrib/raw/README
new file mode 100644
index 00000000..82ae74e1
--- /dev/null
+++ b/contrib/raw/README
@@ -0,0 +1,5 @@
+These scripts are from my (rladams) local ledger customizations.
+
+They are intended as examples for features that can be made generic to benefit other Ledger users.
+
+As they become refined, the raw files will be removed and replaced by suitable sources in contrib.
diff --git a/contrib/raw/VerifyImages.sh b/contrib/raw/VerifyImages.sh
new file mode 100755
index 00000000..5975f7cf
--- /dev/null
+++ b/contrib/raw/VerifyImages.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+grep -h '; RECEIPT: ' \
+ *.dat \
+ */*.dat \
+ | sed 's,\W*; RECEIPT: ,,g' \
+ | tr , '\n' \
+ | sort -u \
+ | while read X
+do
+ [ -f "$X" ] \
+ && echo OK $X \
+ || echo XX $X
+done
diff --git a/contrib/raw/dotemacs.el b/contrib/raw/dotemacs.el
new file mode 100644
index 00000000..b270042e
--- /dev/null
+++ b/contrib/raw/dotemacs.el
@@ -0,0 +1,201 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Ledger
+
+;; Maybe later add this to the expense repo once it settles
+(add-to-list 'load-path "/home/adamsrl/.emacs.d/addons/ledger")
+
+(add-to-list 'load-path "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/bin")
+(autoload 'ledger-mode "ldg-new" nil t)
+(add-to-list 'auto-mode-alist '("\\.dat$" . ledger-mode))
+
+(add-hook 'ledger-mode-hook
+ (lambda ()
+ (setq truncate-lines 1)
+ (url-handler-mode 1) ; Enable hyperlinks
+ (require 'ledger-matching) ; Requires ldg-report anyway
+ (load-file "/home/adamsrl/.emacs.d/addons/ledger/ldg-xact.el")
+ (let ((map (current-local-map)))
+ (define-key map (kbd "\C-c o") 'find-file-at-point) ; Open images
+ (define-key map (kbd "<f8>") 'ledger-expense-shortcut)
+ (define-key map (kbd "M-i") 'ledger-expense-internal)
+ (define-key map (kbd "M-o") 'ledger-expense-personal)
+ (define-key map (kbd "M-'") 'ledger-expense-split)
+ (define-key map (kbd "M-n") '(lambda ()
+ (interactive)
+ (ledger-post-next-xact)
+ (recenter)
+ (when (get-buffer "*Receipt*")
+ (ledger-expense-show-receipt))))
+ (define-key map (kbd "M-p") '(lambda () (interactive)
+ (ledger-post-prev-xact)
+ (recenter)
+ (when (get-buffer "*Receipt*")
+ (ledger-expense-show-receipt))))
+ (local-unset-key [tab]) ; Ideally this turns off pcomplete
+ (local-unset-key [(control ?i)]) ; Ideally this turns off pcomplete
+ )
+
+ ;(defface ledger-report-face-account-ok '((t (:foreground "Cyan"))) "Derp")
+ ;(defface ledger-report-face-account-bad '((t (:foreground "Red"))) "Derp")
+
+ (font-lock-add-keywords
+ 'ledger-mode
+ '(("Unassigned\\|Unknown\\|; RECEIPT:$" 0 'highlight prepend))) ))
+
+;; My customizations to make receipt image matching work with ledger-report mode
+(add-hook 'ledger-report-mode-hook
+ (lambda ()
+ (hl-line-mode 1)
+ (local-set-key (kbd "<RET>") 'ledger-report-visit-source) ; Make return jump to the right txn
+ (local-set-key (kbd "<tab>") 'ledger-report-visit-source) ; Make tab jump to the right txn
+ (local-set-key (kbd "n") '(lambda ()
+ (interactive)
+ (save-selected-window
+ (next-line)
+ (ledger-report-visit-source)))) ; Update a txn window but keep focus
+ (local-set-key (kbd "p") '(lambda ()
+ (interactive)
+ (save-selected-window
+ (previous-line)
+ (ledger-report-visit-source)))) ; Update a txn window but keep focus
+
+
+ (local-set-key (kbd "M-r") 'ledger-receipt-matching) ; Link receipt to current item
+ (local-set-key (kbd "M-l") 'ledger-matching-tie-receipt-to-txn) ; Link receipt to current item
+ (local-set-key (kbd "M-n") '(lambda ()
+ (interactive)
+ (ledger-matching-image-offset-adjust 1))) ; Next receipt image
+ (local-set-key (kbd "M-p") '(lambda ()
+ (interactive)
+ (ledger-matching-image-offset-adjust -1))) ; prev receipt image
+ (local-set-key (kbd "M-s") '(lambda ()
+ (interactive)
+ (ledger-receipt-skip))) ; Skip receipt image
+ (local-set-key (kbd "C-c C-e") '(lambda () (interactive)
+ (save-selected-window
+ (ledger-report-visit-source)
+ (ledger-toggle-current-entry) ))) ; Toggle entry
+ ))
+
+(defvar *ledger-expense-shortcut-ER*
+ "Current expense report number, just last four digits (ie: 1234 results in AISER1234).")
+
+(defvar *ledger-expense-shortcut-split-ER*
+ "Split (ie: internal) expense report number, just last four digits (ie: 1234 results in AISER1234).")
+
+(defvar *ledger-expense-shortcut-Proj* ""
+ "Current export report project code (ie: AGIL1292)")
+
+(defun ledger-expense-shortcut-ER-format-specifier () *ledger-expense-shortcut-ER*)
+
+(defun ledger-expense-shortcut-setup (ER Split Proj)
+ "Sets the variables expanded into the transaction."
+ (interactive "MER Number (4 digit number only): \nMSplit ER Number (4 digit number only): \nMProject: ")
+ (setq *ledger-expense-shortcut-ER*
+ (concatenate 'string "AISER" ER))
+ (setq *ledger-expense-shortcut-split-ER*
+ (concatenate 'string "AISER" Split))
+ (setq *ledger-expense-shortcut-Proj* Proj)
+ (setq ledger-matching-project Proj)
+ (message "Set Proj to %s and ER to %s, split to %s"
+ *ledger-expense-shortcut-Proj*
+ *ledger-expense-shortcut-ER*
+ *ledger-expense-shortcut-split-ER*))
+
+(defun ledger-expense-shortcut ()
+ "Updates the ER and Project metadata with the current values of the shortcut variables."
+ (interactive)
+ (when (eq major-mode 'ledger-mode)
+ (if (or (eql *ledger-expense-shortcut-ER* "")
+ (eql *ledger-expense-shortcut-Proj* ""))
+ (message "Run ledger-expense-shortcut-setup first.")
+ (save-excursion
+ (search-forward "; ER:")
+ (kill-line nil)
+ (insert " " *ledger-expense-shortcut-ER*))
+ (save-excursion
+ (search-forward "; PROJECT:")
+ (kill-line nil)
+ (insert " " *ledger-expense-shortcut-Proj*)))))
+
+(defun ledger-expense-split ()
+ "Splits the current transaction between internal and projects."
+ (interactive)
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (re-search-forward "^ +Dest:Projects")
+ (move-beginning-of-line nil)
+ (let ((begin (point))
+ (end (re-search-forward "^$")))
+ (goto-char end)
+ (insert (buffer-substring begin end))
+ (goto-char end)
+ (re-search-forward "^ Dest:Projects")
+ (replace-match " Dest:Internal")
+ (re-search-forward "; ER: +[A-Za-z0-9]+")
+ (replace-match (concat "; ER: " *ledger-expense-shortcut-split-ER*) t)
+ (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
+ (replace-match "; CATEGORY: Travel" t))))
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (re-search-forward "^ +Dest:Projects")
+ (insert-string " $") ))
+
+(defun ledger-expense-internal ()
+ "Makes the expense an internal one."
+ (interactive)
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (let ((begin (point))
+ (end (save-excursion (re-search-forward "^$"))))
+ (when (re-search-forward "^ Dest:Projects" end t)
+ (replace-match " Dest:Internal") )
+ (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
+ (replace-match "; CATEGORY: Travel" t))))))
+
+(defun ledger-expense-personal ()
+ "Makes the expense an personal one, eliminating metadata and receipts."
+ (interactive)
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (let ((begin (point))
+ (end (save-excursion (re-search-forward "^$"))))
+ (when (re-search-forward "^ Dest:Projects" end t)
+ (replace-match " Other:Personal"))
+ (goto-char begin)
+ (save-excursion
+ (when (re-search-forward "^ +; ER:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (save-excursion
+ (when (re-search-forward "^ +; PROJECT:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (save-excursion
+ (when (re-search-forward "^ +; CATEGORY:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (save-excursion
+ (when (re-search-forward "^ +; RECEIPT:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (ledger-toggle-current-entry)))))
+
+(defun ledger-expense-show-receipt ()
+ "Uses the Receipt buffer to show the receipt of the txn we're on."
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (let ((begin (point))
+ (end (save-excursion (re-search-forward "^$"))))
+ (save-excursion
+ (when (re-search-forward "^\\( +; RECEIPT: +\\)\\([^,]+?.jpg\\).*$" end t)
+ (ledger-matching-display-image
+ (concat "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/"
+ (match-string 2))) ))))))
diff --git a/contrib/raw/ledger-matching.el b/contrib/raw/ledger-matching.el
new file mode 100644
index 00000000..7c568126
--- /dev/null
+++ b/contrib/raw/ledger-matching.el
@@ -0,0 +1,342 @@
+;; This library is intended to allow me to view a receipt on one panel, and tie it to ledger transactions in another
+
+(require 'ldg-report)
+
+(defgroup ledger-matching nil
+ "Ledger image matching")
+
+(defcustom ledger-matching-sourcedir "~/AdamsInfoServ/BusinessDocuments/Ledger/Incoming"
+ "Source directory for images to process, ie: the incoming queue of images."
+ :group 'ledger-matching)
+
+(defcustom ledger-matching-destdir "~/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/Receipts"
+ "Destination directory for images when matched, will still have a project directory appended to it."
+ :group 'ledger-matching)
+
+(defcustom ledger-matching-relative-receipt-dir "Receipts"
+ "Relative directory root for destination images used in Ledger entries, will have the project directory appended and receipt filename."
+ :group 'ledger-matching)
+
+(defcustom ledger-matching-convert-binary "/usr/bin/convert"
+ "Path to the Imagemagick convert command."
+ :group 'ledger-matching)
+
+(defcustom ledger-matching-scale 50
+ "Scaling parameter to Imagemagick's convert to resize an image for viewing."
+ :group 'ledger-matching)
+
+(defcustom ledger-matching-rotation 0
+ "Rotation parameter to Imagemagick's convert to rotate an image for viewing. Images on disk should always be upright for reading."
+ :group 'ledger-matching)
+
+
+(defconst ledger-matching-image-buffer "*Receipt*"
+ "Buffer name we load images into. Created if it doesn't exist, and persists across image loads.")
+
+
+(defvar ledger-matching-project "Internal"
+ "The directory appended to the destination for the project code where receipts will be stored.")
+
+(defvar ledger-matching-image-offset 0
+ "The index of the current file from the SORTED source directory contents.")
+
+(defvar ledger-matching-image-name nil
+ "The filename only of the current image.")
+
+
+(defun ledger-matching-display-image (image-filename)
+ "Resize the image and load it into our viewing buffer."
+
+ ;; Create our viewing buffer if needed, and set it. Do NOT switch,
+ ;; this buffer isn't the primary. Let the user leave it where they
+ ;; place it.
+ (unless (get-buffer ledger-matching-image-buffer)
+ (get-buffer-create ledger-matching-image-buffer))
+ (set-buffer ledger-matching-image-buffer)
+ (erase-buffer)
+ (goto-char (point-min))
+ (insert-string image-filename "\n")
+
+ ;; Convert the source to the temporary dest applying resizing and rotation
+ (let* ((source (expand-file-name image-filename ledger-matching-sourcedir))
+ (dest (make-temp-file "ledger-matching-" nil ".jpg"))
+ (result (call-process ledger-matching-convert-binary nil (get-buffer "*Messages*") nil
+ source
+ "-scale" (concat (number-to-string ledger-matching-scale) "%")
+ "-rotate" (number-to-string ledger-matching-rotation)
+ dest)))
+
+ (if (/= 0 result)
+
+ ;; Bomb out if the convert fails
+ (message "Error running convert, see *Messages* buffer for details.")
+
+ ;; Insert scaled image into the viewing buffer, replacing
+ ;; current contents Temp buffer is to force sync reading into
+ ;; memory of the jpeg due to async race condition with display
+ ;; and file deletion
+ (let ((image (create-image (with-temp-buffer
+ (insert-file-contents-literally dest)
+ (string-as-unibyte (buffer-string)))
+ 'jpeg t)))
+ (insert-image image)
+ (goto-char (point-min))
+
+ ;; Redisplay is required to prevent a race condition between displaying the image and the deletion. Apparently its async.
+ ;; Either redisplay or the above string method work, both together can't hurt.
+ (redisplay)
+ ))
+
+ ;; Delete our temporary file
+ (delete-file dest)))
+
+
+
+(defun ledger-matching-update-current-image ()
+ "Grab the image from the source directory by offset and display"
+
+ (let* ((file-listing (directory-files ledger-matching-sourcedir nil "\.jpg$" nil))
+ (len (safe-length file-listing)))
+
+ ;; Ensure our offset doesn't exceed the file list
+ (cond ((= len 0)
+ (message "No files found in source directory."))
+
+ ((< len 0)
+ (message "Error, list of files should never be negative. Epic fail."))
+
+ ((>= ledger-matching-image-offset len)
+ (message "Hit end of list. Last image.")
+ (setq ledger-matching-image-offset (1- len)))
+
+ ((< ledger-matching-image-offset 0)
+ (message "Beginning of list. First image.")
+ (setq ledger-matching-image-offset 0)))
+
+ ;; Get the name for the offset
+ (setq ledger-matching-image-name (nth ledger-matching-image-offset file-listing))
+
+ (ledger-matching-display-image ledger-matching-image-name)))
+
+
+
+(defun ledger-matching-image-offset-adjust (amount)
+ "Incr/decr the offset and update the receipt buffer."
+
+ (setq ledger-matching-image-offset (+ ledger-matching-image-offset amount))
+ (ledger-matching-update-current-image))
+
+
+
+(defun ledger-receipt-matching ()
+ "Open the receipt buffer and start with the first image."
+ (interactive)
+ (setq ledger-matching-image-offset 0)
+ (ledger-matching-update-current-image))
+
+
+
+(defun ledger-matching-tie-receipt-to-txn ()
+ (interactive)
+ (save-selected-window
+ (ledger-report-visit-source)
+
+ ;; Assumes we're in a narrowed buffer with ONLY this txn
+ (backward-paragraph)
+ (beginning-of-line)
+
+ ;; ;; Update the ER and Project while I'm there
+ ;; (save-excursion
+ ;; (search-forward "; ER:")
+ ;; (kill-line nil)
+ ;; (insert " " *ledger-expense-shortcut-ER*))
+ ;; Just do the project for now.
+ (save-excursion
+ (search-forward "; PROJECT:")
+ (kill-line nil)
+ (insert " " *ledger-expense-shortcut-Proj*))
+
+ ;; Goto the receipt line, unless their isn't one then add one
+ (unless (search-forward "RECEIPT:" nil t)
+
+ ;; Still at date line if that failed
+ (next-line)
+ (newline)
+ (insert-string " ; RECEIPT:"))
+
+ ;; Point immediately after : on tag
+
+ ;; Check for existing jpg file
+ (if (search-forward ".jpg" (line-end-position) t)
+
+ ;; if present make it a comma delimited list
+ (insert-string ",")
+
+ ;; otherwise just add a space to pad
+ (insert-string " "))
+
+ ;; Add our relative filename as the value of the RECEIPT tag
+ (insert-string (concat ledger-matching-relative-receipt-dir "/"
+ ledger-matching-project "/"
+ ledger-matching-image-name))
+
+ ;; Create the destination project dir if it doesn't exist.
+ (let ((full-destination (concat ledger-matching-destdir "/" ledger-matching-project )))
+ (unless (file-accessible-directory-p full-destination)
+ (make-directory full-destination t)))
+
+ ;; Rename the file from the source directory to its permanent home
+ (rename-file (concat ledger-matching-sourcedir "/"
+ ledger-matching-image-name)
+ (concat ledger-matching-destdir "/"
+ ledger-matching-project "/"
+ ledger-matching-image-name))
+
+ ;; Update the receipt screen
+ (ledger-matching-update-current-image)
+
+ (message "Filed %s to project %s" ledger-matching-image-name ledger-matching-project)))
+
+
+
+(defun ledger-receipt-skip ()
+ "Move the current image to the Skip directory because its not relevant."
+
+ (rename-file (concat ledger-matching-sourcedir "/"
+ ledger-matching-image-name)
+ (concat ledger-matching-sourcedir "/Skip/"
+ ledger-matching-image-name))
+
+ ;; Update the receipt screen at the same offset
+ (ledger-matching-update-current-image))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Items below are speed entry macros, and should eventually migrate to their own file.
+
+(defvar *ledger-expense-shortcut-ER*
+ "Current expense report number, just last four digits (ie: 1234 results in AISER1234).")
+
+(defvar *ledger-expense-shortcut-split-ER*
+ "Split (ie: internal) expense report number, just last four digits (ie: 1234 results in AISER1234).")
+
+(defvar *ledger-expense-shortcut-Proj* ""
+ "Current export report project code (ie: AGIL1292)")
+
+(defun ledger-expense-shortcut-ER-format-specifier () *ledger-expense-shortcut-ER*)
+
+(defun ledger-expense-shortcut-Project-format-specifier () *ledger-expense-shortcut-Proj*)
+
+(defun ledger-expense-shortcut-setup (ER Split Proj)
+ "Sets the variables expanded into the transaction."
+ (interactive "MER Number (ER or IN and 4 digit number only): \nMSplit ER Number (ER or IN and 4 digit number only): \nMProject: ")
+ (setq *ledger-expense-shortcut-ER*
+ (concatenate 'string "AIS" ER))
+ (setq *ledger-expense-shortcut-split-ER*
+ (concatenate 'string "AIS" Split))
+ (setq *ledger-expense-shortcut-Proj* Proj)
+ (setq ledger-matching-project Proj)
+ (message "Set Proj to %s and ER to %s, split to %s"
+ *ledger-expense-shortcut-Proj*
+ *ledger-expense-shortcut-ER*
+ *ledger-expense-shortcut-split-ER*))
+
+(defun ledger-expense-shortcut ()
+ "Updates the ER and Project metadata with the current values of the shortcut variables."
+ (interactive)
+ (when (eq major-mode 'ledger-mode)
+ (if (or (eql *ledger-expense-shortcut-ER* "")
+ (eql *ledger-expense-shortcut-Proj* ""))
+ (message "Run ledger-expense-shortcut-setup first.")
+ (save-excursion
+ (search-forward "; ER:")
+ (kill-line nil)
+ (insert " " *ledger-expense-shortcut-ER*))
+ (save-excursion
+ (search-forward "; PROJECT:")
+ (kill-line nil)
+ (insert " " *ledger-expense-shortcut-Proj*)))))
+
+(defun ledger-expense-split ()
+ "Splits the current transaction between internal and projects."
+ (interactive)
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (re-search-forward "^ +Dest:Projects")
+ (move-beginning-of-line nil)
+ (let ((begin (point))
+ (end (re-search-forward "^$")))
+ (goto-char end)
+ (insert (buffer-substring begin end))
+ (goto-char end)
+ (re-search-forward "^ Dest:Projects")
+ (replace-match " Dest:Internal")
+ (re-search-forward "; ER: +[A-Za-z0-9]+")
+ (replace-match (concat "; ER: " *ledger-expense-shortcut-split-ER*) t)
+ (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
+ (replace-match "; CATEGORY: Travel" t))))
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (re-search-forward "^ +Dest:Projects")
+ (insert-string " $") ))
+
+(defun ledger-expense-internal ()
+ "Makes the expense an internal one."
+ (interactive)
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (let ((begin (point))
+ (end (save-excursion (re-search-forward "^$"))))
+ (when (re-search-forward "^ Dest:Projects" end t)
+ (replace-match " Dest:Internal") )
+ (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
+ (replace-match "; CATEGORY: Travel" t))))))
+
+(defun ledger-expense-personal ()
+ "Makes the expense an personal one, eliminating metadata and receipts."
+ (interactive)
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (let ((begin (point))
+ (end (save-excursion (re-search-forward "^$"))))
+ (when (re-search-forward "^ Dest:Projects" end t)
+ (replace-match " Other:Personal"))
+ (goto-char begin)
+ (save-excursion
+ (when (re-search-forward "^ +; ER:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (save-excursion
+ (when (re-search-forward "^ +; PROJECT:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (save-excursion
+ (when (re-search-forward "^ +; CATEGORY:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (save-excursion
+ (when (re-search-forward "^ +; RECEIPT:" end t)
+ (beginning-of-line)
+ (kill-line 1)))
+ (ledger-toggle-current-entry)))))
+
+(defun ledger-expense-show-receipt ()
+ "Uses the Receipt buffer to show the receipt of the txn we're on."
+ (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
+ (save-excursion
+ (end-of-line)
+ (re-search-backward "^[0-9]\\{4\\}/")
+ (let ((begin (point))
+ (end (save-excursion (re-search-forward "^$"))))
+ (save-excursion
+ (when (re-search-forward "^\\( +; RECEIPT: +\\)\\([^,]+?.jpg\\).*$" end t)
+ (ledger-matching-display-image
+ (concat "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/"
+ (match-string 2))) ))))))
+
+
+(provide 'ledger-matching)
diff --git a/contrib/raw/ledger-shell-environment-functions b/contrib/raw/ledger-shell-environment-functions
new file mode 100644
index 00000000..7746dc41
--- /dev/null
+++ b/contrib/raw/ledger-shell-environment-functions
@@ -0,0 +1,90 @@
+# Environment for ledger expenses
+
+[ $(whoami) == "adamsrl" ] \
+ && export LEDGER_HOME="/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell" \
+ || export LEDGER_HOME="/home/Heather/AdamsRussell"
+
+[ $(hostname) == "cardamom" ] \
+ && export LEDGER_BIN="${LEDGER_HOME}/ledger" \
+ || export LEDGER_BIN="${LEDGER_HOME}/ledger.exe"
+
+[ $(whoami) == "andersonll" ] \
+ && export LEDGER_HOME="/home/andersonll/AdamsInfoServ/Expenses" \
+ && export LEDGER_BIN="${LEDGER_HOME}/ledger"
+
+# Common reports
+
+alias ledger='${LEDGER_BIN} -f "${LEDGER_HOME}/.ledger" -VE '
+alias ERSummary='ledger --pivot ER bal | egrep "AIS(ER|IN)[0-9]+|Unassigned"'
+
+function ERTxns() {
+ [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
+
+ ledger reg "%ER=${1}"
+}
+
+function ERCategorySummary() {
+ [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
+
+ ledger bal --pivot CATEGORY "%ER=${1}"
+}
+
+function ERMealSummary() {
+ [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
+
+ ledger reg "%ER=${1}" and %CATEGORY=Meals -D
+}
+
+function ERMeals() {
+ [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
+
+ ledger reg "%ER=${1}" and %CATEGORY=Meals
+}
+
+function ERUncleared() {
+ [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
+
+ ledger reg "%ER=${1}" -U
+}
+
+function ERMissingReceipts() {
+ [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
+
+ ledger reg "%ER=${1}" and not %RECEIPT
+}
+
+function ERVerify() {
+ [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
+
+ echo "========== Uncleared txns below =========="
+ ERUncleared "$1"
+ echo "========== Missing receipts below (miles and stubs ok) =========="
+ ERMissingReceipts "$1"
+ echo "========== Category Summary (airline? mileage? car? hotel? =========="
+ ERCategorySummary "$1"
+ echo "========== Meal summary (<\$50 / day unless otherwise specified) =========="
+ ERMealSummary "$1"
+ echo "========== Account Verification (Internal vs Project ER should be ONE type) =========="
+ echo $1 | grep AISIN >/dev/null 2>&1 \
+ || { ledger reg "%ER=${1}" | grep Dest:Internal ; } \
+ && { ledger reg "%ER=${1}" | grep Dest:Projects ; }
+ echo "========== Project Verification (only one project code should be listed) =========="
+ ledger print "%ER=${1}" | grep PROJECT | sort -u
+ echo "========== Receipts missing =========="
+ ledger print "%ER=${1}" | grep -h '; RECEIPT: ' \
+ | sed 's,\W*; RECEIPT: ,,g' \
+ | tr , '\n' \
+ | sort -u \
+ | while read X ; do
+ [ -f "${LEDGER_HOME}/${X}" ] \
+ || echo XX $X
+ done
+}
+
+function ERListing() {
+ ledger reg Stub --register-format="%(tag('ER')) %(tag('NOTE'))\n" | sort -u
+}
+
+function ERQueue() {
+ ledger reg %ER=Unassigned --prepend-format="%(filename) "
+}