[{"data":1,"prerenderedAt":2123},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger":2088,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger":2097,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger":2102,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger":2103},{"id":4,"title":5,"body":6,"dateModified":2053,"datePublished":2053,"description":2054,"extension":2055,"faq":2056,"keyword":2065,"kind":2066,"meta":2067,"navigation":88,"path":2077,"seo":2078,"slug":2081,"stem":2082,"topics":2083,"type":2086,"__hash__":2087},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger\u002Findex.md","Reconcile Payroll to the General Ledger",{"type":7,"value":8,"toc":2041},"minimark",[9,19,24,53,56,60,330,333,337,735,743,1008,1012,1233,1240,1333,1337,1738,1745,1923,1927,1930,1934,1969,1973,1976,1980,1986,1992,1998,2004,2008,2037],[10,11,12,13,18],"p",{},"Payroll is usually the largest single cost a business posts each month, and it is posted from one system — the payroll software — into another — the general ledger — through a journal that someone imports or types. Errors are rarely in the payroll calculation itself; they are in the posting: a new employee's cost centre mapped to the wrong department, employer pension posted to the wrong account, a manual adjustment made in payroll after the journal was exported. The monthly payroll reconciliation catches them by proving three things: that every payslip adds up, that the ledger received exactly what the payroll register says by account and cost centre, and that the net pay sent to the bank equals the net pay in the register. This page does all three with pandas and writes a workbook in which every difference is listed and explained. It belongs to ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002F","Building Finance and Operations Reports",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," pandas",[35,51,52],{"class":45}," openpyxl\n",[10,54,55],{},"Three files for September 2026: the payroll register (one row per employee with cost centre, gross pay, deductions, net pay and employer costs), the payroll journal as it was posted to the general ledger (account, cost centre, amount — debits positive, credits negative) and the bank payment file of net pay per employee. They come from three different systems, as they usually do.",[20,57,59],{"id":58},"step-1-every-payslip-adds-up","Step 1 — Every payslip adds up",[25,61,65],{"className":62,"code":63,"language":64,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nreg = pd.read_excel(\"payroll_register_2026-09.xlsx\")\nreg[\"Calculated net\"] = (reg[\"Gross\"] - reg[\"Tax\"] - reg[\"Employee NI\"] - reg[\"Employee pension\"]).round(2)\nreg[\"Net difference\"] = (reg[\"Net\"] - reg[\"Calculated net\"]).round(2)\nbad_slips = reg[reg[\"Net difference\"] != 0]\nprint(f\"{len(reg)} employees, gross {reg['Gross'].sum():,.2f}, net {reg['Net'].sum():,.2f}\")\nprint(bad_slips[[\"Employee\", \"Gross\", \"Net\", \"Calculated net\", \"Net difference\"]].to_string(index=False))\n","python",[32,66,67,83,90,108,166,197,221,287],{"__ignoreMap":30},[35,68,69,73,77,80],{"class":37,"line":38},[35,70,72],{"class":71},"s-kum","import",[35,74,76],{"class":75},"skGVy"," pandas ",[35,78,79],{"class":71},"as",[35,81,82],{"class":75}," pd\n",[35,84,86],{"class":37,"line":85},2,[35,87,89],{"emptyLinePlaceholder":88},true,"\n",[35,91,93,96,99,102,105],{"class":37,"line":92},3,[35,94,95],{"class":75},"reg ",[35,97,98],{"class":71},"=",[35,100,101],{"class":75}," pd.read_excel(",[35,103,104],{"class":45},"\"payroll_register_2026-09.xlsx\"",[35,106,107],{"class":75},")\n",[35,109,111,114,117,120,122,125,128,130,133,136,139,141,143,145,148,150,152,154,157,160,164],{"class":37,"line":110},4,[35,112,113],{"class":75},"reg[",[35,115,116],{"class":45},"\"Calculated net\"",[35,118,119],{"class":75},"] ",[35,121,98],{"class":71},[35,123,124],{"class":75}," (reg[",[35,126,127],{"class":45},"\"Gross\"",[35,129,119],{"class":75},[35,131,132],{"class":71},"-",[35,134,135],{"class":75}," reg[",[35,137,138],{"class":45},"\"Tax\"",[35,140,119],{"class":75},[35,142,132],{"class":71},[35,144,135],{"class":75},[35,146,147],{"class":45},"\"Employee NI\"",[35,149,119],{"class":75},[35,151,132],{"class":71},[35,153,135],{"class":75},[35,155,156],{"class":45},"\"Employee pension\"",[35,158,159],{"class":75},"]).round(",[35,161,163],{"class":162},"sP0c6","2",[35,165,107],{"class":75},[35,167,169,171,174,176,178,180,183,185,187,189,191,193,195],{"class":37,"line":168},5,[35,170,113],{"class":75},[35,172,173],{"class":45},"\"Net difference\"",[35,175,119],{"class":75},[35,177,98],{"class":71},[35,179,124],{"class":75},[35,181,182],{"class":45},"\"Net\"",[35,184,119],{"class":75},[35,186,132],{"class":71},[35,188,135],{"class":75},[35,190,116],{"class":45},[35,192,159],{"class":75},[35,194,163],{"class":162},[35,196,107],{"class":75},[35,198,200,203,205,208,210,212,215,218],{"class":37,"line":199},6,[35,201,202],{"class":75},"bad_slips ",[35,204,98],{"class":71},[35,206,207],{"class":75}," reg[reg[",[35,209,173],{"class":45},[35,211,119],{"class":75},[35,213,214],{"class":71},"!=",[35,216,217],{"class":162}," 0",[35,219,220],{"class":75},"]\n",[35,222,224,227,230,233,236,240,243,246,249,252,254,256,259,262,265,267,270,272,274,277,279,281,283,285],{"class":37,"line":223},7,[35,225,226],{"class":162},"print",[35,228,229],{"class":75},"(",[35,231,232],{"class":71},"f",[35,234,235],{"class":45},"\"",[35,237,239],{"class":238},"sSjpA","{",[35,241,242],{"class":162},"len",[35,244,245],{"class":75},"(reg)",[35,247,248],{"class":238},"}",[35,250,251],{"class":45}," employees, gross ",[35,253,239],{"class":238},[35,255,113],{"class":75},[35,257,258],{"class":45},"'Gross'",[35,260,261],{"class":75},"].sum()",[35,263,264],{"class":71},":,.2f",[35,266,248],{"class":238},[35,268,269],{"class":45},", net ",[35,271,239],{"class":238},[35,273,113],{"class":75},[35,275,276],{"class":45},"'Net'",[35,278,261],{"class":75},[35,280,264],{"class":71},[35,282,248],{"class":238},[35,284,235],{"class":45},[35,286,107],{"class":75},[35,288,290,292,295,298,301,303,305,307,309,311,313,315,318,322,324,327],{"class":37,"line":289},8,[35,291,226],{"class":162},[35,293,294],{"class":75},"(bad_slips[[",[35,296,297],{"class":45},"\"Employee\"",[35,299,300],{"class":75},", ",[35,302,127],{"class":45},[35,304,300],{"class":75},[35,306,182],{"class":45},[35,308,300],{"class":75},[35,310,116],{"class":45},[35,312,300],{"class":75},[35,314,173],{"class":45},[35,316,317],{"class":75},"]].to_string(",[35,319,321],{"class":320},"sa561","index",[35,323,98],{"class":71},[35,325,326],{"class":162},"False",[35,328,329],{"class":75},"))\n",[10,331,332],{},"Gross pay minus tax, employee National Insurance and employee pension should equal net pay for every employee. One payslip does not: E107's net pay is 25.00 less than its components imply — typically a deduction such as a loan repayment or a salary sacrifice that the register export does not show as a column. It may be correct, but it must be explained, because it is exactly the kind of manual adjustment that later causes a ledger difference.",[20,334,336],{"id":335},"step-2-map-the-register-to-the-accounts-it-should-hit","Step 2 — Map the register to the accounts it should hit",[25,338,340],{"className":62,"code":339,"language":64,"meta":30,"style":30},"POSTINGS = [\n    (\"6100 Salaries\", \"Gross\", 1, True),\n    (\"6110 Employer NI\", \"Employer NI\", 1, True),\n    (\"6120 Employer pension\", \"Employer pension\", 1, True),\n    (\"2210 PAYE and NI payable\", [\"Tax\", \"Employee NI\", \"Employer NI\"], -1, False),\n    (\"2220 Pension payable\", [\"Employee pension\", \"Employer pension\"], -1, False),\n    (\"2230 Net wages payable\", \"Net\", -1, False),\n]\nexpected = []\nfor account, cols, sign, by_cc in POSTINGS:\n    amount = reg[cols].sum(axis=1) if isinstance(cols, list) else reg[cols]\n    grouped = (reg.assign(a=amount).groupby(\"Cost centre\")[\"a\"].sum() if by_cc\n               else pd.Series({\"\": amount.sum()}))\n    for cc, value in grouped.items():\n        expected.append({\"Account\": account, \"Cost centre\": cc, \"Expected\": round(sign * value, 2)})\nexpected = pd.DataFrame(expected)\nprint(expected.to_string(index=False))\nprint(\"expected journal balances to:\", round(expected[\"Expected\"].sum(), 2))\n",[32,341,342,353,378,400,422,455,482,505,509,520,538,579,614,629,643,683,693,709],{"__ignoreMap":30},[35,343,344,347,350],{"class":37,"line":38},[35,345,346],{"class":162},"POSTINGS",[35,348,349],{"class":71}," =",[35,351,352],{"class":75}," [\n",[35,354,355,358,361,363,365,367,370,372,375],{"class":37,"line":85},[35,356,357],{"class":75},"    (",[35,359,360],{"class":45},"\"6100 Salaries\"",[35,362,300],{"class":75},[35,364,127],{"class":45},[35,366,300],{"class":75},[35,368,369],{"class":162},"1",[35,371,300],{"class":75},[35,373,374],{"class":162},"True",[35,376,377],{"class":75},"),\n",[35,379,380,382,385,387,390,392,394,396,398],{"class":37,"line":92},[35,381,357],{"class":75},[35,383,384],{"class":45},"\"6110 Employer NI\"",[35,386,300],{"class":75},[35,388,389],{"class":45},"\"Employer NI\"",[35,391,300],{"class":75},[35,393,369],{"class":162},[35,395,300],{"class":75},[35,397,374],{"class":162},[35,399,377],{"class":75},[35,401,402,404,407,409,412,414,416,418,420],{"class":37,"line":110},[35,403,357],{"class":75},[35,405,406],{"class":45},"\"6120 Employer pension\"",[35,408,300],{"class":75},[35,410,411],{"class":45},"\"Employer pension\"",[35,413,300],{"class":75},[35,415,369],{"class":162},[35,417,300],{"class":75},[35,419,374],{"class":162},[35,421,377],{"class":75},[35,423,424,426,429,432,434,436,438,440,442,445,447,449,451,453],{"class":37,"line":168},[35,425,357],{"class":75},[35,427,428],{"class":45},"\"2210 PAYE and NI payable\"",[35,430,431],{"class":75},", [",[35,433,138],{"class":45},[35,435,300],{"class":75},[35,437,147],{"class":45},[35,439,300],{"class":75},[35,441,389],{"class":45},[35,443,444],{"class":75},"], ",[35,446,132],{"class":71},[35,448,369],{"class":162},[35,450,300],{"class":75},[35,452,326],{"class":162},[35,454,377],{"class":75},[35,456,457,459,462,464,466,468,470,472,474,476,478,480],{"class":37,"line":199},[35,458,357],{"class":75},[35,460,461],{"class":45},"\"2220 Pension payable\"",[35,463,431],{"class":75},[35,465,156],{"class":45},[35,467,300],{"class":75},[35,469,411],{"class":45},[35,471,444],{"class":75},[35,473,132],{"class":71},[35,475,369],{"class":162},[35,477,300],{"class":75},[35,479,326],{"class":162},[35,481,377],{"class":75},[35,483,484,486,489,491,493,495,497,499,501,503],{"class":37,"line":223},[35,485,357],{"class":75},[35,487,488],{"class":45},"\"2230 Net wages payable\"",[35,490,300],{"class":75},[35,492,182],{"class":45},[35,494,300],{"class":75},[35,496,132],{"class":71},[35,498,369],{"class":162},[35,500,300],{"class":75},[35,502,326],{"class":162},[35,504,377],{"class":75},[35,506,507],{"class":37,"line":289},[35,508,220],{"class":75},[35,510,512,515,517],{"class":37,"line":511},9,[35,513,514],{"class":75},"expected ",[35,516,98],{"class":71},[35,518,519],{"class":75}," []\n",[35,521,523,526,529,532,535],{"class":37,"line":522},10,[35,524,525],{"class":71},"for",[35,527,528],{"class":75}," account, cols, sign, by_cc ",[35,530,531],{"class":71},"in",[35,533,534],{"class":162}," POSTINGS",[35,536,537],{"class":75},":\n",[35,539,541,544,546,549,552,554,556,559,562,565,568,571,573,576],{"class":37,"line":540},11,[35,542,543],{"class":75},"    amount ",[35,545,98],{"class":71},[35,547,548],{"class":75}," reg[cols].sum(",[35,550,551],{"class":320},"axis",[35,553,98],{"class":71},[35,555,369],{"class":162},[35,557,558],{"class":75},") ",[35,560,561],{"class":71},"if",[35,563,564],{"class":162}," isinstance",[35,566,567],{"class":75},"(cols, ",[35,569,570],{"class":162},"list",[35,572,558],{"class":75},[35,574,575],{"class":71},"else",[35,577,578],{"class":75}," reg[cols]\n",[35,580,582,585,587,590,592,594,597,600,603,606,609,611],{"class":37,"line":581},12,[35,583,584],{"class":75},"    grouped ",[35,586,98],{"class":71},[35,588,589],{"class":75}," (reg.assign(",[35,591,14],{"class":320},[35,593,98],{"class":71},[35,595,596],{"class":75},"amount).groupby(",[35,598,599],{"class":45},"\"Cost centre\"",[35,601,602],{"class":75},")[",[35,604,605],{"class":45},"\"a\"",[35,607,608],{"class":75},"].sum() ",[35,610,561],{"class":71},[35,612,613],{"class":75}," by_cc\n",[35,615,617,620,623,626],{"class":37,"line":616},13,[35,618,619],{"class":71},"               else",[35,621,622],{"class":75}," pd.Series({",[35,624,625],{"class":45},"\"\"",[35,627,628],{"class":75},": amount.sum()}))\n",[35,630,632,635,638,640],{"class":37,"line":631},14,[35,633,634],{"class":71},"    for",[35,636,637],{"class":75}," cc, value ",[35,639,531],{"class":71},[35,641,642],{"class":75}," grouped.items():\n",[35,644,646,649,652,655,657,660,663,666,669,672,675,678,680],{"class":37,"line":645},15,[35,647,648],{"class":75},"        expected.append({",[35,650,651],{"class":45},"\"Account\"",[35,653,654],{"class":75},": account, ",[35,656,599],{"class":45},[35,658,659],{"class":75},": cc, ",[35,661,662],{"class":45},"\"Expected\"",[35,664,665],{"class":75},": ",[35,667,668],{"class":162},"round",[35,670,671],{"class":75},"(sign ",[35,673,674],{"class":71},"*",[35,676,677],{"class":75}," value, ",[35,679,163],{"class":162},[35,681,682],{"class":75},")})\n",[35,684,686,688,690],{"class":37,"line":685},16,[35,687,514],{"class":75},[35,689,98],{"class":71},[35,691,692],{"class":75}," pd.DataFrame(expected)\n",[35,694,696,698,701,703,705,707],{"class":37,"line":695},17,[35,697,226],{"class":162},[35,699,700],{"class":75},"(expected.to_string(",[35,702,321],{"class":320},[35,704,98],{"class":71},[35,706,326],{"class":162},[35,708,329],{"class":75},[35,710,712,714,716,719,721,723,726,728,731,733],{"class":37,"line":711},18,[35,713,226],{"class":162},[35,715,229],{"class":75},[35,717,718],{"class":45},"\"expected journal balances to:\"",[35,720,300],{"class":75},[35,722,668],{"class":162},[35,724,725],{"class":75},"(expected[",[35,727,662],{"class":45},[35,729,730],{"class":75},"].sum(), ",[35,732,163],{"class":162},[35,734,329],{"class":75},[10,736,737,738,742],{},"The mapping is the rule book of the payroll journal, written as data: gross pay and employer costs are expenses posted by cost centre; tax, National Insurance (employee and employer), pensions (employee and employer) and net pay are liabilities posted in total, as credits. Applied to the register, it produces the journal that ",[739,740,741],"em",{},"should"," have been posted. Like any journal it should balance — debits equal credits — because every pound of gross pay and employer cost ends up owed to someone: the employee, the tax authority or the pension scheme. The expected journal is out of balance by exactly 25.00: E107's unexplained deduction from Step 1 is money taken from gross pay that the register does not assign to any liability, so the rules cannot post it. That is the second sign of the same problem, found before the ledger has even been looked at.",[744,745,753,754,753,758,753,762,753,770,753,779,753,783,753,790,753,794,753,798,753,802,753,806,753,808,753,812,753,814,753,817,753,820,753,823,753,825,753,829,753,831,753,834,753,837,753,841,753,843,753,846,753,848,753,850,753,853,753,856,753,858,753,861,753,863,753,866,753,869,753,873,753,875,753,878,753,880,753,882,753,885,753,889,753,892,753,896,753,899,753,901,753,904,753,909,753,913,753,915,753,918,753,920,753,922,753,924,753,927,753,929,753,932,753,934,753,936,753,938,753,941,753,943,753,945,753,947,753,949,753,951,753,954,753,956,753,959,753,961,753,963,753,965,753,968,753,970,753,972,753,978,753,982,753,989,753,994,753,1002],"svg",{"viewBox":746,"role":747,"ariaLabelledBy":748,"xmlns":751,"style":752},"4 0 752 220","img",[749,750],"pr-map-t","pr-map-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[755,756,757],"title",{"id":749},"From payroll register to expected journal",[759,760,761],"desc",{"id":750},"Gross pay, employer National Insurance and employer pension are debited to expense accounts by cost centre. Tax and both National Insurance amounts are credited to PAYE and NI payable, both pension amounts to pension payable and net pay to net wages payable. The expected journal balances to zero.",[763,764],"rect",{"x":765,"y":766,"width":767,"height":768,"fill":769},"4","0","752","220","#ffffff",[763,771],{"x":772,"y":773,"width":774,"height":775,"rx":766,"fill":776,"stroke":777,"style":778},"20.0","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[763,780],{"x":781,"y":773,"width":782,"height":775,"rx":766,"fill":776,"stroke":777,"style":778},"52.0","136.5",[784,785,789],"text",{"x":786,"y":787,"style":788},"120.2","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[763,791],{"x":792,"y":773,"width":793,"height":775,"rx":766,"fill":776,"stroke":777,"style":778},"188.5","129.1",[784,795,797],{"x":796,"y":787,"style":788},"253.0","B",[763,799],{"x":772,"y":800,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},"64.0","28.0",[784,803,369],{"x":804,"y":805,"style":788},"36.0","83.0",[763,807],{"x":781,"y":800,"width":782,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,809,811],{"x":786,"y":805,"style":810},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Register column",[763,813],{"x":792,"y":800,"width":793,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,815,816],{"x":796,"y":805,"style":810},"Per employee",[763,818],{"x":772,"y":819,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},"92.0",[784,821,163],{"x":804,"y":822,"style":788},"111.0",[763,824],{"x":781,"y":819,"width":782,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,826,828],{"x":786,"y":822,"style":827},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Gross",[763,830],{"x":792,"y":819,"width":793,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,832,833],{"x":796,"y":822,"style":827},"debit by cost centre",[763,835],{"x":772,"y":836,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},"120.0",[784,838,840],{"x":804,"y":839,"style":788},"139.0","3",[763,842],{"x":781,"y":836,"width":782,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,844,845],{"x":786,"y":839,"style":827},"Employer NI, pension",[763,847],{"x":792,"y":836,"width":793,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,849,833],{"x":796,"y":839,"style":827},[763,851],{"x":772,"y":852,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},"148.0",[784,854,765],{"x":804,"y":855,"style":788},"167.0",[763,857],{"x":781,"y":852,"width":782,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,859,860],{"x":786,"y":855,"style":827},"Tax, NI (ee + er)",[763,862],{"x":792,"y":852,"width":793,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,864,865],{"x":796,"y":855,"style":827},"credit total",[763,867],{"x":772,"y":868,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},"176.0",[784,870,872],{"x":804,"y":871,"style":788},"195.0","5",[763,874],{"x":781,"y":868,"width":782,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,876,877],{"x":786,"y":871,"style":827},"Net",[763,879],{"x":792,"y":868,"width":793,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,881,865],{"x":796,"y":871,"style":827},[763,883],{"x":884,"y":773,"width":774,"height":775,"rx":766,"fill":776,"stroke":777,"style":778},"500.6",[763,886],{"x":887,"y":773,"width":888,"height":775,"rx":766,"fill":776,"stroke":777,"style":778},"532.6","162.6",[784,890,789],{"x":891,"y":787,"style":788},"613.9",[763,893],{"x":894,"y":773,"width":895,"height":775,"rx":766,"fill":776,"stroke":777,"style":778},"695.2","44.8",[784,897,797],{"x":898,"y":787,"style":788},"717.6",[763,900],{"x":884,"y":800,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},[784,902,369],{"x":903,"y":805,"style":788},"516.6",[763,905],{"x":887,"y":800,"width":888,"height":801,"rx":766,"fill":906,"stroke":907,"style":908},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[784,910,912],{"x":891,"y":805,"style":911},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Account",[763,914],{"x":894,"y":800,"width":895,"height":801,"rx":766,"fill":906,"stroke":907,"style":908},[784,916,917],{"x":898,"y":805,"style":911},"Side",[763,919],{"x":884,"y":819,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},[784,921,163],{"x":903,"y":822,"style":788},[763,923],{"x":887,"y":819,"width":888,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,925,926],{"x":891,"y":822,"style":827},"6100 Salaries",[763,928],{"x":894,"y":819,"width":895,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,930,931],{"x":898,"y":822,"style":827},"Dr",[763,933],{"x":884,"y":836,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},[784,935,840],{"x":903,"y":839,"style":788},[763,937],{"x":887,"y":836,"width":888,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,939,940],{"x":891,"y":839,"style":827},"6110 \u002F 6120",[763,942],{"x":894,"y":836,"width":895,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,944,931],{"x":898,"y":839,"style":827},[763,946],{"x":884,"y":852,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},[784,948,765],{"x":903,"y":855,"style":788},[763,950],{"x":887,"y":852,"width":888,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,952,953],{"x":891,"y":855,"style":827},"2210 PAYE and NI payable",[763,955],{"x":894,"y":852,"width":895,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,957,958],{"x":898,"y":855,"style":827},"Cr",[763,960],{"x":884,"y":868,"width":774,"height":801,"rx":766,"fill":776,"stroke":777,"style":778},[784,962,872],{"x":903,"y":871,"style":788},[763,964],{"x":887,"y":868,"width":888,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,966,967],{"x":891,"y":871,"style":827},"2230 Net wages payable",[763,969],{"x":894,"y":868,"width":895,"height":801,"rx":766,"fill":769,"stroke":777,"style":778},[784,971,958],{"x":898,"y":871,"style":827},[784,973,977],{"x":974,"y":975,"style":976},"168.8","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[784,979,981],{"x":980,"y":975,"style":976},"620.3","after",[37,983],{"x1":984,"y1":985,"x2":986,"y2":985,"stroke":987,"style":988},"327.5","122.0","486.6","var(--brand,#5b5cf0)","stroke-width:2px",[990,991],"polygon",{"points":992,"fill":993},"490.6,122.0 481.6,127.0 481.6,117.0","#5b5cf0",[763,995],{"x":996,"y":997,"width":998,"height":999,"rx":1000,"fill":769,"stroke":777,"style":1001},"351.8","89.0","114.5","22.0","11","stroke-width:1.2px",[784,1003,1007],{"x":1004,"y":1005,"style":1006},"409.1","104.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","posting rules",[20,1009,1011],{"id":1010},"step-3-compare-with-what-was-posted","Step 3 — Compare with what was posted",[25,1013,1015],{"className":62,"code":1014,"language":64,"meta":30,"style":30},"gl = pd.read_excel(\"gl_payroll_journal_2026-09.xlsx\").fillna({\"Cost centre\": \"\"})\nposted = gl.groupby([\"Account\", \"Cost centre\"], as_index=False)[\"Debit\"].sum().rename(columns={\"Debit\": \"Posted\"})\nrecon = expected.merge(posted, on=[\"Account\", \"Cost centre\"], how=\"outer\").fillna({\"Expected\": 0, \"Posted\": 0})\nrecon[\"Difference\"] = (recon[\"Posted\"] - recon[\"Expected\"]).round(2)\ndifferences = recon[recon[\"Difference\"] != 0]\nprint(differences.to_string(index=False))\nprint(\"posted journal balances to:\", round(posted[\"Posted\"].sum(), 2))\n",[32,1016,1017,1041,1090,1142,1174,1194,1209],{"__ignoreMap":30},[35,1018,1019,1022,1024,1026,1029,1032,1034,1036,1038],{"class":37,"line":38},[35,1020,1021],{"class":75},"gl ",[35,1023,98],{"class":71},[35,1025,101],{"class":75},[35,1027,1028],{"class":45},"\"gl_payroll_journal_2026-09.xlsx\"",[35,1030,1031],{"class":75},").fillna({",[35,1033,599],{"class":45},[35,1035,665],{"class":75},[35,1037,625],{"class":45},[35,1039,1040],{"class":75},"})\n",[35,1042,1043,1046,1048,1051,1053,1055,1057,1059,1062,1064,1066,1068,1071,1074,1077,1079,1081,1083,1085,1088],{"class":37,"line":85},[35,1044,1045],{"class":75},"posted ",[35,1047,98],{"class":71},[35,1049,1050],{"class":75}," gl.groupby([",[35,1052,651],{"class":45},[35,1054,300],{"class":75},[35,1056,599],{"class":45},[35,1058,444],{"class":75},[35,1060,1061],{"class":320},"as_index",[35,1063,98],{"class":71},[35,1065,326],{"class":162},[35,1067,602],{"class":75},[35,1069,1070],{"class":45},"\"Debit\"",[35,1072,1073],{"class":75},"].sum().rename(",[35,1075,1076],{"class":320},"columns",[35,1078,98],{"class":71},[35,1080,239],{"class":75},[35,1082,1070],{"class":45},[35,1084,665],{"class":75},[35,1086,1087],{"class":45},"\"Posted\"",[35,1089,1040],{"class":75},[35,1091,1092,1095,1097,1100,1103,1105,1108,1110,1112,1114,1116,1119,1121,1124,1126,1128,1130,1132,1134,1136,1138,1140],{"class":37,"line":92},[35,1093,1094],{"class":75},"recon ",[35,1096,98],{"class":71},[35,1098,1099],{"class":75}," expected.merge(posted, ",[35,1101,1102],{"class":320},"on",[35,1104,98],{"class":71},[35,1106,1107],{"class":75},"[",[35,1109,651],{"class":45},[35,1111,300],{"class":75},[35,1113,599],{"class":45},[35,1115,444],{"class":75},[35,1117,1118],{"class":320},"how",[35,1120,98],{"class":71},[35,1122,1123],{"class":45},"\"outer\"",[35,1125,1031],{"class":75},[35,1127,662],{"class":45},[35,1129,665],{"class":75},[35,1131,766],{"class":162},[35,1133,300],{"class":75},[35,1135,1087],{"class":45},[35,1137,665],{"class":75},[35,1139,766],{"class":162},[35,1141,1040],{"class":75},[35,1143,1144,1147,1150,1152,1154,1157,1159,1161,1163,1166,1168,1170,1172],{"class":37,"line":110},[35,1145,1146],{"class":75},"recon[",[35,1148,1149],{"class":45},"\"Difference\"",[35,1151,119],{"class":75},[35,1153,98],{"class":71},[35,1155,1156],{"class":75}," (recon[",[35,1158,1087],{"class":45},[35,1160,119],{"class":75},[35,1162,132],{"class":71},[35,1164,1165],{"class":75}," recon[",[35,1167,662],{"class":45},[35,1169,159],{"class":75},[35,1171,163],{"class":162},[35,1173,107],{"class":75},[35,1175,1176,1179,1181,1184,1186,1188,1190,1192],{"class":37,"line":168},[35,1177,1178],{"class":75},"differences ",[35,1180,98],{"class":71},[35,1182,1183],{"class":75}," recon[recon[",[35,1185,1149],{"class":45},[35,1187,119],{"class":75},[35,1189,214],{"class":71},[35,1191,217],{"class":162},[35,1193,220],{"class":75},[35,1195,1196,1198,1201,1203,1205,1207],{"class":37,"line":199},[35,1197,226],{"class":162},[35,1199,1200],{"class":75},"(differences.to_string(",[35,1202,321],{"class":320},[35,1204,98],{"class":71},[35,1206,326],{"class":162},[35,1208,329],{"class":75},[35,1210,1211,1213,1215,1218,1220,1222,1225,1227,1229,1231],{"class":37,"line":223},[35,1212,226],{"class":162},[35,1214,229],{"class":75},[35,1216,1217],{"class":45},"\"posted journal balances to:\"",[35,1219,300],{"class":75},[35,1221,668],{"class":162},[35,1223,1224],{"class":75},"(posted[",[35,1226,1087],{"class":45},[35,1228,730],{"class":75},[35,1230,163],{"class":162},[35,1232,329],{"class":75},[10,1234,1235,1236,1239],{},"An outer merge on account and cost centre keeps lines that appear on only one side — an account the journal used but the rules did not expect, or the other way round. Three groups of differences appear. Salaries for CC20 are too high and CC30 too low by the same amount: one employee's pay went to the wrong cost centre, exactly E112's gross pay, which points to a cost-centre mapping in the journal export that was not updated when the employee moved. Employer pension is missing from 6120 and the same total sits in ",[739,1237,1238],{},"6130 Staff welfare",": the right amount on the wrong account. And net wages payable was credited 25.00 more than the register's net pay — the same 25.00 as the unexplained payslip in Step 1: whoever built the posted journal balanced it by putting the missing amount into net wages. The posted journal balances perfectly, which is why none of this would be noticed without the reconciliation.",[744,1241,753,1246,753,1249,753,1252,753,1255,753,1259,753,1264,753,1270,753,1273,753,1277,753,1281,753,1286,753,1288,753,1293,753,1295,753,1298,753,1301,753,1305,753,1307,753,1310,753,1312,753,1315,753,1318,753,1322,753,1324,753,1328,753,1330],{"viewBox":1242,"role":747,"ariaLabelledBy":1243,"xmlns":751,"style":752},"4 0 752 174",[1244,1245],"pr-diffs-t","pr-diffs-d",[755,1247,1248],{"id":1244},"The differences and what caused them",[759,1250,1251],{"id":1245},"Salaries were overstated in CC20 and understated in CC30 by one employee's gross pay, a cost centre mapping error. Employer pension was posted to staff welfare instead of the pension account. Net wages payable differs by 25.00, the same amount as the payslip that does not add up.",[763,1253],{"x":765,"y":766,"width":767,"height":1254,"fill":769},"174",[763,1256],{"x":772,"y":1257,"width":1258,"height":773,"rx":766,"fill":769,"stroke":777,"style":778},"16.0","147.6",[763,1260],{"x":1261,"y":1257,"width":1262,"height":773,"rx":766,"fill":1263,"stroke":987,"style":1001},"167.6","286.2","#f0f4ff",[784,1265,1269],{"x":1266,"y":1267,"style":1268},"310.7","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","difference",[763,1271],{"x":1272,"y":1257,"width":1262,"height":773,"rx":766,"fill":1263,"stroke":987,"style":1001},"453.8",[784,1274,1276],{"x":1275,"y":1267,"style":1268},"596.9","cause",[763,1278],{"x":772,"y":1279,"width":1258,"height":1280,"rx":766,"fill":776,"stroke":777,"style":778},"56.0","34.0",[784,1282,1285],{"x":774,"y":1283,"style":1284},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","6100 CC20 \u002F CC30",[763,1287],{"x":1261,"y":1279,"width":1262,"height":1280,"rx":766,"fill":769,"stroke":777,"style":778},[784,1289,1292],{"x":1266,"y":1290,"style":1291},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","+ \u002F − one employee's gross",[763,1294],{"x":1272,"y":1279,"width":1262,"height":1280,"rx":766,"fill":769,"stroke":777,"style":778},[784,1296,1297],{"x":1275,"y":1290,"style":1291},"wrong cost centre mapping",[763,1299],{"x":772,"y":1300,"width":1258,"height":1280,"rx":766,"fill":776,"stroke":777,"style":778},"90.0",[784,1302,1304],{"x":774,"y":1303,"style":1284},"112.0","6120 vs 6130",[763,1306],{"x":1261,"y":1300,"width":1262,"height":1280,"rx":766,"fill":769,"stroke":777,"style":778},[784,1308,1309],{"x":1266,"y":822,"style":1291},"all employer pension",[763,1311],{"x":1272,"y":1300,"width":1262,"height":1280,"rx":766,"fill":769,"stroke":777,"style":778},[784,1313,1314],{"x":1275,"y":822,"style":1291},"wrong account",[763,1316],{"x":772,"y":1317,"width":1258,"height":1280,"rx":766,"fill":776,"stroke":777,"style":778},"124.0",[784,1319,1321],{"x":774,"y":1320,"style":1284},"146.0","2230 net wages",[763,1323],{"x":1261,"y":1317,"width":1262,"height":1280,"rx":766,"fill":769,"stroke":777,"style":778},[784,1325,1327],{"x":1266,"y":1326,"style":1291},"145.0","25.00",[763,1329],{"x":1272,"y":1317,"width":1262,"height":1280,"rx":766,"fill":769,"stroke":777,"style":778},[784,1331,1332],{"x":1275,"y":1326,"style":1291},"same as payslip E107",[20,1334,1336],{"id":1335},"step-4-net-pay-against-the-bank-file-and-the-workbook","Step 4 — Net pay against the bank file, and the workbook",[25,1338,1340],{"className":62,"code":1339,"language":64,"meta":30,"style":30},"bank = pd.read_excel(\"bank_payment_file_2026-09.xlsx\")\npay_check = reg[[\"Employee\", \"Net\"]].merge(bank, on=\"Employee\", how=\"outer\", indicator=True)\npay_check[\"Difference\"] = (pay_check[\"Amount\"].fillna(0) - pay_check[\"Net\"].fillna(0)).round(2)\nprint(f\"bank file {bank['Amount'].sum():,.2f} vs register net {reg['Net'].sum():,.2f};\",\n      \"unmatched employees:\", int((pay_check[\"_merge\"] != \"both\").sum()), \"| amount differences:\", int((pay_check[\"Difference\"] != 0).sum()))\n\nwith pd.ExcelWriter(\"payroll_recon_2026-09.xlsx\", engine=\"openpyxl\") as xw:\n    recon.to_excel(xw, sheet_name=\"GL reconciliation\", index=False)\n    EXPLAIN = {\"6100 Salaries\": \"E112 posted to CC20, belongs to CC30\",\n               \"6120 Employer pension\": \"employer pension posted to 6130 instead of 6120\",\n               \"6130 Staff welfare\": \"employer pension posted to 6130 instead of 6120\",\n               \"2230 Net wages payable\": \"net pay credited 25.00 above register — see payslip E107\"}\n    differences.assign(Explanation=differences[\"Account\"].map(EXPLAIN)).to_excel(xw, sheet_name=\"Differences\", index=False)\n    bad_slips.to_excel(xw, sheet_name=\"Payslip checks\", index=False)\n    pay_check.drop(columns=\"_merge\").to_excel(xw, sheet_name=\"Bank file\", index=False)\n",[32,1341,1342,1356,1400,1442,1488,1534,1538,1566,1589,1608,1620,1631,1644,1685,1707],{"__ignoreMap":30},[35,1343,1344,1347,1349,1351,1354],{"class":37,"line":38},[35,1345,1346],{"class":75},"bank ",[35,1348,98],{"class":71},[35,1350,101],{"class":75},[35,1352,1353],{"class":45},"\"bank_payment_file_2026-09.xlsx\"",[35,1355,107],{"class":75},[35,1357,1358,1361,1363,1366,1368,1370,1372,1375,1377,1379,1381,1383,1385,1387,1389,1391,1394,1396,1398],{"class":37,"line":85},[35,1359,1360],{"class":75},"pay_check ",[35,1362,98],{"class":71},[35,1364,1365],{"class":75}," reg[[",[35,1367,297],{"class":45},[35,1369,300],{"class":75},[35,1371,182],{"class":45},[35,1373,1374],{"class":75},"]].merge(bank, ",[35,1376,1102],{"class":320},[35,1378,98],{"class":71},[35,1380,297],{"class":45},[35,1382,300],{"class":75},[35,1384,1118],{"class":320},[35,1386,98],{"class":71},[35,1388,1123],{"class":45},[35,1390,300],{"class":75},[35,1392,1393],{"class":320},"indicator",[35,1395,98],{"class":71},[35,1397,374],{"class":162},[35,1399,107],{"class":75},[35,1401,1402,1405,1407,1409,1411,1414,1417,1420,1422,1424,1426,1429,1431,1433,1435,1438,1440],{"class":37,"line":92},[35,1403,1404],{"class":75},"pay_check[",[35,1406,1149],{"class":45},[35,1408,119],{"class":75},[35,1410,98],{"class":71},[35,1412,1413],{"class":75}," (pay_check[",[35,1415,1416],{"class":45},"\"Amount\"",[35,1418,1419],{"class":75},"].fillna(",[35,1421,766],{"class":162},[35,1423,558],{"class":75},[35,1425,132],{"class":71},[35,1427,1428],{"class":75}," pay_check[",[35,1430,182],{"class":45},[35,1432,1419],{"class":75},[35,1434,766],{"class":162},[35,1436,1437],{"class":75},")).round(",[35,1439,163],{"class":162},[35,1441,107],{"class":75},[35,1443,1444,1446,1448,1450,1453,1455,1458,1461,1463,1465,1467,1470,1472,1474,1476,1478,1480,1482,1485],{"class":37,"line":110},[35,1445,226],{"class":162},[35,1447,229],{"class":75},[35,1449,232],{"class":71},[35,1451,1452],{"class":45},"\"bank file ",[35,1454,239],{"class":238},[35,1456,1457],{"class":75},"bank[",[35,1459,1460],{"class":45},"'Amount'",[35,1462,261],{"class":75},[35,1464,264],{"class":71},[35,1466,248],{"class":238},[35,1468,1469],{"class":45}," vs register net ",[35,1471,239],{"class":238},[35,1473,113],{"class":75},[35,1475,276],{"class":45},[35,1477,261],{"class":75},[35,1479,264],{"class":71},[35,1481,248],{"class":238},[35,1483,1484],{"class":45},";\"",[35,1486,1487],{"class":75},",\n",[35,1489,1490,1493,1495,1498,1501,1504,1506,1508,1511,1514,1517,1519,1521,1523,1525,1527,1529,1531],{"class":37,"line":168},[35,1491,1492],{"class":45},"      \"unmatched employees:\"",[35,1494,300],{"class":75},[35,1496,1497],{"class":162},"int",[35,1499,1500],{"class":75},"((pay_check[",[35,1502,1503],{"class":45},"\"_merge\"",[35,1505,119],{"class":75},[35,1507,214],{"class":71},[35,1509,1510],{"class":45}," \"both\"",[35,1512,1513],{"class":75},").sum()), ",[35,1515,1516],{"class":45},"\"| amount differences:\"",[35,1518,300],{"class":75},[35,1520,1497],{"class":162},[35,1522,1500],{"class":75},[35,1524,1149],{"class":45},[35,1526,119],{"class":75},[35,1528,214],{"class":71},[35,1530,217],{"class":162},[35,1532,1533],{"class":75},").sum()))\n",[35,1535,1536],{"class":37,"line":199},[35,1537,89],{"emptyLinePlaceholder":88},[35,1539,1540,1543,1546,1549,1551,1554,1556,1559,1561,1563],{"class":37,"line":223},[35,1541,1542],{"class":71},"with",[35,1544,1545],{"class":75}," pd.ExcelWriter(",[35,1547,1548],{"class":45},"\"payroll_recon_2026-09.xlsx\"",[35,1550,300],{"class":75},[35,1552,1553],{"class":320},"engine",[35,1555,98],{"class":71},[35,1557,1558],{"class":45},"\"openpyxl\"",[35,1560,558],{"class":75},[35,1562,79],{"class":71},[35,1564,1565],{"class":75}," xw:\n",[35,1567,1568,1571,1574,1576,1579,1581,1583,1585,1587],{"class":37,"line":289},[35,1569,1570],{"class":75},"    recon.to_excel(xw, ",[35,1572,1573],{"class":320},"sheet_name",[35,1575,98],{"class":71},[35,1577,1578],{"class":45},"\"GL reconciliation\"",[35,1580,300],{"class":75},[35,1582,321],{"class":320},[35,1584,98],{"class":71},[35,1586,326],{"class":162},[35,1588,107],{"class":75},[35,1590,1591,1594,1596,1599,1601,1603,1606],{"class":37,"line":511},[35,1592,1593],{"class":162},"    EXPLAIN",[35,1595,349],{"class":71},[35,1597,1598],{"class":75}," {",[35,1600,360],{"class":45},[35,1602,665],{"class":75},[35,1604,1605],{"class":45},"\"E112 posted to CC20, belongs to CC30\"",[35,1607,1487],{"class":75},[35,1609,1610,1613,1615,1618],{"class":37,"line":522},[35,1611,1612],{"class":45},"               \"6120 Employer pension\"",[35,1614,665],{"class":75},[35,1616,1617],{"class":45},"\"employer pension posted to 6130 instead of 6120\"",[35,1619,1487],{"class":75},[35,1621,1622,1625,1627,1629],{"class":37,"line":540},[35,1623,1624],{"class":45},"               \"6130 Staff welfare\"",[35,1626,665],{"class":75},[35,1628,1617],{"class":45},[35,1630,1487],{"class":75},[35,1632,1633,1636,1638,1641],{"class":37,"line":581},[35,1634,1635],{"class":45},"               \"2230 Net wages payable\"",[35,1637,665],{"class":75},[35,1639,1640],{"class":45},"\"net pay credited 25.00 above register — see payslip E107\"",[35,1642,1643],{"class":75},"}\n",[35,1645,1646,1649,1652,1654,1657,1659,1662,1665,1668,1670,1672,1675,1677,1679,1681,1683],{"class":37,"line":616},[35,1647,1648],{"class":75},"    differences.assign(",[35,1650,1651],{"class":320},"Explanation",[35,1653,98],{"class":71},[35,1655,1656],{"class":75},"differences[",[35,1658,651],{"class":45},[35,1660,1661],{"class":75},"].map(",[35,1663,1664],{"class":162},"EXPLAIN",[35,1666,1667],{"class":75},")).to_excel(xw, ",[35,1669,1573],{"class":320},[35,1671,98],{"class":71},[35,1673,1674],{"class":45},"\"Differences\"",[35,1676,300],{"class":75},[35,1678,321],{"class":320},[35,1680,98],{"class":71},[35,1682,326],{"class":162},[35,1684,107],{"class":75},[35,1686,1687,1690,1692,1694,1697,1699,1701,1703,1705],{"class":37,"line":631},[35,1688,1689],{"class":75},"    bad_slips.to_excel(xw, ",[35,1691,1573],{"class":320},[35,1693,98],{"class":71},[35,1695,1696],{"class":45},"\"Payslip checks\"",[35,1698,300],{"class":75},[35,1700,321],{"class":320},[35,1702,98],{"class":71},[35,1704,326],{"class":162},[35,1706,107],{"class":75},[35,1708,1709,1712,1714,1716,1718,1721,1723,1725,1728,1730,1732,1734,1736],{"class":37,"line":645},[35,1710,1711],{"class":75},"    pay_check.drop(",[35,1713,1076],{"class":320},[35,1715,98],{"class":71},[35,1717,1503],{"class":45},[35,1719,1720],{"class":75},").to_excel(xw, ",[35,1722,1573],{"class":320},[35,1724,98],{"class":71},[35,1726,1727],{"class":45},"\"Bank file\"",[35,1729,300],{"class":75},[35,1731,321],{"class":320},[35,1733,98],{"class":71},[35,1735,326],{"class":162},[35,1737,107],{"class":75},[10,1739,1740,1741,18],{},"The bank file matches the register employee by employee: every net payment that left the bank is in the register at the same amount, so the 25.00 difference is in the ledger journal, not in what was paid — and once the bank payment is posted, 25.00 will be left sitting on the net wages control account, where it would otherwise surface months later as an unexplained balance. The workbook gathers the full comparison by account and cost centre, the differences with an explanation for each, the payslip that does not add up and the bank comparison. The corrections — move E112's salary between cost centres, reclassify employer pension, investigate the 25.00 — go into an adjusting journal, and next month's run should show no differences at all. The same compare-two-sources structure underlies ",[14,1742,1744],{"href":1743},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report\u002F","Build a Bank Reconciliation Report",[744,1746,753,1751,753,1754,753,1757,753,1760,753,1765,753,1776,753,1782,753,1786,753,1790,753,1794,753,1798,753,1803,753,1806,753,1809,753,1816,753,1822,753,1826,753,1830,753,1834,753,1838,753,1841,753,1846,753,1853,753,1858,753,1861,753,1864,753,1867,753,1871,753,1874,753,1877,753,1884,753,1889,753,1892,753,1895,753,1899,753,1902,753,1905,753,1911,753,1915,753,1918,753,1921],{"viewBox":1747,"role":747,"ariaLabelledBy":1748,"xmlns":751,"style":752},"4 0 752 178",[1749,1750],"pr-flow-t","pr-flow-d",[755,1752,1753],{"id":1749},"The three checks of a payroll reconciliation",[759,1755,1756],{"id":1750},"Check that each payslip's net pay equals gross minus deductions, build the expected journal from the register with posting rules and compare it with the posted journal by account and cost centre, then tie register net pay to the bank payment file, and write all differences with explanations.",[763,1758],{"x":765,"y":766,"width":767,"height":1759,"fill":769},"178",[763,1761],{"x":772,"y":1257,"width":1762,"height":1763,"rx":1764,"fill":776,"stroke":777,"style":988},"115.2","145.5","12",[1766,1767,1769],"g",{"transform":1768},"translate(65.6,29.0) scale(1.000)",[1770,1771],"path",{"d":1772,"fill":1773,"stroke":1774,"style":1775},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[784,1777,1781],{"x":1778,"y":1779,"style":1780},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Payslip",[784,1783,1785],{"x":1778,"y":1784,"style":1780},"93.0","arithmetic",[784,1787,1789],{"x":1778,"y":1788,"style":1291},"112.5","gross −",[784,1791,1793],{"x":1778,"y":1792,"style":1291},"130.0","deductions =",[784,1795,1797],{"x":1778,"y":1796,"style":1291},"147.5","net",[37,1799],{"x1":1800,"y1":1801,"x2":1802,"y2":1801,"stroke":987,"style":988},"140.2","88.8","162.2",[990,1804],{"points":1805,"fill":993},"166.2,88.8 157.2,93.8 157.2,83.8",[763,1807],{"x":1808,"y":1257,"width":1762,"height":1763,"rx":1764,"fill":1263,"stroke":987,"style":988},"171.2",[1766,1810,1812],{"transform":1811},"translate(216.8,37.8) scale(1.000)",[1770,1813],{"d":1814,"fill":1773,"stroke":1815,"style":1775},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--brand-strong,#4338ca)",[784,1817,1821],{"x":1818,"y":1819,"style":1820},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Expected",[784,1823,1825],{"x":1818,"y":1824,"style":1820},"101.8","journal",[784,1827,1829],{"x":1818,"y":1828,"style":1291},"121.2","posting rules as",[784,1831,1833],{"x":1818,"y":1832,"style":1291},"138.8","data",[37,1835],{"x1":1836,"y1":1801,"x2":1837,"y2":1801,"stroke":987,"style":988},"291.4","313.4",[990,1839],{"points":1840,"fill":993},"317.4,88.8 308.4,93.8 308.4,83.8",[763,1842],{"x":1843,"y":1257,"width":1762,"height":1763,"rx":1764,"fill":1844,"stroke":1845,"style":988},"322.4","#fdefd8","var(--gold,#b4740a)",[1766,1847,1849],{"transform":1848},"translate(368.0,37.8) scale(1.000)",[1770,1850],{"d":1851,"fill":1773,"stroke":1852,"style":1775},"M6 4v5a5 5 0 0 0 5 5h7 M6 20v-5 M15 11l3 3-3 3","var(--gold-ink,#7a4e06)",[784,1854,1857],{"x":1855,"y":1819,"style":1856},"380.0","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Compare",[784,1859,1860],{"x":1855,"y":1824,"style":1856},"with GL",[784,1862,1863],{"x":1855,"y":1828,"style":1291},"outer merge,",[784,1865,1866],{"x":1855,"y":1832,"style":1291},"account × CC",[37,1868],{"x1":1869,"y1":1801,"x2":1870,"y2":1801,"stroke":987,"style":988},"442.6","464.6",[990,1872],{"points":1873,"fill":993},"468.6,88.8 459.6,93.8 459.6,83.8",[763,1875],{"x":1876,"y":1257,"width":1762,"height":1763,"rx":1764,"fill":906,"stroke":907,"style":988},"473.6",[1766,1878,1880],{"transform":1879},"translate(519.2,46.5) scale(1.000)",[1770,1881],{"d":1882,"fill":1773,"stroke":1883,"style":1775},"M5 6c0-1.7 3.1-3 7-3s7 1.3 7 3-3.1 3-7 3-7-1.3-7-3z M5 6v12c0 1.7 3.1 3 7 3s7-1.3 7-3V6 M5 12c0 1.7 3.1 3 7 3s7-1.3 7-3","var(--teal-ink,#0b6157)",[784,1885,1888],{"x":1886,"y":1784,"style":1887},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Bank file",[784,1890,1891],{"x":1886,"y":1788,"style":1291},"net pay paid =",[784,1893,1894],{"x":1886,"y":1792,"style":1291},"posted",[37,1896],{"x1":1897,"y1":1801,"x2":1898,"y2":1801,"stroke":987,"style":988},"593.8","615.8",[990,1900],{"points":1901,"fill":993},"619.8,88.8 610.8,93.8 610.8,83.8",[763,1903],{"x":1904,"y":1257,"width":1762,"height":1763,"rx":1764,"fill":776,"stroke":777,"style":988},"624.8",[1766,1906,1908],{"transform":1907},"translate(670.4,37.8) scale(1.000)",[1770,1909],{"d":1910,"fill":1773,"stroke":1774,"style":1775},"M5 12.5l4.5 4.5L19 7",[784,1912,1914],{"x":1913,"y":1819,"style":1780},"682.4","Explain +",[784,1916,1917],{"x":1913,"y":1824,"style":1780},"correct",[784,1919,1920],{"x":1913,"y":1828,"style":1291},"adjusting",[784,1922,1825],{"x":1913,"y":1832,"style":1291},[20,1924,1926],{"id":1925},"making-it-a-monthly-routine","Making it a monthly routine",[10,1928,1929],{},"The value of this reconciliation comes from running it every month, the same way, before the ledger is closed. Keep the posting rules in a version-controlled file rather than in the script, so a change in pension provider or a new benefit is one reviewed edit. Store each month's workbook alongside the payroll reports; auditors ask for exactly this evidence. And track the differences themselves over time: a cost-centre mapping error that recurs every time someone changes department is a process problem in the payroll system, not twelve separate mistakes, and the reconciliation history is what reveals it.",[20,1931,1933],{"id":1932},"common-pitfalls","Common pitfalls",[1935,1936,1937,1945,1951,1957,1963],"ul",{},[1938,1939,1940,1944],"li",{},[1941,1942,1943],"strong",{},"Comparing totals only."," Totals can agree while cost centres and accounts are wrong.",[1938,1946,1947,1950],{},[1941,1948,1949],{},"An inner merge."," Use an outer merge so lines on only one side appear.",[1938,1952,1953,1956],{},[1941,1954,1955],{},"Ignoring payslip arithmetic."," Unexplained deductions resurface as ledger differences.",[1938,1958,1959,1962],{},[1941,1960,1961],{},"Skipping the bank file."," Prove that what was paid equals what was posted.",[1938,1964,1965,1968],{},[1941,1966,1967],{},"Rules buried in code."," Keep the posting rules as data and review them.",[20,1970,1972],{"id":1971},"conclusion","Conclusion",[10,1974,1975],{},"Reconcile payroll to the general ledger in three steps: check that every payslip's net pay equals gross minus deductions, apply the posting rules to the payroll register to build the journal that should have been posted and compare it with the actual journal by account and cost centre using an outer merge, and tie register net pay to the bank payment file. Write the comparison, the explained differences and the supporting checks to a workbook, and correct the ledger before closing the month.",[20,1977,1979],{"id":1978},"frequently-asked-questions","Frequently asked questions",[10,1981,1982,1985],{},[1941,1983,1984],{},"How do I reconcile payroll to the general ledger?","\nBuild the expected journal from the payroll register using the posting rules for each pay element, then compare it with the posted journal by account and cost centre and investigate each difference.",[10,1987,1988,1991],{},[1941,1989,1990],{},"Why does my payroll journal balance but the reconciliation shows differences?","\nA journal can balance while amounts sit on the wrong account or cost centre; comparing by account and cost centre finds those misclassifications.",[10,1993,1994,1997],{},[1941,1995,1996],{},"What checks should a payroll reconciliation include?","\nPayslip arithmetic, register against ledger by account and cost centre, and register net pay against the bank payment file.",[10,1999,2000,2003],{},[1941,2001,2002],{},"How do I find lines that are only in the ledger or only in payroll?","\nMerge the expected and posted amounts with how=\"outer\" and treat missing values as zero, so one-sided lines show as differences.",[20,2005,2007],{"id":2006},"related","Related",[1935,2009,2010,2016,2023,2030],{},[1938,2011,2012,2013,2015],{},"Up one level: ",[14,2014,17],{"href":16}," — finance reports from Python.",[1938,2017,2018,2022],{},[14,2019,2021],{"href":2020},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-trial-balance-from-a-general-ledger\u002F","Build a Trial Balance from a General Ledger"," — the ledger side.",[1938,2024,2025,2029],{},[14,2026,2028],{"href":2027},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report\u002F","Build a Headcount and Attrition Report"," — people data from the same source.",[1938,2031,2032,2036],{},[14,2033,2035],{"href":2034},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcompare-two-excel-files-for-differences-with-python\u002F","Compare Two Excel Files for Differences with Python"," — general comparison techniques.",[2038,2039,2040],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":30,"searchDepth":85,"depth":85,"links":2042},[2043,2044,2045,2046,2047,2048,2049,2050,2051,2052],{"id":22,"depth":85,"text":23},{"id":58,"depth":85,"text":59},{"id":335,"depth":85,"text":336},{"id":1010,"depth":85,"text":1011},{"id":1335,"depth":85,"text":1336},{"id":1925,"depth":85,"text":1926},{"id":1932,"depth":85,"text":1933},{"id":1971,"depth":85,"text":1972},{"id":1978,"depth":85,"text":1979},{"id":2006,"depth":85,"text":2007},"2026-10-02","Prove each month that payroll was posted to the ledger correctly with pandas: check every payslip adds up, map the payroll register to the expense and liability accounts it should hit, compare with the general ledger journal by account and cost centre, tie net pay to the bank payment file, and produce a reconciliation workbook that explains every difference.","md",[2057,2059,2061,2063],{"q":1984,"a":2058},"Build the expected journal from the payroll register using the posting rules for each pay element, then compare it with the posted journal by account and cost centre and investigate each difference.",{"q":1990,"a":2060},"A journal can balance while amounts sit on the wrong account or cost centre; comparing by account and cost centre finds those misclassifications.",{"q":1996,"a":2062},"Payslip arithmetic, register against ledger by account and cost centre, and register net pay against the bank payment file.",{"q":2002,"a":2064},"Merge the expected and posted amounts with how=\"outer\" and treat missing values as zero, so one-sided lines show as differences.","reconcile payroll to general ledger python",null,{"breadcrumb":2068},[2069,2072,2075],{"name":2070,"item":2071},"Home","\u002F",{"name":2073,"item":2074},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":2076,"item":16},"Building Finance and Operations Reports in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger",{"title":2079,"description":2080},"Reconcile Payroll to the General Ledger with Python","Monthly payroll reconciliation in pandas: payslip arithmetic checks, mapping gross pay, employer costs and deductions to GL accounts, comparison by account and cost centre, net pay against the bank file, and an explained-differences workbook.","reconcile-payroll-to-the-general-ledger","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-payroll-to-the-general-ledger\u002Findex",[2084,2085],"report-layouts","joins-and-lookups","how-to","k9WtCFoGddcqoeSlhDwFV9iHWYKFeYMr_ZcevDI-utM",[2089,2093],{"title":2090,"path":2091,"stem":2092,"children":-1},"Reconcile a Stock Count Against System Inventory","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-a-stock-count-against-system-inventory","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-a-stock-count-against-system-inventory\u002Findex",{"title":2094,"path":2095,"stem":2096,"children":-1},"Send Out Budget Templates and Consolidate the Returns","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fsend-out-budget-templates-and-consolidate-the-returns","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fsend-out-budget-templates-and-consolidate-the-returns\u002Findex",[2098,2100],{"path":2099,"title":2076},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports",{"path":2101,"title":2073},"\u002Fautomating-reporting-workflows",[],[2104,2108,2111,2114,2117,2120],{"path":2105,"title":2106,"kind":2107},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","Building Multi-Sheet Excel Dashboards","guide",{"path":2109,"title":2110,"kind":2086},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-data-dictionary-sheet-to-an-excel-report","Add a Data Dictionary Sheet to an Excel Report",{"path":2112,"title":2113,"kind":2086},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-kpi-summary-block-to-an-excel-dashboard","Add a KPI Summary Block to an Excel Dashboard",{"path":2115,"title":2116,"kind":2086},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-table-of-contents-sheet-with-hyperlinks-in-excel","Add a Table of Contents Sheet with Hyperlinks in Excel",{"path":2118,"title":2119,"kind":2086},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-slicer-style-filters-to-an-excel-dashboard","Add a Dropdown Filter That Drives an Excel Dashboard",{"path":2121,"title":2122,"kind":2086},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python","Add a Summary Sheet to an Excel Report with Python",1790972128091]