P3: the IIF in plain ASCII with every change listed, and the CPA package as of one day (defects 7, 21)
The CPA package's general-journal.iif now holds every posted entry through as_of (closing entries left out, D-36), written as plain ASCII with CR LF, DOCNUM the entry id, QuickBooks Desktop's field limits and subaccount types honoured, and every character written differently listed in the export's warnings and the README (grouped by substitution, TINs masked). The 1099 worksheet stops at as_of too. PDFs print CJK, Arabic, Hebrew, Thai and Devanagari (Noto fallbacks, OFL), and so does the viewer. has_full_tin reads Unicode separators. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
48 files changed
+5661
-317
No files matched your search
@@ -0,0 +1,144 @@
|
||||
# What a 1099 worksheet as of a day counts toward a payee: the payments
|
||||
# of that day's calendar year made from January 1 through the day.
|
||||
#
|
||||
# wanderbooks: P3 defect 21 and decision D-36 (the CPA package's 1099
|
||||
# worksheet counted the whole calendar year in a package as of June 30);
|
||||
# rules.md B-19, B-33; regression test `the_1099_worksheet_stops_at_as_of`
|
||||
# (ledger-package tests/package.rs).
|
||||
#
|
||||
# Setup beyond the accounts: `cash_accounts` are the accounts payments
|
||||
# are made from; each `[[setup.payees]]` is a payee marked for a form;
|
||||
# an event's `payee` tags the payment to them. Expected values:
|
||||
# vendor_1099_paid gives each marked payee's payments counted from `from`
|
||||
# through `to` (both in one calendar year). The 2026 threshold is $2,000
|
||||
# (B-19), so the payee is not yet reportable as of June 30 (1400.00) and
|
||||
# is for the year (3050.00).
|
||||
|
||||
id = "1099-worksheet-through-as-of"
|
||||
title = "A 1099 worksheet as of a day counts the year's payments through that day"
|
||||
rule = "A payee's 1099 amount as of a day D MUST be the payments to the payee made from January 1 of D's calendar year through D, excluding payments made with a payment card; as of December 31 it is the whole calendar year's."
|
||||
status = "AUTHORITY"
|
||||
sources = [
|
||||
"26 U.S.C. §6041(a), as amended by Pub. L. 119-21 §70433: payments \"of $2,000 or more in any calendar year\" (docs/spec/sources/irs-1099-thresholds.md)",
|
||||
"26 U.S.C. §6041A(a)(2): \"the aggregate of such remuneration paid to such person during such calendar year\" (docs/spec/sources/irs-1099-thresholds.md)",
|
||||
"IRS, Instructions for Forms 1099-MISC and 1099-NEC (Rev. December 2026), Form 1099-K: \"Payments made with a credit card or payment card and certain other types of payments, including third-party network transactions, must be reported on Form 1099-K by the payment settlement entity under section 6050W and are not subject to reporting on Form 1099-MISC or Form 1099-NEC.\" (docs/spec/sources/irs-1099-thresholds.md)",
|
||||
]
|
||||
tags = ["1099", "as-of", "calendar-year", "information-return", "period-boundary"]
|
||||
|
||||
[setup]
|
||||
currency = "USD"
|
||||
fiscal_year_start = "01-01"
|
||||
basis = "cash"
|
||||
rounding = "half_away_from_zero"
|
||||
cash_accounts = ["1000"]
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "1000"
|
||||
name = "Checking"
|
||||
type = "asset"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "2200"
|
||||
name = "Credit card"
|
||||
type = "liability"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "3000"
|
||||
name = "Owner capital"
|
||||
type = "equity"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "6100"
|
||||
name = "Contract labor"
|
||||
type = "expense"
|
||||
|
||||
[[setup.payees]]
|
||||
name = "Contractor A"
|
||||
form = "1099-NEC"
|
||||
|
||||
# 0: the year before.
|
||||
[[events]]
|
||||
date = "2025-12-31"
|
||||
description = "Contract work, December"
|
||||
payee = "Contractor A"
|
||||
lines = [
|
||||
{ account = "6100", side = "debit", amount = "100.00" },
|
||||
{ account = "1000", side = "credit", amount = "100.00" },
|
||||
]
|
||||
|
||||
# 1
|
||||
[[events]]
|
||||
date = "2026-01-02"
|
||||
description = "Owner contribution"
|
||||
lines = [
|
||||
{ account = "1000", side = "debit", amount = "10000.00" },
|
||||
{ account = "3000", side = "credit", amount = "10000.00" },
|
||||
]
|
||||
|
||||
# 2
|
||||
[[events]]
|
||||
date = "2026-03-15"
|
||||
description = "Contract work, March"
|
||||
payee = "Contractor A"
|
||||
lines = [
|
||||
{ account = "6100", side = "debit", amount = "900.00" },
|
||||
{ account = "1000", side = "credit", amount = "900.00" },
|
||||
]
|
||||
|
||||
# 3: by card, so reported on Form 1099-K, never counted here.
|
||||
[[events]]
|
||||
date = "2026-06-10"
|
||||
description = "Contract work, June, by card"
|
||||
payee = "Contractor A"
|
||||
lines = [
|
||||
{ account = "6100", side = "debit", amount = "300.00" },
|
||||
{ account = "2200", side = "credit", amount = "300.00" },
|
||||
]
|
||||
|
||||
# 4: made on the day itself, so counted.
|
||||
[[events]]
|
||||
date = "2026-06-30"
|
||||
description = "Contract work, June"
|
||||
payee = "Contractor A"
|
||||
lines = [
|
||||
{ account = "6100", side = "debit", amount = "500.00" },
|
||||
{ account = "1000", side = "credit", amount = "500.00" },
|
||||
]
|
||||
|
||||
# 5: the day after.
|
||||
[[events]]
|
||||
date = "2026-07-01"
|
||||
description = "Contract work, July"
|
||||
payee = "Contractor A"
|
||||
lines = [
|
||||
{ account = "6100", side = "debit", amount = "700.00" },
|
||||
{ account = "1000", side = "credit", amount = "700.00" },
|
||||
]
|
||||
|
||||
# 6
|
||||
[[events]]
|
||||
date = "2026-11-20"
|
||||
description = "Contract work, November"
|
||||
payee = "Contractor A"
|
||||
lines = [
|
||||
{ account = "6100", side = "debit", amount = "950.00" },
|
||||
{ account = "1000", side = "credit", amount = "950.00" },
|
||||
]
|
||||
|
||||
[[expect]]
|
||||
query = "vendor_1099_paid"
|
||||
from = "2026-01-01"
|
||||
to = "2026-06-30"
|
||||
values = { "Contractor A" = "1400.00" }
|
||||
|
||||
[[expect]]
|
||||
query = "vendor_1099_paid"
|
||||
from = "2026-01-01"
|
||||
to = "2026-12-31"
|
||||
values = { "Contractor A" = "3050.00" }
|
||||
|
||||
[[expect]]
|
||||
query = "vendor_1099_paid"
|
||||
from = "2025-01-01"
|
||||
to = "2025-12-31"
|
||||
values = { "Contractor A" = "100.00" }
|
||||
@@ -0,0 +1,119 @@
|
||||
# A report as of a day shows the books through that day, and nothing
|
||||
# dated after it, however long after the day it is made.
|
||||
#
|
||||
# wanderbooks: P3 defect 21 (the CPA package's QuickBooks file held entries
|
||||
# dated after the package's as_of); rules.md B-33; regression tests
|
||||
# `every_file_in_an_as_of_package_stops_at_as_of` and
|
||||
# `as_of_package_iif_stops_at_as_of` (ledger-package tests/package.rs).
|
||||
#
|
||||
# Expected values: trial_balance gives each account's balance, debit
|
||||
# positive and credit negative, every account listed; profit_and_loss
|
||||
# gives each revenue and expense account's activity for the period as a
|
||||
# positive amount, and net_income (revenue less expenses).
|
||||
|
||||
id = "as-of-package-stops-at-as-of"
|
||||
title = "A report as of a day leaves out what is dated after it"
|
||||
rule = "A balance or report as of a day D MUST include every posted entry dated on or before D and MUST NOT include any entry dated after D, whenever it is produced; a period report through D covers the entries dated from the period's first day through D."
|
||||
status = "CONVENTION"
|
||||
sources = [
|
||||
"IRS Publication 538 (Rev. January 2022), Tax year: \"A tax year is an annual accounting period for keeping records and reporting income and expenses.\" (docs/spec/sources/irs-periods-and-methods.md)",
|
||||
"Pacioli, Summa de arithmetica (1494), ch. 36, the trial balance: the total of the debits equals the total of the credits (Geijsbeek translation, 1914; docs/spec/sources/pacioli-1494.md)",
|
||||
]
|
||||
tags = ["as-of", "trial-balance", "profit-and-loss", "reversal", "period-boundary"]
|
||||
|
||||
[setup]
|
||||
currency = "USD"
|
||||
fiscal_year_start = "01-01"
|
||||
basis = "accrual"
|
||||
rounding = "half_away_from_zero"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "1000"
|
||||
name = "Checking"
|
||||
type = "asset"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "3000"
|
||||
name = "Owner capital"
|
||||
type = "equity"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "4000"
|
||||
name = "Sales"
|
||||
type = "revenue"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "6000"
|
||||
name = "Rent"
|
||||
type = "expense"
|
||||
|
||||
# 0
|
||||
[[events]]
|
||||
date = "2026-01-02"
|
||||
description = "Owner contribution"
|
||||
lines = [
|
||||
{ account = "1000", side = "debit", amount = "10000.00" },
|
||||
{ account = "3000", side = "credit", amount = "10000.00" },
|
||||
]
|
||||
|
||||
# 1
|
||||
[[events]]
|
||||
date = "2026-02-01"
|
||||
description = "Sale"
|
||||
lines = [
|
||||
{ account = "1000", side = "debit", amount = "4000.00" },
|
||||
{ account = "4000", side = "credit", amount = "4000.00" },
|
||||
]
|
||||
|
||||
# 2: dated the day itself, so in.
|
||||
[[events]]
|
||||
date = "2026-06-30"
|
||||
description = "June rent"
|
||||
lines = [
|
||||
{ account = "6000", side = "debit", amount = "1200.00" },
|
||||
{ account = "1000", side = "credit", amount = "1200.00" },
|
||||
]
|
||||
|
||||
# 3: the day after, so out of anything as of 2026-06-30.
|
||||
[[events]]
|
||||
date = "2026-07-01"
|
||||
description = "July rent"
|
||||
lines = [
|
||||
{ account = "6000", side = "debit", amount = "1200.00" },
|
||||
{ account = "1000", side = "credit", amount = "1200.00" },
|
||||
]
|
||||
|
||||
# 4: a reversal dated after the day leaves its original in.
|
||||
[[events]]
|
||||
date = "2026-07-15"
|
||||
description = "Reversal of the February sale"
|
||||
reverses = 1
|
||||
lines = [
|
||||
{ account = "4000", side = "debit", amount = "4000.00" },
|
||||
{ account = "1000", side = "credit", amount = "4000.00" },
|
||||
]
|
||||
|
||||
[[expect]]
|
||||
query = "trial_balance"
|
||||
from = "2026-01-01"
|
||||
to = "2026-06-30"
|
||||
values = { "1000" = "12800.00", "3000" = "-10000.00", "4000" = "-4000.00", "6000" = "1200.00" }
|
||||
|
||||
[[expect]]
|
||||
query = "profit_and_loss"
|
||||
from = "2026-01-01"
|
||||
to = "2026-06-30"
|
||||
values = { "4000" = "4000.00", "6000" = "1200.00", net_income = "2800.00" }
|
||||
|
||||
# The same books a month later: the July rent and the reversal are in.
|
||||
[[expect]]
|
||||
query = "trial_balance"
|
||||
from = "2026-01-01"
|
||||
to = "2026-07-31"
|
||||
values = { "1000" = "7600.00", "3000" = "-10000.00", "4000" = "0.00", "6000" = "2400.00" }
|
||||
|
||||
[[expect]]
|
||||
query = "profit_and_loss"
|
||||
from = "2026-01-01"
|
||||
to = "2026-07-31"
|
||||
values = { "4000" = "0.00", "6000" = "2400.00", net_income = "-2400.00" }
|
||||
@@ -0,0 +1,183 @@
|
||||
# The journal file QuickBooks Desktop imports (IIF), as of a day. A format,
|
||||
# not accounting: P13's appendix of interchange formats.
|
||||
#
|
||||
# wanderbooks: P3 defects 7 and 21, decision D-36 and the P3 review (the
|
||||
# file numbered transactions by their place in it, held entries dated after
|
||||
# the package's as_of, wrote '?' for every character past ASCII, and knew
|
||||
# nothing of QuickBooks' length limits, its double quote, or its rule that
|
||||
# a subaccount has its parent's type, and could declare a subaccount
|
||||
# before its parent); rules.md B-34; regression tests
|
||||
# `iif_docnum_is_the_entry_id`,
|
||||
# `iif_writes_plain_ascii_and_lists_every_change`,
|
||||
# `iif_fits_quickbooks_desktop_limits`,
|
||||
# `iif_a_subaccount_of_another_type_stands_at_the_top_level`,
|
||||
# `iif_declares_each_parent_before_its_subaccounts` (ledger-import
|
||||
# tests/spec_import.rs) and `as_of_package_iif_stops_at_as_of`
|
||||
# (ledger-package tests/package.rs).
|
||||
#
|
||||
# Setup beyond the accounts: `cash_accounts` are the bank accounts (BANK);
|
||||
# an account's QuickBooks type otherwise follows its type (asset OCASSET,
|
||||
# liability OCLIAB, equity EQUITY, revenue INC, expense EXP); an account's
|
||||
# `parent` is its parent's code. Each event is a posted entry, its id one
|
||||
# more than its index. Expected values: iif_lines gives the file's lines
|
||||
# in order, each a string of fields joined by tabs ("\t"), every line
|
||||
# ending CR LF in the file; iif_changes gives each text written
|
||||
# differently from the books, in file order (the accounts as declared:
|
||||
# by code, each after its parent; then the entries), as [what, books'
|
||||
# text, file's text], where what is "name <code>", "number" (left out of
|
||||
# ACCNUM when the file's text is empty), "top level <code>" (written
|
||||
# apart from its parent, the books' text: the parent's code) or
|
||||
# "memo <entry id>".
|
||||
|
||||
id = "iif-quickbooks-journal-file"
|
||||
appendix = "formats"
|
||||
title = "A QuickBooks Desktop journal file as of a day"
|
||||
rule = "An IIF journal file as of a day D MUST declare every account first (NAME, ACCNTTYPE, ACCNUM), then hold one GENERAL JOURNAL transaction per posted entry dated on or before D, period closes excepted, in the order posted: a TRNS line and SPL lines, debits positive and credits negative, dated M/D/YYYY, each numbered (DOCNUM) with the entry's id; then ENDTRNS. It MUST be ASCII text, every line ending CR LF, no field holding a tab, a line break or a double quote; an account name level MUST be at most 31 characters and no two NAMEs alike, an account number at most 7 characters and no two alike (else left out), a memo at most 4,095 characters; each parent MUST be declared before its subaccounts, and a subaccount whose QuickBooks type differs from its parent's MUST be written at the top level. Each text written differently from the books MUST be reported."
|
||||
status = "AUTHORITY"
|
||||
sources = [
|
||||
"Intuit, IIF Overview: import kit, sample files, and headers: IIF files are ASCII text, tab-separated; an account not already in the company file is created as a new bank account (docs/spec/sources/intuit-iif.md)",
|
||||
"Intuit, IIF Import Kit, IIF Header Help !TRNS, !SPL, !ACCNT: TRNS and SPL lines then ENDTRNS; \"Debit amounts are always positive, credit amounts are always negative\"; DOCNUM is the transaction's number; a subaccount's NAME its parents' names first, joined by colons (docs/spec/sources/intuit-iif.md)",
|
||||
"Intuit, IIF Import Kit, 2007 General Journal Entry example file: GENERAL JOURNAL, M/D/YYYY, every line ending CR LF (docs/spec/sources/intuit-iif.md)",
|
||||
"Intuit, Character limitations for fields in QuickBooks (US, updated 8/3/2026): Account Name 31, Account Numbers 7, Memo 4,095 (docs/spec/sources/intuit-iif.md)",
|
||||
"Intuit, Add, edit, or delete accounts in QuickBooks Desktop (updated 8/4/2026): \"The parent and subaccount must be the same account type.\" (docs/spec/sources/intuit-iif.md)",
|
||||
"Reckon, IIF import changes a double quote to a single one by design; J. Clark (2003): QuickBooks uses double quotes as text delimiters (docs/spec/sources/intuit-iif.md; not Intuit)",
|
||||
]
|
||||
tags = ["iif", "quickbooks", "as-of", "format", "transliteration"]
|
||||
|
||||
[setup]
|
||||
currency = "USD"
|
||||
fiscal_year_start = "01-01"
|
||||
basis = "cash"
|
||||
rounding = "half_away_from_zero"
|
||||
cash_accounts = ["1000"]
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "1090"
|
||||
name = "Bank accounts"
|
||||
type = "asset"
|
||||
|
||||
# A bank account (BANK) under a header that is no bank (OCASSET): written
|
||||
# at the top level.
|
||||
[[setup.accounts]]
|
||||
code = "1000"
|
||||
name = "Checking"
|
||||
type = "asset"
|
||||
parent = "1090"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "12345678"
|
||||
name = "Suspense"
|
||||
type = "expense"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "3000"
|
||||
name = "Owner capital"
|
||||
type = "equity"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "4000"
|
||||
name = "Sales"
|
||||
type = "revenue"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "6000"
|
||||
name = "Café meals"
|
||||
type = "expense"
|
||||
|
||||
# Coded before its parent: declared after it.
|
||||
[[setup.accounts]]
|
||||
code = "5900"
|
||||
name = "Snacks"
|
||||
type = "expense"
|
||||
parent = "6000"
|
||||
|
||||
[[setup.accounts]]
|
||||
code = "6100"
|
||||
name = "Professional services and consulting fees"
|
||||
type = "expense"
|
||||
|
||||
# 0: entry 1.
|
||||
[[events]]
|
||||
date = "2026-01-02"
|
||||
description = "Owner contribution"
|
||||
lines = [
|
||||
{ account = "1000", side = "debit", amount = "5000.00" },
|
||||
{ account = "3000", side = "credit", amount = "5000.00" },
|
||||
]
|
||||
|
||||
# 1: entry 2. An em dash and double quotes.
|
||||
[[events]]
|
||||
date = "2026-01-15"
|
||||
description = "Lunch — \"team\""
|
||||
lines = [
|
||||
{ account = "6000", side = "debit", amount = "42.50" },
|
||||
{ account = "1000", side = "credit", amount = "42.50" },
|
||||
]
|
||||
|
||||
# 2: entry 3. An account named past 31 characters.
|
||||
[[events]]
|
||||
date = "2026-01-20"
|
||||
description = "Consulting"
|
||||
lines = [
|
||||
{ account = "6100", side = "debit", amount = "300.00" },
|
||||
{ account = "1000", side = "credit", amount = "300.00" },
|
||||
]
|
||||
|
||||
# 3: entry 4, dated the day itself: in.
|
||||
[[events]]
|
||||
date = "2026-02-01"
|
||||
description = "Sale"
|
||||
lines = [
|
||||
{ account = "1000", side = "debit", amount = "1000.00" },
|
||||
{ account = "4000", side = "credit", amount = "1000.00" },
|
||||
]
|
||||
|
||||
# 4: entry 5, the day after: out.
|
||||
[[events]]
|
||||
date = "2026-02-02"
|
||||
description = "Second sale"
|
||||
lines = [
|
||||
{ account = "1000", side = "debit", amount = "250.00" },
|
||||
{ account = "4000", side = "credit", amount = "250.00" },
|
||||
]
|
||||
|
||||
[[expect]]
|
||||
query = "iif_lines"
|
||||
as_of = "2026-02-01"
|
||||
values = [
|
||||
"!ACCNT\tNAME\tACCNTTYPE\tACCNUM",
|
||||
"ACCNT\tChecking\tBANK\t1000",
|
||||
"ACCNT\tBank accounts\tOCASSET\t1090",
|
||||
"ACCNT\tSuspense\tEXP\t",
|
||||
"ACCNT\tOwner capital\tEQUITY\t3000",
|
||||
"ACCNT\tSales\tINC\t4000",
|
||||
"ACCNT\tCafe meals\tEXP\t6000",
|
||||
"ACCNT\tCafe meals:Snacks\tEXP\t5900",
|
||||
"ACCNT\tProfessional services and consu\tEXP\t6100",
|
||||
"!TRNS\tTRNSID\tTRNSTYPE\tDATE\tACCNT\tCLASS\tAMOUNT\tDOCNUM\tMEMO",
|
||||
"!SPL\tSPLID\tTRNSTYPE\tDATE\tACCNT\tCLASS\tAMOUNT\tDOCNUM\tMEMO",
|
||||
"!ENDTRNS",
|
||||
"TRNS\t\tGENERAL JOURNAL\t1/2/2026\tChecking\t\t5000.00\t1\tOwner contribution",
|
||||
"SPL\t\tGENERAL JOURNAL\t1/2/2026\tOwner capital\t\t-5000.00\t1\tOwner contribution",
|
||||
"ENDTRNS",
|
||||
"TRNS\t\tGENERAL JOURNAL\t1/15/2026\tCafe meals\t\t42.50\t2\tLunch -- 'team'",
|
||||
"SPL\t\tGENERAL JOURNAL\t1/15/2026\tChecking\t\t-42.50\t2\tLunch -- 'team'",
|
||||
"ENDTRNS",
|
||||
"TRNS\t\tGENERAL JOURNAL\t1/20/2026\tProfessional services and consu\t\t300.00\t3\tConsulting",
|
||||
"SPL\t\tGENERAL JOURNAL\t1/20/2026\tChecking\t\t-300.00\t3\tConsulting",
|
||||
"ENDTRNS",
|
||||
"TRNS\t\tGENERAL JOURNAL\t2/1/2026\tChecking\t\t1000.00\t4\tSale",
|
||||
"SPL\t\tGENERAL JOURNAL\t2/1/2026\tSales\t\t-1000.00\t4\tSale",
|
||||
"ENDTRNS",
|
||||
]
|
||||
|
||||
[[expect]]
|
||||
query = "iif_changes"
|
||||
as_of = "2026-02-01"
|
||||
values = [
|
||||
["top level 1000", "1090", ""],
|
||||
["number", "12345678", ""],
|
||||
["name 6000", "Café meals", "Cafe meals"],
|
||||
["name 6100", "Professional services and consulting fees", "Professional services and consu"],
|
||||
["memo 2", "Lunch — \"team\"", "Lunch -- 'team'"],
|
||||
]
|
||||
Reference in new issue
Block a user