class LucaBook::Journal

Journal has several annotations on headers:

x-customer

Identifying customer.

x-editor

Application name editing the journal.

x-tax

For tracking tax related transaction.

Constants

ACCEPTED_HEADERS

Public Class Methods

add_header(journal_hash, key, val) click to toggle source

Set accepted header with key/value, update record if exists.

# File lib/luca_book/journal.rb, line 77
def self.add_header(journal_hash, key, val)
  return journal_hash if val.nil?
  return journal_hash unless ACCEPTED_HEADERS.include?(key)

  journal_hash.tap do |o|
    o[:headers] = {} unless o.dig(:headers)
    o[:headers][key] = val
    save o if o[:id]
  end
end
create(dat) click to toggle source

create journal from hash

# File lib/luca_book/journal.rb, line 23
def self.create(dat)
  d = LucaSupport::Code.keys_stringify(dat)
  validate(d)
  raise 'NoDateKey' unless d.key?('date')

  date = Date.parse(d['date'])

  # TODO: need to sync filename & content. Limit code length for filename
  # codes = (debit_code + credit_code).uniq
  codes = nil

  create_record(nil, date, codes) { |f| f.write journal2csv(d) }
end
journal2csv(d) click to toggle source

Convert journal object to TSV format.

# File lib/luca_book/journal.rb, line 54
def self.journal2csv(d)
  debit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['debit'], 'amount'))
  credit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['credit'], 'amount'))
  raise 'BalanceUnmatch' if debit_amount.inject(:+) != credit_amount.inject(:+)

  debit_code = serialize_on_key(d['debit'], 'code')
  credit_code = serialize_on_key(d['credit'], 'code')

  csv = CSV.generate(String.new, col_sep: "\t", headers: false) do |f|
    f << debit_code
    f << LucaSupport::Code.readable(debit_amount)
    f << credit_code
    f << LucaSupport::Code.readable(credit_amount)
    ACCEPTED_HEADERS.each do |x_header|
      f << [x_header, d['headers'][x_header]] if d.dig('headers', x_header)
    end
    f << []
    f << [d.dig('note')]
  end
end
load_data(io, path) click to toggle source

override de-serializing journal format. Sample format is:

{
  id: '2021A/V001',
  headers: {
    'x-customer' => 'Some Customer Co.'
  },
  debit: [
    { code: 'A12', amount: 1000 }
  ],
  credit: [
    { code: '311', amount: 1000 }
  ],
  note: 'note for each journal'
}
# File lib/luca_book/journal.rb, line 132
def self.load_data(io, path)
  {}.tap do |record|
    body = false
    record[:id] = "#{path[0]}/#{path[1]}"
    CSV.new(io, headers: false, col_sep: "\t", encoding: 'UTF-8')
      .each.with_index(0) do |line, i|
      case i
      when 0
        record[:debit] = line.map { |row| { code: row } }
      when 1
        line.each_with_index { |amount, j| record[:debit][j][:amount] = BigDecimal(amount.to_s) }
      when 2
        record[:credit] = line.map { |row| { code: row } }
      when 3
        line.each_with_index { |amount, j| record[:credit][j][:amount] = BigDecimal(amount.to_s) }
      else
        case body
        when false
          if line.empty?
            record[:note] ||= []
            body = true
          else
            record[:headers] ||= {}
            record[:headers][line[0]] = line[1]
          end
        when true
          record[:note] << line.join(' ') if body
        end
      end
    end
    record[:note] = record[:note]&.join('\n')
  end
end
save(dat) click to toggle source

update journal with hash. If record not found with id, no record will be created.

# File lib/luca_book/journal.rb, line 40
def self.save(dat)
  d = LucaSupport::Code.keys_stringify(dat)
  raise 'record has no id.' if d['id'].nil?

  validate(d)
  parts = d['id'].split('/')
  raise 'invalid ID' if parts.length != 2

  codes = nil
  open_records(@dirname, parts[0], parts[1], codes, 'w') { |f, _path| f.write journal2csv(d) }
end
serialize_on_key(array_of_hash, key) click to toggle source

collect values on specified key

# File lib/luca_book/journal.rb, line 112
def self.serialize_on_key(array_of_hash, key)
  array_of_hash.map { |h| h[key] }
end
update_codes(obj) click to toggle source
# File lib/luca_book/journal.rb, line 88
def self.update_codes(obj)
  debit_code = serialize_on_key(obj[:debit], :code)
  credit_code = serialize_on_key(obj[:credit], :code)
  codes = (debit_code + credit_code).uniq.sort.compact
  change_codes(obj[:id], codes)
end
validate(obj) click to toggle source
# File lib/luca_book/journal.rb, line 95
def self.validate(obj)
  raise 'NoDebitKey' unless obj.key?('debit')
  raise 'NoCreditKey' unless obj.key?('credit')
  debit_codes = serialize_on_key(obj['debit'], 'code').compact
  debit_amount = serialize_on_key(obj['debit'], 'amount').compact
  raise 'NoDebitCode' if debit_codes.empty?
  raise 'NoDebitAmount' if debit_amount.empty?
  raise 'UnmatchDebit' if debit_codes.length != debit_amount.length
  credit_codes = serialize_on_key(obj['credit'], 'code').compact
  credit_amount = serialize_on_key(obj['credit'], 'amount').compact
  raise 'NoCreditCode' if credit_codes.empty?
  raise 'NoCreditAmount' if credit_amount.empty?
  raise 'UnmatchCredit' if credit_codes.length != credit_amount.length
end