import { describe,it,expect,beforeEach,afterEach } from 'vitest' import { mkdtemp,rm,writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import path from 'node:path' import { parseMoney,chineseMoney,moneyCells,MAX_CENTS } from '../src/core/money' import { parseReceiptDate } from '../src/core/date' import { buildDocument,TemplateRegistry } from '../src/core/templates' import { defaultSettings } from '../src/shared/settings' import { Ledger } from '../src/main/ledger' import { XlsxReader,createWorkbookTemplate } from '../src/main/workbooks' import ExcelJS from 'exceljs' describe('金额与日期',()=>{ it('以整数分保存,金额格不截断',()=>{expect(parseMoney('3,500.01')).toBe(350001);expect(parseMoney('0.01')).toBe(1);expect(parseMoney('9999999.99')).toBe(MAX_CENTS);expect(moneyCells(MAX_CENTS).join('')).toBe('999999999')}) it.each(['-1','0','1.001','1e3','12,34','10000000','abc'])('拒绝无效金额 %s',v=>{expect(()=>parseMoney(v)).toThrow()}) it.each([[350000,'叁仟伍佰元整'],[100101,'壹仟零壹元零壹分'],[100000001,'壹佰万元零壹分'],[1010000,'壹万零壹佰元整'],[1234,'壹拾贰元叁角肆分']])('正确转换金额大写', (n,text)=>expect(chineseMoney(n as number)).toBe(text)) it('验证真实日期与 Excel 的 1900/1904 日期制',()=>{expect(parseReceiptDate('2024-2-29')).toBe('2024-02-29');expect(()=>parseReceiptDate('2026-2-29')).toThrow();expect(parseReceiptDate(61)).toBe('1900-03-01');expect(parseReceiptDate(0,true)).toBe('1904-01-01');expect(()=>parseReceiptDate(60)).toThrow()}) }) let dir:string beforeEach(async()=>{dir=await mkdtemp(path.join(tmpdir(),'receipt-studio-test-'))}) afterEach(async()=>{await rm(dir,{recursive:true,force:true})}) const input={date:'2026-10-16',customer:'测试客户',item:'服务费',amountCents:350000,cashier:'',reviewer:''} describe('持久编号',()=>{ it('预览不推进,保留批次快照并在重启后继续编号',async()=>{const store=new Ledger(path.join(dir,'data.sqlite'));await store.open();expect(store.nextNumber('SJ')).toBe('SJ00000001');expect(store.nextNumber('SJ')).toBe('SJ00000001');const settings={...defaultSettings(),defaultCashier:'张三'};const batch=await store.reserve({name:'a.xlsx',hash:'hash'},[input,input],settings,null);expect(batch.receipts.map(r=>r.number)).toEqual(['SJ00000001','SJ00000002']);expect(batch.receipts[0].cashier).toBe('张三');const again=new Ledger(path.join(dir,'data.sqlite'));await again.open();expect(again.nextNumber('SJ')).toBe('SJ00000003');expect(again.get(batch.id).receipts).toEqual(batch.receipts)}) it('数据损坏时停止分配编号,保留原文件',async()=>{const file=path.join(dir,'bad.sqlite');await writeFile(file,'broken');await expect(new Ledger(file).open()).rejects.toThrow('停止分配编号')}) }) describe('模板和工作簿',()=>{ it('模板转义用户文字,奇数张 A4 保留空白半页',()=>{const registry=new TemplateRegistry(),r={...input,number:'SJ00000001',customer:''};const html=buildDocument(registry.get('classic-v1'),[r,r,r],{settings:defaultSettings(),seal:null,preview:false},'print',2,true);expect(html).not.toContain('