☰
付款申请审批与凭证自动生成系统:完整设计与数据库方案
2026/10/16 4:20:31 网站建设 项目流程

作为一个经常跟财务系统打交道的人,我太理解这种痛点了。付款申请、审批、凭证生成,这三个环节一旦分开管理,那就是灾难现场:审批单号跟凭证号对不上、手工录凭证格式五花八门、月底对账翻聊天记录找单据……这套系统我之前做过一个类似的,把整个链路打通之后,财务同事的幸福感直接提升两个档次。

下面我把完整的设计方案、数据库脚本、页面规划和核心逻辑一次性给全,你可以直接拿着去落地实施。

1. 系统整体设计与流程拆解

1.1 核心需求解析

这个系统的本质,是把“业务单据流”和“财务凭证流”合并成一条线。从你描述的需求来看,核心要解决三个问题:

  • 审批效率:付款申请在线登记,领导手机/电脑上就能批,不用抱着纸质单到处找人签字。
  • 凭证标准化:审批通过后,系统按预设的科目映射规则自动生成凭证,杜绝手工录入的格式混乱。
  • 数据可追溯:每一笔付款申请都能直接关联到生成的凭证,反过来,每一张凭证都能查到对应的申请单和附件。

整个流程可以概括为:登记付款申请 → 审批流转 → 生成凭证 → 入账总账 → 数据统计查询。

1.2 技术选型与架构逻辑

你说B/S架构、内网部署、SQL Server,这组合非常稳妥,尤其适合传统企业的财务环境。

  • 前端:建议用Vue或纯HTML+JS,如果你团队没有专职前端,直接用服务端渲染(比如ASP.NET MVC的Razor页面)也行,关键是别在页面上堆太多花哨组件,财务系统最重要的是表单清晰、操作顺手。
  • 后端:ASP.NET Core或Java Spring Boot都可以。考虑到SQL Server的天然适配性,我建议用.NET系,部署和内网运维都省心。
  • 数据库:SQL Server 2016以上,开启透明数据加密更好,财务数据嘛,安全第一。

核心逻辑是:业务审批通过后,系统自动根据付款申请的类型、供应商、项目等字段,匹配出对应的借方和贷方科目,直接写入凭证表。

1.3 需要明确的几个基础概念

在设计表结构之前,你必须先跟财务确认几个口径,否则后面会返工:

  • 核算维度:凭证入账是按项目核算、部门核算,还是两者都要?这决定了凭证分录表里要预留哪些字段。
  • 币种处理:你们有没有外币付款?有的话,凭证上要不要同时体现原币金额和本位币金额?
  • 审批层级:是“部门经理 → 财务 → 总经理”三级,还是更简单?审批流必须可配置,千万别写死在代码里。
  • 凭证编号规则:是“记-001”这种流水号,还是按月份重排?按月份重排在月末结转时非常有用。

2. 数据库表结构设计(可直接执行的SQL脚本)

我直接给你整套建表脚本,包含系统核心的11张表。命名规范统一,加了注释,你可以直接拿到SQL Server里跑。

-- ===================================================== -- 付款申请管理系统 数据库脚本 -- 适用数据库:SQL Server 2016+ -- 用途:付款申请登记、审批、凭证生成、总账入账 -- 作者:基于通用财务业务模式设计 -- ===================================================== USE [FinanceDB]; -- 注意:请先创建数据库或修改为目标库名 GO -- ----------------------------------------------------- -- 1. 项目表(用于项目维度核算) -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_project', N'U') IS NOT NULL DROP TABLE dbo.pay_project; GO CREATE TABLE dbo.pay_project ( project_id INT IDENTITY(1,1) PRIMARY KEY, -- 项目ID project_code NVARCHAR(50) NOT NULL UNIQUE, -- 项目编码 project_name NVARCHAR(100) NOT NULL, -- 项目名称 project_manager NVARCHAR(50) NULL, -- 项目负责人 payment_ratio DECIMAL(8,4) NULL, -- 付款比例(如0.3000表示30%) receipt_ratio DECIMAL(8,4) NULL, -- 收款比例(如0.5000表示50%) is_active BIT NOT NULL DEFAULT 1, -- 是否启用 create_time DATETIME NOT NULL DEFAULT GETDATE(), -- 创建时间 remark NVARCHAR(255) NULL -- 备注 ); GO -- ----------------------------------------------------- -- 2. 字典表(统一维护付款类型、费用类型等) -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_dictionary', N'U') IS NOT NULL DROP TABLE dbo.pay_dictionary; GO CREATE TABLE dbo.pay_dictionary ( dict_id INT IDENTITY(1,1) PRIMARY KEY, -- 字典ID dict_type NVARCHAR(50) NOT NULL, -- 字典类型(PAY_TYPE / COST_TYPE / CURRENCY) dict_code NVARCHAR(50) NOT NULL, -- 字典值编码 dict_name NVARCHAR(100) NOT NULL, -- 字典显示名称 sort_order INT NOT NULL DEFAULT 0, -- 排序 is_active BIT NOT NULL DEFAULT 1, -- 是否启用 CONSTRAINT UQ_dict_type_code UNIQUE (dict_type, dict_code) ); GO -- ----------------------------------------------------- -- 3. 付款申请单主表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_apply', N'U') IS NOT NULL DROP TABLE dbo.pay_apply; GO CREATE TABLE dbo.pay_apply ( apply_id INT IDENTITY(1,1) PRIMARY KEY, -- 申请单ID apply_no NVARCHAR(30) NOT NULL UNIQUE, -- 申请单编号(业务单据号) apply_title NVARCHAR(200) NOT NULL, -- 申请标题/事由 apply_date DATE NOT NULL, -- 申请日期 applicant_id INT NOT NULL, -- 申请人ID(关联用户表) applicant_name NVARCHAR(50) NOT NULL, -- 申请人姓名(冗余存储) dept_id INT NULL, -- 部门ID dept_name NVARCHAR(100) NULL, -- 部门名称(冗余存储) project_id INT NULL, -- 项目ID(FK) pay_type NVARCHAR(50) NULL, -- 付款类型(字典) cost_type NVARCHAR(50) NULL, -- 费用类型(字典) payee_name NVARCHAR(200) NOT NULL, -- 收款人/供应商名称 payee_bank NVARCHAR(100) NULL, -- 收款银行 payee_account NVARCHAR(100) NULL, -- 收款账号 currency_code NVARCHAR(10) NOT NULL DEFAULT 'CNY', -- 币种 exchange_rate DECIMAL(18,6) NOT NULL DEFAULT 1, -- 汇率 apply_amount DECIMAL(18,2) NOT NULL, -- 申请金额(原币) amount_in_base DECIMAL(18,2) NULL, -- 本位币金额 payment_ratio DECIMAL(8,4) NULL, -- 本次付款比例(冗余自项目) expected_pay_date DATE NULL, -- 期望付款日期 status INT NOT NULL DEFAULT 0, -- 状态:0草稿 1审批中 2审批通过 3已驳回 4已生成凭证 5已入总账 attachment_count INT NOT NULL DEFAULT 0, -- 附件数量 create_by INT NOT NULL, -- 创建人 create_time DATETIME NOT NULL DEFAULT GETDATE(), -- 创建时间 update_by INT NULL, -- 更新人 update_time DATETIME NULL, -- 更新时间 remark NVARCHAR(500) NULL -- 备注 ); GO -- ----------------------------------------------------- -- 4. 付款申请明细表(支持一单多付) -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_apply_item', N'U') IS NOT NULL DROP TABLE dbo.pay_apply_item; GO CREATE TABLE dbo.pay_apply_item ( item_id INT IDENTITY(1,1) PRIMARY KEY, -- 明细ID apply_id INT NOT NULL, -- 主表ID(FK) line_no INT NOT NULL, -- 行号 item_description NVARCHAR(255) NOT NULL, -- 内容摘要 account_code NVARCHAR(20) NULL, -- 预算科目/会计科目编码 item_amount DECIMAL(18,2) NOT NULL, -- 明细金额 item_amount_base DECIMAL(18,2) NULL, -- 明细本位币金额 cost_type NVARCHAR(50) NULL, -- 费用类型 remark NVARCHAR(255) NULL, CONSTRAINT FK_apply_item_header FOREIGN KEY (apply_id) REFERENCES dbo.pay_apply(apply_id) ); GO -- ----------------------------------------------------- -- 5. 附件表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_attachment', N'U') IS NOT NULL DROP TABLE dbo.pay_attachment; GO CREATE TABLE dbo.pay_attachment ( attachment_id INT IDENTITY(1,1) PRIMARY KEY, -- 附件ID apply_id INT NOT NULL, -- 申请单ID(FK) file_name NVARCHAR(200) NOT NULL, -- 文件名 file_path NVARCHAR(500) NOT NULL, -- 存储路径 file_ext NVARCHAR(10) NULL, -- 扩展名 file_size_KB INT NULL, -- 文件大小(KB) upload_by INT NOT NULL, -- 上传人 upload_time DATETIME NOT NULL DEFAULT GETDATE(), -- 上传时间 CONSTRAINT FK_attachment_apply FOREIGN KEY (apply_id) REFERENCES dbo.pay_apply(apply_id) ); GO -- ----------------------------------------------------- -- 6. 审批流配置表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_approval_flow', N'U') IS NOT NULL DROP TABLE dbo.pay_approval_flow; GO CREATE TABLE dbo.pay_approval_flow ( flow_id INT IDENTITY(1,1) PRIMARY KEY, -- 流程ID flow_name NVARCHAR(100) NOT NULL, -- 流程名称 flow_type NVARCHAR(50) NOT NULL, -- 流程类型(如:默认付款流程) step_count INT NOT NULL DEFAULT 1, -- 审批步骤数 is_active BIT NOT NULL DEFAULT 1, -- 是否启用 create_time DATETIME NOT NULL DEFAULT GETDATE() ); GO -- ----------------------------------------------------- -- 7. 审批步骤定义表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_approval_step', N'U') IS NOT NULL DROP TABLE dbo.pay_approval_step; GO CREATE TABLE dbo.pay_approval_step ( step_id INT IDENTITY(1,1) PRIMARY KEY, -- 步骤ID flow_id INT NOT NULL, -- 流程ID(FK) step_no INT NOT NULL, -- 步骤序号(1,2,3...) approver_role NVARCHAR(50) NOT NULL, -- 审批角色(如:DEPT_MANAGER, CFO, GM) step_name NVARCHAR(50) NOT NULL, -- 步骤名称(如:部门审批、财务审批) max_amount_limit DECIMAL(18,2) NULL, -- 金额上限(NULL表示不限) CONSTRAINT FK_approval_step_flow FOREIGN KEY (flow_id) REFERENCES dbo.pay_approval_flow(flow_id) ); GO -- ----------------------------------------------------- -- 8. 审批记录表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.pay_approval_log', N'U') IS NOT NULL DROP TABLE dbo.pay_approval_log; GO CREATE TABLE dbo.pay_approval_log ( log_id INT IDENTITY(1,1) PRIMARY KEY, -- 日志ID apply_id INT NOT NULL, -- 申请单ID(FK) step_no INT NOT NULL, -- 步骤序号 approver_id INT NULL, -- 审批人ID approver_name NVARCHAR(50) NULL, -- 审批人姓名 approver_role NVARCHAR(50) NULL, -- 审批角色 action NVARCHAR(20) NOT NULL, -- 动作:SUBMIT / APPROVE / REJECT / CANCEL comment NVARCHAR(500) NULL, -- 审批意见 action_time DATETIME NOT NULL DEFAULT GETDATE(), -- 操作时间 CONSTRAINT FK_approval_log_apply FOREIGN KEY (apply_id) REFERENCES dbo.pay_apply(apply_id) ); GO -- ----------------------------------------------------- -- 9. 凭证主表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.gen_voucher', N'U') IS NOT NULL DROP TABLE dbo.gen_voucher; GO CREATE TABLE dbo.gen_voucher ( voucher_id INT IDENTITY(1,1) PRIMARY KEY, -- 凭证ID voucher_no NVARCHAR(30) NOT NULL UNIQUE, -- 凭证编号(如记-2025-0001) voucher_date DATE NOT NULL, -- 凭证日期 voucher_type NVARCHAR(10) NOT NULL DEFAULT '记', -- 凭证类型 source_type NVARCHAR(50) NOT NULL, -- 来源:PAY_APPLY source_id INT NOT NULL, -- 来源单ID(apply_id) source_no NVARCHAR(30) NOT NULL, -- 来源单编号(apply_no) total_debit DECIMAL(18,2) NOT NULL DEFAULT 0, -- 借方合计 total_credit DECIMAL(18,2) NOT NULL DEFAULT 0, -- 贷方合计 status INT NOT NULL DEFAULT 0, -- 0已生成 1已入总账 create_by INT NOT NULL, -- 制单人ID create_name NVARCHAR(50) NOT NULL, -- 制单人姓名 create_time DATETIME NOT NULL DEFAULT GETDATE(), -- 生成时间 post_time DATETIME NULL, -- 入账时间 remark NVARCHAR(255) NULL -- 备注 ); GO -- ----------------------------------------------------- -- 10. 凭证分录表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.gen_voucher_entry', N'U') IS NOT NULL DROP TABLE dbo.gen_voucher_entry; GO CREATE TABLE dbo.gen_voucher_entry ( entry_id INT IDENTITY(1,1) PRIMARY KEY, -- 分录ID voucher_id INT NOT NULL, -- 凭证ID(FK) line_no INT NOT NULL, -- 行号 entry_direction NVARCHAR(4) NOT NULL, -- 方向:借 / 贷 account_code NVARCHAR(20) NOT NULL, -- 科目编码 account_name NVARCHAR(100) NOT NULL, -- 科目名称 entry_amount DECIMAL(18,2) NOT NULL, -- 金额(本位币) original_amount DECIMAL(18,2) NULL, -- 原币金额 currency_code NVARCHAR(10) NULL, -- 币种 project_id INT NULL, -- 项目ID(项目核算维度) dept_id INT NULL, -- 部门ID(部门核算维度) summary NVARCHAR(255) NOT NULL, -- 摘要 CONSTRAINT FK_voucher_entry_header FOREIGN KEY (voucher_id) REFERENCES dbo.gen_voucher(voucher_id) ); GO -- ----------------------------------------------------- -- 11. 科目映射规则表 -- ----------------------------------------------------- IF OBJECT_ID(N'dbo.gen_subject_mapping', N'U') IS NOT NULL DROP TABLE dbo.gen_subject_mapping; GO CREATE TABLE dbo.gen_subject_mapping ( mapping_id INT IDENTITY(1,1) PRIMARY KEY, -- 映射ID mapping_name NVARCHAR(100) NOT NULL, -- 规则名称 pay_type NVARCHAR(50) NULL, -- 付款类型(匹配条件) cost_type NVARCHAR(50) NULL, -- 费用类型(匹配条件) debit_account_code NVARCHAR(20) NOT NULL, -- 借方科目编码 debit_account_name NVARCHAR(100) NOT NULL, -- 借方科目名称 credit_account_code NVARCHAR(20) NOT NULL, -- 贷方科目编码(默认银行存款) credit_account_name NVARCHAR(100) NOT NULL, -- 贷方科目名称 is_active BIT NOT NULL DEFAULT 1, create_time DATETIME NOT NULL DEFAULT GETDATE(), remark NVARCHAR(255) NULL ); GO -- 创建索引 CREATE NONCLUSTERED INDEX IX_pay_apply_status ON dbo.pay_apply(status); CREATE NONCLUSTERED INDEX IX_pay_apply_project ON dbo.pay_apply(project_id); CREATE NONCLUSTERED INDEX IX_voucher_source ON dbo.gen_voucher(source_type, source_id); CREATE NONCLUSTERED INDEX IX_voucher_entry_account ON dbo.gen_voucher_entry(account_code); GO

考虑到系统里还要有用户和权限,我再补两张基础表:

-- 用户表(简化版) IF OBJECT_ID(N'dbo.sys_user', N'U') IS NOT NULL DROP TABLE dbo.sys_user; GO CREATE TABLE dbo.sys_user ( user_id INT IDENTITY(1,1) PRIMARY KEY, user_account NVARCHAR(50) NOT NULL UNIQUE, user_name NVARCHAR(50) NOT NULL, dept_id INT NULL, dept_name NVARCHAR(100) NULL, role_code NVARCHAR(50) NULL, -- 角色编码 role_name NVARCHAR(50) NULL, -- 角色名称 password_hash NVARCHAR(256) NOT NULL, email NVARCHAR(100) NULL, phone NVARCHAR(20) NULL, is_active BIT NOT NULL DEFAULT 1, create_time DATETIME NOT NULL DEFAULT GETDATE() ); GO -- 角色权限表(简化版) IF OBJECT_ID(N'dbo.sys_role_permission', N'U') IS NOT NULL DROP TABLE dbo.sys_role_permission; GO CREATE TABLE dbo.sys_role_permission ( permission_id INT IDENTITY(1,1) PRIMARY KEY, role_code NVARCHAR(50) NOT NULL, menu_code NVARCHAR(50) NOT NULL, -- 菜单编码 can_view BIT NOT NULL DEFAULT 1, can_create BIT NOT NULL DEFAULT 0, can_approve BIT NOT NULL DEFAULT 0, can_generate_voucher BIT NOT NULL DEFAULT 0, can_post BIT NOT NULL DEFAULT 0 ); GO

3. 页面功能规划与交互逻辑

页面这块我建议按角色区分工作台,因为财务系统和一般业务系统不一样,每个角色关心的东西完全不同。但底层页面其实是同一套,只是按钮和可用操作不同。

3.1 页面清单总览

页面名称访问角色核心功能
付款申请登记页所有员工新增/编辑/提交付款申请、上传附件
我的申请列表所有员工查看自己提交的单据、跟踪审批进度
待办审批中心审批人审批待办列表、审批通过/驳回
审批进度详情页所有员工查看审批流时间线、审批意见
凭证管理列表财务查询已生成凭证、查看凭证详情
凭证生成/入账操作页财务一键生成凭证、入账总账
付款比例统计报表财务/管理层按项目汇总付款/收款比例及金额
基础数据配置页系统管理员项目、科目映射、审批流配置

3.2 核心业务页面交互逻辑

付款申请登记页

这个页面的设计核心是“让填写的人少打字”。付款类型选完之后,如果有科目映射规则,就自动带出建议科目。收款人信息可以在系统里维护一个供应商档案表(我上面表结构里没加,实际项目建议再补一张供应商表),输入名称后自动带出银行账号。

付款比例这里要特别注意:如果项目设置了默认付款比例,填申请单时自动带出,同时允许申请人修改,但要保留修改记录。

审批中心

审批列表要支持按金额排序、按项目筛选、按紧急程度标记。审批详情页必须能直接看到附件预览(尤其是合同、发票的照片),审批人不用下载下来再打开。这是审批效率最关键的细节之一。

凭证生成操作页

这是整个系统最核心的页面。操作路径是:选择审批通过的申请单 → 点击“生成凭证” → 系统根据科目映射规则自动生成借贷分录并预览 → 财务确认无误 → 点击“入账总账”。

页面左边显示申请单信息,右边显示生成的凭证分录预览,这样财务可以一眼对比来源单据和凭证是否一致。凭证预览页面必须允许手动调整科目和金额,并记录调整日志。完全不让改的话,财务会疯掉的,现实业务总有特殊场景。

3.3 权限设计的细节

  • 申请人:只能看到自己创建的单据。
  • 部门经理:能看到本部门所有申请单,有审批权限。
  • 财务人员:能看到所有审批通过的单据,有凭证生成和入账权限,但没有审批权限(除非角色兼任)。
  • 管理层:只读权限,看报表和数据统计,不参与具体操作。

权限控制建议做到按钮级别,比如“生成凭证”按钮只在状态为“审批通过”时才显示,而且只有具有凭证权限的人才能看到和点击。

4. 核心业务逻辑与原理解析

4.1 审批流引擎设计

审批流这块我用的是“角色+步骤节点”的配置化思路。每一张付款申请单在提交时,根据金额大小匹配不同的审批流。

// 审批流匹配伪代码 public ApprovalFlow MatchApprovalFlow(PayApply apply) { // 规则:金额 <= 5万 走两步审批;5万~20万 走三步;>20万 走四步 if (apply.ApplyAmount <= 50000) return GetFlowByType("小额标准流程"); else if (apply.ApplyAmount <= 200000) return GetFlowByType("中额标准流程"); else return GetFlowByType("大额严审流程"); }

审批状态的流转我用的是状态机模式,严格限制状态之间的跳转:

草稿(0) → 审批中(1) → 审批通过(2) → 已生成凭证(4) → 已入总账(5) ↓ 驳回(3) → 编辑后重新提交 → 审批中(1)

状态机的好处是逻辑清晰,不会出现“已驳回的单据还能生成凭证”这种脏数据。

再说一个大家容易忽略的细节:所有单据状态的变更,都要记录变更日志(operations_log表),而且不允许删除流程记录。别问我为什么强调这个,审计的时候你就知道了。

4.2 凭证生成规则(重中之重)

凭证生成是整个系统的价值核心,科目映射规则是最关键的部分。我建议把规则做成可配置的gen_subject_mapping表,而不是写死在代码里,否则后面科目调整一次就得改一次代码。

默认的映射逻辑是这样:

付款类型费用类型借方科目贷方科目
采购付款材料费原材料/库存商品银行存款
采购付款设备费固定资产银行存款
费用报销差旅费管理费用-差旅费银行存款
费用报销办公费管理费用-办公费银行存款
项目付款外包服务项目成本-外包费银行存款
预付款采购预付款预付账款银行存款

凭证分录生成逻辑拆解:

  1. 借方:根据pay_type和cost_type匹配gen_subject_mapping中的借方科目。如果匹配不到,则使用映射表中的默认借方科目(比如“其他应付款”),同时给出警告提示。
  2. 贷方:默认是银行存款科目。如果有外币,则贷方银行存款金额是换算后的本位币金额,同时生成一条外币辅助核算分录。
  3. 摘要:直接用申请单号+摘要标题,比如“【PAY202501001】采购设备预付款”。

凭证编号规则建议用记-YYYYMM-XXXX格式。注意:凭证号必须在入账时生成,而不是生成凭证时生成。也就是说,凭证生成时可以预览,但没入账前凭证号是空的,入账时才分配正式编号。这能避免“预览了凭证但最后没入账”导致的断号问题。

下面这段是凭证生成和入账的核心逻辑,用C#风格写的,方便你理解:

public Voucher GenerateVoucherFromApply(PayApply apply, User currentUser) { var mapping = _subjectMapRepo.GetMatchMapping(apply.PayType, apply.CostType); if (mapping == null) throw new BizException("未找到科目映射规则,请联系财务配置"); // 计算本位币金额 decimal baseAmount = apply.CurrencyCode == "CNY" ? apply.ApplyAmount : apply.ApplyAmount * apply.ExchangeRate; using (var tx = _db.BeginTransaction()) { // 1. 创建凭证头 var voucher = new Voucher { VoucherDate = DateTime.Now.Date, VoucherType = "记", SourceType = "PAY_APPLY", SourceId = apply.ApplyId, SourceNo = apply.ApplyNo, TotalDebit = baseAmount, TotalCredit = baseAmount, Status = 0, // 已生成 CreateBy = currentUser.UserId, CreateName = currentUser.UserName, CreateTime = DateTime.Now }; _db.Vouchers.Add(voucher); _db.SaveChanges(); // 2. 创建借方分录(按费用/资产科目) _db.VoucherEntries.Add(new VoucherEntry { VoucherId = voucher.VoucherId, LineNo = 1, EntryDirection = "借", AccountCode = mapping.DebitAccountCode, AccountName = mapping.DebitAccountName, EntryAmount = baseAmount, OriginalAmount = apply.ApplyAmount, CurrencyCode = apply.CurrencyCode, ProjectId = apply.ProjectId, DeptId = apply.DeptId, Summary = $"【{apply.ApplyNo}】{apply.ApplyTitle}" }); // 3. 创建贷方分录(银行科目) _db.VoucherEntries.Add(new VoucherEntry { VoucherId = voucher.VoucherId, LineNo = 2, EntryDirection = "贷", AccountCode = mapping.CreditAccountCode, AccountName = mapping.CreditAccountName, EntryAmount = baseAmount, OriginalAmount = apply.ApplyAmount, CurrencyCode = apply.CurrencyCode, ProjectId = apply.ProjectId, DeptId = apply.DeptId, Summary = $"【{apply.ApplyNo}】{apply.ApplyTitle}" }); // 4. 更新申请单状态 apply.Status = 4; // 已生成凭证 _db.SaveChanges(); tx.Commit(); return voucher; } }

4.3 付款收款比例的统计逻辑

你提到“不同项目的付款收款比例要能统计”,这块我建议做两张报表:

项目付款进度统计表(按项目汇总)

SELECT p.project_code, p.project_name, p.payment_ratio AS 合同付款比例, p.receipt_ratio AS 合同收款比例, SUM(CASE WHEN a.status >= 2 THEN a.apply_amount ELSE 0 END) AS 已审批付款金额, SUM(CASE WHEN a.status >= 4 THEN a.apply_amount ELSE 0 END) AS 已生成凭证付款金额, p.payment_ratio * ISNULL(SUM(CASE WHEN a.status >= 2 THEN a.apply_amount ELSE 0 END), 0) AS 预计应付总额 FROM dbo.pay_project p LEFT JOIN dbo.pay_apply a ON p.project_id = a.project_id WHERE p.is_active = 1 GROUP BY p.project_code, p.project_name, p.payment_ratio, p.receipt_ratio;

项目资金流明细查询

选中一个项目,查所有关联的付款申请单和凭证状态,用于管理层看具体项目的资金流向。

4.4 附件与打印逻辑

附件上传要注意几个细节:

  • 文件格式白名单:PDF、JPG、PNG、Excel。合同扫描件通常PDF,发票是图片,Excel是为了偶尔上传对账单。
  • 单个文件大小限制在10MB以内,超过这个大小建议压缩后再传。
  • 附件存储路径建议用“年/月/申请单号/文件名”的结构,这样文件管理起来不会乱成一锅粥。
  • 打印模板:审批通过的申请单直接打印成PDF,格式要包含申请编号、付款信息、审批轨迹,方便线下报销归档。

5. 部署与实施过程全记录

5.1 内网部署步骤

我们当时实施时的服务器环境是这样的:Windows Server 2019 + SQL Server 2019 + IIS 10。整个过程分为四步:

  1. 数据库部署:创建数据库库,执行我上面给你的建表脚本,然后初始化基础数据(字典表、用户、角色权限、科目映射、审批流)。
  2. 应用发布:把后端服务发布到IIS,配置好连接字符串(注意SQL Server的实例名和端口),前端静态文件直接放在应用目录下。
  3. 服务启动与验证:启动服务后,先用管理员账号登录,检查基础数据是否加载正常,然后用一个测试账号走一遍完整流程。

5.2 初始化基础数据

下面是字典表和科目映射的初始化脚本,你可以直接用:

-- 初始化字典数据:付款类型 INSERT INTO dbo.pay_dictionary (dict_type, dict_code, dict_name, sort_order, is_active) VALUES ('PAY_TYPE', 'PURCHASE', '采购付款', 1, 1), ('PAY_TYPE', 'EXPENSE', '费用报销', 2, 1), ('PAY_TYPE', 'PROJECT', '项目付款', 3, 1), ('PAY_TYPE', 'PREPAY', '预付款', 4, 1); -- 初始化字典数据:费用类型 INSERT INTO dbo.pay_dictionary (dict_type, dict_code, dict_name, sort_order, is_active) VALUES ('COST_TYPE', 'MATERIAL', '材料费', 1, 1), ('COST_TYPE', 'EQUIPMENT', '设备费', 2, 1), ('COST_TYPE', 'TRAVEL', '差旅费', 3, 1), ('COST_TYPE', 'OFFICE', '办公费', 4, 1), ('COST_TYPE', 'OUTSOURCING', '外包服务', 5, 1); -- 初始化科目映射规则 INSERT INTO dbo.gen_subject_mapping (mapping_name, pay_type, cost_type, debit_account_code, debit_account_name, credit_account_code, credit_account_name, is_active) VALUES ('采购-材料', 'PURCHASE', 'MATERIAL', '1403', '原材料', '1002', '银行存款', 1), ('采购-设备', 'PURCHASE', 'EQUIPMENT', '1601', '固定资产', '1002', '银行存款', 1), ('费用-差旅', 'EXPENSE', 'TRAVEL', '6602', '管理费用-差旅费', '1002', '银行存款', 1), ('费用-办公', 'EXPENSE', 'OFFICE', '6602', '管理费用-办公费', '1002', '银行存款', 1), ('项目-外包', 'PROJECT', 'OUTSOURCING', '5401', '主营业务成本-外包费', '1002', '银行存款', 1), ('预付款', 'PREPAY', NULL, '1123', '预付账款', '1002', '银行存款', 1);

提示一下,正式上线时,这些科目编码必须由你们财务确认,每个公司的科目编码多少有差异,别用我给的默认值直接上线。

5.3 上线迁移方案

如果你们之前有Excel台账,建议先做数据迁移。最简单的做法是写一个导入模板,列字段和pay_apply表对齐,然后在系统里做一个“Excel导入”按钮。迁移后要抽查对账一段时间,确保新旧数据金额一致。老系统的手工凭证可以不用迁移,但要留一个查询入口,方便审计回溯。

6. 踩坑记录与核心问题排查手册

这里记录的是我在实施过程中真实遇到过的几个问题,给我省了不少事,也给读者们排个雷。

6.1 常见问题一:凭证生成后对不上账

现象:某个付款申请单生成凭证后,发现借方金额和申请金额不一致。

原因:申请单填的是美元,汇率设置错误,导致本位币金额算错了。

解决办法:在申请单上把汇率做成表单字段,默认带出月初汇率,但允许财务手动修改。同时在凭证生成时,校验apply_amount × exchange_rate = amount_in_base,超出0.01元就拦截,绝不静默处理。我还加了一个校验触发器:

-- 凭证生成时校验平衡 CREATE OR ALTER TRIGGER trg_voucher_entry_balance ON dbo.gen_voucher_entry AFTER INSERT, UPDATE AS BEGIN SET NOCOUNT ON; IF EXISTS ( SELECT v.voucher_id FROM dbo.gen_voucher v INNER JOIN ( SELECT voucher_id, SUM(CASE WHEN entry_direction = '借' THEN entry_amount ELSE 0 END) AS debit_total, SUM(CASE WHEN entry_direction = '贷' THEN entry_amount ELSE 0 END) AS credit_total FROM dbo.gen_voucher_entry GROUP BY voucher_id ) e ON v.voucher_id = e.voucher_id WHERE ABS(e.debit_total - e.credit_total) > 0.01 ) BEGIN ROLLBACK; RAISERROR('凭证借贷不平衡,已强制回滚', 16, 1); END END; GO

6.2 常见问题二:审批流卡住没人审

现象:单据在部门经理这一环节一直没人处理,申请人来问怎么回事。

原因:部门经理调岗了,但系统的审批角色还是旧的人,没人看到待办。

解决办法:审批流配置里的“审批人”不要直接绑具体的人名,而是绑“角色+部门”的组合。这样人员变动了,只要角色分配跟着变,审批流就不会断。如果一定要绑具体的人,加一个“催办”功能,超过24小时自动给审批人发提醒。

6.3 常见问题三:驳回后修改流程不清晰

现象:申请单被驳回后,申请人修改了金额重新提交,但审批历史里看不到修改记录。

解决办法:在驳回时,系统自动生成一条新的审批日志,状态回到草稿。同时申请单上标记“已驳回(修改中)”,修改后再次提交时,审批进度从第一步重新开始,但保留所有历史记录。这里要注意:驳回后的修改必须记录修改内容,最简单的方式是加一个update_history字段,存JSON格式的变更记录。

6.4 常见问题四:凭证入账后发现科目错误

现象:凭证已经入总账了,财务发现科目用错了。

解决办法:系统不能提供“修改凭证”按钮,只能“红字冲销”。生成一张金额为负的红字凭证,再生成一张正确的蓝字凭证。这个虽然麻烦,但财务规范就是这么要求的。设计系统时一定要把这个逻辑做进去,否则财务做账不规范,审计时有风险。

6.5 常见问题五:并发提交导致数据库锁表

现象:月底集中付款时,多个人同时提交申请单,偶尔出现超时或锁冲突。

解决办法:在申请单提交时,加一个ROWVERSION字段(乐观并发控制),更新时检查版本号不匹配就提示“该单据已被其他人修改,请刷新后再操作”。这个比SQL行锁高效很多,简单实用。

7. 进阶优化方向

系统上线跑通之后,有几个方向可以持续优化,我按推荐优先级排个序:

  1. 接入合同管理:合同信息和付款申请关联,审批时能看到合同金额、已付款比例,防止超付。
  2. 预算控制:在每个费用类型上设置预算额度,申请时实时校验预算余额,超标直接预警,这是财务最想要的功能。
  3. 银企互联:审批通过后直接推送付款指令到网银系统,回传付款状态,彻底打通“申请到付款”的全链路。
  4. 移动端审批:给领导做一个简化版移动端,只显示审批待办、金额、摘要和附件预览,很多领导会因为手机上能批而大幅提升审批速度。

最后说点实在的:这套系统真的落地之后,给我最大的感触不是“凭证生成自动化了”这个爽点,而是数据终于从Excel表和聊天记录里解放出来了。以前月底对账时翻聊天记录找审批截图的日子,真的可以结束。系统本身的架构并不复杂,真正难的是让财务愿意用、让审批人习惯在系统里操作,这需要在实施时多听听他们的实际需求,多打磨细节。我把这套方案完整发出来,希望能给准备做同类系统的朋友省点弯路。如果你在实施中遇到具体问题,欢迎随时交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询