[{"data":1,"prerenderedAt":3525},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template":3,"surround:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template":3490,"ancestors:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template":3498,"children:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template":3503,"related:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template":3504},{"id":4,"title":5,"body":6,"dateModified":3457,"datePublished":3457,"description":3458,"extension":3459,"faq":3460,"keyword":3469,"kind":3470,"meta":3471,"navigation":453,"path":3479,"seo":3480,"slug":3483,"stem":3484,"topics":3485,"type":3488,"__hash__":3489},"docs\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template\u002Findex.md","Generate Invoices from an Excel Template with Python",{"type":7,"value":8,"toc":3445},"minimark",[9,19,349,354,383,386,1165,1169,1176,1275,1287,1291,1302,1679,1686,1814,1818,1821,2240,2255,2259,2262,2872,2875,3019,3023,3026,3194,3304,3318,3322,3370,3374,3380,3384,3390,3396,3402,3408,3412,3441],[10,11,12,13,18],"p",{},"Invoices are the template job where mistakes cost the most: a wrong total, a reused number or a missing line is a problem with a customer and an accountant, not just a cosmetic flaw. The mechanics are the same as any template fill — load, write cells, save — but invoices add money arithmetic, numbering rules and a paper trail. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002F","Generating Excel Reports from Templates",", builds an invoice run that uses named cells, exact decimal arithmetic, gap-free numbers and a register, and produces a workbook and a PDF per customer.",[20,21,29,30,29,34,29,38,29,46,29,55,29,59,29,66,29,70,29,74,29,78,29,82,29,86,29,90,29,94,29,98,29,102,29,107,29,109,29,111,29,114,29,116,29,118,29,120,29,122,29,124,29,127,29,130,29,134,29,136,29,140,29,142,29,144,29,146,29,149,29,154,29,158,29,161,29,164,29,166,29,169,29,171,29,173,29,175,29,178,29,180,29,183,29,186,29,189,29,191,29,194,29,196,29,199,29,201,29,203,29,205,29,208,29,212,29,216,29,220,29,222,29,225,29,227,29,230,29,232,29,235,29,237,29,240,29,243,29,247,29,251,29,255,29,257,29,260,29,262,29,264,29,266,29,269,29,272,29,274,29,277,29,279,29,281,29,283,29,286,29,288,29,291,29,294,29,298,29,300,29,303,29,305,29,307,29,309,29,312,29,314,29,317,29,320,29,324,29,326,29,329,29,331,29,333,29,335,29,338,29,340,29,343],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"129 0 502 334","img",[25,26],"iv-layout-t","iv-layout-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:502px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"The invoice template and its named cells",[35,36,37],"desc",{"id":26},"The template has fields on the right for the invoice number, date and due date, the customer's name and address on the left, a block of ten line-item rows with description, quantity, unit price and amount, and three totals at the bottom for net, VAT and total due. Each field and the line block has a defined name that the Python code uses instead of a cell address.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"129","0","502","334","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"156.8","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[39,56],{"x":57,"y":49,"width":58,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"188.8","57.0",[60,61,65],"text",{"x":62,"y":63,"style":64},"217.3","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[39,67],{"x":68,"y":49,"width":69,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"245.9","123.0",[60,71,73],{"x":72,"y":63,"style":64},"307.3","B",[39,75],{"x":76,"y":49,"width":77,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"368.8","46.0",[60,79,81],{"x":80,"y":63,"style":64},"391.8","C",[39,83],{"x":84,"y":49,"width":85,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"414.8","91.8",[60,87,89],{"x":88,"y":63,"style":64},"460.7","D",[39,91],{"x":92,"y":49,"width":93,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"506.6","96.6",[60,95,97],{"x":96,"y":63,"style":64},"554.9","E",[39,99],{"x":48,"y":100,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"40.0","28.0",[60,103,106],{"x":104,"y":105,"style":64},"172.8","59.0","1",[39,108],{"x":57,"y":100,"width":58,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,110],{"x":68,"y":100,"width":69,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,112,65],{"x":72,"y":105,"style":113},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[39,115],{"x":76,"y":100,"width":77,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,117,89],{"x":80,"y":105,"style":113},[39,119],{"x":84,"y":100,"width":85,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,121,97],{"x":88,"y":105,"style":113},[39,123],{"x":92,"y":100,"width":93,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,125,126],{"x":96,"y":105,"style":113},"F",[39,128],{"x":48,"y":129,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"68.0",[60,131,133],{"x":104,"y":132,"style":64},"87.0","2",[39,135],{"x":57,"y":129,"width":58,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,137,139],{"x":62,"y":132,"style":138},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","3",[39,141],{"x":68,"y":129,"width":69,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,143],{"x":76,"y":129,"width":77,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,145],{"x":84,"y":129,"width":85,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,147,148],{"x":88,"y":132,"style":138},"Invoice no.",[39,150],{"x":92,"y":129,"width":93,"height":101,"rx":42,"fill":151,"stroke":152,"style":153},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[60,155,157],{"x":96,"y":132,"style":156},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","inv_number",[39,159],{"x":48,"y":160,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"96.0",[60,162,139],{"x":104,"y":163,"style":64},"115.0",[39,165],{"x":57,"y":160,"width":58,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,167,168],{"x":62,"y":163,"style":138},"4",[39,170],{"x":68,"y":160,"width":69,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,172],{"x":76,"y":160,"width":77,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,174],{"x":84,"y":160,"width":85,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,176,177],{"x":88,"y":163,"style":138},"Date",[39,179],{"x":92,"y":160,"width":93,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,181,182],{"x":96,"y":163,"style":138},"inv_date",[39,184],{"x":48,"y":185,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"124.0",[60,187,168],{"x":104,"y":188,"style":64},"143.0",[39,190],{"x":57,"y":185,"width":58,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,192,193],{"x":62,"y":188,"style":138},"7",[39,195],{"x":68,"y":185,"width":69,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,197,198],{"x":72,"y":188,"style":138},"customer_name",[39,200],{"x":76,"y":185,"width":77,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,202],{"x":84,"y":185,"width":85,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,204],{"x":92,"y":185,"width":93,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,206],{"x":48,"y":207,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"152.0",[60,209,211],{"x":104,"y":210,"style":64},"171.0","5",[39,213],{"x":57,"y":207,"width":58,"height":101,"rx":42,"fill":214,"stroke":215,"style":153},"#f0f4ff","var(--brand,#5b5cf0)",[60,217,219],{"x":62,"y":210,"style":218},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","11",[39,221],{"x":68,"y":207,"width":69,"height":101,"rx":42,"fill":214,"stroke":215,"style":153},[60,223,224],{"x":72,"y":210,"style":218},"Description",[39,226],{"x":76,"y":207,"width":77,"height":101,"rx":42,"fill":214,"stroke":215,"style":153},[60,228,229],{"x":80,"y":210,"style":218},"Qty",[39,231],{"x":84,"y":207,"width":85,"height":101,"rx":42,"fill":214,"stroke":215,"style":153},[60,233,234],{"x":88,"y":210,"style":218},"Unit price",[39,236],{"x":92,"y":207,"width":93,"height":101,"rx":42,"fill":214,"stroke":215,"style":153},[60,238,239],{"x":96,"y":210,"style":218},"Amount",[39,241],{"x":48,"y":242,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"180.0",[60,244,246],{"x":104,"y":245,"style":64},"199.0","6",[39,248],{"x":57,"y":242,"width":58,"height":101,"rx":42,"fill":249,"stroke":250,"style":153},"#fdefd8","var(--gold,#b4740a)",[60,252,254],{"x":62,"y":245,"style":253},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","12–21",[39,256],{"x":68,"y":242,"width":69,"height":101,"rx":42,"fill":249,"stroke":250,"style":153},[60,258,259],{"x":72,"y":245,"style":253},"lines (10 rows)",[39,261],{"x":76,"y":242,"width":77,"height":101,"rx":42,"fill":249,"stroke":250,"style":153},[39,263],{"x":84,"y":242,"width":85,"height":101,"rx":42,"fill":249,"stroke":250,"style":153},[39,265],{"x":92,"y":242,"width":93,"height":101,"rx":42,"fill":249,"stroke":250,"style":153},[39,267],{"x":48,"y":268,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"208.0",[60,270,193],{"x":104,"y":271,"style":64},"227.0",[39,273],{"x":57,"y":268,"width":58,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,275,276],{"x":62,"y":271,"style":138},"23",[39,278],{"x":68,"y":268,"width":69,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,280],{"x":76,"y":268,"width":77,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,282],{"x":84,"y":268,"width":85,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,284,285],{"x":88,"y":271,"style":138},"Net",[39,287],{"x":92,"y":268,"width":93,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,289,290],{"x":96,"y":271,"style":138},"net_total",[39,292],{"x":48,"y":293,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"236.0",[60,295,297],{"x":104,"y":296,"style":64},"255.0","8",[39,299],{"x":57,"y":293,"width":58,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,301,302],{"x":62,"y":296,"style":138},"24",[39,304],{"x":68,"y":293,"width":69,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,306],{"x":76,"y":293,"width":77,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,308],{"x":84,"y":293,"width":85,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,310,311],{"x":88,"y":296,"style":138},"VAT 20%",[39,313],{"x":92,"y":293,"width":93,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,315,316],{"x":96,"y":296,"style":138},"vat_total",[39,318],{"x":48,"y":319,"width":50,"height":101,"rx":42,"fill":52,"stroke":53,"style":54},"264.0",[60,321,323],{"x":104,"y":322,"style":64},"283.0","9",[39,325],{"x":57,"y":319,"width":58,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,327,328],{"x":62,"y":322,"style":138},"25",[39,330],{"x":68,"y":319,"width":69,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,332],{"x":76,"y":319,"width":77,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[39,334],{"x":84,"y":319,"width":85,"height":101,"rx":42,"fill":45,"stroke":53,"style":54},[60,336,337],{"x":88,"y":322,"style":138},"Total due",[39,339],{"x":92,"y":319,"width":93,"height":101,"rx":42,"fill":151,"stroke":152,"style":153},[60,341,342],{"x":96,"y":322,"style":156},"gross_total",[60,344,348],{"x":345,"y":346,"style":347},"380.0","314.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","code refers to names; the designer can move cells without breaking the run",[350,351,353],"h2",{"id":352},"prerequisites","Prerequisites",[355,356,361],"pre",{"className":357,"code":358,"language":359,"meta":360,"style":360},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[362,363,364],"code",{"__ignoreMap":360},[365,366,369,373,377,380],"span",{"class":367,"line":368},"line",1,[365,370,372],{"class":371},"sMTad","pip",[365,374,376],{"class":375},"srMev"," install",[365,378,379],{"class":375}," openpyxl",[365,381,382],{"class":375}," pandas\n",[10,384,385],{},"A designer would normally build the template in Excel. For a reproducible example, this creates one with the same structure — styles, number formats and the defined names the code relies on:",[355,387,391],{"className":388,"code":389,"language":390,"meta":360,"style":360},"language-python shiki shiki-themes github-light github-dark-high-contrast","from decimal import Decimal, ROUND_HALF_UP\nfrom pathlib import Path\nimport csv\nimport datetime as dt\n\nfrom openpyxl import Workbook, load_workbook\nfrom openpyxl.styles import Font\nfrom openpyxl.workbook.defined_name import DefinedName\n\ndef make_template(path=\"invoice_template.xlsx\"):\n    wb = Workbook()\n    ws = wb.active\n    ws.title = \"Invoice\"\n    ws[\"A1\"] = \"INVOICE\"\n    ws[\"A1\"].font = Font(size=20, bold=True)\n    for row, label in [(3, \"Invoice no.\"), (4, \"Date\"), (5, \"Due\")]:\n        ws.cell(row, 5, label)\n    ws[\"F4\"].number_format = ws[\"F5\"].number_format = \"yyyy-mm-dd\"\n    for col, label in zip(\"ADEF\", [\"Description\", \"Qty\", \"Unit price\", \"Amount\"]):\n        ws[f\"{col}11\"] = label\n        ws[f\"{col}11\"].font = Font(bold=True)\n    for row in range(12, 22):\n        ws[f\"E{row}\"].number_format = ws[f\"F{row}\"].number_format = \"#,##0.00\"\n    for row, label in [(23, \"Net\"), (24, \"VAT 20%\"), (25, \"Total due\")]:\n        ws.cell(row, 5, label)\n        ws.cell(row, 6).number_format = \"#,##0.00\"\n    ws[\"F25\"].font = Font(bold=True)\n    ws.column_dimensions[\"A\"].width = 34\n    for col in \"EF\":\n        ws.column_dimensions[col].width = 14\n    names = {\"inv_number\": \"$F$3\", \"inv_date\": \"$F$4\", \"due_date\": \"$F$5\",\n             \"customer_name\": \"$A$7\", \"customer_address\": \"$A$8\", \"lines\": \"$A$12:$F$21\",\n             \"net_total\": \"$F$23\", \"vat_total\": \"$F$24\", \"gross_total\": \"$F$25\"}\n    for name, ref in names.items():\n        wb.defined_names[name] = DefinedName(name, attr_text=f\"Invoice!{ref}\")\n    wb.save(path)\n\nmake_template()\n","python",[362,392,393,413,426,434,448,455,468,481,494,499,521,532,543,554,571,609,653,664,690,733,765,796,821,866,904,913,927,949,966,982,993,1036,1069,1103,1116,1148,1154,1159],{"__ignoreMap":360},[365,394,395,399,403,406,409],{"class":367,"line":368},[365,396,398],{"class":397},"s-kum","from",[365,400,402],{"class":401},"skGVy"," decimal ",[365,404,405],{"class":397},"import",[365,407,408],{"class":401}," Decimal, ",[365,410,412],{"class":411},"sP0c6","ROUND_HALF_UP\n",[365,414,416,418,421,423],{"class":367,"line":415},2,[365,417,398],{"class":397},[365,419,420],{"class":401}," pathlib ",[365,422,405],{"class":397},[365,424,425],{"class":401}," Path\n",[365,427,429,431],{"class":367,"line":428},3,[365,430,405],{"class":397},[365,432,433],{"class":401}," csv\n",[365,435,437,439,442,445],{"class":367,"line":436},4,[365,438,405],{"class":397},[365,440,441],{"class":401}," datetime ",[365,443,444],{"class":397},"as",[365,446,447],{"class":401}," dt\n",[365,449,451],{"class":367,"line":450},5,[365,452,454],{"emptyLinePlaceholder":453},true,"\n",[365,456,458,460,463,465],{"class":367,"line":457},6,[365,459,398],{"class":397},[365,461,462],{"class":401}," openpyxl ",[365,464,405],{"class":397},[365,466,467],{"class":401}," Workbook, load_workbook\n",[365,469,471,473,476,478],{"class":367,"line":470},7,[365,472,398],{"class":397},[365,474,475],{"class":401}," openpyxl.styles ",[365,477,405],{"class":397},[365,479,480],{"class":401}," Font\n",[365,482,484,486,489,491],{"class":367,"line":483},8,[365,485,398],{"class":397},[365,487,488],{"class":401}," openpyxl.workbook.defined_name ",[365,490,405],{"class":397},[365,492,493],{"class":401}," DefinedName\n",[365,495,497],{"class":367,"line":496},9,[365,498,454],{"emptyLinePlaceholder":453},[365,500,502,505,509,512,515,518],{"class":367,"line":501},10,[365,503,504],{"class":397},"def",[365,506,508],{"class":507},"s_Opv"," make_template",[365,510,511],{"class":401},"(path",[365,513,514],{"class":397},"=",[365,516,517],{"class":375},"\"invoice_template.xlsx\"",[365,519,520],{"class":401},"):\n",[365,522,524,527,529],{"class":367,"line":523},11,[365,525,526],{"class":401},"    wb ",[365,528,514],{"class":397},[365,530,531],{"class":401}," Workbook()\n",[365,533,535,538,540],{"class":367,"line":534},12,[365,536,537],{"class":401},"    ws ",[365,539,514],{"class":397},[365,541,542],{"class":401}," wb.active\n",[365,544,546,549,551],{"class":367,"line":545},13,[365,547,548],{"class":401},"    ws.title ",[365,550,514],{"class":397},[365,552,553],{"class":375}," \"Invoice\"\n",[365,555,557,560,563,566,568],{"class":367,"line":556},14,[365,558,559],{"class":401},"    ws[",[365,561,562],{"class":375},"\"A1\"",[365,564,565],{"class":401},"] ",[365,567,514],{"class":397},[365,569,570],{"class":375}," \"INVOICE\"\n",[365,572,574,576,578,581,583,586,590,592,595,598,601,603,606],{"class":367,"line":573},15,[365,575,559],{"class":401},[365,577,562],{"class":375},[365,579,580],{"class":401},"].font ",[365,582,514],{"class":397},[365,584,585],{"class":401}," Font(",[365,587,589],{"class":588},"sa561","size",[365,591,514],{"class":397},[365,593,594],{"class":411},"20",[365,596,597],{"class":401},", ",[365,599,600],{"class":588},"bold",[365,602,514],{"class":397},[365,604,605],{"class":411},"True",[365,607,608],{"class":401},")\n",[365,610,612,615,618,621,624,626,628,631,634,636,638,641,643,645,647,650],{"class":367,"line":611},16,[365,613,614],{"class":397},"    for",[365,616,617],{"class":401}," row, label ",[365,619,620],{"class":397},"in",[365,622,623],{"class":401}," [(",[365,625,139],{"class":411},[365,627,597],{"class":401},[365,629,630],{"class":375},"\"Invoice no.\"",[365,632,633],{"class":401},"), (",[365,635,168],{"class":411},[365,637,597],{"class":401},[365,639,640],{"class":375},"\"Date\"",[365,642,633],{"class":401},[365,644,211],{"class":411},[365,646,597],{"class":401},[365,648,649],{"class":375},"\"Due\"",[365,651,652],{"class":401},")]:\n",[365,654,656,659,661],{"class":367,"line":655},17,[365,657,658],{"class":401},"        ws.cell(row, ",[365,660,211],{"class":411},[365,662,663],{"class":401},", label)\n",[365,665,667,669,672,675,677,680,683,685,687],{"class":367,"line":666},18,[365,668,559],{"class":401},[365,670,671],{"class":375},"\"F4\"",[365,673,674],{"class":401},"].number_format ",[365,676,514],{"class":397},[365,678,679],{"class":401}," ws[",[365,681,682],{"class":375},"\"F5\"",[365,684,674],{"class":401},[365,686,514],{"class":397},[365,688,689],{"class":375}," \"yyyy-mm-dd\"\n",[365,691,693,695,698,700,703,706,709,712,715,717,720,722,725,727,730],{"class":367,"line":692},19,[365,694,614],{"class":397},[365,696,697],{"class":401}," col, label ",[365,699,620],{"class":397},[365,701,702],{"class":411}," zip",[365,704,705],{"class":401},"(",[365,707,708],{"class":375},"\"ADEF\"",[365,710,711],{"class":401},", [",[365,713,714],{"class":375},"\"Description\"",[365,716,597],{"class":401},[365,718,719],{"class":375},"\"Qty\"",[365,721,597],{"class":401},[365,723,724],{"class":375},"\"Unit price\"",[365,726,597],{"class":401},[365,728,729],{"class":375},"\"Amount\"",[365,731,732],{"class":401},"]):\n",[365,734,736,739,742,745,749,752,755,758,760,762],{"class":367,"line":735},20,[365,737,738],{"class":401},"        ws[",[365,740,741],{"class":397},"f",[365,743,744],{"class":375},"\"",[365,746,748],{"class":747},"sSjpA","{",[365,750,751],{"class":401},"col",[365,753,754],{"class":747},"}",[365,756,757],{"class":375},"11\"",[365,759,565],{"class":401},[365,761,514],{"class":397},[365,763,764],{"class":401}," label\n",[365,766,768,770,772,774,776,778,780,782,784,786,788,790,792,794],{"class":367,"line":767},21,[365,769,738],{"class":401},[365,771,741],{"class":397},[365,773,744],{"class":375},[365,775,748],{"class":747},[365,777,751],{"class":401},[365,779,754],{"class":747},[365,781,757],{"class":375},[365,783,580],{"class":401},[365,785,514],{"class":397},[365,787,585],{"class":401},[365,789,600],{"class":588},[365,791,514],{"class":397},[365,793,605],{"class":411},[365,795,608],{"class":401},[365,797,799,801,804,806,809,811,814,816,819],{"class":367,"line":798},22,[365,800,614],{"class":397},[365,802,803],{"class":401}," row ",[365,805,620],{"class":397},[365,807,808],{"class":411}," range",[365,810,705],{"class":401},[365,812,813],{"class":411},"12",[365,815,597],{"class":401},[365,817,818],{"class":411},"22",[365,820,520],{"class":401},[365,822,824,826,828,831,833,836,838,840,842,844,846,848,851,853,855,857,859,861,863],{"class":367,"line":823},23,[365,825,738],{"class":401},[365,827,741],{"class":397},[365,829,830],{"class":375},"\"E",[365,832,748],{"class":747},[365,834,835],{"class":401},"row",[365,837,754],{"class":747},[365,839,744],{"class":375},[365,841,674],{"class":401},[365,843,514],{"class":397},[365,845,679],{"class":401},[365,847,741],{"class":397},[365,849,850],{"class":375},"\"F",[365,852,748],{"class":747},[365,854,835],{"class":401},[365,856,754],{"class":747},[365,858,744],{"class":375},[365,860,674],{"class":401},[365,862,514],{"class":397},[365,864,865],{"class":375}," \"#,##0.00\"\n",[365,867,869,871,873,875,877,879,881,884,886,888,890,893,895,897,899,902],{"class":367,"line":868},24,[365,870,614],{"class":397},[365,872,617],{"class":401},[365,874,620],{"class":397},[365,876,623],{"class":401},[365,878,276],{"class":411},[365,880,597],{"class":401},[365,882,883],{"class":375},"\"Net\"",[365,885,633],{"class":401},[365,887,302],{"class":411},[365,889,597],{"class":401},[365,891,892],{"class":375},"\"VAT 20%\"",[365,894,633],{"class":401},[365,896,328],{"class":411},[365,898,597],{"class":401},[365,900,901],{"class":375},"\"Total due\"",[365,903,652],{"class":401},[365,905,907,909,911],{"class":367,"line":906},25,[365,908,658],{"class":401},[365,910,211],{"class":411},[365,912,663],{"class":401},[365,914,916,918,920,923,925],{"class":367,"line":915},26,[365,917,658],{"class":401},[365,919,246],{"class":411},[365,921,922],{"class":401},").number_format ",[365,924,514],{"class":397},[365,926,865],{"class":375},[365,928,930,932,935,937,939,941,943,945,947],{"class":367,"line":929},27,[365,931,559],{"class":401},[365,933,934],{"class":375},"\"F25\"",[365,936,580],{"class":401},[365,938,514],{"class":397},[365,940,585],{"class":401},[365,942,600],{"class":588},[365,944,514],{"class":397},[365,946,605],{"class":411},[365,948,608],{"class":401},[365,950,952,955,958,961,963],{"class":367,"line":951},28,[365,953,954],{"class":401},"    ws.column_dimensions[",[365,956,957],{"class":375},"\"A\"",[365,959,960],{"class":401},"].width ",[365,962,514],{"class":397},[365,964,965],{"class":411}," 34\n",[365,967,969,971,974,976,979],{"class":367,"line":968},29,[365,970,614],{"class":397},[365,972,973],{"class":401}," col ",[365,975,620],{"class":397},[365,977,978],{"class":375}," \"EF\"",[365,980,981],{"class":401},":\n",[365,983,985,988,990],{"class":367,"line":984},30,[365,986,987],{"class":401},"        ws.column_dimensions[col].width ",[365,989,514],{"class":397},[365,991,992],{"class":411}," 14\n",[365,994,996,999,1001,1004,1007,1010,1013,1015,1018,1020,1023,1025,1028,1030,1033],{"class":367,"line":995},31,[365,997,998],{"class":401},"    names ",[365,1000,514],{"class":397},[365,1002,1003],{"class":401}," {",[365,1005,1006],{"class":375},"\"inv_number\"",[365,1008,1009],{"class":401},": ",[365,1011,1012],{"class":375},"\"$F$3\"",[365,1014,597],{"class":401},[365,1016,1017],{"class":375},"\"inv_date\"",[365,1019,1009],{"class":401},[365,1021,1022],{"class":375},"\"$F$4\"",[365,1024,597],{"class":401},[365,1026,1027],{"class":375},"\"due_date\"",[365,1029,1009],{"class":401},[365,1031,1032],{"class":375},"\"$F$5\"",[365,1034,1035],{"class":401},",\n",[365,1037,1039,1042,1044,1047,1049,1052,1054,1057,1059,1062,1064,1067],{"class":367,"line":1038},32,[365,1040,1041],{"class":375},"             \"customer_name\"",[365,1043,1009],{"class":401},[365,1045,1046],{"class":375},"\"$A$7\"",[365,1048,597],{"class":401},[365,1050,1051],{"class":375},"\"customer_address\"",[365,1053,1009],{"class":401},[365,1055,1056],{"class":375},"\"$A$8\"",[365,1058,597],{"class":401},[365,1060,1061],{"class":375},"\"lines\"",[365,1063,1009],{"class":401},[365,1065,1066],{"class":375},"\"$A$12:$F$21\"",[365,1068,1035],{"class":401},[365,1070,1072,1075,1077,1080,1082,1085,1087,1090,1092,1095,1097,1100],{"class":367,"line":1071},33,[365,1073,1074],{"class":375},"             \"net_total\"",[365,1076,1009],{"class":401},[365,1078,1079],{"class":375},"\"$F$23\"",[365,1081,597],{"class":401},[365,1083,1084],{"class":375},"\"vat_total\"",[365,1086,1009],{"class":401},[365,1088,1089],{"class":375},"\"$F$24\"",[365,1091,597],{"class":401},[365,1093,1094],{"class":375},"\"gross_total\"",[365,1096,1009],{"class":401},[365,1098,1099],{"class":375},"\"$F$25\"",[365,1101,1102],{"class":401},"}\n",[365,1104,1106,1108,1111,1113],{"class":367,"line":1105},34,[365,1107,614],{"class":397},[365,1109,1110],{"class":401}," name, ref ",[365,1112,620],{"class":397},[365,1114,1115],{"class":401}," names.items():\n",[365,1117,1119,1122,1124,1127,1130,1132,1134,1137,1139,1142,1144,1146],{"class":367,"line":1118},35,[365,1120,1121],{"class":401},"        wb.defined_names[name] ",[365,1123,514],{"class":397},[365,1125,1126],{"class":401}," DefinedName(name, ",[365,1128,1129],{"class":588},"attr_text",[365,1131,514],{"class":397},[365,1133,741],{"class":397},[365,1135,1136],{"class":375},"\"Invoice!",[365,1138,748],{"class":747},[365,1140,1141],{"class":401},"ref",[365,1143,754],{"class":747},[365,1145,744],{"class":375},[365,1147,608],{"class":401},[365,1149,1151],{"class":367,"line":1150},36,[365,1152,1153],{"class":401},"    wb.save(path)\n",[365,1155,1157],{"class":367,"line":1156},37,[365,1158,454],{"emptyLinePlaceholder":453},[365,1160,1162],{"class":367,"line":1161},38,[365,1163,1164],{"class":401},"make_template()\n",[350,1166,1168],{"id":1167},"step-1-refer-to-cells-by-name","Step 1 — Refer to cells by name",[10,1170,1171,1172,1175],{},"Hard-coding ",[362,1173,1174],{},"\"F3\""," ties the code to today's layout; the first time someone adds a logo row, every invoice is filled one row off. Defined names move with the cells when the designer edits the template, so the code keeps working. A small helper resolves a name to its cell or range:",[355,1177,1179],{"className":388,"code":1178,"language":390,"meta":360,"style":360},"def named(wb, name):\n    sheet, ref = next(iter(wb.defined_names[name].destinations))\n    return wb[sheet][ref.replace(\"$\", \"\")]\n\nwb = load_workbook(\"invoice_template.xlsx\")\nprint(named(wb, \"inv_number\").coordinate, len(named(wb, \"lines\")))\n# F3 10\n",[362,1180,1181,1191,1209,1228,1232,1246,1269],{"__ignoreMap":360},[365,1182,1183,1185,1188],{"class":367,"line":368},[365,1184,504],{"class":397},[365,1186,1187],{"class":507}," named",[365,1189,1190],{"class":401},"(wb, name):\n",[365,1192,1193,1196,1198,1201,1203,1206],{"class":367,"line":415},[365,1194,1195],{"class":401},"    sheet, ref ",[365,1197,514],{"class":397},[365,1199,1200],{"class":411}," next",[365,1202,705],{"class":401},[365,1204,1205],{"class":411},"iter",[365,1207,1208],{"class":401},"(wb.defined_names[name].destinations))\n",[365,1210,1211,1214,1217,1220,1222,1225],{"class":367,"line":428},[365,1212,1213],{"class":397},"    return",[365,1215,1216],{"class":401}," wb[sheet][ref.replace(",[365,1218,1219],{"class":375},"\"$\"",[365,1221,597],{"class":401},[365,1223,1224],{"class":375},"\"\"",[365,1226,1227],{"class":401},")]\n",[365,1229,1230],{"class":367,"line":436},[365,1231,454],{"emptyLinePlaceholder":453},[365,1233,1234,1237,1239,1242,1244],{"class":367,"line":450},[365,1235,1236],{"class":401},"wb ",[365,1238,514],{"class":397},[365,1240,1241],{"class":401}," load_workbook(",[365,1243,517],{"class":375},[365,1245,608],{"class":401},[365,1247,1248,1251,1254,1256,1259,1262,1264,1266],{"class":367,"line":457},[365,1249,1250],{"class":411},"print",[365,1252,1253],{"class":401},"(named(wb, ",[365,1255,1006],{"class":375},[365,1257,1258],{"class":401},").coordinate, ",[365,1260,1261],{"class":411},"len",[365,1263,1253],{"class":401},[365,1265,1061],{"class":375},[365,1267,1268],{"class":401},")))\n",[365,1270,1271],{"class":367,"line":470},[365,1272,1274],{"class":1273},"s-wDw","# F3 10\n",[10,1276,1277,1278,1281,1282,1286],{},"A missing name raises ",[362,1279,1280],{},"KeyError"," with the name in it — much clearer than an invoice with the total written into an empty cell. The general technique is covered in ",[14,1283,1285],{"href":1284},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl\u002F","Fill an Excel Template with Python and openpyxl",".",[350,1288,1290],{"id":1289},"step-2-calculate-money-with-decimal-and-one-rounding-rule","Step 2 — Calculate money with Decimal and one rounding rule",[10,1292,1293,1294,1297,1298,1301],{},"Floats cannot represent most cent amounts exactly, and a sum of floats can end in ...99999. Use ",[362,1295,1296],{},"Decimal"," for every amount, round each line to cents with ",[362,1299,1300],{},"ROUND_HALF_UP",", and decide where VAT is rounded — per line or once on the net total — because the two can disagree:",[355,1303,1305],{"className":388,"code":1304,"language":390,"meta":360,"style":360},"CENT = Decimal(\"0.01\")\nVAT_RATE = Decimal(\"0.20\")\n\ndef money(value):\n    return Decimal(value).quantize(CENT, rounding=ROUND_HALF_UP)\n\nlines = [\n    {\"description\": \"Paper A4, box\", \"qty\": 3, \"unit\": Decimal(\"4.13\")},\n    {\"description\": \"Toner cartridge\", \"qty\": 1, \"unit\": Decimal(\"38.43\")},\n    {\"description\": \"Labels\", \"qty\": 7, \"unit\": Decimal(\"2.37\")},\n    {\"description\": \"Pens, 10 pack\", \"qty\": 2, \"unit\": Decimal(\"3.07\")},\n    {\"description\": \"Stapler\", \"qty\": 1, \"unit\": Decimal(\"7.99\")},\n]\nfor line in lines:\n    line[\"amount\"] = money(line[\"qty\"] * line[\"unit\"])\n\nnet = sum(line[\"amount\"] for line in lines)\nvat_on_total = money(net * VAT_RATE)\nvat_per_line = sum(money(line[\"amount\"] * VAT_RATE) for line in lines)\nprint(net, vat_on_total, vat_per_line)\n# 81.54 16.31 16.32\n",[362,1306,1307,1323,1337,1341,1351,1371,1375,1385,1421,1451,1481,1511,1541,1546,1559,1589,1593,1619,1636,1667,1674],{"__ignoreMap":360},[365,1308,1309,1312,1315,1318,1321],{"class":367,"line":368},[365,1310,1311],{"class":411},"CENT",[365,1313,1314],{"class":397}," =",[365,1316,1317],{"class":401}," Decimal(",[365,1319,1320],{"class":375},"\"0.01\"",[365,1322,608],{"class":401},[365,1324,1325,1328,1330,1332,1335],{"class":367,"line":415},[365,1326,1327],{"class":411},"VAT_RATE",[365,1329,1314],{"class":397},[365,1331,1317],{"class":401},[365,1333,1334],{"class":375},"\"0.20\"",[365,1336,608],{"class":401},[365,1338,1339],{"class":367,"line":428},[365,1340,454],{"emptyLinePlaceholder":453},[365,1342,1343,1345,1348],{"class":367,"line":436},[365,1344,504],{"class":397},[365,1346,1347],{"class":507}," money",[365,1349,1350],{"class":401},"(value):\n",[365,1352,1353,1355,1358,1360,1362,1365,1367,1369],{"class":367,"line":450},[365,1354,1213],{"class":397},[365,1356,1357],{"class":401}," Decimal(value).quantize(",[365,1359,1311],{"class":411},[365,1361,597],{"class":401},[365,1363,1364],{"class":588},"rounding",[365,1366,514],{"class":397},[365,1368,1300],{"class":411},[365,1370,608],{"class":401},[365,1372,1373],{"class":367,"line":457},[365,1374,454],{"emptyLinePlaceholder":453},[365,1376,1377,1380,1382],{"class":367,"line":470},[365,1378,1379],{"class":401},"lines ",[365,1381,514],{"class":397},[365,1383,1384],{"class":401}," [\n",[365,1386,1387,1390,1393,1395,1398,1400,1403,1405,1407,1409,1412,1415,1418],{"class":367,"line":483},[365,1388,1389],{"class":401},"    {",[365,1391,1392],{"class":375},"\"description\"",[365,1394,1009],{"class":401},[365,1396,1397],{"class":375},"\"Paper A4, box\"",[365,1399,597],{"class":401},[365,1401,1402],{"class":375},"\"qty\"",[365,1404,1009],{"class":401},[365,1406,139],{"class":411},[365,1408,597],{"class":401},[365,1410,1411],{"class":375},"\"unit\"",[365,1413,1414],{"class":401},": Decimal(",[365,1416,1417],{"class":375},"\"4.13\"",[365,1419,1420],{"class":401},")},\n",[365,1422,1423,1425,1427,1429,1432,1434,1436,1438,1440,1442,1444,1446,1449],{"class":367,"line":496},[365,1424,1389],{"class":401},[365,1426,1392],{"class":375},[365,1428,1009],{"class":401},[365,1430,1431],{"class":375},"\"Toner cartridge\"",[365,1433,597],{"class":401},[365,1435,1402],{"class":375},[365,1437,1009],{"class":401},[365,1439,106],{"class":411},[365,1441,597],{"class":401},[365,1443,1411],{"class":375},[365,1445,1414],{"class":401},[365,1447,1448],{"class":375},"\"38.43\"",[365,1450,1420],{"class":401},[365,1452,1453,1455,1457,1459,1462,1464,1466,1468,1470,1472,1474,1476,1479],{"class":367,"line":501},[365,1454,1389],{"class":401},[365,1456,1392],{"class":375},[365,1458,1009],{"class":401},[365,1460,1461],{"class":375},"\"Labels\"",[365,1463,597],{"class":401},[365,1465,1402],{"class":375},[365,1467,1009],{"class":401},[365,1469,193],{"class":411},[365,1471,597],{"class":401},[365,1473,1411],{"class":375},[365,1475,1414],{"class":401},[365,1477,1478],{"class":375},"\"2.37\"",[365,1480,1420],{"class":401},[365,1482,1483,1485,1487,1489,1492,1494,1496,1498,1500,1502,1504,1506,1509],{"class":367,"line":523},[365,1484,1389],{"class":401},[365,1486,1392],{"class":375},[365,1488,1009],{"class":401},[365,1490,1491],{"class":375},"\"Pens, 10 pack\"",[365,1493,597],{"class":401},[365,1495,1402],{"class":375},[365,1497,1009],{"class":401},[365,1499,133],{"class":411},[365,1501,597],{"class":401},[365,1503,1411],{"class":375},[365,1505,1414],{"class":401},[365,1507,1508],{"class":375},"\"3.07\"",[365,1510,1420],{"class":401},[365,1512,1513,1515,1517,1519,1522,1524,1526,1528,1530,1532,1534,1536,1539],{"class":367,"line":534},[365,1514,1389],{"class":401},[365,1516,1392],{"class":375},[365,1518,1009],{"class":401},[365,1520,1521],{"class":375},"\"Stapler\"",[365,1523,597],{"class":401},[365,1525,1402],{"class":375},[365,1527,1009],{"class":401},[365,1529,106],{"class":411},[365,1531,597],{"class":401},[365,1533,1411],{"class":375},[365,1535,1414],{"class":401},[365,1537,1538],{"class":375},"\"7.99\"",[365,1540,1420],{"class":401},[365,1542,1543],{"class":367,"line":545},[365,1544,1545],{"class":401},"]\n",[365,1547,1548,1551,1554,1556],{"class":367,"line":556},[365,1549,1550],{"class":397},"for",[365,1552,1553],{"class":401}," line ",[365,1555,620],{"class":397},[365,1557,1558],{"class":401}," lines:\n",[365,1560,1561,1564,1567,1569,1571,1574,1576,1578,1581,1584,1586],{"class":367,"line":573},[365,1562,1563],{"class":401},"    line[",[365,1565,1566],{"class":375},"\"amount\"",[365,1568,565],{"class":401},[365,1570,514],{"class":397},[365,1572,1573],{"class":401}," money(line[",[365,1575,1402],{"class":375},[365,1577,565],{"class":401},[365,1579,1580],{"class":397},"*",[365,1582,1583],{"class":401}," line[",[365,1585,1411],{"class":375},[365,1587,1588],{"class":401},"])\n",[365,1590,1591],{"class":367,"line":611},[365,1592,454],{"emptyLinePlaceholder":453},[365,1594,1595,1598,1600,1603,1606,1608,1610,1612,1614,1616],{"class":367,"line":655},[365,1596,1597],{"class":401},"net ",[365,1599,514],{"class":397},[365,1601,1602],{"class":411}," sum",[365,1604,1605],{"class":401},"(line[",[365,1607,1566],{"class":375},[365,1609,565],{"class":401},[365,1611,1550],{"class":397},[365,1613,1553],{"class":401},[365,1615,620],{"class":397},[365,1617,1618],{"class":401}," lines)\n",[365,1620,1621,1624,1626,1629,1631,1634],{"class":367,"line":666},[365,1622,1623],{"class":401},"vat_on_total ",[365,1625,514],{"class":397},[365,1627,1628],{"class":401}," money(net ",[365,1630,1580],{"class":397},[365,1632,1633],{"class":411}," VAT_RATE",[365,1635,608],{"class":401},[365,1637,1638,1641,1643,1645,1648,1650,1652,1654,1656,1659,1661,1663,1665],{"class":367,"line":692},[365,1639,1640],{"class":401},"vat_per_line ",[365,1642,514],{"class":397},[365,1644,1602],{"class":411},[365,1646,1647],{"class":401},"(money(line[",[365,1649,1566],{"class":375},[365,1651,565],{"class":401},[365,1653,1580],{"class":397},[365,1655,1633],{"class":411},[365,1657,1658],{"class":401},") ",[365,1660,1550],{"class":397},[365,1662,1553],{"class":401},[365,1664,620],{"class":397},[365,1666,1618],{"class":401},[365,1668,1669,1671],{"class":367,"line":735},[365,1670,1250],{"class":411},[365,1672,1673],{"class":401},"(net, vat_on_total, vat_per_line)\n",[365,1675,1676],{"class":367,"line":767},[365,1677,1678],{"class":1273},"# 81.54 16.31 16.32\n",[10,1680,1681,1682,1286],{},"One cent apart on a five-line invoice. Neither is wrong in general; what matters is using the same rule as the accounting system that receives the invoice, so the two never disagree. This guide rounds VAT on the total. Why Excel and Python round differently in the first place is covered in ",[14,1683,1685],{"href":1684},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas\u002F","Match Excel Rounding in pandas",[20,1687,29,1693,29,1696,29,1699,29,1703,29,1710,29,1721,29,1727,29,1732,29,1738,29,1745,29,1749,29,1754,29,1758,29,1763,29,1767,29,1770,29,1777,29,1782,29,1786,29,1790,29,1795,29,1798,29,1803,29,1806,29,1811],{"viewBox":1688,"role":23,"ariaLabelledBy":1689,"xmlns":27,"style":1692},"4 0 752 233",[1690,1691],"iv-vat-t","iv-vat-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1694,1695],{"id":1690},"Two VAT rules, one cent apart",[35,1697,1698],{"id":1691},"Five lines with a net total of 81.54. Rounding VAT on the net total gives 16.31. Rounding VAT on each line and adding the results gives 16.32, because each line's small rounding up adds together. Either rule is legitimate; the invoice must match the rule the accounting system uses.",[39,1700],{"x":168,"y":42,"width":1701,"height":1702,"fill":45},"752","233",[39,1704],{"x":1705,"y":49,"width":1706,"height":1707,"rx":1708,"fill":151,"stroke":152,"style":1709},"20.0","351.0","201.0","14","stroke-width:2px",[1711,1712,1714],"g",{"transform":1713},"translate(182.5,29.0) scale(1.083)",[1715,1716],"path",{"d":1717,"fill":1718,"stroke":1719,"style":1720},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","none","var(--teal-ink,#0b6157)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[60,1722,1726],{"x":1723,"y":1724,"style":1725},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","VAT on the net total",[367,1728],{"x1":1729,"y1":1730,"x2":1731,"y2":1730,"stroke":152,"style":54},"36.0","91.0","355.0",[60,1733,1737],{"x":1734,"y":1735,"style":1736},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","81.54 × 20% = 16.308",[1711,1739,1741],{"transform":1740},"translate(38.5,99.5) scale(0.625)",[1715,1742],{"d":1743,"fill":1718,"stroke":152,"style":1744},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[60,1746,1748],{"x":1734,"y":1747,"style":1736},"137.0","rounded once: 16.31",[1711,1750,1752],{"transform":1751},"translate(38.5,125.5) scale(0.625)",[1715,1753],{"d":1743,"fill":1718,"stroke":152,"style":1744},[60,1755,1757],{"x":1734,"y":1756,"style":1736},"163.0","one rounding step",[1711,1759,1761],{"transform":1760},"translate(38.5,151.5) scale(0.625)",[1715,1762],{"d":1743,"fill":1718,"stroke":152,"style":1744},[60,1764,1766],{"x":1734,"y":1765,"style":1736},"189.0","− line VAT not shown",[39,1768],{"x":1769,"y":49,"width":1706,"height":1707,"rx":1708,"fill":249,"stroke":250,"style":1709},"389.0",[1711,1771,1773],{"transform":1772},"translate(551.5,29.0) scale(1.083)",[1715,1774],{"d":1775,"fill":1718,"stroke":1776,"style":1720},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--gold-ink,#7a4e06)",[60,1778,1781],{"x":1779,"y":1724,"style":1780},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","VAT per line, then summed",[367,1783],{"x1":1784,"y1":1730,"x2":1785,"y2":1730,"stroke":250,"style":54},"405.0","724.0",[60,1787,1789],{"x":1788,"y":1735,"style":1736},"429.0","2.48 + 7.69 + 3.32 + 1.23 + 1.60",[1711,1791,1793],{"transform":1792},"translate(407.5,99.5) scale(0.625)",[1715,1794],{"d":1743,"fill":1718,"stroke":152,"style":1744},[60,1796,1797],{"x":1788,"y":1747,"style":1736},"total: 16.32",[1711,1799,1801],{"transform":1800},"translate(407.5,125.5) scale(0.625)",[1715,1802],{"d":1743,"fill":1718,"stroke":152,"style":1744},[60,1804,1805],{"x":1788,"y":1756,"style":1736},"each line shows its VAT",[1711,1807,1809],{"transform":1808},"translate(407.5,151.5) scale(0.625)",[1715,1810],{"d":1743,"fill":1718,"stroke":152,"style":1744},[60,1812,1813],{"x":1788,"y":1765,"style":1736},"− rounding errors add up",[350,1815,1817],{"id":1816},"step-3-fill-the-template-with-a-capacity-check","Step 3 — Fill the template, with a capacity check",[10,1819,1820],{},"The line block has ten rows. An order with twelve lines must not silently lose two of them, so check capacity before writing anything. Write the totals as values rather than formulas: openpyxl saves formulas without results, so previews and programs that read the file without recalculating would show empty totals:",[355,1822,1824],{"className":388,"code":1823,"language":390,"meta":360,"style":360},"def fill_invoice(template, invoice, out_path):\n    wb = load_workbook(template)\n    block = named(wb, \"lines\")\n    if len(invoice[\"lines\"]) > len(block):\n        raise ValueError(f\"{invoice['number']}: {len(invoice['lines'])} lines, template holds {len(block)}\")\n    named(wb, \"inv_number\").value = invoice[\"number\"]\n    named(wb, \"inv_date\").value = invoice[\"date\"]\n    named(wb, \"due_date\").value = invoice[\"date\"] + dt.timedelta(days=30)\n    named(wb, \"customer_name\").value = invoice[\"customer\"]\n    named(wb, \"customer_address\").value = invoice[\"address\"]\n    for row, line in zip(block, invoice[\"lines\"] + [None] * len(block)):\n        values = [None] * 6 if line is None else [\n            line[\"description\"], None, None, line[\"qty\"], line[\"unit\"], line[\"amount\"]]\n        for cell, value in zip(row, values):\n            cell.value = value                           # clears unused rows too\n    named(wb, \"net_total\").value = invoice[\"net\"]\n    named(wb, \"vat_total\").value = invoice[\"vat\"]\n    named(wb, \"gross_total\").value = invoice[\"net\"] + invoice[\"vat\"]\n    wb.save(out_path)\n",[362,1825,1826,1836,1845,1859,1883,1942,1962,1979,2011,2029,2046,2081,2115,2148,2163,2176,2194,2211,2235],{"__ignoreMap":360},[365,1827,1828,1830,1833],{"class":367,"line":368},[365,1829,504],{"class":397},[365,1831,1832],{"class":507}," fill_invoice",[365,1834,1835],{"class":401},"(template, invoice, out_path):\n",[365,1837,1838,1840,1842],{"class":367,"line":415},[365,1839,526],{"class":401},[365,1841,514],{"class":397},[365,1843,1844],{"class":401}," load_workbook(template)\n",[365,1846,1847,1850,1852,1855,1857],{"class":367,"line":428},[365,1848,1849],{"class":401},"    block ",[365,1851,514],{"class":397},[365,1853,1854],{"class":401}," named(wb, ",[365,1856,1061],{"class":375},[365,1858,608],{"class":401},[365,1860,1861,1864,1867,1870,1872,1875,1878,1880],{"class":367,"line":436},[365,1862,1863],{"class":397},"    if",[365,1865,1866],{"class":411}," len",[365,1868,1869],{"class":401},"(invoice[",[365,1871,1061],{"class":375},[365,1873,1874],{"class":401},"]) ",[365,1876,1877],{"class":397},">",[365,1879,1866],{"class":411},[365,1881,1882],{"class":401},"(block):\n",[365,1884,1885,1888,1891,1893,1895,1897,1899,1902,1905,1908,1910,1912,1914,1916,1918,1921,1924,1926,1929,1931,1933,1936,1938,1940],{"class":367,"line":450},[365,1886,1887],{"class":397},"        raise",[365,1889,1890],{"class":411}," ValueError",[365,1892,705],{"class":401},[365,1894,741],{"class":397},[365,1896,744],{"class":375},[365,1898,748],{"class":747},[365,1900,1901],{"class":401},"invoice[",[365,1903,1904],{"class":375},"'number'",[365,1906,1907],{"class":401},"]",[365,1909,754],{"class":747},[365,1911,1009],{"class":375},[365,1913,748],{"class":747},[365,1915,1261],{"class":411},[365,1917,1869],{"class":401},[365,1919,1920],{"class":375},"'lines'",[365,1922,1923],{"class":401},"])",[365,1925,754],{"class":747},[365,1927,1928],{"class":375}," lines, template holds ",[365,1930,748],{"class":747},[365,1932,1261],{"class":411},[365,1934,1935],{"class":401},"(block)",[365,1937,754],{"class":747},[365,1939,744],{"class":375},[365,1941,608],{"class":401},[365,1943,1944,1947,1949,1952,1954,1957,1960],{"class":367,"line":457},[365,1945,1946],{"class":401},"    named(wb, ",[365,1948,1006],{"class":375},[365,1950,1951],{"class":401},").value ",[365,1953,514],{"class":397},[365,1955,1956],{"class":401}," invoice[",[365,1958,1959],{"class":375},"\"number\"",[365,1961,1545],{"class":401},[365,1963,1964,1966,1968,1970,1972,1974,1977],{"class":367,"line":470},[365,1965,1946],{"class":401},[365,1967,1017],{"class":375},[365,1969,1951],{"class":401},[365,1971,514],{"class":397},[365,1973,1956],{"class":401},[365,1975,1976],{"class":375},"\"date\"",[365,1978,1545],{"class":401},[365,1980,1981,1983,1985,1987,1989,1991,1993,1995,1998,2001,2004,2006,2009],{"class":367,"line":483},[365,1982,1946],{"class":401},[365,1984,1027],{"class":375},[365,1986,1951],{"class":401},[365,1988,514],{"class":397},[365,1990,1956],{"class":401},[365,1992,1976],{"class":375},[365,1994,565],{"class":401},[365,1996,1997],{"class":397},"+",[365,1999,2000],{"class":401}," dt.timedelta(",[365,2002,2003],{"class":588},"days",[365,2005,514],{"class":397},[365,2007,2008],{"class":411},"30",[365,2010,608],{"class":401},[365,2012,2013,2015,2018,2020,2022,2024,2027],{"class":367,"line":496},[365,2014,1946],{"class":401},[365,2016,2017],{"class":375},"\"customer_name\"",[365,2019,1951],{"class":401},[365,2021,514],{"class":397},[365,2023,1956],{"class":401},[365,2025,2026],{"class":375},"\"customer\"",[365,2028,1545],{"class":401},[365,2030,2031,2033,2035,2037,2039,2041,2044],{"class":367,"line":501},[365,2032,1946],{"class":401},[365,2034,1051],{"class":375},[365,2036,1951],{"class":401},[365,2038,514],{"class":397},[365,2040,1956],{"class":401},[365,2042,2043],{"class":375},"\"address\"",[365,2045,1545],{"class":401},[365,2047,2048,2050,2053,2055,2057,2060,2062,2064,2066,2069,2072,2074,2076,2078],{"class":367,"line":523},[365,2049,614],{"class":397},[365,2051,2052],{"class":401}," row, line ",[365,2054,620],{"class":397},[365,2056,702],{"class":411},[365,2058,2059],{"class":401},"(block, invoice[",[365,2061,1061],{"class":375},[365,2063,565],{"class":401},[365,2065,1997],{"class":397},[365,2067,2068],{"class":401}," [",[365,2070,2071],{"class":411},"None",[365,2073,565],{"class":401},[365,2075,1580],{"class":397},[365,2077,1866],{"class":411},[365,2079,2080],{"class":401},"(block)):\n",[365,2082,2083,2086,2088,2090,2092,2094,2096,2099,2102,2104,2107,2110,2113],{"class":367,"line":534},[365,2084,2085],{"class":401},"        values ",[365,2087,514],{"class":397},[365,2089,2068],{"class":401},[365,2091,2071],{"class":411},[365,2093,565],{"class":401},[365,2095,1580],{"class":397},[365,2097,2098],{"class":411}," 6",[365,2100,2101],{"class":397}," if",[365,2103,1553],{"class":401},[365,2105,2106],{"class":397},"is",[365,2108,2109],{"class":411}," None",[365,2111,2112],{"class":397}," else",[365,2114,1384],{"class":401},[365,2116,2117,2120,2122,2125,2127,2129,2131,2134,2136,2139,2141,2143,2145],{"class":367,"line":545},[365,2118,2119],{"class":401},"            line[",[365,2121,1392],{"class":375},[365,2123,2124],{"class":401},"], ",[365,2126,2071],{"class":411},[365,2128,597],{"class":401},[365,2130,2071],{"class":411},[365,2132,2133],{"class":401},", line[",[365,2135,1402],{"class":375},[365,2137,2138],{"class":401},"], line[",[365,2140,1411],{"class":375},[365,2142,2138],{"class":401},[365,2144,1566],{"class":375},[365,2146,2147],{"class":401},"]]\n",[365,2149,2150,2153,2156,2158,2160],{"class":367,"line":556},[365,2151,2152],{"class":397},"        for",[365,2154,2155],{"class":401}," cell, value ",[365,2157,620],{"class":397},[365,2159,702],{"class":411},[365,2161,2162],{"class":401},"(row, values):\n",[365,2164,2165,2168,2170,2173],{"class":367,"line":573},[365,2166,2167],{"class":401},"            cell.value ",[365,2169,514],{"class":397},[365,2171,2172],{"class":401}," value                           ",[365,2174,2175],{"class":1273},"# clears unused rows too\n",[365,2177,2178,2180,2183,2185,2187,2189,2192],{"class":367,"line":611},[365,2179,1946],{"class":401},[365,2181,2182],{"class":375},"\"net_total\"",[365,2184,1951],{"class":401},[365,2186,514],{"class":397},[365,2188,1956],{"class":401},[365,2190,2191],{"class":375},"\"net\"",[365,2193,1545],{"class":401},[365,2195,2196,2198,2200,2202,2204,2206,2209],{"class":367,"line":655},[365,2197,1946],{"class":401},[365,2199,1084],{"class":375},[365,2201,1951],{"class":401},[365,2203,514],{"class":397},[365,2205,1956],{"class":401},[365,2207,2208],{"class":375},"\"vat\"",[365,2210,1545],{"class":401},[365,2212,2213,2215,2217,2219,2221,2223,2225,2227,2229,2231,2233],{"class":367,"line":666},[365,2214,1946],{"class":401},[365,2216,1094],{"class":375},[365,2218,1951],{"class":401},[365,2220,514],{"class":397},[365,2222,1956],{"class":401},[365,2224,2191],{"class":375},[365,2226,565],{"class":401},[365,2228,1997],{"class":397},[365,2230,1956],{"class":401},[365,2232,2208],{"class":375},[365,2234,1545],{"class":401},[365,2236,2237],{"class":367,"line":692},[365,2238,2239],{"class":401},"    wb.save(out_path)\n",[10,2241,2242,2243,2245,2246,2249,2250,2254],{},"openpyxl accepts ",[362,2244,1296],{}," values and stores them as numbers, so the cells keep their ",[362,2247,2248],{},"#,##0.00"," format and can be summed in Excel. When orders legitimately exceed the block, add a continuation sheet or a longer template variant rather than inserting rows, which openpyxl does not do cleanly; ",[14,2251,2253],{"href":2252},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Freplace-placeholders-in-an-excel-template-with-python\u002F","Replace Placeholders in an Excel Template with Python"," shows how to expand a repeating row when you must.",[350,2256,2258],{"id":2257},"step-4-number-invoices-from-a-register","Step 4 — Number invoices from a register",[10,2260,2261],{},"Invoice numbers must be unique and, in many jurisdictions, sequential without gaps. Keep a register of every invoice issued and derive the next number from it. Write the invoice first, then append it to the register, and never overwrite an existing invoice file — if the run dies between the two steps, the next run stops at the existing file instead of issuing a duplicate:",[355,2263,2265],{"className":388,"code":2264,"language":390,"meta":360,"style":360},"REGISTER = Path(\"invoice_register.csv\")\nFIELDS = [\"number\", \"date\", \"customer\", \"net\", \"vat\", \"gross\", \"file\"]\n\ndef next_number(prefix=\"INV-\", first=1041):\n    if not REGISTER.exists():\n        return f\"{prefix}{first:06d}\"\n    with REGISTER.open(newline=\"\") as f:\n        last = max(int(row[\"number\"].removeprefix(prefix)) for row in csv.DictReader(f))\n    return f\"{prefix}{last + 1:06d}\"\n\ndef issue(customer, address, lines, date, out_dir=Path(\"invoices\")):\n    out_dir.mkdir(exist_ok=True)\n    net = sum(line[\"amount\"] for line in lines)\n    invoice = {\"number\": next_number(), \"date\": date, \"customer\": customer,\n               \"address\": address, \"lines\": lines, \"net\": net, \"vat\": money(net * VAT_RATE)}\n    out = out_dir \u002F f\"{invoice['number']}.xlsx\"\n    if out.exists():\n        raise FileExistsError(f\"{out} exists but is not registered; check before continuing\")\n    fill_invoice(\"invoice_template.xlsx\", invoice, out)\n    new = not REGISTER.exists()\n    with REGISTER.open(\"a\", newline=\"\") as f:\n        writer = csv.DictWriter(f, FIELDS)\n        if new:\n            writer.writeheader()\n        writer.writerow({\"number\": invoice[\"number\"], \"date\": date.isoformat(), \"customer\": customer,\n                         \"net\": net, \"vat\": invoice[\"vat\"], \"gross\": net + invoice[\"vat\"], \"file\": out.name})\n    return out\n\nprint(issue(\"Harbour Stationers Ltd\", \"12 Quay Street, Bristol\", lines, dt.date(2026, 9, 30)))\nprint(issue(\"Northfield Primary School\", \"Mill Lane, Leeds\", lines[:2], dt.date(2026, 9, 30)))\n# invoices\u002FINV-001041.xlsx\n# invoices\u002FINV-001042.xlsx\n",[362,2266,2267,2282,2321,2325,2350,2363,2392,2416,2448,2476,2480,2501,2515,2538,2562,2592,2622,2629,2654,2664,2678,2703,2717,2725,2730,2753,2786,2793,2797,2828,2862,2867],{"__ignoreMap":360},[365,2268,2269,2272,2274,2277,2280],{"class":367,"line":368},[365,2270,2271],{"class":411},"REGISTER",[365,2273,1314],{"class":397},[365,2275,2276],{"class":401}," Path(",[365,2278,2279],{"class":375},"\"invoice_register.csv\"",[365,2281,608],{"class":401},[365,2283,2284,2287,2289,2291,2293,2295,2297,2299,2301,2303,2305,2307,2309,2311,2314,2316,2319],{"class":367,"line":415},[365,2285,2286],{"class":411},"FIELDS",[365,2288,1314],{"class":397},[365,2290,2068],{"class":401},[365,2292,1959],{"class":375},[365,2294,597],{"class":401},[365,2296,1976],{"class":375},[365,2298,597],{"class":401},[365,2300,2026],{"class":375},[365,2302,597],{"class":401},[365,2304,2191],{"class":375},[365,2306,597],{"class":401},[365,2308,2208],{"class":375},[365,2310,597],{"class":401},[365,2312,2313],{"class":375},"\"gross\"",[365,2315,597],{"class":401},[365,2317,2318],{"class":375},"\"file\"",[365,2320,1545],{"class":401},[365,2322,2323],{"class":367,"line":428},[365,2324,454],{"emptyLinePlaceholder":453},[365,2326,2327,2329,2332,2335,2337,2340,2343,2345,2348],{"class":367,"line":436},[365,2328,504],{"class":397},[365,2330,2331],{"class":507}," next_number",[365,2333,2334],{"class":401},"(prefix",[365,2336,514],{"class":397},[365,2338,2339],{"class":375},"\"INV-\"",[365,2341,2342],{"class":401},", first",[365,2344,514],{"class":397},[365,2346,2347],{"class":411},"1041",[365,2349,520],{"class":401},[365,2351,2352,2354,2357,2360],{"class":367,"line":450},[365,2353,1863],{"class":397},[365,2355,2356],{"class":397}," not",[365,2358,2359],{"class":411}," REGISTER",[365,2361,2362],{"class":401},".exists():\n",[365,2364,2365,2368,2371,2373,2375,2378,2381,2384,2387,2389],{"class":367,"line":457},[365,2366,2367],{"class":397},"        return",[365,2369,2370],{"class":397}," f",[365,2372,744],{"class":375},[365,2374,748],{"class":747},[365,2376,2377],{"class":401},"prefix",[365,2379,2380],{"class":747},"}{",[365,2382,2383],{"class":401},"first",[365,2385,2386],{"class":397},":06d",[365,2388,754],{"class":747},[365,2390,2391],{"class":375},"\"\n",[365,2393,2394,2397,2399,2402,2405,2407,2409,2411,2413],{"class":367,"line":470},[365,2395,2396],{"class":397},"    with",[365,2398,2359],{"class":411},[365,2400,2401],{"class":401},".open(",[365,2403,2404],{"class":588},"newline",[365,2406,514],{"class":397},[365,2408,1224],{"class":375},[365,2410,1658],{"class":401},[365,2412,444],{"class":397},[365,2414,2415],{"class":401}," f:\n",[365,2417,2418,2421,2423,2426,2428,2431,2434,2436,2439,2441,2443,2445],{"class":367,"line":483},[365,2419,2420],{"class":401},"        last ",[365,2422,514],{"class":397},[365,2424,2425],{"class":411}," max",[365,2427,705],{"class":401},[365,2429,2430],{"class":411},"int",[365,2432,2433],{"class":401},"(row[",[365,2435,1959],{"class":375},[365,2437,2438],{"class":401},"].removeprefix(prefix)) ",[365,2440,1550],{"class":397},[365,2442,803],{"class":401},[365,2444,620],{"class":397},[365,2446,2447],{"class":401}," csv.DictReader(f))\n",[365,2449,2450,2452,2454,2456,2458,2460,2462,2465,2467,2470,2472,2474],{"class":367,"line":496},[365,2451,1213],{"class":397},[365,2453,2370],{"class":397},[365,2455,744],{"class":375},[365,2457,748],{"class":747},[365,2459,2377],{"class":401},[365,2461,2380],{"class":747},[365,2463,2464],{"class":401},"last ",[365,2466,1997],{"class":397},[365,2468,2469],{"class":411}," 1",[365,2471,2386],{"class":397},[365,2473,754],{"class":747},[365,2475,2391],{"class":375},[365,2477,2478],{"class":367,"line":501},[365,2479,454],{"emptyLinePlaceholder":453},[365,2481,2482,2484,2487,2490,2492,2495,2498],{"class":367,"line":523},[365,2483,504],{"class":397},[365,2485,2486],{"class":507}," issue",[365,2488,2489],{"class":401},"(customer, address, lines, date, out_dir",[365,2491,514],{"class":397},[365,2493,2494],{"class":401},"Path(",[365,2496,2497],{"class":375},"\"invoices\"",[365,2499,2500],{"class":401},")):\n",[365,2502,2503,2506,2509,2511,2513],{"class":367,"line":534},[365,2504,2505],{"class":401},"    out_dir.mkdir(",[365,2507,2508],{"class":588},"exist_ok",[365,2510,514],{"class":397},[365,2512,605],{"class":411},[365,2514,608],{"class":401},[365,2516,2517,2520,2522,2524,2526,2528,2530,2532,2534,2536],{"class":367,"line":545},[365,2518,2519],{"class":401},"    net ",[365,2521,514],{"class":397},[365,2523,1602],{"class":411},[365,2525,1605],{"class":401},[365,2527,1566],{"class":375},[365,2529,565],{"class":401},[365,2531,1550],{"class":397},[365,2533,1553],{"class":401},[365,2535,620],{"class":397},[365,2537,1618],{"class":401},[365,2539,2540,2543,2545,2547,2549,2552,2554,2557,2559],{"class":367,"line":556},[365,2541,2542],{"class":401},"    invoice ",[365,2544,514],{"class":397},[365,2546,1003],{"class":401},[365,2548,1959],{"class":375},[365,2550,2551],{"class":401},": next_number(), ",[365,2553,1976],{"class":375},[365,2555,2556],{"class":401},": date, ",[365,2558,2026],{"class":375},[365,2560,2561],{"class":401},": customer,\n",[365,2563,2564,2567,2570,2572,2575,2577,2580,2582,2585,2587,2589],{"class":367,"line":573},[365,2565,2566],{"class":375},"               \"address\"",[365,2568,2569],{"class":401},": address, ",[365,2571,1061],{"class":375},[365,2573,2574],{"class":401},": lines, ",[365,2576,2191],{"class":375},[365,2578,2579],{"class":401},": net, ",[365,2581,2208],{"class":375},[365,2583,2584],{"class":401},": money(net ",[365,2586,1580],{"class":397},[365,2588,1633],{"class":411},[365,2590,2591],{"class":401},")}\n",[365,2593,2594,2597,2599,2602,2605,2607,2609,2611,2613,2615,2617,2619],{"class":367,"line":611},[365,2595,2596],{"class":401},"    out ",[365,2598,514],{"class":397},[365,2600,2601],{"class":401}," out_dir ",[365,2603,2604],{"class":397},"\u002F",[365,2606,2370],{"class":397},[365,2608,744],{"class":375},[365,2610,748],{"class":747},[365,2612,1901],{"class":401},[365,2614,1904],{"class":375},[365,2616,1907],{"class":401},[365,2618,754],{"class":747},[365,2620,2621],{"class":375},".xlsx\"\n",[365,2623,2624,2626],{"class":367,"line":655},[365,2625,1863],{"class":397},[365,2627,2628],{"class":401}," out.exists():\n",[365,2630,2631,2633,2636,2638,2640,2642,2644,2647,2649,2652],{"class":367,"line":666},[365,2632,1887],{"class":397},[365,2634,2635],{"class":411}," FileExistsError",[365,2637,705],{"class":401},[365,2639,741],{"class":397},[365,2641,744],{"class":375},[365,2643,748],{"class":747},[365,2645,2646],{"class":401},"out",[365,2648,754],{"class":747},[365,2650,2651],{"class":375}," exists but is not registered; check before continuing\"",[365,2653,608],{"class":401},[365,2655,2656,2659,2661],{"class":367,"line":692},[365,2657,2658],{"class":401},"    fill_invoice(",[365,2660,517],{"class":375},[365,2662,2663],{"class":401},", invoice, out)\n",[365,2665,2666,2669,2671,2673,2675],{"class":367,"line":735},[365,2667,2668],{"class":401},"    new ",[365,2670,514],{"class":397},[365,2672,2356],{"class":397},[365,2674,2359],{"class":411},[365,2676,2677],{"class":401},".exists()\n",[365,2679,2680,2682,2684,2686,2689,2691,2693,2695,2697,2699,2701],{"class":367,"line":767},[365,2681,2396],{"class":397},[365,2683,2359],{"class":411},[365,2685,2401],{"class":401},[365,2687,2688],{"class":375},"\"a\"",[365,2690,597],{"class":401},[365,2692,2404],{"class":588},[365,2694,514],{"class":397},[365,2696,1224],{"class":375},[365,2698,1658],{"class":401},[365,2700,444],{"class":397},[365,2702,2415],{"class":401},[365,2704,2705,2708,2710,2713,2715],{"class":367,"line":798},[365,2706,2707],{"class":401},"        writer ",[365,2709,514],{"class":397},[365,2711,2712],{"class":401}," csv.DictWriter(f, ",[365,2714,2286],{"class":411},[365,2716,608],{"class":401},[365,2718,2719,2722],{"class":367,"line":823},[365,2720,2721],{"class":397},"        if",[365,2723,2724],{"class":401}," new:\n",[365,2726,2727],{"class":367,"line":868},[365,2728,2729],{"class":401},"            writer.writeheader()\n",[365,2731,2732,2735,2737,2740,2742,2744,2746,2749,2751],{"class":367,"line":906},[365,2733,2734],{"class":401},"        writer.writerow({",[365,2736,1959],{"class":375},[365,2738,2739],{"class":401},": invoice[",[365,2741,1959],{"class":375},[365,2743,2124],{"class":401},[365,2745,1976],{"class":375},[365,2747,2748],{"class":401},": date.isoformat(), ",[365,2750,2026],{"class":375},[365,2752,2561],{"class":401},[365,2754,2755,2758,2760,2762,2764,2766,2768,2770,2773,2775,2777,2779,2781,2783],{"class":367,"line":915},[365,2756,2757],{"class":375},"                         \"net\"",[365,2759,2579],{"class":401},[365,2761,2208],{"class":375},[365,2763,2739],{"class":401},[365,2765,2208],{"class":375},[365,2767,2124],{"class":401},[365,2769,2313],{"class":375},[365,2771,2772],{"class":401},": net ",[365,2774,1997],{"class":397},[365,2776,1956],{"class":401},[365,2778,2208],{"class":375},[365,2780,2124],{"class":401},[365,2782,2318],{"class":375},[365,2784,2785],{"class":401},": out.name})\n",[365,2787,2788,2790],{"class":367,"line":929},[365,2789,1213],{"class":397},[365,2791,2792],{"class":401}," out\n",[365,2794,2795],{"class":367,"line":951},[365,2796,454],{"emptyLinePlaceholder":453},[365,2798,2799,2801,2804,2807,2809,2812,2815,2818,2820,2822,2824,2826],{"class":367,"line":968},[365,2800,1250],{"class":411},[365,2802,2803],{"class":401},"(issue(",[365,2805,2806],{"class":375},"\"Harbour Stationers Ltd\"",[365,2808,597],{"class":401},[365,2810,2811],{"class":375},"\"12 Quay Street, Bristol\"",[365,2813,2814],{"class":401},", lines, dt.date(",[365,2816,2817],{"class":411},"2026",[365,2819,597],{"class":401},[365,2821,323],{"class":411},[365,2823,597],{"class":401},[365,2825,2008],{"class":411},[365,2827,1268],{"class":401},[365,2829,2830,2832,2834,2837,2839,2842,2845,2847,2850,2852,2854,2856,2858,2860],{"class":367,"line":984},[365,2831,1250],{"class":411},[365,2833,2803],{"class":401},[365,2835,2836],{"class":375},"\"Northfield Primary School\"",[365,2838,597],{"class":401},[365,2840,2841],{"class":375},"\"Mill Lane, Leeds\"",[365,2843,2844],{"class":401},", lines[:",[365,2846,133],{"class":411},[365,2848,2849],{"class":401},"], dt.date(",[365,2851,2817],{"class":411},[365,2853,597],{"class":401},[365,2855,323],{"class":411},[365,2857,597],{"class":401},[365,2859,2008],{"class":411},[365,2861,1268],{"class":401},[365,2863,2864],{"class":367,"line":995},[365,2865,2866],{"class":1273},"# invoices\u002FINV-001041.xlsx\n",[365,2868,2869],{"class":367,"line":1038},[365,2870,2871],{"class":1273},"# invoices\u002FINV-001042.xlsx\n",[10,2873,2874],{},"Run the batch from one process at a time; two runs reading the register simultaneously would pick the same number. The register doubles as the control total for the batch: its gross column should add up to what the accounting export expects.",[20,2876,29,2881,29,2884,29,2887,29,2890,29,2894,29,2901,29,2907,29,2912,29,2916,29,2921,29,2926,29,2929,29,2935,29,2938,29,2942,29,2945,29,2949,29,2952,29,2955,29,2961,29,2965,29,2968,29,2971,29,2975,29,2978,29,2981,29,2987,29,2992,29,2996,29,3000,29,3003,29,3006,29,3011,29,3016],{"viewBox":2877,"role":23,"ariaLabelledBy":2878,"xmlns":27,"style":1692},"4 0 752 142",[2879,2880],"iv-flow-t","iv-flow-d",[31,2882,2883],{"id":2879},"Issuing one invoice",[35,2885,2886],{"id":2880},"The next number comes from the register of issued invoices. Line amounts and VAT are calculated with Decimal. The template is filled through its defined names after a capacity check. The workbook is saved under the invoice number, refusing to overwrite, and only then is the invoice appended to the register. PDFs are produced for the whole batch at the end.",[39,2888],{"x":168,"y":42,"width":1701,"height":2889,"fill":45},"142",[39,2891],{"x":1705,"y":49,"width":2892,"height":2893,"rx":813,"fill":214,"stroke":215,"style":1709},"115.2","110.5",[1711,2895,2897],{"transform":2896},"translate(65.6,29.0) scale(1.000)",[1715,2898],{"d":1717,"fill":1718,"stroke":2899,"style":2900},"var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[60,2902,2906],{"x":2903,"y":2904,"style":2905},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Next number",[60,2908,2911],{"x":2903,"y":2909,"style":2910},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","from the",[60,2913,2915],{"x":2903,"y":2914,"style":2910},"112.5","register",[367,2917],{"x1":2918,"y1":2919,"x2":2920,"y2":2919,"stroke":215,"style":1709},"140.2","71.2","162.2",[2922,2923],"polygon",{"points":2924,"fill":2925},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[39,2927],{"x":2928,"y":49,"width":2892,"height":2893,"rx":813,"fill":214,"stroke":215,"style":1709},"171.2",[1711,2930,2932],{"transform":2931},"translate(216.8,29.0) scale(1.000)",[1715,2933],{"d":2934,"fill":1718,"stroke":2899,"style":2900},"M9 8l-4 4 4 4 M15 8l4 4-4 4",[60,2936,1296],{"x":2937,"y":2904,"style":2905},"228.8",[60,2939,2941],{"x":2937,"y":2940,"style":2905},"93.0","amounts",[60,2943,2944],{"x":2937,"y":2914,"style":2910},"one VAT rule",[367,2946],{"x1":2947,"y1":2919,"x2":2948,"y2":2919,"stroke":215,"style":1709},"291.4","313.4",[2922,2950],{"points":2951,"fill":2925},"317.4,71.2 308.4,76.2 308.4,66.2",[39,2953],{"x":2954,"y":49,"width":2892,"height":2893,"rx":813,"fill":249,"stroke":250,"style":1709},"322.4",[1711,2956,2958],{"transform":2957},"translate(368.0,29.0) scale(1.000)",[1715,2959],{"d":2960,"fill":1718,"stroke":1776,"style":2900},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[60,2962,2964],{"x":345,"y":2904,"style":2963},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Fill by name",[60,2966,2967],{"x":345,"y":2909,"style":2910},"capacity",[60,2969,2970],{"x":345,"y":2914,"style":2910},"checked",[367,2972],{"x1":2973,"y1":2919,"x2":2974,"y2":2919,"stroke":215,"style":1709},"442.6","464.6",[2922,2976],{"points":2977,"fill":2925},"468.6,71.2 459.6,76.2 459.6,66.2",[39,2979],{"x":2980,"y":49,"width":2892,"height":2893,"rx":813,"fill":214,"stroke":215,"style":1709},"473.6",[1711,2982,2984],{"transform":2983},"translate(519.2,37.8) scale(1.000)",[1715,2985],{"d":2986,"fill":1718,"stroke":2899,"style":2900},"M6 3h9l4 4v14H6z M15 3v4h4",[60,2988,2991],{"x":2989,"y":2990,"style":2905},"531.2","84.2","Save",[60,2993,2995],{"x":2989,"y":2994,"style":2910},"103.8","never overwrite",[367,2997],{"x1":2998,"y1":2919,"x2":2999,"y2":2919,"stroke":215,"style":1709},"593.8","615.8",[2922,3001],{"points":3002,"fill":2925},"619.8,71.2 610.8,76.2 610.8,66.2",[39,3004],{"x":3005,"y":49,"width":2892,"height":2893,"rx":813,"fill":151,"stroke":152,"style":1709},"624.8",[1711,3007,3009],{"transform":3008},"translate(670.4,37.8) scale(1.000)",[1715,3010],{"d":1743,"fill":1718,"stroke":1719,"style":2900},[60,3012,3015],{"x":3013,"y":2990,"style":3014},"682.4","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Register",[60,3017,3018],{"x":3013,"y":2994,"style":2910},"then PDF batch",[350,3020,3022],{"id":3021},"step-5-check-each-invoice-and-export-pdfs","Step 5 — Check each invoice and export PDFs",[10,3024,3025],{},"Read every invoice back and check that its lines add up to its net total before anything is sent. Then convert the batch to PDF in a single LibreOffice call:",[355,3027,3029],{"className":388,"code":3028,"language":390,"meta":360,"style":360},"def check_invoice(path):\n    wb = load_workbook(path)\n    amounts = [row[5].value for row in named(wb, \"lines\") if row[5].value is not None]\n    net = named(wb, \"net_total\").value\n    assert money(sum(Decimal(str(a)) for a in amounts)) == money(Decimal(str(net))), path\n\nfor path in sorted(Path(\"invoices\").glob(\"*.xlsx\")):\n    check_invoice(path)\nprint(\"all invoices add up\")\n",[362,3030,3031,3041,3050,3095,3108,3149,3153,3178,3183],{"__ignoreMap":360},[365,3032,3033,3035,3038],{"class":367,"line":368},[365,3034,504],{"class":397},[365,3036,3037],{"class":507}," check_invoice",[365,3039,3040],{"class":401},"(path):\n",[365,3042,3043,3045,3047],{"class":367,"line":415},[365,3044,526],{"class":401},[365,3046,514],{"class":397},[365,3048,3049],{"class":401}," load_workbook(path)\n",[365,3051,3052,3055,3057,3060,3062,3065,3067,3069,3071,3073,3075,3077,3080,3083,3085,3087,3089,3091,3093],{"class":367,"line":428},[365,3053,3054],{"class":401},"    amounts ",[365,3056,514],{"class":397},[365,3058,3059],{"class":401}," [row[",[365,3061,211],{"class":411},[365,3063,3064],{"class":401},"].value ",[365,3066,1550],{"class":397},[365,3068,803],{"class":401},[365,3070,620],{"class":397},[365,3072,1854],{"class":401},[365,3074,1061],{"class":375},[365,3076,1658],{"class":401},[365,3078,3079],{"class":397},"if",[365,3081,3082],{"class":401}," row[",[365,3084,211],{"class":411},[365,3086,3064],{"class":401},[365,3088,2106],{"class":397},[365,3090,2356],{"class":397},[365,3092,2109],{"class":411},[365,3094,1545],{"class":401},[365,3096,3097,3099,3101,3103,3105],{"class":367,"line":436},[365,3098,2519],{"class":401},[365,3100,514],{"class":397},[365,3102,1854],{"class":401},[365,3104,2182],{"class":375},[365,3106,3107],{"class":401},").value\n",[365,3109,3110,3113,3116,3119,3122,3125,3128,3130,3133,3135,3138,3141,3144,3146],{"class":367,"line":450},[365,3111,3112],{"class":397},"    assert",[365,3114,3115],{"class":401}," money(",[365,3117,3118],{"class":411},"sum",[365,3120,3121],{"class":401},"(Decimal(",[365,3123,3124],{"class":411},"str",[365,3126,3127],{"class":401},"(a)) ",[365,3129,1550],{"class":397},[365,3131,3132],{"class":401}," a ",[365,3134,620],{"class":397},[365,3136,3137],{"class":401}," amounts)) ",[365,3139,3140],{"class":397},"==",[365,3142,3143],{"class":401}," money(Decimal(",[365,3145,3124],{"class":411},[365,3147,3148],{"class":401},"(net))), path\n",[365,3150,3151],{"class":367,"line":457},[365,3152,454],{"emptyLinePlaceholder":453},[365,3154,3155,3157,3160,3162,3165,3168,3170,3173,3176],{"class":367,"line":470},[365,3156,1550],{"class":397},[365,3158,3159],{"class":401}," path ",[365,3161,620],{"class":397},[365,3163,3164],{"class":411}," sorted",[365,3166,3167],{"class":401},"(Path(",[365,3169,2497],{"class":375},[365,3171,3172],{"class":401},").glob(",[365,3174,3175],{"class":375},"\"*.xlsx\"",[365,3177,2500],{"class":401},[365,3179,3180],{"class":367,"line":483},[365,3181,3182],{"class":401},"    check_invoice(path)\n",[365,3184,3185,3187,3189,3192],{"class":367,"line":496},[365,3186,1250],{"class":411},[365,3188,705],{"class":401},[365,3190,3191],{"class":375},"\"all invoices add up\"",[365,3193,608],{"class":401},[355,3195,3197],{"className":388,"code":3196,"language":390,"meta":360,"style":360},"import subprocess\n\nfiles = sorted(str(p) for p in Path(\"invoices\").glob(\"*.xlsx\"))\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"pdf\", \"--outdir\", \"invoices\"] + files,\n               check=True, timeout=600)\n",[362,3198,3199,3206,3210,3244,3283],{"__ignoreMap":360},[365,3200,3201,3203],{"class":367,"line":368},[365,3202,405],{"class":397},[365,3204,3205],{"class":401}," subprocess\n",[365,3207,3208],{"class":367,"line":415},[365,3209,454],{"emptyLinePlaceholder":453},[365,3211,3212,3215,3217,3219,3221,3223,3226,3228,3231,3233,3235,3237,3239,3241],{"class":367,"line":428},[365,3213,3214],{"class":401},"files ",[365,3216,514],{"class":397},[365,3218,3164],{"class":411},[365,3220,705],{"class":401},[365,3222,3124],{"class":411},[365,3224,3225],{"class":401},"(p) ",[365,3227,1550],{"class":397},[365,3229,3230],{"class":401}," p ",[365,3232,620],{"class":397},[365,3234,2276],{"class":401},[365,3236,2497],{"class":375},[365,3238,3172],{"class":401},[365,3240,3175],{"class":375},[365,3242,3243],{"class":401},"))\n",[365,3245,3246,3249,3252,3254,3257,3259,3262,3264,3267,3269,3272,3274,3276,3278,3280],{"class":367,"line":436},[365,3247,3248],{"class":401},"subprocess.run([",[365,3250,3251],{"class":375},"\"soffice\"",[365,3253,597],{"class":401},[365,3255,3256],{"class":375},"\"--headless\"",[365,3258,597],{"class":401},[365,3260,3261],{"class":375},"\"--convert-to\"",[365,3263,597],{"class":401},[365,3265,3266],{"class":375},"\"pdf\"",[365,3268,597],{"class":401},[365,3270,3271],{"class":375},"\"--outdir\"",[365,3273,597],{"class":401},[365,3275,2497],{"class":375},[365,3277,565],{"class":401},[365,3279,1997],{"class":397},[365,3281,3282],{"class":401}," files,\n",[365,3284,3285,3288,3290,3292,3294,3297,3299,3302],{"class":367,"line":450},[365,3286,3287],{"class":588},"               check",[365,3289,514],{"class":397},[365,3291,605],{"class":411},[365,3293,597],{"class":401},[365,3295,3296],{"class":588},"timeout",[365,3298,514],{"class":397},[365,3300,3301],{"class":411},"600",[365,3303,608],{"class":401},[10,3305,3306,3307,3309,3310,3312,3313,3317],{},"Values read back as floats, so convert through ",[362,3308,3124],{}," before comparing as ",[362,3311,1296],{},". PDF page setup — margins, fitting to one page — lives in the template, and ",[14,3314,3316],{"href":3315},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Ffit-an-excel-sheet-to-one-page-in-pdf\u002F","Fit an Excel Sheet to One Page Wide When Exporting to PDF"," covers it.",[350,3319,3321],{"id":3320},"common-pitfalls","Common pitfalls",[3323,3324,3325,3337,3354,3360],"ul",{},[3326,3327,3328,3332,3333,3336],"li",{},[3329,3330,3331],"strong",{},"Overwriting the template."," Always save to a new path. One accidental ",[362,3334,3335],{},"wb.save(template)"," bakes a customer's data into every future invoice.",[3326,3338,3339,3342,3343,3346,3347,3350,3351,3353],{},[3329,3340,3341],{},"Floats for money."," ",[362,3344,3345],{},"0.1 + 0.2"," is not ",[362,3348,3349],{},"0.3",", and totals built from floats occasionally end one cent off. Keep amounts as ",[362,3352,1296],{}," until the moment they are written.",[3326,3355,3356,3359],{},[3329,3357,3358],{},"Reissuing a number after a failed run."," Deleting the half-written invoice and rerunning is fine only if it never left the building. Once sent, correct it with a credit note, never a reused number.",[3326,3361,3362,3365,3366,3369],{},[3329,3363,3364],{},"Date as text."," Write ",[362,3367,3368],{},"datetime.date"," objects, not strings, so the template's date format applies and the cell sorts correctly.",[350,3371,3373],{"id":3372},"conclusion","Conclusion",[10,3375,3376,3377,3379],{},"A dependable invoice run fills a designed template through defined names, calculates every amount with ",[362,3378,1296],{}," under one stated VAT rounding rule, checks the line block's capacity, writes totals as values, takes numbers from a register it appends to only after the file is saved, and verifies each invoice before converting the batch to PDF.",[350,3381,3383],{"id":3382},"frequently-asked-questions","Frequently asked questions",[10,3385,3386,3389],{},[3329,3387,3388],{},"How do I fill an Excel invoice template with Python?","\nLoad the template with openpyxl, look up each field through a defined name such as inv_number or customer_name, write the values, fill the line-item rows, clear the unused ones and save under a new file name. Never save over the template.",[10,3391,3392,3395],{},[3329,3393,3394],{},"Should an invoice template use formulas or values for the totals?","\nWrite the totals as values calculated in Python. openpyxl saves formulas without results, so viewers that do not recalculate — file previews, mobile apps, other programs reading the file — show empty totals. Values also guarantee the invoice shows exactly the amounts you recorded.",[10,3397,3398,3401],{},[3329,3399,3400],{},"Should VAT be rounded per line or on the invoice total?","\nEither can be correct depending on the rules you follow, but the two can differ by a cent. In our example VAT per line summed to 16.32 and VAT on the net total was 16.31. Pick one rule, apply it consistently and match what the accounting system does.",[10,3403,3404,3407],{},[3329,3405,3406],{},"How do I make invoice numbers sequential without gaps or duplicates?","\nDerive the next number from a register of issued invoices, write the invoice, then append it to the register. Refuse to overwrite an existing invoice file, so a crash between the two steps stops the next run instead of reusing a number.",[350,3409,3411],{"id":3410},"related","Related",[3323,3413,3414,3420,3427,3434],{},[3326,3415,3416,3417,3419],{},"Up one level: ",[14,3418,17],{"href":16}," — filling designed workbooks from data.",[3326,3421,3422,3426],{},[14,3423,3425],{"href":3424},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting\u002F","Populate an Excel Template Without Losing Formatting"," — what openpyxl keeps when it re-saves.",[3326,3428,3429,3433],{},[14,3430,3432],{"href":3431},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Frepeat-a-template-block-for-each-record\u002F","Repeat a Template Block for Each Record in an Excel Report"," — many records in one workbook instead of one file each.",[3326,3435,3436,3440],{},[14,3437,3439],{"href":3438},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-excel-report-per-region-in-a-loop\u002F","Generate One Excel Report per Region in a Loop"," — batch runs that survive one failure.",[3442,3443,3444],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":360,"searchDepth":415,"depth":415,"links":3446},[3447,3448,3449,3450,3451,3452,3453,3454,3455,3456],{"id":352,"depth":415,"text":353},{"id":1167,"depth":415,"text":1168},{"id":1289,"depth":415,"text":1290},{"id":1816,"depth":415,"text":1817},{"id":2257,"depth":415,"text":2258},{"id":3021,"depth":415,"text":3022},{"id":3320,"depth":415,"text":3321},{"id":3372,"depth":415,"text":3373},{"id":3382,"depth":415,"text":3383},{"id":3410,"depth":415,"text":3411},"2026-09-23","Produce one invoice workbook per customer from a designed Excel template — named cells instead of hard-coded addresses, money calculated with Decimal and a stated VAT rounding rule, invoice numbers that never repeat or skip, a fixed block of line rows with an overflow check, a register of everything issued and a PDF for each invoice.","md",[3461,3463,3465,3467],{"q":3388,"a":3462},"Load the template with openpyxl, look up each field through a defined name such as inv_number or customer_name, write the values, fill the line-item rows, clear the unused ones and save under a new file name. Never save over the template.",{"q":3394,"a":3464},"Write the totals as values calculated in Python. openpyxl saves formulas without results, so viewers that do not recalculate — file previews, mobile apps, other programs reading the file — show empty totals. Values also guarantee the invoice shows exactly the amounts you recorded.",{"q":3400,"a":3466},"Either can be correct depending on the rules you follow, but the two can differ by a cent. In our example VAT per line summed to 16.32 and VAT on the net total was 16.31. Pick one rule, apply it consistently and match what the accounting system does.",{"q":3406,"a":3468},"Derive the next number from a register of issued invoices, write the invoice, then append it to the register. Refuse to overwrite an existing invoice file, so a crash between the two steps stops the next run instead of reusing a number.","generate invoices from an excel template with python",null,{"breadcrumb":3472},[3473,3475,3478],{"name":3474,"item":2604},"Home",{"name":3476,"item":3477},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":17,"item":16},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template",{"title":3481,"description":3482},"Generate Invoices from an Excel Template with Python (openpyxl)","Fill an Excel invoice template for each customer with openpyxl — defined names for fields and the line-item block, Decimal amounts with ROUND_HALF_UP, VAT per line versus on the total, sequential invoice numbers from a register, capacity checks, stored values instead of formulas, and batch PDF export.","generate-invoices-from-an-excel-template","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-invoices-from-an-excel-template\u002Findex",[3486,3487],"report-layouts","openpyxl","how-to","d7-bmM0kBO6f-YtC7uPUEZFZPVuvtA77tOwHm_fnRu0",[3491,3495],{"title":3492,"path":3493,"stem":3494,"children":-1},"Generate Customer Account Statements from a Ledger","\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-customer-account-statements-from-a-ledger","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-customer-account-statements-from-a-ledger\u002Findex",{"title":3439,"path":3496,"stem":3497,"children":-1},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-excel-report-per-region-in-a-loop","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-excel-report-per-region-in-a-loop\u002Findex",[3499,3501],{"path":3500,"title":17},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates",{"path":3502,"title":3476},"\u002Fautomating-reporting-workflows",[],[3505,3509,3512,3515,3518,3521],{"path":3506,"title":3507,"kind":3508},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports","Building Finance and Operations Reports in Excel with Python","guide",{"path":3510,"title":3511,"kind":3488},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fallocate-shared-costs-across-departments","Allocate Shared Costs Across Departments with Python",{"path":3513,"title":3514,"kind":3488},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report","Build a Bank Reconciliation Report with Python and Excel",{"path":3516,"title":3517,"kind":3488},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bill-of-materials-cost-rollup","Build a Bill of Materials Cost Rollup",{"path":3519,"title":3520,"kind":3488},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-break-even-analysis-in-excel-with-python","Build a Break-Even Analysis in Excel with Python",{"path":3522,"title":3523,"kind":3524},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report","Build a Budget vs Actual Variance Report in Excel with Python","compare",1790972138054]