3434
3535from ..api import Account
3636from ..util import commas , is_listlike , is_mapping
37- from ..defaults import INVOICE_CURRENCY , INVOICE_ROWS , INVOICE_STRFTIME , INVOICE_DUE , MM_IN , FILENAME_FORMAT
37+ from ..defaults import (
38+ INVOICE_CURRENCY , INVOICE_ROWS , INVOICE_STRFTIME , INVOICE_DUE , INVOICE_DESCRIPTION_MAX ,
39+ MM_IN , FILENAME_FORMAT ,
40+ )
3841from ..layout import Region , Text , Image , Box , Coordinate , layout_pdf
3942from .ethereum import tokeninfo , tokenprices , tokenknown
4043
4548
4649 Receipt -- The Invoice, marked "PAID"
4750
48-
4951"""
5052log = logging .getLogger ( "customer" )
5153
@@ -109,19 +111,40 @@ def conversions_table( conversions, symbols=None, greater=None ):
109111
110112 # The columns are typed according to the least generic type that *all* rows are convertible
111113 # into. So, if any row is a string, it'll cause the entire column to be formatted as strings.
112- return tabulate (
113- [
114- [ r ] + list (
115- ( '' if r == c or (r ,c ) not in conversions
116- else '' if ( greater and conversions .get ( (r ,c ) ) and conversions .get ( (c ,r ) )
117- and ( conversions [r ,c ] < ( greater if isinstance ( greater , (float ,int ) ) else conversions [c ,r ] )))
118- else conversions [(r ,c )] )
119- for c in symbols
114+ def fmt ( v , d ):
115+ if v :
116+ return round ( v , d )
117+ return v
118+
119+ convers_raw = [
120+ [ r ] + list (
121+ (
122+ '' if r == c or (r ,c ) not in conversions
123+ else '' if ( greater and conversions .get ( (r ,c ) ) and conversions .get ( (c ,r ) )
124+ and ( conversions [r ,c ] < ( greater if isinstance ( greater , (float ,int ) ) else conversions [c ,r ] )))
125+ else fmt ( conversions [r ,c ], 8 ) # ie. to the Sat (1/10^8 Bitcoin)
120126 )
121- for r in symbols
122- ],
123- headers = [ 'Coin' ] + [ f"in { s } " for s in symbols ],
124- floatfmt = ',.7g' ,
127+ for c in symbols
128+ )
129+ for r in symbols
130+ ]
131+ # Now, remove any column that are completely blank ('', not None). This will take place for
132+ # worthless (or very low valued) currencies, esp. w/ a small 'greater'. Transpose the
133+ # conversions (so each column is a row), and elide any w/ empty conversions rows. Finally,
134+ # re-transpose back to columns.
135+ headers_raw = [ 'Coin' ] + [ f"in { s } " for s in symbols ]
136+ convers_txp = list ( zip ( * convers_raw ))
137+ headers_use ,convers_use_txp = zip ( * [
138+ (hdr ,col )
139+ for hdr ,col in zip ( headers_raw , convers_txp )
140+ if any ( c != '' for c in col )
141+ ])
142+ convers_use = list ( zip ( * convers_use_txp ))
143+
144+ return tabulate (
145+ convers_use ,
146+ headers = headers_use ,
147+ floatfmt = ',.15g' ,
125148 intfmt = ',' ,
126149 missingval = '?' ,
127150 tablefmt = 'orgtbl' ,
@@ -406,6 +429,7 @@ def __init__(
406429 self .currencies_proxy = currencies_proxy # { "BTC": TokenInfo( "WBTC", ... ), ... }
407430 self .currencies_alias = currencies_alias # { "WBTC": TokenInfo( "BTC", ... ), ... }
408431 self .conversions = conversions # { ("BTC","ETH"): 14.3914, ... }
432+ self .created = datetime .utcnow ().astimezone ( timezone .utc )
409433
410434 def headers ( self ):
411435 """Output the headers to use in tabular formatting of iterator. By default, we'd recommend hiding any starting
@@ -615,7 +639,7 @@ def taxi( c ):
615639 # And the per-currency Sub-totals (for each page)
616640 [
617641 [
618- self .currencies_account [c ]. address ,
642+ str ( self .currencies_account [c ] ) ,
619643 round ( page [- 1 ][taxi ( c )], deci ( c )) if first else round ( page [- 1 ][taxi ( c )] - page_prev [- 1 ][taxi ( c )], deci ( c )),
620644 round ( page [- 1 ][toti ( c )], deci ( c )) if first else round ( page [- 1 ][toti ( c )] - page_prev [- 1 ][toti ( c )], deci ( c )),
621645 c ,
@@ -631,14 +655,14 @@ def taxi( c ):
631655 'Currency' ,
632656 ),
633657 intfmt = ',' ,
634- floatfmt = ',g ' ,
658+ floatfmt = ',.15g ' ,
635659 tablefmt = totalfmt or 'orgtbl' ,
636660 )
637661
638662 # And the per-currency Totals (up to current page)
639663 total_rows = [
640664 [
641- self .currencies_account [c ]. address ,
665+ str ( self .currencies_account [c ] ) ,
642666 round ( page [- 1 ][taxi ( c )], deci ( c )),
643667 round ( page [- 1 ][toti ( c )], deci ( c )),
644668 c ,
@@ -657,13 +681,13 @@ def taxi( c ):
657681 total_rows ,
658682 headers = total_headers ,
659683 intfmt = ',' ,
660- floatfmt = ',g ' ,
684+ floatfmt = ',.15g ' ,
661685 tablefmt = totalfmt or 'orgtbl' ,
662686 )
663687
664688 def fmt ( val , hdr , coin ):
665689 if can ( hdr ) in ( 'price' , 'taxes' , 'amount' ):
666- return round ( val , deci ( coin )) # f"{float( val ):,.{deci( coin )}f}" # rounds to designated decimal places, inserts comma
690+ return round ( val , deci ( coin ))
667691 return val
668692
669693 # Produce the page line-items. We must round each line-item's numeric price values
@@ -706,7 +730,7 @@ def fmt( val, hdr, coin ):
706730 # Presently, the Description column is the only one likely to have a width issue...
707731 maxcolwidths = None
708732 if description_max is None :
709- description_max = 48
733+ description_max = INVOICE_DESCRIPTION_MAX
710734 if description_max :
711735 desc_i = headers_can .index ( can ( "Description" ))
712736 maxcolwidths = [
@@ -718,8 +742,8 @@ def fmt( val, hdr, coin ):
718742 table = tabulate (
719743 table_rows ,
720744 headers = table_headers ,
721- intfmt = ',' , # intfmt,
722- floatfmt = ',g' , # floatfmt ,
745+ intfmt = ',' ,
746+ floatfmt = ',.15g' ,
723747 tablefmt = tablefmt or 'orgtbl' ,
724748 maxcolwidths = maxcolwidths ,
725749 )
@@ -1009,7 +1033,6 @@ def produce_invoice(
10091033 inv_date : Optional [datetime ] = None ,
10101034 inv_due : Optional [Any ] = None , # another datetime, or args/kwds for datetime_advance
10111035 terms : Optional [str ] = None , # eg. "Payable on receipt in $USDC, $ETH, $BTC"
1012- conversions : Optional [Dict ] = None ,
10131036 rows : Optional [int ] = None ,
10141037 paper_format : Any = None , # 'Letter', 'Legal', 'A4', (x,y) dimensions in mm.
10151038 orientation : Optional [str ] = None , # available orientations; default portrait, landscape
@@ -1042,7 +1065,7 @@ def produce_invoice(
10421065 # Any datetime WITHOUT a timezone designation is re-interpreted as the local timezone of the
10431066 # invoice issuer, or UTC.
10441067 if inv_date is None :
1045- inv_date = datetime . utcnow (). astimezone ( timezone . utc )
1068+ inv_date = invoice . created
10461069 if inv_date .tzname () is None :
10471070 try :
10481071 inv_zone = get_localzone ()
@@ -1054,6 +1077,7 @@ def produce_invoice(
10541077 if not isinstance ( inv_due , datetime ):
10551078 if not inv_due :
10561079 inv_due = INVOICE_DUE
1080+ log .info ( f"Due w/ { inv_due !r} " )
10571081 if is_mapping ( inv_due ):
10581082 inv_due = datetime_advance ( inv_date , ** dict ( inv_due ))
10591083 elif is_listlike ( inv_due ):
@@ -1081,9 +1105,6 @@ def produce_invoice(
10811105 directory = directory ,
10821106 )
10831107
1084- if conversions is None :
1085- conversions = {}
1086-
10871108 # Default to full page, given the desired paper and orientation. All PDF dimensions are mm.
10881109 invs_pp ,orientation ,page_xy ,pdf ,comp_dim = layout_pdf (
10891110 paper_format = paper_format ,
@@ -1130,14 +1151,15 @@ def produce_invoice(
11301151 inv_tpl ['inv-client-info-bg' ] = layout / '1x1-ffffffbf.png'
11311152
11321153 dets = tabulate ( [
1133- [ 'Invoice #' , metadata .number ],
1154+ [ 'Invoice #: ' , metadata .number ],
11341155 [ 'Date:' , metadata .date .strftime ( INVOICE_STRFTIME ) ],
1135- [ 'Due:' , metadata .date .strftime ( INVOICE_STRFTIME ) ],
1156+ [ 'Due:' , metadata .due .strftime ( INVOICE_STRFTIME ) ],
11361157 ], colalign = ( 'right' , 'left' ), tablefmt = 'plain' )
11371158
11381159 exch = conversions_table ( invoice .conversions )
1160+ exch_date = invoice .created .strftime ( INVOICE_STRFTIME )
11391161
1140- inv_tpl ['inv-table' ] = '\n \n ' .join ( (dets , tbl , f"Conversion ratios used:\n { exch } " ) ) # f"{tbl}\n\n{80 * ( '=' if last else '-' )}\n{tot if last else sub}"
1162+ inv_tpl ['inv-table' ] = '\n \n ' .join ( (dets , tbl , f"Conversion ratios used (est. { exch_date } ) :\n { exch } " ) )
11411163 inv_tpl ['inv-table-bg' ] = layout / '1x1-ffffffbf.png'
11421164
11431165 inv_tpl .render ( offsetx = offsetx , offsety = offsety )
0 commit comments