Bookkeeping handbook
How ledger intake forms, the expense app, Google Sheets (schedule / compliance / bank rec), and BI reporting combine into books that satisfy IRS record-keeping requirements — and how to run them day to day.
How the pieces fit
Four systems, each doing one job. Day-to-day money in/out is captured in apps; Sheets keep the pieces that are schedules, tax rolls, or bank controls — not routine posting intake.
| System | Holds | Why there |
|---|---|---|
| Ledger (intake + books) | Daily sales, vendor payments, and card charges (forms → import to journal), plus the double-entry GL: accounts, dated entries, and pointers to source detail. | Same pattern as expense: capture once, import as posted entries with source_app + source_record_id. Immutable, balanced, auditable. |
| Expense app | Reimbursement requests with receipt images, approval chain, and payment details. | Receipts attached to an approved record is exactly the substantiation §162 and §274 want. |
| Google Sheets | Depreciation schedule, 1099 vendor tally, bank reconciliation — and any §274(d) meals/travel detail that must stay per-item. | These are schedule / compliance / control worksheets, not simple posting forms. Keep them in Sheets until (if ever) built as full modules. |
| BI reporting | Dashboards over the above. | Analysis only. A dashboard is not a record — it is not archived and cannot substantiate anything. |
What the IRS actually requires
IRC §6001 and Treas. Reg. §1.6001-1 are the whole of it: keep permanent books or records sufficient to establish the gross income, deductions, and credits shown on your return.
There is no prescribed chart of accounts, no mandated double-entry, no required subledger, and no approved software list. A spreadsheet and a folder of receipts is legally adequate if it substantiates the return. What gets scrutinised in practice:
- §162 — expenses must be ordinary and necessary, and you must be able to show it.
- §274(d) — heightened substantiation for travel, meals, gifts, and listed property. This is where small businesses actually lose in audit.
- Rev. Proc. 97-22 — electronic records are fine, provided the system reproduces legible copies, provides indexing, and has reasonable controls to detect unauthorised alteration.
Retention
| Record | Keep for |
|---|---|
| General books and supporting detail | 3 years from filing (§6501) |
| If >25% of gross income was omitted | 6 years |
| If no return was filed, or fraud | Indefinitely |
| Employment tax records | 4 years |
| Asset records (basis, improvements) | Until 3 years after the year of disposition |
Why cash basis, and what would change it
These books are cash basis: income is recorded when money arrives, expenses when money leaves. That is available to you under the §448(c) small business taxpayer exception — if average annual gross receipts over the prior three years are below an inflation-indexed threshold (in the $30–32M range recently), the cash method is permitted regardless of entity type, and you are exempt from §471 inventory accounting, §263A UNICAP, and §460 long-term contracts.
Only three things force accrual:
- Gross receipts above the threshold
- A C corporation, or a partnership with a C corp partner, above the threshold
- Tax shelters — never eligible at any size
What you file
| Entity | Form | Balance sheet? |
|---|---|---|
| Single-member LLC (disregarded) | Schedule C on Form 1040 | None exists — nowhere to report AR/AP |
| Multi-member LLC | Form 1065 | Schedules L / M-1 / M-2 skippable under $250K receipts and $250K assets |
| LLC electing S corp | Form 1120-S | Same $250K / $250K skip |
Who holds which record
| Record | Lives in | Reaches the ledger as |
|---|---|---|
| Daily / weekly sales | ledger_intake (kind=sales) | Import, source_app = sales |
| Vendor payments | ledger_intake (kind=vendor, keep payee/TIN) | Import, source_app = vendor |
| Card charges | ledger_intake (kind=card) + receipt folder | Import, source_app = card |
| Employee reimbursements | Expense app (with receipts) | Import from the Tools tab, source_app = expense |
| Bank reconciliation | Sheet | Nothing — it is a control, not a transaction |
| Depreciation schedule | Sheet | One annual entry, source_app = sheet |
| 1099 vendor totals | Sheet (roll up from vendor payees) | Nothing — feeds Form 1099-NEC directly |
| Meals & travel detail | Sheet, one row per item | Summary entry; detail must stay per-item |
| Opening balances | — | One entry, source_app = opening |
Never summarise these
Summary journal entries are fine and normal. Three categories are exceptions — the detail has to stay per-item and reachable no matter how the ledger records it.
1. Meals, travel, gifts, listed property
§274(d) requires five elements per item, not per month. Your sheet needs a column for each:
- Amount
- Time (date)
- Place
- Business purpose
- Business relationship — who was present
Receipts are not required under $75 for travel and entertainment
(Reg. §1.274-5(c)(2)(iii)), but the five written elements always are — and
lodging requires a receipt at any amount. Affected accounts:
64300 Meals and Entertainment, 68600 Travel Expense.
2. Payments to contractors
Pay an unincorporated contractor $600 or more in a calendar year and you must file
Form 1099-NEC with a W-9 on file. That needs per-payee annual totals, which a
summarised expense line destroys. Keep a vendor tab with name, TIN, address, and a running
total. Affected accounts: 62000 Contract Services, 66700 Professional Fees.
3. Deposits
Summarise no coarser than what ties to a bank statement line. If one ledger entry covers three deposits that appear separately on the statement, reconciliation stops working — and an unreconciled ledger is the first thing challenged in an examination.
Freeze the source when locking a date
Advancing locked_through_date is therefore not just a settings edit. Before locking through a day:
- Export every source tab referenced by entries on or before that day to CSV or PDF.
- Export the trial balance for that date range from the Reports tab.
- Archive both read-only, somewhere separate from the live sheet.
- Set
locked_through_datein Admin → Settings (store accordion).
The source_url on each entry tells you exactly which tabs step 1 covers — filter
the journal by date and collect the links.
Core concepts
Double entry, signed cents
Every entry has two or more lines. Amounts are stored as whole cents in
amount_cents, signed: debit positive, credit negative. The entire
correctness rule is therefore one check — the lines must sum to zero. The
Debit and Credit columns you see are presentation only.
Integers, not decimals, because 0.1 + 0.2 ≠ 0.3 in floating point and "the trial
balance is off by a penny" is miserable to chase.
Draft, then posted
A manual entry is first saved as draft. You can reopen and edit it as often as
needed. Validate & post then reads the stored lines back, verifies the
balance, source, and entry_date against store lock settings, and flips the entry
to posted. Every report filters on posted, so an unfinished draft
never affects the books.
Posted entries are permanent
Period lock
There is no separate period record — the period is entry_date. You may draft
anything at any time, but posting requires the date to be on or after
books_start_date and after locked_through_date (the
inclusive last closed day). Set both in Admin → Settings (store accordion).
Chart of accounts
Five-digit codes, grouped by leading digit:
| Range | Type | Examples |
|---|---|---|
1xxxx | Asset | 10100 Checking, 17000 Furniture and Equipment |
2xxxx | Liability | 20100 Credit Card, 24000 Sales Tax Payable |
3xxxx | Equity | 30000 Opening Balance Equity, 39000 Retained Earnings |
4xxxx | Income | 40000 Sales Revenue, 41000 Service Revenue |
5xxxx | Cost of goods sold | 50000 Cost of Goods Sold |
6xxxx | Operating expense | 67100 Rent, 68700 Utilities |
8xxxx | Other | 80000 Ask My Accountant |
Each account stores an explicit normal_balance rather than deriving it from type,
because contra accounts break the rule: 17900 Accumulated Depreciation
is an asset carrying a credit balance, and 33000 Owner's Draw is equity carrying a
debit balance.
is_active = false before an admin can delete one at all. Prefer leaving it
deactivated forever.
80000 Ask My Accountant exists for transactions you genuinely cannot classify.
Use it rather than guessing, and clear it before year-end.
Books start & lock
There is no ledger_periods collection. The accounting period is each entry's
entry_date. Two store settings control what dates may be posted:
books_start_date— when the books begin; BI history floor. Posting rejects earlier dates.locked_through_date— inclusive last closed day. Posting requires a laterentry_date.
Set both under Admin → Settings (store accordion). Closing a month means
setting locked_through_date to that month's last day — after export and archive.
Post a manual entry
Journal tab → + New Entry.
- Date — the accounting date (
entry_date), which is not necessarily today. Must be on or afterbooks_start_dateand afterlocked_through_date. - Memo — what this entry is for, in words a stranger would understand a year from now.
- Source — leave on Manual.
- Fill the lines: account, and an amount in either the Debit or the Credit column.
- Save Draft. The entry remains editable and does not affect reports.
- Open the draft and make any corrections. Before posting, Balance must read
0.00and turn green. - Choose Validate & post, or Edit draft → Save & Post.
Common shapes:
Cash sale DR 10100 Checking CR 40000 Sales Revenue
Pay rent by check DR 67100 Rent Expense CR 10100 Checking
Card purchase DR 64900 Office Supplies CR 20100 Credit Card
Pay the card DR 20100 Credit Card CR 10100 Checking
Owner takes money DR 33000 Owner's Draw CR 10100 Checking
AI Quick Add
Journal tab → ✦ Quick AI Add, next to + New Entry.
Describe the transaction in plain language. The AI drafts a balanced entry — a simple transfer
or a multi-line split — into the same New Entry form + New Entry would open.
Nothing posts automatically: review the lines, fix anything wrong, then Save Draft
or Save & Post exactly as you would for a manual entry. Memos in the draft
follow your note_language setting (below).
What it does under the hood
- The free text and the store's active chart of accounts are sent to
myapi's/ai/chatendpoint — the AI sees only account codes/names/types, and the provider API keys stay server-side. - The model returns a memo, an optional date, and a list of lines (account code, debit or credit, memo, type) that balance to the penny.
- Each line's account code is matched against the real chart of accounts. A code the AI invented or mis-typed is left blank on that line and flagged, rather than silently guessed — fix it in the split editor before saving.
- Which backend/model would answer (or did answer) is shown under the text box — a resolved default when the panel opens, the actual one used once you've asked.
- Entry and line memos are written in your
note_language(see below). Account codes stay as in the chart. - In the split editor, hovering a Debit or Credit box explains what that number means for that specific account — e.g. debiting Checking Account reads "money in", crediting Unearned Revenue reads "you owe more" — not just the generic debit/credit rule.
Memo language (note_language)
Quick AI Add uses the signed-in user's
users.settings.ledger_settings.note_language for every memo it drafts
(entry memo and each line memo). Set it under Account → Settings
→ AI memo language.
| Value | Memos written in |
|---|---|
en (default) | English |
zh-tw | Traditional Chinese |
zh-cn | Simplified Chinese |
Missing or invalid values fall back to English. This does not change the UI language toggle, and it does not rewrite memos on entries you type by hand.
Worked example
Typed:
received wire transfer in of 30784 for Dreamwear shipment 75872, we keep 2%
service revenue and calculate the rest to unearned client deposit
Drafted:
DR 10100 Checking Account 30,784.00
CR 41000 Service Revenue 615.68
CR 25000 Unearned Revenue 30,168.32
ledger_entries.ai_model (e.g. groq llama-3.1-8b-instant) and shown
in the entry detail panel. Empty for entries entered by hand.
Post a spreadsheet summary
This is how sheet detail reaches the books. Same form, but set Source to Spreadsheet summary and four extra fields appear.
| Field | Required | What to put |
|---|---|---|
| Sheet URL | yes | Deep link to the exact tab, including the #gid= fragment. This is the indexing Rev. Proc. 97-22 asks for. |
| Batch key | yes | A stable name for this batch, e.g. 2027-01-sales. Makes the entry idempotent. |
| Rows | yes | How many source rows this entry stands for. |
| Sheet total | optional | What the sheet says the batch totals. Cross-checked against the entry's debits. |
What the checks do for you
- Leave out the URL, batch key, or row count and the post is refused before touching the network. A summary that doesn't say what it summarises is unauditable.
- Enter a Sheet total that disagrees with your lines and the post is refused with the exact difference. This catches a mis-keyed summary immediately rather than at year end.
- The batch key can never post twice — a unique database index enforces it. Re-running a month is safe.
Worked example
January sales sheet: 31 rows, totalling 18,400.00
Date 2027-01-31
Memo January sales summary
Source Spreadsheet summary
Sheet URL https://docs.google.com/spreadsheets/d/…#gid=0
Batch key 2027-01-sales
Rows 31
Sheet total 18400.00
DR 10100 Checking Account 18,400.00
CR 40000 Sales Revenue 18,400.00
YYYY-MM-what consistently — 2027-01-sales,
2027-01-cardcharges, 2027-depreciation. Because the key is what
prevents double-posting, an inconsistent naming habit is what defeats it.
Corrections
Posted entries cannot be edited. To fix one, post two more:
- A reversing entry — same accounts, every amount negated, with
reversal_ofpointing at the original. - A corrected entry with the right figures.
The audit trail then shows what you originally recorded, that you caught it, and what you replaced it with. That is a feature: an examiner seeing clean corrections sees a controlled process, whereas silently edited books raise the question of what else changed.
There is deliberately no reversed_by field, because setting it would mean updating
a posted record. Find reversals by searching for entries whose reversal_of is the
original's id.
locked_through_date. Post the reversal and
correction with a later date, and note the reason in the memo.
Reports
All reports count posted entries only. Drafts are invisible everywhere.
| Report | Use it for |
|---|---|
| Trial balance | Every account with a balance, debits and credits totalled. The totals must be equal — the footer says so explicitly. This is what you export and archive when locking a date. |
| Account activity | Every line hitting one account, in date order, with a running balance. Pick the account first. This is the drill-down when a figure looks wrong. |
| Cash snapshot | Balances of bank and cash accounts as of a date. Ignores the "from" filter — it is a position, not a movement. |
| Integrity | The audit of the books themselves. See below. |
Integrity report reference
These are an audit, not a guardrail. The API rules and the posting protocol are the controls; this report confirms both worked. Anything appearing here means something bypassed the intended flow — most likely an admin acting directly on the database.
| Issue | Means | Do |
|---|---|---|
| Entry lines do not sum to zero | A posted entry is unbalanced — impossible through the app. | Serious. Investigate directly in PocketBase; the books are wrong until fixed. |
line_count mismatch |
Lines were added or removed after the entry was posted. | Compare against the archived export for that date range to see which is right. |
total_debit_cents mismatch |
Line amounts changed after posting. | Same — reconcile against the archive. |
source_total_cents mismatch |
The entry no longer agrees with the total its source claimed at post time. | Open the source_url. Either the sheet was edited after posting, or the entry was. |
| Sheet-sourced entry with no URL / row count | A summary with no pointer back to its detail. | Unauditable as-is. Find the source, and record it — reverse and repost if necessary. |
| Line references inactive account | Usually benign — the account was deactivated after the entry was posted. | Confirm the account was correct at the time. Usually no action. |
| Draft older than 7 days | An abandoned or half-finished entry. | Finish it or delete it. Drafts affect nothing, but they hide real work. |
| Booked expense not in ledger | An expense marked booked that never reached the ledger. |
Run Import Booked Expenses on the Tools tab. |
Tools tab
Pending import
Lists expense reimbursements marked booked that have not yet reached the ledger.
Expand it to see what is waiting. If this is empty, expense and ledger agree.
Import expense reimbursements
Posts one ledger entry per booked expense, with source_app = expense and
source_record_id set to the expense record id. That id is covered by a unique
index, so running the import twice is safe — already-imported expenses are
skipped, not duplicated.
Seed sample journal entries
Command line
Run from the ledger/ directory. All three need PocketBase credentials in the environment.
Seed the chart of accounts
Idempotent — accounts are matched on (store, code) and updated rather than duplicated. Run without --run first to preview.
PB_ADMIN=1 [email protected] PB_PASSWORD=… \
node scripts/seed-accounts.js --store hilinkpro # dry run
PB_ADMIN=1 [email protected] PB_PASSWORD=… \
node scripts/seed-accounts.js --store hilinkpro --run # write
Test the posting protocol
No credentials and no network — pure logic. Run it after touching pb-ledger.js.
cd scripts && node test-posting.js
Covers money parsing, the balance invariant, the source-field rules, and every failure mode of
the posting protocol — asserting in each case that a failed post is never
flipped to posted.
Verify the API rules
Proves the security rules behave as the design assumes — in particular that an update rule matches the record's value before the update, which is what makes the two-phase post possible at all. Creates and cleans up real records.
PB_ADMIN=1 [email protected] PB_PASSWORD=… \
node scripts/verify-rules.js
Monthly routine
- Reconcile the bank in the sheet — every statement line accounted for, ending balance agreed.
- Enter / import the month's intake — daily sales, vendor payments, and card charges in the ledger app (once those forms ship); until then, post summary entries from Sheets as today.
- Import booked expenses from the Tools tab.
- Check Pending import is empty (expenses and, later, other intake sources).
- Run the Integrity report and resolve anything it lists.
- Compare the Cash snapshot to the reconciled bank balance. They must agree. If they don't, stop and find out why before doing anything else.
- Confirm receipts are filed for the month — expense app for reimbursements, indexed folder for everything else.
Year-end routine
- Complete the monthly routine for the final month.
- Clear
80000 Ask My Accountant— reclassify everything sitting in it. - Post depreciation from the schedule sheet, as one entry with
source_app = sheet. - Verify §274(d) detail is complete — every meals and travel row has all five elements. Fix gaps now, while you can still remember.
- Produce 1099-NEC totals from the vendor tab. Anyone unincorporated at $600+ needs a form, and you need their W-9. Due to recipients and the IRS by January 31.
- Run the Integrity report — it must be empty.
- Export and archive: every source tab, the trial balance, and account activity for any account you expect questions about.
- Set
locked_through_dateto year-end in Admin → Settings. - Hand the archive to your CPA.
What this system does not do
Known gaps, so you don't discover them in April:
| Missing | Consequence | Workaround today |
|---|---|---|
| Daily sales / vendor / card intake forms | Needs the ledger_intake collection created in PocketBase (LEDGER_POCKETBASE_SETUP.md Step 5). |
Use Intake tab → mark ready → Import Ready (or Tools). |
| Profit & loss report | No categorised income statement to file from. | Export the trial balance and group by account range in a sheet. |
| Year-end closing entry | Net income is not rolled into 39000 Retained Earnings, so year two's trial balance carries year one's income and expense balances. |
Post it manually: debit each income account and credit each expense account to zero, with the difference to Retained Earnings. |
| Bank reconciliation | No statement-matching UI (import statement, match groups). | Mark lines with is_reconciled on journal detail or Account activity; compare to Cash snapshot / sheet. |
| AR / AP subledger | No aging, no per-customer or per-vendor balances. | Not required on cash basis. Track in a sheet if you need to know who owes you. |
| Fixed asset register | No cost basis, method, or disposition tracking for Form 4562. | Keep the depreciation schedule in a sheet; post one entry a year. |
| Payroll | No payroll detail or tax liability tracking. | Use a payroll provider; post summary entries to 66000 and 21000. |
Schema and API rules are documented in ledger/LEDGER_POCKETBASE_SETUP.md.
記帳作業手冊
總帳進件表單、費用報銷、Google 試算表(折舊/1099/銀行對帳)與 BI 報表如何組合成一套 符合美國國稅局(IRS)帳簿保存要求的制度,以及日常該怎麼操作。
四個系統如何分工
四個系統,各司其職。日常收付款由應用程式進件;試算表只保留「排程/稅務彙總/銀行控制」這類 工作表,而不是例行過帳的入口。
| 系統 | 存放內容 | 為何放這裡 |
|---|---|---|
| 總帳(進件+帳簿) | 每日銷售、供應商付款、信用卡消費(表單 → 匯入分錄),以及複式簿記總帳:科目、依日期分錄與來源指標。 | 與費用報銷相同:先記錄、再以 source_app + source_record_id 匯入已過帳分錄。不可竄改、必然平衡、可供查核。 |
| 費用報銷系統 | 報銷申請單,含收據影像、簽核流程與付款資訊。 | 收據附在已核准的單據上,正是 §162 與 §274 所要求的佐證形式。 |
| Google 試算表 | 折舊計算表、1099 供應商彙總、銀行對帳——以及必須逐筆保留的 §274(d) 餐飲/差旅明細。 | 這些是排程/合規/控制工作表,不是單純的過帳表單。在做成完整模組之前,繼續放在試算表。 |
| BI 報表 | 建立在以上資料之上的儀表板。 | 僅供分析。儀表板不是帳簿記錄——它不會被封存,也無法作為任何佐證。 |
國稅局實際要求什麼
IRC §6001 與 Treas. Reg. §1.6001-1 就是全部規定: 保存永久性的帳簿或記錄,足以證明申報表上所列的總收入、扣除額與抵減額。
沒有規定的會計科目表、沒有強制的複式簿記、沒有必備的明細帳,也沒有核准的軟體清單。 只要能佐證申報表,一份試算表加一疊收據在法律上就是充分的。實務上真正被檢視的是:
- §162 —— 費用必須是「一般且必要」(ordinary and necessary),而且你要能證明。
- §274(d) —— 對差旅、餐飲、禮品與特定財產(listed property)的加強佐證要求。小型企業在查核中失分,多半就在這裡。
- Rev. Proc. 97-22 —— 電子記錄可以,前提是系統能重製清晰可讀的副本、提供索引,並具備偵測未授權竄改的合理控制。
保存年限
| 記錄類型 | 保存期間 |
|---|---|
| 一般帳簿與佐證明細 | 自申報日起 3 年(§6501) |
| 漏報總收入超過 25% | 6 年 |
| 未申報或涉及詐欺 | 永久 |
| 薪資稅相關記錄 | 4 年 |
| 資產記錄(成本基礎、資本支出) | 至處分年度後 3 年 |
為什麼採現金收付制,什麼情況會被迫改制
本帳簿採現金收付制:錢進來才認列收入,錢出去才認列費用。 這是依據 §448(c) 小型企業納稅人例外規定——若前三年平均年度總收入低於 經通膨調整的門檻(近年約在 3,000 萬至 3,200 萬美元區間), 則不論實體型態均可採用現金收付制,並免適用 §471 存貨會計、§263A 統一資本化規定 與 §460 長期工程合約規定。
只有三種情況會被迫改採權責發生制:
- 總收入超過門檻
- C 型公司,或合夥人中有 C 型公司的合夥組織,且超過門檻
- 避稅安排(tax shelter)—— 任何規模皆不適用現金收付制
您需要申報什麼
| 實體型態 | 申報表 | 是否需附資產負債表 |
|---|---|---|
| 單一成員 LLC(視同無實體) | Form 1040 Schedule C | 根本沒有這張表——無處可填應收/應付帳款 |
| 多成員 LLC | Form 1065 | 總收入與總資產雙雙低於 25 萬美元時,Schedule L/M-1/M-2 可免附 |
| LLC 選擇按 S 型公司課稅 | Form 1120-S | 同樣的 25 萬/25 萬免附門檻 |
哪份記錄放在哪裡
| 記錄 | 存放於 | 以何種形式進入總帳 |
|---|---|---|
| 每日/每週銷售 | ledger_intake(kind=sales) | 匯入,source_app = sales |
| 供應商付款 | ledger_intake(kind=vendor,保留收款人/TIN) | 匯入,source_app = vendor |
| 信用卡消費 | ledger_intake(kind=card)+ 收據資料夾 | 匯入,source_app = card |
| 員工報銷 | 費用報銷系統(含收據) | 由「工具」頁籤匯入,source_app = expense |
| 銀行對帳 | 試算表 | 不入帳——它是內部控制,不是交易 |
| 折舊計算表 | 試算表 | 每年一筆分錄,source_app = sheet |
| 1099 供應商彙總 | 試算表(由供應商收款人彙總) | 不入帳——直接用於填報 Form 1099-NEC |
| 餐飲與差旅明細 | 試算表,一筆一列 | 以彙總分錄入帳;明細必須逐筆保留 |
| 期初餘額 | — | 單筆分錄,source_app = opening |
絕不可彙總的三類
彙總分錄本身完全正常。但有三類是例外——不論總帳怎麼記,明細都必須逐筆保留且可追查。
1. 餐飲、差旅、禮品、特定財產
§274(d) 要求每一筆都具備五項要素,不是每個月一次。您的試算表需要為每一項各設一欄:
- 金額
- 時間(日期)
- 地點
- 營業目的
- 業務關係 —— 在場的人是誰
差旅與招待費用在 75 美元以下免附收據(Reg. §1.274-5(c)(2)(iii)),
但上述五項書面要素永遠必要——而且住宿費不論金額大小一律須附收據。
相關科目:64300 餐飲及招待、68600 差旅費。
2. 支付予承攬人的款項
若一個曆年內支付非公司組織的承攬人達 600 美元以上,即須申報
Form 1099-NEC,並須事先取得其 W-9。這需要按收款人彙總的年度金額,
而彙總分錄會把收款人資訊完全抹掉。請另開一個供應商工作表,記錄姓名、稅籍編號(TIN)、
地址與累計金額。相關科目:62000 承包服務、66700 專業服務費。
3. 存款
彙總的粗細程度,不可超過能對應銀行對帳單逐筆項目的程度。 若一筆總帳分錄涵蓋了對帳單上分別列示的三筆存款,銀行對帳就失效了—— 而未經對帳的總帳,是查核時第一個被質疑的對象。
鎖定日期前凍結來源
因此推進 locked_through_date 不只是改設定。在鎖定至某日之前:
- 將該日(含)以前分錄所引用的每一個來源工作表匯出成 CSV 或 PDF。
- 從「報表」頁籤匯出該日期區間的試算表。
- 兩者以唯讀方式封存,且存放位置與線上試算表分開。
- 在「管理 → 設定」(門市區塊)設定
locked_through_date。
每筆分錄上的 source_url 會準確告訴您第 1 步該收哪些工作表——
用日期篩選日記帳,把連結全部收集起來即可。
核心觀念
複式簿記,帶正負號的「分」
每筆分錄至少有兩行明細。金額以整數「分」儲存在 amount_cents,
並帶正負號:借方為正,貸方為負。因此整個正確性規則只有一條——
明細加總必須等於零。畫面上看到的「借方」「貸方」兩欄純粹是呈現方式。
使用整數而非小數,是因為浮點數中 0.1 + 0.2 ≠ 0.3,
而「試算表差一分錢」這種問題追查起來極其痛苦。
先草稿,再過帳
手動分錄會先儲存為 draft(草稿),可不限次數重新開啟與修改。
按「驗證並過帳」後,系統會讀回已儲存的明細、核對借貸平衡、來源與
entry_date 是否符合門市鎖定設定,再切換為 posted(已過帳)。
所有報表只計算已過帳分錄,因此未完成草稿不會影響帳務。
已過帳的分錄永久不可更改
期間鎖定
沒有獨立的期間記錄——期間就是 entry_date。您隨時可以建立草稿,但過帳要求
日期不早於 books_start_date,且晚於
locked_through_date(含該日在內的最後已結帳日)。兩者都在「管理 → 設定」
(門市區塊)設定。
會計科目表
五位數科目代碼,以首位數字分類:
| 區間 | 類別 | 範例 |
|---|---|---|
1xxxx | 資產 | 10100 支票帳戶、17000 傢俱與設備 |
2xxxx | 負債 | 20100 信用卡、24000 應付銷售稅 |
3xxxx | 權益 | 30000 期初餘額權益、39000 保留盈餘 |
4xxxx | 收入 | 40000 銷售收入、41000 服務收入 |
5xxxx | 銷貨成本 | 50000 銷貨成本 |
6xxxx | 營業費用 | 67100 租金支出、68700 水電費 |
8xxxx | 其他 | 80000 會計師諮詢 |
每個科目都明確儲存 normal_balance(正常餘額方向),而不是從類別推導,
因為抵銷科目會打破規則:17900 累計折舊
是資產卻帶貸方餘額,33000 業主提款 是權益卻帶借方餘額。
is_active = false,管理員才可能刪除。建議永遠只停用、不刪除。
80000 會計師諮詢 是給你真的分不出類別的交易用的。
寧可用它,也不要亂猜;並在年度結帳前把它清空。
帳簿起始與鎖定
沒有 ledger_periods 集合。會計期間就是每筆分錄的 entry_date。
兩個門市設定控制哪些日期可以過帳:
books_start_date—— 帳簿起始日;BI 歷史下限。早於此日的分錄無法過帳。locked_through_date—— 含該日在內的最後已結帳日。過帳的entry_date必須更晚。
兩者都在「管理 → 設定」(門市區塊)設定。結束一個月,就是在匯出封存後,
把 locked_through_date 設為該月最後一天。
手動建立分錄
「日記帳」頁籤 → 「+ 新增分錄」。
- 日期 —— 會計入帳日(
entry_date),不一定是今天。須不早於books_start_date,且晚於locked_through_date。 - 摘要 —— 這筆分錄的用途,用一年後的陌生人也看得懂的文字寫。
- 來源 —— 保持在「手動」。
- 填寫明細:選科目,並在「借方」或「貸方」其中一欄填入金額。
- 按「儲存草稿」。草稿仍可編輯,也不會計入報表。
- 開啟草稿並完成修正。過帳前「差額」必須顯示
0.00並轉為綠色。 - 按「驗證並過帳」,或選擇「編輯草稿 → 儲存並過帳」。
常見分錄型態:
現金銷貨 借 10100 支票帳戶 貸 40000 銷售收入
開票付房租 借 67100 租金支出 貸 10100 支票帳戶
刷卡採購 借 64900 辦公用品 貸 20100 信用卡
繳信用卡款 借 20100 信用卡 貸 10100 支票帳戶
業主提取現金 借 33000 業主提款 貸 10100 支票帳戶
AI 快速新增
「日記帳」頁籤 → 「✦ AI 快速新增」,就在「+ 新增分錄」旁邊。
用一般文字描述這筆交易,AI 會草擬一筆借貸平衡的分錄——可能是簡易轉帳,也可能是多筆明細的拆分——
直接填入與「+ 新增分錄」相同的表單。不會自動過帳:請先檢視每一行明細、修正錯誤,
再按「儲存草稿」或「儲存並過帳」,與手動輸入的分錄完全相同。
草擬出的摘要會依你的 note_language 設定撰寫(見下方)。
背後如何運作
- 你輸入的文字與門市目前啟用中的會計科目表,會送到
myapi的/ai/chat端點——AI 只看得到科目代碼/名稱/類型,供應商的 API 金鑰全程留在伺服器端。 - AI 會回傳摘要、(若文字中有提到)日期,以及一組借貸平衡到分的明細(科目代碼、借方或貸方、 明細摘要、類型)。
- 每一行的科目代碼都會對照真實的會計科目表。若 AI 編造或打錯代碼,該行的科目會留空 並標示出來,而不是幫你亂猜——請在拆分編輯畫面中自行修正後再儲存。
- 目前(或剛剛)使用的 AI 供應商/模型會顯示在輸入框下方——開啟面板時顯示預設會使用的模型, 解析完成後則顯示實際使用的模型。
- 分錄摘要與每一行明細摘要會依你的
note_language撰寫(見下方)。科目代碼維持科目表原樣。 - 在拆分編輯畫面中,把滑鼠移到「借方」或「貸方」欄位上,會顯示這個金額對「這個科目」實際代表 的意義——例如借記支票帳戶顯示「存入」,貸記預收收入顯示「負債增加(欠更多)」,而不只是通用的 借貸規則。
摘要語言(note_language)
AI 快速新增會讀取登入使用者的
users.settings.ledger_settings.note_language,用來撰寫它草擬的所有摘要
(分錄摘要與每一行明細摘要)。請到「帳號 → 設定」→
「AI 摘要語言」設定。
| 值 | 摘要語言 |
|---|---|
en(預設) | 英文 |
zh-tw | 繁體中文 |
zh-cn | 簡體中文 |
若未設定或值無效,則回退為英文。這與介面語言切換無關,也不會改寫你手動輸入的摘要。
範例
輸入:
收到 Dreamwear 出貨單 75872 的電匯 30784,其中 2% 認列服務收入,
其餘記為預收客戶款項
草擬結果:
借 10100 支票帳戶 30,784.00
貸 41000 服務收入 615.68
貸 25000 預收收入 30,168.32
ledger_entries.ai_model
(例如 groq llama-3.1-8b-instant),並顯示於分錄詳細資訊面板。手動輸入的分錄則此欄位留空。
試算表彙總分錄
這是試算表明細進入帳簿的方式。表單相同,但把「來源」切換為 「試算表彙總」後,會多出四個欄位。
| 欄位 | 必填 | 填什麼 |
|---|---|---|
| 試算表連結 | 必填 | 指向該工作表分頁的完整連結,含 #gid= 片段。這就是 Rev. Proc. 97-22 所要求的索引。 |
| 批次代碼 | 必填 | 這一批的固定名稱,例如 2027-01-sales。用來確保不會重複入帳。 |
| 列數 | 必填 | 這筆分錄代表了多少列來源資料。 |
| 試算表合計 | 選填 | 試算表自己算出的這批總額。系統會與分錄的借方合計交叉核對。 |
這些檢查為你做了什麼
- 少填連結、批次代碼或列數,在連上伺服器之前就會被拒絕。說不出自己彙總了什麼的分錄無法查核。
- 填入的試算表合計與明細不符時,過帳會被拒絕並顯示確切差額。這能當下抓到打錯的彙總,而不是拖到年底。
- 同一個批次代碼永遠不可能過帳兩次——由資料庫唯一索引強制執行。整個月重跑一次是安全的。
實例
一月銷售試算表:31 列,合計 18,400.00
日期 2027-01-31
摘要 一月銷售彙總
來源 試算表彙總
試算表連結 https://docs.google.com/spreadsheets/d/…#gid=0
批次代碼 2027-01-sales
列數 31
試算表合計 18400.00
借 10100 支票帳戶 18,400.00
貸 40000 銷售收入 18,400.00
YYYY-MM-用途——2027-01-sales、
2027-01-cardcharges、2027-depreciation。
因為防止重複入帳靠的就是這個代碼,命名習慣不一致正是讓這道防線失效的原因。
更正錯誤
已過帳的分錄不能編輯。要更正,請再過帳兩筆:
- 一筆迴轉分錄 —— 科目相同、每個金額取負值,並將
reversal_of指向原分錄。 - 一筆更正後分錄,填入正確數字。
如此軌跡上便清楚呈現:你原本記了什麼、你發現了錯誤、你用什麼取代它。 這是優點:查核人員看到乾淨的更正軌跡,看到的是一套受控的流程; 而被悄悄改過的帳簿,只會讓人開始想「還有什麼被改過」。
系統刻意不設 reversed_by 欄位,因為要寫入它就必須更新一筆已過帳的記錄。
要找迴轉分錄,請改以「reversal_of 等於原分錄 ID」來查詢。
locked_through_date 當日或更早的日期過帳。請以較晚的日期過帳
迴轉與更正分錄,並在摘要中註明原因。
報表
所有報表只計算已過帳的分錄。草稿在任何地方都不會出現。
| 報表 | 用途 |
|---|---|
| 試算表 | 列出所有有餘額的科目及借貸方合計。兩邊合計必須相等,表尾會明確標示。這是結帳時要匯出封存的報表。 |
| 科目明細 | 單一科目的所有異動,依日期排序並顯示累計餘額。需先選定科目。數字看起來不對時,就用這張表往下追。 |
| 現金概況 | 指定日期的銀行與現金科目餘額。會忽略「起日」篩選——它呈現的是時點餘額,不是期間變動。 |
| 完整性檢查 | 對帳簿本身的稽核。詳見下節。 |
完整性檢查對照
這些是稽核,不是防護欄。API 規則與過帳協定才是控制機制, 這份報表只是確認兩者都生效了。任何出現在這裡的項目,都代表有東西繞過了既定流程—— 最可能是管理員直接對資料庫動手。
| 問題 | 代表 | 該怎麼做 |
|---|---|---|
| 分錄明細加總不為零 | 一筆已過帳分錄不平衡——透過應用程式不可能發生。 | 嚴重。請直接到 PocketBase 調查;在修正前帳是錯的。 |
line_count 不符 |
分錄過帳之後有明細被新增或刪除。 | 與該日期區間的封存檔比對,判斷哪一邊才是正確的。 |
total_debit_cents 不符 |
過帳後明細金額被更動。 | 同上——與封存檔核對。 |
source_total_cents 不符 |
分錄與其來源在過帳當下所宣稱的合計已不一致。 | 打開 source_url。不是試算表在過帳後被改,就是分錄被改。 |
| 試算表來源分錄缺少連結/列數 | 一筆沒有指回明細的彙總。 | 就現狀無法查核。請找出來源並補記——必要時迴轉後重新過帳。 |
| 明細引用已停用科目 | 通常無害——科目是在分錄過帳之後才被停用。 | 確認當時使用該科目是正確的。通常不需處理。 |
| 草稿超過 7 天 | 被遺忘或做到一半的分錄。 | 補完或刪除。草稿不影響帳務,但會掩蓋真正待辦的事。 |
| 已入帳費用未進入總帳 | 某筆標記為 booked 的報銷從未進到總帳。 |
到「工具」頁籤執行「匯入已入帳費用」。 |
工具頁籤
待匯入
列出已標記為 booked 但尚未進入總帳的報銷單。展開即可看到待處理清單。
若這裡是空的,代表費用系統與總帳一致。
匯入已入帳費用
為每一筆已入帳的報銷單各建立一筆總帳分錄,source_app = expense,
source_record_id 設為該報銷單的記錄 ID。該 ID 受唯一索引保護,
所以重複執行匯入是安全的——已匯入的會被略過,不會重複產生。
建立範例分錄
命令列工具
請在 ledger/ 目錄下執行。三個腳本都需要在環境變數中提供 PocketBase 帳密。
建立會計科目表
可重複執行——科目以 (store, code) 比對後更新,不會重複建立。建議先不加 --run 預覽。
PB_ADMIN=1 [email protected] PB_PASSWORD=… \
node scripts/seed-accounts.js --store hilinkpro # 預覽,不寫入
PB_ADMIN=1 [email protected] PB_PASSWORD=… \
node scripts/seed-accounts.js --store hilinkpro --run # 實際寫入
測試過帳協定
不需帳密、不連網路——純邏輯測試。修改 pb-ledger.js 後請執行。
cd scripts && node test-posting.js
涵蓋金額解析、平衡不變式、來源欄位規則,以及過帳協定的每一種失敗情境——
並在每種情境下都斷言失敗的分錄絕不會被切換為 posted。
驗證 API 規則
證明安全規則的行為與設計假設一致——特別是「更新規則比對的是更新前的欄位值」 這一點,正是兩階段過帳得以成立的前提。此腳本會建立並清除真實記錄。
PB_ADMIN=1 [email protected] PB_PASSWORD=… \
node scripts/verify-rules.js
每月流程
- 在試算表完成銀行對帳 —— 對帳單每一筆都有著落,期末餘額相符。
- 登錄/匯入當月進件 —— 每日銷售、供應商付款、信用卡消費(表單上線後於總帳完成);在此之前,仍依現行方式自試算表建立彙總分錄。
- 從「工具」頁籤匯入已入帳費用。
- 確認「待匯入」為空(費用,以及之後其他進件來源)。
- 執行完整性檢查,並處理列出的每一項。
- 比對「現金概況」與已對帳的銀行餘額。兩者必須相符。若不符,先停下來查明原因,再做其他事。
- 確認當月收據都已歸檔 —— 報銷放在費用系統,其餘放在已建立索引的資料夾。
年度結帳流程
- 先完成最後一個月的每月流程。
- 清空
80000 會計師諮詢—— 把裡面的東西全部重新分類。 - 過帳折舊 —— 依折舊計算表建立一筆分錄,
source_app = sheet。 - 檢查 §274(d) 明細完整 —— 每一列餐飲與差旅都具備五項要素。趁還記得的時候補齊。
- 產出 1099-NEC 彙總 —— 由供應商工作表產生。非公司組織且達 600 美元以上者都需要一份表,而你需要對方的 W-9。1 月 31 日前須送達收款人與國稅局。
- 執行完整性檢查 —— 必須是空的。
- 匯出並封存:所有來源工作表、試算表,以及任何你預期會被追問的科目明細。
- 在「管理 → 設定」將
locked_through_date設為年度最後一天。 - 把封存資料交給你的會計師。
本系統做不到的事
已知的缺口,先講清楚,免得四月才發現:
| 缺少 | 後果 | 目前的替代做法 |
|---|---|---|
| 每日銷售/供應商/信用卡進件表單 | 需先在 PocketBase 建立 ledger_intake 集合(LEDGER_POCKETBASE_SETUP.md 步驟 5)。 |
使用「進件」頁籤 → 標為就緒 → 匯入就緒項目(或「工具」)。 |
| 損益表 | 沒有可直接用於申報的分類損益表。 | 匯出試算表,在試算表軟體中依科目區間分組彙總。 |
| 年度結帳分錄 | 本期損益不會結轉至 39000 保留盈餘,因此第二年的試算表會帶著第一年的收入與費用餘額。 |
手動過帳:借記各收入科目、貸記各費用科目使其歸零,差額轉入保留盈餘。 |
| 銀行對帳 | 沒有對帳單匯入/自動勾銷介面。 | 在分錄明細或科目明細以 is_reconciled 手動勾選;再與「現金概況」/試算表比對。 |
| 應收/應付明細帳 | 沒有帳齡分析,也沒有客戶別或供應商別餘額。 | 現金收付制下並非必要。若需要知道誰欠你錢,用試算表追蹤。 |
| 固定資產帳 | 沒有成本基礎、折舊方法或處分記錄可供填報 Form 4562。 | 折舊計算表放在試算表;每年過帳一筆分錄。 |
| 薪資 | 沒有薪資明細或稅務負債追蹤。 | 使用薪資服務商;將彙總分錄過帳至 66000 與 21000。 |
資料表結構與 API 規則記載於 ledger/LEDGER_POCKETBASE_SETUP.md(英文)。