ntg-context - mailing list for ConTeXt users
 help / color / mirror / Atom feed
* Invoice Forms
@ 2018-06-13 17:50 jdh
  2018-06-14 10:48 ` Henning Hraban Ramm
  2018-06-14 13:29 ` Peter Münster
  0 siblings, 2 replies; 6+ messages in thread
From: jdh @ 2018-06-13 17:50 UTC (permalink / raw)
  To: ntg-context



Is there a source of context document forms for invoices, (and other documents), that have been contributed for open modifications and use?   If not it would be very beneficial to people to have such a depository. Please let me know.

Regards

___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the Wiki!

maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
archive  : https://bitbucket.org/phg/context-mirror/commits/
wiki     : http://contextgarden.net
___________________________________________________________________________________

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Invoice Forms
  2018-06-13 17:50 Invoice Forms jdh
@ 2018-06-14 10:48 ` Henning Hraban Ramm
  2018-06-14 14:00   ` Henning Hraban Ramm
  2018-06-14 13:29 ` Peter Münster
  1 sibling, 1 reply; 6+ messages in thread
From: Henning Hraban Ramm @ 2018-06-14 10:48 UTC (permalink / raw)
  To: mailing list for ConTeXt users

[-- Attachment #1: Type: text/plain, Size: 1025 bytes --]

Am 2018-06-13 um 19:50 schrieb jdh <dhenman@gmail.com>:

> Is there a source of context document forms for invoices, (and other documents), that have been contributed for open modifications and use?   If not it would be very beneficial to people to have such a depository. Please let me know.

Dear unknown,
there’s nothing out there, AFAIK.

But I’m writing my invoices with ConTeXt, using a mixture of Shell, Python, Lua and ConTeXt, also mixed German/English:
* A bunch of Shell scripts call the main Python script with the right parameters.
* Python script/library manages customers and catalogue and fills a ConTeXt/Lua invoice template
* Python or Shell script calls ConTeXt, invoice calculations in Lua

I can provide you with the ConTeXt & Lua part, the Python stuff contains too much private information.
There’s no documentation and I don’t claim the code to be good.

Greetlings, Hraban
---
https://www.fiee.net
http://wiki.contextgarden.net
https://www.dreiviertelhaus.de
GPG Key ID 1C9B22FD

[-- Attachment #2: classy.lua --]
[-- Type: application/octet-stream, Size: 13852 bytes --]

-- class-based OO module for Lua
-- from https://github.com/siffiejoe/lua-classy

-- cache globals
local assert = assert
local V = assert( _VERSION )
local setmetatable = assert( setmetatable )
local select = assert( select )
local pairs = assert( pairs )
local ipairs = assert( ipairs )
local type = assert( type )
local error = assert( error )
local load = assert( load )
local s_rep = assert( string.rep )
local t_unpack = assert( V == "Lua 5.1" and unpack or table.unpack )


-- list of all metamethods that a user of this library is allowed to
-- add to a class
local allowed_metamethods = {
  __add = true, __sub = true, __mul = true, __div = true,
  __mod = true, __pow = true, __unm = true, __concat = true,
  __len = true, __eq = true, __lt = true, __le = true, __call = true,
  __tostring = true, __pairs = true, __ipairs = true, __gc = true,
  __newindex = true, __metatable = true, __idiv = true, __band = true,
  __bor = true, __bxor = true, __bnot = true, __shl = true,
  __shr = true,
}

-- this metatable is (re-)used often:
local mode_k_meta = { __mode = "k" }

-- store information for every registered class (still in use)
-- [ cls ] = {
--   -- the name of the class
--   name = "clsname",
--   -- an array of superclasses in an order suitable for method
--   -- lookup, the first n are direct superclasses (parents)
--   super = { n = 2, super1, super2, super1_1, super1_2 },
--   -- a set of subclasses (value is "inheritance difference")
--   sub = { [ subcls1 ] = 1, [ subcls2 ] = 2 }, -- mode="k"
--   -- direct member functions/variables for this class
--   members = {},
--   -- the metatable for objects of this class
--   o_meta = { __index = {} },
--   -- the metatable for the class itself
--   c_meta = { __index = ..., __call = ..., __newindex = ... },
-- }
local classinfo = setmetatable( {}, mode_k_meta )


-- object constructor for the class if no custom __init function is
-- defined
local function default_constructor( meta )
  return function()
    return setmetatable( {}, meta )
  end
end

-- object constructor for the class if a custom __init function is
-- available
local function init_constructor( meta, init )
  return function( _, ... )
    local o = setmetatable( {}, meta )
    init( o, ... )
    return o
  end
end


-- propagate a changed method to a sub class
local function propagate_update( cls, key )
  local info = classinfo[ cls ]
  if info.members[ key ] ~= nil then
    info.o_meta.__index[ key ] = info.members[ key ]
  else
    for i = 1, #info.super do
      local val = classinfo[ info.super[ i ] ].members[ key ]
      if val ~= nil then
        info.o_meta.__index[ key ] = val
        return
      end
    end
    info.o_meta.__index[ key ] = nil
  end
end


-- __newindex handler for class proxy tables, allowing to set certain
-- metamethods, initializers, and normal members. updates sub classes!
local function class_newindex( cls, key, val )
  local info = classinfo[ cls ]
  if allowed_metamethods[ key ] then
    assert( info.o_meta[ key ] == nil,
            "overwriting metamethods not allowed" )
    info.o_meta[ key ] = val
  elseif key == "__init" then
    info.members.__init = val
    info.o_meta.__index.__init = val
    if type( val ) == "function" then
      info.c_meta.__call = init_constructor( info.o_meta, val )
    else
      info.c_meta.__call = default_constructor( info.o_meta )
    end
  else
    assert( key ~= "__class", "key '__class' is reserved" )
    info.members[ key ] = val
    propagate_update( cls, key )
    for sub in pairs( info.sub ) do
      propagate_update( sub, key )
    end
  end
end


-- __pairs/__ipairs metamethods for iterating members of classes
local function class_pairs( cls )
  return pairs( classinfo[ cls ].o_meta.__index )
end

local function class_ipairs( cls )
  return ipairs( classinfo[ cls ].o_meta.__index )
end


-- put the inheritance tree into a flat array using a width-first
-- iteration (similar to a binary heap); also set the "inheritance
-- difference" in superclasses
local function linearize_ancestors( cls, super, ... )
  local n = select( '#', ... )
  for i = 1, n do
    local pcls = select( i, ... )
    assert( classinfo[ pcls ], "invalid class" )
    super[ i ] = pcls
  end
  super.n = n
  local diff, newn = 1, n
  for i,p in ipairs( super ) do
    local pinfo = classinfo[ p ]
    local psuper, psub = pinfo.super, pinfo.sub
    if not psub[ cls ] then psub[ cls ] = diff end
    for i = 1, psuper.n do
      super[ #super+1 ] = psuper[ i ]
    end
    newn = newn + psuper.n
    if i == n then
      n, diff = newn, diff+1
    end
  end
end


-- create the necessary metadata for the class, setup the inheritance
-- hierarchy, set a suitable metatable, and return the class
local function create_class( _, name, ... )
  assert( type( name ) == "string", "class name must be a string" )
  local cls, index = {}, {}
  local o_meta = {
    __index = index,
    __name = name,
  }
  local info = {
    name = name,
    super = { n = 0 },
    sub = setmetatable( {}, mode_k_meta ),
    members = {},
    o_meta = o_meta,
    c_meta = {
      __index = index,
      __newindex = class_newindex,
      __call = default_constructor( o_meta ),
      __pairs = class_pairs,
      __ipairs = class_ipairs,
      __name = "class",
      __metatable = false,
    },
  }
  linearize_ancestors( cls, info.super, ... )
  for i = #info.super, 1, -1 do
    for k,v in pairs( classinfo[ info.super[ i ] ].members ) do
      if k ~= "__init" then index[ k ] = v end
    end
  end
  index.__class = cls
  classinfo[ cls ] = info
  return setmetatable( cls, info.c_meta )
end


-- the exported class module
local M = {}
setmetatable( M, { __call = create_class } )


-- returns the class of an object
function M.of( o )
  return type( o ) == "table" and o.__class or nil
end


-- returns the class name of an object or class
function M.name( oc )
  if oc == nil then return nil end
  oc = type( oc ) == "table" and oc.__class or oc
  local info = classinfo[ oc ]
  return info and info.name
end


-- checks if an object or class is in an inheritance
-- relationship with a given class
function M.is_a( oc, cls )
  if oc == nil then return nil end
  local info = assert( classinfo[ cls ], "invalid class" )
  oc = type( oc ) == "table" and oc.__class or oc
  if oc == cls then return 0 end
  return info.sub[ oc ]
end


-- change the type of an object to the new class
function M.cast( o, newcls )
  local info = classinfo[ newcls ]
  if not info then
    error( "invalid class" )
  end
  setmetatable( o, info.o_meta )
  return o
end


local function make_delegate( cls, field, method )
  cls[ method ] = function( self, ... )
    local obj = self[ field ]
    return obj[ method ]( obj, ... )
  end
end

-- create delegation methods
function M.delegate( cls, fieldname, ... )
  if type( (...) ) == "table" then
    for k,v in pairs( (...) ) do
      if cls[ k ] == nil and k ~= "__init" and
         type( v ) == "function" then
        make_delegate( cls, fieldname, k )
      end
    end
  else
    for i = 1, select( '#', ... ) do
      local k = select( i, ... )
      if cls[ k ] == nil and k ~= "__init" then
        make_delegate( cls, fieldname, k )
      end
    end
  end
  return cls
end


-- multimethod stuff
do
  -- store multimethods and map them to the meta-data
  local mminfo = setmetatable( {}, mode_k_meta )

  local erroffset = 0
  if V == "Lua 5.1" then erroffset = 1 end

  local function no_match2()
    error( "no matching multimethod overload", 2+erroffset )
  end

  local function no_match3()
    error( "no matching multimethod overload", 3+erroffset )
  end

  local function amb_call()
    error( "ambiguous multimethod call", 3+erroffset )
  end

  local empty = {}   -- just an empty table used as dummy
  local FIRST_OL = 4 -- index of first overload specification


  -- create a multimethod using the parameter indices given
  -- as arguments for dynamic dispatch
  function M.multimethod( ... )
    local t, n = { ... }, select( '#', ... )
    assert( n >= 1, "no polymorphic parameter for multimethod" )
    local max = 0
    for i = 1, n do
      local x = t[ i ]
      max = assert( x > max and x % 1 == 0 and x,
                    "invalid parameter overload specification" )
    end
    local mm_impl = { no_match2, t, max }
    local function mm( ... )
      return mm_impl[ 1 ]( mm_impl, ... )
    end
    mminfo[ mm ] = mm_impl
    return mm
  end


  local function make_weak()
    return setmetatable( {}, mode_k_meta )
  end


  local function calculate_cost( ol, ... )
    local c = 0
    for i = 1, select( '#', ... ) do
      local a, pt = ol[ i ], select( i, ... )
      if type( a ) == "table" then -- class table
        local info = classinfo[ a ]
        local diff = (pt == a) and 0 or info and info.sub[ pt ]
        if not diff then return nil end
        c = c + diff
      else -- type name
        if pt ~= a then return nil end
      end
    end
    return c
  end


  local function select_impl( cost, f, amb, ol, ... )
    local c = calculate_cost( ol, ... )
    if c then
      if cost then
        if c < cost then
          cost, f, amb = c, ol.func, false
        elseif c == cost then
          amb = true
        end
      else
        cost, f, amb = c, ol.func, false
      end
    end
    return cost, f, amb
  end


  local function collect_type_checkers( mm, a )
    local funcs = {}, {}
    for i = FIRST_OL, #mm do
      local ol = mm[ i ]
      for k,v in pairs( ol ) do
        if type( k ) == "function" and
           (a == nil or v[ a ]) and
           not funcs[ k ] then
          local j = #funcs+1
          funcs[ j ] = k
          funcs[ k ] = j
        end
      end
    end
    return funcs
  end


  local function c_varlist( t, m, prefix )
    local n = #t
    if m >= 1 then
      t[ n+1 ] = prefix
      t[ n+2 ] = 1
    end
    for i = 2, m do
      local j = i*3+n
      t[ j-3 ] = ","
      t[ j-2 ] = prefix
      t[ j-1 ] = i
    end
  end

  local function c_typecheck( t, mm, funcs, j )
    local n, ai = #t, mm[ 2 ][ j ]
    t[ n+1 ] = "  t=type(_"
    t[ n+2 ] = ai
    t[ n+3 ] = ")\n  local t"
    t[ n+4 ] = j
    t[ n+5 ] = "=(t=='table' and _"
    t[ n+6 ] = ai
    t[ n+7 ] = ".__class) or "
    local ltcs = collect_type_checkers( mm, j )
    local m = #ltcs
    for i = 1, m do
      local k = i*5+n+3
      t[ k ] = "tc"
      t[ k+1 ] = funcs[ ltcs[ i ] ]
      t[ k+2 ] = "(_"
      t[ k+3 ] = ai
      t[ k+4 ] = ") or "
    end
    t[ m*5+n+8 ] = "t\n"
  end

  local function c_cache( t, mm )
    local c = #mm[ 2 ]
    local n = #t
    t[ n+1 ] = s_rep( "(", c-1 )
    t[ n+2 ] = "cache"
    for i = 1, c-1 do
      local j = i*3+n
      t[ j ] = "[t"
      t[ j+1 ] = i
      t[ j+2 ] = "] or empty)"
    end
    local j = c*3+n
    t[ j ] = "[t"
    t[ j+1 ] = c
    t[ j+2 ] = "]\n"
  end

  local function c_costcheck( t, i, j )
    local n = #t
    t[ n+1 ] = "    cost,f,is_amb=sel_impl(cost,f,is_amb,mm["
    t[ n+2 ] = j+FIRST_OL-1
    t[ n+3 ] = "],"
    c_varlist( t, i, "t" )
    t[ #t+1 ] = ")\n"
  end

  local function c_updatecache( t, i )
    local n = #t
    t[ n+1 ] = "    if not t[t"
    t[ n+2 ] = i
    t[ n+3 ] = "] then t[t"
    t[ n+4 ] = i
    t[ n+5 ] = "]=mk_weak() end\n    t=t[t"
    t[ n+6 ] = i
    t[ n+7 ] = "]\n"
  end


  local function recompile_and_call( mm, ... )
    local n = #mm[ 2 ] -- number of polymorphic parameters
    local tcs = collect_type_checkers( mm )
    local code = {
      "local type,cache,empty,mk_weak,sel_impl,no_match,amb_call"
    }
    if #tcs >= 1 then
      code[ #code+1 ] = ","
    end
    c_varlist( code, #tcs, "tc" )
    code[ #code+1 ] = "=...\nreturn function(mm,"
    c_varlist( code, mm[ 3 ], "_" )
    code[ #code+1 ] = ",...)\n  local t\n"
    for i = 1, n do
      c_typecheck( code, mm, tcs, i )
    end
    code[ #code+1 ] = "  local f="
    c_cache( code, mm )
    code[ #code+1 ] = [=[
  if f==nil then
    local is_amb,cost
]=]
    for i = 1, #mm-FIRST_OL+1 do
      c_costcheck( code, n, i )
    end
    code[ #code+1 ] = [=[
    if f==nil then
      no_match()
    elseif is_amb then
      amb_call()
    end
    t=cache
]=]
    for i = 1, n-1 do
      c_updatecache( code, i )
    end
    code[ #code+1 ] = "    t[t"
    code[ #code+1 ] = n
    code[ #code+1 ] = "]=f\n  end\n  return f("
    c_varlist( code, mm[ 3 ], "_" )
    code[ #code+1 ] = ",...)\nend\n"
    local i = 0
    local function ld()
      i = i + 1
      return code[ i ]
    end
    --print( table.concat( code ) ) -- XXX
    local f = assert( load( ld, "=[multimethod]" ) )(
      type, make_weak(), empty, make_weak, select_impl, no_match3,
      amb_call, t_unpack( tcs )
    )
    mm[ 1 ] = f
    return f( mm, ... )
  end


  -- register a new overload for this multimethod
  function M.overload( f, ... )
    local mm = assert( type( f ) == "function" and mminfo[ f ],
                       "argument is not a multimethod" )
    local i, n = 1, select( '#', ... )
    local ol = {}
    local func = assert( n >= 1 and select( n, ... ),
                         "missing function in overload specification" )
    while i < n do
      local a = select( i, ... )
      local t = type( a )
      if t == "string" then
        ol[ #ol+1 ] = a
      elseif t == "table" then
        assert( classinfo[ a ], "invalid class" )
        ol[ #ol+1 ] = a
      else
        assert( t == "function", "invalid overload specification" )
        i = i + 1
        assert( i < n, "missing function in overload specification" )
        ol[ a ] = ol[ a ] or {}
        ol[ #ol+1 ] = select( i, ... )
        ol[ a ][ #ol ] = true
      end
      i = i + 1
    end
    assert( #mm[ 2 ] == #ol, "wrong number of overloaded parameters" )
    ol.func = func
    mm[ #mm+1 ] = ol
    mm[ 1 ] = recompile_and_call
  end

end


-- return module table
return M

[-- Attachment #3: invoicelib.lua --]
[-- Type: application/octet-stream, Size: 10977 bytes --]

require("table")
require("string")
local class = require("classy")

function table.copy(t)
  local u = { }
  for k, v in pairs(t) do u[k] = v end
  return setmetatable(u, getmetatable(t))
end

-- ++++++++++++++++++++++

InvItem = class('InvItem')

function InvItem:__init(o, r)
  -- print('InvItem:init', o)
  self.count = 0
  self.unit = ''
  self.text = ''
  self.tax = r.tax
  self.taxmode = r.taxmode
  self.amount = 0
  self.typ = 'any' -- e.g. time, lump, text
  self.discount = r.discount
  for key, val in pairs(o) do
    self[key] = val
  end
end

function InvItem:validate(key, list)
  -- is the item’s key value within the list?
  if not list then
    print(list, "is not a valid list but ", type(list))
    return false
  end
  for _, v in pairs(list) do
    if self[key] == v then
      return true
    end
  end
  print("value of '" .. key .. "' is not valid: '" .. self[key] .. "'" .. type(key))
  return false
end

function InvItem:sum()
  return self.count * self.amount * (100 - self.discount) / 100
end

function InvItem:taxsum()
  -- included or added VAT
  local sum = self:sum()
  if self.taxmode == 'brutto' then
    return sum - (sum / (1 + self.tax / 100))
  else
    return sum / 100 * self.tax
  end
end

-- ++++++++++++++++++++++

Invoice = class('Invoice')

function Invoice:__init(o)
  -- print('Invoice:init', o)
  self._zerosums = { [7] = 0, [19] = 0, [0] = 0, hours = 0, netto = 0, brutto = 0, discount = 0 }
  self.valid_tax = { 0, 7, 19 }
  self.valid_typ = { 'any', 'time', 'lump', 'text', 'porto'}
  self.sums = {}
  self.nettosums = true -- rechne mit Netto-Summen // taxmode?
  self.declaretax = true -- weise MwSt. aus
  self.tax = 19
  self.taxmode = 'brutto'
  self.discount = 0 -- Rabatt in Prozent
  self.printtitle = false
  self.printprice = not tex.modes['lieferschein'] -- Lieferschein: false
  self.printdiscounttitle = false
  self.printhoursum = false
  self.perhour = 50 -- Stundensatz
  self.discount = 0.0 -- Rabatt in Prozent
  self.alldiscounts = {}
  self.currency1 = "€"
  self.currency2 = "€" --"US\\$"
  self.exchangerate = 1/1.0707 -- currency2-Preis = currency1-Preis * exchangerate
  self.defaultunit = ""
  self.timeunit = "h"
  self.allunits = {} -- Liste der verwendeten Einheiten
  self.items = {} -- Posten
  self.language = "de"
  self.numberseparator = { default = ".", de = ",", en = "." }
  self.taxname = { default = "tax", de = "{MwSt․}", en = "VAT"}
  self.sumname = { default = "∑", de = "gesamt", en = "sum" }
  self.singlename = { default = "single", de = "einzeln", en = "single" }
  self.amountname = { default = "", de = "{Anz․}", en = "{Qnt․}" }
  self.textname = { default = "", de = "Bezeichnung", en = "title" }
  self.discountname = { default = "", de = "Rabatt", en = "discount" }
  self.bold = '\\bf '

  self.sums = table.copy(self._zerosums)
  for key, val in pairs(o) do
    self[key] = val
  end
end

function Invoice:addItem(o)
  -- print('Invoice:addItem', o)
  item = InvItem(o, self)
  if not item:validate("tax", self.valid_tax) then
    print("value of 'tax' is not valid: '" .. item.tax .. "'")
    return nil
  end
  if not item:validate("typ", self.valid_typ) then
    print("value of 'typ' is not valid: '" .. item.typ .. "'")
    return nil
  end
  if item.typ == 'lump' then
    item.count = 1
  elseif item.typ == 'porto' then
    item.count = 1
    -- tax: like most of other items...
  elseif item.typ == 'time' and o.amount == nil then
    item.amount = self.perhour
  end
  if item.discount > 0 then
    self.printdiscounttitle = true
  end
  table.insert(self.items, item)
  -- self.items[#self.items+1] = item
  return item
end

function Invoice:summarize()
  -- calculate sums
  self.sums = table.copy(self._zerosums)
  for _, item in pairs(self.items) do
    self.sums[item.tax] = self.sums[item.tax] + item:taxsum()
    self.sums.netto = self.sums.netto + item:sum()
    if self.taxmode == 'netto' then
        self.sums.brutto = self.sums.brutto + item:sum() + item:taxsum()
    else
        self.sums.brutto = self.sums.brutto + item:sum()
    end
    if (item.typ == "time") or (item.unit == self.timeunit) then
      self.sums.hours = self.sums.hours + item.count
    end
    self.allunits[item.unit] = true
    if item.discount then
        self.alldiscounts[item.discount] = true
        self.sums.discount = self.sums.discount + item:sum() * item.discount/100
    end
  end
  return self.sums
end

function Invoice:fmt_currency(amount, symbol)
  -- format number to currency
  if not symbol then
    symbol = self.currency1
  end
  local sep = self.numberseparator[self.language]
  return "" .. string.gsub(string.format('%.2f\\,%s', amount, symbol), "%.", sep)
end

function Invoice:fmt_percent(value, hideifempty)
  -- format number to percent
  if value == 0 and hideifempty then
      return ""
  end
  return "" .. string.format('%d\\,\\%%', value)
end

function Invoice:fmt_number(value, hideifempty)
  -- format number
  if value == 0 and hideifempty then
      return ""
  end
  local sep = self.numberseparator[self.language]

  if tonumber(value) == math.floor(value) then
    return "" .. string.format('%d', value)
  end
  return "" .. string.gsub(string.format('%0.2f', value), "%.", sep)
end

function Invoice:output()
  local sep = self.numberseparator[self.language]
  -- TODO: Unterscheidung brutto/netto, Rabatt; Einheit nur anzeigen, wenn sinnvoll
  local fullwidth = 0.75 + 0.75 + 7.5 + 1.5 + 1.75 + 2.25
  local countwidth = 0.75
  local unitwidth = 0.5
  local textwidth = 9.25
  local taxwidth = 1.0
  local singlepricewidth = 1.5
  local discountwidth = 1.5
  local sumwidth = 1.5
  if #self.allunits == 1 then
      unitwidth = 0
  end
  self:summarize()
  print('DISCOUNT', self.sums.discount)
  if self.sums.discount == 0 then
      discountwidth = 0
  end
  if not self.declaretax then
      taxwidth = 0
  end
  textwidth = fullwidth - countwidth - unitwidth - taxwidth - singlepricewidth - discountwidth - sumwidth
  context.starttabulate({
    string.format("|rg{%s}w(%scm)|lw(%scm)|lp(%scm)|rg{%s}w(%scm)|rg{%s}w(%scm)|rw(%scm)|rg{%s}w(%scm)|", sep, countwidth, unitwidth, textwidth, sep, taxwidth, sep, singlepricewidth, discountwidth, sep, sumwidth)
  })
  -- title
  if self.printtitle then
      context.NC(self.amountname[self.language])
      context.NC()
      context.NC()
      context.NC(self.taxname[self.language])
      context.NC(self.singlename[self.language])
      if self.printdiscounttitle then
        context.NC(self.discountname[self.language])
      else
        context.NC()
      end
      context.NC(self.sumname[self.language])
      context.NC()
      context.NR()
      context.HL()
  end
  -- items
  -- print into ConTeXt tabulate or table (old style)
  for _, item in pairs(self.items) do
    if item.typ == 'text' then
      context.NC()
      context.NC()
      context.NC(item.text)
      context.NC()
      context.NC()
      context.NC()
      context.NC()
      context.NR()
    elseif item.typ == 'lump' then
      context.NC()
      context.NC()
      context.NC(item.text)
      if self.declaretax then
        context.NC(self:fmt_percent(item.tax))
      else
        context.NC()
      end
      context.NC()
      context.NC(self:fmt_percent(item.discount, true))
      if self.printprice then
          context.NC(self:fmt_currency(item:sum()))
      else
          context.NC()
      end
      context.NC()
      context.NR()
    elseif item.typ == 'porto' then
      if self.printprice then
        context.NC()
        context.NC()
        context.NC(item.text)
        if self.declaretax then
          context.NC(self:fmt_percent(item.tax))
        else
          context.NC()
        end
        context.NC()
        context.NC(self:fmt_percent(item.discount, true))
        context.NC(self:fmt_currency(item:sum()))
        context.NC()
        context.NR()
      end
    else
      context.NC(self:fmt_number(item.count))
      context.NC(item.unit)
      context.NC(item.text)
      if self.declaretax then
        context.NC(self:fmt_percent(item.tax))
      else
        context.NC()
      end
      context.NC("à " .. self:fmt_currency(item.amount))
      context.NC(self:fmt_percent(item.discount, true))
      if self.printprice then
          context.NC(self:fmt_currency(item:sum()))
      else
          context.NC()
      end
      context.NC()
      context.NR()
    end
  end
  context.HL()
  if self.printprice then
      -- sums
      local endsum = self.sums.netto
      if self.printhoursum then
        context.NC(self:fmt_number(self.sums.hours))
        context.NC(self.timeunit)
      else
        context.NC()
        context.NC()
      end
      context.NC(self.sumname[self.language] .. ' ' .. self.taxmode)
      context.NC()
      context.NC()
      context.NC()
      context.NC(self.bold .. self:fmt_currency(endsum))
      context.NC()
      context.NR()
      if self.taxmode == 'netto' then
        endsum = self.sums.brutto
        local brutto = self.sums.netto
        for _, t in pairs(self.valid_tax) do
          if self.sums[t] > 0 then
            local tax = self.sums[t]
            brutto = brutto + tax
            context.NC()
            context.NC()
            context.NC(self.taxname[self.language])
            context.NC(self:fmt_percent(t))
            context.NC(self:fmt_currency(tax))
            context.NC()
            context.NC(self:fmt_currency(brutto))
            context.NC()
            context.NR()
          end
        end
        context.NC()
        context.NC()
        context.NC(self.sumname[self.language] .. ' brutto')
        context.NC()
        context.NC()
        context.NC()
        context.NC(self.bold .. self:fmt_currency(endsum))
        context.NC()
        context.NR()
      else
        for _, t in pairs(self.valid_tax) do
          if self.sums[t] > 0 then
            context.NC()
            context.NC()
            context.NC('inkl. ' .. self.taxname[self.language])
            context.NC(self:fmt_percent(t))
            context.NC(self:fmt_currency(self.sums[t]))
            context.NC()
            context.NC()
            context.NC()
            context.NR()
          end
        end
      end
      if self.currency2 ~= self.currency1 then
        local converted = endsum * self.exchangerate
        context.NC()
        context.NC()
        context.NC()
        context.NC()
        context.NC()
        context.NC()
        context.NC("(" .. self:fmt_currency(converted, self.currency2) .. ")")
        context.NC()
        context.NR()
      end
  end
  context.stoptabulate()
end

[-- Attachment #4: classy.lua --]
[-- Type: application/octet-stream, Size: 13852 bytes --]

-- class-based OO module for Lua
-- from https://github.com/siffiejoe/lua-classy

-- cache globals
local assert = assert
local V = assert( _VERSION )
local setmetatable = assert( setmetatable )
local select = assert( select )
local pairs = assert( pairs )
local ipairs = assert( ipairs )
local type = assert( type )
local error = assert( error )
local load = assert( load )
local s_rep = assert( string.rep )
local t_unpack = assert( V == "Lua 5.1" and unpack or table.unpack )


-- list of all metamethods that a user of this library is allowed to
-- add to a class
local allowed_metamethods = {
  __add = true, __sub = true, __mul = true, __div = true,
  __mod = true, __pow = true, __unm = true, __concat = true,
  __len = true, __eq = true, __lt = true, __le = true, __call = true,
  __tostring = true, __pairs = true, __ipairs = true, __gc = true,
  __newindex = true, __metatable = true, __idiv = true, __band = true,
  __bor = true, __bxor = true, __bnot = true, __shl = true,
  __shr = true,
}

-- this metatable is (re-)used often:
local mode_k_meta = { __mode = "k" }

-- store information for every registered class (still in use)
-- [ cls ] = {
--   -- the name of the class
--   name = "clsname",
--   -- an array of superclasses in an order suitable for method
--   -- lookup, the first n are direct superclasses (parents)
--   super = { n = 2, super1, super2, super1_1, super1_2 },
--   -- a set of subclasses (value is "inheritance difference")
--   sub = { [ subcls1 ] = 1, [ subcls2 ] = 2 }, -- mode="k"
--   -- direct member functions/variables for this class
--   members = {},
--   -- the metatable for objects of this class
--   o_meta = { __index = {} },
--   -- the metatable for the class itself
--   c_meta = { __index = ..., __call = ..., __newindex = ... },
-- }
local classinfo = setmetatable( {}, mode_k_meta )


-- object constructor for the class if no custom __init function is
-- defined
local function default_constructor( meta )
  return function()
    return setmetatable( {}, meta )
  end
end

-- object constructor for the class if a custom __init function is
-- available
local function init_constructor( meta, init )
  return function( _, ... )
    local o = setmetatable( {}, meta )
    init( o, ... )
    return o
  end
end


-- propagate a changed method to a sub class
local function propagate_update( cls, key )
  local info = classinfo[ cls ]
  if info.members[ key ] ~= nil then
    info.o_meta.__index[ key ] = info.members[ key ]
  else
    for i = 1, #info.super do
      local val = classinfo[ info.super[ i ] ].members[ key ]
      if val ~= nil then
        info.o_meta.__index[ key ] = val
        return
      end
    end
    info.o_meta.__index[ key ] = nil
  end
end


-- __newindex handler for class proxy tables, allowing to set certain
-- metamethods, initializers, and normal members. updates sub classes!
local function class_newindex( cls, key, val )
  local info = classinfo[ cls ]
  if allowed_metamethods[ key ] then
    assert( info.o_meta[ key ] == nil,
            "overwriting metamethods not allowed" )
    info.o_meta[ key ] = val
  elseif key == "__init" then
    info.members.__init = val
    info.o_meta.__index.__init = val
    if type( val ) == "function" then
      info.c_meta.__call = init_constructor( info.o_meta, val )
    else
      info.c_meta.__call = default_constructor( info.o_meta )
    end
  else
    assert( key ~= "__class", "key '__class' is reserved" )
    info.members[ key ] = val
    propagate_update( cls, key )
    for sub in pairs( info.sub ) do
      propagate_update( sub, key )
    end
  end
end


-- __pairs/__ipairs metamethods for iterating members of classes
local function class_pairs( cls )
  return pairs( classinfo[ cls ].o_meta.__index )
end

local function class_ipairs( cls )
  return ipairs( classinfo[ cls ].o_meta.__index )
end


-- put the inheritance tree into a flat array using a width-first
-- iteration (similar to a binary heap); also set the "inheritance
-- difference" in superclasses
local function linearize_ancestors( cls, super, ... )
  local n = select( '#', ... )
  for i = 1, n do
    local pcls = select( i, ... )
    assert( classinfo[ pcls ], "invalid class" )
    super[ i ] = pcls
  end
  super.n = n
  local diff, newn = 1, n
  for i,p in ipairs( super ) do
    local pinfo = classinfo[ p ]
    local psuper, psub = pinfo.super, pinfo.sub
    if not psub[ cls ] then psub[ cls ] = diff end
    for i = 1, psuper.n do
      super[ #super+1 ] = psuper[ i ]
    end
    newn = newn + psuper.n
    if i == n then
      n, diff = newn, diff+1
    end
  end
end


-- create the necessary metadata for the class, setup the inheritance
-- hierarchy, set a suitable metatable, and return the class
local function create_class( _, name, ... )
  assert( type( name ) == "string", "class name must be a string" )
  local cls, index = {}, {}
  local o_meta = {
    __index = index,
    __name = name,
  }
  local info = {
    name = name,
    super = { n = 0 },
    sub = setmetatable( {}, mode_k_meta ),
    members = {},
    o_meta = o_meta,
    c_meta = {
      __index = index,
      __newindex = class_newindex,
      __call = default_constructor( o_meta ),
      __pairs = class_pairs,
      __ipairs = class_ipairs,
      __name = "class",
      __metatable = false,
    },
  }
  linearize_ancestors( cls, info.super, ... )
  for i = #info.super, 1, -1 do
    for k,v in pairs( classinfo[ info.super[ i ] ].members ) do
      if k ~= "__init" then index[ k ] = v end
    end
  end
  index.__class = cls
  classinfo[ cls ] = info
  return setmetatable( cls, info.c_meta )
end


-- the exported class module
local M = {}
setmetatable( M, { __call = create_class } )


-- returns the class of an object
function M.of( o )
  return type( o ) == "table" and o.__class or nil
end


-- returns the class name of an object or class
function M.name( oc )
  if oc == nil then return nil end
  oc = type( oc ) == "table" and oc.__class or oc
  local info = classinfo[ oc ]
  return info and info.name
end


-- checks if an object or class is in an inheritance
-- relationship with a given class
function M.is_a( oc, cls )
  if oc == nil then return nil end
  local info = assert( classinfo[ cls ], "invalid class" )
  oc = type( oc ) == "table" and oc.__class or oc
  if oc == cls then return 0 end
  return info.sub[ oc ]
end


-- change the type of an object to the new class
function M.cast( o, newcls )
  local info = classinfo[ newcls ]
  if not info then
    error( "invalid class" )
  end
  setmetatable( o, info.o_meta )
  return o
end


local function make_delegate( cls, field, method )
  cls[ method ] = function( self, ... )
    local obj = self[ field ]
    return obj[ method ]( obj, ... )
  end
end

-- create delegation methods
function M.delegate( cls, fieldname, ... )
  if type( (...) ) == "table" then
    for k,v in pairs( (...) ) do
      if cls[ k ] == nil and k ~= "__init" and
         type( v ) == "function" then
        make_delegate( cls, fieldname, k )
      end
    end
  else
    for i = 1, select( '#', ... ) do
      local k = select( i, ... )
      if cls[ k ] == nil and k ~= "__init" then
        make_delegate( cls, fieldname, k )
      end
    end
  end
  return cls
end


-- multimethod stuff
do
  -- store multimethods and map them to the meta-data
  local mminfo = setmetatable( {}, mode_k_meta )

  local erroffset = 0
  if V == "Lua 5.1" then erroffset = 1 end

  local function no_match2()
    error( "no matching multimethod overload", 2+erroffset )
  end

  local function no_match3()
    error( "no matching multimethod overload", 3+erroffset )
  end

  local function amb_call()
    error( "ambiguous multimethod call", 3+erroffset )
  end

  local empty = {}   -- just an empty table used as dummy
  local FIRST_OL = 4 -- index of first overload specification


  -- create a multimethod using the parameter indices given
  -- as arguments for dynamic dispatch
  function M.multimethod( ... )
    local t, n = { ... }, select( '#', ... )
    assert( n >= 1, "no polymorphic parameter for multimethod" )
    local max = 0
    for i = 1, n do
      local x = t[ i ]
      max = assert( x > max and x % 1 == 0 and x,
                    "invalid parameter overload specification" )
    end
    local mm_impl = { no_match2, t, max }
    local function mm( ... )
      return mm_impl[ 1 ]( mm_impl, ... )
    end
    mminfo[ mm ] = mm_impl
    return mm
  end


  local function make_weak()
    return setmetatable( {}, mode_k_meta )
  end


  local function calculate_cost( ol, ... )
    local c = 0
    for i = 1, select( '#', ... ) do
      local a, pt = ol[ i ], select( i, ... )
      if type( a ) == "table" then -- class table
        local info = classinfo[ a ]
        local diff = (pt == a) and 0 or info and info.sub[ pt ]
        if not diff then return nil end
        c = c + diff
      else -- type name
        if pt ~= a then return nil end
      end
    end
    return c
  end


  local function select_impl( cost, f, amb, ol, ... )
    local c = calculate_cost( ol, ... )
    if c then
      if cost then
        if c < cost then
          cost, f, amb = c, ol.func, false
        elseif c == cost then
          amb = true
        end
      else
        cost, f, amb = c, ol.func, false
      end
    end
    return cost, f, amb
  end


  local function collect_type_checkers( mm, a )
    local funcs = {}, {}
    for i = FIRST_OL, #mm do
      local ol = mm[ i ]
      for k,v in pairs( ol ) do
        if type( k ) == "function" and
           (a == nil or v[ a ]) and
           not funcs[ k ] then
          local j = #funcs+1
          funcs[ j ] = k
          funcs[ k ] = j
        end
      end
    end
    return funcs
  end


  local function c_varlist( t, m, prefix )
    local n = #t
    if m >= 1 then
      t[ n+1 ] = prefix
      t[ n+2 ] = 1
    end
    for i = 2, m do
      local j = i*3+n
      t[ j-3 ] = ","
      t[ j-2 ] = prefix
      t[ j-1 ] = i
    end
  end

  local function c_typecheck( t, mm, funcs, j )
    local n, ai = #t, mm[ 2 ][ j ]
    t[ n+1 ] = "  t=type(_"
    t[ n+2 ] = ai
    t[ n+3 ] = ")\n  local t"
    t[ n+4 ] = j
    t[ n+5 ] = "=(t=='table' and _"
    t[ n+6 ] = ai
    t[ n+7 ] = ".__class) or "
    local ltcs = collect_type_checkers( mm, j )
    local m = #ltcs
    for i = 1, m do
      local k = i*5+n+3
      t[ k ] = "tc"
      t[ k+1 ] = funcs[ ltcs[ i ] ]
      t[ k+2 ] = "(_"
      t[ k+3 ] = ai
      t[ k+4 ] = ") or "
    end
    t[ m*5+n+8 ] = "t\n"
  end

  local function c_cache( t, mm )
    local c = #mm[ 2 ]
    local n = #t
    t[ n+1 ] = s_rep( "(", c-1 )
    t[ n+2 ] = "cache"
    for i = 1, c-1 do
      local j = i*3+n
      t[ j ] = "[t"
      t[ j+1 ] = i
      t[ j+2 ] = "] or empty)"
    end
    local j = c*3+n
    t[ j ] = "[t"
    t[ j+1 ] = c
    t[ j+2 ] = "]\n"
  end

  local function c_costcheck( t, i, j )
    local n = #t
    t[ n+1 ] = "    cost,f,is_amb=sel_impl(cost,f,is_amb,mm["
    t[ n+2 ] = j+FIRST_OL-1
    t[ n+3 ] = "],"
    c_varlist( t, i, "t" )
    t[ #t+1 ] = ")\n"
  end

  local function c_updatecache( t, i )
    local n = #t
    t[ n+1 ] = "    if not t[t"
    t[ n+2 ] = i
    t[ n+3 ] = "] then t[t"
    t[ n+4 ] = i
    t[ n+5 ] = "]=mk_weak() end\n    t=t[t"
    t[ n+6 ] = i
    t[ n+7 ] = "]\n"
  end


  local function recompile_and_call( mm, ... )
    local n = #mm[ 2 ] -- number of polymorphic parameters
    local tcs = collect_type_checkers( mm )
    local code = {
      "local type,cache,empty,mk_weak,sel_impl,no_match,amb_call"
    }
    if #tcs >= 1 then
      code[ #code+1 ] = ","
    end
    c_varlist( code, #tcs, "tc" )
    code[ #code+1 ] = "=...\nreturn function(mm,"
    c_varlist( code, mm[ 3 ], "_" )
    code[ #code+1 ] = ",...)\n  local t\n"
    for i = 1, n do
      c_typecheck( code, mm, tcs, i )
    end
    code[ #code+1 ] = "  local f="
    c_cache( code, mm )
    code[ #code+1 ] = [=[
  if f==nil then
    local is_amb,cost
]=]
    for i = 1, #mm-FIRST_OL+1 do
      c_costcheck( code, n, i )
    end
    code[ #code+1 ] = [=[
    if f==nil then
      no_match()
    elseif is_amb then
      amb_call()
    end
    t=cache
]=]
    for i = 1, n-1 do
      c_updatecache( code, i )
    end
    code[ #code+1 ] = "    t[t"
    code[ #code+1 ] = n
    code[ #code+1 ] = "]=f\n  end\n  return f("
    c_varlist( code, mm[ 3 ], "_" )
    code[ #code+1 ] = ",...)\nend\n"
    local i = 0
    local function ld()
      i = i + 1
      return code[ i ]
    end
    --print( table.concat( code ) ) -- XXX
    local f = assert( load( ld, "=[multimethod]" ) )(
      type, make_weak(), empty, make_weak, select_impl, no_match3,
      amb_call, t_unpack( tcs )
    )
    mm[ 1 ] = f
    return f( mm, ... )
  end


  -- register a new overload for this multimethod
  function M.overload( f, ... )
    local mm = assert( type( f ) == "function" and mminfo[ f ],
                       "argument is not a multimethod" )
    local i, n = 1, select( '#', ... )
    local ol = {}
    local func = assert( n >= 1 and select( n, ... ),
                         "missing function in overload specification" )
    while i < n do
      local a = select( i, ... )
      local t = type( a )
      if t == "string" then
        ol[ #ol+1 ] = a
      elseif t == "table" then
        assert( classinfo[ a ], "invalid class" )
        ol[ #ol+1 ] = a
      else
        assert( t == "function", "invalid overload specification" )
        i = i + 1
        assert( i < n, "missing function in overload specification" )
        ol[ a ] = ol[ a ] or {}
        ol[ #ol+1 ] = select( i, ... )
        ol[ a ][ #ol ] = true
      end
      i = i + 1
    end
    assert( #mm[ 2 ] == #ol, "wrong number of overloaded parameters" )
    ol.func = func
    mm[ #mm+1 ] = ol
    mm[ 1 ] = recompile_and_call
  end

end


-- return module table
return M

[-- Attachment #5: invoicelib.lua --]
[-- Type: application/octet-stream, Size: 10977 bytes --]

require("table")
require("string")
local class = require("classy")

function table.copy(t)
  local u = { }
  for k, v in pairs(t) do u[k] = v end
  return setmetatable(u, getmetatable(t))
end

-- ++++++++++++++++++++++

InvItem = class('InvItem')

function InvItem:__init(o, r)
  -- print('InvItem:init', o)
  self.count = 0
  self.unit = ''
  self.text = ''
  self.tax = r.tax
  self.taxmode = r.taxmode
  self.amount = 0
  self.typ = 'any' -- e.g. time, lump, text
  self.discount = r.discount
  for key, val in pairs(o) do
    self[key] = val
  end
end

function InvItem:validate(key, list)
  -- is the item’s key value within the list?
  if not list then
    print(list, "is not a valid list but ", type(list))
    return false
  end
  for _, v in pairs(list) do
    if self[key] == v then
      return true
    end
  end
  print("value of '" .. key .. "' is not valid: '" .. self[key] .. "'" .. type(key))
  return false
end

function InvItem:sum()
  return self.count * self.amount * (100 - self.discount) / 100
end

function InvItem:taxsum()
  -- included or added VAT
  local sum = self:sum()
  if self.taxmode == 'brutto' then
    return sum - (sum / (1 + self.tax / 100))
  else
    return sum / 100 * self.tax
  end
end

-- ++++++++++++++++++++++

Invoice = class('Invoice')

function Invoice:__init(o)
  -- print('Invoice:init', o)
  self._zerosums = { [7] = 0, [19] = 0, [0] = 0, hours = 0, netto = 0, brutto = 0, discount = 0 }
  self.valid_tax = { 0, 7, 19 }
  self.valid_typ = { 'any', 'time', 'lump', 'text', 'porto'}
  self.sums = {}
  self.nettosums = true -- rechne mit Netto-Summen // taxmode?
  self.declaretax = true -- weise MwSt. aus
  self.tax = 19
  self.taxmode = 'brutto'
  self.discount = 0 -- Rabatt in Prozent
  self.printtitle = false
  self.printprice = not tex.modes['lieferschein'] -- Lieferschein: false
  self.printdiscounttitle = false
  self.printhoursum = false
  self.perhour = 50 -- Stundensatz
  self.discount = 0.0 -- Rabatt in Prozent
  self.alldiscounts = {}
  self.currency1 = "€"
  self.currency2 = "€" --"US\\$"
  self.exchangerate = 1/1.0707 -- currency2-Preis = currency1-Preis * exchangerate
  self.defaultunit = ""
  self.timeunit = "h"
  self.allunits = {} -- Liste der verwendeten Einheiten
  self.items = {} -- Posten
  self.language = "de"
  self.numberseparator = { default = ".", de = ",", en = "." }
  self.taxname = { default = "tax", de = "{MwSt․}", en = "VAT"}
  self.sumname = { default = "∑", de = "gesamt", en = "sum" }
  self.singlename = { default = "single", de = "einzeln", en = "single" }
  self.amountname = { default = "", de = "{Anz․}", en = "{Qnt․}" }
  self.textname = { default = "", de = "Bezeichnung", en = "title" }
  self.discountname = { default = "", de = "Rabatt", en = "discount" }
  self.bold = '\\bf '

  self.sums = table.copy(self._zerosums)
  for key, val in pairs(o) do
    self[key] = val
  end
end

function Invoice:addItem(o)
  -- print('Invoice:addItem', o)
  item = InvItem(o, self)
  if not item:validate("tax", self.valid_tax) then
    print("value of 'tax' is not valid: '" .. item.tax .. "'")
    return nil
  end
  if not item:validate("typ", self.valid_typ) then
    print("value of 'typ' is not valid: '" .. item.typ .. "'")
    return nil
  end
  if item.typ == 'lump' then
    item.count = 1
  elseif item.typ == 'porto' then
    item.count = 1
    -- tax: like most of other items...
  elseif item.typ == 'time' and o.amount == nil then
    item.amount = self.perhour
  end
  if item.discount > 0 then
    self.printdiscounttitle = true
  end
  table.insert(self.items, item)
  -- self.items[#self.items+1] = item
  return item
end

function Invoice:summarize()
  -- calculate sums
  self.sums = table.copy(self._zerosums)
  for _, item in pairs(self.items) do
    self.sums[item.tax] = self.sums[item.tax] + item:taxsum()
    self.sums.netto = self.sums.netto + item:sum()
    if self.taxmode == 'netto' then
        self.sums.brutto = self.sums.brutto + item:sum() + item:taxsum()
    else
        self.sums.brutto = self.sums.brutto + item:sum()
    end
    if (item.typ == "time") or (item.unit == self.timeunit) then
      self.sums.hours = self.sums.hours + item.count
    end
    self.allunits[item.unit] = true
    if item.discount then
        self.alldiscounts[item.discount] = true
        self.sums.discount = self.sums.discount + item:sum() * item.discount/100
    end
  end
  return self.sums
end

function Invoice:fmt_currency(amount, symbol)
  -- format number to currency
  if not symbol then
    symbol = self.currency1
  end
  local sep = self.numberseparator[self.language]
  return "" .. string.gsub(string.format('%.2f\\,%s', amount, symbol), "%.", sep)
end

function Invoice:fmt_percent(value, hideifempty)
  -- format number to percent
  if value == 0 and hideifempty then
      return ""
  end
  return "" .. string.format('%d\\,\\%%', value)
end

function Invoice:fmt_number(value, hideifempty)
  -- format number
  if value == 0 and hideifempty then
      return ""
  end
  local sep = self.numberseparator[self.language]

  if tonumber(value) == math.floor(value) then
    return "" .. string.format('%d', value)
  end
  return "" .. string.gsub(string.format('%0.2f', value), "%.", sep)
end

function Invoice:output()
  local sep = self.numberseparator[self.language]
  -- TODO: Unterscheidung brutto/netto, Rabatt; Einheit nur anzeigen, wenn sinnvoll
  local fullwidth = 0.75 + 0.75 + 7.5 + 1.5 + 1.75 + 2.25
  local countwidth = 0.75
  local unitwidth = 0.5
  local textwidth = 9.25
  local taxwidth = 1.0
  local singlepricewidth = 1.5
  local discountwidth = 1.5
  local sumwidth = 1.5
  if #self.allunits == 1 then
      unitwidth = 0
  end
  self:summarize()
  print('DISCOUNT', self.sums.discount)
  if self.sums.discount == 0 then
      discountwidth = 0
  end
  if not self.declaretax then
      taxwidth = 0
  end
  textwidth = fullwidth - countwidth - unitwidth - taxwidth - singlepricewidth - discountwidth - sumwidth
  context.starttabulate({
    string.format("|rg{%s}w(%scm)|lw(%scm)|lp(%scm)|rg{%s}w(%scm)|rg{%s}w(%scm)|rw(%scm)|rg{%s}w(%scm)|", sep, countwidth, unitwidth, textwidth, sep, taxwidth, sep, singlepricewidth, discountwidth, sep, sumwidth)
  })
  -- title
  if self.printtitle then
      context.NC(self.amountname[self.language])
      context.NC()
      context.NC()
      context.NC(self.taxname[self.language])
      context.NC(self.singlename[self.language])
      if self.printdiscounttitle then
        context.NC(self.discountname[self.language])
      else
        context.NC()
      end
      context.NC(self.sumname[self.language])
      context.NC()
      context.NR()
      context.HL()
  end
  -- items
  -- print into ConTeXt tabulate or table (old style)
  for _, item in pairs(self.items) do
    if item.typ == 'text' then
      context.NC()
      context.NC()
      context.NC(item.text)
      context.NC()
      context.NC()
      context.NC()
      context.NC()
      context.NR()
    elseif item.typ == 'lump' then
      context.NC()
      context.NC()
      context.NC(item.text)
      if self.declaretax then
        context.NC(self:fmt_percent(item.tax))
      else
        context.NC()
      end
      context.NC()
      context.NC(self:fmt_percent(item.discount, true))
      if self.printprice then
          context.NC(self:fmt_currency(item:sum()))
      else
          context.NC()
      end
      context.NC()
      context.NR()
    elseif item.typ == 'porto' then
      if self.printprice then
        context.NC()
        context.NC()
        context.NC(item.text)
        if self.declaretax then
          context.NC(self:fmt_percent(item.tax))
        else
          context.NC()
        end
        context.NC()
        context.NC(self:fmt_percent(item.discount, true))
        context.NC(self:fmt_currency(item:sum()))
        context.NC()
        context.NR()
      end
    else
      context.NC(self:fmt_number(item.count))
      context.NC(item.unit)
      context.NC(item.text)
      if self.declaretax then
        context.NC(self:fmt_percent(item.tax))
      else
        context.NC()
      end
      context.NC("à " .. self:fmt_currency(item.amount))
      context.NC(self:fmt_percent(item.discount, true))
      if self.printprice then
          context.NC(self:fmt_currency(item:sum()))
      else
          context.NC()
      end
      context.NC()
      context.NR()
    end
  end
  context.HL()
  if self.printprice then
      -- sums
      local endsum = self.sums.netto
      if self.printhoursum then
        context.NC(self:fmt_number(self.sums.hours))
        context.NC(self.timeunit)
      else
        context.NC()
        context.NC()
      end
      context.NC(self.sumname[self.language] .. ' ' .. self.taxmode)
      context.NC()
      context.NC()
      context.NC()
      context.NC(self.bold .. self:fmt_currency(endsum))
      context.NC()
      context.NR()
      if self.taxmode == 'netto' then
        endsum = self.sums.brutto
        local brutto = self.sums.netto
        for _, t in pairs(self.valid_tax) do
          if self.sums[t] > 0 then
            local tax = self.sums[t]
            brutto = brutto + tax
            context.NC()
            context.NC()
            context.NC(self.taxname[self.language])
            context.NC(self:fmt_percent(t))
            context.NC(self:fmt_currency(tax))
            context.NC()
            context.NC(self:fmt_currency(brutto))
            context.NC()
            context.NR()
          end
        end
        context.NC()
        context.NC()
        context.NC(self.sumname[self.language] .. ' brutto')
        context.NC()
        context.NC()
        context.NC()
        context.NC(self.bold .. self:fmt_currency(endsum))
        context.NC()
        context.NR()
      else
        for _, t in pairs(self.valid_tax) do
          if self.sums[t] > 0 then
            context.NC()
            context.NC()
            context.NC('inkl. ' .. self.taxname[self.language])
            context.NC(self:fmt_percent(t))
            context.NC(self:fmt_currency(self.sums[t]))
            context.NC()
            context.NC()
            context.NC()
            context.NR()
          end
        end
      end
      if self.currency2 ~= self.currency1 then
        local converted = endsum * self.exchangerate
        context.NC()
        context.NC()
        context.NC()
        context.NC()
        context.NC()
        context.NC()
        context.NC("(" .. self:fmt_currency(converted, self.currency2) .. ")")
        context.NC()
        context.NR()
      end
  end
  context.stoptabulate()
end

[-- Attachment #6: Type: text/plain, Size: 492 bytes --]

___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the Wiki!

maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
archive  : https://bitbucket.org/phg/context-mirror/commits/
wiki     : http://contextgarden.net
___________________________________________________________________________________

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Invoice Forms
  2018-06-13 17:50 Invoice Forms jdh
  2018-06-14 10:48 ` Henning Hraban Ramm
@ 2018-06-14 13:29 ` Peter Münster
  1 sibling, 0 replies; 6+ messages in thread
From: Peter Münster @ 2018-06-14 13:29 UTC (permalink / raw)
  To: ConTeXt users

On Wed, Jun 13 2018, jdh wrote:

> Is there a source of context document forms for invoices, (and other
> documents), that have been contributed for open modifications and use? If not
> it would be very beneficial to people to have such a depository. Please let me
> know.

These pages can help perhaps:
- https://mailman.ntg.nl/pipermail/ntg-context/2008/031680.html
- https://mailman.ntg.nl/pipermail/ntg-context/2009/037920.html
- https://mailman.ntg.nl/pipermail/ntg-context/2014/076641.html

-- 
           Peter
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the Wiki!

maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
archive  : https://bitbucket.org/phg/context-mirror/commits/
wiki     : http://contextgarden.net
___________________________________________________________________________________

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Invoice Forms
  2018-06-14 10:48 ` Henning Hraban Ramm
@ 2018-06-14 14:00   ` Henning Hraban Ramm
  2018-06-14 14:17     ` Floris van Manen
  0 siblings, 1 reply; 6+ messages in thread
From: Henning Hraban Ramm @ 2018-06-14 14:00 UTC (permalink / raw)
  To: mailing list for ConTeXt users

[-- Attachment #1: Type: text/plain, Size: 1260 bytes --]

Am 2018-06-14 um 12:48 schrieb Henning Hraban Ramm <texml@fiee.net>:

> Am 2018-06-13 um 19:50 schrieb jdh <dhenman@gmail.com>:
> 
>> Is there a source of context document forms for invoices, (and other documents), that have been contributed for open modifications and use?   If not it would be very beneficial to people to have such a depository. Please let me know.
> 
> Dear unknown,
> there’s nothing out there, AFAIK.
> 
> But I’m writing my invoices with ConTeXt, using a mixture of Shell, Python, Lua and ConTeXt, also mixed German/English:
> * A bunch of Shell scripts call the main Python script with the right parameters.
> * Python script/library manages customers and catalogue and fills a ConTeXt/Lua invoice template
> * Python or Shell script calls ConTeXt, invoice calculations in Lua
> 
> I can provide you with the ConTeXt & Lua part, the Python stuff contains too much private information.
> There’s no documentation and I don’t claim the code to be good.
> 
> <classy.lua><invoicelib.lua><classy.lua><invoicelib.lua>

Sorry, didn’t check what I attached. Here are the missing tex files.

Greetlings, Hraban
---
https://www.fiee.net
http://wiki.contextgarden.net
https://www.dreiviertelhaus.de
GPG Key ID 1C9B22FD

[-- Attachment #2: environment.tex --]
[-- Type: application/octet-stream, Size: 3356 bytes --]

\mainlanguage[de]
\usemodule[letter]
\startluacode
require("invoicelib")
\stopluacode

\setuplanguage [en] [date={year, –, mm, –, dd}] % Datum nach ISO 8601
\setupinterlinespace[3.0ex] % default: 2.8ex
\setbreakpoints[compound] % break at / and -
\setupletteroptions[language=german, whitespace=1.5ex]

\setuptabulate[distance=0pt]

\usetypescriptfile	[type-computer-modern-unicode,]
\usetypescript	[computer-modern-unicode-concrete]
\setupbodyfont	[computer-modern-unicode-concrete,rm,10pt]
\setupletteroptions[bodyfont={computer-modern-unicode-concrete,rm,10pt}]

\setupletter[
	% Sender address in envelope window
	backaddress={}
]

\startnotmode[nologo]
% Define logo for the first page
\defineletterelement[layer][head][fiee]%
	{\externalfigure[briefbogen]}

% Other logo for subsequent (right) pages
\defineletterelement[layer][nexthead][fiee]%
	{\externalfigure[briefbogen][height=2cm,width=6cm]}

% We put our logo in the head
\setupletterlayer[head,nexthead]
	[preset=lefttop,
	alternative=fiee,
	x=0mm,y=0mm,
]

\setupletterlayer[nexthead][state=right]
\stopnotmode

\setupletter[
  company={},
	name={},
	street={},
	city={},
	phone={},
	mobile={},
	email={},
	web={}
]

% center around the :
\defineletterelement[layer][location][fiee]%
	{\hskip5em \fieevis
    \setuptabulate[bodyfont=small]
	\starttabulate[|Irw(5em)|Ip|]%[distance=0pt,bodyfont=small]
    %\NC \strut      \NC{\definedfont[SerifItalic at 10pt]\correspondenceparameter{company}}\NC\NR
	\NC Who 	\NC\correspondenceparameter{name} \NC\NR
	\NC Where 		\NC\correspondenceparameter{street}\\\correspondenceparameter{city}\NC\NR
	\NC Phone 	\NC\correspondenceparameter{phone}\\\correspondenceparameter{mobile} \NC\NR
	\NC Internet 	\NC\correspondenceparameter{email}\\\correspondenceparameter{web} \NC\NR
\stoptabulate}

\setupletterlayer[location]
	[alternative=fiee,
    x=147mm,y=45mm
	]

% Our own recipient setup without too much space before the address
\defineletterelement[layer][address][fiee]%
	{\correspondenceparameter{toname}\\
	\correspondenceparameter{toaddress}
	\par}

\setupletterlayer[address][alternative=fiee]

% Subject and date on the same line, date below logo
\startsetups[letter:section:subject]
  \bTABLE[frame=off]
    \bTR % ex 169mm
      \bTD[width=\dimexpr164mm-\backspace\relax]\correspondenceparameter{subject}\eTD
     \bTD{\tf\correspondenceparameter{date}}\eTD
    \eTR
  \eTABLE
\stopsetups

\setuplettersection[subject][alternative=setups]

% account information at the foot, below the logo
\defineletterelement[layer][foot][fiee]%
  {\setuptabulate[bodyfont=small]
  \starttabulate[|Irw(39mm)|Ip|]%[distance=0pt,bodyfont=small]
		\NC Accounts \NC \NC\NR
		\NC Tax No. \NC ... \NC\NR
		\NC UStID \NC ... \NC\NR
		\NC IBAN \NC ... \NC\NR
		\NC BIC \NC ... \NC\NR
	\stoptabulate}

\setupletterlayer[foot][
	preset=leftbottom,
	x=122mm, %x=127mm,
	y=24mm, %y=34mm,
	alternative=fiee]

% switch off reference line
\setupletterlayer[reference][state=stop]

% Move the text a bit up
\setupletterlayout[firstpage][topspace=10cm]

% Adjust text start on subsequent pages
\setupletterlayout[secondpage][topspace=3cm]

% Move marks to the paper rim (won't print on most printers)
\setupletterlayer[topmark,botmark,cutmark][x=-2mm]

[-- Attachment #3: rechnung_tpl.tex --]
[-- Type: application/octet-stream, Size: 667 bytes --]

%%
\input environment.tex

\setupletter[
  toname={},
  toaddress={},
  subject={},
  date={}
]

\startletter
\strut

{
\setuptabulate[distance=.25em]
\startluacode

local rg = Invoice{
    printtitle = true,
    tax = 19,
    declaretax = true,
    taxmode = "netto",
    perhour = 50
}

-- rg:addItem{count=1, unit="x", text="Sample", tax=19, amount=12.3}
-- rg:addItem{count=0.25, unit="h", text="Work", typ="time"}
-- rg:addItem{text="Project ConTeXt", typ="text"}
-- rg:addItem{text="Something", amount=500, typ="lump"}

rg:output()

\stopluacode
}

Payable...

\blank[2*big]

Best regards,
\blank[small]

\externalfigure[signature]%%
Your Provider

\stopletter

[-- Attachment #4: Type: text/plain, Size: 492 bytes --]

___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the Wiki!

maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
archive  : https://bitbucket.org/phg/context-mirror/commits/
wiki     : http://contextgarden.net
___________________________________________________________________________________

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Invoice Forms
  2018-06-14 14:00   ` Henning Hraban Ramm
@ 2018-06-14 14:17     ` Floris van Manen
  2018-06-14 14:50       ` Henning Hraban Ramm
  0 siblings, 1 reply; 6+ messages in thread
From: Floris van Manen @ 2018-06-14 14:17 UTC (permalink / raw)
  To: mailing list for ConTeXt users


[-- Attachment #1.1: Type: text/plain, Size: 2111 bytes --]

almost…
what is \fieevis ?


\670>:letter:layer:location:fiee ... 5em \fieevis
                                                  \setuptabulate [bodyfont=s...
l.4 }





> On 14 Jun 2018, at 16:00, Henning Hraban Ramm <texml@fiee.net> wrote:
> 
> Am 2018-06-14 um 12:48 schrieb Henning Hraban Ramm <texml@fiee.net>:
> 
>> Am 2018-06-13 um 19:50 schrieb jdh <dhenman@gmail.com>:
>> 
>>> Is there a source of context document forms for invoices, (and other documents), that have been contributed for open modifications and use?   If not it would be very beneficial to people to have such a depository. Please let me know.
>> 
>> Dear unknown,
>> there’s nothing out there, AFAIK.
>> 
>> But I’m writing my invoices with ConTeXt, using a mixture of Shell, Python, Lua and ConTeXt, also mixed German/English:
>> * A bunch of Shell scripts call the main Python script with the right parameters.
>> * Python script/library manages customers and catalogue and fills a ConTeXt/Lua invoice template
>> * Python or Shell script calls ConTeXt, invoice calculations in Lua
>> 
>> I can provide you with the ConTeXt & Lua part, the Python stuff contains too much private information.
>> There’s no documentation and I don’t claim the code to be good.
>> 
>> <classy.lua><invoicelib.lua><classy.lua><invoicelib.lua>
> 
> Sorry, didn’t check what I attached. Here are the missing tex files.
> 
> Greetlings, Hraban
> ---
> https://www.fiee.net
> http://wiki.contextgarden.net
> https://www.dreiviertelhaus.de
> GPG Key ID 1C9B22FD
> <environment.tex><rechnung_tpl.tex>___________________________________________________________________________________
> If your question is of interest to others as well, please add an entry to the Wiki!
> 
> maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
> webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
> archive  : https://bitbucket.org/phg/context-mirror/commits/
> wiki     : http://contextgarden.net
> ___________________________________________________________________________________


[-- Attachment #1.2: Message signed with OpenPGP using GPGMail --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

[-- Attachment #2: Type: text/plain, Size: 492 bytes --]

___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the Wiki!

maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
archive  : https://bitbucket.org/phg/context-mirror/commits/
wiki     : http://contextgarden.net
___________________________________________________________________________________

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: Invoice Forms
  2018-06-14 14:17     ` Floris van Manen
@ 2018-06-14 14:50       ` Henning Hraban Ramm
  0 siblings, 0 replies; 6+ messages in thread
From: Henning Hraban Ramm @ 2018-06-14 14:50 UTC (permalink / raw)
  To: mailing list for ConTeXt users

Am 2018-06-14 um 16:17 schrieb Floris van Manen <vm@klankschap.nl>:

> almost…
> what is \fieevis ?
> 
> 
> \670>:letter:layer:location:fiee ... 5em \fieevis
>                                                  \setuptabulate [bodyfont=s...
> l.4 }
> 

Sorry, missed that. It’s a macro that contains my company name, just delete it.

Best, Hraban
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the Wiki!

maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
archive  : https://bitbucket.org/phg/context-mirror/commits/
wiki     : http://contextgarden.net
___________________________________________________________________________________

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2018-06-14 14:50 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-06-13 17:50 Invoice Forms jdh
2018-06-14 10:48 ` Henning Hraban Ramm
2018-06-14 14:00   ` Henning Hraban Ramm
2018-06-14 14:17     ` Floris van Manen
2018-06-14 14:50       ` Henning Hraban Ramm
2018-06-14 13:29 ` Peter Münster

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).