# ERPLite **Repository Path**: known/ERPLite ## Basic Information - **Project Name**: ERPLite - **Description**: ERPLite是一个基于Known框架开发的轻量级中小企业ERP管理系统。 - **Primary Language**: C# - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 129 - **Forks**: 61 - **Created**: 2015-05-04 - **Last Updated**: 2026-10-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: blazor, dotNET, ERP ## README 
Logo
![.NET](https://img.shields.io/badge/.NET-10-512BD4) ![Blazor](https://img.shields.io/badge/Blazor-WebAssembly-7A43B6) ![Known](https://img.shields.io/badge/Known-4.0-orange) ![License](https://img.shields.io/badge/License-AGPL%203.0-blue) > 面向中小企业的通用、轻量、可扩展 ERP 系统基座(模块化单仓方案)。 ## 🎯 项目定位 `ERPLite` 以“业财税一体化 + 模块可插拔”为目标,提供: - 通用业务基座(物料主数据、编码规则、库存引擎、状态词表、菜单与权限装配) - 销售、采购、生产、仓储、人事、财务六个开箱可用的业务模块 - 面向**制造型中小企业**的业务主线:`BOM` → 缺料分析 → 生产工单 → 领退料 → 完工入库 - 单据状态机与信用、库存、成本之间的自洽约束(不是只有增删改查的界面壳子) ## ✨ 功能特性 已实现(六个业务模块共 50 个页面,均为「列表 + 单据录入 + 详情/看板」三件套;另有 `Known.Admin` 提供的 2 个权限页面): - **销售管理**:客户档案与实时信用占用、销售订单、发货单、**销售退货单**(行级溯源、部分退、换货自动派生补发)、订单全流程状态机与信用门禁 - **采购管理**:供应商档案、采购订单、收货验收(含合格量/批次)、**采购退货单**(按收货行退、冲减应付与进项税)、供应商准时率与来料合格率分析 - **生产管理**:`BOM` 多层展开(防环)、缺料分析(`MRP`)、生产工单与工序报工、排程甘特、领退料、质量检验 - **仓储管理**:多仓库/货位、库存台账与移动加权平均成本、出入库流水账、库存预警、盘点调平(差异自动过账:先挂 `1901 待处理财产损溢`,再按批准意见结转 `6602` / `6711`)、货位调拨 - **人事管理**:组织架构、员工档案与入转离、考勤记录与月度汇总、薪资生成、人事分析看板 - **财务管理**:会计科目、记账凭证(借贷平衡校验、过账、红字冲销)、应收应付与账龄分析、收付款核销、成本差异、**期末结转**、科目余额表/资产负债表/利润表 - **业财一体**:基座 `ErpPosting` / `ErpLedger` 过账引擎,八类业务动作在**同一事务内**自动生成财务凭证——销售发货(借应收/贷收入+销项税,借成本/贷库存)、采购收货(借存货+进项税/贷应付)、完工入库(借产成品/贷生产成本—直接材料—直接人工)、生产领退料(借直接材料或制造费用/贷各存货科目)、**委外发料与收回**(发料借 `1408 委托加工物资`/贷原材料;收回一张四行做平三件事:借产成品+进项税/贷 1408 +应付)、**薪酬四步链**(工资计提 → 企业社保计提 → 发放拆代扣 → 缴纳划平,四步走完四个薪酬相关科目全部归零)、**收付款结算**(收款借银行存款/贷应收账款、付款反向;现金走 `1001 库存现金`;支持一笔拆核多张账单)、**期末结转**(损益累计净额并入 `4103 本年利润`)。作废与反审核同步冲销:未过账凭证直接作废,已过账凭证开红字凭证,应收应付台账一并冲回。凭证以「来源类型 + 来源单号」为幂等唯一键,重复提交不会记第二遍,且提示会明确说「已缴纳过,本次未重复缴纳」。 - **系统管理**:账号管理、角色管理(模块 / 按钮 / 栏位三级授权,由 `Known.Admin` 插件提供)、**数据备份**(整库快照的手工与每日自动两路、下载、保留份数滚动清理,附恢复说明书)、**数据导入**(物料 / 客户 / 供应商 / BOM / 期初库存 / 期初往来六类 xlsx 模板,预检精确到行列、整批事务回滚、导入日志)、**单据审批流**(见下条) - **单据审批流**(需启用「申请中心」插件):**14 类单据**的审核可改走可配置的审批链(会签 / 或签 / 票签、条件分支、加签、撤回)——销售订单、采购订单、销售发货、销售退货、采购收货、采购退货、记账凭证、收付款单、库存盘点、货位调拨、生产工单、生产领退料、委外收回、人事申请。建议审批链自带**金额分级**:门槛写进那一级节点的自动通过表达式(如销售订单「财务主管(满 50 万)」「总经理(满 200 万)」),小额单一路放行、不必惊动上级,图仍是一条直线。设计器里分级**不用写表达式**:节点属性把「免审条件」做成「字段 / 比较符 / 阈值」三件控件(`amount` `<` `500000`),下方直接用普通话写出「金额不到 500000 时本级自动通过」;打开别人配好的流程时会自动识别并回填,认不出的复合式(`amount < 500000 && region == "外销"`)原样留在「自定义表达式」模式,绝不拆半。工单没有金额,门槛落在交期上(距计划完工 ≤7 天算加急才惊动厂长)。配置入口在 `系统管理 → 单据审批流`:点「配置流程」按建议审批链生成草稿 → 进流程设计器微调 → 发布 + 启用;此后点「审核」不再直接改状态,而是把单据送进流程,**最后一级通过时才执行真正的审核**——信用占用、库存、凭证与手工审核走的是同一段代码。三道保险:**门禁在服务端**(`AuditXxxAsync`,绕开页面直接调接口一样被拦)、**未配置即现状**(不装插件、不建流程、停用流程,审核行为逐字不变)、**回写走旁路**(终审回写不会被自己的门禁再拦一次,不会"批完却又发一条新流程")。无提交环节的单据(收付款 / 退货 / 盘点 / 调拨 / 领料 / 委外收回 / 发货 / 收货)发起审批时会自动锁成「待审核」,审批中改不了也删不掉;驳回退草稿,与作废严格分开。销售订单 / 采购订单 / 生产工单详情页有**审批进度条**:已批的绿点、在等的黄点、驳回的红点,停在某一级可看意见与时间。审批人不在时用**审批代理**(`申请中心 → 审批代理`):出差前把待办交给同事、限定时段与适用单据范围,代理人待办里标「代 张三」,批完轨迹里同时留下两个人的名字——**授权而非转移**,归属始终在委托人身上,停用即时收权。待办页支持**批量通过**(勾选后一次批完,逐条独立事务,一条失败不拖累其余;不提供批量驳回,驳回要写理由)。工资表与 BOM 版本**刻意不接**(账期批次与主数据没有单据级状态位可锁,接了会变成"审批中内容还能被重算"的假审批),理由见设计文档 §十。设计见 `Document/单据审批流设计方案.md`。 - **打印与套打**:十类模板——送货单、销售订单、采购订单、收货单、领料单、完工单、记账凭证、工资条、客户对账单、供应商对账单;A4 或针式自定义尺寸(如 `241×140 mm`),浏览器打印或另存 PDF,金额大写由服务端生成 - **Excel 导出**:15 个列表页把「当前筛选后的行」导出为 xlsx(库存台账、凭证、三表、工资、往来等),数字保持数值类型,浏览器直接下载 - **电子附件**:凭证、销售订单、采购订单详情可上传合同 / 发票 / 签收单照片,支持图片、PDF、文本预览与下载;按「模块 + 业务 Id」绑定,删除仅限上传者本人或超管 规划中(尚未落地,接口与表结构未预留): - **资产管理**:资产台账、折旧计提、资产盘点、变动处置 - **税务管理**:进销项发票、纳税申报、税负预警、税务档案 ## 🧩 解决方案结构 ```text ERPLite/ ├─ Deploy/ # 交付件:Dockerfile、publish.ps1、restore.ps1 / restore.sh ├─ Document/ # 项目文档与截图 ├─ ERPLite/ # 基座层:物料、编码规则、库存引擎、会计原语与过账引擎、状态词表、复用组件、登录 ├─ ERPLite.Sale/ # 领域模块:销售 ├─ ERPLite.Purchase/ # 领域模块:采购 ├─ ERPLite.Produce/ # 领域模块:生产 ├─ ERPLite.Warehouse/ # 领域模块:仓储 ├─ ERPLite.Human/ # 领域模块:人事 ├─ ERPLite.Finance/ # 领域模块:财务 ├─ ERPLiteWasm/ # 客户端:Blazor WebAssembly 入口、工作台、跨模块聚合与种子数据 ├─ ERPLiteWeb/ # 服务端宿主:应用入口、WebApi 端点、SQLite 加固、每日自动备份、日志 └─ ERPLite.slnx # 解决方案文件(含 ../Known 框架引用) ``` > 各业务模块统一包含:`Entities`(实体)、`Services`(`[WebApi]` 契约 + `[Service]` 实现)、`Pages`(Razor 页面)、`Models`(投影与 DTO),以及一个 `Extension.cs` 装配入口。 **依赖方向是单向的**:业务模块只依赖基座 `ERPLite`,模块之间互不引用。跨模块的汇总(工作台待办、经营指标)统一放在 `ERPLiteWasm`,因为它是唯一被允许同时看见六个模块的项目。库存与单号生成、凭证与往来台账这些必须与业务单据同事务完成的动作,做成基座的静态引擎(`ErpStock`、`ErpCode`、`ErpPosting`、`ErpLedger`),由调用方把事务内的 `Database` 实例传进去——引擎自身不开事务,否则凭证会落到外层事务之外、回滚不掉。 因此**会计科目、凭证、分录、应收应付这四类实体属于跨模块共享的会计原语,放在基座而不是财务模块**:销售发货要生成凭证,若凭证在财务模块里,销售就得反向依赖财务。财务模块保留的是报表、成本归集与账龄分析这些纯核算视图。库存原语(仓库、货位、台账、流水)同理。 ## 🔌 定制与扩展(一份核心,多个客户) 开源核心与私有定制之间只有一条线:**客户名字不允许出现在核心代码里**。为此核心提供三个扩展点, 外加一份部署期清单,客户差异全部落在自己的私有包中。 **① 装配清单——装哪些模块、按什么顺序装**(`ErpModules`)。基座 `ERPLite` 恒装,六个业务模块由 appsettings 点名;没被点名的模块其页面、菜单、服务与建表全部撤下,其所在侧栏分组因空而被框架自动裁剪。 同一份核心因此可以装配成全套 ERP,也可以只装供应链。 ```jsonc // ERPLiteWeb/appsettings.json 与 ERPLiteWasm/wwwroot/appsettings.json(两份必须一致,有用例把关) "ErpModules": { "Enabled": [ "sale", "purchase", "warehouse", "produce", "human", "finance", "cust.demo" ] } ``` 登记顺序即服务实现的解析顺序(框架按后登记者胜出),所以**客户扩展包一律排在核心模块之后**; 新增一个模块是在宿主的目录表里加一行,基座与各业务模块零改动。清单里写了不存在的 id 会拒绝启动—— 静默少装一组的表征与「权限没配」完全同形,现场查起来最费时间。 **② 单据动作条插槽**(`ErpDocAction` / `ErpDocActions`)。客户要在订单页加一颗按钮,登记一条动作即可, 不必改 `OrderDetail.razor` 与 `DocActions.razor`: ```csharp ErpDocActions.Add(new ErpDocAction { Id = "push-oa", Scope = "/sale/order/detail", Text = "推送 OA 审批", Icon = "send", Action = "push", Statuses = [ErpStatus.Submitted], Confirm = "推送后不可撤回", HandlerType = typeof(PushOaAction), // 实现 IErpDocAction,逻辑留在扩展包里 }); ``` 作用域缺省按当前页面路由推断,因此**没有要求 46 个页面逐个补参数**(那等于把改核心的成本换了个地方付); `Scope` 支持 `/sale/*` 与 `*`。按钮权限复用框架的 `Button.Action` 判定,无权即整颗不渲染。 内置流转与扩展动作共用 `Busy`、状态收拢与确认弹层,不会出现两套禁用逻辑。 **③ 字段字典**(`FieldDef` / `ErpExtFields`)。客户要加几栏,不建数据库列、不改实体、不改页面: 值落在 `EntityBase.Extension` 这个 JSON 槽里,定义进 `ErpFieldDef` 表,在「基础数据 → 自定义字段」维护。 控件由框架的 `FieldEditor` 按类型自动选;支持文本/整数/数值/是·否/日期/单选六种。 写入经服务端逐项校验(含必填、类型、选项、长度),且拒绝未定义的键,扩展槽不会长成谁都能塞的抽屉。 **停用或删除定义都不抹已录入的值**——重建同名字段即重新显示。可挂字段的实体由代码里的 `[ErpExtensible]` 显式声明,明细行不支持。当前五个实体都已把这三处接完(列表扩展列 + 录入表单 + 详情只读摊出):**销售订单、采购订单、生产工单**走独立路由页,**物料主数据、客户档案**走弹窗录入 (弹窗里用两列网格,四列会把每栏压到 140px)。演示种子 `SeedFields` 给这五个实体各配了 3–5 栏、 六种类型全覆盖,并给一部分历史记录回填了值,打开页面即可看到效果。 三条路的成本: | 客户的改动 | 落点 | 是否改核心 | | --- | --- | --- | | 新页面 / 新菜单 / 新实体 | 扩展包加一个带 `[Menu]` 或 `[Table]` 的类型 | 否(框架 `Config.AddModule` 本就自动装配) | | 覆写某个服务的业务逻辑 | 扩展包实现同一契约,注册顺序在后即胜出 | 否 | | 单据保存前后的插桩 | 扩展包的 `CrudHook` | 否(仅运行时 `SchemaService` 路径生效,编译期实体不走) | | 在既有页面加一颗按钮 | `ErpDocActions` 登记 | 否(②) | | 加几栏自定义字段 | 字段字典里配 | 否(③) | | 让某个列表页显示扩展列 | 该页加一段 `@foreach`(销售订单列表已有样板) | 是,一次性 | **代码管理**:开源核心单仓单 `master`,只打 tag,不开客户分支;每个客户一个私有仓库,里面只有 `ERPLite.CustX` 程序集,按 tag 引用核心,**不把核心源码复制进客户仓**。一次交付 = `(核心 tag, 装配清单, 客户包 commit)` 三元组,据此可复现任何客户的历史版本。禁止两件看似省事的事:用 `git branch cust-A` 表达客户(分支不能双向合并,六个月后是在手工 diff 三套 ERP),以及在核心里写 `#if CUSTOMER_A` (N 个客户即 N 次构建,且开源部分被私有逻辑污染)。通用改进一律以 PR 回流 `master`。 当前的刻意边界:不共用数据库做多租户(框架读取不按 `CompNo` 自动过滤,一客户一个 `.db` 文件反而是最强 隔离);不做运行期动态装载(业务私有包本就随项目编译发布,`AssemblyLoadContext` 只增加攻击面与调试成本); 字段字典不做引用型字段与明细行扩展(前者要复制一套运行时取数,后者要逐行一个槽,都不该顺手做)。 ## 🛠️ 技术栈 - `.NET 10`(主目标框架) - `Blazor WebAssembly` + 服务端宿主(`[WebApi]` 接口经 `DispatchProxy` 生成客户端代理,前后端同构) - `Known 4.*`(组件库、微 `ORM`、菜单与权限装配、主题与国际化) - `SQLite`(默认,表结构由框架启动时按 `[Table]` 非破坏式创建) ## 🚀 运行方式 ### 📌 环境要求 - `.NET 10 SDK` - 同级目录下的 `Known` 框架(解决方案按 `../Known/Known` 引用,未发布为 NuGet 包) ### ▶️ 启动命令 ```bash dotnet restore dotnet run --project ERPLiteWeb ``` 浏览器打开 `https://localhost:50001`(或 `http://localhost:50000`)。 首次启动会自动建表并写入一套演示数据:一家约 60 人的精密机械加工厂,含物料、`BOM`、客户、供应商、库存、各类单据、考勤薪资与凭证,工作台开箱即有待办与指标。数据幂等——已初始化过则整段跳过,不会覆盖你改过的内容;想重跑演示数据,删掉根目录的 `ERPLite.db` 再启动即可。 ### 🔑 演示账号(只在演示档存在) 演示档(`ERPLite:SeedDemoData = true`,即开发/评审用的缺省档位)首次启动会写入七个**库里的真实后台账号** (`SysUser` + 角色 + 归属,PBKDF2 散列,绝不明文入库),登录页直接列出快捷入口,口令统一 `erplite`: | 账号 | 身份 | 账号 | 身份 | | --- | --- | --- | --- | | `admin` | 系统管理员 | `warehouse` | 仓管员 | | `sales` | 销售经理 | `hr` | 人事专员 | | `purchase` | 采购主管 | `finance` | 财务主管 | | `produce` | 生产主管 | | | 种子幂等:已有同名账号整块跳过,不覆盖你在「账号管理 / 个人中心」改过的口令与资料。 **把 `SeedDemoData` 置 false(生产档)之后,后面六个演示账号就不存在了**——它们是给人看演示的道具, 不该躺在真实工厂的账号表里当七个口令相同、人人可登的入口;登录页那句口令提示与那排角色卡片同时消失。 生产档仍然会建:七个**角色**(`销售经理`/`采购主管`/…,服务端 `[ServiceRoles]` 点的是角色名,第一天就要有人可指派) + 一个**引导账号** `admin`。它的口令按以下顺序确定: 1. 环境变量 `Admin__BootstrapPassword`(推荐,见下面的上线清单); 2. 没注入 → 进程启动时随机生成 16 位口令,**只在启动日志里打印一次**,去日志中捞。 任何一档都不再有公开的缺省口令:`Admin:BootstrapPassword` 留空时框架那条「不查库的保底登录路」直接关闭 (`ConfigCredentials=false`),而不是回落到 `admin123`。 ### 🔐 上线前必改清单 按顺序做完这六条,才算把一份演示代码变成能进厂的系统。前四条都在配置里,改错一条的后果多半是「看着正常、 数据早没了」,所以每条都写了怎么验证。 1. **`ERPLiteWeb/appsettings.Production.json` 里的 `ERPLite:SeedDemoData` 保持 false, 并把 `ERPLiteWasm/wwwroot/appsettings.json` 的同名键也改成 false。** 两份文件分开是刻意的(客户端那份随站点公开下发,读不到服务端配置),但值必须一致, `ModuleManifestTests` 会盯着模块清单与这个开关。验证:登录页不再出现演示口令提示与角色卡片, 「账号管理」里只有 `admin` 一行。 2. **注入引导口令**:`Admin__BootstrapPassword=<一条只有你们知道的口令>`(环境变量,写进 systemd 的 `Environment=` 或容器的 secret,**不要**写进任何 appsettings 文件)。 验证:不带该变量启动一次,日志应出现「已为账号 admin 生成一次性口令」,且 `erplite`/`admin123` 都登不进去。 3. **库文件指到独立数据卷的绝对路径**:`ConnString=Data Source=/var/lib/erplite/ERPLite.db`。 基础档里那个 `Data Source=..\ERPLite.db` 是相对**当前工作目录**的,换成服务/容器后目录一变, 轻则新建一份空库(老库还在原地没人读)、重则落进随机可写目录。验证:启动日志里的建表路径与数据卷一致。 4. **改成贵司名称**:`ERPLite:CompanyName`(两份 appsettings 都要改,它同时是打印页眉与报表抬头)。 5. **给业务角色收紧授权**:种子只按职责铺最小集(`SeedAccounts.RoleGrants`),仍可能与贵司的岗位职责不同。 在「角色管理」里逐角色勾;改动不会被下次启动的种子覆盖——授权行只在「该角色一条都没有」时铺。 同一句话的反面也要记着:**升级后新出现的模块不会自动铺给已有角色**(老库里那些角色早就有授权行,种子整块跳过), 所以换版本后要专门去「角色管理」把新模块勾一次——最典型的就是「数据备份」,不勾的表征是财务主管点下载拿到 403, 而不是任何报错。注意**看得见不等于改得动**:页面授权决定菜单,写接口另由服务端 `[ServiceRoles]` 把关,两边都通才算放行。 6. **打开「系统管理 → 数据备份」把自动备份设好**(每日时刻 + 保留份数,改完即生效不用重启), 并定期把列表里的文件下载到另一台机器。SQLite 只有一个文件,它丢了就是全丢了; 同一块盘上的备份不算是备份。 ## 🗄️ 备份与恢复 SQLite 只有一个文件,它丢了就是全丢了。这一节是基座自带的三条路,都不需要停机。 **① 页面上手工备份**(`系统管理 → 数据备份`,缺省只给「财务主管」角色):点「立即备份」出一份整库快照。 快照走 SQLite 的 `VACUUM INTO`,不是拷文件——正在写入的 `.db` 旁边还压着 `-wal` 里尚未回写的页, 手工拷出去的是一份半截库;`VACUUM INTO` 由 SQLite 在事务里重排出一份完整、紧凑的单文件副本, 备份期间业务照常录入。文件名形如 `ERPLite-20261030-091500-manual.db`,落点由 `ERPLite:BackupDir` 决定 (缺省 `{库目录}/data/backups`)。列表里能下载、能删除;手动档**永远不受保留份数约束**, 定时任务只清自动生成的那批。 **附件不在 `.db` 里**:电子附件字节落在 `{库目录}/uploads`(缺省;可用 `Admin:UploadRoot` 指到独立数据卷), 库里只存元数据。所以「备份」要覆盖的是**库 + 整棵 uploads 目录**;只拷 `.db` 再恢复,附件清单还在、文件会打不开。 跨天恢复时把同期的 `uploads` 一并换回,页面上才不会出现「有记录、点开 404」。 **② 每日自动备份**:同一个页面上设时刻与保留份数,跑在 Web 宿主自己的后台任务里(不额外起常驻进程、 不引调度库)。判据是「今天还没有自动档」而不是「时刻到没到」,所以 03:00 恰好断电或改版, 起来之后会自己补上今天这一份。设置存在库里的 `SysConfig`(键 `Erp.Backup`),改完即生效、不用重启站点 ——这一条是给没有服务器文件权限的财务主管留的。 **③ 恢复走脚本,页面上刻意没有「恢复」按钮**:要覆盖的 `.db` 正被本进程打开着,覆盖之后旧连接还挂在 原文件句柄上,表现是「点了恢复、刷新一下数据没变」——那种假成功比一句报错难查得多。 ```powershell # Windows(站点已停:-StopService 会让脚本自己去停和再起) .\Deploy\restore.ps1 ERPLite-20261030-091500-auto.db -Database D:\erplite-data\ERPLite.db -StopService erplite ``` ```bash # Linux(systemd 托管:--stop 会让脚本自己去停和再起) ./Deploy/restore.sh ERPLite-20261030-091500-auto.db /var/lib/erplite/ERPLite.db --stop erplite # 容器:数据在卷上,进程用 docker 管,脚本只负责覆盖与校验 docker stop erplite ./Deploy/restore.sh ERPLite-20261030-091500-auto.db /var/lib/erplite/data/ERPLite.db --force docker start erplite ``` 脚本按「停站 → 先给现库另存一份 → 覆盖并清掉 `-wal` / `-shm` 旁文件 → 校验文件头与 SHA256」的顺序做, 最后要人自己核对两处:物料档案条数、最新一张销售订单号。都对上了才算恢复完成。 **下载那条地址是逐人把关的**:`/backup/{文件名}` 由宿主自己映射,按 `b_数据备份_download` 这个按钮授权判定, 不在框架那条只验「登录了没有」的附件端点上——整库快照里装着工资表和成本单价,而文件名是按时间戳排的、可猜。 **库文件请放本机磁盘或独立数据卷**。放在网络共享盘上时 SQLite 会静默退回非 WAL 模式(不报错、页面照开), 代价是读写互相阻塞、偶发「database is locked」;备份页顶部那行「日志模式」显示的是**回读到的真实值**, 不是我们想要的那个值——它不是 `wal` 就该挪库了。同理,`ConnString` 请用绝对路径:相对路径按进程的 当前工作目录解析,`dotnet run` / Windows 服务 / 容器三种起法会各自指向不同的库。 ## 🚢 部署 ### 容器(推荐) Known 未发布 NuGet 包,镜像构建要两棵树,所以框架用 BuildKit 的命名上下文挂进来(Docker 23+ 默认支持)。 在 ERPLite 仓库根目录执行: ```bash docker build -f Deploy/Dockerfile --build-context known=../Known -t erplite:1 . docker run -d --name erplite -p 8080:8080 \ -v erplite-data:/data \ -e Admin__BootstrapPassword='一条只有你们知道的口令' \ -e ERPLite__CompanyName='某某精密机械有限公司' \ erplite:1 ``` 库(`/data/ERPLite.db`)、备份(`/data/backups`)与日志都在同一个卷里:换镜像不动数据,备份也天然跟着卷走。 `/data` 请落在命名卷或宿主机本地目录上——绑到 NFS/CIFS 挂载点会踩上面说的 WAL 静默降级。 ### Windows 服务 / IIS ```powershell .\Deploy\publish.ps1 -OutputDir D:\deploy\erplite # 产出目录里连 restore 脚本一起带好 ``` 发布完照它末尾打印的那六条清单走一遍(全文见上面「🔐 上线前必改清单」)。挂成 Windows 服务: ```powershell sc.exe create erplite binPath= "C:\dotnet\dotnet.exe D:\deploy\erplite\ERPLiteWeb.dll" start= auto sc.exe config erplite depend= tcpip ``` 服务的**当前目录是 `System32`**,所以 `ConnString` 必须是绝对路径(这也是基座把相对路径按内容根折成绝对值的原因, 但别让部署靠兜底活着)。IIS 则把应用池的「专用进程」工作目录与标识配好,并同样用环境变量注入口令。 ## 📱 手机端(PWA) 站点可「添加到主屏幕」当 App 用:登录后按设备自动落在 `/app`,底部四个 Tab(首页 / 待办 / 工作台 / 我的)。 页面跟着业务模块走(`ERPLite.Xxx/Apps/`),与桌面页同工程、同服务、同模型,**不另建一套接口**。 **手机端只做现场要办的事**,这是这一块唯一的设计前提: | 页面 | 内容 | |---|---| | 首页 `/app` | 待办合计 / 待审核单 / 逾期未收三个数 + 卡住的事 + 模块宫格 | | 待办 `/app/todo` | 审批待办(代理来的标「代 某某」)、批量通过、整页办理(单据快照 + 轨迹 + 同意/驳回) | | 工作台 `/app/work` | 按模块分组的全部入口,可见性 = 桌面菜单可见性(同一份授权快照) | | 我的 `/app/mine` | 账号、深色主题、回电脑版、退出 | | 销售 `/app/sale` | 四项经营数字、订单列表(近 90 天,状态分段 + 搜索)、订单详情(客户信用 / 明细 / 审批轨迹 / 提交 / 审核 / 退回)、**发货执行**(扫单号按行填量 → 生成草稿 → 审核出库) | | 采购 `/app/purchase` | 四项数字(本月采购 / 待审核 / 待收货 / 逾期未到)、**扫码收货**(扫单号 → 待收行 → 填到货 / 合格 / 批次 → 生成草稿 → 审核入库) | | 仓储 `/app/warehouse` | 库存金额 / 在库品种 / 低于安全库存 / 呆滞、**扫码查库存**(各仓现存量 + 最近流水)、库存预警、盘点单列表与**扫码盘点**(扫一件加一件,差异实时) | | 生产 `/app/produce` | 在制工单 / 本月完工 / 待领料 / 缺料项、工单列表(近 180 天,状态分段 + 进度 + 缺料小标)、工单详情(工序 / 用料 / 提交 / 审核 / 开工)、**工序报工**(扫工单号,合格数与工时按默认值预填,只改不对的那个数)、**领退料**(扫工单号按定额领 / 退 → 生成草稿 → 审核)、领退料单列表(草稿就地审核)、**完工入库**(数量默认剩余量,显示单位成本与凭证号) | **扫码不另做中转页**:每个功能页自带扫码(收货页扫单号、盘点页扫物料),不做"先扫再问你想干什么"的中枢—— 多一次选择就多一次犹豫,而现场的人已经知道自己要干什么。二维码内容就是单号或物料编码的文本, 贴一张打印的二维码在料架或送货单上即可。相机取流需 HTTPS,非安全环境自动退回 PDA 键盘仿真或图片解码。 **审批与桌面完全同源**:手机上批的那一票写进同一张 `SysFlowTask`,会签、代理、越权闸门、回写全部一致。 配了审批流的单据在手机上点「审核」不会直接改状态,而是被告知「正在审批中,停在「财务主管」」并给出「去待办」。 **刻意不做**:新建订单、改单、凭证录入、BOM 编辑、工资核算、报表设计。判据只有一句—— 这件事发生时,人是不是不在工位?是就做,不是就回桌面。 已开放的是第一、二、三批(骨架 + 审批闭环 + 销售查询 + 扫码现场 + 生产报工);员工自助与财务按批次推进, 方案与逐批落地记录见 `Document/手机端设计方案.md`。 ## ⚠️ 已知边界 以下内容是当前的刻意取舍,接入真实企业前需要先补齐: - **自动过账已覆盖八类动作(另有两类补充:销售 / 采购退货红冲、盘点差异挂 1901 后结转损益)**:销售发货、采购收货、完工入库、生产领退料、委外发料与收回、薪酬(工资计提/社保企业部分计提/发放/缴纳)、收付款结算、**期末结转**。各动作的作废/反审核冲销均已接入,结转可撤销后重做。 - **数据导入只新增、不覆盖**:物料 / 客户 / 供应商编码已存在会整批拒绝,避免静默改写被外键引用的档案;BOM 同编码同策略;期初库存与期初往来只能导一次,重导须先冲销原批次。考勤仍是粘贴 CSV 文本,未改成文件导入。 - **打印是浏览器打印,不是像素级套打**:纸宽 / 纸高可按针式打印机自定义(`?paper=continuous&w=241&h=140`),但版式调整要改打印页 CSS,没有可视化模板设计器;目前覆盖十类单据 / 对账单,销售与采购退货单已有单据但还没有打印模板,报价、发票类模板随单据一起待做。 - **电子附件走 JSON base64**:单文件大小受「系统设置 · 单文件上限」约束(默认 10 MB),大文件应放共享盘;附件不参与单据状态机,已过账凭证也能补传发票照片,但它不改变任何金额。 - **导出的范围是「当前筛选后的全部行」**,不是分页后的当前页,也不是未筛选的全表;分页、关键字、状态筛选改了什么,导出跟着变。 - **薪酬是一条四步链**:① 计提工资负债(按部门拆人工费用)→ ② 计提企业承担社保公积金(同事务自动一并产生)→ ③ 发放(冲减工资负债,把代扣的个税/社保/工会经费拆成对第三方的负债)→ ④ 缴纳(含企业部分一次划平)。四步走完 `221101`/`221102`/`222103`/`224101` 四个科目全部精确归零,这是这条链的自检指标。企业社保未计提时缴纳会被拒绝,否则 `221102` 会被缴成负数。 - **一笔收付款只能对一个往来对象核销**:跨客户的合并收款要拆成多张单,否则凭证的往来辅助核算会失真。支持一笔拆核同一对象的多个账单、也支持一个账单被多笔收款凑齐;预收/暂收(收得比欠款多)明确拒绝并提示改走预收科目。 - **委外在制余额与科目余额双向勾稽**:「委外加工」页的 1408 余额按业务单据(已审核发料 − 已审核收回结转)算,不读凭证——凭证是单据的派生物,用派生物校验原物会在过账规则调整时两头对不上。两侧口径一致已由实测确认(看板 568.25 = 科目余额表期末借方 568.25)。委外收回的材料成本按比例结转,末批吃掉全部余额以吸收尾差。 - **完工成本按实际归集,制造费用仍未分摊**:完工入库的单位成本 = 本工单实际领料额按完工数量占比结转 + 实际工时 × 工单费率;一分料都没领(试制、漏录)时才回退到 BOM 底层料标准成本估算并标注。制造费用未做真实分配。完工入库支持**按批退回**(反向出库 + 红冲该批凭证 + 回退完工数),入口在工单详情「完工入库记录」;成品已被下游消耗时拒绝退回。 - **自动凭证的分录模板写死在代码里**:科目映射集中在基座 `ErpAccounts`(含 `StockAccountFor` / `IssueExpenseFor` / `LaborAccountFor` 三个函数),改口径只动一处,但仍需改代码。生产环境应做成可配置的「过账规则」表。 - **报表只取已过账凭证**,而演示种子把部分销售收入凭证留在「已审核」状态、却把对应的收款凭证过了账,于是资产负债表上 `应收账款` 出现负数、`应交税费` 呈借方余额(留抵)。这是种子数据的凭证状态没配平,不是报表逻辑问题——把销售凭证一并过账即可归正。 - **资产负债表按原型口径自动补行**:成本类科目(`5001 生产成本`、`5101 制造费用`)的期末余额作为在制品/未结转制造费用并入资产侧;权益侧在期末结转落地前,按损益类科目累计净额折算一行「未分配利润」。**结转完成后该行自动归零并被抑制**,改由 `4103 本年利润` 作为正常权益科目列示——两者恒有一个为零,不会重复计。 - **期末结转要求本期凭证全部已过账**,且不存在更晚期间的结转,否则拒绝。结转金额只按已过账凭证计算,有草稿挂着就结转会把将来才产生的损益漏在本期之外、本期利润算少。结转凭证的来源类型固定为 `carry_forward`、凭证字「结」,**利润表与财务总览取数时按它剔除**——不剔除的话结转分录会把 6001 主营业务收入借记清零,利润表会把自己冲成 0;科目余额表与资产负债表则必须包含它。 - **利润表是单步式**(收入 − 成本 − 期间费用),报表行按科目自动成行、不可配置;生产环境应接可配置模板做多步式(营业利润→利润总额→净利润,含所得税费用)。 - **自动凭证的分录模板写死在代码里**:科目映射(`1122` 应收、`6001` 收入、`222101` 销项税、`6401` 成本、`140501`/`140502` 库存)以常量形式集中在基座 `ErpAccounts`,改科目表要改代码。生产环境应做成可配置的「过账规则」表。 - **收入不含订单级折让**:自动分录按本张发货单的不含税金额与税额确认收入,整单折让不在发货环节摊,属期末处理。 - **缺料分析的「采购在途」恒为 0**:生产模块不引用采购模块,因此读不到未到货的采购订单量。 - **登录凭据以 `Known.Admin` 的账号表为唯一来源**:演示档的七个账号由宿主种子 `SeedAccounts` 落进 `SysUser`(PBKDF2 散列,绝不明文入库),生产档只留引导账号 `admin`(口令来自环境变量或一次性随机值,见「上线前必改清单」)。基座原先那份写死在代码里的 `ErpAuthService` 已删除。因此「个人中心」能自助改资料与口令、「账号管理」有真实数据、`Known.Admin` 的菜单授权体系也随角色播种而真正上线。`AdminOption` 的引导账号只保留为「账号表被清空 / 库文件读不动」时的最后一把钥匙,库里同名账号优先,它盖不住;`Admin:BootstrapPassword` 留空等于关掉这条路,不回落公开口令。 - **缺省授权按职责给最小集**(`SeedAccounts.RoleGrants`):销售经理看不到记账凭证、人事改不动库存,第一天就上生产也不会出现「人人全能」。这份矩阵与各模块契约上的 `[ServiceRoles]` 是成套的——页面授权管「看得见」,服务端角色门禁管「改得动」。岗位职责因企业而异,收紧或放宽请在「角色管理」里勾;种子只在某角色一条授权行都没有时铺缺省,不覆盖运维的改动。 - **按钮级权限靠动作码词表**:`DocActions` 的七颗内置流转按钮(提交/审核/退回/反审核/关闭/作废/删除)与 37 个页面上的敏感按钮都挂了 `Action` 码,页面在 `@code` 里用 `[Action("approve","审核")]` 就近登记,「角色管理」据此列出该模块可勾的按钮。没登记的动作码表现为「能看见本页就能点」(框架对未登记动作放行),`ButtonActionGuardTests` 会拦住「按钮挂了码而页面没登记」这类漂移。 - **界面导航形态**:采用 `Known` 标准 `AdminLayout`(模块分组在左侧栏,顶栏为标题 / 面包屑 / 全局搜索),与历史截图的顶部模块导航不同。 - **手机端目前开放第一、二、三批**:审批闭环、销售订单查询与发货、扫码收货、扫码查库存 / 预警 / 盘点、生产工单 / 报工 / 领退料 / 完工入库。员工自助、收款登记、账龄还没做,工作台里不会出现点不动的入口——没做的功能宁可不露。采购订单列表/详情与调拨单**刻意不挂手机**:那两页在手机上只能看不能改,点进去发现改不了还得回电脑。**报工的权限门禁是「生产主管」角色**:只授了「生产工单」菜单而没有这个角色的账号,能打开报工页但提交会被服务端拒绝——是否给报工单独开一个"报工员"角色,按实际岗位设置决定。另外手机首屏会一次性下载全部懒加载插件(与桌面一致):首页宫格与工作台需要"全部模块有哪些入口"这份全局元数据,不下载就拼不出来,代价是首次打开比桌面版慢一点,之后走浏览器缓存。相机扫码还要求站点走 HTTPS,内网 http 部署时只能退回 PDA 键盘仿真或图片解码。 ## 🤝 商用合作 `ERPLite` 遵循 **AGPL-3.0** 开源协议,个人及企业内部使用免费。如需**商用部署、OEM 集成、功能定制、技术支持或去除 AGPL 约束**,请通过以下方式联系我们获取商业授权: - 提供专属功能开发与定制服务 - 企业级部署与运维支持 - 私有化部署授权(可转为商业闭源协议) - 按年订阅的技术支持与升级保障 ## 📮 联系方式 - 项目官网:`https://www.erplite.cn` - 商务邮箱:`knownchen@163.com` - 技术支持:`knownchen@163.com` - 微信号码:`knownchen` - 微信公众号:`Blazor程序员` ## ⚠️ 侵权声明 - 未经授权,任何组织或个人不得将本软件或其修改版本用于**商业牟利**(包括但不限于直接销售、捆绑销售、提供商业托管服务等)**除非获得明确的商业授权**。 - 在保留版权信息、遵循 AGPL-3.0 协议的前提下,允许个人学习、研究及内部非商业使用。 - 对侵权行为,项目团队保留追究法律责任的权利。 ## 📜 软件著作权
软件著作权
## 📄 许可证 本项目基于 `AGPL-3.0` 协议开源。详细条款请参见 [LICENSE](./LICENSE.txt) 文件。 **注意**:若您需要将本软件用于商业场景且不希望公开源码,请联系我们获取商业授权。