<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Rails on idiotfan</title><link>https://idiotfan.wang/tags/rails/</link><description>Recent content in Rails on idiotfan</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sat, 22 Aug 2026 15:00:00 +0800</lastBuildDate><atom:link href="https://idiotfan.wang/tags/rails/index.xml" rel="self" type="application/rss+xml"/><item><title>给精酿连锁店做自动化薪酬引擎：从手写考勤、多维营收聚合到 HTML 工资单</title><link>https://idiotfan.wang/posts/damon-salary-automation-system/</link><pubDate>Sat, 22 Aug 2026 15:00:00 +0800</pubDate><guid>https://idiotfan.wang/posts/damon-salary-automation-system/</guid><description>跨两家门店十余名员工、手写排班、多渠道POS、部门×班次复杂提成，我是如何用一套 Python + 腾讯文档 + HTML/Rails 8 体系实现月度一键结薪的。</description><content:encoded><![CDATA[<p>在中小餐饮连锁店，每个月最令人头疼的除了盘点，就是算工资。</p>
<p>在大梦（可能实验室），两家门店涵盖了侍酒师、调酒师、咖啡师、主厨、出品厨师和兼任店长等多种角色。看似人不多，背后的算薪逻辑却极其复杂：</p>
<ul>
<li><strong>考勤源散落各处</strong>：有人在群里发手写考勤表照片，有人交 <code>.xlsx</code>，店长交 <code>.xls</code> 评估表，西湖夜班员工在腾讯文档里打卡。</li>
<li><strong>提成与多维营收挂钩</strong>：薪酬不仅看基本工资，还挂钩<strong>部门业绩</strong>（精酿/调酒/咖啡/厨房）、<strong>班次业绩</strong>（白班/中班/晚班），甚至需要按<strong>部门 × 班次</strong>矩阵进行交叉剥离。</li>
<li><strong>新 SKU 归口漂移</strong>：每个月两家店都会上新酒水或新菜品，如果菜品库没有及时维护，POS 导出的几百条订单就会分错部门。</li>
<li><strong>输出要求高</strong>：算完不仅要在腾讯文档 43 列大表里逐行填平并标注店色，还要给每位员工生成带有公章、大写金额、社保代扣明细的精美工资单。</li>
</ul>
<p>这篇文章记录我如何通过一套组合拳（<strong>Claude Fable 5</strong> 驱动的 Python 管道 + 腾讯文档 API + 响应式 HTML 工资单 + Rails 8 HR 系统集成），将原本需要耗费一整天的结薪工作缩减为 10 分钟自动化流程。</p>
<hr>
<h2 id="一-考勤与数据清洗手写照片与多格式兼容">一、 考勤与数据清洗：手写照片与多格式兼容</h2>
<p>结薪的第一步是收集出勤、法定假日、加班与请假数据。</p>
<p>面对不同来源的考勤资料，我们建立了一条标准化的数据摄取流水线：</p>
<ol>
<li><strong>手写纸质排班表</strong>：通过多模态 Vision 模型直接识别出勤天数与请假备注（休、年假、调休）。</li>
<li><strong>员工个人提交的 xlsx/xls</strong>：使用 openpyxl 和 xlrd 脚本批量读取出勤字段。</li>
<li><strong>腾讯文档夜班表</strong>：调用腾讯文档 API 自动拉取最新的夜班排班记录。</li>
</ol>
<h3 id="考勤规则的程序化">考勤规则的程序化</h3>
<p>在计算出勤时，有几条必须严格执行的业务口径：</p>
<ul>
<li><strong>法定节假日</strong>：按国家法定假日天数计算双倍津贴（<code>基本工资 / 计薪基准 × 天数 × 2</code>）。</li>
<li><strong>加班是「存」还是「换钱」</strong>：员工备注若为「存」，则计入调休池，不折现发放；若为「换钱」，则按小时工资折算为加班费。</li>
<li><strong>月计薪基准的口径区分</strong>：国家劳动法标准的月计薪天数是 <code>21.75</code> 天，而门店按「月休 4 天」的排班体系内部约定了自己的计薪基准。两者绝不可混用——这是薪酬计算里最容易犯的低级错误之一。</li>
</ul>
<hr>
<h2 id="二-破解营收交叉难题伪菜品库与部门班次矩阵">二、 破解营收交叉难题：伪菜品库与部门×班次矩阵</h2>
<p>很多餐饮店算提成算不准，根源在于<strong>POS 订单数据无法自动归口到部门和班次</strong>。</p>
<h3 id="1-伪菜品库反向生成法100-覆盖新-sku">1. 「伪菜品库」反向生成法（100% 覆盖新 SKU）</h3>
<p>早先我们维护了一份静态的「菜品库.xlsx」，但每月一到结薪，发现上月新上的十几款精酿或特调都在库外，脚本只能靠关键词模糊匹配，导致大量调酒被误归入精酿。</p>
<p>为了彻底根治这个问题，我们从 6 月起设计了**「伪菜品库生成器」**（<code>make_menu_lib.py</code>）：</p>
<ul>
<li>直接读取美团/收钱吧 POS 导出的当月「<strong>菜品销售明细</strong>」（包含 POS 真实的「菜品大类」和「菜品小类」）。</li>
<li>脚本根据 POS 大类自动映射四部门，反向为两家门店各生成一份当月专用的全覆盖菜品库（数百个在售 SKU 全部自动归口）。</li>
<li>实测除扑克牌、雨伞等 2 笔杂物外，全部 SKU 100% 自动归口，彻底告别了人工维护。</li>
</ul>
<h3 id="2-部门--班次交叉聚合算法">2. 部门 × 班次交叉聚合算法</h3>
<p>有了准确的菜品归属后，流水线运行 <code>build_analysis.py</code> 进行多维交叉切片：</p>
<ul>
<li><strong>部门业绩</strong>：从全渠道订单中提取含团购套餐的实际到账金额。</li>
<li><strong>班次业绩</strong>：按结账时间（17:00 前为白班，17:00 后为晚班，特定时段为中班）切分。</li>
<li><strong>特殊规则硬编码</strong>：例如滨江后厨团队（主厨与出品厨师）只考核整体后厨部门业绩，班次业绩置 0；前厅运营无特定部门归属，部门业绩置 0。</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># build_analysis 核心交叉切片逻辑示意</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">compute_cross_matrix</span><span class="p">(</span><span class="n">orders_df</span><span class="p">,</span> <span class="n">menu_lib</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 结合伪菜品库匹配部门</span>
</span></span><span class="line"><span class="cl">    <span class="n">merged</span> <span class="o">=</span> <span class="n">orders_df</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="n">menu_lib</span><span class="p">,</span> <span class="n">on</span><span class="o">=</span><span class="s1">&#39;sku_id&#39;</span><span class="p">,</span> <span class="n">how</span><span class="o">=</span><span class="s1">&#39;left&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 结合结账时间戳划分班次</span>
</span></span><span class="line"><span class="cl">    <span class="n">merged</span><span class="p">[</span><span class="s1">&#39;shift&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">merged</span><span class="p">[</span><span class="s1">&#39;pay_time&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">apply</span><span class="p">(</span><span class="n">classify_shift</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 交叉透视表：店 x 部门 x 班次</span>
</span></span><span class="line"><span class="cl">    <span class="n">cross_pivot</span> <span class="o">=</span> <span class="n">merged</span><span class="o">.</span><span class="n">pivot_table</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">index</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;store&#39;</span><span class="p">,</span> <span class="s1">&#39;department&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">columns</span><span class="o">=</span><span class="s1">&#39;shift&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">values</span><span class="o">=</span><span class="s1">&#39;actual_amount&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">aggfunc</span><span class="o">=</span><span class="s1">&#39;sum&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill_value</span><span class="o">=</span><span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">calibrate_totals</span><span class="p">(</span><span class="n">cross_pivot</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>最终脚本自动输出包含 13 个分析页签的月度 Excel，并将部门起伏 MoM 根因下钻到具体 SKU（例如：精酿本月下滑主要由于哪几款酒头缺货）。</p>
<hr>
<h2 id="三-腾讯文档-v2-自动回写与店色渲染">三、 腾讯文档 V2 自动回写与店色渲染</h2>
<p>大梦团队日常使用腾讯文档「员工档案 V2」作为薪酬管理中枢。整个表结构多达 43 列。</p>
<p>算薪脚本在核算完成后，会调用腾讯文档开放接口完成自动化回填：</p>
<ol>
<li><strong>行号探测与防偏移</strong>：自动读取表格末行，锁定当月全员的起始写入行（与上月行段空一行隔开），写完后立即回读校验。</li>
<li><strong>写入动态计算字段</strong>：出勤天数、法假天数、部门业绩、班次业绩、基本工资、加班费、KPI 倍数、管理倍数、全勤行为规范奖、出品提成、社保个人代扣（在册员工按月代扣）、最终私账剩余应发。</li>
<li><strong>自动化店色美化</strong>：
<ul>
<li>西湖店：全行设置浅绿底色（<code>#FFE2EFDA</code>）</li>
<li>滨江店：全行设置浅蓝底色（<code>#FFDDEBF7</code>）</li>
</ul>
</li>
</ol>
<hr>
<h2 id="四-双轨工资单从-html-模板到-rails-8-系统集成">四、 双轨工资单：从 HTML 模板到 Rails 8 系统集成</h2>
<p>算完薪后，如何将工资条体面、私密、优雅地分发给每位员工？</p>
<p>我们采取了「双轨制」交付方案：</p>
<h3 id="1-独立-htmljs-工资单生成器">1. 独立 HTML/JS 工资单生成器</h3>
<p>在本地提供一个自包含的单页应用：</p>
<ul>
<li><strong>复古票据设计</strong>：两店分色边框、水印防伪印章、应发金额自动转换为中文大写（如「肆仟捌佰伍拾元整」）。</li>
<li><strong>智能字段收起</strong>：没有提成的员工自动隐藏提成明细行；加班小时选择「存」的员工自动将加班费显示为 0 并附带文字备注。</li>
<li><strong>一键导出 PNG</strong>：内置 <code>html2canvas</code> 引擎，右上角提供「EXPORT ALL」批量打包，单卡片下方支持单张保存。</li>
</ul>
<h3 id="2-水龙头-hr-系统rails-8--sqlite--tailwind">2. 水龙头 HR 系统（Rails 8 + SQLite + Tailwind）</h3>
<p>为了让店长和管理层具备更系统的历史查询与入职管理能力，我们将这套工资单生成逻辑无损移植进了内网 HR 系统 <code>shuilongtou</code>：</p>
<ul>
<li><strong>无损组件复用</strong>：将 HTML 模板的 CSS 与 JS 渲染核心原样封装为 Rails View Component。</li>
<li><strong>服务端数据注入</strong>：<code>SalarySlipBuilder</code> 服务从 SQLite 读取当月评定记录，构造成标准 JSON 挂载在前端 <code>window.SALARY_DATA</code> 上。</li>
<li><strong>批量与单人导出</strong>：在后台 <code>/salary-slips</code> 页面，店长可以按月份一键预览全员并导出高清图片分发微信。</li>
</ul>
<hr>
<h2 id="结语">结语</h2>
<p>在服务实体餐饮的过程中，我们往往容易走向两个极端：要么沉迷于引入庞大昂贵的全套 SaaS 软件，结果员工根本不会用；要么退回原始的手工复制粘贴，每个月重复踩坑。</p>
<p>这套以 <strong>Python 脚本打通数据流、腾讯文档作为协作底座、轻量 HTML/Rails 提供交互展示</strong>的架构，恰好找到了那个平衡点：低成本、极度灵活，并且能在业务规则演进时随手调整。</p>
<p>当技术真正落进日常的柴米油盐与账目明细里，解决真实世界的琐碎麻烦，那种踏实感才是最迷人的。</p>
<hr>
<h3 id="-项目环境与模型署名">🛠️ 项目环境与模型署名</h3>
<ul>
<li><strong>主导逻辑与代码构建</strong>：Anthropic <strong>Claude Fable 5</strong>（桌面端默认模型）</li>
<li><strong>多模态考勤识别</strong>：Claude Fable 5 视觉能力（手写纸质排班表与多源照片读取）</li>
<li><strong>客户端与执行环境</strong>：<strong>Claude Desktop 桌面端</strong>（定制 <code>dameng-salary</code> Skill + <code>tencent-docs</code> MCP）</li>
<li><strong>系统技术栈</strong>：Python 3 (<code>pandas</code>, <code>openpyxl</code>, <code>xlrd</code>) + 腾讯文档 OpenAPI + Rails 8 (<code>shuilongtou</code> 员工管理系统) + HTML/CSS 矢量打印模板</li>
</ul>
]]></content:encoded></item></channel></rss>