summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Earls <enderw88@gmail.com>2013-01-29 13:45:44 -0700
committerCraig Earls <enderw88@gmail.com>2013-01-29 13:45:44 -0700
commit870c62bf14ec75d0bf2e951126c20747bee174f1 (patch)
tree39c0fa9e16775f2477710d6b0960146ed09f62dd
parentc1ad213f4e197b80a0b619bb04769f807a2be645 (diff)
parentc235b59a68d04ff22a16d317e4d67b69e570a1e4 (diff)
downloadfork-ledger-870c62bf14ec75d0bf2e951126c20747bee174f1.tar.gz
fork-ledger-870c62bf14ec75d0bf2e951126c20747bee174f1.tar.bz2
fork-ledger-870c62bf14ec75d0bf2e951126c20747bee174f1.zip
Merge commit 'c235b59a68d04ff22a16d317e4d67b69e570a1e4' into kitchen-sink
-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
-rw-r--r--doc/ledger3.texi89
-rw-r--r--lisp/ldg-report.el64
9 files changed, 1282 insertions, 63 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) "
+}
diff --git a/doc/ledger3.texi b/doc/ledger3.texi
index f60bb26e..377d740c 100644
--- a/doc/ledger3.texi
+++ b/doc/ledger3.texi
@@ -2325,7 +2325,9 @@ doing it.
Journal files are simple free text files easily modified by any text
editor. A special mode for EMACS is included with the source
-distribution.
+distribution. This mode provides syntax highlighting, a reconcile mode
+and a report mode. This makes it quote possible to use ledger without
+ever leaving EMACS.
@cindex EMACS .emacs file
@@ -2335,7 +2337,7 @@ Add the following line to your @file{.emacs} (or equivalent,
(load "ldg-new")
@end smallexample
-Copy the several lisp files from the source lisp directory your your
+Copy the several lisp files (@file{ldg-*.el}) from the source lisp directory your your
@file{site-lisp} directory, or add the ledger lisp source directory to
your EMACS load path by adding:
@smallexample
@@ -2352,7 +2354,8 @@ To enter ledger-mode on a new file, type @command{M-x ledger-mode}.
Once you have loaded a Journal file into EMACS, you have several
commands available to make entering, clearing and reconciling
-transactions and producing reports:
+transactions and producing reports (these are also available from the
+menu if you can't remember the key combinations):
@cindex EMACS commands
@table @code
@@ -2431,10 +2434,11 @@ will be interpreted as a new account by ledger.
@code{C-c C-a} will run the @code{ledger entry} command (@pxref{entry
and xact}) from within EMACS. When typed, the mini-buffer will appear
with the current year and month, waiting for you to enter the day and
-the payee. Ledger will generate a new entry based on the most recent
-entry for that payee, using the amount and accounts from that
-transaction. If you have a new amount simply type the amount after the
-payee. For example, if your journal contains an entry
+the payee, and optionally, a commoditized amount. Ledger will generate
+a new entry based on the most recent entry for that payee, using the
+amount and accounts from that transaction. If you have a new amount
+simply type the amount after the payee. For example, if your journal
+contains an entry
@smallexample
2011/11/25 Viva Italiano
Expenses:Food $12.45
@@ -2454,7 +2458,10 @@ Entry: 2011/11/28 viva food 34 tip 7 <enter>
Liabilities:MasterCard
@end smallexample
@noindent Notice that the entry will appear at the correct place in the journal
-ordered by date, not necessarily at the bottom of the file.
+ordered by date, not at the bottom of the file. If you need to include
+spaces in the payee name, then surrond the name of the payee with double
+quotes, otherwise ledger will interpret the second part of the name as
+an account.
@node Clearing Transactions, , Automagically Adding new entries, Working with entries
@subsubsection Clearing Transactions and Postings
@cindex clearing transactions in EMACS
@@ -2483,21 +2490,46 @@ toggled.
@node Reconciling accounts, Generating Reports, Working with entries, Using EMACS
@subsection Reconciling accounts
-In the reconcile buffer, use SPACE to toggle the cleared status of a
-transaction, C-x C-s to save changes (to the ledger file as well).
+Enter the reconcile mode using the menu entry, or @code{C-c C-r}. Emacs
+will prompt you for an account name, then display a second buffer with
+all of the uncleared transactions. The reconcile buffer has several functions:
+@table @code
+ @item SPACE
+ toggles the cleared status of a transaction, and show cleared balance inthe minibuffer
+ @item RETURN
+ moves the cursor to that transaction in the ledger.
+ @item C-x C-s
+ to save changes (to the ledger file as well).
+ @item q
+ quite the reconcile mode
+ @item n p
+ next line or previous line
+ @item A
+ add entry
+ @item D
+ delete entry
+ @item C-l
+ refresh display
+@end table
@node Generating Reports, , Reconciling accounts, Using EMACS
@subsection Generating Reports
The ledger reports command asks the user to select a report to run then
creates a report buffer containing the results of running the associated
-command line. Its' behavior is modified by a prefix argument which,
-when given, causes the generated command line that will be used to
-create the report to be presented for editing before the report is
-actually run. Arbitrary unnamed command lines can be run by specifying
-an empty name for the report. The command line used can later be named
-and saved for future use as a named report from the generated reports
-buffer.
+command line. This allows you to run frequently used reports without
+retyping the command line, or writing shell scripts for simple one line
+commands.
+
+To generate a report, select the @code{Run Reports} menu, or type
+@code{C-c C-o C-r}. Emacs will prompt for a report name. If it
+recognizes the name it will run the report again. If it is a new name,
+or blank it will respond by giving you an example command line to edit.
+Hitting return willrun the report and present it in a new buffer.
+
+If you have given it a new name, then @code{s} will save the report for
+future use.
+
In a report buffer, the following keys are available:
@table @code
@@ -2542,7 +2574,7 @@ kill the report buffer
* Lot dates::
* Lot notes::
* Lot value expressions::
-* Automated Transactions::
+* Automated Transactions::
@end menu
@node Basic format, Eliding amounts, Transactions , Transactions
@@ -4744,6 +4776,7 @@ database files.
@menu
* accounts::
* commodities::
+* tags::
* entry and xact::
* payees::
@end menu
@@ -4753,15 +4786,27 @@ database files.
The @command{accounts} reports all of the accounts in the journal.
Following the command with a regular expression will limit the output to
-accounts matching the regex.
+accounts matching the regex. The output is sorted by name. Using the
+@code{--count} option will tell you haw many entries use each account.
-@node commodities, entry and xact, accounts, Reports about your Journals
+@node commodities, tags, accounts, Reports about your Journals
@subsection @command{commodities}
-Report all commodities present in the journals under consideration.
+Report all commodities present in the journals under consideration. The
+ output is sorted by name. Using the @code{--count} option will tell
+ you haw many entries use each commodity.
+
+@node tags, entry and xact, commodities, Reports about your Journals
+@subsection @command{tags}
+
+The @command{tags} reports all of the tags in the journal. The output
+is sorted by name. Using the @code{--count} option will tell you haw
+many entries use each tag. Using the @code{--values} option will report
+the values used by each tag.
+
-@node entry and xact, payees, commodities, Reports about your Journals
+@node entry and xact, payees, tags, Reports about your Journals
@subsection @command{draft}, @command{entry} and @command{xact}
The @code{draft}, @code{entry} and @command{xact} commands simplify the
diff --git a/lisp/ldg-report.el b/lisp/ldg-report.el
index efd9bdb4..a1ffe3b0 100644
--- a/lisp/ldg-report.el
+++ b/lisp/ldg-report.el
@@ -246,72 +246,54 @@ the default."
(ledger-reports-custom-save))
report-cmd))
-(defvar ledger-report-patch-alist nil)
-
-(defun ledger-report-patch-reports (buf)
- (when ledger-report-patch-alist
- (let ((entry (assoc (expand-file-name (buffer-file-name buf))
- ledger-report-patch-alist)))
- (when entry
- (dolist (b (cdr entry))
- (if (buffer-live-p b)
- (with-current-buffer b
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (let ((record (get-text-property (point) 'ledger-source)))
- (if (and record (not (markerp (cdr record))))
- (setcdr record (with-current-buffer buf
- (save-excursion
- (goto-char (point-min))
- (forward-line (cdr record))
- (point-marker))))))
- (forward-line 1))))))))))
-
(defun ledger-do-report (cmd)
"Run a report command line."
(goto-char (point-min))
(insert (format "Report: %s\n" ledger-report-name)
(format "Command: %s\n" cmd)
(make-string (- (window-width) 1) ?=)
- "\n")
- (let ((register-report (string-match " reg\\(ister\\)? " cmd))
+ "\n\n")
+ (let ((data-pos (point))
+ (register-report (string-match " reg\\(ister\\)? " cmd))
files-in-report)
(shell-command
(if register-report
(concat cmd " --prepend-format='%(filename):%(beg_line):'")
cmd) t nil)
(when register-report
- (goto-char (point-min))
+ (goto-char data-pos)
(while (re-search-forward "^\\([^:]+\\)?:\\([0-9]+\\)?:" nil t)
(let ((file (match-string 1))
- (line (string-to-number (match-string 2))))
+ (line (string-to-number (match-string 2))))
(delete-region (match-beginning 0) (match-end 0))
(set-text-properties (line-beginning-position) (line-end-position)
- (list 'ledger-source (cons file line)))
- (let* ((fullpath (expand-file-name file))
- (entry (assoc fullpath ledger-report-patch-alist)))
- (if entry
- (nconc (cdr entry) (list (current-buffer)))
- (push (cons (expand-file-name file)
- (list (current-buffer)))
- ledger-report-patch-alist))
- (add-to-list 'files-in-report fullpath)))
-
- (dolist (path files-in-report)
- (let ((buf (get-file-buffer path)))
- (if (and buf (buffer-live-p buf))
- (ledger-report-patch-reports buf))))))))
+ (list 'ledger-source (cons file (save-window-excursion
+ (save-excursion
+ (find-file file)
+ (widen)
+ (goto-char (point-min))
+ (forward-line (1- line))
+ (point-marker))))))
+ (end-of-line))))
+ (goto-char data-pos)))
+
(defun ledger-report-visit-source ()
(interactive)
(let ((prop (get-text-property (point) 'ledger-source)))
(destructuring-bind (file . line-or-marker) prop
(find-file-other-window file)
+ (widen)
(if (markerp line-or-marker)
(goto-char line-or-marker)
(goto-char (point-min))
- (forward-line (1- line-or-marker))))))
+ (forward-line (1- line-or-marker))
+ (re-search-backward "^[0-9]+")
+ (beginning-of-line)
+ (let ((start-of-txn (point)))
+ (forward-paragraph)
+ (narrow-to-region start-of-txn (point))
+ (backward-paragraph))))))
(defun ledger-report-goto ()
"Goto the ledger report buffer."