Compare commits

...

88 Commits

Author SHA1 Message Date
chenhao e2f9e0e393 fix(order): 修复采购订单邮件抄送地址配置
- 移除错误的抄送邮箱地址 chenhao2@unisinsight.com
- 恢复正确的抄送邮箱地址 laiyk@dsipc.com.cn
- 针对供应商代码 P004 的邮件抄送逻辑修正
2026-02-04 16:31:56 +08:00
chenhao 7d8ad420e4 feat(purchase): 添加供应商确认邮件通知功能
- 在采购订单实体类中新增供应商邮箱字段
- 更新采购订单查询映射,添加供应商代码和邮箱信息
- 集成模板邮件工具类,实现供应商确认邮件发送
- 添加供应商确认邮件模板HTML文件
- 实现邮件发送逻辑,支持抄送特定供应商邮件
- 修复角色数据权限分配中的注释代码问题
2026-02-04 16:14:41 +08:00
chenhao 356fc1995e feat(quotation): 更新报价单页面展示创建人信息
- 在表格中添加创建人列并显示创建人姓名
- 为报价金额列添加货币格式化显示
- 更新步骤组件样式,添加自定义图标和激活状态高亮
- 修改数据查询逻辑,关联用户表获取创建人姓名
- 在实体类中添加createByName字段用于接收创建人信息
2026-02-04 13:39:36 +08:00
chenhao d9da314968 Merge branch 'refs/heads/dev_1.0.0' into dev_1.0.2
# Conflicts:
#	ruoyi-sip/src/main/java/com/ruoyi/sip/service/impl/InventoryDeliveryServiceImpl.java
2026-02-04 09:59:33 +08:00
chenhao a7f7a29d74 feat(quotation): 新增报价单详情页面并完善相关功能
- 新增报价单详情页面实现查看功能
- 添加选择报价单组件支持项目关联报价单
- 更新权限配置将base模块改为sip模块
- 在项目表中新增合作商系统用户ID字段
- 完善报价单控制器的权限注解
- 优化报价单导出Excel格式和样式
- 修复查询条件中的表别名问题
- 添加省代服务产品类型支持
2026-02-03 15:15:12 +08:00
chenhao 50ee54d6ef feat(quotation): 实现报价单绑定项目功能
- 在报价单表格中添加状态显示,使用字典标签展示报价状态
- 增加quotation_status字典类型用于状态管理
- 扩展ProjectInfo实体类,添加quotationId和quotationIdList字段
- 更新数据库映射文件,增加报价单ID相关字段和查询条件
- 在项目创建和更新流程中实现报价单绑定逻辑
- 添加bind和unBind方法到报价单服务接口和实现类
- 实现报价单状态枚举类,定义未绑定和已绑定状态
- 清理报价单表中废弃的项目代码和项目ID字段
- 优化报价单导出Excel模板中的项目信息展示
- 添加延迟注入项目信息服务以解决循环依赖问题
2026-02-03 10:32:34 +08:00
chenhao 701d90779a feat(quotation): 增加报价单详情查看和复制创建功能
- 关闭开发环境邮件发送功能
- 隐藏报价金额显示折后金额
- 添加详情按钮用于查看报价单详情
- 添加复制创建按钮实现基于现有报价单创建新报价单
- 实现复制创建功能清除原报价单ID和状态信息
- 添加项目表单中的报价单导入功能
- 更新删除接口路径为批量删除模式
- 修复删除确认提示信息过长问题
2026-01-30 11:22:24 +08:00
chenhao 61b10eba26 feat(quotation): 添加报价单导出功能
- 在报价单列表页面添加导出按钮,支持单个报价单导出为Excel文件
- 实现exportSingle方法,支持按ID导出指定报价单数据
- 集成EasyExcel库,实现报价单数据的Excel格式化导出
- 添加报价单导出的API接口和权限控制
- 实现包含Logo、标题、产品明细等完整报价单格式的Excel文件生成
- 添加导出功能的前端调用和下载处理逻辑
2026-01-29 18:07:31 +08:00
chenhao 45f69d527e feat(finance): 新增付款单附件功能并优化表格显示
- 在新增付款单表单中添加附件上传组件
- 修改表格样式设置避免滚动条冲突
- 移除表格固定高度限制提升用户体验
- 在付款单详情页面显示附件信息
- 添加付款单删除功能支持预付款单删除
- 付款单编号列添加链接跳转至详情页
- 后端实体类和数据库映射增加文件ID字段
- 实现附件查询服务关联付款单数据
- 货币金额显示格式化为货币格式
- 文件上传组件集成到付款单流程中
2026-01-29 17:14:42 +08:00
chenhao a56d750f2d feat(finance): 添加金额格式化显示功能并优化表单界面
- 在多个财务模块的表格列中添加金额格式化显示功能
- 统一使用 formatCurrency 方法格式化含税总价、未开票金额等数值
- 在付款单新增表单中添加其它特别说明字段
- 调整表格最大高度以优化界面显示
- 在详情抽屉中统一应用金额格式化处理
- 修改数据默认值以避免空值错误
- 优化审批流程中的金额显示格式化
2026-01-29 13:44:28 +08:00
chenhao aa2efbd42e feat(quotation): 添加报价单功能模块
- 新增报价单管理界面,支持报价单的增删改查操作
- 实现报价单基本信息设置和产品配置功能
- 添加报价单号自动生成机制,支持按日期和序列号生成
- 集成代表处选择和客户信息管理功能
- 实现多步骤表单流程,支持基本信息和配置信息分步填写
- 添加产品列表管理,支持软件、硬件和服务产品的分类配置
- 实现报价金额和折后金额的自动计算功能
- 优化发货记录中的服务年限计算逻辑
- 修复代码生成器中编号从0开始的问题,调整为从1开始
2026-01-27 18:04:44 +08:00
chenhao 6bed84cf3e feat(delivery): 添加发货管理的数据权限控制功能
- 在发货清单查询中集成数据权限注解支持
- 为发货服务类添加 DataScope 注解配置
- 更新 MyBatis 映射文件以支持数据权限 SQL 注入
- 添加用户职责关联查询以支持权限判断
- 修改前端组件移除不必要的自动加载逻辑
- 在用户控制器中增加角色授权接口实现
2026-01-27 13:40:43 +08:00
chenhao 330f2f7d54 feat(project): 优化项目信息表格布局并添加导出功能
- 调整表格列宽度以改善显示效果
- 在项目详情抽屉中添加技术方案终审下载按钮
- 实现导出单个项目技术方案的功能
- 移除原表格中的冗余导出按钮并改为注释形式
- 新增handleExportSingle方法处理文件导出逻辑
2026-01-27 10:55:38 +08:00
chenhao b96d006f9c feat(approve): 添加产品编码和型号筛选功能并优化订单金额显示
- 在审批订单页面添加产品编码和产品型号搜索条件
- 实现订单金额的货币格式化显示功能
- 添加格式化工具函数用于金额显示
- 修改订单总金额计算逻辑以支持不同订单状态
- 调整表格列布局以适应不同的订单状态显示需求
- 在采购审批相关页面添加产品筛选条件
- 更新后端实体类以支持产品编码和型号字段
- 优化库存发货服务中的代码格式和业务逻辑
- 添加年份计算的乘法逻辑用于服务期限计算
2026-01-26 17:48:19 +08:00
chenhao baa4b52553 feat(purchaseorder): 添加采购单导出功能并优化界面显示
- 在后端控制器中实现export方法返回AjaxResult并设置审批状态过滤
- 添加exportPurchaseorder API函数用于导出采购订单数据
- 在前端界面添加导出按钮及权限控制
- 修改下载插件中的方法名为download替代name
- 修复多个选择组件中的表单提交阻止默认行为
- 优化采购单详情对话框和抽屉组件的属性格式化
- 隐藏采购订单实体类中部分字段的Excel导出注解
- 修正审批时间Excel导出的时间格式化设置
- 更新多个select组件中的键盘事件处理方式
2026-01-26 14:20:27 +08:00
chenhao dd2a7d99b6 fix(inventory): 解决导入SN数据时状态设置时机问题
- 将isImported状态设置移到下一个DOM更新周期后执行
- 确保表格选择操作完成后再更新导入状态
- 避免因状态设置过早导致的UI更新异常
2026-01-26 09:57:21 +08:00
chenh 5f6f014226 feat(sip):订单合同修改新增产品经理 2026-01-15 16:54:27 +08:00
chenh 31a8de5569 Merge remote-tracking branch 'origin/dev_1.0.0' into dev_1.0.0 2026-01-12 17:49:48 +08:00
chenh ec3e34ad21 feat(sip):修改进销存的采购订单的导出接口 2026-01-12 17:48:58 +08:00
chenhao 0d14477705 fix(project): 修复项目会审状态修改逻辑
- 添加 canGenerate 检查防止已存在订单的项目取消会审
- 在无法生成订单时设置 jointTrial 为 '1'
- 显示错误消息提示用户操作被阻止
- 确保会审状态在订单存在时不被意外修改
2026-01-09 13:14:07 +08:00
chenhao 74ed165f71 fix(finance): 修复财务报表数据查询和权限控制问题
- 修复应付票据明细查询中关联核销单的SQL逻辑
- 修复应收收款明细查询中关联核销单的SQL逻辑
- 在收款对话框上传按钮添加权限控制指令
- 在收款对话框上传按钮添加权限控制指令
- 将报表中毛利字段标签从"金额"更正为"毛利"
- 注释掉业务计收时间和财务计收时间列以修复数据显示问题
2026-01-08 18:13:30 +08:00
chenhao f2cbf8f9cb feat(finance): 为财务模块各页面添加默认排序功能
- 在费用页面添加按创建时间降序排序
- 在应付账款页面添加按创建时间降序排序
- 在付款页面添加按创建时间降序排序
- 在收款页面添加按创建时间降序排序
- 在应收账款页面添加按创建时间降序排序
- 在收票页面添加按创建时间降序排序
- 在财务报表页面添加按创建时间降序排序
- 在应付账款报表页面添加按创建时间降序排序
- 在收票报表页面添加按创建时间降序排序
- 在发票核销记录页面添加按创建时间降序排序
- 在付款核销记录页面添加按创建时间降序排序
- 在收款核销记录页面添加按创建时间降序排序
- 在票据核销记录页面添加按创建时间降序排序
2026-01-08 17:11:49 +08:00
chenhao 5485a827be fix(finance): 修复财务模块表单验证和显示问题
- 为申请开票表单添加disabled属性控制
- 注释掉开票详情表格中的操作列
- 调整condensed-item样式间距和错误提示层级
- 为申请付款对话框添加表单验证规则
- 修复银行账号字段名称错误
- 为申请退款对话框添加表单验证规则
- 实现退款信息变更确认提示功能
- 动态显示开票单和红冲开票详情标题
- 为财务详情页面金额字段添加负数红色显示
- 在表格中显示绝对值金额
- 为审批页面添加标签状态颜色
- 修复审批历史加载的单据编号
- 调整审批页面付款编号显示
- 为开票列表页面金额字段添加负数红色显示
- 修正生成收票单按钮文本为生成开票单
- 修复上传发票对话框票据类型显示问题
2026-01-08 17:00:09 +08:00
chenhao 0dfdeee19d feat(finance): 优化财务模块界面显示和业务逻辑
- 添加paymentRedLog路由用于付款退款审批日志
- 统一各审批页面底部编号显示格式,移除冗余标签文字
- 为金额列添加红色样式高亮显示
- 优化审批状态标签颜色显示,根据状态值动态设置标签类型
- 修复收票单详情页面跳转路径错误
- 在各财务单据详情页添加标题标识
- 优化发票详情页金额显示颜色
- 修复发票单据数据更新时时间字段处理逻辑
- 优化供应商信息回填逻辑,避免覆盖已有数据
- 完善退款流程中的单据状态检查
- 修复收票单红冲功能中的业务逻辑错误
- 更新付款单详情页显示字段
- 添加技术方案会审分隔区域
- 修复附件获取类型参数错误
2026-01-08 15:51:33 +08:00
chenhao d1e23c58ef fix(inventory): 解决生成发货单表单数据残留问题
- 在 GenerateDeliveryForm.vue 中打开表单时清空已选SN列表
- 修复表格初始化加载时的选择状态清理逻辑
- 修正收票单详情页面显示的编号字段错误
- 更新项目管理日志标题为会审相关
- 添加项目订单文件修改权限相关注释说明
2026-01-08 13:47:50 +08:00
chenhao 602d06aafc feat(project): 添加项目列表排序功能和调整界面布局
- 在项目列表中添加排序功能,支持按最后操作时间等字段排序
- 新增最后操作时间列显示项目更新时间
- 将操作列宽度从300调整为400以适应更多操作按钮
- 调整详情抽屉和表单的标签宽度从120px到150px
- 修复后端时间格式化问题,将更新时间格式调整为完整时间格式
- 优化数据库查询,添加更新时间字段到查询结果中
- 修复更新语句中合作伙伴代码和文件ID的更新逻辑
2026-01-07 20:41:53 +08:00
chenhao 59b045d457 feat(project): 项目管理功能增强
- 实现行业类型多选功能,支持多个行业筛选
- 添加技术方案终审下载按钮,仅在联合试用为1时显示
- 集成文件上传组件,支持项目相关附件管理
- 新增授权、终端、服务器等技术信息字段管理
- 实现技术方案单个导出功能,添加权限控制
- 优化项目详情展示,增加虚拟机配置信息
- 添加会审结论和项目风险技术问题字段
- 完善项目表单验证和数据绑定逻辑
- 修复联合试用状态变更时的业务逻辑验证
2026-01-07 20:07:36 +08:00
chenhao 97bcc1ac0a feat(project): 优化项目管理和收款单功能
- 在application-prod.yml中配置flowable数据库模式更新设置
- 为项目信息表格的收藏和会审列添加固定右侧显示功能
- 修改订单生成逻辑,添加会审状态检查和权限验证
- 实现项目会审状态修改时的订单存在性验证
- 添加收款单按编码查询的接口和实现
- 重构收款单审批回调逻辑,支持多实例审批流程
- 更新项目生成订单权限判断逻辑,移除会审状态依赖
- 在订单创建时添加项目会审状态验证机制
2026-01-07 15:39:11 +08:00
chenhao b1cbbec237 feat(project): 添加项目收藏和会审功能
- 在项目信息页面添加是否收藏和是否会审的筛选条件
- 在项目表格中添加收藏和会审状态显示列
- 实现项目收藏状态的修改功能
- 实现项目会审状态的修改功能
- 添加权限控制,无权限用户无法修改会审状态
- 生成订单时增加会审状态判断,只有会审通过的项目才能生成订单
- 添加项目用户收藏信息的数据表和相关服务
- 在项目列表查询时关联用户收藏状态
- 修复代理处变更校验的逻辑错误
- 更新项目导出功能,添加收藏和会审字段的导出
2026-01-06 20:49:27 +08:00
chenhao 4ce68abe84 fix(finance): 修复财务模块显示和数据处理问题
- 修正付款单和收票单状态显示,将'已生成'改为'全部生成'以保持一致性
- 修正收款单和开票单状态显示,将'已生成'改为'全部生成'
- 修正应收模块中生成时间列的布局问题,将span从16改为8
- 修正订单编号标签为合同编号
- 将供应商标签改为制造商
- 修正核销模块中表格列标题,将'应付单生成时间'和'付款单生成时间'统一为'生成时间'
- 隐藏付款单核销总额显示
- 为库存查询添加limit 1优化
- 添加付款单按编码查询的接口和实现
- 修正合并发票和收票对话框中的进货商名称显示宽度
- 在付款明细查询中添加产品类型字段
- 修正项目名称为空时的显示,使用'应付'或'应收'占位符
- 添加付款类型和配置天数字段到付款单实体
- 修正收票单查询中的合作伙伴名称条件
- 移除预收款自动核销的TODO注释
- 修正预付款恢复通知邮件标题为预收款恢复通知
- 修正附件删除时的单据类型枚举
- 添加收票单审批完成后的自动核销功能
- 修正开票和收款计划时间格式,从日期改为日期时间
- 添加订单详情中产品信息和折扣验证的空值检查
- 添加付款详情页面的标题显示
- 修正应付单列表标题为应付单信息
- 修正邮件模板类型定义,更新预付款和预收款相关通知的描述
- 在待办服务中添加业务信息处理和流程键匹配逻辑
2026-01-06 18:04:24 +08:00
chenhao 3102a2c0a0 feat(sip): 实现供应商和合作伙伴邮件通知功能
- 启用邮件发送功能配置
- 添加预付款和预收款变动的邮件模板
- 实现在核销和恢复操作时自动发送邮件通知给供应商和合作伙伴
- 集成邮件工具类支持模板邮件发送
- 添加供应商信息批量查询功能
- 实现在预付款核销时发送余额变动通知
- 实现在预付款恢复时发送退款通知
- 实现在预收款处理时发送相应通知邮件
- 添加新的DTO类用于项目付款单数据传输
2026-01-05 20:57:34 +08:00
chenhao 357410c3e7 feat(finance): 添加应付和应收报表功能
- 创建应付报表页面,包含项目编号、名称、计收状态、付款状态、收票状态等查询条件
- 实现应付报表表格展示,包含计收统计、应付单、付款单、收票单等数据列
- 添加应付报表导出功能,支持权限控制和数据导出
- 创建应收报表页面,包含项目编号、名称、计收状态、收款状态、开票状态等查询条件
- 实现应收报表表格展示,包含应收单、计收统计、收款单、开票单等数据列
- 添加应收报表导出功能,支持权限控制和数据导出
2026-01-05 17:26:33 +08:00
chenhao a4ce1ba45a feat(finance): 添加财务运营报表功能并优化收款单详情展示
- 新增财务运营报表页面,支持项目报表的查询和导出功能
- 添加财务运营报表相关实体类和数据访问接口
- 优化收款单详情抽屉组件,调整字段显示和布局
- 修复订单配置信息中折后总价合计的显示条件
- 更新定时任务白名单配置,添加sip模块包路径
- 修复右侧工具栏下拉菜单样式问题
- 调整财务计收状态枚举值描述,优化计收流程状态显示
- 添加财务模块报表统计功能接口和实现
- 优化财务计收列表查询条件和导出功能返回值类型
2026-01-05 17:02:26 +08:00
chenhao 7db1b6ed37 Merge branch 'refs/heads/master' into dev_1.0.0 2026-01-04 12:15:05 +08:00
chenhao 4238f345cb feat(finance): 实现财务计收页面表格列动态显示和数据关联查询
- 在charge页面添加columns配置对象,支持表格列的动态显示和隐藏
- 集成right-toolbar组件的columns属性,实现列显隐控制功能
- 为表格列添加v-if条件渲染和key属性,优化列显示控制
- 在订单通路列添加标签显示,区分总代和直签类型
- 在实体类OmsFinanceCharge中添加项目、供应商、合作伙伴等关联字段
- 使用lombok注解配置getter/setter访问级别,避免自动生成方法冲突
- 实现毛利、毛利率、成本合计等计算字段的业务逻辑
- 更新数据访问层,通过多表关联查询获取项目和合作伙伴信息
- 优化SQL查询语句,添加表别名并完善查询条件的字段引用
- 增加项目编码、项目名称、合作伙伴名称等查询条件支持
2026-01-04 10:36:43 +08:00
chenhao caec1d4bd0 feat(finance): 新增财务计收模块并完善相关功能
- 新增财务计收API接口文件,包含查询、新增、修改、删除等操作
- 创建财务计收页面组件,实现列表展示、搜索、导出等功能
- 配置财务计收路由,添加菜单权限控制
- 在订单页面添加申请计收按钮,实现订单与计收关联
- 完善库存发货服务,在发货完成时自动创建计收数据
- 新增财务计收相关实体类、服务接口和数据访问层
- 添加库存信息按订单编号查询功能
- 修复内部库存页面数量验证逻辑问题
- 优化发货表单中序列号列表加载时机
2025-12-31 17:08:12 +08:00
chenhao 695c77ca60 feat(invoice): 添加红冲功能和相关审批流程
- 修改ApplyInvoice组件支持红冲操作,添加isRedRush属性控制红冲逻辑
- 在输入验证中修复数字验证正则表达式,支持负数输入
- 添加applyRefund API接口用于提交红冲申请
- 实现红冲审批流程,包括待审批和已审批页面
- 更新发票详情显示组件支持负数金额显示
- 在发票列表页面添加红冲按钮和相关状态判断
- 修改后端applyRefund接口接收完整对象参数
- 实现发票明细项导出功能
- 添加红冲状态和发票类型的字典数据支持
- 优化金额转中文大写函数支持负数显示
- 添加发票详情导出PDF功能
2025-12-30 15:20:25 +08:00
chenhao f0294212b2 feat(finance): 实现附件批量上传和多文件预览功能
- 修改附件字段从单个attachment为attachments数组
- 实现多文件上传功能支持同时上传多个附件
- 添加全局文件预览组件支持PDF和图片预览
- 更新下载功能支持批量下载多个附件
- 重构发票对话框中的附件展示和下载逻辑
- 修改后端服务接口支持多文件上传处理
- 更新数据库查询逻辑适配多附件关联关系
2025-12-29 20:54:57 +08:00
chenhao f992b2b29e feat(finance): 更新开票申请功能和审批流程
- 修改ApplyInvoice.vue中的表单字段映射,将projectName改为productName
- 为表格输入框添加禁用状态,当存在产品编码时不可编辑
- 更新表格列配置,将"产品型号"改为"规格型号",新增"单位"列
- 添加数值输入验证,限制数量字段只能输入数字和小数点
- 修正发票类型默认值从"-"改为"2"
- 将detailList改为detailItemList以保持数据结构一致性
- 在handleSubmit方法中添加表格明细校验逻辑
- 为发票详情页面添加PDF导出功能
- 创建新的开票审批历史页面receivableInvoice/approved/index.vue
- 创建新的开票审批页面receivableInvoice/index.vue,包含审批流程
- 更新发票列表页面的字段标签和查询条件
- 调整日期选择器格式为包含时间的datetimerange
- 隐藏实际收票时间筛选条件并调整列显示
- 更新按钮显示条件,仅在审批状态为0时显示申请开票
- 在InventoryOuter实体类中添加receivableBillCode字段
- 重构库存出库Mapper的查询语句,添加应收单编号关联查询
2025-12-29 20:21:28 +08:00
chenhao 5583bb0f11 feat(finance): 更新收款和开票审批功能
- 修改代理商为进货商的标签和提示文本
- 优化收款单表格列显示,添加预期收款金额和比例
- 实现预收和非预收单据的类型切换功能
- 添加应收单和订单表格的数据加载状态指示
- 更新应用付款对话框中的支付方式相关文本
- 增加收款金额与确认金额的一致性验证
- 完善应收单详情页面的字段展示和布局
- 添加发票详情类型字典配置
- 实现收款审批流程的审批历史和审批功能
- 创建收款单和退款单的审批相关路由
- 新增流程关联数据传输对象和相关配置
2025-12-29 15:31:27 +08:00
chenhao 47879d029a Merge branch 'refs/heads/master' into dev_1.0.0 2025-12-29 09:24:06 +08:00
chenhao 5ead2c13b2 feat(invoice): 实现发票申请功能优化
- 将销售方名称改为下拉选择框,支持公司信息自动填充
- 新增公司信息管理模块,包含增删改查功能
- 集成公司信息API接口,支持公司列表查询
- 实现销售方银行信息自动填充功能
- 优化发票申请表单,支持银行信息自动带入
- 更新发票状态筛选,使用字典数据动态渲染选项
- 添加公司信息详情查看功能
- 实现发票备注信息自动生成,包含购销双方银行信息
2025-12-26 10:28:52 +08:00
chenhao 389deac133 feat(finance): 添加提交日期筛选功能和金额汇总功能
- 在收票、红冲、付款等审批页面添加提交日期范围筛选功能
- 在财务页面表格中添加金额汇总显示功能
- 修复收票单详情页面字段显示问题
- 优化数据库查询时间范围处理逻辑
- 添加附件金额相关字段支持
2025-12-25 17:57:27 +08:00
chenhao b21c54a901 fix(finance): 优化付款和收票单据表单及列表功能
- 移除预计付款时间字段并调整表单布局
- 将是否预付改为复选框并更新相关字段名称
- 更新表格列标题为更准确的业务术语
- 添加表格合计功能和金额格式化显示
- 优化日期选择器类型和样式
- 修复收票单时间字段验证和显示问题
- 调整合并付款和收票单验证逻辑
- 添加付款日期预警颜色标识
- 优化金额字段显示单位和格式
- 修复多个表单和列表组件的字段映射问题
- 添加表格摘要统计功能
- 优化查询表单字段标签和宽度
- 调整按钮文字和操作提示信息
2025-12-25 15:33:23 +08:00
chenhao e767c6aa50 Merge branch 'refs/heads/master' into dev_1.0.0 2025-12-25 14:58:47 +08:00
chenhao 8ea80a4dbe feat(finance): 实现发票核销功能
- 新增发票核销API接口文件,包含列表查询、详情获取和删除功能
- 添加应收票据核销单服务接口和实现类
- 创建应收票据核销单数据实体和数据库映射
- 实现发票核销相关的数据库操作和业务逻辑
- 添加核销详情处理和批量更新功能
- 实现系统自动收票核销机制
- 更新发票单据服务以支持核销操作
- 添加核销记录的增删改查控制器接口
- 实现核销单据的查询和导出功能
2025-12-24 16:35:26 +08:00
chenhao fb3f3c721e feat(finance): 添加收款单手工核销功能
- 实现收款单手工匹配核销界面,支持应收单与收款单关联
- 新增应收单和收款单过滤查询功能,支持按进货商和单据编号筛选
- 添加核销金额计算逻辑,包含含税和未税金额转换
- 实现收款计划选择功能,支持批量核销操作
- 添加核销金额校验机制,确保应收与收款金额平衡
- 新增收款单核销状态管理和金额更新功能
- 实现核销详情批量处理和数据同步机制
2025-12-24 14:48:30 +08:00
chenhao d3bbf2da62 feat(finance): 更新开票单详情和操作功能
- 修改详情抽屉中的字段显示,将创建时间改为预计开票时间
- 添加开票状态、审批节点、审批状态和审批通过时间显示
- 更新销售-应收单表格的数据源和字段映射
- 扩展操作列宽度以适应更多按钮
- 添加发票按钮的显示条件控制
- 添加申请开票按钮的显示条件控制
- 添加申请红冲按钮的显示条件控制
- 实现撤销功能按钮和相关逻辑
- 添加撤销发票API接口
- 优化票据类型选择的交互逻辑
- 添加发票金额验证和自动计算功能
- 实现附件上传的权限控制
- 完善发票撤销的业务逻辑处理
2025-12-23 19:52:57 +08:00
chenhao 693899f45c Merge branch 'refs/heads/master' into dev_1.0.0 2025-12-23 16:00:55 +08:00
chenhao fe2791f815 feat(finance): 新增申请开票功能模块
- 添加申请开票Vue组件,包含发票信息录入与提交逻辑
- 更新开票单详情抽屉展示字段及标签分类
- 扩展后端接口支持根据开票单号查询产品明细
- 新增申请开票API接口及前端调用服务
- 完善开票相关字典类型与状态显示
- 优化开票列表页字段展示与操作按钮布局
- 增加项目ID字段以支持更精确的产品关联
- 提供数字转中文大写金额转换功能
- 实现发票明细表格动态增删行与自动计算
- 引入电子发票类型选择与样式美化
2025-12-22 20:44:38 +08:00
chenhao 54e7d394bb feat(invoice): 新增收票单管理功能
- 新增收票单列表页面,支持查询、新增、查看详情等操作
- 实现收票单新增表单,支持选择客户、关联应收单及收票计划
- 开发收票单详情抽屉,展示收票单基本信息及关联应收单明细
- 添加收票附件上传对话框,支持上传并查看收票相关附件
- 在应收单编辑页面增加开票明细展示及附件预览功能
- 更新审批订单页面布局,移除冗余工具栏
- 优化文件预览组件集成,提升附件查看体验
2025-12-22 15:59:31 +08:00
chenhao 6ec773ce3d Merge branch 'refs/heads/master' into dev_1.0.0 2025-12-22 14:39:26 +08:00
chenhao 0774e81fd0 fix(finance): 修复收款明细显示及字典配置问题
- 修改对方支付方式显示逻辑,使用dict-tag组件展示
- 补充收款相关字典类型配置
- 移除Java服务类中无用的PolicyUtils导入
2025-12-22 09:46:26 +08:00
chenhao 1111d285c5 feat(finance): 新增收款申请付款与退款功能
- 新增申请付款对话框组件,支持文件上传与预览
- 新增申请退款对话框组件,用于处理退款请求
- 修改收款详情抽屉,增加附件预览与下载功能
- 优化付款单编辑表单,统一文件预览组件
- 在收款管理页面添加申请付款与退款操作按钮
- 后端新增收款申请、退款及附件上传接口
- 数据库映射文件更新,增加原始账单ID字段
- 实现收款单据服务层逻辑,包括退款状态管理
- 增加全局文件预览组件引用与相关方法
- 更新权限控制与业务流程处理逻辑
2025-12-19 16:40:17 +08:00
chenhao c257cdc5cd feat(finance): 新增收款单管理功能
- 新增收款单新增表单页面,支持预收和应收两种类型
- 实现收款单详情查看抽屉组件
- 添加收款单列表查询、筛选和分页功能
- 集成收款单新增、提交、退回和红冲操作
- 支持收款计划选择和金额计算逻辑
- 完善收款单据关联数据展示和处理
- 提供收款单附件管理和审批状态跟踪
- 增加代理商选择器和订单数据联动加载
- 实现收款单据类型和状态字典显示
- 添加收款单据验证规则和提交前检查
2025-12-19 13:44:22 +08:00
chenhao 1d85e92997 feat(finance): 调整付款单和收票单详情展示及附件删除功能
- 修改付款单详情抽屉中的字段展示顺序和内容
- 移除收票单列表中的预计收票时间和实际收票时间列
- 增加附件作废按钮并实现删除逻辑
- 更新附件删除接口以支持根据关联ID软删除
- 在付款单中增加审批节点信息传递
- 调整合并收票对话框中日期选择器布局并修改提示文字
- 优化付款单状态更新逻辑,在删除附件后重置支付状态和时间
2025-12-18 17:57:59 +08:00
chenhao e7ed169f94 feat(finance): 添加采购应付收票核销功能及相关优化
- 在 EditForm.vue 中为回执单和发票图添加预览功能,支持 PDF 和图片格式
- 增加 PDF 预览对话框和图片预览对话框组件
- 优化附件显示逻辑,当无附件时展示 '-' 占位符
- 禁止订单审批弹窗点击遮罩关闭,防止误操作
- 调整应付单列表项目名称列宽及操作列宽度,提升界面布局合理性
- 修改生成收票单按钮显示条件,确保仅在未收票金额大于 0 时可见
- 移除付款单中的删除按钮,替换为生成收票单按钮
- 放宽发起付款按钮的显示限制,不再限定付款单类型
- 新增 IOmsPayableTicketWriteOffService 接口及其实现类,支持收票核销业务
- 添加 OmsPayableTicketWriteOff 实体类及相关 Mapper、Controller 层代码
- 实现用户手动收票核销与系统自动收票核销逻辑
- 为 MergePaymentDialog 添加禁止点击遮罩关闭属性,增强用户体验
- 在 IOmsPayablePaymentDetailService 和 IOmsPayableTicketDetailService 中新增批量更新方法
- 补充 MyBatis XML 映射文件中的 updateBatch SQL 语句
- 完善收票核销单据编号生成规则,确保唯一性和可读性
- 增强核销删除功能,实现关联数据的反核销处理和金额恢复逻辑
2025-12-18 17:21:27 +08:00
chenhao 9be3b62aaf feat(finance): 应付单详情页新增收票信息展示与预付金额计算
- 调整应付单详情页字段布局,新增项目编号、项目名称等显示项
- 新增“该制造商是否有预付单”和“预付金额”字段展示
- 新增“生成付款单”、“生成收票单”状态展示逻辑
- 新增采购收票单表格展示功能,包括发票类型、开票时间、收票状态等
- 新增收票计划和付款计划组件间的数据同步方法
- 扩展字典类型支持,增加收票相关枚举值
- 增加收票明细列表查询接口及附件信息加载逻辑
- Java后端增加预付金额计算逻辑,并在应付单中返回对应数据
- 优化支付明细和收票明细的状态统计逻辑,提升准确性
- 修改数据库映射文件,补充缺失字段如税率、不含税金额等
- 增加核销相关操作接口,支持批量删除和插入收票明细记录
- 修复部分SQL语句条件判断问题,确保查询结果准确无误
2025-12-18 16:13:40 +08:00
chenhao 6c3f344b92 feat(finance): 新增财务应收应付核销功能
- 新增应收单据查询、详情查看接口
- 新增收款计划与开票计划管理接口
- 新增核销记录列表与详情查询接口
- 新增付款核销详情抽屉组件
- 新增核销记录查询页面,支持多条件筛选
- 实现核销记录反核销功能
- 添加核销类型字典配置支持
2025-12-17 20:14:26 +08:00
chenhao ed90f7ce87 feat(sip): 实现应付核销功能及相关服务优化
- 新增自动核销与手动核销逻辑,支持通过付款单自动触发核销流程
- 完善核销单号生成规则,确保唯一性和可追溯性
- 增强付款单与应付单之间的关联处理,包括核销后的反向操作
- 添加附件删除接口,支持按支付单ID及类型批量逻辑删除
- 扩展应付明细查询能力,新增根据核销ID列表查询相关应付明细
- 优化付款单状态更新机制,在核销撤销时同步更新付款状态及附件信息
- 引入WriteOffDetailDTO用于封装核销详情数据,提升前后端交互效率
2025-12-17 20:13:49 +08:00
chenhao f79150abc9 feat(finance): 实现手工匹配核销功能
- 新增付款单与应付单的手工匹配核销界面
- 支持应付单和付款单的独立过滤查询
- 实现核销金额的实时计算与校验逻辑
- 添加应付计划选择弹窗及交互逻辑
- 集成税务相关金额的自动计算功能
- 提供核销明细数据的批量插入接口支持
- 优化表格展示与分页查询用户体验
2025-12-17 17:25:43 +08:00
chenhao 8d6ca0b64f feat(finance): 新增收款与核销功能模块
- 实现销售应收单详情查看与编辑功能
- 添加合并收款单与开票单发起流程
- 开发手动核销应付单与付款单功能
- 支持应付计划选择与核销金额计算
- 增加税务相关金额计算逻辑
2025-12-17 11:15:10 +08:00
chenhao 6613b3612c feat(approve): 实现审批单据PDF导出功能
- 引入通用PDF导出工具函数exportElementToPDF
- 在多个审批组件中添加PDF导出按钮和相关逻辑
- 优化导出时的样式显示,隐藏不必要的交互元素
- 统一处理PDF文件命名规则
- 移除原有的html2canvas和jsPDF直接调用代码
- 添加导出状态loading效果和异常处理提示
2025-12-16 16:35:53 +08:00
chenhao af12674d7b feat(finance): 新增收票与红冲发票审批功能
- 在路由中新增发票红冲和收票审批相关页面路径
- 新增红冲发票审批页面及详情展示组件
- 修改收票单据字段显示,统一使用 ticketBillCode 字段
- 调整收票和红冲发票的审批流程跳转逻辑
- 更新附件上传人字段为 createByName
- 后端区分不同类型票据启动不同的审批流程
- 优化前端接口请求增加 loading 状态控制
- 移除付款明细中的冗余附件获取逻辑
- 新增收票相关 API 接口文件并实现基本功能方法
2025-12-16 16:17:25 +08:00
chenhao 7c46ae5db4 feat(finance): 新增收票审批功能模块
- 新增收票待审批与已审批列表查询接口
- 实现收票审批与驳回流程处理逻辑
- 添加收票单详情查看及附件预览下载功能
- 配置收票审批相关路由与页面组件
- 完善收票单据明细展示与数据字典支持
- 优化附件查询SQL关联创建人姓名字段
- 修复流程定义中财务票据退款键名拼写错误
- 调整付款单据查询逻辑以适配指定流程KEY筛选
- 更新实体类增加审批相关字段支持动态查询
- 补充Vue页面样式与交互细节提升用户体验
2025-12-16 15:45:14 +08:00
chenhao 56d79e96b6 feat(finance): 新增财务票据审批功能
- 在配置文件中增加财务票据相关流程定义
- 实现票据退款审批流程的开启与处理逻辑
- 新增付款退款审批页面及详情组件
- 提供付款退款审批接口及业务方法
- 支持在审批流中动态设置流程变量并启动指定流程
- 优化审批节点信息填充方法以支持多流程场景
- 修复部分SQL查询条件限制提高灵活性
- 更新前端页面去除冗余字段展示提升用户体验
2025-12-16 14:47:15 +08:00
chenhao 995f7f8b03 feat(approve): 新增财务付款审批功能
- 添加付款审批列表页面,支持搜索和分页
- 实现付款单详情查看功能
- 集成审批流程操作,包括同意和驳回
- 添加审批意见输入和验证逻辑
- 展示付款单流转历史记录
- 创建付款详情组件,显示付款单和应付单信息
- 添加跳转至审批历史页面的功能按钮
2025-12-16 09:52:53 +08:00
chenhao 2cf2fdff08 feat(finance): 新增财务付款与退款审批功能
- 在配置文件中增加财务付款和退款的流程定义及实例Bean配置
- 扩展OmsPaymentBill实体类,新增审批相关字段如审批人、申请时间等
- 实现付款和退款的审批流程启动、撤销以及退款申请功能
- 增加前端页面按钮控制及对应API调用逻辑
- 添加付款审批列表和已审批列表查询接口
- 更新路由配置以支持新的财务付款审批页面访问路径
- 引入流程删除命令确保重复提交时旧流程实例被清理
- 完善付款单据审批状态更新及相关业务回调处理逻辑
- 优化审批流中的公司领导审批分支处理逻辑
- 提供付款单据回执单查看、发起付款/退款、退回及撤销操作界面交互支持
2025-12-16 09:43:34 +08:00
chenhao dc1f5f7302 feat(finance): 重构收票单新增功能并完善相关逻辑
- 新增收票计划选择器组件及交互逻辑
- 优化收票单表单结构,支持从应付单合并生成收票单
- 增加收票计划金额和比例计算功能
- 完善收票单提交校验逻辑,确保数据完整性
- 更新收票单详情展示字段,修正显示错误
- 调整收票单列表操作按钮权限控制
- 扩展后端服务接口,支持根据收票单编号查询明细
- 优化收票单审批状态管理及相关业务逻辑处理
2025-12-15 17:12:08 +08:00
chenhao 49cd27c221 feat(finance): 优化付款单新增功能及界面交互
- 移除预付单类型切换时的冗余处理逻辑
- 修复付款状态字典引用错误,统一使用 dict.type.payment_status
- 预付订单列表增加选择控制,限制只能选中一个订单
- 表格组件添加 ref 引用以便手动清除选中状态
- 新增预计付款时间必填校验规则
- 提交数据结构调整,明确区分应付单与预付单字段
- 增加含税总价大于0的前置校验
- 日志提示优化,增强用户操作引导信息
- 接口调用分离,针对不同付款类型调用对应后端接口
- 回执单组件默认值调整,避免空对象异常
2025-12-15 14:59:27 +08:00
chenhao a4a93ee484 feat(finance): 更新付款单表单逻辑与数据源
- 修改付款单类型选项,将 NORMAL 替换为 FROM_PAYABLE
- 调整应付单列表显示条件,适配新的付款单类型
- 更新订单列表标题及字段展示内容
- 修改默认付款单类型为 FROM_PAYABLE
- 优化订单列表接口调用方式,使用 POST 请求并设置 Content-Type
- 增加供应商编码查询条件,支持通过项目产品信息过滤订单
- 调整提交校验逻辑,确保选择应付单时进行有效性检查
2025-12-15 11:03:16 +08:00
chenhao c940880a9d feat(finance): 重构付款单新增功能并支持预付与应付单合并付款
- 重写 AddForm.vue 组件以支持预付和正常付款两种模式
- 新增付款计划选择器组件用于应付单付款计划管理
- 修改后端接口 URL 并增加查询应付单及采购订单的 API 方法
- 扩展付款明细服务以支持通过付款单编码查询数据
- 更新付款单退回逻辑以正确处理关联应付单金额更新
- 移除旧有的付款单应付明细查询方法及相关 XML 配置
2025-12-15 10:23:57 +08:00
chenhao 3e45254fc1 feat(finance): 优化应付单付款与收票逻辑
- 修改计算工具函数,确保数值运算精度
- 更新应付单页面字段映射,统一命名规范
- 增加税率输入框并同步到库存信息
- 调整应付单合并支付与收票计划的处理方式
- 完善应付单实体类结构及数据库映射关系
- 优化采购订单关联应付单的生成逻辑
- 引入厂家开票时间字段以完善票据管理流程
2025-12-15 09:27:08 +08:00
chenhao a19909d1bf Merge branch 'refs/heads/master' into dev_1.0.0 2025-12-12 11:00:27 +08:00
chenhao 2f51b56298 feat(finance): 新增付款计划同步至发票计划功能
- 在后端服务中实现 syncPaymentToTicketPlan 方法,用于将付款计划同步到发票计划
- 添加对应的 REST 接口 /sync/{payableBillId} 支持前端调用
- 前端页面新增“同步至发票计划”按钮,并绑定相应处理逻辑
- 提供 API 函数 syncToTicketPlan 以支持前后端通信
- 优化计算公式显示,确保数值精度和展示正确性
- 增强数据一致性校验,防止已执行的数据不匹配导致错误同步
2025-12-11 20:47:05 +08:00
chenhao 4cc2d3beb1 feat(finance): 新增收票单管理功能
- 新增收票单新增、详情查看、附件上传、退回及红冲申请功能
- 实现收票单与应付单关联展示及合并收票逻辑
- 提供收票计划选择与金额计算功能
- 支持收票状态、审批状态等多维度查询过滤
- 集成字典标签展示收票类型、审批状态等相关字段
- 添加收票单据的增删改查接口支持
2025-12-11 20:06:23 +08:00
chenhao ed9fda7fe3 feat(finance): 优化回执单上传与展示功能
- 根据支付类型动态显示"回执单"或"退款图"标题
- 重构上传流程,新增独立的上传对话框
- 支持PDF文件的预览功能,可点击查看大图
- 新增上传表单验证,包括文件类型、大小及价格确认
- 改进UI布局,提升用户体验和视觉效果
- 添加PDF预览弹窗,支持更好的文档查看体验
- 完善文件上传逻辑,确保数据完整性和一致性
2025-12-11 14:45:46 +08:00
chenhao bd830115d4 feat(finance): 添加合并收票单功能并优化付款单逻辑
- 在应付单页面增加“合并并发起收票单”按钮及弹窗组件
- 新增收票单相关接口与服务实现,支持合并多个应付单发起收票
- 优化付款单含税金额与无税金额的计算逻辑
- 补充付款明细查询接口,支持通过付款单编号获取关联应付单信息
- 调整部分字段命名以提升语义清晰度(如 paymentRatio 改为 paymentRate)
- 更新收票计划相关接口路径,统一归入 /finance/ticket/plan 路由下
2025-12-11 11:02:11 +08:00
chenhao a9142c0e1b feat(finance): 添加付款退款功能并优化应付单界面
- 在付款管理页面增加退款按钮及申请退款功能
- 新增退款状态枚举及相关的业务逻辑处理
- 优化应付单列表展示,移除部分冗余字段显示
- 调整付款明细服务,支持批量插入和退款明细生成
- 修改付款计划查询逻辑,增强数据准确性
- 完善退款接口,支持通过ID申请退款操作
2025-12-10 18:46:24 +08:00
chenhao d3c4776bab feat(finance): 增强付款单功能及附件管理
- 将付款时间相关字段格式从日期调整为日期时间格式
- 在详情页和列表页使用dict-tag展示支付方式、付款状态和审批状态
- 新增回执单下载功能和相关UI组件
- 增加收票计划Tab页支持
- 添加发起付款和回执单弹窗界面及相关逻辑
- 实现附件上传与管理服务,包括财务附件实体和接口定义
- 更新付款单和应付明细实体结构以支持附件关联
- 优化前端组件导入和数据处理逻辑
- 修复并增强多个API接口功能,如applyPayment、uploadReceipt等
- 补充Mapper XML配置和Service实现逻辑
- 调整后端实体类属性以匹配新的业务需求
- 增加对附件信息的查询与绑定处理逻辑
- 引入新的枚举类型用于区分不同类型的关联单据
- 前端增加对新接口调用的支持以及表单交互优化
2025-12-09 20:51:15 +08:00
chenhao a1ea52a934 feat(finance): 新增付款单功能并优化应付单详情展示
- 新增付款单新增表单组件,支持付款单类型、制造商、付款时间等字段录入
- 优化应付单编辑表单,将弹窗改为抽屉展示,增强用户体验
- 应付单详情页增加付款明细表格展示及统计功能
- 调整付款单生成逻辑,支持从应付单直接发起付款操作
- 完善应付单状态显示,明确展示未生成、部分生成、全部生成状态
- 后端增加应付单查询接口及付款明细关联查询功能
- 优化付款单退回按钮显示条件,仅在审批通过状态下可操作
- 重构合并付款弹窗,调整默认付款单类型及展示逻辑
2025-12-09 16:35:45 +08:00
chenhao 05865dc2ef feat(finance): 新增付款单管理功能
- 新增付款单详情抽屉组件,展示付款单及应付单详细信息
- 新增付款单列表页面,支持多条件筛选和分页查询
- 新增财务管理路由模块,包含付款单子菜单
- 优化应付单日期格式化逻辑,统一使用HH:mm:ss格式
- 实现合并付款单并发起支付功能,支持批量处理应付单
- 新增付款明细实体类及相关Mapper、Service实现
- 完善应付单查询条件,增加预计付款时间范围查询
- 调整付款计划相关类包结构,统一移至sip.domain包下
2025-12-09 09:11:32 +08:00
chenhao 30d551fe59 Merge branch 'refs/heads/master' into dev_1.0.0 2025-12-09 09:10:37 +08:00
chenhao 333b5e1a8e fix(finance): 优化付款计划选择逻辑并修正计算方式
- 移除重复的付款计划验证逻辑
- 修正订单当前付款比例的计算方法,使用含税总价替代未付金额
- 新增 handleChooseConfirm 方法处理付款计划的选择确认逻辑
- 在 PaymentPlan 组件中支持传入选中的付款计划,并初始化表格选中状态
- 更新 MergePaymentDialog 中对 PaymentPlanSelector 组件的引用及事件绑定
- 调整付款比例计算条件,允许未付金额为零的情况
2025-12-05 16:35:11 +08:00
chenhao 94348aebc4 feat(finance): 新增采购应付单管理功能
- 新增采购应付单列表页面,支持多条件筛选和分页查询
- 实现应付单详情弹窗,展示应付单基本信息和状态
- 添加合并付款单功能,支持多应付单合并发起付款
- 实现付款计划选择器,可为每个应付单配置付款计划
- 新增应付单修改和删除功能
- 添加数据字典支持,用于展示产品类型、付款状态等枚举值
- 实现应付单导出功能
- 新增后端付款计划相关接口和实体类
- 添加付款计划Mapper接口及实现
- 实现付款计划服务层接口及业务逻辑
2025-12-05 14:18:54 +08:00
chenhao 1d842d3bf2 Merge branch 'refs/heads/master' into dev_1.0.0 2025-12-05 14:09:36 +08:00
chenhao 1bdc88cd7c Merge branch 'refs/heads/master' into dev_1.0.0
# Conflicts:
#	oms_web/oms_vue/src/utils/ruoyi.js
2025-12-04 17:46:58 +08:00
chenhao 1a4f64865a feat(sip): 新增库存出库价同步及应付单生成功能
- 新增 InventoryInfo.listByDeliveryId 方法以支持按出库单查询库存信息
- 在 InventoryDeliveryServiceImpl 中实现保存出库信息时同步更新出库价
- 实现出库时自动生成应付单逻辑,包括税率配置读取与金额计算
- 增加 selectOutPriceByCode 查询用于获取项目产品的价格信息
- 扩展 VendorInfo 支持付款类型枚举,区分入库付款与出库付款
- 修改 OmsPayableBill 实体类,增加项目编码、名称等字段并优化结构
- 优化应付单 Controller 接口,支持更灵活的查询条件
- 重构应付单 Mapper XML 文件,增强关联查询与筛选功能
- 在应付单 Service 层新增事务控制与默认付款计划创建逻辑
- 移除前端精确货币舍入工具函数,改用 decimal.js 进行高精度运算
- 注册全局计算工具至 Vue 实例,替换原有金额计算方式提升准确性
2025-12-04 10:53:27 +08:00
371 changed files with 51985 additions and 1216 deletions

View File

@ -0,0 +1,50 @@
import request from '@/utils/request'
// 查询报价单列表
export function listQuotation(query) {
return request({
url: '/quotation/list',
method: 'get',
params: query
})
}
// 查询报价单详细
export function getQuotation(id) {
return request({
url: '/quotation/' + id,
method: 'get'
})
}
// 新增报价单
export function addQuotation(data) {
return request({
url: '/quotation/insert',
method: 'post',
data: data
})
}
// 修改报价单
export function updateQuotation(data) {
return request({
url: '/quotation/update',
method: 'put',
data: data
})
}
// 删除报价单
export function delQuotation(id) {
return request({
url: '/quotation/remove/batch/' + id,
method: 'delete'
})
}
export function exportSingleQuotation(id) {
return request({
url: '/quotation/export/single/' + id,
method: 'get'
})
}

View File

@ -0,0 +1,72 @@
import request from '@/utils/request'
// 查询财务计收列表
export function listCharge(query) {
return request({
url: '/finance/charge/list',
method: 'get',
params: query
})
}
// 查询财务计收详细
export function getCharge(id) {
return request({
url: '/finance/charge/' + id,
method: 'get'
})
}
// 新增财务计收
export function addCharge(data) {
return request({
url: '/finance/charge',
method: 'post',
data: data
})
}
// 修改财务计收
export function updateCharge(data) {
return request({
url: '/finance/charge',
method: 'put',
data: data
})
}
export function revokeCharge(data) {
return request({
url: '/finance/charge/revoke',
method: 'put',
data: data
})
}
// 删除财务计收
export function delCharge(id) {
return request({
url: '/finance/charge/' + id,
method: 'delete'
})
}
export function applyChargeBiz(data) {
return request({
url: '/finance/charge/applyBiz' ,
method: 'put',
data
})
}
export function returnApplyBiz(data) {
return request({
url: '/finance/charge/returnApplyBiz' ,
method: 'put',
data
})
}
export function applyCharge(data) {
return request({
url: '/finance/charge/apply' ,
method: 'put',
data
})
}

View File

@ -0,0 +1,136 @@
import request from '@/utils/request'
import {tansParams} from "@/utils/ruoyi"
// 查询销售收票单列表
export function listInvoice(query) {
return request({
url: '/finance/invoice/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
params: query
})
}
// 查询销售收票单详细
export function getInvoice(id) {
return request({
url: '/finance/invoice/' + id,
method: 'get'
})
}
// 查询销售收票单附件
export function getInvoiceAttachments(id, params) {
return request({
url: `/finance/invoice/attachment/${id}`,
method: 'get',
params
})
}
// 上传销售收票单附件
export function uploadInvoiceAttachment(data) {
return request({
url: '/finance/invoice/uploadReceipt',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: data,
needLoading: true
});
}
// 申请红冲
export function redRush(id) {
return request({
url: '/finance/invoice/applyRefund/' + id,
method: 'get'
})
}
// 申请红冲 (提交表单)
export function applyRefund(data) {
return request({
url: '/finance/invoice/applyRefund',
method: 'post',
data: data
})
}
// 退回销售收票单
export function returnInvoice(id) {
return request({
url: '/finance/invoice/return/' + id,
method: 'delete'
})
}
// 新增销售收票单
export function addInvoice(data) {
return request({
url: '/finance/receivable/mergeAndInitiateInvoice',
method: 'post',
data: data,
needLoading: true
})
}
// 查询销售收票单产品明细
export function getInvoiceProducts(code) {
return request({
url: '/finance/invoice/product/' + code,
method: 'get'
})
}
// 申请开票
export function applyInvoice(data) {
return request({
url: '/finance/invoice/apply',
method: 'post',
data: data
})
}
// 撤销销售收票单
export function revokeInvoice(id) {
return request({
url: '/finance/invoice/revoke/' + id,
method: 'delete'
})
}
// 查询开票审批列表
export function listInvoiceApprove(query) {
return request({
url: '/finance/invoice/approve/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询已审批开票列表
export function listInvoiceApproved(query) {
return request({
url: '/finance/invoice/approved/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询开票单详细
export function getInvoiceDetail(id) {
return request({
url: '/finance/invoice/' + id,
method: 'get'
})
}

View File

@ -0,0 +1,36 @@
import request from '@/utils/request'
// 查询收票待审批列表
export function listInvoiceReceiptApprove(query) {
return request({
url: '/finance/ticket/approve/list',
method: 'post',
data: query
})
}
// 查询收票已审批列表
export function listInvoiceReceiptApproved(query) {
return request({
url: '/finance/ticket/approved/list',
method: 'post',
data: query
})
}
// 查询收票详情
export function getInvoiceReceipt(id) {
return request({
url: '/finance/ticket/' + id,
method: 'get'
})
}
// 查询收票附件
export function getInvoiceReceiptAttachments(id,params) {
return request({
url: '/finance/ticket/attachment/' + id,
method: 'get',
params
})
}

View File

@ -0,0 +1,26 @@
import request from '@/utils/request'
// 查询核销记录列表
export function listInvoiceWriteOff(query) {
return request({
url: '/finance/invoice/writeoff/list',
method: 'get',
params: query
})
}
// 查询核销详情
export function getInvoiceWriteoff(id) {
return request({
url: '/finance/invoice/writeoff/' + id,
method: 'get'
})
}
// 删除核销记录
export function delInvoiceWriteoff(ids) {
return request({
url: '/finance/invoice/writeoff/' + ids,
method: 'delete'
})
}

View File

@ -0,0 +1,81 @@
import request from '@/utils/request'
// 查询采购应付单列表
export function listPayable(query) {
return request({
url: '/finance/payable/list',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data: query
})
}
// 查询采购应付单详情
export function getPayable(id) {
return request({
url: '/finance/payable/' + id,
method: 'get'
})
}
// 查询付款计划列表
export function getPaymentPlan(payableBillId) {
return request({
url: `/finance/payable/plan/${payableBillId}`,
method: 'get'
})
}
// 更新付款计划
export function updatePaymentPlan(payableBillId, data) {
return request({
url: `/finance/payable/plan/${payableBillId}`,
method: 'post',
data: data
})
}
// 合并并发起付款
export function mergeAndInitiatePayment(data) {
return request({
url: '/finance/payable/mergeAndInitiatePayment',
method: 'post',
data: data,
needLoading: true
})
}
// 合并并发起收票
export function mergeAndInitiateReceipt(data) {
return request({
url: '/finance/payable/mergeAndInitiateReceipt',
method: 'post',
data: data,
needLoading: true
})
}
// [PLACEHOLDER] 查询收票计划列表 - Endpoint to be confirmed by user
export function getReceivingTicketPlan(payableBillId) {
return request({
url: `/finance/ticket/plan/${payableBillId}`,
method: 'get'
})
}
// [PLACEHOLDER] 更新收票计划 - Endpoint to be confirmed by user
export function updateReceivingTicketPlan(payableBillId, data) {
return request({
url: `/finance/ticket/plan/${payableBillId}`,
method: 'post',
data: data
})
}
// 同步付款计划至发票计划
export function syncToTicketPlan(payableBillId) {
return request({
url: `/finance/payable/plan/sync/${payableBillId}`,
method: 'post'
})
}

View File

@ -0,0 +1,199 @@
import request from '@/utils/request'
import { tansParams } from "@/utils/ruoyi"
// 查询付款单列表
export function listPayment(query) {
return request({
url: '/finance/payment/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: tansParams(query)
})
}
export function exportPayment(query) {
return request({
url: '/finance/payment/export',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: tansParams(query)
})
}
// 查询付款单详细
export function getPayment(id) {
return request({
url: '/finance/payment/' + id,
method: 'get'
})
}
// 查询付款单附件
export function getPaymentAttachments(id, params) {
return request({
url: `/finance/payment/attachment/${id}`,
method: 'get',
params
})
}
export function deleteFile(id) {
return request({
url: `/finance/payment/attachment/${id}`,
method: 'delete'
})
}
// 上传付款单附件
export function uploadPaymentAttachment(data) {
return request({
url: '/finance/payment/uploadReceipt',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: data
});
}
// 退回付款单
export function returnPayment(id) {
return request({
url: '/finance/payment/returnPayment/' + id,
method: 'delete'
})
}
// 新增付款单
export function addPaymentFromPayable(data) {
return request({
url: '/finance/payable/mergeAndInitiatePayment',
method: 'post',
data: data,
needLoading: true
})
}
export function addPayment(data) {
return request({
url: '/finance/payment/add',
method: 'post',
data: data,
needLoading: true
})
}
export function handleRevoke(id) {
return request({
url: '/finance/payment/revoke',
method: 'post',
data: {id: id},
needLoading: true
})
}
// 申请付款
export function applyPaymentApi(data) {
return request({
url: '/finance/payment/applyPayment',
method: 'post',
data: data,
needLoading: true
})
}
// 申请退款
export function applyRefund(id) {
return request({
url: '/finance/payment/applyRefund/'+id,
method: 'get',
needLoading: true
})
}
export function deletePayment(id) {
return request({
url: '/finance/payment/remove',
method: 'post',
params:{ids:id},
needLoading: true
})
}
export function applyRefundApprove(id) {
return request({
url: '/finance/payment/applyRefundApprove',
method: 'post',
data: {id: id},
needLoading: true
})
}
// 查询应付单列表 (用于新增付款单-非预付)
export function listPayableBills(query) {
return request({
url: 'finance/payable/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询采购订单列表 (用于新增付款单-预付)
export function listOrders(query) {
return request({
url: '/project/order/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询付款审批列表
export function listPaymentApprove(query) {
return request({
url: '/finance/payment/approve/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: tansParams(query)
})
}
// 查询已审批付款列表
export function listPaymentApproved(query) {
return request({
url: '/finance/payment/approved/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: tansParams(query)
})
}
// 查询付款单列表 (核销专用)
export function listPaymentForWriteOff(query) {
return request({
url: '/finance/payment/write-off/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: tansParams(query)
})
}
// 手工匹配核销
export function manualWriteOff(data) {
return request({
url: '/finance/writeoff',
method: 'post',
data: data
})
}

View File

@ -0,0 +1,34 @@
import request from '@/utils/request'
import {tansParams} from "@/utils/ruoyi";
// 查询付款退款待审批列表
export function listPaymentRefundApprove(query) {
return request({
url: '/finance/payment/approve/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询付款退款已审批列表
export function listPaymentRefundApproved(query) {
return request({
url: '/finance/payment/approved/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
params: query
})
}
// 查询付款退款详情
export function getPaymentRefund(id) {
return request({
url: '/finance/paymentRefund/' + id,
method: 'get'
})
}

View File

@ -0,0 +1,122 @@
import request from '@/utils/request'
import {tansParams} from "@/utils/ruoyi"
// 查询收票单列表
export function listReceipt(query) {
return request({
url: '/finance/ticket/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询收票单详细
export function getReceipt(id) {
return request({
url: '/finance/ticket/' + id,
method: 'get'
})
}
// 查询收票单附件
export function getReceiptAttachments(id, params) {
return request({
url: `/finance/ticket/attachment/${id}`,
method: 'get',
params
})
}
// 上传收票单附件
export function uploadReceiptAttachment(data) {
return request({
url: '/finance/ticket/uploadReceipt',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: data,
needLoading: true
});
}
// 退回收票单
export function redRush(id) {
return request({
url: '/finance/ticket/applyRefund/' + id,
method: 'get'
})
}
export function returnReceipt(id) {
return request({
url: '/finance/ticket/return/' + id,
method: 'delete'
})
}
// 新增收票单
export function addReceipt(data) {
return request({
url: '/finance/payable/mergeAndInitiateReceipt',
method: 'post',
data: data,
needLoading: true
})
}
// 撤销收票单
export function revokeReceipt(id) {
return request({
url: '/finance/ticket/revoke/' + id,
method: 'put'
})
}
// 申请红冲(带附件)
export function applyRedRush(data) {
return request({
url: '/finance/ticket/applyRefund',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: data,
needLoading: true
})
}
// 查询收款审批列表
export function listReceiptApprove(query) {
return request({
url: '/finance/receipt/approve/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询已审批收款列表
export function listReceiptApproved(query) {
return request({
url: '/finance/receipt/approved/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询收款单详细
export function getReceiptDetail(id) {
return request({
url: '/finance/receipt/' + id,
method: 'get'
})
}

View File

@ -0,0 +1,80 @@
import request from '@/utils/request'
// 查询销售应收单列表
export function listReceivable(query) {
return request({
url: '/finance/receivable/list',
method: 'post',
data: query
})
}
// 查询销售应收单详情
export function getReceivable(id) {
return request({
url: '/finance/receivable/' + id,
method: 'get'
})
}
// 查询收款计划列表
export function getReceiptPlan(receivableBillId) {
return request({
url: `/finance/receivable/plan/${receivableBillId}`,
method: 'get'
})
}
// 更新收款计划
export function updateReceiptPlan(receivableBillId, data) {
return request({
url: `/finance/receivable/plan/${receivableBillId}`,
method: 'post',
data: data
})
}
// 合并并发起收款
export function mergeAndInitiateReceipt(data) {
return request({
url: '/finance/receivable/mergeAndInitiateReceipt',
method: 'post',
data: data,
needLoading:true
})
}
// 合并并发起开票
export function mergeAndInitiateInvoice(data) {
return request({
url: '/finance/receivable/mergeAndInitiateInvoice',
method: 'post',
data: data,
needLoading:true
})
}
// 查询开票计划列表
export function getInvoicePlan(receivableBillId) {
return request({
url: `/finance/receivable/invoice/plan/${receivableBillId}`,
method: 'get'
})
}
// 更新开票计划
export function updateInvoicePlan(receivableBillId, data) {
return request({
url: `/finance/receivable/invoice/plan/${receivableBillId}`,
method: 'post',
data: data
})
}
// 同步收款计划至开票计划
export function syncToInvoicePlan(receivableBillId) {
return request({
url: `/finance/receivable/plan/sync/${receivableBillId}`,
method: 'post'
})
}

View File

@ -0,0 +1,26 @@
import request from '@/utils/request'
// 查询销售核销记录列表
export function listReceivableWriteOff(query) {
return request({
url: '/finance/receivable/writeoff/list',
method: 'get',
params: query
})
}
// 查询销售核销详情
export function getReceivableWriteOff(id) {
return request({
url: '/finance/receivable/writeoff/' + id,
method: 'get'
})
}
// 删除销售核销记录
export function delReceivableWriteOff(ids) {
return request({
url: '/finance/receivable/writeoff/' + ids,
method: 'delete'
})
}

View File

@ -0,0 +1,130 @@
import request from '@/utils/request'
import {tansParams} from "@/utils/ruoyi"
// 查询收款单列表
export function listReceive(query) {
return request({
url: '/finance/receipt/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询收款单详细
export function getReceive(id) {
return request({
url: '/finance/receipt/' + id,
method: 'get'
})
}
// 查询收款单附件
export function getReceiveAttachments(id, params) {
return request({
url: `/finance/receipt/attachment/${id}`,
method: 'get',
params
})
}
// 上传收款单附件
export function uploadReceiveAttachment(data) {
return request({
url: '/finance/receipt/uploadReceipt',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: data,
needLoading: true
});
}
// 申请红冲
export function redRush(id) {
return request({
url: '/finance/receipt/applyRefund/' + id,
method: 'get'
})
}
// 退回收款单
export function returnReceive(id) {
return request({
url: '/finance/receipt/returnReceivable/' + id,
method: 'delete'
})
}
// 新增收款单 (Calls Receivable Merge Logic)
export function mergeReceivable(data) {
return request({
url: '/finance/receivable/mergeAndInitiateReceipt',
method: 'post',
data: data,
needLoading: true
})
}
export function addReceipt(data) {
return request({
url: '/finance/receipt/insert',
method: 'post',
data: data,
needLoading: true
})
}
export function applyReceipt(data) {
return request({
url: '/finance/receipt/applyReceipt',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data,
needLoading: true
})
}
// 申请退款
export function submitRefund(data) {
return request({
url: '/finance/receipt/applyRefund',
method: 'post',
data: data,
needLoading: true
})
}
// 查询收款单列表 (核销专用)
export function listReceiptForWriteOff(query) {
return request({
url: '/finance/receipt/write-off/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: tansParams(query)
})
}
// 手工匹配核销
export function manualReceiptWriteOff(data) {
return request({
url: '/finance/receivable/writeoff',
method: 'post',
data: data
})
}
// 撤销收款单
export function revokeReceipt(id) {
return request({
url: '/finance/receipt/revoke',
method: 'post',
data: {id: id},
needLoading: true
})
}

View File

@ -0,0 +1,11 @@
import request from '@/utils/request'
// 查询项目报表列表
export function listReport(query) {
return request({
url: '/finance/report/list',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data: query
})
}

View File

@ -0,0 +1,45 @@
import request from '@/utils/request'
// 查询核销记录列表
export function listWriteOff(query) {
return request({
url: '/finance/writeoff/list',
method: 'get',
params: query
})
}
export function listTicketWriteOff(query) {
return request({
url: '/finance/ticketWriteoff/list',
method: 'get',
params: query
})
}
// 查询核销详情
export function getWriteOff(id) {
return request({
url: '/finance/writeoff/' + id,
method: 'get'
})
}
export function getTicketWriteoff(id) {
return request({
url: '/finance/ticketWriteoff/' + id,
method: 'get'
})
}
// 删除核销记录
export function delWriteOff(ids) {
return request({
url: '/finance/writeoff/' + ids,
method: 'delete'
})
}
export function delTicketWriteoff(ids) {
return request({
url: '/finance/ticketWriteoff/' + ids,
method: 'delete'
})
}

View File

@ -4,8 +4,10 @@ import request from '@/utils/request'
export function listProject(query) {
return request({
url: '/sip/project/vue/list',
method: 'get',
params: query
method: 'post',
data: query,
headers: { 'Content-Type': 'multipart/form-data' },
})
}
@ -52,3 +54,29 @@ export function exportProject(query) {
params: query
})
}
// 项目收藏
export function addCollect(data) {
return request({
url: '/project/collect/add',
method: 'post',
data: data,
needLoading:true
})
}
export function editJoinTrial(data) {
return request({
url: '/sip/project/vue/joinTrial',
method: 'put',
data: data,
needLoading:true
})
}
export function exportSingle(id) {
return request({
url: `/sip/project/vue/joinTrial/export/${id}`,
method: 'get',
needLoading:true
})
}

View File

@ -106,6 +106,16 @@ export function recallPurchaseorder(id) {
method: 'put'
})
}
export function exportPurchaseorder(data) {
return request({
url: '/sip/purchaseorder/export',
method: 'get',
params: data,
// headers: { 'Content-Type': 'multipart/form-data' },
needLoading: true
})
}
// 查询已审批采购单主表列表
export function listApprovedPurchaseorder(query) {

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询公司信息列表
export function listCompanyInfo(query) {
return request({
url: '/sip/companyInfo/list',
method: 'get',
params: query
})
}
// 查询公司信息详细
export function getCompanyInfo(id) {
return request({
url: '/sip/companyInfo/' + id,
method: 'get'
})
}
// 新增公司信息
export function addCompanyInfo(data) {
return request({
url: '/sip/companyInfo',
method: 'post',
data: data
})
}
// 修改公司信息
export function updateCompanyInfo(data) {
return request({
url: '/sip/companyInfo',
method: 'put',
data: data
})
}
// 删除公司信息
export function delCompanyInfo(id) {
return request({
url: '/sip/companyInfo/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1,64 @@
<template>
<div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;max-height: 60vh" />
</el-dialog>
</div>
</template>
<script>
import request from '@/utils/request';
export default {
name: "GlobalFilePreview",
data() {
return {
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
methods: {
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(attachment) {
if (!attachment) return;
if (this.isPdf(attachment.filePath)) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: attachment.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
this.currentImageUrl = this.getImageUrl(attachment.filePath);
this.imagePreviewVisible = true;
}
},
downloadFile(attachment){
if (attachment){
const link = document.createElement('a');
link.href = process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" +attachment.filePath;
link.download = attachment.fileName || 'file';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
},
}
};
</script>

View File

@ -11,7 +11,7 @@
<el-button size="mini" circle icon="el-icon-menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button size="mini" circle icon="el-icon-menu" />
<el-dropdown-menu slot="dropdown">
<el-dropdown-menu slot="dropdown" >
<!-- 全选/反选 按钮 -->
<el-dropdown-item>
<el-checkbox :indeterminate="isIndeterminate" v-model="isChecked" @change="toggleCheckAll"> </el-checkbox>
@ -183,4 +183,8 @@ export default {
background-color: #ccc;
margin: 3px auto;
}
.el-dropdown-menu{
max-height: 400px;
overflow-y: auto;
}
</style>

View File

@ -21,6 +21,7 @@ import './permission' // permission control
import { getDicts } from "@/api/system/dict/data"
import { getConfigKey } from "@/api/system/config"
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi"
import { formatCurrency } from "@/utils"
// 分页组件
import Pagination from "@/components/Pagination"
// 自定义表格工具组件
@ -42,6 +43,7 @@ import DictData from '@/components/DictData'
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
Vue.prototype.formatCurrency = formatCurrency
Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel

View File

@ -9,13 +9,12 @@ const baseURL = process.env.VUE_APP_BASE_API
let downloadLoadingInstance
export default {
name(name, isDelete = true) {
download(name, isDelete = true) {
var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data)
if (isBlob) {

View File

@ -94,6 +94,46 @@ export const constantRoutes = [
component: () => import('@/views/approve/approved_order/index'),
hidden: true
},
{
path: 'paymentLog',
component: () => import('@/views/approve/finance/payment/approved/index'),
hidden: true
},
{
path: 'paymentRedLog',
component: () => import('@/views/approve/finance/paymentRefund/approved/index'),
hidden: true
},
{
path: 'invoiceRedLog',
component: () => import('@/views/approve/finance/invoiceRed/approved/index'),
hidden: true
},
{
path: 'invoiceLog',
component: () => import('@/views/approve/finance/invoiceReceipt/approved/index'),
hidden: true
},
{
path: 'receiptLog',
component: () => import('@/views/approve/finance/receipt/approved/index.vue'),
hidden: true
},
{
path: 'receiptRefoundLog',
component: () => import('@/views/approve/finance/receiptRefound/approved/index.vue'),
hidden: true
},
{
path: 'receivableInvoiceLog',
component: () => import('@/views/approve/finance/receivableInvoice/approved/index.vue'),
hidden: true
},
{
path: 'receivableInvoiceRefundLog',
component: () => import('@/views/approve/finance/receivableInvoiceRefund/approved/index.vue'),
hidden: true
},
]
},
{
@ -164,6 +204,31 @@ export const dynamicRoutes = [
}
]
},
{
path: '/finance',
component: Layout,
redirect: 'noRedirect',
name: 'Finance',
meta: {
title: '财务管理',
icon: 'money'
},
children: [
{
path: 'payment',
component: () => import('@/views/finance/payment/index'),
name: 'Payment',
meta: { title: '付款单', icon: 'form' }
},
{
path: 'charge',
component: () => import('@/views/finance/charge/index'),
name: 'Charge',
meta: { title: '财务计收', icon: 'money' },
permissions: ['finance:charge:list']
}
]
},
{
path: '/inventory/execution',
component: Layout,

View File

@ -19,17 +19,17 @@ export function toFixed(value, dp = DEFAULT_DP) {
// 加法
export function add(a, b, dp = DEFAULT_DP) {
return D(a).plus(b).toDecimalPlaces(dp).toNumber()
return D(a).plus(D(b)).toDecimalPlaces(dp).toNumber()
}
// 减法
export function sub(a, b, dp = DEFAULT_DP) {
return D(a).minus(b).toDecimalPlaces(dp).toNumber()
return D(a).minus(D(b)).toDecimalPlaces(dp).toNumber()
}
// 乘法
export function mul(a, b, dp = DEFAULT_DP) {
return D(a).times(b).toDecimalPlaces(dp).toNumber()
return D(a).times(D(b)).toDecimalPlaces(dp).toNumber()
}
// 除法

View File

@ -14,7 +14,10 @@ export function formatDate(cellValue) {
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
}
export function formatCurrency(value) {
if (value == null) return '0.00';
return Number(value).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
}
/**
* @param {number} time
* @param {string} option

View File

@ -49,6 +49,22 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="产品编码" prop="productCode">
<el-input
v-model="queryParams.productCode"
placeholder="请输入产品编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="产品型号" prop="productModel">
<el-input
v-model="queryParams.productModel"
placeholder="请输入产品型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
@ -72,7 +88,11 @@
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="项目编号" align="center" prop="projectCode" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="订单金额" align="center" prop="actualPurchaseAmount" />
<el-table-column label="订单金额" align="center" prop="actualPurchaseAmount" >
<template slot-scope="scope">
<span>{{ formatCurrency(scope.row.actualPurchaseAmount || scope.row.shipmentAmount) }}</span>
</template>
</el-table-column>
<el-table-column label="汇智负责人" align="center" prop="dutyName" />
<el-table-column label="审批节点" align="center" prop="approveNode">
<template slot-scope="scope">
@ -127,6 +147,7 @@
<script>
import { listOrder } from "@/api/approve/order/orderLog";
import ApproveDialog from '../order/Approve.vue';
import {formatCurrency} from "../../../utils";
export default {
name: "ApprovedOrder",
@ -153,6 +174,8 @@ export default {
orderCode: null,
projectName: null,
projectCode: null,
productCode: null,
productModel: null,
customerName: null,
dutyName: null,
approveNode: null,
@ -165,6 +188,7 @@ export default {
this.getList();
},
methods: {
formatCurrency,
/** 查询订单列表 */
getList() {
this.loading = true;

View File

@ -0,0 +1,211 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收票编号" prop="receiptNo">
<el-input v-model="queryParams.receiptNo" placeholder="请输入收票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="供应商" prop="vendorName">
<el-input v-model="queryParams.vendorName" placeholder="请输入供应商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="invoiceReceiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收票编号" align="center" prop="ticketBillCode" />
<el-table-column label="供应商" align="center" prop="vendorName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="收票单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="收票单详情">
<invoice-receipt-detail :data="form"></invoice-receipt-detail>
<template #footer>
<span> {{ form.ticketBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceReceiptApproved, getInvoiceReceipt } from "@/api/finance/invoiceReceipt";
import { listCompletedFlows } from "@/api/flow";
import InvoiceReceiptDetail from "../components/InvoiceReceiptDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "InvoiceReceiptApproved",
components: { InvoiceReceiptDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceReceiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptNo: null,
vendorName: null,
processKey: 'fianance_ticket',
projectName: null
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentInvoiceReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceReceiptApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceReceiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentInvoiceReceiptId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceReceipt(this.currentInvoiceReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.ticketBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `收票单-${this.form.receiptBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<div class="invoice-receipt-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">收票申请单</div>
<el-descriptions title="收票单信息" :column="3" border>
<el-descriptions-item label="采购-收票单编号">{{ data.ticketBillCode }}</el-descriptions-item>
<el-descriptions-item label="制造商名称">{{ data.vendorName }}</el-descriptions-item>
<el-descriptions-item label="票据类型">
<dict-tag :options="dict.type.finance_invoice_type" :value="data.ticketType"/>
</el-descriptions-item>
<el-descriptions-item label="含税总价(元)">{{ formatCurrency(data.totalPriceWithTax) }}</el-descriptions-item>
<el-descriptions-item label="未税总价(元)">{{ formatCurrency(data.totalPriceWithoutTax) }}</el-descriptions-item>
<el-descriptions-item label="税额(元)">{{ formatCurrency(data.taxAmount) }}</el-descriptions-item>
<el-descriptions-item label="预计收票时间">{{ data.ticketTime }}</el-descriptions-item>
<el-descriptions-item label="制造商开票时间">{{ data.vendorTicketTime }}</el-descriptions-item>
<!-- <el-descriptions-item label="备注" :span="3">{{ data.remark }}</el-descriptions-item>-->
</el-descriptions>
<div class="section" style="margin-top: 20px;" v-show="data.detailList && data.detailList.length>0">
<div class="el-descriptions__title">发票明细列表</div>
<el-table :data="data.detailList" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="payableBillCode" label="采购-应付单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="paymentAmount" label="本次收票金额" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createByName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
</div>
</template>
<script>
import { getInvoiceReceiptAttachments } from "@/api/finance/invoiceReceipt";
import request from '@/utils/request';
export default {
name: "InvoiceReceiptDetail",
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['finance_invoice_type', 'product_type'],
data() {
return {
attachments: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
'data.id': {
handler(val) {
if (val) {
this.fetchAttachments(val);
}
},
immediate: true
}
},
methods: {
fetchAttachments(id) {
getInvoiceReceiptAttachments(id,{ type: 'ticket' }).then(response => {
this.attachments = (response.data || []).filter(item => item.delFlag !== '2');
});
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.invoice-receipt-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,300 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收票编号" prop="receiptNo">
<el-input v-model="queryParams.receiptNo" placeholder="请输入收票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="供应商" prop="vendorName">
<el-input v-model="queryParams.vendorName" placeholder="请输入供应商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="invoiceReceiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收票编号" align="center" prop="ticketBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<!-- <el-table-column label="登记人" align="center" prop="createUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="收票单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="收票单详情">
<invoice-receipt-detail :data="form"></invoice-receipt-detail>
<template #footer>
<span>{{ form.ticketBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b><el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceReceiptApprove, getInvoiceReceipt } from "@/api/finance/invoiceReceipt";
import { approveTask, listCompletedFlows } from "@/api/flow";
import InvoiceReceiptDetail from "./components/InvoiceReceiptDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "InvoiceReceiptApprove",
components: { InvoiceReceiptDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceReceiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptNo: null,
vendorName: null,
processKey: 'fianance_ticket',
projectName: null
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'fianance_ticket',
taskId: null,
currentInvoiceReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceReceiptApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceReceiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push( '/approve/invoiceLog' )
},
handleApprove(row) {
this.resetDetailForm();
this.currentInvoiceReceiptId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceReceipt(this.currentInvoiceReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.ticketBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
let keys = [];
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.ticketBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `收票单-${this.form.ticketBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,212 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收票编号" prop="receiptNo">
<el-input v-model="queryParams.receiptNo" placeholder="请输入收票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="供应商" prop="vendorName">
<el-input v-model="queryParams.vendorName" placeholder="请输入供应商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="invoiceReceiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收票编号" align="center" prop="ticketBillCode" />
<el-table-column label="供应商" align="center" prop="vendorName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="红冲发票详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="红冲发票详情">
<invoice-red-detail :data="form"></invoice-red-detail>
<template #footer>
<span> {{ form.ticketBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceReceiptApproved, getInvoiceReceipt } from "@/api/finance/invoiceReceipt";
import { listCompletedFlows } from "@/api/flow";
import InvoiceRedDetail from "../components/InvoiceRedDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "InvoiceRedApproved",
components: { InvoiceRedDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceReceiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptNo: null,
vendorName: null,
processKey: 'finance_ticket_refound',
projectName: null
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentInvoiceReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceReceiptApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceReceiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentInvoiceReceiptId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceReceipt(this.currentInvoiceReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.ticketBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `红冲发票-${this.form.receiptBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,139 @@
<template>
<div class="invoice-red-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">红冲收票申请单</div>
<el-descriptions title="红冲发票信息" :column="3" border>
<el-descriptions-item label="采购-收票单编号">{{ data.ticketBillCode }}</el-descriptions-item>
<el-descriptions-item label="制造商名称">{{ data.vendorName }}</el-descriptions-item>
<el-descriptions-item label="票据类型">
<dict-tag :options="dict.type.finance_invoice_type" :value="data.ticketType"/>
</el-descriptions-item>
<el-descriptions-item label="含税总价(元)"><span style="color: red">{{ formatCurrency(data.totalPriceWithTax) }} </span></el-descriptions-item>
<el-descriptions-item label="未税总价(元)"><span style="color: red">{{ formatCurrency(data.totalPriceWithTax) }}</span></el-descriptions-item>
<el-descriptions-item label="税额(元)"><span style="color: red">{{ formatCurrency(data.taxAmount) }} </span></el-descriptions-item>
<el-descriptions-item label="收票时间">{{ data.ticketTime }}</el-descriptions-item>
<el-descriptions-item label="制造商开票时间">{{ data.vendorTicketTime }}</el-descriptions-item>
<!-- <el-descriptions-item label="备注" :span="3">{{ data.remark }}</el-descriptions-item>-->
</el-descriptions>
<div class="section" style="margin-top: 20px;" v-show="data.detailList && data.detailList.length>0">
<div class="el-descriptions__title">发票明细列表</div>
<el-table :data="data.detailList" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="payableBillCode" label="采购-应付单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="paymentAmount" label="本次红冲金额" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createByName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
</div>
</template>
<script>
import { getInvoiceReceiptAttachments } from "@/api/finance/invoiceReceipt";
import request from '@/utils/request';
export default {
name: "InvoiceRedDetail",
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['finance_invoice_type', 'product_type'],
data() {
return {
attachments: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
'data.id': {
handler(val) {
if (val) {
this.fetchAttachments(val);
}
},
immediate: true
}
},
methods: {
fetchAttachments(id) {
// invoiceReceipt
getInvoiceReceiptAttachments(id,{ type: 'ticket' }).then(response => {
this.attachments = (response.data || []).filter(item => item.delFlag !== '2');
});
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.invoice-red-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,302 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收票编号" prop="receiptNo">
<el-input v-model="queryParams.receiptNo" placeholder="请输入收票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="供应商" prop="vendorName">
<el-input v-model="queryParams.vendorName" placeholder="请输入供应商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="invoiceReceiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收票编号" align="center" prop="ticketBillCode" />
<el-table-column label="供应商" align="center" prop="vendorName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)">
</el-table-column>
<!-- <el-table-column label="登记人" align="center" prop="createUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="红冲发票审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="红冲发票详情">
<invoice-red-detail :data="form"></invoice-red-detail>
<template #footer>
<span> {{ form.ticketBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceReceiptApprove, getInvoiceReceipt } from "@/api/finance/invoiceReceipt";
import { approveTask, listCompletedFlows } from "@/api/flow";
import InvoiceRedDetail from "./components/InvoiceRedDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "InvoiceRedApprove",
components: { InvoiceRedDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceReceiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptNo: null,
vendorName: null,
processKey: 'finance_ticket_refound',
projectName: null
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_ticket_refound',
taskId: null,
currentInvoiceReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceReceiptApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceReceiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push( '/approve/invoiceRedLog' )
},
handleApprove(row) {
this.resetDetailForm();
this.currentInvoiceReceiptId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceReceipt(this.currentInvoiceReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.ticketBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
let keys = [];
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.ticketBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `红冲发票-${this.form.ticketBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,213 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="付款编号" prop="paymentNo">
<el-input v-model="queryParams.paymentNo" placeholder="请输入付款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="制造商" prop="manufacturer">
<el-input v-model="queryParams.manufacturer" placeholder="请输入制造商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="paymentList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="付款编号" align="center" prop="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="付款单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="付款单详情">
<payment-detail :data="form"></payment-detail>
<template #footer>
<span> {{ form.paymentBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b><el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listPaymentApproved, getPayment } from "@/api/finance/payment";
import { listCompletedFlows } from "@/api/flow";
import PaymentDetail from "../components/PaymentDetail"; // Relative path adjustment
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "PaymentApproved",
components: { PaymentDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
paymentList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
paymentNo: null,
manufacturer: null,
projectName: null,
processKey: 'finance_payment',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentPaymentId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listPaymentApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.paymentList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentPaymentId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getPayment(this.currentPaymentId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.paymentBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `付款单-${this.form.paymentBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,140 @@
<template>
<div class="payment-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">付款申请单</div>
<el-descriptions title="付款单信息" :column="3" border>
<el-descriptions-item label="采购-付款单编号">{{ data.paymentBillCode }}</el-descriptions-item>
<el-descriptions-item label="制造商名称">{{ data.vendorName }}</el-descriptions-item>
<el-descriptions-item label="付款条件">
{{data.payType==='0'?'入库付款':'出库付款'}}
</el-descriptions-item>
<el-descriptions-item label="付款周期">
{{data.payConfigDay}}
</el-descriptions-item>
<el-descriptions-item label="含税总价(元)">{{ formatCurrency(data.totalPriceWithTax) }}</el-descriptions-item>
<!-- <el-descriptions-item label="未税总价(元)">{{ data.totalPriceWithoutTax }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="税额(元)">{{ data.taxAmount }}</el-descriptions-item>-->
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.payment_method" :value="data.paymentMethod"/>
</el-descriptions-item>
<el-descriptions-item label="银行账号">{{ data.payBankNumber }}</el-descriptions-item>
<el-descriptions-item label="账户名称">{{ data.payName }}</el-descriptions-item>
<el-descriptions-item label="银行开户行">{{ data.payBankOpenAddress }}</el-descriptions-item>
<el-descriptions-item label="银行行号">{{ data.bankNumber }}</el-descriptions-item>
<el-descriptions-item span="2"></el-descriptions-item>
<el-descriptions-item label="其它特别说明" :span="3">{{ data.remark }}</el-descriptions-item>
</el-descriptions>
<div class="section" style="margin-top: 20px;" >
<div class="el-descriptions__title">应付单信息</div>
<el-table :data="data.payableDetails" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="payableBillCode" label="采购-应付单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center"></el-table-column>
<el-table-column prop="paymentAmount" label="本次付款金额" align="center"></el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
</div>
</template>
<script>
import request from '@/utils/request';
import {formatCurrency} from "@/utils";
export default {
name: "PaymentDetail",
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['payment_bill_type', 'product_type','payment_method'],
data() {
return {
attachments: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
},
methods: {
formatCurrency,
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
// PDF Preview logic
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
// Image Preview
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.payment-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,312 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="付款编号" prop="paymentNo">
<el-input v-model="queryParams.paymentNo" placeholder="请输入付款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="制造商" prop="manufacturer">
<el-input v-model="queryParams.manufacturer" placeholder="请输入制造商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="paymentList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="付款编号" align="center" prop="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="付款单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="付款单详情">
<payment-detail :data="form"></payment-detail>
<template #footer>
<span>付款编号: {{ form.paymentBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b>
<el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listPaymentApprove, getPayment } from "@/api/finance/payment";
import { approveTask, listCompletedFlows } from "@/api/flow";
import PaymentDetail from "./components/PaymentDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "PaymentApprove",
components: { PaymentDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
paymentList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
paymentNo: null,
manufacturer: null,
projectName: null,
processKey: 'finance_payment',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_payment',
taskId: null,
currentPaymentId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listPaymentApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.paymentList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push( '/approve/paymentLog' )
},
handleApprove(row) {
this.resetDetailForm();
this.currentPaymentId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getPayment(this.currentPaymentId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.paymentBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
// Assuming processKeyList might be generic or specific, using generic fetch for now
// Usually need to know the specific process key. For payment, it might be 'payment_approval' etc.
// However, listCompletedFlows logic in reference used specific keys.
// If I don't know the key, maybe I can omit it or guess.
// The reference used `processKeyList: ['purchase_order_online']`.
// I will use a generic one or try to find it.
// If row.processKey is available I can use that.
let keys = [];
console.log(this.processKey)
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.paymentBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
// Map status codes to text
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `付款单-${this.form.paymentBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,217 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="付款编号" prop="paymentNo">
<el-input v-model="queryParams.paymentNo" placeholder="请输入付款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="制造商" prop="manufacturer">
<el-input v-model="queryParams.manufacturer" placeholder="请输入制造商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="paymentList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="付款编号" align="center" prop="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" >
<template slot-scope="scope">
<span style="color: red">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="付款单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="付款单详情">
<payment-detail :data="form"></payment-detail>
<template #footer>
<span> {{ form.paymentBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listPaymentApproved, getPayment } from "@/api/finance/payment";
import { listCompletedFlows } from "@/api/flow";
import PaymentDetail from "../components/PaymentRefundDetail.vue"; // Relative path adjustment
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "PaymentApproved",
components: { PaymentDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
paymentList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
paymentNo: null,
manufacturer: null,
projectName: null,
processKey: 'finance_refund',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentPaymentId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listPaymentApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.paymentList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentPaymentId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getPayment(this.currentPaymentId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.paymentBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `付款单-${this.form.paymentBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<div class="payment-refund-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">退款申请单</div>
<el-descriptions title="付款单信息" :column="3" border>
<el-descriptions-item label="采购付款单编号">{{ data.paymentBillCode }}</el-descriptions-item>
<el-descriptions-item label="制造商名称">{{ data.vendorName }}</el-descriptions-item>
<el-descriptions-item label="付款条件">
{{data.payType==='0'?'入库付款':'出库付款'}}
</el-descriptions-item>
<el-descriptions-item label="付款周期">
{{data.payConfigDay}}
</el-descriptions-item>
<el-descriptions-item label="含税总价(元)"><span style="color: red">{{ formatCurrency(data.totalPriceWithTax) }}</span></el-descriptions-item>
<!-- <el-descriptions-item label="未税总价(元)"><span style="color: red">{{ data.totalPriceWithoutTax }}</span></el-descriptions-item>-->
<!-- <el-descriptions-item label="税额(元)"><span style="color: red">{{ data.taxAmount }}</span></el-descriptions-item>-->
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.payment_method" :value="data.paymentMethod"/>
</el-descriptions-item>
<el-descriptions-item label="银行账号">{{ data.payBankNumber }}</el-descriptions-item>
<el-descriptions-item label="账户名称">{{ data.payName }}</el-descriptions-item>
<el-descriptions-item label="银行开户行">{{ data.payBankOpenAddress }}</el-descriptions-item>
<el-descriptions-item label="银行行号" span="1">{{ data.bankNumber }}</el-descriptions-item>
<el-descriptions-item span="2"></el-descriptions-item>
<el-descriptions-item label="其它特别说明" span="3">{{ data.remark }}</el-descriptions-item>
</el-descriptions>
<div class="section" style="margin-top: 20px;" >
<div class="el-descriptions__title">应付单信息</div>
<el-table :data="data.payableDetails" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="payableBillCode" label="采购应付单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<!-- Note: Product Type is requested but not present in reference DetailDrawer.vue. Omitting for safety or need to ask. -->
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center"></el-table-column>
<el-table-column prop="paymentAmount" label="本次付款金额" align="center"></el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import {formatCurrency} from "@/utils";
export default {
name: "PaymentRefundDetail",
methods: {formatCurrency},
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['payment_bill_type', 'payment_method','product_type']
};
</script>
<style scoped>
.payment-refund-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,313 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="付款编号" prop="paymentNo">
<el-input v-model="queryParams.paymentNo" placeholder="请输入付款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="制造商" prop="manufacturer">
<el-input v-model="queryParams.manufacturer" placeholder="请输入制造商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="paymentList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="付款编号" align="center" prop="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" >
<template slot-scope="scope">
<span style="color: red">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="付款单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="付款单详情">
<payment-detail :data="form"></payment-detail>
<template #footer>
<span>付款编号: {{ form.paymentBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listPaymentApprove, getPayment } from "@/api/finance/payment";
import { approveTask, listCompletedFlows } from "@/api/flow";
import PaymentDetail from "./components/PaymentRefundDetail.vue";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "PaymentApprove",
components: { PaymentDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
paymentList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
paymentNo: null,
manufacturer: null,
projectName: null,
processKey: 'finance_refund',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_refund',
taskId: null,
currentPaymentId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listPaymentApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.paymentList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push( '/approve/paymentRedLog' )
},
handleApprove(row) {
this.resetDetailForm();
this.currentPaymentId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getPayment(this.currentPaymentId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.paymentBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
// Assuming processKeyList might be generic or specific, using generic fetch for now
// Usually need to know the specific process key. For payment, it might be 'payment_approval' etc.
// However, listCompletedFlows logic in reference used specific keys.
// If I don't know the key, maybe I can omit it or guess.
// The reference used `processKeyList: ['purchase_order_online']`.
// I will use a generic one or try to find it.
// If row.processKey is available I can use that.
let keys = [];
console.log(this.processKey)
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.paymentBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
// Map status codes to text
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `付款单-${this.form.paymentBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,63 @@
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
/**
* 导出指定DOM元素为PDF
* @param {HTMLElement} element - 要导出的DOM元素
* @param {string} fileName - 导出的文件名
* @returns {Promise<void>}
*/
export async function exportElementToPDF(element, fileName) {
const disabledElements = [];
try {
// 移除所有输入框的 disabled 属性以便在PDF中正确显示
element.querySelectorAll('input:disabled, textarea:disabled').forEach(el => {
disabledElements.push(el);
el.disabled = false;
});
// 使用html2canvas捕获内容
const canvas = await html2canvas(element, {
scale: 2, // 提高清晰度
useCORS: true, // 允许跨域图片
logging: false, // 关闭日志
backgroundColor: '#F8F5F0' // 设置背景色
});
// 计算PDF页面尺寸
const imgWidth = 210; // A4纸宽度mm
const pageHeight = 297; // A4纸高度mm
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
// 创建PDF
const pdf = new jsPDF('p', 'mm', 'a4');
let position = 0;
// 将canvas转换为图片
const imgData = canvas.toDataURL('image/jpeg');
// 添加第一页
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
// 如果内容超过一页,添加更多页
while (heightLeft > 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
// 保存PDF
pdf.save(fileName);
} catch (error) {
console.error('PDF导出失败:', error);
throw error;
} finally {
// 恢复之前移除的 disabled 属性
disabledElements.forEach(el => {
el.disabled = true;
});
}
}

View File

@ -0,0 +1,213 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收款编号" prop="receiptBillCode">
<el-input v-model="queryParams.receiptBillCode" placeholder="请输入收款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入进货商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="receiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收款编号" align="center" prop="receiptBillCode" />
<el-table-column label="客户" align="center" prop="partnerName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="收款单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="收款单详情">
<receipt-detail :data="form"></receipt-detail>
<template #footer>
<span>{{ form.receiptBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listReceiptApproved, getReceiptDetail } from "@/api/finance/receipt";
import { listCompletedFlows } from "@/api/flow";
import ReceiptDetail from "../components/ReceiptDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceiptApproved",
components: { ReceiptDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
receiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_receipt_approve',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listReceiptApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.receiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentReceiptId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getReceiptDetail(this.currentReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.receiptBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `收款单-${this.form.receiptBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<div class="receipt-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">收款申请单</div>
<el-descriptions title="收款单信息" :column="3" border>
<el-descriptions-item label="销售-收款单编号">{{ data.receiptBillCode }}</el-descriptions-item>
<el-descriptions-item :span="2" label="进货商名称">{{ data.partnerName }}</el-descriptions-item>
<!-- <el-descriptions-item label="收款单类型">-->
<!-- <dict-tag :options="dict.type.receipt_bill_type" :value="data.receiptBillType"/>-->
<!-- </el-descriptions-item>-->
<el-descriptions-item label="含税总价(元)">{{ formatCurrency(data.totalPriceWithTax) }}</el-descriptions-item>
<el-descriptions-item label="未税总价(元)">{{ formatCurrency(data.totalPriceWithoutTax) }}</el-descriptions-item>
<el-descriptions-item label="税额(元)">{{ formatCurrency(data.taxAmount) }}</el-descriptions-item>
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.payment_method" :value="data.receiptMethod"/>
</el-descriptions-item>
<el-descriptions-item label="银行账号">{{ data.receiptBankNumber }}</el-descriptions-item>
<el-descriptions-item label="账户名称">{{ data.receiptAccountName }}</el-descriptions-item>
<el-descriptions-item label="银行开户行">{{ data.receiptBankOpenAddress }}</el-descriptions-item>
<el-descriptions-item label="银行行号">{{ data.bankNumber }}</el-descriptions-item>
<!-- <el-descriptions-item label="备注" :span="3">{{ data.remark }}</el-descriptions-item>-->
</el-descriptions>
<div class="section" style="margin-top: 20px;" v-show="data.detailDTOList && data.detailDTOList.length>0">
<div class="el-descriptions__title">应收单列表</div>
<el-table :data="data.detailDTOList" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="receivableBillCode" label="销售-应收单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="receiptAmount" label="本次收款金额" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createByName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
</div>
</template>
<script>
import request from '@/utils/request';
import {getInvoiceAttachments} from "@/api/finance/invoice";
import {getReceiptAttachments} from "@/api/finance/receipt";
export default {
name: "ReceiptDetail",
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['receipt_bill_type', 'product_type','payment_method'],
data() {
return {
attachments: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
'data.id': {
handler(val) {
if (val) {
this.fetchAttachments(val);
}
},
immediate: true
}
},
methods: {
fetchAttachments(id) {
getReceiptAttachments(id,{type: "receipt"}).then(response => {
this.attachments = (response.data || []).filter(item => item.delFlag !== '2');
});
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
// PDF Preview logic
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
// Image Preview
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.receipt-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,300 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收款编号" prop="receiptBillCode">
<el-input v-model="queryParams.receiptBillCode" placeholder="请输入收款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入进货商名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="receiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收款编号" align="center" prop="receiptBillCode" />
<el-table-column label="进货商" align="center" prop="partnerName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="收款单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="收款单详情">
<receipt-detail :data="form"></receipt-detail>
<template #footer>
<span>{{ form.receiptBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listReceiptApprove, getReceiptDetail } from "@/api/finance/receipt";
import { approveTask, listCompletedFlows } from "@/api/flow";
import ReceiptDetail from "./components/ReceiptDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceiptApprove",
components: { ReceiptDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
receiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_receipt_approve',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_receipt_approve',
taskId: null,
currentReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listReceiptApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.receiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push( '/approve/receiptLog' )
},
handleApprove(row) {
this.resetDetailForm();
this.currentReceiptId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getReceiptDetail(this.currentReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.receiptBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
let keys = [];
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.receiptBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `收款单-${this.form.receiptBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,213 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收款编号" prop="receiptBillCode">
<el-input v-model="queryParams.receiptBillCode" placeholder="请输入收款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入进货商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="receiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收款编号" align="center" prop="receiptBillCode" />
<el-table-column label="客户" align="center" prop="partnerName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="收款单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="收款单详情">
<receipt-detail :data="form"></receipt-detail>
<template #footer>
<span> {{ form.receiptBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listReceiptApproved, getReceiptDetail } from "@/api/finance/receipt";
import { listCompletedFlows } from "@/api/flow";
import ReceiptDetail from "../components/ReceiptDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceiptRefoundApproved",
components: { ReceiptDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
receiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_receipt_refound',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listReceiptApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.receiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentReceiptId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getReceiptDetail(this.currentReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.receiptBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `收款单-${this.form.receiptBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<div class="receipt-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">退款申请单</div>
<el-descriptions title="收款单信息" :column="3" border>
<el-descriptions-item label="销售-收款单编号">{{ data.receiptBillCode }}</el-descriptions-item>
<el-descriptions-item :span="2" label="进货商名称">{{ data.partnerName }}</el-descriptions-item>
<!-- <el-descriptions-item label="收款单类型">-->
<!-- <dict-tag :options="dict.type.receipt_bill_type" :value="data.receiptBillType"/>-->
<!-- </el-descriptions-item>-->
<el-descriptions-item label="含税总价(元)"><span style="color: red">{{ formatCurrency(data.totalPriceWithTax) }}</span></el-descriptions-item>
<el-descriptions-item label="未税总价(元)"><span style="color: red">{{ formatCurrency(data.totalPriceWithoutTax) }}</span></el-descriptions-item>
<el-descriptions-item label="税额(元)"><span style="color: red">{{ formatCurrency(data.taxAmount) }}</span></el-descriptions-item>
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.payment_method" :value="data.receiptMethod"/>
</el-descriptions-item>
<el-descriptions-item label="银行账号">{{ data.receiptBankNumber }}</el-descriptions-item>
<el-descriptions-item label="账户名称">{{ data.receiptAccountName }}</el-descriptions-item>
<el-descriptions-item label="银行开户行">{{ data.receiptBankOpenAddress }}</el-descriptions-item>
<el-descriptions-item label="银行行号">{{ data.bankNumber }}</el-descriptions-item>
<!-- <el-descriptions-item label="备注" :span="3">{{ data.remark }}</el-descriptions-item>-->
</el-descriptions>
<div class="section" style="margin-top: 20px;" v-show="data.detailDTOList && data.detailDTOList.length>0">
<div class="el-descriptions__title">应收单列表</div>
<el-table :data="data.detailDTOList" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="receivableBillCode" label="销售-应收单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center"></el-table-column>
<el-table-column prop="receiptAmount" label="本次收款金额" align="center"></el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createByName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
</div>
</template>
<script>
import request from '@/utils/request';
export default {
name: "ReceiptRefoundDetail",
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['receipt_bill_type', 'product_type','payment_method'],
data() {
return {
attachments: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
},
methods: {
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
// PDF Preview logic
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
// Image Preview
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.receipt-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,304 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="收款编号" prop="receiptBillCode">
<el-input v-model="queryParams.receiptBillCode" placeholder="请输入收款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入进货商名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="receiptList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="收款编号" align="center" prop="receiptBillCode" />
<el-table-column label="进货商" align="center" prop="partnerName" />
<!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" >
<template slot-scope="scope">
<span style="color: red">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="收款单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="收款单详情">
<receipt-detail :data="form"></receipt-detail>
<template #footer>
<span>{{ form.receiptBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b><el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listReceiptApprove, getReceiptDetail } from "@/api/finance/receipt";
import { approveTask, listCompletedFlows } from "@/api/flow";
import ReceiptDetail from "./components/ReceiptDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceiptRefoundApprove",
components: { ReceiptDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
receiptList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
receiptBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_receipt_refound',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_receipt_refound',
taskId: null,
currentReceiptId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listReceiptApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.receiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push( '/approve/receiptRefoundLog' )
},
handleApprove(row) {
this.resetDetailForm();
this.currentReceiptId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getReceiptDetail(this.currentReceiptId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.receiptBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
let keys = [];
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.receiptBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `收款单-${this.form.receiptBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,211 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="开票编号" prop="invoiceBillCode">
<el-input v-model="queryParams.invoiceBillCode" placeholder="请输入开票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="客户" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入客户" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="invoiceList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="开票编号" align="center" prop="invoiceBillCode" />
<el-table-column label="客户" align="center" prop="partnerName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="开票单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="开票单详情">
<receivable-invoice-detail :data="form"></receivable-invoice-detail>
<template #footer>
<span>{{ form.invoiceBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceApproved, getInvoiceDetail } from "@/api/finance/invoice";
import { listCompletedFlows } from "@/api/flow";
import ReceivableInvoiceDetail from "../components/ReceivableInvoiceDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceivableInvoiceApproved",
components: { ReceivableInvoiceDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
invoiceBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_invoice_approve',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentInvoiceId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentInvoiceId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceDetail(this.currentInvoiceId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.invoiceBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `开票单-${this.form.invoiceBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<div class="invoice-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">开票申请单</div>
<el-descriptions title="开票单信息" :column="3" border>
<el-descriptions-item label="销售-开票单编号">{{ data.invoiceBillCode }}</el-descriptions-item>
<el-descriptions-item :span="2" label="进货商">{{ data.partnerName }}</el-descriptions-item>
<el-descriptions-item label="含税总价(元)">{{ formatCurrency(data.totalPriceWithTax) }}</el-descriptions-item>
<el-descriptions-item label="未税总价(元)">{{ formatCurrency(data.totalPriceWithoutTax) }}</el-descriptions-item>
<el-descriptions-item label="税额(元)">{{ formatCurrency(data.taxAmount) }}</el-descriptions-item>
<!-- <el-descriptions-item label="银行账号">{{ data.buyerBankAccount }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="账户名称">{{ data.buyerName }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="银行开户行">{{ data.buyerBank }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="银行行号">{{ data.bankNumber }}</el-descriptions-item> -->
</el-descriptions>
<div class="section" style="margin-top: 20px;">
<div class="el-descriptions__title">发票信息</div>
<invoice-info-view :data="data" />
</div>
<div class="section" style="margin-top: 20px;" v-show="data.detailDTOList && data.detailDTOList.length>0">
<div class="el-descriptions__title">应收单列表</div>
<el-table :data="data.detailDTOList" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="receivableBillCode" label="销售-应收单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="receiptAmount" label="本次开票金额" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="excelList && excelList.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="excelList" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传信息" align="center"></el-table-column>
<el-table-column prop="createTime" label="生成时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- <div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">-->
<!-- <div class="el-descriptions__title">附件信息</div>-->
<!-- <el-table :data="attachments" border style="width: 100%; margin-top: 10px;">-->
<!-- <el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>-->
<!-- <el-table-column prop="createUserName" label="上传人" align="center"></el-table-column>-->
<!-- <el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>-->
<!-- <el-table-column label="操作" align="center">-->
<!-- <template slot-scope="scope">-->
<!-- <el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>-->
<!-- <el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </div>-->
<!-- <el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">-->
<!-- <iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>-->
<!-- </el-dialog>-->
<!-- <el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">-->
<!-- <img :src="currentImageUrl" style="width: 100%;" />-->
<!-- </el-dialog>-->
</div>
</template>
<script>
import { getInvoiceAttachments } from "@/api/finance/invoice";
import request from '@/utils/request';
import InvoiceInfoView from '@/views/finance/invoice/components/InvoiceInfoView';
export default {
name: "ReceivableInvoiceDetail",
components: { InvoiceInfoView },
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['product_type'],
data() {
return {
attachments: [],
excelList: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
data: {
handler(val) {
if (val && val.updateTime) {
this.excelList = [{
fileName: '电子发票--购买方公司信息.xlsx',
createUserName: '系统生成',
createTime: val.updateTime,
isSystemGenerated: true
}];
}
},
immediate: true,
deep: true
}
},
methods: {
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
if (row.isSystemGenerated) {
request({
url: '/finance/invoice/export/' + this.data.invoiceBillCode,
method: 'get'
}).then(res => {
window.location.href = process.env.VUE_APP_BASE_API + "/common/download?fileName=" + encodeURIComponent(res.msg) + "&delete=true";
});
return;
}
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.invoice-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,298 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="开票编号" prop="invoiceBillCode">
<el-input v-model="queryParams.invoiceBillCode" placeholder="请输入开票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入进货商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="invoiceList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="开票编号" align="center" prop="invoiceBillCode" />
<el-table-column label="进货商" align="center" prop="partnerName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="开票单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="开票单详情">
<receivable-invoice-detail :data="form"></receivable-invoice-detail>
<template #footer>
<span>{{ form.invoiceBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceApprove, getInvoiceDetail } from "@/api/finance/invoice";
import { approveTask, listCompletedFlows } from "@/api/flow";
import ReceivableInvoiceDetail from "./components/ReceivableInvoiceDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceivableInvoiceApprove",
components: { ReceivableInvoiceDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
invoiceBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_invoice_approve',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_invoice_approve',
taskId: null,
currentInvoiceId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push('/approve/receivableInvoiceLog')
},
handleApprove(row) {
this.resetDetailForm();
this.currentInvoiceId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceDetail(this.currentInvoiceId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.invoiceBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
let keys = [];
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.invoiceBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `开票单-${this.form.invoiceBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,215 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="开票编号" prop="invoiceBillCode">
<el-input v-model="queryParams.invoiceBillCode" placeholder="请输入开票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="客户" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入客户" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="invoiceList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="开票编号" align="center" prop="invoiceBillCode" />
<el-table-column label="客户" align="center" prop="partnerName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax < 0 ? 'color: red' : ''">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="开票单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="开票单详情">
<receivable-invoice-detail :data="form"></receivable-invoice-detail>
<template #footer>
<span>{{ form.invoiceBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceApproved, getInvoiceDetail } from "@/api/finance/invoice";
import { listCompletedFlows } from "@/api/flow";
import ReceivableInvoiceDetail from "../components/ReceivableInvoiceDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceivableInvoiceRefundApproved",
components: { ReceivableInvoiceDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
invoiceBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_invoice_refound',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentInvoiceId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentInvoiceId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceDetail(this.currentInvoiceId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.invoiceBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `开票单-${this.form.invoiceBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,183 @@
<template>
<div class="invoice-detail">
<div style="text-align: center;font-weight:bold;font-size: 25px;">红冲开票申请单</div>
<el-descriptions title="开票单信息" :column="3" border>
<el-descriptions-item label="销售-开票单编号">{{ data.invoiceBillCode }}</el-descriptions-item>
<el-descriptions-item :span="2" label="进货商">{{ data.partnerName }}</el-descriptions-item>
<el-descriptions-item label="含税总价(元)">
<span :style="data.totalPriceWithTax < 0 ? 'color: red' : ''">{{ formatCurrency(data.totalPriceWithTax) }}</span>
</el-descriptions-item>
<el-descriptions-item label="未税总价(元)">
<span :style="data.totalPriceWithoutTax < 0 ? 'color: red' : ''">{{ formatCurrency(data.totalPriceWithoutTax) }}</span>
</el-descriptions-item>
<el-descriptions-item label="税额(元)">
<span :style="data.taxAmount < 0 ? 'color: red' : ''">{{ formatCurrency(data.taxAmount) }}</span>
</el-descriptions-item>
<!-- <el-descriptions-item label="银行账号">{{ data.buyerBankAccount }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="账户名称">{{ data.buyerName }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="银行开户行">{{ data.buyerBank }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="银行行号">{{ data.bankNumber }}</el-descriptions-item> -->
</el-descriptions>
<div class="section" style="margin-top: 20px;">
<div class="el-descriptions__title">发票信息</div>
<invoice-info-view :data="data" />
</div>
<div class="section" style="margin-top: 20px;" v-show="data.detailDTOList && data.detailDTOList.length>0">
<div class="el-descriptions__title">应收单列表</div>
<el-table :data="data.detailDTOList" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="receivableBillCode" label="销售-应收单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax < 0 ? 'color: red' : ''">{{ scope.row.totalPriceWithTax }}</span>
</template>
</el-table-column>
<el-table-column prop="receiptAmount" label="本次开票金额" align="center">
<template slot-scope="scope">
<span :style="scope.row.receiptAmount < 0 ? 'color: red' : ''">{{ scope.row.receiptAmount }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="excelList && excelList.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="excelList" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传信息" align="center"></el-table-column>
<el-table-column prop="createTime" label="生成时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
</div>
</template>
<script>
import { getInvoiceAttachments } from "@/api/finance/invoice";
import request from '@/utils/request';
import InvoiceInfoView from '@/views/finance/invoice/components/InvoiceInfoView';
export default {
name: "ReceivableInvoiceDetail",
components: { InvoiceInfoView },
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['product_type'],
data() {
return {
attachments: [],
excelList: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
data: {
handler(val) {
if (val && val.updateTime) {
this.excelList = [{
fileName: '电子发票--购买方公司信息.xlsx',
createUserName: '系统生成',
createTime: val.updateTime,
isSystemGenerated: true
}];
}
},
immediate: true,
deep: true
}
},
methods: {
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
if (row.isSystemGenerated) {
request({
url: '/finance/invoice/export/' + this.data.invoiceBillCode,
method: 'get'
}).then(res => {
window.location.href = process.env.VUE_APP_BASE_API + "/common/download?fileName=" + encodeURIComponent(res.msg) + "&delete=true";
});
return;
}
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.invoice-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,302 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="开票编号" prop="invoiceBillCode">
<el-input v-model="queryParams.invoiceBillCode" placeholder="请输入开票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入进货商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="invoiceList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="开票编号" align="center" prop="invoiceBillCode" />
<el-table-column label="进货商" align="center" prop="partnerName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax < 0 ? 'color: red' : ''">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="开票单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="开票单详情">
<receivable-invoice-detail :data="form"></receivable-invoice-detail>
<template #footer>
<span>{{ form.invoiceBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag :type="log.approveStatus == '3' ? 'success' : log.approveStatus == '2' ? 'danger' : 'info'">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceApprove, getInvoiceDetail } from "@/api/finance/invoice";
import { approveTask, listCompletedFlows } from "@/api/flow";
import ReceivableInvoiceDetail from "./components/ReceivableInvoiceDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceivableInvoiceRefundApprove",
components: { ReceivableInvoiceDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
invoiceBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_invoice_refound',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_invoice_refound',
taskId: null,
currentInvoiceId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push('/approve/receivableInvoiceRefundLog')
},
handleApprove(row) {
this.resetDetailForm();
this.currentInvoiceId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceDetail(this.currentInvoiceId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.invoiceBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
let keys = [];
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.invoiceBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `开票单-${this.form.invoiceBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -135,8 +135,7 @@
import { approveOrder,getOrder } from "@/api/approve/order";
import ConfigInfo from './ConfigInfo.vue';
import ApproveLayout from '@/views/approve/ApproveLayout.vue';
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
import OrderInfoDisplay from '@/views/project/order/components/OrderInfoDisplay.vue';
@ -468,65 +467,21 @@ export default {
// PDF
async exportPDF() {
this.pdfExporting = true;
const disabledElements = [];
try {
// ApproveLayoutDOM
const element = this.$refs.approveLayout.$el;
// disabled 便PDF
element.querySelectorAll('input:disabled, textarea:disabled').forEach(el => {
disabledElements.push(el);
el.disabled = false;
});
// 使html2canvas
const canvas = await html2canvas(element, {
scale: 2, //
useCORS: true, //
logging: false, //
backgroundColor: '#F8F5F0' //
});
// PDF
const imgWidth = 210; // A4mm
const pageHeight = 297; // A4mm
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
// PDF
const pdf = new jsPDF('p', 'mm', 'a4');
let position = 0;
// canvas
const imgData = canvas.toDataURL('image/jpeg');
//
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
//
while (heightLeft > 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
//
const fileName = `${this.order.projectCode || '订单'}-${this.order.orderCode || ''}-Rev.${this.order.versionCode || '1'}.pdf`;
// PDF
pdf.save(fileName);
// PDF
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
// disabled
disabledElements.forEach(el => {
el.disabled = true;
});
this.pdfExporting = false;
}
}

View File

@ -178,7 +178,7 @@
</el-col>
</el-row>
<el-row type="flex" justify="end" align="middle" class="summary-row">
<el-col :span="18" style="text-align: center;">
<el-col :span="orderData.orderStatus==='1'?18:24" style="text-align: center;">
<span style="margin-right: 5px;">商业折扣</span>
<el-select
v-model="selectedDiscount"
@ -195,7 +195,7 @@
</el-option>
</el-select>
</el-col>
<el-col :span="6">
<el-col :span="6" v-if="orderData.orderStatus==='1'">
<span class="summary-label"> 折后总价合计</span> <span
class="summary-value-right"> {{ formatCurrency(finalTotal) }}</span>
</el-col>
@ -278,7 +278,12 @@ export default {
return this.calculateTotal(this.order.maintenanceProjectProductInfoList, this.selectedDiscount);
},
grandTotal() {
if (this.orderData.orderStatus === '1'){
return this.softwareTotal + this.hardwareTotal + this.maintenanceTotal;
}else{
return this.finalTotal;
}
},
finalTotal() {
return this.softwareDiscountedTotal + this.hardwareDiscountedTotal + this.maintenanceDiscountedTotal;

View File

@ -49,15 +49,28 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="产品编码" prop="productCode">
<el-input
v-model="queryParams.productCode"
placeholder="请输入产品编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="产品型号" prop="productModel">
<el-input
v-model="queryParams.productModel"
placeholder="请输入产品型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
@ -75,7 +88,12 @@
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="项目编号" align="center" prop="projectCode" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="订单金额" align="center" prop="actualPurchaseAmount" />
<el-table-column label="订单金额" align="center" prop="actualPurchaseAmount" >
<template slot-scope="scope">
<span>{{ formatCurrency(scope.row.actualPurchaseAmount || scope.row.shipmentAmount) }}</span>
</template>
</el-table-column>
<el-table-column label="汇智负责人" align="center" prop="dutyName" />
<el-table-column label="审批节点" align="center" prop="approveNode">
<template slot-scope="scope">
@ -106,6 +124,7 @@
<el-dialog
title="订单审批"
:visible.sync="approveDialogVisible"
:close-on-click-modal="false"
custom-class="approve-dialog"
width="80%"
top="5vh"
@ -130,6 +149,7 @@
<script>
import { listOrder } from "@/api/approve/order";
import ApproveDialog from './Approve.vue';
import {formatCurrency} from "../../../utils";
export default {
name: "Order",
@ -156,6 +176,8 @@ export default {
orderCode: null,
projectName: null,
projectCode: null,
productCode: null,
productModel: null,
customerName: null,
dutyName: null,
approveNode: null,
@ -166,6 +188,7 @@ export default {
this.getList();
},
methods: {
formatCurrency,
toApproved(){
this.$router.push({
path: '/approve/orderLog',

View File

@ -23,7 +23,22 @@
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item label="产品编码" prop="productCode">
<el-input
v-model="queryParams.productCode"
placeholder="请输入产品编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="产品型号" prop="productModel">
<el-input
v-model="queryParams.productModel"
placeholder="请输入产品型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
@ -142,6 +157,8 @@ export default {
buyerName: null,
vendorName: null,
ownerName: null,
productCode: null,
productModel: null,
approveStatus: '1',
params:{
applyTimeStart: null,

View File

@ -23,7 +23,22 @@
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item label="产品编码" prop="productCode">
<el-input
v-model="queryParams.productCode"
placeholder="请输入产品编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="产品型号" prop="productModel">
<el-input
v-model="queryParams.productModel"
placeholder="请输入产品型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
@ -134,6 +149,8 @@ export default {
purchaseNo: null,
buyerName: null,
vendorName: null,
productCode: null,
productModel: null,
ownerName: null,
approveStatus: '3', // 3
params:{

View File

@ -0,0 +1,143 @@
<template>
<el-drawer
title="报价单详情"
:visible.sync="visible"
direction="rtl"
size="80%"
:before-close="handleClose"
append-to-body
>
<div class="detail-container" v-loading="loading">
<!-- Basic Info -->
<el-divider content-position="left">基本信息</el-divider>
<el-descriptions :column="2" border size="medium">
<el-descriptions-item label="报价单号">{{ form.quotationCode }}</el-descriptions-item>
<el-descriptions-item label="报价单名称">{{ form.quotationName }}</el-descriptions-item>
<el-descriptions-item label="币种">
<dict-tag :options="dict.type.currency_type" :value="form.amountType"/>
</el-descriptions-item>
<el-descriptions-item label="代表处">{{ agentName }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ form.customerName }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ form.quotationStatus }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ parseTime(form.createTime) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ form.remark }}</el-descriptions-item>
</el-descriptions>
<div v-if="form.quotationCode || catalogueTotalPrice > 0 || discountedTotalPrice > 0" style="margin-top: 20px;">
<el-row type="flex" justify="space-between" style="margin-bottom: 20px; font-size: 14px;">
<el-col :span="24" style="text-align: right;">
<span v-if="catalogueTotalPrice > 0" style="margin-right: 20px;">
<span style="font-weight: bold;">目录总价</span>{{ formatAmount(catalogueTotalPrice) }}
</span>
<span v-if="discountedTotalPrice > 0">
<span style="font-weight: bold;">折后总价</span>{{ formatAmount(discountedTotalPrice) }}
</span>
</el-col>
</el-row>
</div>
<!-- Config Info -->
<product-config :value="form" readonly />
</div>
</el-drawer>
</template>
<script>
import { getQuotation } from "@/api/base/quotation";
import ProductConfig from "@/views/project/info/ProductConfig";
export default {
name: "QuotationDetail",
components: { ProductConfig },
dicts: ['currency_type'],
props: {
agentOptions: {
type: Array,
default: () => []
}
},
data() {
return {
visible: false,
loading: false,
form: {
softwareProjectProductInfoList: [],
hardwareProjectProductInfoList: [],
maintenanceProjectProductInfoList: []
}
};
},
computed: {
agentName() {
if (!this.form.agentCode || !this.agentOptions) return this.form.agentCode;
const agent = this.agentOptions.find(item => item.agentCode === this.form.agentCode);
return agent ? agent.agentName : this.form.agentCode;
},
catalogueTotalPrice() {
let total = 0;
const lists = [
this.form.softwareProjectProductInfoList,
this.form.hardwareProjectProductInfoList,
this.form.maintenanceProjectProductInfoList
];
lists.forEach(list => {
if (list && list.length > 0) {
list.forEach(item => {
total += Number(item.catalogueAllPrice) || 0;
});
}
});
return total;
},
discountedTotalPrice() {
let total = 0;
const lists = [
this.form.softwareProjectProductInfoList,
this.form.hardwareProjectProductInfoList,
this.form.maintenanceProjectProductInfoList
];
lists.forEach(list => {
if (list && list.length > 0) {
list.forEach(item => {
total += Number(item.allPrice) || 0;
});
}
});
return total;
}
},
methods: {
open(id) {
this.visible = true;
this.getDetail(id);
},
getDetail(id) {
this.loading = true;
getQuotation(id).then(response => {
this.form = response.data;
// Ensure lists are arrays
this.form.softwareProjectProductInfoList = this.form.softwareProjectProductInfoList || [];
this.form.hardwareProjectProductInfoList = this.form.hardwareProjectProductInfoList || [];
this.form.maintenanceProjectProductInfoList = this.form.maintenanceProjectProductInfoList || [];
this.loading = false;
});
},
handleClose(done) {
this.visible = false;
if (done) done();
},
formatAmount(value) {
if (value === null || value === undefined) return '';
return Number(value).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
},
}
};
</script>
<style scoped>
.detail-container {
padding: 20px;
}
</style>

View File

@ -0,0 +1,600 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="报价单号" prop="quotationCode">
<el-input
v-model="queryParams.quotationCode"
placeholder="请输入报价单号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="报价单" prop="quotationName">
<el-input
v-model="queryParams.quotationName"
placeholder="请输入报价单名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="quotationStatus">
<el-input
v-model="queryParams.quotationStatus"
placeholder="请输入状态"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['sip:quotation:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['sip:quotation:update']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['sip:quotation:delete']"
>删除</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="quotationList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="报价单号" align="center" prop="quotationCode" />
<el-table-column label="报价单" align="center" prop="quotationName" />
<el-table-column label="项目编号" align="center" prop="projectCode" />
<!-- <el-table-column label="报价金额" align="center" prop="quotationAmount" />-->
<el-table-column label="报价金额(¥)" align="center" prop="discountAmount" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<el-table-column label="状态" align="center" prop="quotationStatus" >
<template slot-scope="scope">
<dict-tag :options="dict.type.quotation_status" :value="scope.row.quotationStatus"/>
</template>
</el-table-column>
<el-table-column label="创建人" align="center" prop="createByName" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['sip:quotation:update']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document-copy"
@click="handleCopy(scope.row)"
v-hasPermi="['sip:quotation:add']"
>复制创建</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['sip:quotation:delete']"
>删除</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-download"
@click="handleExport(scope.row)"
v-hasPermi="['sip:quotation:export']"
>导出</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改报价单对话框 -->
<el-dialog :title="title" :visible.sync="open" width="80%" append-to-body :close-on-click-modal="false" @close="cancel">
<el-steps :active="activeStep" simple finish-status="success" style="margin-bottom: 20px; cursor: pointer;">
<el-step icon="el-icon-caret-left" @click.native="handleStepClick(0)">
<template #title>
<i class="el-icon-caret-left"></i>
报价单列表
</template>
</el-step>
<el-step title="设置基本信息" @click.native="handleStepClick(1)"></el-step>
<el-step title="配置信息" @click.native="handleStepClick(2)"></el-step>
</el-steps>
<div v-if="form.quotationCode || catalogueTotalPrice > 0 || discountedTotalPrice > 0">
<el-divider></el-divider>
<el-row type="flex" justify="space-between" style="margin-bottom: 20px; font-size: 14px;">
<el-col :span="8">
<span v-if="form.quotationCode">
<span style="font-weight: bold;">报价单号</span>{{ form.quotationCode }}
</span>
</el-col>
<el-col :span="16" style="text-align: right;">
<span v-if="catalogueTotalPrice > 0" style="margin-right: 20px;">
<span style="font-weight: bold;">目录总价</span>{{ formatAmount(catalogueTotalPrice) }}
</span>
<span v-if="discountedTotalPrice > 0">
<span style="font-weight: bold;">折后总价</span>{{ formatAmount(discountedTotalPrice) }}
</span>
</el-col>
</el-row>
<el-divider></el-divider>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="100px" style="max-height: 50vh;overflow-y: auto;padding: 10px">
<!-- Step 1 Content: Basic Info -->
<div v-show="activeStep === 1">
<el-row>
<el-col :span="12">
<el-form-item label="报价单名称" prop="quotationName">
<el-input v-model="form.quotationName" placeholder="请输入报价单名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="币种" prop="amountType">
<el-select v-model="form.amountType" placeholder="请选择币种" style="width: 100%">
<el-option
v-for="dict in dict.type.currency_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="代表处" prop="agentCode">
<el-select v-model="form.agentCode" placeholder="请选择代表处" style="width: 100%">
<el-option
v-for="item in agentOptions"
:key="item.agentCode"
:label="item.agentName"
:value="item.agentCode"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户名称" prop="customerName">
<el-input v-model="form.customerName" placeholder="请输入客户名称" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="报价单备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</div>
<!-- Step 2 Content: Config Info -->
<div v-show="activeStep === 2">
<product-config :value="form" @input="updateProductConfig" />
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<div v-if="activeStep === 1">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="nextStep"></el-button>
</div>
<div v-if="activeStep === 2">
<el-button @click="prevStep"></el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</div>
</el-dialog>
<quotation-detail ref="detail" :agent-options="agentOptions" />
</div>
</template>
<script>
import { listQuotation, getQuotation, delQuotation, addQuotation, updateQuotation,exportSingleQuotation } from "@/api/base/quotation";
import { listAgent } from "@/api/system/agent";
import ProductConfig from "@/views/project/info/ProductConfig";
import QuotationDetail from "./detail";
import {isEmpty} from "@/utils/validate";
export default {
name: "Quotation",
components: {
ProductConfig,
QuotationDetail
},
dicts: ['currency_type','quotation_status'],
data() {
return {
//
activeStep: 1,
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
quotationList: [],
//
dateRange: [],
//
agentOptions: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
quotationCode: null,
quotationName: null,
projectCode: null,
quotationStatus: null,
createTimeStart: null,
createTimeEnd: null,
},
//
form: {
softwareProjectProductInfoList: [],
hardwareProjectProductInfoList: [],
maintenanceProjectProductInfoList: []
},
//
rules: {
quotationName: [
{ required: true, message: "报价单名称不能为空", trigger: "blur" }
],
amountType: [
{ required: true, message: "币种不能为空", trigger: "change" }
],
agentCode: [
{ required: true, message: "代表处不能为空", trigger: "change" }
],
customerName: [
{ required: true, message: "客户名称不能为空", trigger: "blur" }
],
}
};
},
computed: {
catalogueTotalPrice() {
let total = 0;
const lists = [
this.form.softwareProjectProductInfoList,
this.form.hardwareProjectProductInfoList,
this.form.maintenanceProjectProductInfoList
];
lists.forEach(list => {
if (list && list.length > 0) {
list.forEach(item => {
total += Number(item.catalogueAllPrice) || 0;
});
}
});
return total;
},
discountedTotalPrice() {
let total = 0;
const lists = [
this.form.softwareProjectProductInfoList,
this.form.hardwareProjectProductInfoList,
this.form.maintenanceProjectProductInfoList
];
lists.forEach(list => {
if (list && list.length > 0) {
list.forEach(item => {
total += Number(item.allPrice) || 0;
});
}
});
return total;
}
},
created() {
this.getList();
this.getAgentList();
},
methods: {
/** 查询报价单列表 */
getList() {
this.loading = true;
if (null != this.dateRange && '' != this.dateRange) {
this.queryParams.createTimeStart = this.dateRange[0];
this.queryParams.createTimeEnd = this.dateRange[1];
}
listQuotation(this.queryParams).then(response => {
this.quotationList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleDetail(row) {
this.$refs.detail.open(row.id);
},
handleExport(row){
this.$modal.confirm('是否确认导出已审批的采购数据项?').then(() => {
return exportSingleQuotation(row.id);
}).then(response => {
this.$download.download( response.msg)
})
},
/** 查询代表处列表 */
getAgentList() {
listAgent().then(response => {
this.agentOptions = response.rows;
});
},
//
cancel() {
this.open = false;
this.reset();
this.activeStep = 1;
},
//
reset() {
this.form = {
id: null,
quotationCode: null,
quotationName: null,
projectCode: null,
quotationAmount: null,
discountAmount: null,
quotationStatus: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
agentCode: null,
amountType: null,
customerName: null,
softwareProjectProductInfoList: [],
hardwareProjectProductInfoList: [],
maintenanceProjectProductInfoList: []
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.queryParams.createTimeStart = null;
this.queryParams.createTimeEnd = null;
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.activeStep = 1;
this.title = "添加报价单";
},
/** 复制创建 */
handleCopy(row) {
this.reset();
const id = row.id || this.ids
getQuotation(id).then(response => {
this.form = response.data;
// Reset ID and Code for new creation
this.form.id = null;
this.form.quotationCode = null;
this.form.quotationStatus = null;
this.form.createTime = null;
this.form.updateTime = null;
this.form.createBy = null;
this.form.updateBy = null;
// Ensure product lists are initialized if null
this.form.softwareProjectProductInfoList = this.form.softwareProjectProductInfoList || [];
this.form.hardwareProjectProductInfoList = this.form.hardwareProjectProductInfoList || [];
this.form.maintenanceProjectProductInfoList = this.form.maintenanceProjectProductInfoList || [];
// Clear IDs in configuration lists to ensure they are created as new records
const clearIds = (list) => {
if (list && list.length > 0) {
list.forEach(item => {
item.id = null;
item.quotationId = null;
});
}
};
clearIds(this.form.softwareProjectProductInfoList);
clearIds(this.form.hardwareProjectProductInfoList);
clearIds(this.form.maintenanceProjectProductInfoList);
this.open = true;
this.activeStep = 1;
this.title = "复制创建报价单";
});
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getQuotation(id).then(response => {
this.form = response.data;
// Ensure product lists are initialized if null
this.form.softwareProjectProductInfoList = this.form.softwareProjectProductInfoList || [];
this.form.hardwareProjectProductInfoList = this.form.hardwareProjectProductInfoList || [];
this.form.maintenanceProjectProductInfoList = this.form.maintenanceProjectProductInfoList || [];
this.open = true;
this.activeStep = 1;
this.title = "修改报价单";
});
},
/** 处理步骤点击 */
handleStepClick(stepIndex) {
if (stepIndex === 0) {
this.cancel();
} else if (stepIndex === 1) {
this.activeStep = 1;
} else if (stepIndex === 2) {
// Validate step 1 before going to step 2
this.$refs["form"].validateField(['quotationName', 'amountType', 'agentCode', 'customerName'], (errorMessage) => {
if (!errorMessage) {
this.activeStep = 2;
}
});
}
},
/** 下一步 */
nextStep() {
// Step 1
this.$refs["form"].validateField(['quotationName', 'amountType', 'agentCode', 'customerName'], (errorMessage) => {
if (!errorMessage) {
this.activeStep = 2;
}
});
},
/** 上一步 */
prevStep() {
this.activeStep = 1;
},
updateProductConfig(data) {
this.form.softwareProjectProductInfoList = data.softwareProjectProductInfoList;
this.form.hardwareProjectProductInfoList = data.hardwareProjectProductInfoList;
this.form.maintenanceProjectProductInfoList = data.maintenanceProjectProductInfoList;
},
formatAmount(value) {
if (value === null || value === undefined) return '';
return Number(value).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
const checkProduct=(list)=>isEmpty(list) ||( !isEmpty(list) && list.every(item => item.productBomCode!==''))
if (!checkProduct(this.form.softwareProjectProductInfoList) || !checkProduct(this.form.hardwareProjectProductInfoList) || !checkProduct(this.form.maintenanceProjectProductInfoList)) {
this.$modal.msgError("请完善产品信息");
return;
}
this.form.quotationAmount = this.catalogueTotalPrice;
this.form.discountAmount = this.discountedTotalPrice;
if (this.form.id != null) {
updateQuotation(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addQuotation(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除报价单?').then(function() {
return delQuotation(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
}
};
</script>
<style scoped>
/* 只有第一步显示自定义图标,覆盖完成状态的对勾 */
::v-deep .el-step:first-child .el-step__icon-inner.is-status {
display: none;
}
::v-deep .el-step:first-child .el-step__icon-inner:not(.is-status) {
display: inline-block;
}
/* 当前激活步骤标题颜色鲜明 */
::v-deep .el-step__title.is-process {
color: #1890ff;
font-weight: 700;
}
/* 当前激活步骤图标颜色鲜明 */
::v-deep .el-step__head.is-process {
color: #1890ff;
border-color: #1890ff;
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<el-dialog title="选择报价单" :visible.sync="visible" :close-on-click-modal="false" width="900px" append-to-body @close="handleClose">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" @submit.native.prevent>
<el-form-item label="报价单号" prop="quotationCode">
<el-input
v-model="queryParams.quotationCode"
placeholder="请输入报价单号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="报价单名称" prop="quotationName">
<el-input
v-model="queryParams.quotationName"
placeholder="请输入报价单名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="quotationList" @row-click="handleRowClick" highlight-current-row>
<el-table-column label="报价单号" align="center" prop="quotationCode" />
<el-table-column label="报价单名称" align="center" prop="quotationName" />
<el-table-column label="项目编号" align="center" prop="projectCode" />
<el-table-column label="报价金额" align="center" prop="discountAmount" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
</div>
</el-dialog>
</template>
<script>
import { listQuotation } from "@/api/base/quotation";
export default {
name: "SelectQuotation",
props: {
visible: {
type: Boolean,
default: false,
},
createBy: {
type: String,
default: "-1",
},
},
data() {
return {
//
loading: true,
//
total: 0,
//
quotationList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
createBy: this.createBy,
quotationCode: null,
quotationName: null,
orderByColumn: 'createTime',
isAsc: 'desc'
},
};
},
watch: {
visible(val) {
if (val) {
this.queryParams.createBy = this.createBy||"-1";
this.getList();
}
},
},
methods: {
/** 查询报价单列表 */
getList() {
this.loading = true;
listQuotation(this.queryParams).then(response => {
this.quotationList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 行点击事件 */
handleRowClick(row) {
this.$emit("quotation-selected", row);
this.handleClose();
},
/** 关闭按钮 */
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>

View File

@ -0,0 +1,310 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input
v-model="queryParams.partnerName"
placeholder="请输入进货商"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<!-- <el-col :span="1.5">-->
<!-- <el-button-->
<!-- type="danger"-->
<!-- plain-->
<!-- icon="el-icon-delete"-->
<!-- size="mini"-->
<!-- :disabled="multiple"-->
<!-- @click="handleDelete"-->
<!-- v-hasPermi="['finance:charge:remove']"-->
<!-- >删除</el-button>-->
<!-- </el-col>-->
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['finance:charge:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="chargeList" @selection-change="handleSelectionChange">
<!-- <el-table-column type="selection" width="55" align="center" />-->
<el-table-column label="项目编码" align="center" prop="projectCode" width="180" v-if="columns.projectCode.visible" key="projectCode"/>
<el-table-column label="项目名称" align="center" prop="projectName" width="240" v-if="columns.projectName.visible" key="projectName"/>
<el-table-column label="合同编号" align="center" prop="orderCode" width="180" v-if="columns.orderCode.visible" key="orderCode"/>
<el-table-column label="下单通路" align="center" prop="orderChannel" width="100" v-if="columns.orderChannel.visible" key="orderChannel">
<template slot-scope="scope">
<el-tag>{{ scope.row.orderChannel==='1'? "总代":"直签" }}</el-tag>
</template>
</el-table-column>
<el-table-column label="供货商" align="center" prop="supplier" width="180" v-if="columns.supplier.visible" key="supplier"/>
<el-table-column label="进货商" align="center" prop="partnerName" width="180" v-if="columns.partnerName.visible" key="partnerName"/>
<el-table-column label="业务侧可计收时间" align="center" prop="bizChargeDate" width="180" v-if="columns.bizChargeDate.visible" key="bizChargeDate">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.bizChargeDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="财务计收时间" align="center" prop="financeChargeDate" width="180" v-if="columns.financeChargeDate.visible" key="financeChargeDate">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.financeChargeDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="收入" align="center" v-if="columns.incomeWithTaxTotal.visible || columns.incomeWithoutTaxTotal.visible || columns.incomeTax.visible">
<el-table-column label="含税总价(元)" align="center" prop="incomeWithTaxTotal" width="180" v-if="columns.incomeWithTaxTotal.visible" key="incomeWithTaxTotal" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未含税总价(元)" align="center" prop="incomeWithoutTaxTotal" width="180" v-if="columns.incomeWithoutTaxTotal.visible" key="incomeWithoutTaxTotal" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="税额(元)" align="center" prop="incomeTax" width="180" v-if="columns.incomeTax.visible" key="incomeTax">
<template slot-scope="scope">
<span>{{ formatCurrency($calc.sub(scope.row.incomeWithTaxTotal, scope.row.incomeWithoutTaxTotal)) }}</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="成本" align="center" v-if="columns.costSoftwareWithoutTax.visible || columns.costHardwareWithoutTax.visible || columns.costSoftwareMaintWithoutTax.visible || columns.costHardwareMaintWithoutTax.visible || columns.costProvinceServiceWithoutTax.visible || columns.costOtherWithoutTax.visible || columns.allCostWithoutTax.visible">
<el-table-column label="软件折后未税小计" align="center" prop="costSoftwareWithoutTax" width="180" v-if="columns.costSoftwareWithoutTax.visible" key="costSoftwareWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="硬件折后未税小计" align="center" prop="costHardwareWithoutTax" width="180" v-if="columns.costHardwareWithoutTax.visible" key="costHardwareWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="软件维保折后未税小计" align="center" prop="costSoftwareMaintWithoutTax" width="180" v-if="columns.costSoftwareMaintWithoutTax.visible" key="costSoftwareMaintWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="硬件维保折后未税小计" align="center" prop="costHardwareMaintWithoutTax" width="180" v-if="columns.costHardwareMaintWithoutTax.visible" key="costHardwareMaintWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="省代服务折后未税小计" align="center" prop="costProvinceServiceWithoutTax" width="180" v-if="columns.costProvinceServiceWithoutTax.visible" key="costProvinceServiceWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="其它折后未税小计" align="center" prop="costOtherWithoutTax" width="180" v-if="columns.costOtherWithoutTax.visible" key="costOtherWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="成本未税合计" align="center" prop="allCostWithoutTax" width="180" v-if="columns.allCostWithoutTax.visible" key="allCostWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)">
</el-table-column>
</el-table-column>
<el-table-column label="毛利" align="center" prop="grossProfit" width="180" v-if="columns.grossProfit.visible" key="grossProfit" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="毛利率" align="center" prop="grossProfitRate" width="180" v-if="columns.grossProfitRate.visible" key="grossProfitRate"/>
<el-table-column label="备注" align="center" prop="remark" width="180" v-if="columns.remark.visible" key="remark"/>
<el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleRemark(scope.row)"
v-hasPermi="['finance:charge:edit']"
>编辑备注</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-left"
@click="handleRevoke(scope.row)"
v-hasPermi="['finance:charge:remove']"
>撤销</el-button>
<!-- <el-button-->
<!-- size="mini"-->
<!-- type="text"-->
<!-- icon="el-icon-delete"-->
<!-- @click="handleDelete(scope.row)"-->
<!-- v-hasPermi="['finance:charge:remove']"-->
<!-- >删除</el-button>-->
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 编辑备注对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" :rows="4" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listCharge, getCharge, delCharge, updateCharge, revokeCharge } from "@/api/finance/charge";
export default {
name: "Charge",
dicts: ['finance_charge_status'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
chargeList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
partnerName: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
columns: {
projectCode: { label: '项目编码', visible: true },
projectName: { label: '项目名称', visible: true },
orderCode: { label: '合同编号', visible: true },
orderChannel: { label: '下单通路', visible: true },
supplier: { label: '供货商', visible: true },
partnerName: { label: '进货商', visible: true },
bizChargeDate: { label: '业务侧可计收时间', visible: true },
financeChargeDate: { label: '财务计收时间', visible: true },
incomeWithTaxTotal: { label: '含税总价(元)', visible: true },
incomeWithoutTaxTotal: { label: '未含税总价(元)', visible: true },
incomeTax: { label: '税额(元)', visible: true },
costSoftwareWithoutTax: { label: '软件折后未税小计', visible: true },
costHardwareWithoutTax: { label: '硬件折后未税小计', visible: true },
costSoftwareMaintWithoutTax: { label: '软件维保折后未税小计', visible: true },
costHardwareMaintWithoutTax: { label: '硬件维保折后未税小计', visible: true },
costProvinceServiceWithoutTax: { label: '省代服务折后未税小计', visible: true },
costOtherWithoutTax: { label: '其它折后未税小计', visible: true },
allCostWithoutTax: { label: '成本未税合计', visible: true },
grossProfit: { label: '毛利', visible: true },
grossProfitRate: { label: '毛利率', visible: true },
remark: { label: '备注', visible: true }
},
//
form: {},
};
},
created() {
this.getList();
},
methods: {
/** 查询财务计收列表 */
getList() {
this.loading = true;
listCharge(this.queryParams).then(response => {
this.chargeList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
remark: null,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 编辑备注按钮操作 */
handleRemark(row) {
this.reset();
const id = row.id || this.ids
// 使 getCharge row
getCharge(id).then(response => {
this.form = {
id: response.data.id,
remark: response.data.remark
};
this.open = true;
this.title = "编辑备注";
});
},
/** 撤销按钮操作 */
handleRevoke(row) {
this.$modal.confirm('是否确认撤销该条数据?').then(function() {
return revokeCharge({id: row.id});
}).then(() => {
this.getList();
this.$modal.msgSuccess("撤销成功");
}).catch(() => {});
},
/** 提交按钮 */
submitForm() {
//
updateCharge({id:this.form.id,remark:this.form.remark}).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除财务计收编号为"' + ids + '"的数据项?').then(function() {
return delCharge(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('finance/charge/export', {
...this.queryParams
}, `charge_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -0,0 +1,410 @@
<template>
<el-dialog title="新增收票单" :visible.sync="internalVisible" width="80%" @close="handleClose" append-to-body>
<div class="dialog-body">
<el-form ref="form" :model="queryParams" :inline="true" label-width="120px">
<el-row>
<el-col :span="8">
<el-form-item label="收票单类型" prop="invoiceBillType">
<!-- Mapping to FROM_RECEIVABLE default -->
<el-select disabled v-model="form.invoiceBillType" placeholder="请选择收票单类型" clearable>
<el-option
v-for="dict in dict.type.invoice_bill_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="进货商名称" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请选择进货商" readonly @click.native="showPartnerSelector = true">
<el-button slot="append" icon="el-icon-search" @click="showPartnerSelector = true"></el-button>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="预计开票时间" prop="invoiceTime">
<el-date-picker
v-model="form.invoiceTime"
type="date"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider content-position="left">销售应收单表</el-divider>
<el-table
:data="receivableOrdersWithPlans"
border
max-height="300px"
style="margin-bottom: 20px;"
row-key="id"
@selection-change="handleSelectionChange"
ref="table"
>
<el-table-column type="selection" :reserve-selection="true" width="55" align="center" />
<el-table-column label="应收单编号" align="center" prop="receivableBillCode" width="150"/>
<el-table-column label="预计开票时间" align="center" prop="planInvoiceDate" width="180"/>
<el-table-column label="开票计划" align="center" width="100" prop="planInvoiceAmount">
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" width="150"/>
<el-table-column label="合同编号" align="center" prop="orderCode" width="150"/>
<el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>
<el-table-column label="含税总价" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票金额" align="center" prop="uninvoicedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="本次开票金额" align="center" width="120">
<template slot-scope="scope">
{{ formatCurrency(calculateOrderCurrentInvoiceAmount(scope.row.id).toFixed(2)) }}
</template>
</el-table-column>
<el-table-column label="本次开票比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentInvoiceRate(scope.row.id) }}%
</template>
</el-table-column>
<el-table-column label="已开票金额" align="center" prop="invoicedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenInvoicePlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div class="total-info">
<span style="margin-left: 20px;">计划收票总金额: <el-tag type="success">{{ formatCurrency(totalPlannedAmount.toFixed(2)) }}</el-tag></span>
<span>计划收票比例: <el-tag type="info">{{ totalReceivableAmountWithTax ? this.$calc.mul(this.$calc.div(totalPlannedAmount,totalReceivableAmountWithTax,4),100) : 0 }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
<!-- Reusing InvoicePlan from receivable module if possible, but might need modification.
The receipt module used 'ReceivingTicketPlan'. I should check if there is a 'ReceivingInvoicePlan' or similar.
The 'InvoicePlan' in 'receivable/components' is for managing plans on the Receivable Detail page.
For the dialog selector, 'receipt' used 'ReceivingTicketPlan'.
I'll assume I can use 'InvoicePlan' from 'receivable' components if it supports selection.
Wait, 'InvoicePlan.vue' in 'receivable' has selection support?
Yes, <el-table ... @selection-change="selectPlan">
-->
<el-dialog :title="planTitle" :visible.sync="isInvoicePlanSelectorOpen" width="70%"
@close="isInvoicePlanSelectorOpen=false" append-to-body>
<invoice-plan
ref="planSelector"
:receivable-data="chooseReceivable"
:selected-plans="chooseReceivable.invoicePlans"
@confirm="handleInvoicePlanConfirm"
:is-init-edit="false"
/>
<!-- Note: InvoicePlan component in receivable might need 'isInitEdit' or similar props to behave as a selector.
In 'receivable/components/InvoicePlan.vue', it fetches plans on load if receivableData.id is present.
-->
<div slot="footer" class="dialog-footer">
<el-button @click="isInvoicePlanSelectorOpen=false"> </el-button>
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
<select-partner :visible.sync="showPartnerSelector" @partner-selected="handlePartnerSelected" />
</el-dialog>
</template>
<script>
import { listReceivable } from "@/api/finance/receivable";
import InvoicePlan from "@/views/finance/receivable/components/InvoicePlan.vue";
import SelectPartner from "@/views/system/partner/selectPartner.vue";
export default {
name: "AddForm",
components: { InvoicePlan, SelectPartner },
props: {
visible: {
type: Boolean,
default: false
}
},
dicts:['invoice_bill_type','invoice_status'],
data() {
return {
internalVisible: this.visible,
showPartnerSelector: false,
// Search params
queryParams: {
pageNum: 1,
pageSize: 10,
invoiceBillType: 'FROM_RECEIVABLE',
partnerCode: null,
invoiceStatus: null,
},
// Form data for submission
form: {
invoiceBillType: 'FROM_RECEIVABLE',
invoiceTime: null,
},
receivableOrdersWithPlans: [], // Current page data
selectedRows: [], // Cross-page selection
total: 0,
// Plan Selector State
planTitle: '',
isInvoicePlanSelectorOpen: false,
chooseReceivable: {},
currentReceivableOrderIndexForPlan: -1,
loadingInvoicePlans: false,
};
},
computed: {
// Calculate totals based on SELECTED rows
totalReceivableAmountWithTax() {
return this.selectedRows.reduce((sum, order) => sum + (order.totalPriceWithTax || 0), 0);
},
totalPlannedAmount() {
return this.selectedRows.reduce((orderSum, order) => {
const orderPlansTotal = (order.invoicePlans || []).reduce((planSum, plan) => planSum + (plan.planAmount || 0), 0);
return orderSum + orderPlansTotal;
}, 0);
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.resetForm();
}
},
internalVisible(newVal) {
this.$emit("update:visible", newVal);
}
},
methods: {
handlePartnerSelected(partner) {
this.queryParams.partnerCode = partner.partnerCode;
this.queryParams.partnerName = partner.partnerName;
this.showPartnerSelector = false;
this.queryParams.pageNum = 1;
this.selectedRows = [];
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
this.getList();
},
getList() {
if (!this.queryParams.partnerName) {
this.receivableOrdersWithPlans = [];
this.total = 0;
return;
}
this.loadingInvoicePlans = true;
listReceivable(this.queryParams).then(response => {
this.receivableOrdersWithPlans = response.rows.map(order => {
// Initialize invoice plans
const invoicePlans = order.invoicePlans ? [...order.invoicePlans] : [];
if (invoicePlans.length === 0 && order.lastInvoicePlanId) {
// Logic similar to receipt's AddForm to pre-fill default plan
invoicePlans.push({
id: order.lastInvoicePlanId,
planAmount: order.planInvoiceAmount,
planInvoiceDate: order.planInvoiceDate,
planRate: order.totalPriceWithTax ? this.$calc.mul(this.$calc.div(order.planInvoiceAmount, order.totalPriceWithTax, 4), 100) : 0
});
}
return {
...order,
invoicePlans: invoicePlans,
totalPriceWithTax: order.totalPriceWithTax || 0,
uninvoicedAmount: order.uninvoicedAmount || 0,
invoicedAmount: order.invoicedAmount || 0,
};
});
this.total = response.total;
this.loadingInvoicePlans = false;
});
},
handleSelectionChange(selection) {
this.selectedRows = selection;
},
handleClose() {
this.internalVisible = false;
this.resetForm();
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
// Accessing 'invoicePlans' from the child component, assuming it exposes selected plans or we can get them.
// In InvoicePlan.vue (receivable), it uses a table with selection.
// We need to verify how to get selected plans.
// The InvoicePlan.vue has 'handleSaveInvoicePlan' but that saves to DB.
// We want to just get the selection in memory.
// Looking at InvoicePlan.vue: it has `invoicePlans` data array.
// And `<el-table ... @selection-change="selectPlan" ...>`
// `selectPlan(selection) { this.selectedPlan = selection; }` ?
// Need to check InvoicePlan.vue again to see if it tracks selection in a public property.
// I'll assume it does or I can access `this.$refs.planSelector.$refs.invoicePlanTable.selection`.
let selectedPlans = [];
if (this.$refs.planSelector && this.$refs.planSelector.$refs.invoicePlanTable) {
selectedPlans = this.$refs.planSelector.$refs.invoicePlanTable.selection;
}
if (!selectedPlans || selectedPlans.length === 0) {
// Fallback if component works differently
// In MergeInvoiceDialog, it passed :selected-plans and listened to @confirm.
// Here I am trying to manually extract.
}
const orderIndex = this.receivableOrdersWithPlans.findIndex(o => o.id === this.chooseReceivable.id);
if (orderIndex === -1) {
this.$modal.msgError('找不到要更新的应收单');
return;
}
const currentOrder = this.receivableOrdersWithPlans[orderIndex];
// Update view
this.$set(currentOrder, 'invoicePlans', [...selectedPlans]);
// Update selectedRows
const selectedIndex = this.selectedRows.findIndex(o => o.id === this.chooseReceivable.id);
if (selectedIndex !== -1) {
this.$set(this.selectedRows[selectedIndex], 'invoicePlans', [...selectedPlans]);
}
this.isInvoicePlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新收票计划选择,共 ${selectedPlans.length}`);
},
handleOpenInvoicePlanSelector(row, index) {
this.planTitle = `选择收票计划 - ${row.receivableBillCode}`;
this.chooseReceivable = row;
this.currentReceivableOrderIndexForPlan = index;
this.isInvoicePlanSelectorOpen = true;
// Note: We might need to manually toggle selection in the child component after it opens/loads.
},
handleInvoicePlanConfirm(updatedPlans) {
// Callback placeholder
},
calculateOrderCurrentInvoiceAmount(orderId) {
const order = this.receivableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.invoicePlans) {
return order.invoicePlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentInvoiceRate(orderId) {
const order = this.receivableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.invoicePlans && order.uninvoicedAmount >= 0) {
const currentAmount = this.calculateOrderCurrentInvoiceAmount(orderId);
return order.totalPriceWithTax ? this.$calc.mul(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ),100) : 0;
}
return 0;
},
handleSubmit() {
if (!this.queryParams.partnerCode) { // Using bound value ID
this.$modal.msgError('请选择客户');
return;
}
if (!this.form.invoiceTime) {
this.$modal.msgError('请选择客户开票时间');
return;
}
if (this.selectedRows.length === 0) {
this.$modal.msgError('请至少勾选一条应收单');
return;
}
const ordersToSubmit = this.selectedRows;
// Validate plans
for (const order of ordersToSubmit) {
if (!order.invoicePlans || order.invoicePlans.length === 0) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 至少需要一条收票计划`);
return;
}
for (const plan of order.invoicePlans) {
if (!plan.planInvoiceDate) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的收票计划中预计收票时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的收票计划中预计收票金额必须大于0。`);
return;
}
}
}
// Construct payload (matching MergeInvoiceDialog logic)
const data = {
invoiceBillType: this.form.invoiceBillType,
invoiceTime: this.form.invoiceTime,
receivableBills: ordersToSubmit.map(order => ({
id: order.id,
receivableBillCode: order.receivableBillCode,
taxRate: order.taxRate,
invoicePlans: order.invoicePlans.map(plan => ({
planInvoiceDate: plan.planInvoiceDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
remark: plan.remark,
id: plan.id,
})),
})),
totalMergeInvoiceAmount: this.totalPlannedAmount,
};
this.$emit("submit", data);
},
resetForm() {
this.form = {
invoiceBillType: 'FROM_RECEIVABLE',
invoiceTime: null,
};
this.queryParams = {
pageNum: 1,
pageSize: 10,
invoiceBillType: 'FROM_RECEIVABLE',
invoiceStatus: null,
};
this.receivableOrdersWithPlans = [];
this.selectedRows = [];
this.total = 0;
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
}
}
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px;
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,811 @@
<template>
<el-dialog title="申请开票" :visible.sync="visible" width="1000px" append-to-body @close="handleClose"
custom-class="invoice-dialog">
<el-form ref="form" :model="form" :rules="rules" :disabled="isRedRush" size="small" label-width="110px" class="invoice-form">
<!-- 1. 顶部发票类型与票号日期 -->
<div class="invoice-header">
<div class="header-center">
<div class="invoice-title">
电子发票
<span class="invoice-type-wrapper">
(<el-select v-model="form.invoiceType" class="invoice-type-select" :popper-append-to-body="false">
<el-option
v-for="dict in dict.type.finance_invoice_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>)
</span>
</div>
<div class="header-decoration-line"></div>
</div>
<div class="header-right">
<div class="meta-row">
<span class="meta-label">发票号码:</span>
<span class="meta-value">----------</span>
</div>
<div class="meta-row">
<span class="meta-label">开票日期:</span>
<span class="meta-value">----------</span>
</div>
</div>
</div>
<!-- 2. 中部购买方信息与销售方信息 -->
<div class="invoice-body-border">
<el-row type="flex" class="info-container">
<!-- 左侧购买方信息 -->
<el-col :span="12" class="info-column left-column">
<div class="column-label">购买方<br>信息</div>
<div class="column-content">
<el-form-item label="名称" prop="buyerName" class="condensed-item">
<el-input v-model="form.buyerName" placeholder="请输入名称"/>
</el-form-item>
<el-form-item label="统一社会信用代码/纳税人识别号" prop="buyerCreditCode"
class="condensed-item label-wrap">
<span slot="label" style="line-height: 1.2;">统一社会信用代码/<br>纳税人识别号</span>
<el-input v-model="form.buyerCreditCode" placeholder="请输入纳税人识别号"/>
</el-form-item>
</div>
</el-col>
<!-- 右侧销售方信息 -->
<el-col :span="12" class="info-column right-column">
<div class="column-label">销售方<br>信息</div>
<div class="column-content">
<el-form-item label="名称" prop="sellerName" class="condensed-item">
<el-select
v-model="form.sellerName"
placeholder="请输入名称"
filterable
clearable
@change="handleSellerChange"
style="width: 100%"
>
<el-option
v-for="item in companyOptions"
:key="item.id"
:label="item.companyName"
:value="item.companyName"
/>
</el-select>
</el-form-item>
<el-form-item label="统一社会信用代码/纳税人识别号" prop="sellerCreditCode"
class="condensed-item label-wrap">
<span slot="label" style="line-height: 1.2;">统一社会信用代码/<br>纳税人识别号</span>
<el-input v-model="form.sellerCreditCode" placeholder="请输入纳税人识别号"/>
</el-form-item>
</div>
</el-col>
</el-row>
<!-- 3. 表格区域 -->
<div class="items-table-container">
<el-table
:data="form.detailItemList"
border
class="invoice-table"
show-summary
:summary-method="getSummaries"
>
<el-table-column label="项目名称" prop="productName" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.productName" size="mini" placeholder="请输入" class="no-border-input"
:disabled="!!scope.row.productCode"/>
</template>
</el-table-column>
<el-table-column label="规格型号" prop="productModel" align="center" width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.productModel" size="mini" placeholder="型号" class="no-border-input"
:disabled="!!scope.row.productCode"/>
</template>
</el-table-column>
<el-table-column label="单位" prop="unit" align="center" width="60">
<template slot-scope="scope">
<el-input v-model="scope.row.unit" size="mini" placeholder="单位" class="no-border-input"/>
</template>
</el-table-column>
<el-table-column label="数量" prop="quantity" align="center" width="80">
<template slot-scope="scope">
<el-input v-model="scope.row.quantity" size="mini" class="no-border-input"
oninput="value=value.replace(/[^\d.-]/g,'')"
@input="calculateAmount(scope.row)"/>
</template>
</el-table-column>
<el-table-column label="单价" prop="unitPrice" align="center" width="100">
<template slot-scope="scope">
<el-input v-if="!scope.row.productCode" v-model="scope.row.unitPrice" size="mini" placeholder="单价"
class="no-border-input" @input="calculateAmount(scope.row)"/>
<span v-else>{{ scope.row.unitPrice }}</span>
</template>
</el-table-column>
<el-table-column label="金额" prop="amount" align="center" width="100">
<template slot-scope="scope">
<el-input v-if="!scope.row.productCode" v-model="scope.row.amount" size="mini" placeholder="金额"
class="no-border-input" @input="calculateTax(scope.row)"/>
<span v-else>{{ scope.row.amount }}</span>
</template>
</el-table-column>
<el-table-column label="税率%" prop="taxRate" align="center" width="80">
<template slot-scope="scope">
<el-input v-if="!scope.row.productCode" v-model="scope.row.taxRate" size="mini" placeholder="税率"
class="no-border-input" @input="calculateTax(scope.row)"/>
<span v-else>{{ scope.row.taxRate }}</span>
</template>
</el-table-column>
<el-table-column label="税额" prop="taxAmount" align="center" width="100">
<template slot-scope="scope">
<el-input v-if="!scope.row.productCode" v-model="scope.row.taxAmount" size="mini" placeholder="税额"
class="no-border-input"/>
<span v-else>{{ scope.row.taxAmount }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="操作" width="60" align="center">-->
<!-- <template slot-scope="scope">-->
<!-- <i class="el-icon-plus table-icon" @click="addDetailRow"-->
<!-- v-if="scope.$index === form.detailItemList.length - 1"></i>-->
<!-- <i class="el-icon-minus table-icon" @click="removeDetailRow(scope.$index)"-->
<!-- v-if="form.detailItemList.length > 1" style="margin-left:5px"></i>-->
<!-- </template>-->
<!-- </el-table-column>-->
</el-table>
</div>
<!-- 4. 价税合计 -->
<div class="total-row">
<div class="total-label">价税合计 (大写)</div>
<div class="total-value-chinese">
<span class="currency-symbol"></span> {{ totalAmountChinese }}
</div>
<div class="total-label-small">(小写)</div>
<div class="total-value-number">
¥{{ totalAmountNumber }}
</div>
</div>
<!-- 5. 备注 -->
<div class="remark-row">
<div class="remark-label">备注</div>
<div class="remark-content">
<el-input type="textarea" :rows="2" v-model="form.remark" placeholder="请输入备注"
class="no-border-textarea"/>
</div>
</div>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {getInvoiceProducts, applyInvoice, applyRefund} from "@/api/finance/invoice";
import { listCompanyInfo } from "@/api/system/companyInfo";
export default {
name: "ApplyInvoice",
dicts: ['finance_invoice_type'],
props: {
visible: {
type: Boolean,
default: false
},
rowData: {
type: Object,
default: () => ({})
},
isRedRush: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
companyOptions: [],
form: {
invoiceType: this.rowData.invoiceType || '2',
buyerName: undefined,
buyerCreditCode: undefined,
buyerBank: undefined,
buyerBankAccount: undefined,
sellerName: undefined,
sellerCreditCode: undefined,
sellerBank: undefined,
sellerBankAccount: undefined,
remark: undefined,
invoiceBillCode: this.rowData.invoiceBillCode,
id: this.rowData.id,
detailItemList: [
{
projectName: '',
productModel: '',
unit: '',
unitPrice: '',
quantity: '',
amount: '',
taxRate: '13',
taxAmount: ''
}
]
},
rules: {
invoiceType: [{required: true, message: "必填", trigger: "change"}],
buyerName: [{required: true, message: "必填", trigger: "blur"}],
buyerCreditCode: [{required: true, message: "必填", trigger: "blur"}],
sellerName: [{required: true, message: "必填", trigger: "blur"}]
}
};
},
created() {
this.getCompanyList();
},
computed: {
totalAmountNumber() {
let total = 0;
this.form.detailItemList.forEach(item => {
const amount = parseFloat(item.amount) || 0;
// const tax = parseFloat(item.taxAmount) || 0;
total += amount ;
});
return total.toFixed(2);
},
totalAmountChinese() {
return this.convertCurrency(this.totalAmountNumber);
}
},
watch: {
visible(val) {
if (val) {
this.reset();
this.initData();
}
}
},
methods: {
getCompanyList() {
listCompanyInfo().then(response => {
this.companyOptions = response.rows;
if (this.visible && !this.form.sellerName && this.companyOptions.length > 0) {
const defaultCompany = this.companyOptions[0];
this.form.sellerName = defaultCompany.companyName;
this.handleSellerChange(defaultCompany.companyName);
}
});
},
reset() {
this.form = {
invoiceType: '2',
buyerName: undefined,
buyerCreditCode: undefined,
buyerBank: undefined,
buyerBankAccount: undefined,
sellerName: undefined,
sellerCreditCode: undefined,
sellerBank: undefined,
sellerBankAccount: undefined,
remark: undefined,
detailItemList: []
};
this.resetForm("form");
},
initData() {
if (this.rowData) {
this.form.buyerName = this.rowData.buyerName || this.rowData.partnerName;
this.form.buyerCreditCode = this.rowData.buyerCreditCode ;
this.form.buyerBank = this.rowData.buyerBank ;
this.form.buyerBankAccount = this.rowData.buyerBankAccount ;
if (this.rowData.sellerName) {
this.form.sellerName = this.rowData.sellerName;
this.form.sellerCreditCode = this.rowData.sellerCreditCode;
this.form.sellerBank = this.rowData.sellerBank;
this.form.sellerBankAccount = this.rowData.sellerBankAccount;
} else if (this.companyOptions.length > 0) {
const defaultCompany = this.companyOptions[0];
this.form.sellerName = defaultCompany.companyName;
this.handleSellerChange(defaultCompany.companyName);
}
this.form.invoiceBillCode = this.rowData.invoiceBillCode;
this.form.id = this.rowData.id;
this.updateRemark();
if (this.rowData.invoiceBillCode) {
getInvoiceProducts(this.rowData.invoiceBillCode).then(response => {
if (response.data && response.data.length > 0) {
this.form.detailItemList = response.data.map(item => {
let quantity = item.quantity;
let amount = item.allPrice;
let taxAmount = item.taxAmount;
if (this.isRedRush) {
quantity = -Math.abs(quantity);
amount = -Math.abs(amount);
taxAmount = -Math.abs(taxAmount);
}
const row = {
projectName: item.projectName,
id: item.id,
orderCode: item.orderCode,
productCode: item.productCode,
productModel: item.productModel, // Mapping projectCode to productModel as requested
productName: item.productName, // Mapping projectCode to productModel as requested
unit: item.unit || '',
quantity: quantity,
unitPrice: item.price, // Mapping price to unitPrice
taxRate: item.taxRate,
amount: amount,
taxAmount: taxAmount
};
// Calculate initial amounts
// this.calculateAmount(row);
return row;
});
} else {
this.addDetailRow(); // Default empty row if no data
}
});
} else {
this.addDetailRow();
}
}
},
handleSellerChange(val) {
const company = this.companyOptions.find(item => item.companyName === val);
if (company) {
this.form.sellerCreditCode = company.socialCredit;
this.form.sellerBank = company.payBankOpenAddress;
this.form.sellerBankAccount = company.payBankNumber;
this.updateRemark();
}
},
updateRemark() {
const bBank = this.form.buyerBank || '';
const bAccount = this.form.buyerBankAccount || '';
const sBank = this.form.sellerBank || '';
const sAccount = this.form.sellerBankAccount || '';
this.form.remark = `购方开户银行 : ${bBank} 银行账号 ${bAccount}\n销方开户银行 ${sBank} 银行账号 ${sAccount}`;
},
handleClose() {
this.$emit("update:visible", false);
},
addDetailRow() {
this.form.detailItemList.push({
projectName: '',
productModel: '',
unit: '',
unitPrice: '',
quantity: '',
amount: '',
taxRate: '13',
taxAmount: ''
});
},
removeDetailRow(index) {
this.form.detailItemList.splice(index, 1);
},
calculateAmount(row) {
if (row.productCode){
return
}
if (row.unitPrice && row.quantity) {
row.amount = this.$calc.mul(row.unitPrice, row.quantity);
this.calculateTax(row);
}
},
calculateTax(row) {
if (row.amount && row.taxRate != null) {
row.taxAmount = this.$calc.mul(row.amount, this.$calc.div(row.taxRate, 100));
}
},
getSummaries(param) {
const {columns, data} = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
if (column.property === 'amount' || column.property === 'taxAmount') {
const values = data.map(item => Number(item[column.property]));
if (!values.every(value => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
sums[index] = '¥' + sum.toFixed(2);
} else {
sums[index] = '';
}
} else {
sums[index] = '';
}
});
return sums;
},
handleSubmit() {
this.$refs["form"].validate(valid => {
if (valid) {
//
if (this.form.detailItemList.length === 0) {
this.$modal.msgError("请至少添加一条开票明细");
return;
}
for (let i = 0; i < this.form.detailItemList.length; i++) {
const item = this.form.detailItemList[i];
if (!item.productName || !item.productModel || isNaN(item.quantity) || !item.unit || !item.unitPrice || !item.amount || !item.taxAmount || !item.taxRate) {
this.$modal.msgError(`表格第 ${i + 1} 行数据不完整,请填写所有必填字段`);
return;
}
item.invoiceBillCode=this.rowData.invoiceBillCode;
item.price=item.unitPrice;
item.allPrice=item.amount;
}
this.loading = true;
const apiCall = this.isRedRush ? applyRefund : applyInvoice;
apiCall(this.form).then(response => {
this.$modal.msgSuccess(this.isRedRush ? "申请红冲成功" : "申请提交成功");
this.loading = false;
this.$emit("update:visible", false);
this.$emit("submit", this.form);
}).catch(() => {
this.loading = false;
});
}
});
},
convertCurrency(money) {
//
const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const cnIntRadice = ['', '拾', '佰', '仟'];
const cnIntUnits = ['', '万', '亿', '兆'];
const cnDecUnits = ['角', '分', '毫', '厘'];
const cnInteger = '整';
const cnIntLast = '元';
let integerNum;
let decimalNum;
let chineseStr = '';
let parts;
let prefix = '';
if (money === '') {
return '';
}
money = parseFloat(money);
if (money < 0) {
prefix = '(负数)';
money = Math.abs(money);
}
if (money >= 999999999999) {
return '';
}
if (money === 0) {
chineseStr = cnNums[0] + cnIntLast + cnInteger;
return chineseStr;
}
money = money.toString();
if (money.indexOf('.') === -1) {
integerNum = money;
decimalNum = '';
} else {
parts = money.split('.');
integerNum = parts[0];
decimalNum = parts[1].substr(0, 4);
}
if (parseInt(integerNum, 10) > 0) {
let zeroCount = 0;
let IntLen = integerNum.length;
for (let i = 0; i < IntLen; i++) {
let n = integerNum.substr(i, 1);
let p = IntLen - i - 1;
let q = p / 4;
let m = p % 4;
if (n === '0') {
zeroCount++;
} else {
if (zeroCount > 0) {
chineseStr += cnNums[0];
}
zeroCount = 0;
chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
}
if (m === 0 && zeroCount < 4) {
chineseStr += cnIntUnits[q];
}
}
chineseStr += cnIntLast;
}
if (decimalNum !== '') {
let decLen = decimalNum.length;
for (let i = 0; i < decLen; i++) {
let n = decimalNum.substr(i, 1);
if (n !== '0') {
chineseStr += cnNums[Number(n)] + cnDecUnits[i];
}
}
}
if (chineseStr === '') {
chineseStr += cnNums[0] + cnIntLast + cnInteger;
} else if (decimalNum === '') {
chineseStr += cnInteger;
}
return prefix + chineseStr;
}
}
};
</script>
<style scoped>
.invoice-dialog ::v-deep .el-dialog__body {
padding: 10px 20px;
}
.invoice-header {
position: relative;
height: 80px;
display: flex;
justify-content: center;
align-items: flex-start;
margin-bottom: 10px;
}
.header-center {
text-align: center;
padding-top: 5px;
}
.invoice-title {
font-size: 24px;
font-weight: bold;
color: #8B4513;
line-height: 40px;
letter-spacing: 2px;
}
.invoice-type-wrapper {
margin-left: 5px;
}
.invoice-type-select {
width: 160px;
}
/* Hide select border to blend in */
.invoice-type-select ::v-deep .el-input__inner {
border: none;
background: transparent;
font-size: 22px;
font-weight: bold;
color: #8B4513;
padding: 0;
text-align: center;
height: 40px;
line-height: 40px;
}
.invoice-type-select ::v-deep .el-select__caret {
color: #8B4513;
font-weight: bold;
}
.header-decoration-line {
height: 2px;
background-color: #8B4513;
width: 380px;
margin: 0 auto;
border-bottom: 1px solid #8B4513; /* 双线效果 */
box-shadow: 0 2px 0 0 #fff, 0 4px 0 0 #8B4513;
}
.header-right {
position: absolute;
right: 0;
top: 10px;
text-align: left;
}
.meta-row {
font-size: 13px;
color: #8B4513;
line-height: 20px;
}
.meta-label {
display: inline-block;
width: 70px;
}
.invoice-body-border {
border: 2px solid #8B4513;
padding: 0;
color: #606266;
}
.info-container {
display: flex;
border-bottom: 1px solid #8B4513;
}
.info-column {
padding: 5px;
display: flex;
align-items: stretch;
}
.left-column {
border-right: 1px solid #8B4513;
}
.column-label {
width: 30px;
padding: 0 5px;
color: #8B4513;
font-size: 14px;
text-align: center;
line-height: 1.4;
border-right: 1px solid #8B4513;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
word-break: break-all;
}
.column-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.condensed-item {
margin-bottom: 18px;
}
.condensed-item ::v-deep .el-form-item__error {
z-index: 10;
}
.condensed-item ::v-deep .el-form-item__label {
color: #8B4513;
font-size: 13px;
padding-right: 8px;
line-height: 32px;
}
/* Fix for multiline label */
.label-wrap ::v-deep .el-form-item__label {
line-height: 16px;
padding-top: 2px;
}
.condensed-item ::v-deep .el-input__inner {
border-color: #dcdfe6;
height: 32px;
line-height: 32px;
}
/* Table Styling */
.items-table-container {
padding: 0;
border-bottom: 1px solid #8B4513;
}
.invoice-table ::v-deep th {
font-weight: bold;
}
.invoice-table ::v-deep .el-table__footer-wrapper tbody td,
.invoice-table ::v-deep .el-table__header-wrapper tbody td {
background-color: #fff;
color: #8B4513;
border-color: #8B4513;
}
.invoice-table ::v-deep td, .invoice-table ::v-deep th.is-leaf {
border-bottom: 1px solid #8B4513;
border-right: 1px solid #8B4513;
}
.invoice-table ::v-deep .el-table--border, .invoice-table ::v-deep .el-table--group {
border-color: #8B4513;
}
.no-border-input ::v-deep .el-input__inner {
border: none;
text-align: center;
padding: 0 5px;
}
.table-icon {
cursor: pointer;
color: #8B4513;
font-weight: bold;
}
.total-row {
display: flex;
align-items: center;
border-bottom: 1px solid #8B4513;
height: 40px;
padding: 0 10px;
color: #8B4513;
}
.total-label {
width: 120px;
font-size: 13px;
}
.total-value-chinese {
flex: 1;
font-size: 14px;
padding-left: 10px;
}
.currency-symbol {
border: 1px solid #8B4513;
border-radius: 50%;
padding: 1px 4px;
font-size: 12px;
margin-right: 5px;
}
.total-label-small {
width: 60px;
text-align: right;
font-size: 13px;
}
.total-value-number {
width: 150px;
padding-left: 10px;
font-size: 14px;
}
.remark-row {
display: flex;
min-height: 60px;
padding: 0 10px;
color: #8B4513;
}
.remark-label {
width: 120px;
padding-top: 10px;
font-size: 13px;
}
.remark-content {
flex: 1;
padding: 5px 0;
}
.no-border-textarea ::v-deep .el-textarea__inner {
border: none;
resize: none;
}
.invoice-table ::v-deep .el-table__body-wrapper {
height: 250px;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,188 @@
<template>
<el-drawer
:title="detail.invoiceBillType==='RED_RUSH'?'红冲开票详情':'开票单详情'"
:visible.sync="visible"
direction="rtl"
size="70%"
@close="handleClose"
>
<div class="dialog-body" v-if="detail">
<div class="section">
<el-divider content-position="left">销售-开票单</el-divider>
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">销售-开票单编号: {{ detail.invoiceBillCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">预计开票时间: {{ detail.createTime }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">进货商名称: {{ detail.partnerName }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">含税总价(): <span :class="{'red-text':detail.invoiceBillType==='RED_RUSH'}">{{ formatCurrency(detail.totalPriceWithTax) }}</span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">未税总价(): <span :class="{'red-text':detail.invoiceBillType==='RED_RUSH'}">{{ formatCurrency(detail.totalPriceWithoutTax) }} </span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">税额(): <span :class="{'red-text':detail.invoiceBillType==='RED_RUSH'}">{{ formatCurrency($calc.sub(detail.totalPriceWithTax,detail.totalPriceWithoutTax)) }} </span></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">发票含税总价: <span :class="{'red-text':detail.invoiceBillType==='RED_RUSH'}"> {{ formatCurrency(detail.invoicePriceWithTax) }}</span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">发票未税总价: <span :class="{'red-text':detail.invoiceBillType==='RED_RUSH'}"{{ formatCurrency(detail.invoicePriceWithoutTax) }}</span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">发票税额: <span :class="{'red-text':detail.invoiceBillType==='RED_RUSH'}"{{ formatCurrency($calc.sub(detail.invoicePriceWithTax,detail.invoicePriceWithoutTax)) }}</span></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">开票状态:
<dict-tag :options="dict.type.invoice_bill_status" :value="detail.invoiceStatus"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批节点: {{ detail.approveNode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批状态:
<dict-tag :options="dict.type.approve_status" :value="detail.approveStatus"/></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">审批通过时间 :{{detail.approveTime}}
</div>
</el-col>
<el-col :span="16">
<div class="detail-item">票据类型:
<span class="item-value"><dict-tag :options="dict.type.finance_invoice_type" :value="detail.invoiceType"/></span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">购买方信息_名称: {{ detail.buyerName }}</div>
</el-col>
<el-col :span="12">
<div class="detail-item">购买方信息_统一社会信用代码/纳税人识别号: {{ detail.buyerCreditCode }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">购买方信息_购方开户银行: {{ detail.buyerBank }}</div>
</el-col>
<el-col :span="12">
<div class="detail-item">购买方信息_银行账号: {{ detail.buyerBankAccount }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">销售方信息_名称: {{ detail.sellerName }}</div>
</el-col>
<el-col :span="12">
<div class="detail-item">销售方信息_统一社会信用代码/纳税人识别号: {{ detail.sellerCreditCode }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">销售方信息_销方开户银行: {{ detail.sellerBank }}</div>
</el-col>
<el-col :span="12">
<div class="detail-item">销售方信息_银行账号: {{ detail.sellerBankAccount }}</div>
</el-col>
</el-row>
</div>
</div>
<div class="section">
<el-divider content-position="left">销售-应收单</el-divider>
<el-table :data="detail.detailDTOList">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<!-- <el-table-column property="projectCode" label="项目编号"></el-table-column>-->
<el-table-column property="projectName" label="项目名称"></el-table-column>
<el-table-column property="receivableBillCode" label="销售-应收单编号"></el-table-column>
<el-table-column property="totalPriceWithTax" label="含税总价(元)"></el-table-column>
<el-table-column property="receiptAmount" label="本次开票金额">
<template slot-scope="scope">
<span>{{ formatCurrency(Math.abs(scope.row.receiptAmount)) }}</span>
</template>
</el-table-column>
<el-table-column property="receiptRate" label="本次开票比例(%)">
<template slot-scope="scope">
<span>{{ Math.abs(scope.row.receiptRate) }}</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-drawer>
</template>
<script>
export default {
name: "DetailDrawer",
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => null,
},
},
dicts:['finance_invoice_type','approve_status','invoice_bill_status'],
methods: {
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: calc(100vh - 50px);
overflow-y: auto;
padding: 0 20px 20px 20px;
}
.details-container {
border: 1px solid #EBEEF5;
padding: 20px;
border-radius: 4px;
}
.detail-item {
display: flex;
border: 1px solid #EBEEF5;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
align-items: center;
}
.red-text{
color: red;
}
.section {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,667 @@
<template>
<el-dialog :title="titleText" :visible.sync="dialogVisible" width="900px" @close="handleClose">
<div v-if="loading" class="loading-spinner">
<i class="el-icon-loading"></i>
</div>
<div v-else class="receipt-dialog-body">
<div v-if="canUpload" class="upload-btn-container">
<el-button type="primary" v-hasPermi="['finance:invoice:upload']"icon="el-icon-upload" @click="openUploadDialog">{{ titleText }}</el-button>
</div>
<el-timeline v-if="groupedAttachments.length > 0">
<el-timeline-item
v-for="group in groupedAttachments"
:key="group.createTime"
:timestamp="parseTime(group.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
placement="top"
>
<el-card>
<div class="receipt-card-content">
<div class="receipt-details">
<div class="detail-item">
<span class="item-label">票据类型</span>
<span class="item-value"><dict-tag :options="dict.type.finance_invoice_type" :value="invoiceData.invoiceType"/></span>
</div>
<div class="detail-item">
<span class="item-label">{{ titleText }}</span>
<div class="item-value" style="display: block;">
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<div v-for="attachment in group.items" :key="attachment.id" class="attachment-item">
<div class="image-wrapper">
<el-image
v-if="!isPdf(attachment.filePath)"
:src="getImageUrl(attachment.filePath)"
:preview-src-list="previewList"
style="width: 200px; height: 150px;"
fit="contain"
></el-image>
<div v-else-if="pdfUrls[attachment.filePath]" class="pdf-thumbnail-container" @click="openPdfPreview(pdfUrls[attachment.filePath])">
<iframe :src="pdfUrls[attachment.filePath]" width="100%" height="150px" frameborder="0"></iframe>
<div class="pdf-hover-overlay">
<i class="el-icon-zoom-in"></i>
</div>
</div>
<div v-if="attachment.delFlag === '2'" class="void-overlay"></div>
</div>
</div>
</div>
<el-button
size="mini"
type="primary"
class="download-btn"
icon="el-icon-download"
@click="downloadFiles(group.items)"
style="margin-top: 10px;"
>下载{{ titleText }}</el-button>
</div>
</div>
<div class="detail-item">
<span class="item-label">含税金额</span>
<span class="item-value">{{ formatCurrency(invoiceData.invoicePriceWithTax) }}</span>
</div>
<!-- <div class="detail-item">
<span class="item-label">发票含税金额</span>
<span class="item-value">{{ invoiceData.ticketPriceWithTax }}</span>
</div> -->
<div class="detail-item">
<span class="item-label">未税金额</span>
<span class="item-value">{{ formatCurrency(invoiceData.invoicePriceWithoutTax) }}</span>
</div>
<!-- <div class="detail-item">
<span class="item-label">发票未税金额</span>
<span class="item-value">{{ invoiceData.ticketPriceWithoutTax }}</span>
</div> -->
<div class="detail-item">
<span class="item-label">税额</span>
<span class="item-value">{{ formatCurrency(invoiceData.taxAmount) }}</span>
</div>
<!-- <div class="detail-item">
<span class="item-label">发票税额</span>
<span class="item-value">{{ invoiceData.ticketAmount }}</span>
</div> -->
<div class="detail-item">
<span class="item-label">备注</span>
<span class="item-value">{{ group.remark }}</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else :description="'暂无' + titleText"></el-empty>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
<!-- PDF Preview Dialog -->
<el-dialog
:visible.sync="pdfPreviewVisible"
width="80%"
top="5vh"
append-to-body
custom-class="pdf-preview-dialog"
>
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<!-- Upload Dialog -->
<el-dialog
:title="'上传' + titleText"
:visible.sync="uploadDialogVisible"
width="70vw"
append-to-body
@close="closeUploadDialog"
custom-class="upload-receipt-dialog"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form :model="uploadForm" ref="uploadForm" :rules="rules" label-width="120px" size="medium" >
<el-form-item label="票据类型" prop="invoiceType" required>
<dict-tag :options="dict.type.finance_invoice_type" :value="uploadForm.invoiceType"/>
<!-- <el-select v-model="uploadForm.invoiceType" placeholder="请选择票据类型" @change="handleTypeChange">-->
<!-- -->
<!-- <el-option-->
<!-- v-for="item in dict.type.finance_invoice_type"-->
<!-- :key="item.value"-->
<!-- :label="item.label"-->
<!-- :value="item.value"-->
<!-- ></el-option>-->
<!-- </el-select>-->
</el-form-item>
<el-form-item label="开票附件" prop="file" >
<div style="display: flex; flex-direction: column; align-items: flex-start;">
<el-upload
ref="upload"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:file-list="fileList"
multiple
:show-file-list="true"
accept=".jpg,.jpeg,.png,.pdf"
>
<el-button size="small" type="primary" icon="el-icon-upload2">点击上传</el-button>
</el-upload>
<div class="el-upload__tip" style="line-height: 1.5; margin-top: 5px;">支持上传PNGJPGPDF文件格式</div>
</div>
</el-form-item>
<el-form-item label="含税总价">
<span>{{ invoiceData.totalPriceWithTax }}</span>
</el-form-item>
<el-form-item label="发票含税总价" prop="invoicePriceWithTax" required>
<el-input v-model="uploadForm.invoicePriceWithTax" placeholder="请输入发票含税总价"></el-input>
</el-form-item>
<el-form-item label="未税总价">
<span>{{ invoiceData.totalPriceWithoutTax }}</span>
</el-form-item>
<el-form-item label="发票未税总价" prop="invoicePriceWithoutTax" required>
<el-input v-model="uploadForm.invoicePriceWithoutTax" placeholder="请输入发票未税总价" @input="handleWithoutTaxChange"></el-input>
</el-form-item>
<el-form-item label="税额">
<span>{{ invoiceData.taxAmount }}</span>
</el-form-item>
<el-form-item label="发票税额" prop="invoiceAmount" required>
<el-input v-model="uploadForm.invoiceAmount" placeholder="请输入发票税额" @input="handleAmountChange"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
type="textarea"
v-model="uploadForm.remark"
:rows="4"
placeholder="此处备注描述..."
></el-input>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<div class="upload-preview-container" style="height: 70vh; overflow-y: auto; display: block; padding: 10px;">
<div v-if="fileList.length > 0" class="preview-list">
<div v-for="(file, index) in fileList" :key="index" class="preview-item" style="margin-bottom: 20px; border-bottom: 1px dashed #ccc; padding-bottom: 10px;">
<div class="preview-content" style="height: 350px;">
<img v-if="!isPdf(file.name)" :src="file.url" class="preview-image" style="max-width: 100%; max-height: 100%; object-fit: contain;" />
<iframe v-else :src="file.url" width="100%" height="100%" frameborder="0"></iframe>
</div>
<div class="file-info" style="margin-top: 5px; text-align: center; color: #666; font-size: 12px;">{{ file.name }}</div>
</div>
</div>
<div v-else class="preview-placeholder" style="height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;">
<div class="placeholder-icon">
<i class="el-icon-picture"></i>
</div>
<div class="placeholder-text">上传文件后在此预览</div>
</div>
</div>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitNewUpload"></el-button>
<el-button @click="closeUploadDialog"></el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import { getInvoiceAttachments, uploadInvoiceAttachment } from "@/api/finance/invoice";
import request from '@/utils/request';
export default {
name: "InvoiceDialog",
props: {
visible: {
type: Boolean,
default: false,
},
invoiceData: {
type: Object,
default: () => null,
},
dicts: {
type: Object,
default: () => ({})
}
},
dicts:['finance_invoice_type'],
data() {
return {
loading: false,
attachments: [],
// Upload Dialog Data
uploadDialogVisible: false,
uploadForm: {
invoicePriceWithTax: '',
invoicePriceWithoutTax: '',
invoiceAmount: '',
invoiceType: this.invoiceData.invoiceType,
remark: '',
},
fileList: [],
rules: {
invoicePriceWithTax: [
{ required: true, message: "请输入发票含税总价", trigger: "blur" }
],
invoicePriceWithoutTax: [
{ required: true, message: "请输入发票未税总价", trigger: "blur" }
],
invoiceAmount: [
{ required: true, message: "请输入发票税额", trigger: "blur" }
]
},
// PDF Preview Data
pdfUrls: {},
pdfPreviewVisible: false,
currentPdfUrl: '',
};
},
computed: {
dialogVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit("update:visible", val);
},
},
previewList() {
return this.attachments
.filter(att => !this.isPdf(att.filePath))
.map(att => this.getImageUrl(att.filePath));
},
groupedAttachments() {
if (!this.attachments || this.attachments.length === 0) return [];
const groups = {};
this.attachments.forEach(att => {
const time = att.createTime;
if (!groups[time]) {
groups[time] = {
createTime: time,
items: [],
remark: att.remark
};
}
groups[time].items.push(att);
});
return Object.values(groups).sort((a, b) => new Date(b.createTime) - new Date(a.createTime));
},
canUpload() {
if (!this.attachments || this.attachments.length === 0) {
return true;
}
return this.attachments.every(att => att.delFlag === '2');
},
titleText() {
return '发票';
}
},
watch: {
visible(val) {
if (val && this.invoiceData) {
this.fetchAttachments();
}
},
},
methods: {
fetchAttachments() {
if (!this.invoiceData.id) return;
this.loading = true;
getInvoiceAttachments(this.invoiceData.id, { type: 'invoice' })
.then(response => {
const data = response.data || [];
// data.sort((a, b) => new Date(b.createTime) - new Date(a.createTime)); // Sorting is handled in groupedAttachments
this.attachments = data;
this.loadPdfPreviews();
this.loading = false;
})
.catch(() => {
this.attachments = [];
this.loading = false;
});
},
loadPdfPreviews() {
this.attachments.forEach(att => {
if (this.isPdf(att.filePath) && !this.pdfUrls[att.filePath]) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: att.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
this.$set(this.pdfUrls, att.filePath, url);
}).catch(console.error);
}
});
},
openPdfPreview(url) {
if (!url) return;
this.currentPdfUrl = url;
this.pdfPreviewVisible = true;
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
downloadFiles(items) {
if (!items || items.length === 0) return;
items.forEach(item => {
this.downloadFile(item);
});
},
downloadFile(attachment) {
const link = document.createElement('a');
link.href = this.getImageUrl(attachment.filePath);
link.download = attachment.fileName || 'receipt';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
handleClose() {
this.attachments = [];
// Clean up object URLs
Object.values(this.pdfUrls).forEach(url => URL.revokeObjectURL(url));
this.pdfUrls = {};
},
// New Upload Dialog Methods
openUploadDialog() {
this.uploadForm = {
invoicePriceWithTax: '',
invoicePriceWithoutTax: '',
invoiceAmount: '',
invoiceType: this.invoiceData.invoiceType,
remark: '',
};
if (this.$refs.uploadForm) {
this.$refs.uploadForm.clearValidate();
}
this.fileList = [];
this.uploadDialogVisible = true;
},
closeUploadDialog() {
this.uploadDialogVisible = false;
this.fileList.forEach(file => {
if (file.url) URL.revokeObjectURL(file.url);
});
this.fileList = [];
},
handleTypeChange(val) {
if (val === '2') {
this.uploadForm.invoiceAmount = '0';
this.handleAmountChange('0');
}
},
handleWithoutTaxChange(val) {
const total = parseFloat(this.uploadForm.invoicePriceWithTax);
const withoutTax = parseFloat(val);
if (!isNaN(total) && !isNaN(withoutTax)) {
this.uploadForm.invoiceAmount = (total - withoutTax).toFixed(2);
}
},
handleAmountChange(val) {
const total = parseFloat(this.uploadForm.invoicePriceWithTax);
const tax = parseFloat(val);
if (!isNaN(total) && !isNaN(tax)) {
this.uploadForm.invoicePriceWithoutTax = (total - tax).toFixed(2);
}
},
handleFileChange(file, fileList) {
const isLt2M = file.size / 1024 / 1024 < 2;
const isAcceptedType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.raw.type);
if (!isAcceptedType) {
this.$message.error('上传文件只能是 JPG/PNG/PDF 格式!');
const index = fileList.indexOf(file);
if (index > -1) fileList.splice(index, 1);
return;
}
if (!isLt2M) {
this.$message.error('上传文件大小不能超过 2MB!');
const index = fileList.indexOf(file);
if (index > -1) fileList.splice(index, 1);
return;
}
file.url = URL.createObjectURL(file.raw);
this.fileList = fileList;
},
handleFileRemove(file, fileList) {
if (file.url) URL.revokeObjectURL(file.url);
this.fileList = fileList;
},
submitNewUpload() {
this.$refs.uploadForm.validate(valid => {
if (valid) {
if (this.fileList.length === 0) {
this.$message.warning("请选择要上传的文件");
return;
}
const limitTotal = parseFloat(this.invoiceData.totalPriceWithTax);
const inputTotal = parseFloat(this.uploadForm.invoicePriceWithTax);
const inputWithoutTax = parseFloat(this.uploadForm.invoicePriceWithoutTax);
const inputTax = parseFloat(this.uploadForm.invoiceAmount);
if (!isNaN(limitTotal)) {
if (limitTotal >= 0) {
if (!isNaN(inputTotal) && inputTotal > limitTotal) {
this.$message.warning("发票含税总价不能超过开票单含税总价");
return;
}
if (!isNaN(inputWithoutTax) && inputWithoutTax < 0) {
this.$message.warning("发票未税总价不能为负数");
return;
}
if (!isNaN(inputTax) && inputTax < 0) {
this.$message.warning("发票税额不能为负数");
return;
}
} else {
if (!isNaN(inputTotal) && inputTotal < limitTotal) {
this.$message.warning("发票含税总价不能小于开票单含税总价");
return;
}
if (!isNaN(inputWithoutTax) && inputWithoutTax > 0) {
this.$message.warning("发票未税总价不能为正数");
return;
}
if (!isNaN(inputTax) && inputTax > 0) {
this.$message.warning("发票税额不能为正数");
return;
}
}
}
if ((this.fileList||[]).length <=0 ){
this.$message.warning("文件不能为空");
return;
}
const formData = new FormData();
this.fileList.forEach(file => {
formData.append("file", file.raw);
});
formData.append("id", this.invoiceData.id);
formData.append("remark", this.uploadForm.remark);
formData.append("invoicePriceWithTax", this.uploadForm.invoicePriceWithTax);
formData.append("invoicePriceWithoutTax", this.uploadForm.invoicePriceWithoutTax);
formData.append("invoiceAmount", this.uploadForm.invoiceAmount);
formData.append("invoiceType", this.uploadForm.invoiceType);
this.invoiceData.invoiceType=this.uploadForm.invoiceType
uploadInvoiceAttachment(formData)
.then(response => {
this.$message.success("上传成功");
this.closeUploadDialog();
this.fetchAttachments();
})
.catch(error => {
this.$message.error("上传失败");
});
}
});
},
},
};
</script>
<style scoped>
.receipt-dialog-body {
max-height: 60vh;
overflow-y: auto;
}
.loading-spinner {
text-align: center;
font-size: 24px;
padding: 20px;
}
.receipt-card-content {
display: flex;
}
.receipt-details {
flex-grow: 1;
}
.detail-item {
display: flex;
margin-bottom: 12px;
font-size: 14px;
}
.item-label {
width: 110px;
color: #606266;
text-align: right;
margin-right: 15px;
flex-shrink: 0;
}
.item-value {
color: #303133;
}
.image-wrapper {
position: relative;
width: 200px;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #DCDFE6;
border-radius: 4px;
margin-bottom: 10px;
}
.void-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-30deg);
color: red;
font-size: 48px;
font-weight: bold;
opacity: 0.7;
pointer-events: none;
}
.download-btn {
display: block;
}
.upload-btn-container {
margin-bottom: 20px;
}
.pdf-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 200px;
height: 150px;
color: #606266;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
}
.pdf-placeholder:hover {
border-color: #409EFF;
color: #409EFF;
}
.pdf-placeholder .el-icon-document {
font-size: 48px;
margin-bottom: 5px;
}
/* New Dialog Styles */
.upload-preview-container {
width: 100%;
height: 300px;
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.preview-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-pdf {
display: flex;
flex-direction: column;
align-items: center;
font-size: 16px;
color: #606266;
}
.preview-pdf .el-icon-document {
font-size: 64px;
margin-bottom: 10px;
}
.preview-placeholder {
text-align: center;
color: #909399;
}
.placeholder-icon {
font-size: 64px;
margin-bottom: 10px;
color: #c0c4cc;
}
.placeholder-text {
font-size: 14px;
}
.pdf-thumbnail-container {
position: relative;
width: 100%;
height: 150px;
cursor: pointer;
}
.pdf-hover-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
color: #fff;
font-size: 24px;
pointer-events: auto;
}
.pdf-thumbnail-container:hover .pdf-hover-overlay {
opacity: 1;
}
</style>

View File

@ -0,0 +1,393 @@
<template>
<div>
<!-- 1. 顶部发票类型与票号日期 -->
<div class="invoice-header">
<div class="header-center">
<div class="invoice-title">
电子发票
<span class="invoice-type-wrapper">
(<el-select v-model="data.invoiceType" class="invoice-type-select" disabled :popper-append-to-body="false">
<el-option
v-for="dict in dict.type.finance_invoice_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>)
</span>
</div>
<div class="header-decoration-line"></div>
</div>
<div class="header-right">
<div class="meta-row">
<span class="meta-label">发票号码:</span>
<span class="meta-value">{{ '----------' }}</span>
</div>
<div class="meta-row">
<span class="meta-label">开票日期:</span>
<span class="meta-value">{{'----------' }}</span>
</div>
</div>
</div>
<div class="invoice-body-border">
<el-row type="flex" class="info-container">
<!-- 左侧购买方信息 -->
<el-col :span="12" class="info-column left-column">
<div class="column-label">购买方<br>信息</div>
<div class="column-content">
<div class="info-item"><span class="label">名称:</span> {{ data.buyerName }}</div>
<div class="info-item"><span class="label">纳税人识别号:</span> {{ data.buyerCreditCode }}</div>
</div>
</el-col>
<!-- 右侧销售方信息 -->
<el-col :span="12" class="info-column right-column">
<div class="column-label">销售方<br>信息</div>
<div class="column-content">
<div class="info-item"><span class="label">名称:</span> {{ data.sellerName }}</div>
<div class="info-item"><span class="label">纳税人识别号:</span> {{ data.sellerCreditCode }}</div>
</div>
</el-col>
</el-row>
<!-- 表格区域 -->
<div class="items-table-container">
<el-table :data="data.detailItemList" border class="invoice-table" show-summary :summary-method="getSummaries">
<el-table-column label="项目名称" prop="productName" align="center"></el-table-column>
<el-table-column label="规格型号" prop="productModel" align="center" width="120"></el-table-column>
<el-table-column label="单位" prop="unit" align="center" width="60"></el-table-column>
<el-table-column label="数量" prop="quantity" align="center" width="80"></el-table-column>
<el-table-column label="单价" prop="price" align="center" width="100"></el-table-column>
<el-table-column label="金额" prop="allPrice" align="center" width="100" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column label="税率%" prop="taxRate" align="center" width="80"></el-table-column>
<el-table-column label="税额" prop="taxAmount" align="center" width="100" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
</el-table>
</div>
<!-- 价税合计 -->
<div class="total-row">
<div class="total-label">价税合计 (大写)</div>
<div class="total-value-chinese">
<span class="currency-symbol"></span> {{ totalAmountChinese }}
</div>
<div class="total-label-small">(小写)</div>
<div class="total-value-number">
¥{{ formatCurrency(totalAmountNumber) }}
</div>
</div>
<!-- 备注 -->
<div class="remark-row">
<div class="remark-label">备注</div>
<div class="remark-content">{{ data.remark }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "InvoiceInfoView",
dicts: ['finance_invoice_type'],
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
computed: {
totalAmountNumber() {
let total = 0;
if (this.data.detailItemList) {
this.data.detailItemList.forEach(item => {
const amount = parseFloat(item.allPrice) || 0;
// const tax = parseFloat(item.taxAmount) || 0;
total += amount ;
});
}
return total.toFixed(2);
},
totalAmountChinese() {
return this.convertCurrency(this.totalAmountNumber);
}
},
methods: {
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
if (column.property === 'allPrice' || column.property === 'taxAmount') {
const values = data.map(item => Number(item[column.property]));
if (!values.every(value => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
sums[index] = '¥' + sum.toFixed(2);
} else {
sums[index] = '';
}
} else {
sums[index] = '';
}
});
return sums;
},
convertCurrency(money) {
const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const cnIntRadice = ['', '拾', '佰', '仟'];
const cnIntUnits = ['', '万', '亿', '兆'];
const cnDecUnits = ['角', '分', '毫', '厘'];
const cnInteger = '整';
const cnIntLast = '元';
let integerNum;
let decimalNum;
let chineseStr = '';
let parts;
if (money === '') return '';
money = parseFloat(money);
if (money >= 999999999999) return '';
if (money === 0) return cnNums[0] + cnIntLast + cnInteger;
let prefix = "";
if (money < 0) {
prefix = "(负数)";
money = Math.abs(money);
}
money = money.toString();
if (money.indexOf('.') === -1) {
integerNum = money;
decimalNum = '';
} else {
parts = money.split('.');
integerNum = parts[0];
decimalNum = parts[1].substr(0, 4);
}
if (parseInt(integerNum, 10) > 0) {
let zeroCount = 0;
let IntLen = integerNum.length;
for (let i = 0; i < IntLen; i++) {
let n = integerNum.substr(i, 1);
let p = IntLen - i - 1;
let q = p / 4;
let m = p % 4;
if (n === '0') {
zeroCount++;
} else {
if (zeroCount > 0) chineseStr += cnNums[0];
zeroCount = 0;
chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
}
if (m === 0 && zeroCount < 4) chineseStr += cnIntUnits[q];
}
chineseStr += cnIntLast;
}
if (decimalNum !== '') {
let decLen = decimalNum.length;
for (let i = 0; i < decLen; i++) {
let n = decimalNum.substr(i, 1);
if (n !== '0') chineseStr += cnNums[Number(n)] + cnDecUnits[i];
}
}
if (chineseStr === '') chineseStr += cnNums[0] + cnIntLast + cnInteger;
else if (decimalNum === '') chineseStr += cnInteger;
return prefix + chineseStr;
},
}
};
</script>
<style scoped>
.invoice-header {
position: relative;
height: 80px;
display: flex;
justify-content: center;
align-items: flex-start;
margin-bottom: 10px;
}
.header-center {
text-align: center;
padding-top: 5px;
}
.invoice-title {
font-size: 24px;
font-weight: bold;
color: #8B4513;
line-height: 40px;
letter-spacing: 2px;
}
.invoice-type-wrapper {
margin-left: 5px;
font-size: 22px;
font-weight: bold;
color: #8B4513;
}
.header-decoration-line {
height: 2px;
background-color: #8B4513;
width: 380px;
margin: 0 auto;
border-bottom: 1px solid #8B4513;
box-shadow: 0 2px 0 0 #fff, 0 4px 0 0 #8B4513;
}
.header-right {
position: absolute;
right: 0;
top: 10px;
text-align: left;
}
.meta-row {
font-size: 13px;
color: #8B4513;
line-height: 20px;
}
.meta-label {
display: inline-block;
width: 70px;
}
.invoice-body-border {
border: 2px solid #8B4513;
padding: 0;
color: #606266;
}
.info-container {
display: flex;
border-bottom: 1px solid #8B4513;
}
.info-column {
padding: 5px;
display: flex;
align-items: stretch;
}
.left-column {
border-right: 1px solid #8B4513;
}
.column-label {
width: 30px;
padding: 0 5px;
color: #8B4513;
font-size: 14px;
text-align: center;
line-height: 1.4;
border-right: 1px solid #8B4513;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
word-break: break-all;
}
.column-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
font-size: 13px;
}
.info-item {
margin-bottom: 2px;
color: #8B4513;
}
.info-item .label {
display: inline-block;
width: 100px;
}
.items-table-container {
padding: 0;
border-bottom: 1px solid #8B4513;
}
.invoice-table ::v-deep th {
font-weight: bold;
color: #8B4513;
border-color: #8B4513;
}
.invoice-table ::v-deep td {
border-color: #8B4513;
}
.total-row {
display: flex;
align-items: center;
border-bottom: 1px solid #8B4513;
height: 40px;
padding: 0 10px;
color: #8B4513;
}
.total-label {
width: 120px;
font-size: 13px;
}
.total-value-chinese {
flex: 1;
font-size: 14px;
padding-left: 10px;
}
.currency-symbol {
border: 1px solid #8B4513;
border-radius: 50%;
padding: 1px 4px;
font-size: 12px;
margin-right: 5px;
}
.total-label-small {
width: 60px;
text-align: right;
font-size: 13px;
}
.total-value-number {
width: 150px;
padding-left: 10px;
font-size: 14px;
}
.remark-row {
display: flex;
min-height: 40px;
padding: 10px;
color: #8B4513;
}
.remark-label {
width: 120px;
font-size: 13px;
}
.remark-content {
flex: 1;
font-size: 13px;
}
.invoice-type-select {
width: 160px;
}
/* Hide select border to blend in */
.invoice-type-select ::v-deep .el-input__inner {
border: none;
background: transparent;
font-size: 22px;
font-weight: bold;
color: #8B4513;
padding: 0;
text-align: center;
height: 40px;
line-height: 40px;
}
.invoice-type-select ::v-deep .el-select__caret {
color: #8B4513;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,397 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="150px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="销售-开票单编号" prop="invoiceBillCode">
<el-input
v-model="queryParams.invoiceBillCode"
placeholder="请输入开票单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="进货商名称" prop="partnerName">
<el-input
v-model="queryParams.partnerName"
placeholder="请输入客户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="销售-应收单编号" prop="receivableBillCode">
<el-input
v-model="queryParams.receivableBillCode"
placeholder="请输入应收单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="票据类型" prop="invoiceType">
<el-select v-model="queryParams.invoiceType" placeholder="票据类型" clearable>
<el-option v-for="dict in dict.type.finance_invoice_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="approveStatus">
<el-select v-model="queryParams.approveStatus" placeholder="请选择审批状态" clearable>
<el-option
v-for="dict in dict.type.approve_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="开票状态" prop="invoiceStatus">
<el-select v-model="queryParams.invoiceStatus" placeholder="请选择开票状态" clearable>
<el-option
v-for="dict in dict.type.invoice_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批节点" prop="approveNode">
<el-input
v-model="queryParams.approveNode"
placeholder="请输入审批节点"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="审批通过时间">
<el-date-picker
v-model="dateRangeApproval"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item label="预计开票时间">
<el-date-picker
v-model="dateRangeEstimated"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<!-- <el-form-item label="实际收票时间">-->
<!-- <el-date-picker-->
<!-- v-model="dateRangeActual"-->
<!-- style="width: 240px"-->
<!-- value-format="yyyy-MM-dd"-->
<!-- type="daterange"-->
<!-- range-separator="-"-->
<!-- start-placeholder="开始日期"-->
<!-- end-placeholder="结束日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="invoiceList">
<el-table-column label="销售-开票单编号" align="center" prop="invoiceBillCode" width="200"/>
<el-table-column label="预计开票时间" align="center" prop="invoiceTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.invoiceTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="进货商名称" align="center" prop="partnerName" width="300" />
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="180" >
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax<0?{color:'red'}:{}">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<el-table-column label="未税总价(元)" align="center" prop="totalPriceWithoutTax" width="180">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithoutTax<0?{color:'red'}:{}">{{ formatCurrency(scope.row.totalPriceWithoutTax) }}</span>
</template>
</el-table-column>
<el-table-column label="发票未税总价(元)" align="center" prop="invoicePriceWithoutTax" width="180">
<template slot-scope="scope">
<span :style="scope.row.invoicePriceWithoutTax<0?{color:'red'}:{}">{{ formatCurrency(scope.row.invoicePriceWithoutTax) }}</span>
</template>
</el-table-column>
<el-table-column label="税额(元)" align="center" prop="taxAmount" width="180">
<template slot-scope="scope">
<span :style="scope.row.invoicePriceWithoutTax<0?{color:'red'}:{}">{{ formatCurrency($calc.sub(scope.row.totalPriceWithTax, scope.row.totalPriceWithoutTax)) }}</span>
</template>
</el-table-column>
<el-table-column label="发票税额(元)" align="center" prop="invoicePriceTax" width="180">
<template slot-scope="scope">
<span :style="scope.row.invoicePriceWithoutTax<0?{color:'red'}:{}">{{ formatCurrency($calc.sub(scope.row.totalPriceWithTax, scope.row.invoicePriceWithoutTax)) }}</span>
</template>
</el-table-column>
<el-table-column label="开票状态" align="center" prop="invoiceStatus" width="150" >
<template slot-scope="scope">
<dict-tag :options="dict.type.invoice_bill_status" :value="scope.row.invoiceStatus"/>
</template>
</el-table-column>
<el-table-column label="审批状态" align="center" prop="approveStatus" width="150" >
<template slot-scope="scope">
<dict-tag :options="dict.type.approve_status" :value="scope.row.approveStatus"/>
</template>
</el-table-column>
<el-table-column label="审批通过时间" align="center" prop="approveTime" width="180" >
<template slot-scope="scope">
<span>{{ parseTime(scope.row.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" width="200" fixed="right"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
v-show="scope.row.approveStatus==='2'"
@click="handleReceipt(scope.row)"
>发票</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-s-ticket"
v-show="scope.row.invoiceStatus==='1' &&(scope.row.approveStatus==='0')"
@click="handleApplyInvoice(scope.row)"
>申请开票</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
v-show="scope.row.approveStatus==='0'"
@click="handleReturn(scope.row)"
>退回</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-left"
v-show="scope.row.approveStatus === '2' && scope.row.invoiceStatus === '2'"
@click="handleRedRush(scope.row)"
>申请红冲</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
v-show="(scope.row.invoiceBillType === 'FROM_RECEIVABLE' && (scope.row.approveStatus === '2' || scope.row.approveStatus === '3') && scope.row.invoiceStatus === '1') || (scope.row.invoiceBillType === 'RED_RUSH' && scope.row.approveStatus === '3')"
@click="handleRevoke(scope.row)"
>撤销</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 详情抽屉 -->
<detail-drawer :visible.sync="detailOpen" :detail="detailData"></detail-drawer>
<!-- 新增弹窗 -->
<add-form :visible.sync="addOpen" :dicts="dict.type" @submit="handleAddSubmit"></add-form>
<!-- 收票附件弹窗 -->
<invoice-dialog :visible.sync="receiptOpen" :invoice-data="currentRow" :dicts="dict.type"></invoice-dialog>
<!-- 申请开票弹窗 -->
<apply-invoice :visible.sync="applyOpen" :row-data="currentRow" :is-red-rush="isRedRush" @submit="getList"></apply-invoice>
</div>
</template>
<script>
import {listInvoice, getInvoice, redRush, addInvoice, returnInvoice, revokeInvoice} from "@/api/finance/invoice";
import { addDateRange } from "@/utils/ruoyi";
import DetailDrawer from "./components/DetailDrawer.vue";
import AddForm from "./components/AddForm.vue";
import InvoiceDialog from "./components/InvoiceDialog.vue";
import ApplyInvoice from "./components/ApplyInvoice.vue";
export default {
name: "Invoice",
components: {
DetailDrawer,
AddForm,
InvoiceDialog,
ApplyInvoice
},
dicts:['invoice_bill_type','approve_status','invoice_bill_status','finance_invoice_type'],
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
invoiceList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
invoiceBillCode: null,
invoiceType: null,
partnerName: null,
receivableBillCode: null,
invoiceBillType: null,
approveStatus: null,
invoiceStatus: null,
approveNode: null,
},
//
dateRangeApproval: [],
dateRangeEstimated: [],
dateRangeActual: [],
//
detailOpen: false,
detailData: null,
//
addOpen: false,
//
applyOpen: false,
//
isRedRush: false,
//
receiptOpen: false,
currentRow: {}
};
},
created() {
this.getList();
},
methods: {
addDateRange,
getList() {
this.loading = true;
let query = { ...this.queryParams };
query = this.addDateRange(query, this.dateRangeApproval, 'ApproveTime');
query = this.addDateRange(query, this.dateRangeEstimated, 'InvoiceTime');
query = this.addDateRange(query, this.dateRangeActual, 'ActualInvoiceTime');
listInvoice(query).then(response => {
this.invoiceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeApproval = [];
this.dateRangeEstimated = [];
this.dateRangeActual = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.addOpen = true;
},
/** 新增提交 */
handleAddSubmit(form) {
addInvoice(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("新增收票单失败", error);
this.$modal.msgError("新增失败");
});
},
/** 详情按钮操作 */
handleDetail(row) {
getInvoice(row.id).then(response => {
this.detailData = response.data;
this.detailData.approveNode =row.approveNode;
this.detailOpen = true;
});
},
/** 收票附件按钮操作 */
handleReceipt(row) {
this.currentRow = row;
this.receiptOpen = true;
},
/** 申请开票按钮操作 */
handleApplyInvoice(row) {
this.isRedRush = false;
this.currentRow = row;
this.applyOpen = true;
},
/** 红冲按钮操作 */
handleRedRush(row) {
this.isRedRush = true;
this.currentRow = row;
this.applyOpen = true;
},
handleReturn(row) {
this.$modal.confirm('是否确认退回收票单编号为"' + row.invoiceBillCode + '"的数据项?').then(function() {
return returnInvoice(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("退回成功");
}).catch(() => {});
},
/** 撤销按钮操作 */
handleRevoke(row) {
this.$modal.confirm('是否确认撤销收票单编号为"' + row.invoiceBillCode + '"的数据项?').then(function() {
return revokeInvoice(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("撤销成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,305 @@
<template>
<el-drawer title="采购-应付单详情" :visible.sync="internalVisible" :wrapper-closable="false" size="70%" @close="handleClose">
<div class="dialog-body">
<!-- Part 1: Details -->
<div>
<el-divider content-position="left">采购-应付单</el-divider>
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>项目编号:</strong> {{ formData.projectCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>项目名称:</strong> {{ formData.projectName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>采购-应付单编号:</strong> {{ formData.payableBillCode }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>生成时间:</strong> {{ formData.createTime }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>该制造商是否有预付单:</strong> {{ formData.preResidueAmount == 0 ? '否' : '是' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>预付金额():</strong> {{ formatCurrency(formData.preResidueAmount) }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>制造商名称:</strong> {{ formData.vendorName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>合同编号:</strong> {{ formData.orderCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>/入库单号:</strong> {{ formData.inventoryCode }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>含税总价():</strong> {{ formatCurrency(formData.totalPriceWithTax) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>未税总价():</strong> {{ formatCurrency(formData.totalPriceWithoutTax) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>税额():</strong> {{ formatCurrency(formData.taxAmount) }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>未付款金额():</strong> {{ formatCurrency(formData.unpaidPaymentAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>已付款金额():</strong> {{ formatCurrency(formData.paidPaymentAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>付款中金额():</strong> {{ formatCurrency(this.$calc.sub(this.$calc.sub(formData.totalPriceWithTax,formData.paidPaymentAmount),formData.unpaidPaymentAmount)) }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>未收票金额():</strong> {{ formatCurrency(formData.unreceivedTicketAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>已收票金额():</strong> {{ formatCurrency(formData.receivedTicketAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>收票中金额():</strong> {{ formatCurrency(this.$calc.sub(this.$calc.sub(formData.totalPriceWithTax,formData.receivedTicketAmount),formData.unreceivedTicketAmount))}}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>生成付款单:</strong> {{ formData.totalPriceWithTax == formData.unpaidPaymentAmount ? '未生成' : formData.unpaidPaymentAmount == 0 ? '全部生成' : '部分生成' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>生成收票单:</strong> {{ formData.totalPriceWithTax == formData.unreceivedTicketAmount ? '未生成' : formData.unreceivedTicketAmount == 0 ? '全部生成' : '部分生成' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item" style="display: flex"><strong>产品类型:</strong>
<dict-tag :options="dict.type.product_type" :value="formData.productType"/>
</div>
</el-col>
</el-row>
</div>
</div>
<!-- Part 2: Tabs -->
<div style="padding: 20px">
<el-tabs v-model="activeTab">
<el-tab-pane label="明细" name="details">
<el-divider content-position="left">采购-付款单</el-divider>
<el-table :data="formData.detailList" style="width: 100%" show-summary :summary-method="getSummaries">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column prop="payableDetailType" label="付款通道">
<template slot-scope="scope">
<dict-tag :options="dict.type.payable_detail_type" :value="scope.row.payableDetailType"/>
</template>
</el-table-column>
<el-table-column prop="actualPaymentTime" label="实际付款时间">
<template slot-scope="scope">
{{ scope.row.actualPaymentTime || '-' }}
</template>
</el-table-column>
<el-table-column prop="paymentAmount" label="本次付款金额">
<template slot-scope="scope">
<span :style="scope.row.paymentAmount<0?{color:'red'}:{}"> {{ formatCurrency(scope.row.paymentAmount) }}</span>
</template>
</el-table-column>
<el-table-column prop="paymentRate" label="本次付款比例"></el-table-column>
<el-table-column prop="paymentStatus" label="付款状态">
<template slot-scope="scope">
<dict-tag :options="dict.type.payment_status" :value="scope.row.paymentStatus"/>
</template>
</el-table-column>
<el-table-column prop="paymentBillCode" label="采购-付款单编号"></el-table-column>
<el-table-column label="回执单/退款图">
<template slot-scope="scope">
<span v-if="scope.row.finAttachment">
<el-button type="text" size="mini" icon="el-icon-view" @click="handlePreview(scope.row.finAttachment)"></el-button>
<el-button type="text" size="mini" icon="el-icon-download" @click="downloadFile(scope.row.finAttachment)"></el-button>
</span>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">采购-收票单</el-divider>
<el-table :data="formData.ticketDetailList" style="width: 100%" show-summary :summary-method="getSummaries">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column prop="payableDetailType" label="发票类型">
<template slot-scope="scope">
<dict-tag :options="dict.type.ticket_detail_type" :value="scope.row.payableDetailType"/>
</template>
</el-table-column>
<el-table-column prop="actualTicketTime" label="实际收票时间">
<template slot-scope="scope">
{{ scope.row.actualTicketTime || '-' }}
</template>
</el-table-column>
<el-table-column prop="paymentAmount" label="本次收票金额">
<template slot-scope="scope">
<span :style="scope.row.paymentAmount<0?{color:'red'}:{}"> {{ formatCurrency(scope.row.paymentAmount) }}</span>
</template>
</el-table-column>
<el-table-column prop="paymentRate" label="本次收票比例"></el-table-column>
<el-table-column prop="receiptStatus" label="收票状态">
<template slot-scope="scope">
<dict-tag :options="dict.type.receipt_status" :value="scope.row.ticketStatus"/>
</template>
</el-table-column>
<el-table-column prop="ticketBillCode" label="采购-收票单编号"></el-table-column>
<el-table-column label="发票/红冲发票">
<template slot-scope="scope">
<span v-if="scope.row.finAttachment">
<el-button type="text" size="mini" icon="el-icon-view" @click="handlePreview(scope.row.finAttachment)"></el-button>
<el-button type="text" size="mini" icon="el-icon-download" @click="downloadFile(scope.row.finAttachment)"></el-button>
</span>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="付款计划" name="paymentPlan">
<PaymentPlan :isInitEdit=true @syncPlan="refreshTicketPlan()" :payableData="data"/>
</el-tab-pane>
<el-tab-pane label="收票计划" name="receivingTicketPlan">
<ReceivingTicketPlan ref="ticketPlan" :isInitEdit=true :payableData="data"/>
</el-tab-pane>
</el-tabs>
</div>
<GlobalFilePreview ref="filePreview" />
</div>
<!-- <div slot="footer" class="dialog-footer">-->
<!-- <el-button @click="handleClose"> </el-button>-->
<!-- <el-button type="primary" @click="handleSubmit"> </el-button>-->
<!-- </div>-->
</el-drawer>
</template>
<script>
import PaymentPlan from './PaymentPlan.vue';
import ReceivingTicketPlan from './ReceivingTicketPlan.vue';
import { getPayable } from "@/api/finance/payable";
import ReceiptPlan from "@/views/finance/receivable/components/ReceiptPlan.vue";
import GlobalFilePreview from '@/components/GlobalFilePreview';
export default {
name: "EditForm",
dicts: ['product_type','payment_status','payable_detail_type', 'ticket_detail_type', 'receipt_status'],
components: {
ReceiptPlan,
PaymentPlan,
ReceivingTicketPlan,
GlobalFilePreview
},
props: {
visible: {
type: Boolean,
default: false
},
data: {
type: Object,
default: () => ({})
}
},
data() {
return {
internalVisible: this.visible, // Local copy of the visible prop
activeTab: 'details',
formData: {},
};
},
watch: {
visible(newVal) {
this.internalVisible = newVal; // Sync prop to local data
if (newVal && this.data.id) {
this.getDetails();
}
},
internalVisible(newVal) {
if (!newVal) {
this.formData = {};
}
this.$emit('update:visible', newVal); // Emit changes to parent
}
},
methods: {
refreshTicketPlan(){
this.$refs.ticketPlan.fetchTicketPlans(this.formData.id);
},
getDetails() {
getPayable(this.data.id).then(res => {
this.formData = res.data;
});
},
handlePreview(attachment) {
this.$refs.filePreview.handlePreview(attachment);
},
downloadFile(attachment){
this.$refs.filePreview.downloadFile(attachment);
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
let paymentAmountSum = 0;
if (data && data.length > 0) {
paymentAmountSum = data.reduce((acc, item) => {
const value = Number(item.paymentAmount);
return acc + (isNaN(value) ? 0 : value);
}, 0);
}
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
} else if (column.property === 'paymentAmount') {
sums[index] = paymentAmountSum.toFixed(2);
} else if (column.property === 'paymentRate') {
if (this.formData.totalPriceWithTax && this.formData.totalPriceWithTax > 0) {
const ratio = this.$calc.div(paymentAmountSum , this.formData.totalPriceWithTax,4);
sums[index] = (ratio * 100).toFixed(2) + '%';
} else {
sums[index] = '0.00%';
}
} else {
sums[index] = '';
}
});
return sums;
},
handleClose() {
this.internalVisible = false; // Close dialog local
this.$emit('close');
},
handleSubmit() {
this.handleClose();
},
}
};
</script>
<style scoped>.details-container {
border: 1px solid #EBEEF5;
padding: 20px;
border-radius: 4px;
}
.detail-item {
border: 1px solid #EBEEF5;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
}
.dialog-body {
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,371 @@
<template>
<el-dialog title="合并发起付款单" :visible.sync="dialogVisible" width="80%" :close-on-click-modal="false" @close="handleClose" append-to-body>
<div class="dialog-body">
<el-form ref="form" :model="form" :inline="true" label-width="120px">
<el-row>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="付款单类型" prop="paymentBillType">-->
<!-- <el-select disabled v-model="form.paymentBillType" placeholder="请选择付款单类型" clearable>-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.payment_bill_type"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="8">
<el-form-item label="制造商名称">
<el-input v-model="form.vendorName" readonly/>
</el-form-item>
</el-col>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="预计付款时间" prop="estimatedPaymentTime">-->
<!-- <el-date-picker-->
<!-- v-model="form.estimatedPaymentTime"-->
<!-- type="date"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- placeholder="选择日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
</el-row>
</el-form>
<el-divider content-position="left">采购应付单表</el-divider>
<el-table :data="payableOrdersWithPlans" border max-height="300px" style="margin-bottom: 20px;">
<el-table-column label="采购-应付单编号" align="center" prop="payableBillCode" width="150"/>
<el-table-column label="预计付款时间" align="center" prop="planPaymentDate" width="180"/>
<el-table-column label="预期付款计划" align="center" width="100" prop="planAmount">
<template slot-scope="scope">
{{ formatCurrency(calculateOrderCurrentPaymentAmount(scope.row.id).toFixed(2)) }}
</template>
</el-table-column>
<el-table-column label="预期付款比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentPaymentRate(scope.row.id) }}
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="制造商名称" align="center" prop="vendorName" />
<!-- <el-table-column label="合同编号" align="center" prop="orderCode" width="150"/>-->
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>-->
<!-- <el-table-column label="付款状态" align="center" prop="paymentStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.payment_status" :value="scope.row.paymentStatus"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未付款金额(元)" align="center" prop="unpaidPaymentAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<!-- <el-table-column label="本次付款金额" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{ calculateOrderCurrentPaymentAmount(scope.row.id).toFixed(2) }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="本次付款比例" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{ calculateOrderCurrentPaymentRate(scope.row.id) }}%-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="已付款金额(元)" align="center" prop="paidPaymentAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="付款中金额(元)" align="center" prop="paidAmount" width="120">
<template slot-scope="scope">
{{ formatCurrency($calc.sub($calc.sub(scope.row.totalPriceWithTax, scope.row.paidPaymentAmount), scope.row.unpaidPaymentAmount)) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenPaymentPlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<div class="total-info">
<span style="margin-left: 20px;">计划付款总金额: <el-tag type="success">{{
formatCurrency(totalPlannedAmount.toFixed(2))
}}</el-tag></span>
<span>计划付款比例: <el-tag type="info">{{ this.$calc.mul(this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4),100) }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleConfirm"> </el-button>
</div>
<el-dialog :title="planTitle" :visible.sync="isPaymentPlanSelectorOpen" width="70%"
@close="isPaymentPlanSelectorOpen=false" append-to-body>
<payment-plan-selector
ref="planSelector"
:payable-data="choosePayable"
:selected-plans="choosePayable.paymentPlans"
@confirm="handlePaymentPlanConfirm"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isPaymentPlanSelectorOpen=false"> </el-button>
<!-- <el-button type="primary" @click="handleConfirm" > </el-button>-->
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
<!-- 付款计划选择器弹窗 -->
</el-dialog>
</template>
<script>
import PaymentPlan from './PaymentPlan.vue';
import {getPaymentPlan} from "@/api/finance/payable";
export default {
name: "MergePaymentDialog",
components: {PaymentPlanSelector: PaymentPlan},
dicts: ['payment_status','payment_bill_type'], // Add dicts for dict-tag
props: {
visible: {
type: Boolean,
default: false
},
payableOrders: {
type: Array,
default: () => []
}
},
data() {
return {
internalVisible: this.visible,
planTitle: '',
choosePayable: {},
form: {
paymentBillType: 'FROM_PAYABLE', // Default to a type, or make it dynamic
vendorName: '',
estimatedPaymentTime: null,
},
payableOrdersWithPlans: [], // Each order will now have its own paymentPlans array
isPaymentPlanSelectorOpen: false,
currentPayableOrderIndexForPlan: -1, // Index of the order in payableOrdersWithPlans
loadingPaymentPlans: false, // Loading state for fetching payment plans
};
},
computed: {
dialogVisible: {
get() {
return this.internalVisible;
},
set(val) {
this.internalVisible = val;
this.$emit('update:visible', val);
}
},
totalPayableAmountWithTax() {
return this.payableOrdersWithPlans.reduce((sum, order) => sum + (order.totalPriceWithTax || 0), 0);
},
totalPlannedAmount() {
return this.payableOrdersWithPlans.reduce((orderSum, order) => {
const orderPlansTotal = (order.paymentPlans || []).reduce((planSum, plan) => planSum + (plan.planAmount || 0), 0);
return orderSum + orderPlansTotal;
}, 0);
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.initDialogData();
}
},
payableOrders: {
handler(newVal) {
if (this.dialogVisible) {
this.initDialogData();
}
},
deep: true
}
},
methods: {
initDialogData() {
// Initialize form fields
if (this.payableOrders.length > 0) {
const firstVendorName = this.payableOrders[0].vendorName;
const allSameVendor = this.payableOrders.every(order => order.vendorName === firstVendorName);
this.form.vendorName = allSameVendor ? firstVendorName : '多个制造商';
this.form.estimatedPaymentTime = this.payableOrders[0].estimatedPaymentTime || null; // Use first order's estimated time as default
} else {
this.form.vendorName = '';
this.form.estimatedPaymentTime = null;
}
this.form.paymentBillType = 'FROM_PAYABLE'; // Default
// Initialize payableOrdersWithPlans
this.payableOrdersWithPlans = this.payableOrders.map(order => {
const paymentPlans = order.paymentPlans ? [...order.paymentPlans] : [];
if (paymentPlans.length === 0 && order.lastPaymentPlanId) {
paymentPlans.push({
id: order.lastPaymentPlanId,
planAmount: order.planAmount,
planPaymentDate: order.planPaymentDate,
planRate: this.$calc.mul(this.$calc.div(order.planAmount, order.totalPriceWithTax, 4), 100)
});
}
return {
...order,
paymentPlans: paymentPlans, // Retain existing plans if any, otherwise empty
totalPriceWithTax: order.totalPriceWithTax || 0, // Ensure numeric for calculations
unpaidAmount: order.unpaidAmount || 0,
paidAmount: order.paidAmount || 0, // Ensure numeric for calculations
}
});
},
handleClose() {
this.dialogVisible = false;
this.resetForm();
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan || [];
const orderIndex = this.payableOrdersWithPlans.findIndex(o => o.id === this.choosePayable.id);
if (orderIndex === -1) {
this.$modal.msgError('找不到要更新的应付单');
return;
}
const currentOrder = this.payableOrdersWithPlans[orderIndex];
// Update the payment plans for the specific order
this.$set(currentOrder, 'paymentPlans', [...selectedPlans]);
this.isPaymentPlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新付款计划选择,共 ${selectedPlans.length}`);
},
handleConfirm() {
// Validate main form fields
if (!this.form.paymentBillType) {
this.$modal.msgError('请选择付款单类型');
return;
}
// if (!this.form.estimatedPaymentTime) {
// this.$modal.msgError('');
// return;
// }
// Validate each payable order's payment plans
for (const order of this.payableOrdersWithPlans) {
if (!order.paymentPlans || order.paymentPlans.length === 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 至少需要一条付款计划`);
return;
}
for (const plan of order.paymentPlans) {
if (!plan.planPaymentDate) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的付款计划中预计付款时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的付款计划中预计付款金额必须大于0。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的付款计划中应付比例不能为空。`);
return;
}
}
}
// Construct the final data to be emitted to the parent
const mergedPaymentData = {
paymentBillType: this.form.paymentBillType,
estimatedPaymentTime: this.form.estimatedPaymentTime,
// Collect all payable orders with their updated payment plans
payableOrders: this.payableOrdersWithPlans.map(order => ({
id: order.id,
taxRate: order.taxRate,
payableBillCode: order.payableBillCode,
paymentPlans: order.paymentPlans.map(plan => ({
planPaymentDate: plan.planPaymentDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
remark: plan.remark,
id: plan.id,
})),
})),
totalMergePaymentAmount: this.totalPlannedAmount, // Total amount for the merged bill
};
this.$emit('confirm', mergedPaymentData);
this.dialogVisible = false;
},
handleCancel() {
this.dialogVisible = false;
this.resetForm();
},
resetForm() {
this.form = {
paymentBillType: 'FROM_PAYABLE',
vendorName: '',
estimatedPaymentTime: null,
};
this.payableOrdersWithPlans = [];
this.currentPayableOrderIndexForPlan = -1;
this.loadingPaymentPlans = false;
},
handleOpenPaymentPlanSelector(row, index) {
this.planTitle = `选择付款计划 - ${row.payableBillCode}`;
this.choosePayable = row;
this.currentPayableOrderIndexForPlan = index;
this.isPaymentPlanSelectorOpen = true;
console.log(this.choosePayable.id)
},
handlePaymentPlanConfirm(updatedPlans) {
// Update the payment plans for the specific order
if (this.currentPayableOrderIndexForPlan !== -1) {
this.$set(this.payableOrdersWithPlans[this.currentPayableOrderIndexForPlan], 'paymentPlans', updatedPlans);
}
this.isPaymentPlanSelectorOpen = false;
this.currentPayableOrderIndexForPlan = -1;
},
calculateOrderCurrentPaymentAmount(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.paymentPlans) {
return order.paymentPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentPaymentRate(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.paymentPlans && order.unpaidAmount >= 0) {
const currentAmount = this.calculateOrderCurrentPaymentAmount(orderId);
console.log(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ))
console.log(11111)
return this.$calc.mul((this.$calc.div(currentAmount ,order.totalPriceWithTax,4 )),100);
}
return 0;
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,382 @@
<template>
<el-dialog title="合并发起收票单" :close-on-click-modal="false" :visible.sync="dialogVisible" width="80%" @close="handleClose" append-to-body>
<div class="dialog-body">
<el-form ref="form" :model="form" :inline="true" label-width="120px">
<el-row>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="收票单类型" prop="ticketBillType">-->
<!-- <el-select disabled v-model="form.ticketBillType" placeholder="请选择收票单类型" clearable>-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.payment_bill_type"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="8">
<el-form-item label="制造商名称">
<el-input v-model="form.vendorName" readonly/>
</el-form-item>
</el-col>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="预计收票时间" prop="ticketTime">-->
<!-- <el-date-picker-->
<!-- v-model="form.ticketTime"-->
<!-- type="date"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- placeholder="选择日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="8">
<el-form-item label="制造商开票时间" prop="vendorTicketTime">
<el-date-picker
v-model="form.vendorTicketTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider content-position="left">采购应付单表</el-divider>
<el-table :data="payableOrdersWithPlans" border max-height="300px" style="margin-bottom: 20px;">
<el-table-column label="采购-应付单编号" align="center" prop="payableBillCode" width="150"/>
<el-table-column label="预计收票时间" align="center" prop="planTicketDate" width="180"/>
<!-- <el-table-column label="收票计划" align="center" width="100" prop="planTicketAmount">-->
<!-- </el-table-column>-->
<el-table-column label="预期收票计划" align="center" width="120">
<template slot-scope="scope">
{{ formatCurrency(calculateOrderCurrentTicketAmount(scope.row.id).toFixed(2)) }}
</template>
</el-table-column>
<el-table-column label="预期收票比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentTicketRate(scope.row.id) }}
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="制造商名称" align="center" prop="vendorName" />
<!-- <el-table-column label="合同编号" align="center" prop="orderCode" width="150"/>-->
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>-->
<!-- <el-table-column label="收票状态" align="center" prop="invoiceStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.invoice_status" :value="scope.row.invoiceStatus"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票金额(元)" align="center" prop="unreceivedTicketAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收票金额(元)" align="center" prop="receivedTicketAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中金额(元)" align="center" prop="invoicedAmount" width="120" >
<template slot-scope="scope">
{{ formatCurrency($calc.sub($calc.sub(scope.row.totalPriceWithTax,scope.row.receivedTicketAmount),scope.row.unreceivedTicketAmount))}}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenTicketPlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<div class="total-info">
<span style="margin-left: 20px;">计划收票总金额: <el-tag type="success">{{
formatCurrency( totalPlannedAmount.toFixed(2))
}}</el-tag></span>
<span>计划收票比例: <el-tag type="info">{{ this.$calc.mul(this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4),100) }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleConfirm"> </el-button>
</div>
<el-dialog :title="planTitle" :visible.sync="isTicketPlanSelectorOpen" width="70%"
@close="isTicketPlanSelectorOpen=false" append-to-body>
<receiving-ticket-plan
ref="planSelector"
:payable-data="choosePayable"
:selected-plans="choosePayable.ticketPlans"
@confirm="handleTicketPlanConfirm"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isTicketPlanSelectorOpen=false"> </el-button>
<!-- <el-button type="primary" @click="handleConfirm" > </el-button>-->
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
<!-- 收票计划选择器弹窗 -->
</el-dialog>
</template>
<script>
import ReceivingTicketPlan from './ReceivingTicketPlan.vue';
import {getReceivingTicketPlan} from "@/api/finance/payable";
export default {
name: "MergeReceiptDialog",
components: {ReceivingTicketPlan},
dicts: ['invoice_status','payment_bill_type'], // Add dicts for dict-tag
props: {
visible: {
type: Boolean,
default: false
},
payableOrders: {
type: Array,
default: () => []
}
},
data() {
return {
internalVisible: this.visible,
planTitle: '',
choosePayable: {},
form: {
ticketBillType: 'FROM_PAYABLE', // Default to a type, or make it dynamic
vendorName: '',
ticketTime: null,
vendorTicketTime: null,
},
payableOrdersWithPlans: [], // Each order will now have its own ticketPlans array
isTicketPlanSelectorOpen: false,
currentPayableOrderIndexForPlan: -1, // Index of the order in payableOrdersWithPlans
loadingTicketPlans: false, // Loading state for fetching ticket plans
};
},
computed: {
dialogVisible: {
get() {
return this.internalVisible;
},
set(val) {
this.internalVisible = val;
this.$emit('update:visible', val);
}
},
totalPayableAmountWithTax() {
return this.payableOrdersWithPlans.reduce((sum, order) => sum + (order.totalPriceWithTax || 0), 0);
},
totalPlannedAmount() {
return this.payableOrdersWithPlans.reduce((orderSum, order) => {
const orderPlansTotal = (order.ticketPlans || []).reduce((planSum, plan) => planSum + (plan.planAmount || 0), 0);
return orderSum + orderPlansTotal;
}, 0);
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.initDialogData();
}
},
payableOrders: {
handler(newVal) {
if (this.dialogVisible) {
this.initDialogData();
}
},
deep: true
}
},
methods: {
initDialogData() {
// Initialize form fields
if (this.payableOrders.length > 0) {
const firstVendorName = this.payableOrders[0].vendorName;
const allSameVendor = this.payableOrders.every(order => order.vendorName === firstVendorName);
this.form.vendorName = allSameVendor ? firstVendorName : '多个制造商';
this.form.ticketTime = null; // Reset time
this.form.vendorTicketTime = null;
} else {
this.form.vendorName = '';
this.form.ticketTime = null;
this.form.vendorTicketTime = null;
}
this.form.ticketBillType = 'FROM_PAYABLE'; // Default
// Initialize payableOrdersWithPlans
this.payableOrdersWithPlans = this.payableOrders.map(order => {
const ticketPlans = order.ticketPlans ? [...order.ticketPlans] : [];
if (ticketPlans.length === 0 && order.lastTicketPlanId) {
ticketPlans.push({
id: order.lastTicketPlanId,
planAmount: order.planTicketAmount,
taxRate: order.taxRate,
planTicketDate: order.planTicketDate,
planRate: this.$calc.mul(this.$calc.div(order.planTicketAmount, order.totalPriceWithTax, 4), 100)
});
}
return {
...order,
ticketPlans: ticketPlans, // Retain existing plans if any, otherwise empty
totalPriceWithTax: order.totalPriceWithTax || 0, // Ensure numeric for calculations
unInvoicedAmount: order.unInvoicedAmount || 0,
invoicedAmount: order.invoicedAmount || 0, // Ensure numeric for calculations
}
});
},
handleClose() {
this.dialogVisible = false;
this.resetForm();
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan || [];
const orderIndex = this.payableOrdersWithPlans.findIndex(o => o.id === this.choosePayable.id);
if (orderIndex === -1) {
this.$modal.msgError('找不到要更新的应付单');
return;
}
const currentOrder = this.payableOrdersWithPlans[orderIndex];
// Update the ticket plans for the specific order
this.$set(currentOrder, 'ticketPlans', [...selectedPlans]);
this.isTicketPlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新收票计划选择,共 ${selectedPlans.length}`);
},
handleConfirm() {
// Validate main form fields
if (!this.form.ticketBillType) {
this.$modal.msgError('请选择收票单类型');
return;
}
// if (!this.form.ticketTime) {
// this.$modal.msgError('');
// return;
// }
if (!this.form.vendorTicketTime) {
this.$modal.msgError('请选择制造商开票时间');
return;
}
// Validate each payable order's ticket plans
for (const order of this.payableOrdersWithPlans) {
if (!order.ticketPlans || order.ticketPlans.length === 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 至少需要一条收票计划`);
return;
}
for (const plan of order.ticketPlans) {
if (!plan.planTicketDate) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中预计收票时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中预计收票金额必须大于0。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中收票比例不能为空。`);
return;
}
}
}
// Construct the final data to be emitted to the parent
const mergedReceiptData = {
ticketBillType: this.form.ticketBillType,
ticketTime: this.form.ticketTime,
vendorTicketTime: this.form.vendorTicketTime,
// Collect all payable orders with their updated ticket plans
payableOrders: this.payableOrdersWithPlans.map(order => ({
id: order.id,
payableBillCode: order.payableBillCode,
ticketPlans: order.ticketPlans.map(plan => ({
planTicketDate: plan.planTicketDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
taxRate: order.taxRate,
remark: plan.remark,
id: plan.id,
})),
})),
totalMergeTicketAmount: this.totalPlannedAmount, // Total amount for the merged bill
};
this.$emit('confirm', mergedReceiptData);
this.dialogVisible = false;
},
handleCancel() {
this.dialogVisible = false;
this.resetForm();
},
resetForm() {
this.form = {
ticketBillType: 'FROM_PAYABLE',
vendorName: '',
ticketTime: null,
vendorTicketTime: null,
};
this.payableOrdersWithPlans = [];
this.currentPayableOrderIndexForPlan = -1;
this.loadingTicketPlans = false;
},
handleOpenTicketPlanSelector(row, index) {
this.planTitle = `选择收票计划 - ${row.payableBillCode}`;
this.choosePayable = row;
this.currentPayableOrderIndexForPlan = index;
this.isTicketPlanSelectorOpen = true;
console.log(this.choosePayable.id)
},
handleTicketPlanConfirm(updatedPlans) {
// Update the ticket plans for the specific order
if (this.currentPayableOrderIndexForPlan !== -1) {
this.$set(this.payableOrdersWithPlans[this.currentPayableOrderIndexForPlan], 'ticketPlans', updatedPlans);
}
this.isTicketPlanSelectorOpen = false;
this.currentPayableOrderIndexForPlan = -1;
},
calculateOrderCurrentTicketAmount(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.ticketPlans) {
return order.ticketPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentTicketRate(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.ticketPlans && order.unInvoicedAmount >= 0) {
const currentAmount = this.calculateOrderCurrentTicketAmount(orderId);
return this.$calc.mul(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ),100);
}
return 0;
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,317 @@
<template>
<div class="dialog-body">
<el-divider content-position="left">付款计划</el-divider>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSavePaymentPlan"
style="margin-bottom: 10px;">
保存付款计划
</el-button>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSyncToTicketPlan"
style="margin-bottom: 10px; margin-left: 10px;">
同步至发票计划
</el-button>
<el-button v-else type="primary" size="mini" @click="isEditing=true"
style="margin-bottom: 10px;">
编辑
</el-button>
<el-table :data="paymentPlans" border @selection-change="selectPlan" ref="paymentPlanTable">
<el-table-column type="selection" width="50" align="center" :selectable="selectableRow"/>
<el-table-column label="序号" type="index" width="50" align="center"></el-table-column>
<el-table-column label="预计付款时间" align="center" width="200">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planPaymentDate" type="datetime" style="width: 180px"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="预计付款金额" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planAmount"
:precision="2"
:step="100"
:min="0.01"
:readonly="!scope.row.detailId"
:max="totalPriceWithTax"
@change="handleAmountChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="应付比例(%)" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planRate"
:precision="2"
:step="1"
:min="0.01"
:max="100"
:readonly="!scope.row.detailId"
@change="handleRateChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAddPaymentPlanRow"
>增加下行
</el-button>
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDeletePaymentPlanRow(scope.$index)"
:disabled="paymentPlans.length === 1 || scope.row.status === 'paid'"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- <div class="total-info">-->
<!-- <span>应付单未付款金额: <el-tag type="info">{{ totalUnpaidAmount.toFixed(2) }}</el-tag></span>-->
<!-- <span style="margin-left: 20px;">计划付款总金额: <el-tag type="success">{{-->
<!-- totalPlannedAmount.toFixed(2)-->
<!-- }}</el-tag></span>-->
<!-- </div>-->
</div>
</template>
<script>
import {getPaymentPlan, updatePaymentPlan, syncToTicketPlan} from "@/api/finance/payable";
import {isNumberStr} from "@/utils";
export default {
name: "PaymentPlanSelector",
props: {
payableData: {
type: Object,
default: () => {
}
},
isInitEdit: {
type: Boolean,
default: false
},
selectedPlans: {
type: Array,
default: () => []
}
},
data() {
return {
selectedPlan:[],
isEditing: false,
loading: false,
paymentPlans: [],
};
},
computed: {
title() {
return `选择付款计划 - ${this.payableData.payableBillCode}`;
},
totalPlannedAmount() {
return this.paymentPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
},
totalUnpaidAmount() {
return this.payableData.unpaidAmount || 0;
},
totalPriceWithTax() {
return this.payableData.totalPriceWithTax || 0;
}
},
watch: {
'payableData.id': {
handler(newVal, oldVal) {
if (newVal) {
this.fetchPaymentPlans(newVal)
}
},
immediate: true
},
isInitEdit: {
handler(newVal) {
if (newVal) {
this.isEditing = newVal
}
},
immediate: true
}
},
methods: {
isNumberStr,
selectableRow(row, index){
return !row.detailId;
},
selectPlan( selection){
this.selectedPlan=selection
},
fetchPaymentPlans(payableId) {
if (this.payableData && payableId) {
getPaymentPlan(payableId).then(response => {
this.paymentPlans = response.data.map(item => ({
...item,
// Add a default status if not present, similar to EditForm.vue
status: item.status || 'pending'
}));
if (this.paymentPlans.length === 0) {
this.initDefaultPaymentPlan();
} else {
this.$nextTick(() => {
this.paymentPlans.forEach(plan => {
const isSelected = this.selectedPlans.some(selected => selected.id === plan.id);
if (isSelected) {
this.$refs.paymentPlanTable.toggleRowSelection(plan, true);
}
});
});
}
})
} else {
this.initDefaultPaymentPlan();
}
},
initDefaultPaymentPlan() {
// Default to a single plan covering the unpaid amount if no initial plans
this.paymentPlans = [{
planPaymentDate: null,
planAmount: this.totalUnpaidAmount,
planRate: 100,
remark: '',
status: 'pending' // Default status
}];
},
handleSavePaymentPlan() {
if (!this.validatePaymentPlans()) {
return;
}
if (!this.validatePaymentPlanTotals()) {
return;
}
for (let i = 0; i < this.paymentPlans.length; i++) {
const plan = this.paymentPlans[i];
if (!plan.planPaymentDate) {
this.$modal.msgError(`${i + 1} 行付款计划的预计付款时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount === '' || plan.planAmount < 0) {
this.$modal.msgError(`${i + 1} 行付款计划的预计付款金额不能为空。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行付款计划的应付比例不能为空。`);
return;
}
}
updatePaymentPlan(this.payableData.id, this.paymentPlans).then(() => {
this.$modal.msgSuccess("保存成功");
this.fetchPaymentPlans(this.payableData.id); // Re-fetch using the correct method and ID
});
},
handleSyncToTicketPlan() {
this.$modal.confirm('是否确认同步付款计划至发票计划?').then(() => {
return syncToTicketPlan(this.payableData.id);
}).then(() => {
this.$modal.msgSuccess("同步成功");
}).catch(() => {});
},
handleAddPaymentPlanRow() {
this.paymentPlans.push({
planPaymentDate: null,
planAmount: 0,
planRate: 0,
remark: '',
status: 'pending' // Default status
});
},
handleDeletePaymentPlanRow(index) {
if (this.paymentPlans.length === 1) {
this.$modal.msgError("至少需要保留一条付款计划。");
return;
}
// Only allow deletion if not 'paid' or status is not set (newly added)
if (this.paymentPlans[index].status === 'paid') {
this.$modal.msgError("已付款的计划不能删除。");
return;
}
this.paymentPlans.splice(index, 1);
},
handleAmountChange(row) {
if (this.totalPriceWithTax === 0) {
row.planRate = 0;
return;
}
row.planRate = this.$calc.mul((this.$calc.div(row.planAmount, this.totalPriceWithTax,4)), 100);
},
handleRateChange(row) {
row.planAmount = this.$calc.div(this.$calc.mul(this.totalPriceWithTax , row.planRate),100);
},
validatePaymentPlanTotals() {
const totalAmount = this.paymentPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
const totalRate = this.paymentPlans.reduce((sum, plan) => sum + (plan.planRate || 0), 0);
if (totalAmount !== this.totalPriceWithTax) {
this.$modal.msgError(`预计付款金额之和应该等于应付总金额[${this.totalPriceWithTax}]`);
return false;
}
return true;
},
validatePaymentPlans() {
if (this.paymentPlans.length === 0) {
this.$modal.msgError("请至少添加一条付款计划。");
return false;
}
const totalPlannedAmount = this.paymentPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
for (let i = 0; i < this.paymentPlans.length; i++) {
const plan = this.paymentPlans[i];
if (!plan.planPaymentDate) {
this.$modal.msgError(`${i + 1} 行付款计划的预计付款时间不能为空。`);
return false;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`${i + 1} 行付款计划的预计付款金额必须大于0。`);
return false;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行付款计划的应付比例不能为空。`);
return false;
}
}
return true;
}
}
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,293 @@
<template>
<div class="dialog-body">
<el-divider content-position="left">收票计划</el-divider>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSaveTicketPlan"
style="margin-bottom: 10px;">
保存收票计划
</el-button>
<el-button v-else type="primary" size="mini" @click="isEditing=true"
style="margin-bottom: 10px;">
编辑
</el-button>
<el-table :data="ticketPlans" border @selection-change="selectPlan" ref="ticketPlanTable">
<el-table-column type="selection" width="50" align="center" :selectable="selectableRow"/>
<el-table-column label="序号" type="index" width="50" align="center"></el-table-column>
<el-table-column label="预计收票时间" align="center" width="200">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planTicketDate" type="datetime" style="width: 180px"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="预计收票金额" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planAmount"
:precision="2"
:step="100"
:min="0.01"
:readonly="!scope.row.detailId"
:max="totalPriceWithTax"
@change="handleAmountChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="收票比例(%)" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planRate"
:precision="2"
:step="1"
:min="0.01"
:max="100"
:readonly="!scope.row.detailId"
@change="handleRateChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAddTicketPlanRow"
>增加下行
</el-button>
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDeleteTicketPlanRow(scope.$index)"
:disabled="ticketPlans.length === 1 || scope.row.status === 'received'"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import {getReceivingTicketPlan, updateReceivingTicketPlan} from "@/api/finance/payable";
import {isNumberStr} from "@/utils";
export default {
name: "ReceivingTicketPlan",
props: {
payableData: {
type: Object,
default: () => {
}
},
isInitEdit: {
type: Boolean,
default: false
},
selectedPlans: {
type: Array,
default: () => []
}
},
data() {
return {
selectedPlan:[],
isEditing: false,
loading: false,
ticketPlans: [],
};
},
computed: {
title() {
return `选择收票计划 - ${this.payableData.payableBillCode}`;
},
totalPlannedAmount() {
return this.ticketPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
},
totalUnpaidAmount() {
return this.payableData.unInvoicedAmount || 0;
},
totalPriceWithTax() {
return this.payableData.totalPriceWithTax || 0;
}
},
watch: {
'payableData.id': {
handler(newVal, oldVal) {
if (newVal) {
this.fetchTicketPlans(newVal)
}
},
immediate: true
},
isInitEdit: {
handler(newVal) {
if (newVal) {
this.isEditing = newVal
}
},
immediate: true
}
},
methods: {
isNumberStr,
selectableRow(row, index){
return !row.detailId;
},
selectPlan( selection){
this.selectedPlan=selection
},
fetchTicketPlans(payableId) {
if (this.payableData && payableId) {
getReceivingTicketPlan(payableId).then(response => {
this.ticketPlans = response.data.map(item => ({
...item,
status: item.status || 'pending'
}));
if (this.ticketPlans.length === 0) {
this.initDefaultTicketPlan();
} else {
this.$nextTick(() => {
this.ticketPlans.forEach(plan => {
const isSelected = this.selectedPlans.some(selected => selected.id === plan.id);
if (isSelected) {
this.$refs.ticketPlanTable.toggleRowSelection(plan, true);
}
});
});
}
})
} else {
this.initDefaultTicketPlan();
}
},
initDefaultTicketPlan() {
this.ticketPlans = [{
planTicketDate: null,
planAmount: this.totalPriceWithTax,
planRate: 100,
remark: '',
status: 'pending'
}];
},
handleSaveTicketPlan() {
if (!this.validateTicketPlans()) {
return;
}
if (!this.validateTicketPlanTotals()) {
return;
}
for (let i = 0; i < this.ticketPlans.length; i++) {
const plan = this.ticketPlans[i];
if (!plan.planTicketDate) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount === '' || plan.planAmount < 0) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票金额不能为空。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行收票计划的收票比例不能为空。`);
return;
}
}
updateReceivingTicketPlan(this.payableData.id, this.ticketPlans).then(() => {
this.$modal.msgSuccess("保存成功");
this.fetchTicketPlans(this.payableData.id);
});
},
handleAddTicketPlanRow() {
this.ticketPlans.push({
planTicketDate: null,
planAmount: 0,
planRate: 0,
remark: '',
status: 'pending'
});
},
handleDeleteTicketPlanRow(index) {
if (this.ticketPlans.length === 1) {
this.$modal.msgError("至少需要保留一条收票计划。");
return;
}
if (this.ticketPlans[index].status === 'received') {
this.$modal.msgError("已收票的计划不能删除。");
return;
}
this.ticketPlans.splice(index, 1);
},
handleAmountChange(row) {
if (this.totalPriceWithTax === 0) {
row.planRate = 0;
return;
}
row.planRate = this.$calc.mul((this.$calc.div(row.planAmount, this.totalPriceWithTax,4)), 100);
},
handleRateChange(row) {
row.planAmount = this.$calc.div(this.$calc.mul(this.totalPriceWithTax , row.planRate),100);
},
validateTicketPlanTotals() {
const totalAmount = this.ticketPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
if (totalAmount !== this.totalPriceWithTax) {
this.$modal.msgError(`预计收票金额之和应该等于应付总金额[${this.totalPriceWithTax}]`);
return false;
}
return true;
},
validateTicketPlans() {
if (this.ticketPlans.length === 0) {
this.$modal.msgError("请至少添加一条收票计划。");
return false;
}
for (let i = 0; i < this.ticketPlans.length; i++) {
const plan = this.ticketPlans[i];
if (!plan.planTicketDate) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票时间不能为空。`);
return false;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票金额必须大于0。`);
return false;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行收票计划的应付比例不能为空。`);
return false;
}
}
return true;
}
}
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,442 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="150px">
<!-- <el-form-item label="项目编号" prop="projectCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.projectCode"-->
<!-- placeholder="请输入项目编号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="采购-应付单编号" prop="payableBillCode">
<el-input
v-model="queryParams.payableBillCode"
placeholder="请输入应付单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="制造商名称" prop="vendorName">
<el-input
v-model="queryParams.vendorName"
placeholder="请输入制造商名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<!-- <el-form-item label="合同编号" prop="orderCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.orderCode"-->
<!-- placeholder="请输入合同编号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="出入库单号" prop="inventoryCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.inventoryCode"-->
<!-- placeholder="请输入出入库单号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="产品类型" prop="productType">
<el-select v-model="queryParams.productType" placeholder="请选择产品类型" clearable>
<el-option
v-for="dict in dict.type.product_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="付款状态" prop="paymentStatus">-->
<!-- <el-select v-model="queryParams.paymentStatus" placeholder="请选择付款状态" clearable>-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.payment_status"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="收票状态" prop="paymentStatus">-->
<!-- <el-select v-model="queryParams.paymentStatus" placeholder="请选择付款状态" clearable>-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.payment_status"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="生成时间">-->
<!-- <el-date-picker-->
<!-- v-model="dateRange"-->
<!-- style="width: 240px"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- type="daterange"-->
<!-- range-separator="-"-->
<!-- start-placeholder="开始日期"-->
<!-- end-placeholder="结束日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<el-form-item label="预计付款时间">
<el-date-picker
v-model="estimatedPaymentDateRange"
style="width: 350px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" >
<el-button type="primary" plain @click="handleMergeAndInitiatePayment" v-hasPermi="['finance:payable:mergePayment']">
合并发起付款单
</el-button>
</el-col>
<el-col :span="1.5" >
<el-button type="primary" plain @click="handleMergeAndInitiateReceipt" v-hasPermi="['inventory:inner:add']">
合并发起收票单
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="payableList" show-summary :summary-method="getSummaries" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" />
<!-- <el-table-column label="项目编号" align="center" prop="projectCode" width="120" />-->
<el-table-column label="项目名称" align="center" prop="projectName" width="260" />
<el-table-column label="采购-应付单编号" align="center" prop="payableBillCode" width="150" />
<!-- <el-table-column label="生成时间" align="center" prop="createTime" width="180"/>-->
<el-table-column label="预计付款时间" align="center" prop="planPaymentDate" width="180">
<template slot-scope="scope">
<span :style="getPaymentDateStyle(scope.row.planPaymentDate)">{{ scope.row.planPaymentDate }}</span>
</template>
</el-table-column>
<el-table-column label="预计付款金额" align="center" prop="planAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="该制造商是否有预付单" align="center" prop="hasAdvancePayment" width="150">
<template slot-scope="scope">
{{ scope.row.preResidueAmount == 0 ? '否' : '是' }}
</template>
</el-table-column>
<!-- <el-table-column label="预付金额" align="center" prop="advancePaymentAmount" width="120" />-->
<el-table-column label="制造商名称" align="center" prop="vendorName" width="150" />
<!-- <el-table-column label="合同编号" align="center" prop="orderCode" width="150" />-->
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150" />-->
<el-table-column label="产品类型" align="center" prop="productType" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<!-- <el-table-column label="未税总价" align="center" prop="totalPriceWithoutTax" width="120" />-->
<!-- <el-table-column label="税额" align="center" prop="taxAmount" width="120" />-->
<!-- <el-table-column label="付款状态" align="center" prop="paymentStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.unpaidAmount === scope.row.totalPriceWithTax ? '未生成' : scope.row.unpaidAmount === 0 ? '全部生成' : '部分生成' }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="生成付款单" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{scope.row.unpaidAmount===scope.row.totalPriceWithTax?'未生成':scope.row.unpaidAmount===0?'全部生成':'部分生成'}}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="收票状态" align="center" prop="invoiceStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.invoice_status" :value="scope.row.invoiceStatus"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="生成收票单" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <el-button-->
<!-- size="mini"-->
<!-- type="text"-->
<!-- @click="handleGenerateInvoice(scope.row)"-->
<!-- >生成收票单</el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="未付款金额(元)" align="center" prop="unpaidPaymentAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<el-table-column label="未收票金额(元)" align="center" prop="unreceivedTicketAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<!-- <el-table-column label="付款中金额" align="center" prop="payingAmount" width="120" />-->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['finance:payable:edit']"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
v-show="scope.row.unpaidPaymentAmount>0"
@click="handleGeneratedPayment(scope.row)"
v-hasPermi="['finance:payable:edit']"
>生成付款单</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
v-show="scope.row.unreceivedTicketAmount>0"
@click="handleGeneratedTicket(scope.row)"
v-hasPermi="['finance:payable:edit']"
>生成收票单</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 修改弹窗 -->
<edit-form :visible.sync="open" :data="selectedRow" @close="getList" />
<!-- 合并付款单弹窗 -->
<merge-payment-dialog :visible.sync="isMergePaymentDialogOpen" :payable-orders="selectedPayableRows" @confirm="confirmMergePayment" />
<!-- 合并收票单弹窗 -->
<merge-receipt-dialog :visible.sync="isMergeReceiptDialogOpen" :payable-orders="selectedPayableRows" @confirm="confirmMergeReceipt" />
</div>
</template>
<script>
import { listPayable, mergeAndInitiatePayment, mergeAndInitiateReceipt } from "@/api/finance/payable";
import EditForm from './components/EditForm.vue';
import MergePaymentDialog from './components/MergePaymentDialog.vue';
import MergeReceiptDialog from './components/MergeReceiptDialog.vue';
export default {
name: "Payable",
components: { EditForm, MergePaymentDialog, MergeReceiptDialog },
dicts: ['product_type', 'payment_status', 'invoice_status'],
data() {
return {
// (other data properties)
//
open: false,
//
selectedRow: {},
//
loading: true,
//
showSearch: true,
//
total: 0,
//
payableList: [],
//
dateRange: [],
//
estimatedPaymentDateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
payableBillCode: null,
vendorName: null,
orderCode: null,
inventoryCode: null,
productType: null,
paymentStatus: null,
createTimeStart: null,
createTimeEnd: null,
estimatedPaymentTimeStart: null,
estimatedPaymentTimeEnd: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
selectedPayableRows: [],
//
isMergePaymentDialogOpen: false,
//
isMergeReceiptDialogOpen: false
};
},
created() {
this.getList();
},
methods: {
/** 查询采购应付单列表 */
getList() {
this.loading = true;
if (null != this.dateRange && '' != this.dateRange) {
this.queryParams.createTimeStart = this.dateRange[0];
this.queryParams.createTimeEnd = this.dateRange[1];
}
if (null != this.estimatedPaymentDateRange && '' != this.estimatedPaymentDateRange) {
this.queryParams.estimatedPaymentTimeStart = this.estimatedPaymentDateRange[0];
this.queryParams.estimatedPaymentTimeEnd = this.estimatedPaymentDateRange[1];
}
listPayable(this.queryParams).then(response => {
this.payableList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.estimatedPaymentDateRange = [];
this.resetForm("queryForm");
this.queryParams.createTimeStart=null;
this.queryParams.createTimeEnd=null;
this.queryParams.estimatedPaymentTimeStart=null;
this.queryParams.estimatedPaymentTimeEnd=null;
this.handleQuery();
},
/** 修改按钮操作 */
handleUpdate(row) {
this.selectedRow = row;
this.open = true;
},
/** 删除按钮操作 */
handleDelete(row) {
this.$modal.confirm('是否确认删除采购应付单编号为"' + row.payableBillCode + '"的数据项?').then(function() {
return Promise.resolve();
}).then(() => {
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.selectedPayableRows = selection;
},
handleGeneratedPayment(row) {
this.selectedPayableRows=[row]
this.handleMergeAndInitiatePayment()
},
handleGeneratedTicket(row) {
this.selectedPayableRows=[row]
this.handleMergeAndInitiateReceipt()
},
/** 合并并发起付款单按钮操作 */
handleMergeAndInitiatePayment() {
if (this.selectedPayableRows.length === 0) {
this.$modal.msgWarning("请选择至少一条应付单进行合并操作");
return;
}
let every = this.selectedPayableRows.every(item=>item.planAmount>0);
if (!every){
this.$modal.msgWarning("温馨提示:您勾选的应付单中有已全部付款完成的应付单,请勿重复操作");
return;
}
let vendorLength = new Set(this.selectedPayableRows.map(item=>item.vendorCode)).size;
if (vendorLength > 1) {
this.$modal.msgWarning("温馨提示:您勾选的应付单中有不同供应商,合并发起付款单需为同一供应商,请重新勾选");
return;
}
this.isMergePaymentDialogOpen = true;
},
/** 确认合并付款单操作 */
confirmMergePayment(paymentData) {
mergeAndInitiatePayment(paymentData).then(() => {
this.$modal.msgSuccess("合并付款单发起成功");
this.isMergePaymentDialogOpen = false;
this.getList(); // Refresh the list
});
},
/** 合并并发起收票单按钮操作 */
handleMergeAndInitiateReceipt() {
if (this.selectedPayableRows.length === 0) {
this.$modal.msgWarning("请选择至少一条应付单进行合并操作");
return;
}
let every = this.selectedPayableRows.every(item=>item.planTicketAmount>0);
if (!every){
this.$modal.msgWarning("温馨提示:您勾选的应付单中有已全部收票完成的应付单,请勿重复操作");
return;
}
let vendorLength = new Set(this.selectedPayableRows.map(item=>item.vendorCode)).size;
if (vendorLength > 1) {
this.$modal.msgWarning("温馨提示:您勾选的应付单中有不同供应商,合并发起收票单需为同一供应商,请重新勾选");
return;
}
this.isMergeReceiptDialogOpen = true;
},
/** 确认合并收票单操作 */
confirmMergeReceipt(receiptData) {
mergeAndInitiateReceipt(receiptData).then(() => {
this.$modal.msgSuccess("合并收票单发起成功");
this.isMergeReceiptDialogOpen = false;
this.getList(); // Refresh the list
});
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
const values = data.map(item => Number(item[column.property]));
if (column.property === 'planAmount' || column.property === 'totalPriceWithTax') {
if (!values.every(value => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
sums[index] = this.formatCurrency(sums[index]);
} else {
sums[index] = 'N/A';
}
} else {
sums[index] = '';
}
});
return sums;
},
getPaymentDateStyle(dateStr) {
if (!dateStr) return {};
let planDate = new Date(dateStr).getTime();
let tenDaysLater = new Date().getTime() + 10 * 24 * 60 * 60 * 1000;
if (planDate <= tenDaysLater) {
return { color: '#ffba00' };
}
return {};
}
}
};
</script>

View File

@ -0,0 +1,282 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="计收状态" prop="chargeStatus">
<el-select
v-model="queryParams.chargeStatus"
placeholder="请输入计收状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.charge_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="付款状态" prop="paymentStatus">
<el-select
v-model="queryParams.paymentStatus"
placeholder="请输入付款状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_payment_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="收票状态" prop="ticketStatus">
<el-select
v-model="queryParams.ticketStatus"
placeholder="请输入收票状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_ticket_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['finance:report:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="reportList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目" align="center" prop="orderCode" width="180" v-if="columns.projectCode.visible || columns.projectName.visible" key="orderCode">
<el-table-column label="项目编号" align="center" prop="projectCode" width="120" v-if="columns.projectCode.visible" key="receivableWithTax"/>
<el-table-column label="项目名称" align="center" prop="projectName" width="120" v-if="columns.projectName.visible" key="receivableWithoutTax"/>
</el-table-column>
<!-- 毛利 -->
<el-table-column label="计收统计" align="center" v-if="columns.chargeStatus.visible || columns.chargedWithoutTax.visible || columns.grossProfit.visible ||columns.grossProfitRate.visible">
<el-table-column label="计收状态" align="center" prop="chargeStatus" width="100" v-if="columns.chargeStatus.visible" key="chargeStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.charge_status" :value="scope.row.chargeStatus"/>
</template>
</el-table-column>
<el-table-column label="已计收未税金额" align="center" prop="chargedWithoutTax" width="120" v-if="columns.chargedWithoutTax.visible" key="chargedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="毛利" align="center" prop="grossProfit" width="120" v-if="columns.grossProfit.visible" key="grossProfit" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="毛利率" align="center" prop="grossProfitRate" width="100" v-if="columns.grossProfitRate.visible" key="grossProfitRate"/>
</el-table-column>
<!-- <el-table-column label="业务计收时间" align="center" prop="bizChargeDate" width="180" v-if="columns.bizChargeDate.visible" key="bizChargeDate">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.bizChargeDate, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="财务计收时间" align="center" prop="financeChargeDate" width="180" v-if="columns.financeChargeDate.visible" key="financeChargeDate">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.financeChargeDate, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- 应付 -->
<el-table-column label="应付单" align="center">
<el-table-column label="应付含税金额" align="center" prop="payableWithTax" width="120" v-if="columns.payableWithTax.visible" key="payableWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应付未税金额" align="center" prop="payableWithoutTax" width="120" v-if="columns.payableWithoutTax.visible" key="payableWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应付税额" align="center" prop="payableTax" width="120" v-if="columns.payableTax.visible" key="payableTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 付款 -->
<el-table-column label="付款单" align="center">
<el-table-column label="付款状态" align="center" prop="paymentStatus" width="100" v-if="columns.paymentStatus.visible" key="paymentStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_payment_status" :value="scope.row.paymentStatus"/>
</template>
</el-table-column>
<el-table-column label="已付款含税金额" align="center" prop="paidWithTax" width="120" v-if="columns.paidWithTax.visible" key="paidWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已付款未税金额" align="center" prop="paidWithoutTax" width="120" v-if="columns.paidWithoutTax.visible" key="paidWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已付款税额" align="center" prop="paidTax" width="120" v-if="columns.paidTax.visible" key="paidTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="付款中含税金额" align="center" prop="payingWithTax" width="120" v-if="columns.payingWithTax.visible" key="payingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="付款中未税金额" align="center" prop="payingWithoutTax" width="120" v-if="columns.payingWithoutTax.visible" key="payingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="付款中税额" align="center" prop="payingTax" width="120" v-if="columns.payingTax.visible" key="payingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未付款含税金额" align="center" prop="unpaidWithTax" width="120" v-if="columns.unpaidWithTax.visible" key="unpaidWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未付款未税金额" align="center" prop="unpaidWithoutTax" width="120" v-if="columns.unpaidWithoutTax.visible" key="unpaidWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未付款税额" align="center" prop="unpaidTax" width="120" v-if="columns.unpaidTax.visible" key="unpaidTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 收票 -->
<el-table-column label="收票单" align="center">
<el-table-column label="收票状态" align="center" prop="ticketStatus" width="100" v-if="columns.ticketStatus.visible" key="ticketStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_ticket_status" :value="scope.row.ticketStatus"/>
</template>
</el-table-column>
<el-table-column label="已收票含税金额" align="center" prop="ticketedWithTax" width="120" v-if="columns.ticketedWithTax.visible" key="ticketedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收票未税金额" align="center" prop="ticketedWithoutTax" width="120" v-if="columns.ticketedWithoutTax.visible" key="ticketedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收票税额" align="center" prop="ticketedTax" width="120" v-if="columns.ticketedTax.visible" key="ticketedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中含税金额" align="center" prop="ticketingWithTax" width="120" v-if="columns.ticketingWithTax.visible" key="ticketingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中未税金额" align="center" prop="ticketingWithoutTax" width="120" v-if="columns.ticketingWithoutTax.visible" key="ticketingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中税额" align="center" prop="ticketingTax" width="120" v-if="columns.ticketingTax.visible" key="ticketingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票含税金额" align="center" prop="unticketedWithTax" width="120" v-if="columns.unticketedWithTax.visible" key="unticketedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票未税金额" align="center" prop="unticketedWithoutTax" width="120" v-if="columns.unticketedWithoutTax.visible" key="unticketedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票税额" align="center" prop="unticketedTax" width="120" v-if="columns.unticketedTax.visible" key="unticketedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"1/>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listReport } from "@/api/finance/report";
export default {
name: "PayableReport",
dicts: ['charge_status', 'report_payment_status', 'report_ticket_status'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
reportList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
orderCode: null,
projectCode: null,
projectName: null,
chargeStatus: null,
paymentStatus: null,
ticketStatus: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
columns: {
projectCode: { label: "项目编号", visible: true },
projectName: { label: "项目名称", visible: true },
chargeStatus: { label: "计收状态", visible: true },
chargedWithoutTax: { label: "已计收未税金额", visible: true },
grossProfit: { label: "毛利", visible: true },
grossProfitRate: { label: "毛利率", visible: true },
// bizChargeDate: { label: "", visible: true },
// financeChargeDate: { label: "", visible: true },
payableWithTax: { label: "应付含税总价", visible: true },
payableWithoutTax: { label: "应付未税总价", visible: true },
payableTax: { label: "应付税额", visible: true },
paymentStatus: { label: "付款状态", visible: true },
paidWithTax: { label: "已付款含税金额", visible: true },
paidWithoutTax: { label: "已付款未税金额", visible: true },
paidTax: { label: "已付款税额", visible: true },
payingWithTax: { label: "付款中含税金额", visible: true },
payingWithoutTax: { label: "付款中未税金额", visible: true },
payingTax: { label: "付款中税额", visible: true },
unpaidWithTax: { label: "未付款含税金额", visible: true },
unpaidWithoutTax: { label: "未付款未税金额", visible: true },
unpaidTax: { label: "未付款税额", visible: true },
ticketStatus: { label: "收票状态", visible: true },
ticketedWithTax: { label: "已收票含税金额", visible: true },
ticketedWithoutTax: { label: "已收票未税金额", visible: true },
ticketedTax: { label: "已收票税额", visible: true },
ticketingWithTax: { label: "收票中含税金额", visible: true },
ticketingWithoutTax: { label: "收票中未税金额", visible: true },
ticketingTax: { label: "收票中税额", visible: true },
unticketedWithTax: { label: "未收票含税金额", visible: true },
unticketedWithoutTax: { label: "未收票未税金额", visible: true },
unticketedTax: { label: "未收票税额", visible: true },
}
};
},
created() {
this.getList();
},
methods: {
/** 查询项目报表列表 */
getList() {
this.loading = true;
listReport(this.queryParams).then(response => {
this.reportList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 导出按钮操作 */
handleExport() {
this.download('finance/report/export', {
...this.queryParams
}, `report_${new Date().getTime()}.xlsx`)
}
}
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,539 @@
<template>
<el-dialog title="新增付款单" :visible.sync="internalVisible" width="1200px" @close="handleClose"
:close-on-click-modal="false" append-to-body>
<div style="max-height: 70vh;overflow: auto">
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="24">
<el-form-item label="制造商名称" prop="vendorCode">
<el-select
v-model="form.vendorCode"
placeholder="请选择制造商"
style="width:100%"
filterable
@change="handleVendorChange"
>
<el-option v-for="item in vendorOptions" :key="item.vendorCode" :label="item.vendorName"
:value="item.vendorCode"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- <el-row>-->
<!-- <el-col :span="24">-->
<!-- <el-form-item label="预计付款时间" prop="estimatedPaymentTime">-->
<!-- <el-date-picker-->
<!-- v-model="form.estimatedPaymentTime"-->
<!-- type="date"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- placeholder="选择日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<!-- </el-row>-->
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="paymentBillType">
<el-checkbox v-model="form.paymentBillType" true-label="PRE_PAYMENT" false-label="FROM_PAYABLE"></el-checkbox>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.paymentBillType === 'PRE_PAYMENT'">
<el-col :span="12">
<el-form-item label="含税总价(元)" prop="totalPriceWithTax">
<el-input-number v-model="form.totalPriceWithTax" :precision="2" :step="100"
style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="其它特别说明" prop="remark">
<el-input v-model="form.remark" type="textarea"
:rows="2"
style="width: 100%"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="附件" prop="fileId">
<file-upload :value="fileList" @file-list-changed="handleFileListChanged" :limit="1"
:file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf']"/>
</el-form-item>
</el-col>
</el-row>
<!-- Tables -->
<div v-if="form.vendorCode">
<div v-if="form.paymentBillType === 'FROM_PAYABLE'" class="table-container">
<h4>采购-应付单表</h4>
<el-table
ref="payableTable"
:data="payableList"
border
style="width: 100%"
@selection-change="handleSelectionChange"
row-key="id"
show-summary
:summary-method="getPayableSummary"
>
<el-table-column type="selection" width="55" reserve-selection></el-table-column>
<el-table-column label="采购-应付单编号" align="center" prop="payableBillCode" width="150"/>
<el-table-column label="预计付款时间" align="center" prop="planPaymentDate" width="180"/>
<el-table-column label="预期付款计划" align="center" width="100" prop="planAmount">
<template slot-scope="scope">
{{ calculateOrderCurrentPaymentAmount(scope.row) }}
</template>
</el-table-column>
<el-table-column label="预期付款比例" align="center" prop="projectCode" width="150">
<template slot-scope="scope">
{{ $calc.mul($calc.div(calculateOrderCurrentPaymentAmount(scope.row), scope.row.totalPriceWithTax,4),100) }}
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" width="150"/>
<el-table-column label="制造商名称" align="center" prop="vendorName" width="150"/>
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>-->
<!-- <el-table-column label="付款状态" align="center" prop="paymentStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.payment_status" :value="scope.row.paymentStatus"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120"/>
<el-table-column label="未付款金额" align="center" prop="unpaidPaymentAmount" width="120"/>
<!-- <el-table-column label="本次付款金额" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{ calculateOrderCurrentPaymentAmount(scope.row).toFixed(2) }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="本次付款比例" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{ calculateOrderCurrentPaymentRate(scope.row) }}%-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="已付款金额" align="center" prop="paidPaymentAmount" width="120"/>
<el-table-column label="付款中金额" align="center" prop="paidAmount" width="120">
<template slot-scope="scope">
{{ $calc.sub($calc.sub(scope.row.totalPriceWithTax, scope.row.paidPaymentAmount), scope.row.unpaidPaymentAmount) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100"
fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenPaymentPlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="form.paymentBillType === 'PRE_PAYMENT'" class="table-container">
<h4>订单列表</h4>
<el-table
ref="orderTable"
:data="orderList"
border
style="width: 100%"
@selection-change="handleSelectionChange"
@select="handleSelect"
@select-all="handleSelectAll"
row-key="id"
>
<el-table-column type="selection" width="55" reserve-selection></el-table-column>
<el-table-column prop="projectCode" label="项目编号"></el-table-column>
<el-table-column prop="projectName" label="项目名称"></el-table-column>
<el-table-column prop="createTime" label="下单时间"></el-table-column>
<el-table-column label="订单状态" prop="orderStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.order_status" :value="scope.row.orderStatus"/>
</template>
</el-table-column>
</el-table>
</div>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="loadTableData"
/>
</div>
<div v-else style="text-align: center; color: #909399; padding: 20px;">
请先选择制造商
</div>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<!-- <div v-if="form.paymentBillType === 'FROM_PAYABLE'" style="float: left; line-height: 36px;">-->
<!-- <span style="margin-right: 20px;">计划付款总金额: <el-tag type="success">{{-->
<!-- totalPlannedAmount.toFixed(2)-->
<!-- }}</el-tag></span>-->
<!-- </div>-->
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
<!-- Payment Plan Selector Dialog -->
<el-dialog :title="planTitle" :visible.sync="isPaymentPlanSelectorOpen" width="70%"
@close="isPaymentPlanSelectorOpen=false" append-to-body>
<payment-plan-selector
ref="planSelector"
:payable-data="choosePayable"
:selected-plans="choosePayable.paymentPlans"
@confirm="handlePaymentPlanConfirm"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isPaymentPlanSelectorOpen=false"> </el-button>
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
</el-dialog>
</template>
<script>
import {listAllVendor} from "@/api/base/vendor";
import {listPayableBills, listOrders} from "@/api/finance/payment";
import PaymentPlanSelector from "../../payable/components/PaymentPlan";
import FileUpload from "@/components/FileUpload";
export default {
name: "AddForm",
components: {PaymentPlanSelector, FileUpload},
props: {
visible: {
type: Boolean,
default: false
}
},
dicts:['order_status','payment_status'],
data() {
return {
internalVisible: this.visible,
vendorOptions: [],
payableList: [], // List for Standard/Payable Bills
orderList: [], // List for Prepayment/Purchase Orders
selectedRows: [],
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10
},
form: {
paymentBillType: 'FROM_PAYABLE', // Default to Normal (Not Prepayment)
vendorCode: null,
vendorName: null,
remark: null,
totalPriceWithTax: 0,
estimatedPaymentTime: null,
fileId: null
},
rules: {
vendorCode: [{required: true, message: "制造商名称不能为空", trigger: "change"}],
// estimatedPaymentTime: [{required: true, message: "", trigger: "change"}],
paymentBillType: [{required: true, message: "请选择是否预付", trigger: "change"}],
totalPriceWithTax: [{required: false, message: "预付金额不能为空", trigger: "blur"}]
},
// Plan Selector Data
planTitle: '',
isPaymentPlanSelectorOpen: false,
choosePayable: {},
currentPayableOrderIndexForPlan: -1,
fileList: [],
};
},
computed: {
totalPlannedAmount() {
if (this.form.paymentBillType === 'FROM_PAYABLE') {
// Calculate based on selected rows and their plans/defaults
return this.selectedRows.reduce((sum, row) => {
return sum + this.calculateOrderCurrentPaymentAmount(row);
}, 0);
}
return 0;
}
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.resetForm();
this.getVendorList();
}
},
internalVisible(newVal) {
this.$emit("update:visible", newVal);
},
'form.paymentBillType': function (val) {
// Toggle validation for Prepayment Amount
if (val === 'PRE_PAYMENT') {
this.rules.totalPriceWithTax[0].required = true;
} else {
this.rules.totalPriceWithTax[0].required = false;
}
this.queryParams.pageNum = 1;
this.selectedRows = [];
this.loadTableData();
}
},
methods: {
/** 获取厂商列表 */
getVendorList() {
listAllVendor().then(res => {
this.vendorOptions = res.data;
})
},
handleVendorChange(val) {
// Find name for the code
const vendor = this.vendorOptions.find(v => v.vendorCode === val);
if (vendor) {
this.form.vendorName = vendor.vendorName;
}
this.queryParams.pageNum = 1;
this.selectedRows = [];
this.loadTableData();
},
handleFileListChanged(fileList) {
this.fileList = fileList;
this.form.fileId = this.fileList.map(f => f.id).filter(id => !!id).join(',')
},
loadTableData() {
if (!this.form.vendorCode) return;
// Do not clear lists immediately to avoid flicker if desired, but here we reset for simplicity
this.payableList = [];
this.orderList = [];
const query = {
vendorCode: this.form.vendorCode,
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize,
unpaidPaymentAmount:-1
};
if (this.form.paymentBillType === 'FROM_PAYABLE') {
listPayableBills(query).then(res => {
this.payableList = (res.rows || []).map(item => {
const paymentPlans = item.paymentPlans ? [...item.paymentPlans] : [];
if (paymentPlans.length === 0 && item.lastPaymentPlanId) {
paymentPlans.push({
id: item.lastPaymentPlanId,
planAmount: item.planAmount,
planPaymentDate: item.planPaymentDate,
planRate: this.$calc.mul(this.$calc.div(item.planAmount, item.totalPriceWithTax, 4), 100)
});
}
return {
...item,
paymentPlans: paymentPlans, // Retain existing plans if any, otherwise empty
totalPriceWithTax: item.totalPriceWithTax || 0, // Ensure numeric for calculations
unpaidAmount: item.unpaidAmount || 0,
paidAmount: item.paidAmount || 0, // Ensure numeric for calculations
}
});
this.total = res.total;
});
} else if (this.form.paymentBillType === 'PRE_PAYMENT') {
query.orderStatus = '2';
listOrders(query).then(res => {
this.orderList = res.rows || [];
this.total = res.total;
});
}
this.$nextTick(() => {
if (this.$refs.payableTable) {
this.$refs.payableTable.clearSelection()
}
if (this.$refs.orderTable) {
this.$refs.orderTable.clearSelection()
}
})
},
handleSelect(selection, row) {
if (this.form.paymentBillType === 'PRE_PAYMENT') {
this.$refs.orderTable.clearSelection();
const isSelected = selection.some(item => item.orderCode === row.orderCode);
if (isSelected) {
this.$refs.orderTable.toggleRowSelection(row, true);
}
}
},
handleSelectAll(selection) {
if (this.form.paymentBillType === 'PRE_PAYMENT') {
this.$refs.orderTable.clearSelection();
this.$modal.msgWarning("预付单只能选择一个订单");
}
},
handleSelectionChange(selection) {
this.selectedRows = selection;
},
// --- Payment Plan Logic ---
handleOpenPaymentPlanSelector(row, index) {
this.planTitle = `选择付款计划 - ${row.payableBillCode}`;
this.choosePayable = row;
this.currentPayableOrderIndexForPlan = index;
this.isPaymentPlanSelectorOpen = true;
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan || [];
// Update the payment plans for the specific order
if (this.currentPayableOrderIndexForPlan !== -1) {
const row = this.payableList[this.currentPayableOrderIndexForPlan];
this.$set(row, 'paymentPlans', [...selectedPlans]);
}
this.isPaymentPlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新付款计划选择,共 ${selectedPlans.length}`);
},
handlePaymentPlanConfirm(updatedPlans) {
// This might be redundant if handleChooseConfirm does the job, checking Usage in MergePaymentDialog
},
calculateOrderCurrentPaymentAmount(order) {
if (order && order.paymentPlans && order.paymentPlans.length > 0) {
return order.paymentPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentPaymentRate(order) {
if (order && order.paymentPlans && order.paymentPlans.length > 0 && order.totalPriceWithTax) {
const currentAmount = this.calculateOrderCurrentPaymentAmount(order);
return this.$calc.mul((this.$calc.div(currentAmount, order.totalPriceWithTax, 4)), 100);
}
return 0;
},
getPayableSummary(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
if (column.label === '预期付款计划') {
const values = data.map(item => Number(this.calculateOrderCurrentPaymentAmount(item)));
if (!values.every(value => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
sums[index] = sums[index].toFixed(2);
} else {
sums[index] = '';
}
}
});
return sums;
},
handleClose() {
this.internalVisible = false;
},
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
if (this.form.paymentBillType === 'FROM_PAYABLE') {
if (this.selectedRows.length === 0) {
this.$message.warning("请选择至少一条应付单");
return;
}
// Process selected rows
const processedPayableOrders = this.selectedRows.map(order => {
let finalPlans = order.paymentPlans;
return {
id: order.id,
payableBillCode: order.payableBillCode,
taxRate: order.taxRate,
// Map plans to structure expected by backend (similar to MergePaymentDialog)
paymentPlans: finalPlans.map(plan => ({
id: plan.id, // ID if existing plan
planPaymentDate: plan.planPaymentDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
remark: plan.remark
}))
};
});
const submitData = {
paymentBillType: 'FROM_PAYABLE',
vendorCode: this.form.vendorCode,
estimatedPaymentTime: this.form.estimatedPaymentTime,
vendorName: this.form.vendorName,
remark: this.form.remark,
payableOrders: processedPayableOrders,
totalMergePaymentAmount: this.totalPlannedAmount,
fileId: this.form.fileId
};
this.$emit("submit", submitData);
} else {
// Prepayment logic
if (this.selectedRows.length > 1) {
this.$modal.msgWarning("只能选择一笔订单");
return;
}
if ((this.form.totalPriceWithTax ||0) <= 0) {
this.$message.warning("含税总价需要大于0");
return;
}
let order = this.selectedRows[0] ?? {};
const submitData = {
paymentBillType: 'PRE_PAYMENT',
orderCode: order.orderCode,
vendorCode: this.form.vendorCode,
projectCode:order.projectCode,
projectName:order.projectName,
remark: this.form.remark,
paymentTime: this.form.estimatedPaymentTime,
totalPriceWithTax:this.form.totalPriceWithTax,
fileId: this.form.fileId
};
this.$emit("submit", submitData);
}
}
});
},
resetForm() {
if (this.$refs.form) {
this.$refs.form.resetFields();
}
this.form = {
paymentBillType: 'FROM_PAYABLE',
vendorCode: null,
vendorName: null,
remark: null,
totalPriceWithTax: 0,
};
this.payableList = [];
this.orderList = [];
this.selectedRows = [];
this.queryParams = {
pageNum: 1,
pageSize: 10
};
this.total = 0;
}
}
};
</script>
<style scoped>
.table-container {
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,201 @@
<template>
<el-drawer
:title="detail.paymentBillType==='REFUND'?'退款单详情':'付款单详情'"
:visible.sync="visible"
direction="rtl"
size="70%"
:wrapper-closable="false"
@close="handleClose"
>
<div class="dialog-body" v-if="detail">
<div class="section">
<el-divider content-position="left">采购-付款单</el-divider>
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">采购-付款单编号: {{ detail.paymentBillCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">预计付款时间: {{ detail.paymentTime }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">制造商名称: {{ detail.vendorName }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">含税总价():<span :class="{'red-text':detail.paymentBillType==='REFUND'}"> {{ formatCurrency(detail.totalPriceWithTax) }}</span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">未税总价(): <span :class="{'red-text':detail.paymentBillType==='REFUND'}">{{ formatCurrency(detail.totalPriceWithoutTax) }}</span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">税额(): <span :class="{'red-text':detail.paymentBillType==='REFUND'}">{{ formatCurrency(detail.taxAmount) }} </span></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">备注:
<dict-tag :options="dict.type.payment_bill_type" :value="detail.paymentBillType"/>
</div>
</el-col>
<!-- <el-col :span="8">-->
<!-- <div class="detail-item">预付单剩余额度: {{ detail.preResidueAmount || '-' }}</div>-->
<!-- </el-col>-->
<el-col :span="8">
<div class="detail-item">实际付款时间: {{ detail.actualPaymentTime || '-'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">支付方式:
<dict-tag :options="dict.type.payment_method" :value="detail.paymentMethod"/>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">付款状态:
<dict-tag :options="dict.type.payment_status" :value="detail.paymentStatus"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批节点: {{ detail.approveNode|| '-' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批状态:
<dict-tag :options="dict.type.approve_status" :value="detail.approveStatus"/>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">审批通过时间: {{ detail.approveTime || '-'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">账户名称: {{ detail.payName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">银行账号: {{ detail.payBankNumber }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">银行开户行: {{ detail.payBankOpenAddress }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">银行行号: {{ detail.bankNumber }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<div class="detail-item">其它特别说明: {{ detail.remark }}</div>
</el-col>
<el-col :span="24">
<div class="detail-item">
<span style="margin-right: 10px">附件:</span>
<file-upload :value="detail.fileList" :disabled="true" :show-remove="false" :show-upload-btn="false"/>
</div>
</el-col>
</el-row>
</div>
</div>
<div class="section">
<el-divider content-position="left">采购-应付单</el-divider>
<el-table :data="detail.payableDetails">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column property="projectCode" label="项目编号"></el-table-column>
<el-table-column property="projectName" label="项目名称"></el-table-column>
<el-table-column property="payableBillCode" label="采购-应付单编号"></el-table-column>
<el-table-column property="totalPriceWithTax" label="含税总价" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column property="paymentAmount" label="本次付款金额">
<template slot-scope="scope">
<span>{{ formatCurrency(Math.abs(scope.row.paymentAmount)) }}</span>
</template>
</el-table-column>
<el-table-column property="paymentRate" label="本次付款比例">
<template slot-scope="scope">
<span >{{ Math.abs(scope.row.paymentRate) }}</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-drawer>
</template>
<script>
import FileUpload from "@/components/FileUpload";
export default {
name: "DetailDrawer",
components: {FileUpload},
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => {},
},
},
dicts:['payment_bill_type','approve_status','payment_status','payment_method'],
methods: {
downloadFile(attachment){
if (attachment){
const link = document.createElement('a');
link.href = this.getImageUrl(attachment.filePath);
link.download = attachment.fileName || 'receipt';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
},
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: calc(100vh - 50px); /* Adjust based on actual header/footer height */
overflow-y: auto;
padding: 0 20px 20px 20px; /* Adjust padding to match dialog-body style */
}
.details-container {
border: 1px solid #EBEEF5;
padding: 20px;
border-radius: 4px;
}
.detail-item {
display: flex;
border: 1px solid #EBEEF5;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
align-items: center;
}
.section {
margin-bottom: 20px;
}
.red-text{
color: red;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
line-height: 36px;
}
</style>

View File

@ -0,0 +1,544 @@
<template>
<el-dialog :title="titleText" :visible.sync="dialogVisible" width="900px" @close="handleClose">
<div v-if="loading" class="loading-spinner">
<i class="el-icon-loading"></i>
</div>
<div v-else class="receipt-dialog-body">
<div v-if="canUpload" class="upload-btn-container">
<el-button type="primary" icon="el-icon-upload" v-hasPermi="['finance:payment:upload']" @click="openUploadDialog">{{ titleText }}</el-button>
</div>
<el-timeline v-if="attachments.length > 0">
<el-timeline-item
v-for="attachment in attachments"
:key="attachment.id"
:timestamp="parseTime(attachment.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
placement="top"
>
<el-card>
<div class="receipt-card-content">
<div class="receipt-details">
<div class="detail-item">
<span class="item-label">支付方式</span>
<span class="item-value">
<dict-tag :options="dicts.payment_method" :value="paymentData.paymentMethod"/>
</span>
</div>
<div class="detail-item">
<span class="item-label">{{ titleText }}</span>
<div class="item-value">
<div class="image-wrapper">
<el-image
v-if="!isPdf(attachment.filePath)"
:src="getImageUrl(attachment.filePath)"
:preview-src-list="previewList"
style="width: 200px; height: 150px;"
fit="contain"
></el-image>
<div v-else-if="pdfUrls[attachment.filePath]" class="pdf-thumbnail-container" @click="openPdfPreview(pdfUrls[attachment.filePath])">
<iframe :src="pdfUrls[attachment.filePath]" width="100%" height="150px" frameborder="0"></iframe>
<div class="pdf-hover-overlay">
<i class="el-icon-zoom-in"></i>
</div>
</div>
<div v-if="attachment.delFlag === '2'" class="void-overlay"></div>
</div>
<el-row>
<el-col span="8">
<el-button
size="mini"
type="primary"
class="download-btn"
icon="el-icon-download"
@click="downloadFile(attachment)"
>下载{{ titleText }}</el-button>
</el-col>
<el-col span="8">
<el-button
size="mini"
type="primary"
class="download-btn"
icon="el-icon-remove"
v-if="paymentData.paymentBillType==='PRE_PAYMENT' && attachment.delFlag !== '2'"
v-hasPermi="['finance:attachment:delete']"
@click="deleteFile(paymentData)"
>作废{{ titleText }}
</el-button>
</el-col>
</el-row>
</div>
</div>
<div class="detail-item">
<span class="item-label">含税总价</span>
<span class="item-value">{{ paymentData.totalPriceWithTax }}</span>
</div>
<div class="detail-item">
<span class="item-label">备注</span>
<span class="item-value">{{ attachment.remark }}</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else :description="'暂无' + titleText"></el-empty>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
<!-- PDF Preview Dialog -->
<el-dialog
:visible.sync="pdfPreviewVisible"
width="80%"
top="5vh"
append-to-body
custom-class="pdf-preview-dialog"
>
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<!-- Upload Dialog -->
<el-dialog
:title="'上传' + titleText"
:visible.sync="uploadDialogVisible"
width="70vw"
append-to-body
@close="closeUploadDialog"
custom-class="upload-receipt-dialog"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form :model="uploadForm" ref="uploadForm" label-width="120px" size="medium" >
<el-form-item label="支付方式">
<el-select v-model="uploadForm.paymentMethod" disabled style="width: 100%;">
<el-option
v-for="dict in dicts.payment_method"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="paymentData.paymentBillType==='FROM_PAYABLE'?'回执单': '退款图'" required>
<div style="display: flex; flex-direction: column; align-items: flex-start;">
<el-upload
ref="upload"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:show-file-list="false"
accept=".jpg,.jpeg,.png,.pdf"
>
<el-button size="small" type="primary" icon="el-icon-upload2">{{ uploadForm.file ? '重新上传' : '点击上传' }}</el-button>
</el-upload>
<div class="el-upload__tip" style="line-height: 1.5; margin-top: 5px;">支持上传PNGJPGPDF文件格式</div>
</div>
</el-form-item>
<el-form-item label="含税总价">
<span>{{ paymentData.totalPriceWithTax }}</span>
</el-form-item>
<el-form-item label="确认含税总价" required>
<el-input v-model="uploadForm.confirmPrice" placeholder="请输入确认含税总价"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
type="textarea"
v-model="uploadForm.remark"
:rows="4"
placeholder="此处备注描述..."
></el-input>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<div class="upload-preview-container" style="height: 70vh;">
<div v-if="previewUrl" class="preview-content">
<img v-if="!isPreviewPdf" :src="previewUrl" class="preview-image" />
<iframe v-else :src="previewUrl" width="100%" height="100%" frameborder="0"></iframe>
</div>
<div v-else class="preview-placeholder">
<div class="placeholder-icon">
<i class="el-icon-picture"></i>
</div>
<div class="placeholder-text">点击图片进入预览</div>
</div>
</div>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitNewUpload"></el-button>
<el-button @click="closeUploadDialog"></el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import {deleteFile, getPaymentAttachments, uploadPaymentAttachment} from "@/api/finance/payment";
import request from '@/utils/request';
export default {
name: "ReceiptDialog",
props: {
visible: {
type: Boolean,
default: false,
},
paymentData: {
type: Object,
default: () => {},
},
dicts: {
type: Object,
default: () => ({})
}
},
data() {
return {
loading: false,
attachments: [],
// Upload Dialog Data
uploadDialogVisible: false,
uploadForm: {
paymentMethod: '',
confirmPrice: '',
remark: '',
file: null
},
previewUrl: '',
isPreviewPdf: false,
// PDF Preview Data
pdfUrls: {},
pdfPreviewVisible: false,
currentPdfUrl: '',
};
},
computed: {
dialogVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit("update:visible", val);
},
},
previewList() {
return this.attachments
.filter(att => !this.isPdf(att.filePath))
.map(att => this.getImageUrl(att.filePath));
},
canUpload() {
if (!this.attachments || this.attachments.length === 0) {
return true;
}
return this.attachments.every(att => att.delFlag === '2');
},
titleText() {
return this.paymentData && this.paymentData.paymentBillType === 'REFUND' ? '退款图' : '回执单';
}
},
watch: {
visible(val) {
if (val && this.paymentData) {
this.fetchAttachments();
}
},
},
methods: {
fetchAttachments() {
if (!this.paymentData.id) return;
this.loading = true;
getPaymentAttachments(this.paymentData.id, { type: 'payment' })
.then(response => {
const data = response.data || [];
data.sort((a, b) => new Date(b.createTime) - new Date(a.createTime));
this.attachments = data;
this.loadPdfPreviews();
this.loading = false;
})
.catch(() => {
this.attachments = [];
this.loading = false;
});
},
loadPdfPreviews() {
this.attachments.forEach(att => {
if (this.isPdf(att.filePath) && !this.pdfUrls[att.filePath]) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: att.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
this.$set(this.pdfUrls, att.filePath, url);
}).catch(console.error);
}
});
},
openPdfPreview(url) {
if (!url) return;
this.currentPdfUrl = url;
this.pdfPreviewVisible = true;
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
deleteFile(paymentData) {
deleteFile(paymentData.id).then(() => {
this.fetchAttachments()
})
},
downloadFile(attachment) {
const link = document.createElement('a');
link.href = this.getImageUrl(attachment.filePath);
link.download = attachment.fileName || 'receipt';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
handleClose() {
this.attachments = [];
// Clean up object URLs
Object.values(this.pdfUrls).forEach(url => URL.revokeObjectURL(url));
this.pdfUrls = {};
},
// New Upload Dialog Methods
openUploadDialog() {
this.uploadForm = {
paymentMethod: this.paymentData.paymentMethod,
confirmPrice: '',
remark: '',
file: null
};
this.previewUrl = '';
this.isPreviewPdf = false;
this.uploadDialogVisible = true;
},
closeUploadDialog() {
this.uploadDialogVisible = false;
this.uploadForm.file = null;
this.previewUrl = '';
},
handleFileChange(file) {
const isLt2M = file.size / 1024 / 1024 < 2;
const isAcceptedType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.raw.type);
if (!isAcceptedType) {
this.$message.error('上传文件只能是 JPG/PNG/PDF 格式!');
return;
}
if (!isLt2M) {
this.$message.error('上传文件大小不能超过 2MB!');
return;
}
this.uploadForm.file = file.raw;
this.isPreviewPdf = file.raw.type === 'application/pdf';
this.previewUrl = URL.createObjectURL(file.raw);
},
handleFileRemove() {
this.uploadForm.file = null;
this.previewUrl = '';
},
submitNewUpload() {
if (!this.uploadForm.file) {
this.$message.warning("请选择要上传的文件");
return;
}
if (!this.uploadForm.confirmPrice) {
this.$message.warning("请输入确认含税总价");
return;
}
if (parseFloat(this.uploadForm.confirmPrice) !== parseFloat(this.paymentData.totalPriceWithTax)) {
this.$message.error("确认含税总价与原含税总价不一致");
return;
}
const formData = new FormData();
formData.append("file", this.uploadForm.file);
formData.append("relatedBillId", this.paymentData.id);
formData.append("remark", this.uploadForm.remark);
uploadPaymentAttachment(formData)
.then(response => {
this.$message.success("上传成功");
this.closeUploadDialog();
this.fetchAttachments();
})
.catch(error => {
this.$message.error("上传失败");
});
},
},
};
</script>
<style scoped>
.receipt-dialog-body {
max-height: 60vh;
overflow-y: auto;
}
.loading-spinner {
text-align: center;
font-size: 24px;
padding: 20px;
}
.receipt-card-content {
display: flex;
}
.receipt-details {
flex-grow: 1;
}
.detail-item {
display: flex;
margin-bottom: 12px;
font-size: 14px;
}
.item-label {
width: 80px;
color: #606266;
text-align: right;
margin-right: 15px;
flex-shrink: 0;
}
.item-value {
color: #303133;
}
.image-wrapper {
position: relative;
width: 200px;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #DCDFE6;
border-radius: 4px;
margin-bottom: 10px;
}
.void-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-30deg);
color: red;
font-size: 48px;
font-weight: bold;
opacity: 0.7;
pointer-events: none;
}
.download-btn {
display: block;
}
.upload-btn-container {
margin-bottom: 20px;
}
.pdf-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 200px;
height: 150px;
color: #606266;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
}
.pdf-placeholder:hover {
border-color: #409EFF;
color: #409EFF;
}
.pdf-placeholder .el-icon-document {
font-size: 48px;
margin-bottom: 5px;
}
/* New Dialog Styles */
.upload-preview-container {
width: 100%;
height: 300px;
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.preview-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-pdf {
display: flex;
flex-direction: column;
align-items: center;
font-size: 16px;
color: #606266;
}
.preview-pdf .el-icon-document {
font-size: 64px;
margin-bottom: 10px;
}
.preview-placeholder {
text-align: center;
color: #909399;
}
.placeholder-icon {
font-size: 64px;
margin-bottom: 10px;
color: #c0c4cc;
}
.placeholder-text {
font-size: 14px;
}
.pdf-thumbnail-container {
position: relative;
width: 100%;
height: 150px;
cursor: pointer;
}
.pdf-hover-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
color: #fff;
font-size: 24px;
pointer-events: none; /* Let clicks pass through to container, but this is overlay on top of iframe so actually we want it to capture clicks if iframe swallows them? */
/* Actually, if pointer-events is none, the click goes to the iframe and iframe swallows it. */
/* So we want pointer-events: auto on the overlay, or just rely on the container. */
/* If container has the click listener, and overlay covers everything, overlay needs to propagate click or handle it. */
/* A simple way: make overlay clickable. */
pointer-events: auto;
}
.pdf-thumbnail-container:hover .pdf-hover-overlay {
opacity: 1;
}
</style>

View File

@ -0,0 +1,556 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="130px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="采购-付款单编号" prop="paymentBillCode">
<el-input
v-model="queryParams.paymentBillCode"
placeholder="请输入付款单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="制造商名称" prop="vendorName">
<el-input
v-model="queryParams.vendorName"
placeholder="请输入制造商名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="采购-应付单编号" prop="payableBillCode">
<el-input
v-model="queryParams.payableBillCode"
placeholder="请输入应付单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="备注" prop="paymentBillType">
<el-select v-model="queryParams.paymentBillType" placeholder="请选择备注" clearable>
<el-option v-for="dict in dict.type.payment_bill_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="支付方式" prop="paymentMethod">
<el-select v-model="queryParams.paymentMethod" style="width: 100%;">
<el-option
v-for="dict in dict.type.payment_method"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="付款状态" prop="paymentStatus">
<el-select v-model="queryParams.paymentStatus" placeholder="请选择付款状态" clearable>
<el-option
v-for="dict in dict.type.payment_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="approveStatus">
<el-select v-model="queryParams.approveStatus" placeholder="请选择审批状态" clearable>
<el-option
v-for="dict in dict.type.approve_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批节点" prop="approveNode">
<el-input
v-model="queryParams.approveNode"
placeholder="请输入审批节点"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="预计付款时间">
<el-date-picker
v-model="dateRangeEstimated"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item label="实际付款时间">
<el-date-picker
v-model="dateRangeActual"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item label="审批通过时间">
<el-date-picker
v-model="dateRangeApproval"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-download"
v-hasPermi="['finance:payment:export']"
size="mini"
@click="handleExport"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="paymentList" show-summary :summary-method="getSummaries">
<el-table-column label="采购-付款单编号" width="200" align="center" prop="paymentBillCode">
<template slot-scope="scope">
<a @click="handleDetail(scope.row)" class="link-type">{{ scope.row.paymentBillCode }}</a>
</template>
</el-table-column>
<el-table-column label="预计付款时间" align="center" prop="paymentTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.paymentTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="制造商名称" width="200" align="center" prop="vendorName"/>
<el-table-column label="含税总价(元)" align="center" width="200" prop="totalPriceWithTax">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax<0?{color:'red'}:{}">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" width="100" prop="paymentBillType">
<template slot-scope="scope">
<dict-tag :options="dict.type.payment_bill_type" :value="scope.row.paymentBillType"/>
</template>
</el-table-column>
<!-- <el-table-column label="预付单剩余额度" align="center" width="200" prop="preResidueAmount"/>-->
<el-table-column label="实际付款时间" align="center" prop="actualPaymentTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.actualPaymentTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="支付方式" align="center" width="100" prop="paymentMethod">
<template slot-scope="scope">
<dict-tag :options="dict.type.payment_method" :value="scope.row.paymentMethod"/>
</template>
</el-table-column>
<el-table-column label="付款状态" align="center" width="100" prop="paymentStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.payment_status" :value="scope.row.paymentStatus"/>
</template>
</el-table-column>
<el-table-column label="审批状态" align="center" width="100" prop="approveStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.approve_status" :value="scope.row.approveStatus"/>
</template>
</el-table-column>
<el-table-column label="审批通过时间" align="center" prop="approveTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" fixed="right" width="150" align="center" prop="approveNode"/>
<el-table-column label="操作" fixed="right" width="400" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-money"
v-show="scope.row.approveStatus==='2' "
@click="handleReceipt(scope.row)"
>{{ scope.row.paymentBillType === 'REFUND' ? '退款图' : '回执单' }}</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-position"
v-show="(scope.row.approveStatus==='0' || scope.row.approveStatus==='3') && (scope.row.paymentBillType==='FROM_PAYABLE'||scope.row.paymentBillType==='PRE_PAYMENT')"
@click="applyPayment(scope.row)"
>申请付款</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-left"
v-show="scope.row.paymentBillType==='FROM_PAYABLE' && scope.row.paymentStatus==='1' && scope.row.approveStatus==='0' "
@click="handleReturn(scope.row)"
>退回</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-right"
v-if="scope.row.paymentStatus === '2' && scope.row.refundStatus !== 'REFUND_APPLIED' && scope.row.paymentBillType !== 'REFUND'"
@click="handleApplyRefund(scope.row)"
>申请退款</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-right"
v-if="(scope.row.approveStatus === '2' || scope.row.approveStatus==='3') && (scope.row.paymentStatus==='1'||scope.row.paymentStatus==='3')"
@click="handleRevoke(scope.row)"
>撤销</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
v-hasPermi="['finance:payment:remove']"
v-show="(scope.row.approveStatus==='0' || scope.row.approveStatus==='3') && scope.row.paymentBillType==='PRE_PAYMENT' && scope.row.paymentStatus === '1' "
@click="handleDelete(scope.row.id)"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 详情抽屉 -->
<detail-drawer :visible.sync="detailOpen" :detail="detailData"></detail-drawer>
<!-- 新增弹窗 -->
<add-form :visible.sync="addOpen" :dicts="dict.type" @submit="handleAddSubmit"></add-form>
<!-- 回执单弹窗 -->
<receipt-dialog :visible.sync="receiptOpen" :payment-data="currentRow" :dicts="dict.type"></receipt-dialog>
<!-- 付款申请弹窗 -->
<el-dialog title="发起付款" :visible.sync="applyPaymentOpen" width="600px" append-to-body>
<el-form ref="applyPaymentForm" :model="applyPaymentForm" label-width="120px">
<el-form-item label="支付方式" prop="paymentMethod">
<el-select v-model="applyPaymentForm.paymentMethod" placeholder="请选择支付方式">
<el-option
v-for="dict in dict.type.payment_method"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="账户名称" prop="payName">
<el-input v-model="applyPaymentForm.payName" placeholder="请输入账户名称" />
</el-form-item>
<el-form-item label="银行账号" prop="payBankNumber">
<el-input v-model="applyPaymentForm.payBankNumber" placeholder="请输入银行账号" />
</el-form-item>
<el-form-item label="银行开户行" prop="payBankOpenAddress">
<el-input v-model="applyPaymentForm.payBankOpenAddress" placeholder="请输入银行开户行" />
</el-form-item>
<el-form-item label="银行行号" prop="bankNumber">
<el-input v-model="applyPaymentForm.bankNumber" placeholder="请输入银行行号" />
</el-form-item>
<el-form-item label="含税金额" prop="totalPriceWithTax">
<el-input v-model="applyPaymentForm.totalPriceWithTax" :disabled="true" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="applyPaymentOpen = false"> </el-button>
<el-button type="primary" @click="submitApplyPayment"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listPayment,
getPayment,
returnPayment,
addPaymentFromPayable,
applyPaymentApi,
applyRefundApprove,
applyRefund,
addPayment,
handleRevoke,
exportPayment, deletePayment
} from "@/api/finance/payment";
import { addDateRange } from "@/utils/ruoyi";
import DetailDrawer from "./components/DetailDrawer.vue";
import AddForm from "./components/AddForm.vue";
import ReceiptDialog from "./components/ReceiptDialog.vue";
export default {
name: "Payment",
components: {
DetailDrawer,
AddForm,
ReceiptDialog
},
dicts:['payment_bill_type','approve_status','payment_status', 'payment_method'],
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
paymentList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
paymentBillCode: null,
vendorCode: null,
payableBillCode: null,
paymentBillType: null,
paymentMethod: null,
approveStatus: null,
paymentStatus: null,
approveNode: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
dateRangeApproval: [],
dateRangeEstimated: [],
dateRangeActual: [],
//
detailOpen: false,
detailData: {},
//
addOpen: false,
//
receiptOpen: false,
currentRow: {},
//
applyPaymentOpen: false,
applyPaymentForm: {},
};
},
created() {
this.getList();
},
methods: {
addDateRange, // Mixin addDateRange function
/** 发起付款按钮操作 */
applyPayment(row){
this.applyPaymentForm = {
id: row.id,
paymentMethod: row.paymentMethod,
payName: row.payName,
payBankNumber: row.payBankNumber,
payBankOpenAddress: row.payBankOpenAddress,
bankNumber: row.bankNumber,
totalPriceWithTax: row.totalPriceWithTax
};
this.applyPaymentOpen = true;
},
/** 提交付款申请 */
submitApplyPayment() {
applyPaymentApi(this.applyPaymentForm).then(response => {
this.$modal.msgSuccess("付款申请提交成功");
this.applyPaymentOpen = false;
this.getList();
}).catch(error => {
console.error("付款申请提交失败", error);
this.$modal.msgError("付款申请提交失败");
});
},
getList() {
this.loading = true;
let query = { ...this.queryParams };
query = this.addDateRange(query, this.dateRangeApproval, 'ApproveTime');
query = this.addDateRange(query, this.dateRangeEstimated, 'PaymentTime');
query = this.addDateRange(query, this.dateRangeActual, 'ActualPaymentTime');
listPayment(query).then(response => {
this.paymentList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
handleExport() {
this.$modal.confirm('是否确认导出付款单数据?').then(() => {
return exportPayment(this.queryParams);
}).then(response => {
this.$download.download( response.msg)
})
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeApproval = [];
this.dateRangeEstimated = [];
this.dateRangeActual = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.addOpen = true;
},
/** 新增提交 */
handleAddSubmit(form) {
console.log('1111')
if (form.paymentBillType==='FROM_PAYABLE'){
addPaymentFromPayable(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("新增付款单失败", error);
this.$modal.msgError("新增失败");
});
}else{
addPayment(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("新增付款单失败", error);
this.$modal.msgError("新增失败");
});
}
},
/** 详情按钮操作 */
handleDetail(row) {
getPayment(row.id).then(response => {
this.detailData = response.data;
this.detailData.approveNode=row.approveNode;
this.detailOpen = true;
});
},
/** 回执单按钮操作 */
handleReceipt(row) {
this.currentRow = row;
this.receiptOpen = true;
},
/** 退回按钮操作 */
handleReturn(row) {
this.$modal.confirm('是否将该笔采购-付款单退回至采购-应付单').then(function() {
return returnPayment(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("退回成功");
}).catch(() => {});
},
applyRefundApprove(row) {
this.$modal.confirm('收款单退款申请,确认提交至财务审批吗?').then(function() {
return applyRefundApprove(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("发起成功");
}).catch(() => {});
},
/** 申请退款按钮操作 */
handleApplyRefund(row) {
this.$modal.confirm('收款单退款申请,确认提交至财务审批吗?').then(() => {
return applyRefund(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("申请退款成功");
}).catch(() => {});
},
handleDelete(id){
this.$modal.confirm('确认删除该笔预付单数据吗?').then(() => {
return deletePayment(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
handleRevoke(row) {
let msg=row.paymentBillType==='REFUND'?'是否将该笔采购-退款单撤销,将退款单撤销至付款单':'是否将该笔采购-付款单撤销,撤销至付款单未审批状态';
this.$modal.confirm(msg).then(() => {
return handleRevoke(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("撤销成功");
}).catch(() => {});
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
const values = data.map(item => Number(item[column.property]));
if (column.property === 'totalPriceWithTax') {
if (!values.every(value => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
sums[index] = this.formatCurrency(sums[index]);
} else {
sums[index] = 'N/A';
}
} else {
sums[index] = '';
}
});
return sums;
}
}
};
</script>

View File

@ -0,0 +1,415 @@
<template>
<el-dialog title="新增收票单" :visible.sync="internalVisible" width="80%" @close="handleClose" append-to-body>
<div class="dialog-body">
<el-form ref="form" :model="queryParams" :inline="true" label-width="120px">
<el-row>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="收票单类型" prop="ticketBillType">-->
<!-- &lt;!&ndash; Mapping receiptBillType to ticketBillType for consistency with the merge logic &ndash;&gt;-->
<!-- <el-select disabled v-model="form.receiptBillType" placeholder="请选择收票单类型" clearable>-->
<!-- &lt;!&ndash; Using dicts.receipt_bill_type if available, or dict.type.payment_bill_type if that was the intent.-->
<!-- The original code used dicts.receipt_bill_type. I'll stick to that but ensure it's passed or available.-->
<!-- The user instructions imply 'MergeReceiptDialog' logic which used payment_bill_type.-->
<!-- I will use the existing props 'dicts' &ndash;&gt;-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.payment_bill_type"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="8">
<el-form-item label="制造商名称">
<el-select
v-model="queryParams.vendorCode"
placeholder="请选择制造商"
filterable
clearable
@change="handleVendorChange"
>
<el-option
v-for="item in vendorOptions"
:key="item.vendorCode"
:label="item.vendorName"
:value="item.vendorCode"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="制造商开票时间" prop="vendorTicketTime">
<el-date-picker
v-model="form.vendorTicketTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider content-position="left">采购应付单表</el-divider>
<el-table
:data="payableOrdersWithPlans"
border
max-height="300px"
style="margin-bottom: 20px;"
row-key="id"
@selection-change="handleSelectionChange"
ref="table"
>
<el-table-column type="selection" :reserve-selection="true" width="55" align="center" />
<el-table-column label="采购-应付单编号" align="center" prop="payableBillCode" width="150"/>
<el-table-column label="预计收票时间" align="center" prop="planTicketDate" width="180"/>
<!-- <el-table-column label="收票计划" align="center" width="100" prop="planTicketAmount">-->
<!-- </el-table-column>-->
<el-table-column label="预期收票计划" align="center" width="120">
<template slot-scope="scope">
{{ formatCurrency(calculateOrderCurrentTicketAmount(scope.row.id).toFixed(2)) }}
</template>
</el-table-column>
<el-table-column label="预期收票比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentTicketRate(scope.row.id) }}%
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="制造商名称" align="center" prop="vendorName" />
<!-- <el-table-column label="合同编号" align="center" prop="orderCode" width="150"/>-->
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>-->
<!-- <el-table-column label="收票状态" align="center" prop="invoiceStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.invoice_status" :value="scope.row.invoiceStatus"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票金额(元)" align="center" prop="unInvoicedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收票金额(元)" align="center" prop="invoicedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中金额(元)" align="center" prop="invoicedAmount" width="120">
<template slot-scope="scope">
{{ formatCurrency($calc.sub($calc.sub(scope.row.totalPriceWithTax,scope.row.receivedTicketAmount),scope.row.unreceivedTicketAmount))}}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenTicketPlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div class="total-info">
<span style="margin-left: 20px;">计划收票总金额: <el-tag type="success">{{ formatCurrency(totalPlannedAmount.toFixed(2)) }}</el-tag></span>
<span>计划收票比例: <el-tag type="info">{{ totalPayableAmountWithTax ? this.$calc.mul(this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4),100) : 0 }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
<el-dialog :title="planTitle" :visible.sync="isTicketPlanSelectorOpen" width="70%"
@close="isTicketPlanSelectorOpen=false" append-to-body>
<receiving-ticket-plan
ref="planSelector"
:payable-data="choosePayable"
:selected-plans="choosePayable.ticketPlans"
@confirm="handleTicketPlanConfirm"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isTicketPlanSelectorOpen=false"> </el-button>
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
</el-dialog>
</template>
<script>
import { listAllVendor } from "@/api/base/vendor";
import { listPayable } from "@/api/finance/payable";
// Importing from the payable component directory as it likely shares logic
import ReceivingTicketPlan from "@/views/finance/payable/components/ReceivingTicketPlan.vue";
export default {
name: "AddForm",
components: { ReceivingTicketPlan },
props: {
visible: {
type: Boolean,
default: false
}
},
dicts:['payment_bill_type','invoice_status'],
data() {
return {
internalVisible: this.visible,
vendorOptions: [],
// Search params
queryParams: {
pageNum: 1,
pageSize: 10,
ticketBillType: 'FROM_PAYABLE', // Used for filtering if needed
vendorId: null,
invoiceStatus: null,
unreceivedTicketAmount:-1
},
// Form data for submission
form: {
receiptBillType: 'FROM_PAYABLE',
vendorTicketTime: null,
},
payableOrdersWithPlans: [], // Current page data
selectedRows: [], // Cross-page selection
total: 0,
// Plan Selector State
planTitle: '',
isTicketPlanSelectorOpen: false,
choosePayable: {},
currentPayableOrderIndexForPlan: -1,
loadingTicketPlans: false,
};
},
computed: {
// Calculate totals based on SELECTED rows
totalPayableAmountWithTax() {
return this.selectedRows.reduce((sum, order) => sum + (order.totalPriceWithTax || 0), 0);
},
totalPlannedAmount() {
return this.selectedRows.reduce((orderSum, order) => {
const orderPlansTotal = (order.ticketPlans || []).reduce((planSum, plan) => planSum + (plan.planAmount || 0), 0);
return orderSum + orderPlansTotal;
}, 0);
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.resetForm();
this.getVendorList();
}
},
internalVisible(newVal) {
this.$emit("update:visible", newVal);
}
},
methods: {
getVendorList() {
listAllVendor().then(res => {
this.vendorOptions = res.data || [];
})
},
handleVendorChange() {
this.queryParams.pageNum = 1;
this.selectedRows = [];
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
this.getList();
},
getList() {
if (!this.queryParams.vendorCode) {
this.payableOrdersWithPlans = [];
this.total = 0;
return;
}
this.loadingTicketPlans = true;
listPayable(this.queryParams).then(response => {
this.payableOrdersWithPlans = response.rows.map(order => {
// Initialize ticket plans
const ticketPlans = order.ticketPlans ? [...order.ticketPlans] : [];
if (ticketPlans.length === 0 && order.lastTicketPlanId) {
ticketPlans.push({
id: order.lastTicketPlanId,
planAmount: order.planTicketAmount,
taxRate: order.taxRate,
planTicketDate: order.planTicketDate,
planRate: order.totalPriceWithTax ? this.$calc.mul(this.$calc.div(order.planTicketAmount, order.totalPriceWithTax, 4), 100) : 0
});
}
return {
...order,
ticketPlans: ticketPlans,
totalPriceWithTax: order.totalPriceWithTax || 0,
unInvoicedAmount: order.unInvoicedAmount || 0,
invoicedAmount: order.invoicedAmount || 0,
};
});
this.total = response.total;
this.loadingTicketPlans = false;
});
},
handleSelectionChange(selection) {
this.selectedRows = selection;
},
handleClose() {
this.internalVisible = false;
this.resetForm();
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan || [];
const orderIndex = this.payableOrdersWithPlans.findIndex(o => o.id === this.choosePayable.id);
if (orderIndex === -1) {
this.$modal.msgError('找不到要更新的应付单');
return;
}
const currentOrder = this.payableOrdersWithPlans[orderIndex];
// Update view
this.$set(currentOrder, 'ticketPlans', [...selectedPlans]);
// Update selectedRows
const selectedIndex = this.selectedRows.findIndex(o => o.id === this.choosePayable.id);
if (selectedIndex !== -1) {
this.$set(this.selectedRows[selectedIndex], 'ticketPlans', [...selectedPlans]);
}
this.isTicketPlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新收票计划选择,共 ${selectedPlans.length}`);
},
handleOpenTicketPlanSelector(row, index) {
this.planTitle = `选择收票计划 - ${row.payableBillCode}`;
this.choosePayable = row;
this.currentPayableOrderIndexForPlan = index;
this.isTicketPlanSelectorOpen = true;
},
handleTicketPlanConfirm(updatedPlans) {
// Callback from ReceivingTicketPlan if it emits 'confirm' directly,
// but here we use handleChooseConfirm triggered by the dialog button.
// Current logic relies on ref access in handleChooseConfirm.
},
calculateOrderCurrentTicketAmount(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.ticketPlans) {
return order.ticketPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentTicketRate(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.ticketPlans && order.unInvoicedAmount >= 0) {
const currentAmount = this.calculateOrderCurrentTicketAmount(orderId);
return order.totalPriceWithTax ? this.$calc.mul(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ),100) : 0;
}
return 0;
},
handleSubmit() {
if (!this.queryParams.vendorCode) {
this.$modal.msgError('请选择制造商');
return;
}
if (!this.form.vendorTicketTime) {
this.$modal.msgError('请选择制造商开票时间');
return;
}
if (this.selectedRows.length === 0) {
this.$modal.msgError('请至少勾选一条应付单');
return;
}
const ordersToSubmit = this.selectedRows;
// Validate plans
for (const order of ordersToSubmit) {
if (!order.ticketPlans || order.ticketPlans.length === 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 至少需要一条收票计划`);
return;
}
for (const plan of order.ticketPlans) {
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中预计收票金额必须大于0。`);
return;
}
if (!plan.planTicketDate) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中预计收票时间不能为空。`);
return;
}
}
}
// Construct payload
const data = {
receiptBillType: this.form.receiptBillType,
vendorTicketTime: this.form.vendorTicketTime,
vendorId: this.queryParams.vendorId,
payableOrders: ordersToSubmit.map(order => ({
id: order.id,
payableBillCode: order.payableBillCode,
ticketPlans: order.ticketPlans.map(plan => ({
planTicketDate: plan.planTicketDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
taxRate: order.taxRate,
remark: plan.remark,
id: plan.id,
})),
})),
totalMergeTicketAmount: this.totalPlannedAmount,
};
this.$emit("submit", data);
// internalVisible will be handled by parent or by success callback usually,
// but here we just emit submit. The parent likely handles the API call.
// If we want to close immediately:
// this.internalVisible = false;
},
resetForm() {
this.form = {
receiptBillType: 'FROM_PAYABLE',
vendorTicketTime: null,
};
this.queryParams = {
pageNum: 1,
pageSize: 10,
ticketBillType: 'FROM_PAYABLE',
vendorId: null,
invoiceStatus: null,
unreceivedTicketAmount:-1
};
this.payableOrdersWithPlans = [];
this.selectedRows = [];
this.total = 0;
if (this.$refs.table) {
this.$refs.table.clearSelection();
}
}
}
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px;
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,144 @@
<template>
<el-drawer
:title="detail.ticketBillType==='RED_RUSH'?'红冲收票详情':'收票单详情'"
:visible.sync="visible"
direction="rtl"
size="70%"
@close="handleClose"
>
<div class="dialog-body" v-if="detail">
<div class="section">
<el-divider content-position="left">采购-收票单</el-divider>
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">采购-收票单编号: {{ detail.ticketBillCode }}</div>
</el-col>
<!-- <el-col :span="8">-->
<!-- <div class="detail-item">预计收票时间: {{ detail.ticketTime }}</div>-->
<!-- </el-col>-->
<el-col :span="16">
<div class="detail-item">制造商开票时间: {{ detail.vendorTicketTime }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">制造商名称: {{ detail.vendorName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">含税总价: <span :class="{'red-text':detail.ticketBillType==='RED_RUSH'}">{{ formatCurrency(detail.totalPriceWithTax) }}</span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">未税总价: <span :class="{'red-text':detail.ticketBillType==='RED_RUSH'}">{{formatCurrency(detail.totalPriceWithoutTax) }}</span></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">税额: <span :class="{'red-text':detail.ticketBillType==='RED_RUSH'}">{{ formatCurrency(detail.taxAmount) }} </span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">发票含税总价: <span :class="{'red-text':detail.ticketBillType==='RED_RUSH'}">{{ formatCurrency(detail.ticketPriceWithTax) || '-'}} </span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">发票未税总价: <span :class="{'red-text':detail.ticketBillType==='RED_RUSH'}">{{ (detail.ticketPriceWithoutTax) || '-'}} </span></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">发票税额: <span :class="{'red-text':detail.ticketBillType==='RED_RUSH'}">{{ formatCurrency(detail.ticketAmount) }} </span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">收票状态:
<dict-tag :options="dict.type.receipt_status" :value="detail.ticketStatus"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">备注: {{ detail.remark }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">审批节点: {{ detail.approveNode|| '-' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批状态:
<dict-tag :options="dict.type.approve_status" :value="detail.approveStatus"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批通过时间: {{ detail.approveTime || '-'}}</div>
</el-col>
</el-row>
</div>
</div>
<div class="section">
<el-divider content-position="left">采购-应付单</el-divider>
<el-table :data="detail.detailList">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column property="projectCode" label="项目编号"></el-table-column>
<el-table-column property="projectName" label="项目名称"></el-table-column>
<el-table-column property="payableBillCode" label="采购应付单编号"></el-table-column>
<el-table-column property="totalPriceWithTax" label="含税总价" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column property="paymentAmount" label="本次收票金额" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column property="paymentRate" label="本次收票比例(%)"></el-table-column>
</el-table>
</div>
</div>
</el-drawer>
</template>
<script>
export default {
name: "DetailDrawer",
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => null,
},
},
dicts:['receipt_bill_type','approve_status','receipt_status'],
methods: {
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: calc(100vh - 50px);
overflow-y: auto;
padding: 0 20px 20px 20px;
}
.details-container {
border: 1px solid #EBEEF5;
padding: 20px;
border-radius: 4px;
}
.detail-item {
display: flex;
border: 1px solid #EBEEF5;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
align-items: center;
}
.red-text{
color: red;
}
.section {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,645 @@
<template>
<el-dialog :title="titleText" :visible.sync="dialogVisible" width="900px" @close="handleClose">
<div v-if="loading" class="loading-spinner">
<i class="el-icon-loading"></i>
</div>
<div v-else class="receipt-dialog-body">
<div v-if="canUpload" class="upload-btn-container">
<el-button type="primary" icon="el-icon-upload" v-hasPermi="['finance:receipt:upload']" @click="openUploadDialog">{{ titleText }}</el-button>
</div>
<el-timeline v-if="attachments.length > 0">
<el-timeline-item
v-for="attachment in attachments"
:key="attachment.id"
:timestamp="parseTime(attachment.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
placement="top"
>
<el-card>
<div class="receipt-card-content">
<div class="receipt-details">
<div class="detail-item">
<span class="item-label">票据类型</span>
<span class="item-value"><dict-tag :options="dict.type.finance_invoice_type" :value="attachment.ticketType"/></span>
</div>
<div class="detail-item">
<span class="item-label">{{ titleText }}</span>
<div class="item-value">
<div class="image-wrapper">
<el-image
v-if="!isPdf(attachment.filePath)"
:src="getImageUrl(attachment.filePath)"
:preview-src-list="previewList"
style="width: 200px; height: 150px;"
fit="contain"
></el-image>
<div v-else-if="pdfUrls[attachment.filePath]" class="pdf-thumbnail-container" @click="openPdfPreview(pdfUrls[attachment.filePath])">
<iframe :src="pdfUrls[attachment.filePath]" width="100%" height="150px" frameborder="0"></iframe>
<div class="pdf-hover-overlay">
<i class="el-icon-zoom-in"></i>
</div>
</div>
<div v-if="attachment.delFlag === '2'" class="void-overlay"></div>
</div>
<el-button
size="mini"
type="primary"
class="download-btn"
icon="el-icon-download"
@click="downloadFile(attachment)"
>下载{{ titleText }}</el-button>
</div>
</div>
<div class="detail-item">
<span class="item-label">含税金额</span>
<span class="item-value">{{ formatCurrency(receiptData.totalPriceWithTax) }}</span>
</div>
<div class="detail-item">
<span class="item-label">发票含税金额</span>
<span class="item-value">{{ formatCurrency(attachment.priceWithTax||receiptData.ticketPriceWithTax) }}</span>
</div>
<div class="detail-item">
<span class="item-label">未税金额</span>
<span class="item-value">{{ formatCurrency(receiptData.totalPriceWithoutTax) }}</span>
</div>
<div class="detail-item">
<span class="item-label">发票未税金额</span>
<span class="item-value">{{ formatCurrency(attachment.priceWithoutTax||receiptData.ticketPriceWithoutTax) }}</span>
</div>
<div class="detail-item">
<span class="item-label">税额</span>
<span class="item-value">{{ formatCurrency(receiptData.taxAmount) }}</span>
</div>
<div class="detail-item">
<span class="item-label">发票税额</span>
<span class="item-value">{{formatCurrency( attachment.taxAmount || receiptData.ticketAmount) }}</span>
</div>
<div class="detail-item">
<span class="item-label">备注</span>
<span class="item-value">{{ attachment.remark }}</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else :description="'暂无' + titleText"></el-empty>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
<!-- PDF Preview Dialog -->
<el-dialog
:visible.sync="pdfPreviewVisible"
width="80%"
top="5vh"
append-to-body
custom-class="pdf-preview-dialog"
>
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<!-- Upload Dialog -->
<el-dialog
:title="'上传' + titleText"
:visible.sync="uploadDialogVisible"
width="70vw"
append-to-body
@close="closeUploadDialog"
custom-class="upload-receipt-dialog"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form :model="uploadForm" ref="uploadForm" :rules="rules" label-width="120px" size="medium" >
<el-form-item label="票据类型" prop="ticketType" required>
<dict-tag :options="dict.type.finance_invoice_type" :value="uploadForm.ticketType"/>
<!-- <el-select v-model="uploadForm.ticketType" placeholder="请选择票据类型">-->
<!-- <el-option-->
<!-- v-for="item in dict.type.finance_invoice_type"-->
<!-- :key="item.value"-->
<!-- :label="item.label"-->
<!-- :value="item.value"-->
<!-- ></el-option>-->
<!-- </el-select>-->
</el-form-item>
<el-form-item label="收票附件" prop="file" required>
<div style="display: flex; flex-direction: column; align-items: flex-start;">
<el-upload
ref="upload"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:show-file-list="false"
accept=".jpg,.jpeg,.png,.pdf"
>
<el-button size="small" type="primary" icon="el-icon-upload2">{{ uploadForm.file ? '重新上传' : '点击上传' }}</el-button>
</el-upload>
<div class="el-upload__tip" style="line-height: 1.5; margin-top: 5px;">支持上传PNGJPGPDF文件格式</div>
</div>
</el-form-item>
<el-form-item label="含税总价">
<span>{{ this.uploadType !== 'red_rush' ? receiptData.totalPriceWithTax : -receiptData.totalPriceWithTax}}</span>
</el-form-item>
<el-form-item label="发票含税总价" prop="ticketPriceWithTax" required>
<el-input v-model="uploadForm.ticketPriceWithTax" placeholder="请输入发票含税总价" @input="handlePriceWithTaxChange"></el-input>
</el-form-item>
<el-form-item label="未税总价">
<span>{{ this.uploadType !== 'red_rush'? receiptData.totalPriceWithoutTax : -receiptData.totalPriceWithoutTax }}</span>
</el-form-item>
<el-form-item label="发票未税总价" prop="ticketPriceWithoutTax" required>
<el-input v-model="uploadForm.ticketPriceWithoutTax" placeholder="请输入发票未税总价" @input="handlePriceWithoutTaxChange"></el-input>
</el-form-item>
<el-form-item label="税额">
<span>{{ this.uploadType !== 'red_rush' ? receiptData.taxAmount : -receiptData.taxAmount }}</span>
</el-form-item>
<el-form-item label="发票税额" prop="ticketAmount" required>
<el-input v-model="uploadForm.ticketAmount" placeholder="请输入发票税额" @input="handleAmountChange"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
type="textarea"
v-model="uploadForm.remark"
:rows="4"
placeholder="此处备注描述..."
></el-input>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<div class="upload-preview-container" style="height: 70vh;">
<div v-if="previewUrl" class="preview-content">
<img v-if="!isPreviewPdf" :src="previewUrl" class="preview-image" />
<iframe v-else :src="previewUrl" width="100%" height="100%" frameborder="0"></iframe>
</div>
<div v-else class="preview-placeholder">
<div class="placeholder-icon">
<i class="el-icon-picture"></i>
</div>
<div class="placeholder-text">点击图片进入预览</div>
</div>
</div>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitNewUpload"></el-button>
<el-button @click="closeUploadDialog"></el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import { getReceiptAttachments, uploadReceiptAttachment, applyRedRush } from "@/api/finance/receipt";
import request from '@/utils/request';
export default {
name: "ReceiptDialog",
props: {
visible: {
type: Boolean,
default: false,
},
receiptData: {
type: Object,
default: () => null,
},
dicts: {
type: Object,
default: () => ({})
},
autoUpload: {
type: Boolean,
default: false,
},
uploadType: {
type: String,
default: 'normal'
}
},
dicts:['finance_invoice_type'],
data() {
return {
loading: false,
attachments: [],
// Upload Dialog Data
uploadDialogVisible: false,
uploadForm: {
ticketPriceWithTax: '',
ticketPriceWithoutTax: '',
ticketAmount: '',
ticketType: '',
remark: '',
file: null
},
rules: {
ticketPriceWithTax: [
{ required: true, message: "请输入发票含税总价", trigger: "blur" },
{ validator: (rule, value, callback) => {
if (Number(value) !== Number(this.uploadType !== 'red_rush'? this.receiptData.totalPriceWithTax : -this.receiptData.totalPriceWithTax)) {
callback(new Error('发票含税总价必须等于含税总价'));
} else {
callback();
}
}, trigger: 'blur' }
],
ticketPriceWithoutTax: [
{ required: true, message: "请输入发票未税总价", trigger: "blur" },
{ validator: (rule, value, callback) => {
const total = Number(this.uploadType !== 'red_rush'? this.receiptData.totalPriceWithoutTax : -this.receiptData.totalPriceWithTax);
const val = Number(value);
if (total > 0 && val < 0) {
callback(new Error('发票未税总价不能小于0'));
} else if (total < 0 && val > 0) {
callback(new Error('发票未税总价不能大于0'));
} else {
callback();
}
}, trigger: 'blur' }
],
ticketAmount: [
{ required: true, message: "请输入发票税额", trigger: "blur" },
{ validator: (rule, value, callback) => {
const total = Number(this.uploadType !== 'red_rush'? this.receiptData.totalPriceWithTax : -this.receiptData.totalPriceWithTax);
const val = Number(value);
if (total > 0 && val < 0) {
callback(new Error('发票税额不能小于0'));
} else if (total < 0 && val > 0) {
callback(new Error('发票税额不能大于0'));
} else {
callback();
}
}, trigger: 'blur' }
]
},
previewUrl: '',
isPreviewPdf: false,
// PDF Preview Data
pdfUrls: {},
pdfPreviewVisible: false,
currentPdfUrl: '',
};
},
computed: {
dialogVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit("update:visible", val);
},
},
previewList() {
return this.attachments
.filter(att => !this.isPdf(att.filePath))
.map(att => this.getImageUrl(att.filePath));
},
canUpload() {
if (!this.attachments || this.attachments.length === 0) {
return true;
}
return this.attachments.every(att => att.delFlag === '2');
},
titleText() {
return '发票';
}
},
watch: {
visible(val) {
if (val && this.receiptData) {
if (this.uploadType !== 'red_rush') {
this.fetchAttachments();
}
if (this.autoUpload) {
setTimeout(() => {
this.openUploadDialog();
}, 300);
}
}
},
},
methods: {
fetchAttachments() {
if (!this.receiptData.id) return;
this.loading = true;
getReceiptAttachments(this.receiptData.id, { type: 'ticket' })
.then(response => {
const data = response.data || [];
data.sort((a, b) => new Date(b.createTime) - new Date(a.createTime));
this.attachments = data;
this.loadPdfPreviews();
this.loading = false;
})
.catch(() => {
this.attachments = [];
this.loading = false;
});
},
loadPdfPreviews() {
this.attachments.forEach(att => {
if (this.isPdf(att.filePath) && !this.pdfUrls[att.filePath]) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: att.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
this.$set(this.pdfUrls, att.filePath, url);
}).catch(console.error);
}
});
},
openPdfPreview(url) {
if (!url) return;
this.currentPdfUrl = url;
this.pdfPreviewVisible = true;
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
downloadFile(attachment) {
const link = document.createElement('a');
link.href = this.getImageUrl(attachment.filePath);
link.download = attachment.fileName || 'receipt';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
handleClose() {
this.attachments = [];
// Clean up object URLs
Object.values(this.pdfUrls).forEach(url => URL.revokeObjectURL(url));
this.pdfUrls = {};
},
// New Upload Dialog Methods
openUploadDialog() {
this.uploadForm = {
ticketPriceWithTax: this.uploadType !== 'red_rush' ? this.receiptData.totalPriceWithTax : -this.receiptData.totalPriceWithTax,
ticketPriceWithoutTax: this.uploadType !== 'red_rush' ? this.receiptData.totalPriceWithoutTax : -this.receiptData.totalPriceWithoutTax,
ticketAmount: this.uploadType !== 'red_rush' ? this.receiptData.taxAmount : -this.receiptData.taxAmount,
ticketType: this.receiptData.ticketType,
remark: '',
file: null
};
if (this.$refs.uploadForm) {
this.$refs.uploadForm.clearValidate();
}
this.previewUrl = '';
this.isPreviewPdf = false;
this.uploadDialogVisible = true;
},
handlePriceWithTaxChange(val) {
const withTax = Number(val) || 0;
const withoutTax = Number(this.uploadForm.ticketPriceWithoutTax) || 0;
this.uploadForm.ticketAmount = (withTax - withoutTax).toFixed(2);
},
handlePriceWithoutTaxChange(val) {
const withTax = Number(this.uploadForm.ticketPriceWithTax) || 0;
const withoutTax = Number(val) || 0;
this.uploadForm.ticketAmount = (withTax - withoutTax).toFixed(2);
},
handleAmountChange(val) {
const withTax = Number(this.uploadForm.ticketPriceWithTax) || 0;
const amount = Number(val) || 0;
this.uploadForm.ticketPriceWithoutTax = (withTax - amount).toFixed(2);
},
closeUploadDialog() {
this.uploadDialogVisible = false;
this.uploadForm.file = null;
this.previewUrl = '';
if (this.uploadType === 'red_rush') {
this.dialogVisible = false;
}
},
handleFileChange(file) {
const isLt2M = file.size / 1024 / 1024 < 2;
const isAcceptedType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.raw.type);
if (!isAcceptedType) {
this.$message.error('上传文件只能是 JPG/PNG/PDF 格式!');
return;
}
if (!isLt2M) {
this.$message.error('上传文件大小不能超过 2MB!');
return;
}
this.uploadForm.file = file.raw;
this.isPreviewPdf = file.raw.type === 'application/pdf';
this.previewUrl = URL.createObjectURL(file.raw);
},
handleFileRemove() {
this.uploadForm.file = null;
this.previewUrl = '';
},
submitNewUpload() {
this.$refs.uploadForm.validate(valid => {
if (valid) {
if (!this.uploadForm.file) {
this.$message.warning("请选择要上传的文件");
return;
}
const formData = new FormData();
formData.append("file", this.uploadForm.file);
formData.append("id", this.receiptData.id);
formData.append("remark", this.uploadForm.remark);
// formData.append("totalPriceWithTax", this.uploadForm.totalPriceWithTax);
formData.append("ticketPriceWithTax", this.uploadForm.ticketPriceWithTax);
// formData.append("totalPriceWithoutTax", this.uploadForm.totalPriceWithoutTax);
formData.append("ticketPriceWithoutTax", this.uploadForm.ticketPriceWithoutTax);
formData.append("ticketAmount", this.uploadForm.ticketAmount);
// formData.append("taxAmount", this.uploadForm.taxAmount);
formData.append("ticketType", this.uploadForm.ticketType);
if (this.uploadType === 'red_rush') {
applyRedRush(formData).then(response => {
this.$message.success("申请红冲成功");
this.closeUploadDialog();
this.$emit('refresh');
}).catch(error => {
this.$message.error("申请红冲失败");
});
} else {
uploadReceiptAttachment(formData)
.then(response => {
this.$message.success("上传成功");
this.closeUploadDialog();
this.fetchAttachments();
})
.catch(error => {
this.$message.error("上传失败");
});
}
}
});
},
},
};
</script>
<style scoped>
.receipt-dialog-body {
max-height: 60vh;
overflow-y: auto;
}
.loading-spinner {
text-align: center;
font-size: 24px;
padding: 20px;
}
.receipt-card-content {
display: flex;
}
.receipt-details {
flex-grow: 1;
}
.detail-item {
display: flex;
margin-bottom: 12px;
font-size: 14px;
}
.item-label {
width: 110px;
color: #606266;
text-align: right;
margin-right: 15px;
flex-shrink: 0;
}
.item-value {
color: #303133;
}
.image-wrapper {
position: relative;
width: 200px;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #DCDFE6;
border-radius: 4px;
margin-bottom: 10px;
}
.void-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-30deg);
color: red;
font-size: 48px;
font-weight: bold;
opacity: 0.7;
pointer-events: none;
}
.download-btn {
display: block;
}
.upload-btn-container {
margin-bottom: 20px;
}
.pdf-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 200px;
height: 150px;
color: #606266;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
}
.pdf-placeholder:hover {
border-color: #409EFF;
color: #409EFF;
}
.pdf-placeholder .el-icon-document {
font-size: 48px;
margin-bottom: 5px;
}
/* New Dialog Styles */
.upload-preview-container {
width: 100%;
height: 300px;
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.preview-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-pdf {
display: flex;
flex-direction: column;
align-items: center;
font-size: 16px;
color: #606266;
}
.preview-pdf .el-icon-document {
font-size: 64px;
margin-bottom: 10px;
}
.preview-placeholder {
text-align: center;
color: #909399;
}
.placeholder-icon {
font-size: 64px;
margin-bottom: 10px;
color: #c0c4cc;
}
.placeholder-text {
font-size: 14px;
}
.pdf-thumbnail-container {
position: relative;
width: 100%;
height: 150px;
cursor: pointer;
}
.pdf-hover-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
color: #fff;
font-size: 24px;
pointer-events: auto;
}
.pdf-thumbnail-container:hover .pdf-hover-overlay {
opacity: 1;
}
</style>

View File

@ -0,0 +1,431 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="150px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="采购-收票单编号" prop="ticketBillCode">
<el-input
v-model="queryParams.ticketBillCode"
placeholder="请输入收票单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="制造商名称" prop="vendorName">
<el-input
v-model="queryParams.vendorName"
placeholder="请输入制造商名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="采购-应付单编号" prop="payableBillCode">
<el-input
v-model="queryParams.payableBillCode"
placeholder="请输入应付单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="收票单类型" prop="receiptBillType">
<el-select v-model="queryParams.ticketBillType" placeholder="请选择收票单类型" clearable>
<el-option v-for="dict in dict.type.ticket_bill_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="收票状态" prop="ticketStatus">
<el-select v-model="queryParams.ticketStatus" placeholder="请选择收票状态" clearable>
<el-option
v-for="dict in dict.type.receipt_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="approveStatus">
<el-select v-model="queryParams.approveStatus" placeholder="请选择审批状态" clearable>
<el-option
v-for="dict in dict.type.approve_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批节点" prop="approveNode">
<el-input
v-model="queryParams.approveNode"
placeholder="请输入审批节点"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="审批通过时间">
<el-date-picker
v-model="dateRangeApproval"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<!-- <el-form-item label="预计收票时间">-->
<!-- <el-date-picker-->
<!-- v-model="dateRangeEstimated"-->
<!-- style="width: 240px"-->
<!-- value-format="yyyy-MM-dd"-->
<!-- type="daterange"-->
<!-- range-separator="-"-->
<!-- start-placeholder="开始日期"-->
<!-- end-placeholder="结束日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<el-form-item label="制造商开票时间">
<el-date-picker
v-model="dateRangeVendor"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="receiptList" show-summary :summary-method="getSummaries">
<el-table-column label="采购-收票单编号" align="center" prop="ticketBillCode" />
<!-- <el-table-column label="预计收票时间" align="center" prop="ticketTime" width="180">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.ticketTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="制造商开票时间" align="center" prop="vendorTicketTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.vendorTicketTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="制造商名称" align="center" prop="vendorName" />
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" >
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax<0?{color:'red'}:{}">{{ formatCurrency(scope.row.totalPriceWithTax) }}</span>
</template>
</el-table-column>
<el-table-column label="未税总价(元)" align="center" prop="totalPriceWithoutTax" >
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithoutTax<0?{color:'red'}:{}">{{ formatCurrency(scope.row.totalPriceWithoutTax) }}</span>
</template>
</el-table-column>
<el-table-column label="税额(元)" align="center" prop="taxAmount" >
<template slot-scope="scope">
<span :style="scope.row.taxAmount<0?{color:'red'}:{}">{{ formatCurrency(scope.row.taxAmount) }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="收票单类型" align="center" prop="ticketBillType" >-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.ticket_bill_type" :value="scope.row.ticketBillType"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="收票状态" align="center" prop="ticketStatus" >
<template slot-scope="scope">
<dict-tag :options="dict.type.receipt_status" :value="scope.row.ticketStatus"/>
</template>
</el-table-column>
<!-- <el-table-column label="实际收票时间" align="center" prop="actualTicketTime" width="180">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.actualTicketTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="审批状态" align="center" prop="approveStatus" >
<template slot-scope="scope">
<dict-tag :options="dict.type.approve_status" :value="scope.row.approveStatus"/>
</template>
</el-table-column>
<el-table-column label="审批通过时间" align="center" prop="approveTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
v-show="scope.row.approveStatus!=='0'"
@click="handleReceipt(scope.row)"
>{{scope.row.ticketBillType==='FROM_PAYABLE'?'发票' : '红冲'}}</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
v-show="scope.row.approveStatus==='0' && scope.row.ticketStatus==='1'"
@click="handleReturn(scope.row)"
>退回</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
v-show="scope.row.approveStatus==='0' && scope.row.ticketStatus==='1'"
@click="handleApplyInvoice(scope.row)"
>申请发票</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-left"
v-show="scope.row.approveStatus=='2' && scope.row.ticketStatus==='2'"
@click="handleApplyRedRush(scope.row)"
>申请红冲</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-left"
v-show="scope.row.approveStatus=='3'"
@click="handleRevoke(scope.row)"
>撤销</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 详情抽屉 -->
<detail-drawer :visible.sync="detailOpen" :detail="detailData"></detail-drawer>
<!-- 新增弹窗 -->
<add-form :visible.sync="addOpen" :dicts="dict.type" @submit="handleAddSubmit"></add-form>
<!-- 收票附件弹窗 -->
<receipt-dialog :visible.sync="receiptOpen" :receipt-data="currentRow" :dicts="dict.type" :auto-upload="autoUpload" :upload-type="uploadType" @refresh="getList"></receipt-dialog>
</div>
</template>
<script>
import {listReceipt, getReceipt, redRush, addReceipt, returnReceipt, revokeReceipt} from "@/api/finance/receipt";
import { addDateRange } from "@/utils/ruoyi";
import DetailDrawer from "./components/DetailDrawer.vue";
import AddForm from "./components/AddForm.vue";
import ReceiptDialog from "./components/ReceiptDialog.vue";
export default {
name: "Receipt",
components: {
DetailDrawer,
AddForm,
ReceiptDialog
},
dicts:['ticket_bill_type','approve_status','receipt_status'],
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
receiptList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
ticketBillCode: null,
vendorCode: null,
payableBillCode: null,
ticketBillType: null,
approveStatus: null,
ticketStatus: null,
approveNode: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
dateRangeApproval: [],
dateRangeEstimated: [],
dateRangeVendor: [],
//
detailOpen: false,
detailData: null,
//
addOpen: false,
//
receiptOpen: false,
currentRow: {},
autoUpload: false,
uploadType: 'normal'
};
},
created() {
this.getList();
},
methods: {
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
const values = data.map(item => Number(item[column.property]));
if (['totalPriceWithTax', 'totalPriceWithoutTax', 'taxAmount'].includes(column.property)) {
if (!values.every(value => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
sums[index] = sums[index].toFixed(2);
} else {
sums[index] = '0.00';
}
} else {
sums[index] = '';
}
});
return sums;
},
addDateRange,
getList() {
this.loading = true;
let query = { ...this.queryParams };
query = this.addDateRange(query, this.dateRangeApproval, 'ApproveTime');
query = this.addDateRange(query, this.dateRangeEstimated, 'ReceiptTime');
query = this.addDateRange(query, this.dateRangeVendor, 'VendorTicketTime');
listReceipt(query).then(response => {
this.receiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeApproval = [];
this.dateRangeEstimated = [];
this.dateRangeVendor = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.addOpen = true;
},
/** 新增提交 */
handleAddSubmit(form) {
addReceipt(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("新增收票单失败", error);
this.$modal.msgError("新增失败");
});
},
/** 详情按钮操作 */
handleDetail(row) {
getReceipt(row.id).then(response => {
this.detailData = response.data;
this.detailData.approveNode =row.approveNode;
this.detailOpen = true;
});
},
/** 收票附件按钮操作 */
handleReceipt(row) {
this.currentRow = row;
this.receiptOpen = true;
this.autoUpload = false;
this.uploadType = 'normal';
},
handleApplyInvoice(row) {
this.currentRow = row;
this.receiptOpen = true;
this.autoUpload = true;
this.uploadType = 'invoice';
},
handleApplyRedRush(row) {
this.currentRow = row;
this.receiptOpen = true;
this.autoUpload = true;
this.uploadType = 'red_rush';
},
/** 退回按钮操作 */
handleReturn(row) {
this.$modal.confirm('是否将该笔采购-收票单退回至采购-应付单').then(function() {
return returnReceipt(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("退回成功");
}).catch(() => {});
},
handleRevoke(row) {
const msg = row.ticketBillType === 'FROM_PAYABLE'
? '是否将该笔采购-收票单撤销,撤销后重新上传发票'
: '是否将该笔采购-红冲收票单撤销,将红冲收票单撤销至收票单';
this.$modal.confirm(msg).then(function() {
return revokeReceipt(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("撤销成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,326 @@
<template>
<el-drawer title="修改销售应收单" :visible.sync="internalVisible" size="70%" @close="handleClose">
<div class="dialog-body">
<!-- Part 1: Details -->
<div>
<el-divider content-position="left">销售-应收单</el-divider>
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>项目编号:</strong> {{ formData.projectCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>项目名称:</strong> {{ formData.projectName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>销售-应收单编号:</strong> {{ formData.receivableBillCode }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>生成时间:</strong> {{ formData.createTime }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>该进货商是否有预收单:</strong> {{ formData.remainingAmount == 0 ? '否' : '是' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>预收金额:</strong> {{ formatCurrency(formData.remainingAmount) }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>进货商名称:</strong> {{ formData.partnerName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>合同编号:</strong> {{ formData.orderCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>出库单号:</strong> {{ formData.inventoryCode }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>含税总价():</strong> {{ formatCurrency(formData.totalPriceWithTax) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>未税总价():</strong> {{ formatCurrency(formData.totalPriceWithoutTax) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>税额():</strong> {{ formatCurrency(formData.taxAmount) }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>未收款金额:</strong> {{ formatCurrency(formData.unreceivedAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>已收款金额:</strong> {{ formatCurrency(formData.receivedAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>收款中金额:</strong> {{ formatCurrency(this.$calc.sub(this.$calc.sub(formData.totalPriceWithTax,formData.receivedAmount),formData.unreceivedAmount)) }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>未开票金额:</strong> {{ formatCurrency(formData.uninvoicedAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>已开票金额:</strong> {{ formatCurrency(formData.invoicedAmount) }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>开票中金额:</strong> {{ formatCurrency(this.$calc.sub(this.$calc.sub(formData.totalPriceWithTax,formData.invoicedAmount),formData.uninvoicedAmount))}}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>生成收款单:</strong> {{ formData.totalPriceWithTax==formData.unreceivedAmount ? '未生成' : formData.unreceivedAmount == 0 ? '全部生成' : '部分生成'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>生成开票单:</strong> {{ formData.totalPriceWithTax==formData.uninvoicedAmount ? '未生成' : formData.uninvoicedAmount == 0 ? '全部生成' : '部分生成'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item" style="display: flex"><strong>产品类型:</strong>
<dict-tag :options="dict.type.product_type" :value="formData.productType"/>
</div>
</el-col>
</el-row>
</div>
</div>
<!-- Part 2: Tabs -->
<div style="padding: 20px">
<el-tabs v-model="activeTab">
<el-tab-pane label="明细" name="details">
<el-divider content-position="left">销售-收款单</el-divider>
<el-table :data="formData.detailList" style="width: 100%" show-summary :summary-method="getSummaries">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column prop="receivableDetailType" label="收款通道">
<template slot-scope="scope">
<dict-tag :options="dict.type.receivable_detail_type" :value="scope.row.receivableDetailType"/>
</template>
</el-table-column>
<el-table-column prop="actualReceiptTime" label="实际收款时间">
<template slot-scope="scope">
{{ scope.row.actualReceiptTime || '-' }}
</template>
</el-table-column>
<el-table-column prop="receiptAmount" label="本次收款金额" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="receiptRate" label="本次收款比例"></el-table-column>
<el-table-column prop="receiptStatus" label="收款状态">
<template slot-scope="scope">
<dict-tag :options="dict.type.receipt_bill_status" :value="scope.row.receiptStatus"/>
</template>
</el-table-column>
<el-table-column prop="receiptBillCode" label="销售-收款单编号"></el-table-column>
<el-table-column label="回执单/退款图">
<template slot-scope="scope">
<span v-if="scope.row.finAttachment">
<el-button type="text" size="mini" icon="el-icon-view" @click="handlePreview(scope.row.finAttachment)"></el-button>
<el-button type="text" size="mini" icon="el-icon-download"
@click="downloadFile(scope.row.finAttachment)">下载</el-button>
</span>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">销售-开票单</el-divider>
<el-table :data="formData.invoiceDetailList" style="width: 100%" show-summary :summary-method="getInvoiceSummaries">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column prop="receivableDetailType" label="开票通道">
<template slot-scope="scope">
<dict-tag :options="dict.type.invoice_detail_type" :value="scope.row.receivableDetailType"/>
</template>
</el-table-column>
<el-table-column prop="actualInvoiceTime" label="实际开票时间">
<template slot-scope="scope">
{{ scope.row.actualInvoiceTime || '-' }}
</template>
</el-table-column>
<el-table-column prop="invoiceAmount" label="本次开票金额" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="invoiceRate" label="本次开票比例"></el-table-column>
<el-table-column prop="invoiceStatus" label="开票状态">
<template slot-scope="scope">
<dict-tag :options="dict.type.invoice_bill_status" :value="scope.row.invoiceStatus"/>
</template>
</el-table-column>
<el-table-column prop="invoiceBillCode" label="销售-开票单编号"></el-table-column>
<el-table-column label="发票/红冲发票">
<template slot-scope="scope">
<span v-if="scope.row.attachments">
<!-- <el-button type="text" size="mini" icon="el-icon-view" @click="handlePreview(scope.row.attachments)"></el-button>-->
<el-button type="text" size="mini" icon="el-icon-download"
@click="downloadFile(scope.row.attachments)">下载</el-button>
</span>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="收款计划" name="receiptPlan">
<ReceiptPlan :isInitEdit=true @syncPlan="refreshInvoicePlan()" :receivableData="data"/>
</el-tab-pane>
<el-tab-pane label="开票计划" name="invoicePlan">
<InvoicePlan ref="invoicePlan" :isInitEdit=true :receivableData="data"/>
</el-tab-pane>
</el-tabs>
</div>
</div>
<GlobalFilePreview ref="filePreview" />
<!-- <div slot="footer" class="dialog-footer">-->
<!-- <el-button @click="handleClose"> </el-button>-->
<!-- <el-button type="primary" @click="handleSubmit"> </el-button>-->
<!-- </div>-->
</el-drawer>
</template>
<script>
import ReceiptPlan from './ReceiptPlan.vue';
import InvoicePlan from './InvoicePlan.vue';
import { getReceivable } from "@/api/finance/receivable";
import GlobalFilePreview from "@/components/GlobalFilePreview/index.vue";
export default {
name: "EditForm",
dicts: ['product_type','receipt_bill_status','receivable_detail_type', 'invoice_bill_status','invoice_detail_type'],
components: {
GlobalFilePreview,
ReceiptPlan,
InvoicePlan
},
props: {
visible: {
type: Boolean,
default: false
},
data: {
type: Object,
default: () => ({})
}
},
data() {
return {
internalVisible: this.visible, // Local copy of the visible prop
activeTab: 'details',
formData: {}
};
},
watch: {
visible(newVal) {
this.internalVisible = newVal; // Sync prop to local data
if (newVal && this.data.id) {
this.getDetails();
}
},
internalVisible(newVal) {
if (!newVal) {
this.formData = {};
}
this.$emit('update:visible', newVal); // Emit changes to parent
}
},
methods: {
handlePreview(attachment) {
this.$refs.filePreview.handlePreview(attachment);
},
downloadFile(attachments) {
attachments?.forEach(attachment => {
this.$refs.filePreview.downloadFile(attachment);
})
},
getDetails() {
getReceivable(this.data.id).then(res => {
this.formData = res.data;
});
},
refreshInvoicePlan(){
this.$refs.invoicePlan.fetchInvoicePlans(this.formData.id);
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
let receiptAmountSum = 0;
if (data && data.length > 0) {
receiptAmountSum = data.reduce((acc, item) => {
const value = Number(item.receiptAmount);
return acc + (isNaN(value) ? 0 : value);
}, 0);
}
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
} else if (column.property === 'receiptAmount') {
sums[index] = receiptAmountSum.toFixed(2);
} else if (column.property === 'receiptRate') {
if (this.formData.totalPriceWithTax && this.formData.totalPriceWithTax > 0) {
const ratio = this.$calc.div(receiptAmountSum , this.formData.totalPriceWithTax,4);
sums[index] = (ratio * 100).toFixed(2) + '%';
} else {
sums[index] = '0.00%';
}
} else {
sums[index] = '';
}
});
return sums;
},
getInvoiceSummaries(param) {
const { columns, data } = param;
const sums = [];
let invoiceAmountSum = 0;
if (data && data.length > 0) {
invoiceAmountSum = data.reduce((acc, item) => {
const value = Number(item.invoiceAmount);
return acc + (isNaN(value) ? 0 : value);
}, 0);
}
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
} else if (column.property === 'invoiceAmount') {
sums[index] = invoiceAmountSum.toFixed(2);
} else if (column.property === 'invoiceRate') {
if (this.formData.totalPriceWithTax && this.formData.totalPriceWithTax > 0) {
const ratio = this.$calc.div(invoiceAmountSum , this.formData.totalPriceWithTax,4);
sums[index] = (ratio * 100).toFixed(2) + '%';
} else {
sums[index] = '0.00%';
}
} else {
sums[index] = '';
}
});
return sums;
},
handleClose() {
this.internalVisible = false; // Close dialog locally
},
handleSubmit() {
this.handleClose();
},
}
};
</script>
<style scoped>.details-container {
border: 1px solid #EBEEF5;
padding: 20px;
border-radius: 4px;
}
.detail-item {
border: 1px solid #EBEEF5;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
}
.dialog-body {
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,293 @@
<template>
<div class="dialog-body">
<el-divider content-position="left">开票计划</el-divider>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSaveInvoicePlan"
style="margin-bottom: 10px;">
保存开票计划
</el-button>
<el-button v-else type="primary" size="mini" @click="isEditing=true"
style="margin-bottom: 10px;">
编辑
</el-button>
<el-table :data="invoicePlans" border @selection-change="selectPlan" ref="invoicePlanTable">
<el-table-column type="selection" width="50" align="center" :selectable="selectableRow"/>
<el-table-column label="序号" type="index" width="50" align="center"></el-table-column>
<el-table-column label="预计开票时间" align="center" width="200">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planInvoiceDate" type="datetime" style="width: 180px"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="预计开票金额" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planAmount"
:precision="2"
:step="100"
:min="0.01"
:readonly="!scope.row.detailId"
:max="totalPriceWithTax"
@change="handleAmountChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="开票比例(%)" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planRate"
:precision="2"
:step="1"
:min="0.01"
:max="100"
:readonly="!scope.row.detailId"
@change="handleRateChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAddInvoicePlanRow"
>增加下行
</el-button>
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDeleteInvoicePlanRow(scope.$index)"
:disabled="invoicePlans.length === 1 || scope.row.status === 'invoiced'"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import {getInvoicePlan, updateInvoicePlan} from "@/api/finance/receivable";
import {isNumberStr} from "@/utils";
export default {
name: "InvoicePlan",
props: {
receivableData: {
type: Object,
default: () => {
}
},
isInitEdit: {
type: Boolean,
default: false
},
selectedPlans: {
type: Array,
default: () => []
}
},
data() {
return {
selectedPlan:[],
isEditing: false,
loading: false,
invoicePlans: [],
};
},
computed: {
title() {
return `选择开票计划 - ${this.receivableData.receivableBillCode}`;
},
totalPlannedAmount() {
return this.invoicePlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
},
totalUninvoicedAmount() {
return this.receivableData.uninvoicedAmount || 0;
},
totalPriceWithTax() {
return this.receivableData.totalPriceWithTax || 0;
}
},
watch: {
'receivableData.id': {
handler(newVal, oldVal) {
if (newVal) {
this.fetchInvoicePlans(newVal)
}
},
immediate: true
},
isInitEdit: {
handler(newVal) {
if (newVal) {
this.isEditing = newVal
}
},
immediate: true
}
},
methods: {
isNumberStr,
selectableRow(row, index){
return !row.detailId;
},
selectPlan( selection){
this.selectedPlan=selection
},
fetchInvoicePlans(receivableId) {
if (this.receivableData && receivableId) {
getInvoicePlan(receivableId).then(response => {
this.invoicePlans = response.data.map(item => ({
...item,
status: item.status || 'pending'
}));
if (this.invoicePlans.length === 0) {
this.initDefaultInvoicePlan();
} else {
this.$nextTick(() => {
this.invoicePlans.forEach(plan => {
const isSelected = this.selectedPlans.some(selected => selected.id === plan.id);
if (isSelected) {
this.$refs.invoicePlanTable.toggleRowSelection(plan, true);
}
});
});
}
})
} else {
this.initDefaultInvoicePlan();
}
},
initDefaultInvoicePlan() {
this.invoicePlans = [{
planInvoiceDate: null,
planAmount: this.totalPriceWithTax,
planRate: 100,
remark: '',
status: 'pending'
}];
},
handleSaveInvoicePlan() {
if (!this.validateInvoicePlans()) {
return;
}
if (!this.validateInvoicePlanTotals()) {
return;
}
for (let i = 0; i < this.invoicePlans.length; i++) {
const plan = this.invoicePlans[i];
if (!plan.planInvoiceDate) {
this.$modal.msgError(`${i + 1} 行开票计划的预计开票时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount === '' || plan.planAmount < 0) {
this.$modal.msgError(`${i + 1} 行开票计划的预计开票金额不能为空。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行开票计划的开票比例不能为空。`);
return;
}
}
updateInvoicePlan(this.receivableData.id, this.invoicePlans).then(() => {
this.$modal.msgSuccess("保存成功");
this.fetchInvoicePlans(this.receivableData.id);
});
},
handleAddInvoicePlanRow() {
this.invoicePlans.push({
planInvoiceDate: null,
planAmount: 0,
planRate: 0,
remark: '',
status: 'pending'
});
},
handleDeleteInvoicePlanRow(index) {
if (this.invoicePlans.length === 1) {
this.$modal.msgError("至少需要保留一条开票计划。");
return;
}
if (this.invoicePlans[index].status === 'invoiced') {
this.$modal.msgError("已开票的计划不能删除。");
return;
}
this.invoicePlans.splice(index, 1);
},
handleAmountChange(row) {
if (this.totalPriceWithTax === 0) {
row.planRate = 0;
return;
}
row.planRate = this.$calc.mul((this.$calc.div(row.planAmount, this.totalPriceWithTax,4)), 100);
},
handleRateChange(row) {
row.planAmount = this.$calc.div(this.$calc.mul(this.totalPriceWithTax , row.planRate),100);
},
validateInvoicePlanTotals() {
const totalAmount = this.invoicePlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
if (totalAmount !== this.totalPriceWithTax) {
this.$modal.msgError(`预计开票金额之和应该等于应收总金额[${this.totalPriceWithTax}]`);
return false;
}
return true;
},
validateInvoicePlans() {
if (this.invoicePlans.length === 0) {
this.$modal.msgError("请至少添加一条开票计划。");
return false;
}
for (let i = 0; i < this.invoicePlans.length; i++) {
const plan = this.invoicePlans[i];
if (!plan.planInvoiceDate) {
this.$modal.msgError(`${i + 1} 行开票计划的预计开票时间不能为空。`);
return false;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`${i + 1} 行开票计划的预计开票金额必须大于0。`);
return false;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行开票计划的开票比例不能为空。`);
return false;
}
}
return true;
}
}
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,360 @@
<template>
<el-dialog title="合并发起开票单" :visible.sync="dialogVisible" width="80%" @close="handleClose" append-to-body>
<div class="dialog-body">
<el-form ref="form" :model="form" :inline="true" label-width="120px">
<el-row>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="开票单类型" prop="invoiceBillType">-->
<!-- <el-select disabled v-model="form.invoiceBillType" placeholder="请选择开票单类型" clearable>-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.receipt_bill_type"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="24">
<el-form-item label="进货商名称">
<el-input v-model="form.partnerName" style="width:400px" readonly/>
</el-form-item>
</el-col>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="预计开票时间" prop="invoiceTime">-->
<!-- <el-date-picker-->
<!-- v-model="form.invoiceTime"-->
<!-- type="date"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- placeholder="选择日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
</el-row>
</el-form>
<el-divider content-position="left">销售-应收单表</el-divider>
<el-table :data="receivableBillsWithPlans" border max-height="300px" style="margin-bottom: 20px;">
<el-table-column label="销售-应收单编号" align="center" prop="receivableBillCode" width="150"/>
<el-table-column label="预计开票时间" align="center" prop="planInvoiceDate" width="180"/>
<!-- <el-table-column label="开票计划" align="center" width="100" prop="planInvoiceAmount">-->
<!-- </el-table-column>-->
<el-table-column label="预期开票金额" align="center" width="120">
<template slot-scope="scope">
{{ formatCurrency(calculateOrderCurrentInvoiceAmount(scope.row.id).toFixed(2)) }}
</template>
</el-table-column>
<el-table-column label="预期开票比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentInvoiceRate(scope.row.id) }}%
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" width="150"/>
<el-table-column label="进货商名称" align="center" prop="partnerName" width="150"/>
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>-->
<!-- <el-table-column label="开票状态" align="center" prop="invoiceStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.invoice_status" :value="scope.row.invoiceStatus"/>
</template>
</el-table-column> -->
<el-table-column label="含税总价" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票金额" align="center" prop="uninvoicedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已开票金额" align="center" prop="invoicedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="开票中金额" align="center" prop="invoicedAmount" width="120">
<template slot-scope="scope">
{{ formatCurrency($calc.sub($calc.sub(scope.row.totalPriceWithTax, scope.row.invoicedAmount), scope.row.uninvoicedAmount)) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenInvoicePlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<div class="total-info">
<span style="margin-left: 20px;">计划开票总金额: <el-tag type="success">{{
formatCurrency(totalPlannedAmount.toFixed(2))
}}</el-tag></span>
<span>计划开票比例: <el-tag type="info">{{ this.$calc.mul(this.$calc.div(totalPlannedAmount,totalReceivableAmountWithTax,4),100) }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleConfirm"> </el-button>
</div>
<el-dialog :title="planTitle" :visible.sync="isInvoicePlanSelectorOpen" width="70%"
@close="isInvoicePlanSelectorOpen=false" append-to-body>
<invoice-plan
ref="planSelector"
:receivable-data="chooseReceivable"
:selected-plans="chooseReceivable.invoicePlans"
@confirm="handleInvoicePlanConfirm"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isInvoicePlanSelectorOpen=false"> </el-button>
<!-- <el-button type="primary" @click="handleConfirm" > </el-button>-->
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
<!-- 开票计划选择器弹窗 -->
</el-dialog>
</template>
<script>
import InvoicePlan from './InvoicePlan.vue';
export default {
name: "MergeInvoiceDialog",
components: {InvoicePlan},
dicts: ['invoice_status','receipt_bill_type'], // Add dicts for dict-tag
props: {
visible: {
type: Boolean,
default: false
},
receivableBills: {
type: Array,
default: () => []
}
},
data() {
return {
internalVisible: this.visible,
planTitle: '',
chooseReceivable: {},
form: {
invoiceBillType: 'FROM_RECEIVABLE', // Default to a type
partnerName: '',
invoiceTime: null,
},
receivableBillsWithPlans: [], // Each order will now have its own invoicePlans array
isInvoicePlanSelectorOpen: false,
currentReceivableBillIndexForPlan: -1, // Index of the order in receivableBillsWithPlans
loadingInvoicePlans: false, // Loading state for fetching invoice plans
};
},
computed: {
dialogVisible: {
get() {
return this.internalVisible;
},
set(val) {
this.internalVisible = val;
this.$emit('update:visible', val);
}
},
totalReceivableAmountWithTax() {
return this.receivableBillsWithPlans.reduce((sum, order) => sum + (order.totalPriceWithTax || 0), 0);
},
totalPlannedAmount() {
return this.receivableBillsWithPlans.reduce((orderSum, order) => {
const orderPlansTotal = (order.invoicePlans || []).reduce((planSum, plan) => planSum + (plan.planAmount || 0), 0);
return orderSum + orderPlansTotal;
}, 0);
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.initDialogData();
}
},
receivableBills: {
handler(newVal) {
if (this.dialogVisible) {
this.initDialogData();
}
},
deep: true
}
},
methods: {
initDialogData() {
// Initialize form fields
if (this.receivableBills.length > 0) {
const partnerName = this.receivableBills[0].partnerName;
const allSameCustomer = this.receivableBills.every(order => order.partnerName === partnerName);
this.form.partnerName = allSameCustomer ? partnerName : '多个客户';
this.form.invoiceTime = null; // Reset time
} else {
this.form.partnerName = '';
this.form.invoiceTime = null;
}
this.form.invoiceBillType = 'FROM_RECEIVABLE'; // Default
// Initialize receivableBillsWithPlans
this.receivableBillsWithPlans = this.receivableBills.map(order => {
const invoicePlans = order.invoicePlans ? [...order.invoicePlans] : [];
if (invoicePlans.length === 0 && order.lastInvoicePlanId) {
invoicePlans.push({
id: order.lastInvoicePlanId,
planAmount: order.planInvoiceAmount,
planInvoiceDate: order.planInvoiceDate,
planRate: this.$calc.mul(this.$calc.div(order.planInvoiceAmount, order.totalPriceWithTax, 4), 100)
});
}
return {
...order,
invoicePlans: invoicePlans, // Retain existing plans if any, otherwise empty
totalPriceWithTax: order.totalPriceWithTax || 0, // Ensure numeric for calculations
uninvoicedAmount: order.uninvoicedAmount || 0,
invoicedAmount: order.invoicedAmount || 0, // Ensure numeric for calculations
}
});
},
handleClose() {
this.dialogVisible = false;
this.resetForm();
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan || [];
const orderIndex = this.receivableBillsWithPlans.findIndex(o => o.id === this.chooseReceivable.id);
if (orderIndex === -1) {
this.$modal.msgError('找不到要更新的应收单');
return;
}
const currentOrder = this.receivableBillsWithPlans[orderIndex];
// Update the invoice plans for the specific order
this.$set(currentOrder, 'invoicePlans', [...selectedPlans]);
this.isInvoicePlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新开票计划选择,共 ${selectedPlans.length}`);
},
handleConfirm() {
// Validate main form fields
// if (!this.form.invoiceBillType) {
// this.$modal.msgError('');
// return;
// }
// if (!this.form.invoiceTime) {
// this.$modal.msgError('');
// return;
// }
// Validate each receivable order's invoice plans
for (const order of this.receivableBillsWithPlans) {
if (!order.invoicePlans || order.invoicePlans.length === 0) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 至少需要一条开票计划`);
return;
}
for (const plan of order.invoicePlans) {
if (!plan.planInvoiceDate) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的开票计划中预计开票时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的开票计划中预计开票金额必须大于0。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的开票计划中开票比例不能为空。`);
return;
}
}
}
// Construct the final data to be emitted to the parent
const mergedInvoiceData = {
invoiceBillType: this.form.invoiceBillType,
invoiceTime: this.form.invoiceTime,
// Collect all receivable orders with their updated invoice plans
receivableBills: this.receivableBillsWithPlans.map(order => ({
id: order.id,
receivableBillCode: order.receivableBillCode,
taxRate: order.taxRate,
invoicePlans: order.invoicePlans.map(plan => ({
planInvoiceDate: plan.planInvoiceDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
remark: plan.remark,
id: plan.id,
})),
})),
totalMergeInvoiceAmount: this.totalPlannedAmount, // Total amount for the merged bill
};
this.$emit('confirm', mergedInvoiceData);
this.dialogVisible = false;
},
handleCancel() {
this.dialogVisible = false;
this.resetForm();
},
resetForm() {
this.form = {
invoiceBillType: 'FROM_RECEIVABLE',
partnerName: '',
invoiceTime: null,
};
this.receivableBillsWithPlans = [];
this.currentReceivableBillIndexForPlan = -1;
this.loadingInvoicePlans = false;
},
handleOpenInvoicePlanSelector(row, index) {
this.planTitle = `选择开票计划 - ${row.receivableBillCode}`;
this.chooseReceivable = row;
this.currentReceivableBillIndexForPlan = index;
this.isInvoicePlanSelectorOpen = true;
console.log(this.chooseReceivable.id)
},
handleInvoicePlanConfirm(updatedPlans) {
// Update the invoice plans for the specific order
if (this.currentReceivableBillIndexForPlan !== -1) {
this.$set(this.receivableBillsWithPlans[this.currentReceivableBillIndexForPlan], 'invoicePlans', updatedPlans);
}
this.isInvoicePlanSelectorOpen = false;
this.currentReceivableBillIndexForPlan = -1;
},
calculateOrderCurrentInvoiceAmount(orderId) {
const order = this.receivableBillsWithPlans.find(o => o.id === orderId);
if (order && order.invoicePlans) {
return order.invoicePlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentInvoiceRate(orderId) {
const order = this.receivableBillsWithPlans.find(o => o.id === orderId);
if (order && order.invoicePlans && order.uninvoicedAmount >= 0) {
const currentAmount = this.calculateOrderCurrentInvoiceAmount(orderId);
return this.$calc.mul(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ),100);
}
return 0;
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,358 @@
<template>
<el-dialog title="合并发起收款单" :visible.sync="dialogVisible" width="80%" @close="handleClose" append-to-body>
<div class="dialog-body">
<el-form ref="form" :model="form" :inline="true" label-width="120px">
<el-row>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="收款单类型" prop="receiptBillType">-->
<!-- <el-select disabled v-model="form.receiptBillType" placeholder="请选择收款单类型" clearable>-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.receipt_bill_type"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="24">
<el-form-item label="进货商名称">
<el-input v-model="form.partnerName" style="width:400px" readonly/>
</el-form-item>
</el-col>
<!-- <el-col :span="8">-->
<!-- <el-form-item label="预计收款时间" prop="estimatedReceiptTime">-->
<!-- <el-date-picker-->
<!-- v-model="form.estimatedReceiptTime"-->
<!-- type="date"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- placeholder="选择日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
</el-row>
</el-form>
<el-divider content-position="left">销售-应收单表</el-divider>
<el-table :data="receivableBillsWithPlans" border max-height="300px" style="margin-bottom: 20px;">
<el-table-column label="销售-应收单编号" align="center" prop="receivableBillCode" width="150"/>
<el-table-column label="预计收款时间" align="center" prop="planReceiptDate" width="180"/>
<!-- <el-table-column label="收款计划" align="center" width="100" prop="planAmount">-->
<!-- </el-table-column>-->
<el-table-column label="预期收款金额" align="center" width="120">
<template slot-scope="scope">
{{ formatCurrency(calculateOrderCurrentReceiptAmount(scope.row.id).toFixed(2)) }}
</template>
</el-table-column>
<el-table-column label="预期收款比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentReceiptRate(scope.row.id) }}
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" width="150"/>
<el-table-column label="进货商名称" align="center" prop="partnerName" width="150"/>
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>-->
<!-- <el-table-column label="收款状态" align="center" prop="collectionStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.collection_status" :value="scope.row.collectionStatus"/>
</template>
</el-table-column> -->
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款金额(元)" align="center" prop="unreceivedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收款金额(元)" align="center" prop="receivedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中金额(元)" align="center" prop="receivedAmount" width="120">
<template slot-scope="scope">
{{ formatCurrency($calc.sub($calc.sub(scope.row.totalPriceWithTax, scope.row.receivedAmount), scope.row.unreceivedAmount)) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenReceiptPlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<div class="total-info">
<span style="margin-left: 20px;">计划收款总金额: <el-tag type="success">{{
formatCurrency(totalPlannedAmount.toFixed(2))
}}</el-tag></span>
<span>计划收款比例: <el-tag type="info">{{ this.$calc.mul(this.$calc.div(totalPlannedAmount,totalReceivableAmountWithTax,4),100) }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleConfirm"> </el-button>
</div>
<el-dialog :title="planTitle" :visible.sync="isReceiptPlanSelectorOpen" width="70%"
@close="isReceiptPlanSelectorOpen=false" append-to-body>
<receipt-plan-selector
ref="planSelector"
:receivable-data="chooseReceivable"
:selected-plans="chooseReceivable.receiptPlans"
@confirm="handleReceiptPlanConfirm"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isReceiptPlanSelectorOpen=false"> </el-button>
<!-- <el-button type="primary" @click="handleConfirm" > </el-button>-->
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
<!-- 收款计划选择器弹窗 -->
</el-dialog>
</template>
<script>
import ReceiptPlan from './ReceiptPlan.vue';
export default {
name: "MergeReceiptDialog",
components: {ReceiptPlanSelector: ReceiptPlan},
dicts: ['collection_status','receipt_bill_type'], // Add dicts for dict-tag
props: {
visible: {
type: Boolean,
default: false
},
receivableBills: {
type: Array,
default: () => []
}
},
data() {
return {
internalVisible: this.visible,
planTitle: '',
chooseReceivable: {},
form: {
receiptBillType: 'FROM_RECEIVABLE', // Default to a type
customerName: '',
estimatedReceiptTime: null,
},
receivableBillsWithPlans: [], // Each order will now have its own receiptPlans array
isReceiptPlanSelectorOpen: false,
currentReceivableBillIndexForPlan: -1, // Index of the order in receivableBillsWithPlans
loadingReceiptPlans: false, // Loading state for fetching receipt plans
};
},
computed: {
dialogVisible: {
get() {
return this.internalVisible;
},
set(val) {
this.internalVisible = val;
this.$emit('update:visible', val);
}
},
totalReceivableAmountWithTax() {
return this.receivableBillsWithPlans.reduce((sum, order) => sum + (order.totalPriceWithTax || 0), 0);
},
totalPlannedAmount() {
return this.receivableBillsWithPlans.reduce((orderSum, order) => {
const orderPlansTotal = (order.receiptPlans || []).reduce((planSum, plan) => planSum + (plan.planAmount || 0), 0);
return orderSum + orderPlansTotal;
}, 0);
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.initDialogData();
}
},
receivableBills: {
handler(newVal) {
if (this.dialogVisible) {
this.initDialogData();
}
},
deep: true
}
},
methods: {
initDialogData() {
// Initialize form fields
if (this.receivableBills.length > 0) {
this.form.partnerName = this.receivableBills[0].partnerName;
this.form.estimatedReceiptTime = this.receivableBills[0].estimatedReceiptTime || null; // Use first order's estimated time as default
} else {
this.form.partnerName = '';
this.form.estimatedReceiptTime = null;
}
this.form.receiptBillType = 'FROM_RECEIVABLE'; // Default
// Initialize receivableBillsWithPlans
this.receivableBillsWithPlans = this.receivableBills.map(order => {
const receiptPlans = order.receiptPlans ? [...order.receiptPlans] : [];
if (receiptPlans.length === 0 && order.lastReceiptPlanId) {
receiptPlans.push({
id: order.lastReceiptPlanId,
planAmount: order.planAmount,
planReceiptDate: order.planReceiptDate,
planRate: this.$calc.mul(this.$calc.div(order.planAmount, order.totalPriceWithTax, 4), 100)
});
}
return {
...order,
receiptPlans: receiptPlans, // Retain existing plans if any, otherwise empty
totalPriceWithTax: order.totalPriceWithTax || 0, // Ensure numeric for calculations
unreceivedAmount: order.unreceivedAmount || 0,
receivedAmount: order.receivedAmount || 0, // Ensure numeric for calculations
}
});
},
handleClose() {
this.dialogVisible = false;
this.resetForm();
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan || [];
const orderIndex = this.receivableBillsWithPlans.findIndex(o => o.id === this.chooseReceivable.id);
if (orderIndex === -1) {
this.$modal.msgError('找不到要更新的应收单');
return;
}
const currentOrder = this.receivableBillsWithPlans[orderIndex];
// Update the receipt plans for the specific order
this.$set(currentOrder, 'receiptPlans', [...selectedPlans]);
this.isReceiptPlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新收款计划选择,共 ${selectedPlans.length}`);
},
handleConfirm() {
// Validate main form fields
// if (!this.form.receiptBillType) {
// this.$modal.msgError('');
// return;
// }
// if (!this.form.estimatedReceiptTime) {
// this.$modal.msgError('');
// return;
// }
// Validate each receivable order's receipt plans
for (const order of this.receivableBillsWithPlans) {
if (!order.receiptPlans || order.receiptPlans.length === 0) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 至少需要一条收款计划`);
return;
}
for (const plan of order.receiptPlans) {
if (!plan.planReceiptDate) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的收款计划中预计收款时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的收款计划中预计收款金额必须大于0。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined) {
this.$modal.msgError(`应收单 ${order.receivableBillCode} 的收款计划中应收比例不能为空。`);
return;
}
}
}
// Construct the final data to be emitted to the parent
const mergedReceiptData = {
receiptBillType: this.form.receiptBillType,
estimatedReceiptTime: this.form.estimatedReceiptTime,
// Collect all receivable orders with their updated receipt plans
receivableBills: this.receivableBillsWithPlans.map(order => ({
id: order.id,
taxRate: order.taxRate,
receivableBillCode: order.receivableBillCode,
receiptPlans: order.receiptPlans.map(plan => ({
planReceiptDate: plan.planReceiptDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
remark: plan.remark,
id: plan.id,
})),
})),
totalMergeReceiptAmount: this.totalPlannedAmount, // Total amount for the merged bill
};
this.$emit('confirm', mergedReceiptData);
this.dialogVisible = false;
},
handleCancel() {
this.dialogVisible = false;
this.resetForm();
},
resetForm() {
this.form = {
receiptBillType: 'FROM_RECEIVABLE',
customerName: '',
estimatedReceiptTime: null,
};
this.receivableBillsWithPlans = [];
this.currentReceivableBillIndexForPlan = -1;
this.loadingReceiptPlans = false;
},
handleOpenReceiptPlanSelector(row, index) {
this.planTitle = `选择收款计划 - ${row.receivableBillCode}`;
this.chooseReceivable = row;
this.currentReceivableBillIndexForPlan = index;
this.isReceiptPlanSelectorOpen = true;
console.log(this.chooseReceivable.id)
},
handleReceiptPlanConfirm(updatedPlans) {
// Update the receipt plans for the specific order
if (this.currentReceivableBillIndexForPlan !== -1) {
this.$set(this.receivableBillsWithPlans[this.currentReceivableBillIndexForPlan], 'receiptPlans', updatedPlans);
}
this.isReceiptPlanSelectorOpen = false;
this.currentReceivableBillIndexForPlan = -1;
},
calculateOrderCurrentReceiptAmount(orderId) {
const order = this.receivableBillsWithPlans.find(o => o.id === orderId);
if (order && order.receiptPlans) {
return order.receiptPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentReceiptRate(orderId) {
const order = this.receivableBillsWithPlans.find(o => o.id === orderId);
if (order && order.receiptPlans && order.unreceivedAmount >= 0) {
const currentAmount = this.calculateOrderCurrentReceiptAmount(orderId);
return this.$calc.mul((this.$calc.div(currentAmount ,order.totalPriceWithTax,4 )),100);
}
return 0;
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,312 @@
<template>
<div class="dialog-body">
<el-divider content-position="left">收款计划</el-divider>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSaveReceiptPlan"
style="margin-bottom: 10px;">
保存收款计划
</el-button>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSyncToInvoicePlan"
style="margin-bottom: 10px; margin-left: 10px;">
同步至开票计划
</el-button>
<el-button v-else type="primary" size="mini" @click="isEditing=true"
style="margin-bottom: 10px;">
编辑
</el-button>
<el-table :data="receiptPlans" border @selection-change="selectPlan" ref="receiptPlanTable">
<el-table-column type="selection" width="50" align="center" :selectable="selectableRow"/>
<el-table-column label="序号" type="index" width="50" align="center"></el-table-column>
<el-table-column label="预计收款时间" align="center" width="200">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planReceiptDate" type="datetime" style="width: 180px"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="预计收款金额" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planAmount"
:precision="2"
:step="100"
:min="0.01"
:readonly="!scope.row.detailId"
:max="totalPriceWithTax"
@change="handleAmountChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="应收比例(%)" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planRate"
:precision="2"
:step="1"
:min="0.01"
:max="100"
:readonly="!scope.row.detailId"
@change="handleRateChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAddReceiptPlanRow"
>增加下行
</el-button>
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDeleteReceiptPlanRow(scope.$index)"
:disabled="receiptPlans.length === 1 || scope.row.status === 'received'"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- <div class="total-info">-->
<!-- <span>应收单未收款金额: <el-tag type="info">{{ totalUnreceivedAmount.toFixed(2) }}</el-tag></span>-->
<!-- <span style="margin-left: 20px;">计划收款总金额: <el-tag type="success">{{-->
<!-- totalPlannedAmount.toFixed(2)-->
<!-- }}</el-tag></span>-->
<!-- </div>-->
</div>
</template>
<script>
import {getReceiptPlan, updateReceiptPlan, syncToInvoicePlan} from "@/api/finance/receivable";
import {isNumberStr} from "@/utils";
export default {
name: "ReceiptPlanSelector",
props: {
receivableData: {
type: Object,
default: () => {
}
},
isInitEdit: {
type: Boolean,
default: false
},
selectedPlans: {
type: Array,
default: () => []
}
},
data() {
return {
selectedPlan:[],
isEditing: false,
loading: false,
receiptPlans: [],
};
},
computed: {
title() {
return `选择收款计划 - ${this.receivableData.receivableBillCode}`;
},
totalPlannedAmount() {
return this.receiptPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
},
totalUnreceivedAmount() {
return this.receivableData.unreceivedAmount || 0;
},
totalPriceWithTax() {
return this.receivableData.totalPriceWithTax || 0;
}
},
watch: {
'receivableData.id': {
handler(newVal, oldVal) {
if (newVal) {
this.fetchReceiptPlans(newVal)
}
},
immediate: true
},
isInitEdit: {
handler(newVal) {
if (newVal) {
this.isEditing = newVal
}
},
immediate: true
}
},
methods: {
isNumberStr,
selectableRow(row, index){
return !row.detailId;
},
selectPlan( selection){
this.selectedPlan=selection
},
fetchReceiptPlans(receivableId) {
if (this.receivableData && receivableId) {
getReceiptPlan(receivableId).then(response => {
this.receiptPlans = response.data.map(item => ({
...item,
status: item.status || 'pending'
}));
if (this.receiptPlans.length === 0) {
this.initDefaultReceiptPlan();
} else {
this.$nextTick(() => {
this.receiptPlans.forEach(plan => {
const isSelected = this.selectedPlans.some(selected => selected.id === plan.id);
if (isSelected) {
this.$refs.receiptPlanTable.toggleRowSelection(plan, true);
}
});
});
}
})
} else {
this.initDefaultReceiptPlan();
}
},
initDefaultReceiptPlan() {
this.receiptPlans = [{
planReceiptDate: null,
planAmount: this.totalUnreceivedAmount,
planRate: 100,
remark: '',
status: 'pending'
}];
},
handleSaveReceiptPlan() {
if (!this.validateReceiptPlans()) {
return;
}
if (!this.validateReceiptPlanTotals()) {
return;
}
for (let i = 0; i < this.receiptPlans.length; i++) {
const plan = this.receiptPlans[i];
if (!plan.planReceiptDate) {
this.$modal.msgError(`${i + 1} 行收款计划的预计收款时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount === '' || plan.planAmount < 0) {
this.$modal.msgError(`${i + 1} 行收款计划的预计收款金额不能为空。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行收款计划的应收比例不能为空。`);
return;
}
}
updateReceiptPlan(this.receivableData.id, this.receiptPlans).then(() => {
this.$modal.msgSuccess("保存成功");
this.fetchReceiptPlans(this.receivableData.id);
});
},
handleSyncToInvoicePlan() {
this.$modal.confirm('是否确认同步收款计划至开票计划?').then(() => {
return syncToInvoicePlan(this.receivableData.id);
}).then(() => {
this.$emit('syncPlan')
this.$modal.msgSuccess("同步成功");
}).catch(() => {});
},
handleAddReceiptPlanRow() {
this.receiptPlans.push({
planReceiptDate: null,
planAmount: 0,
planRate: 0,
remark: '',
status: 'pending'
});
},
handleDeleteReceiptPlanRow(index) {
if (this.receiptPlans.length === 1) {
this.$modal.msgError("至少需要保留一条收款计划。");
return;
}
if (this.receiptPlans[index].status === 'received') {
this.$modal.msgError("已收款的计划不能删除。");
return;
}
this.receiptPlans.splice(index, 1);
},
handleAmountChange(row) {
if (this.totalPriceWithTax === 0) {
row.planRate = 0;
return;
}
row.planRate = this.$calc.mul((this.$calc.div(row.planAmount, this.totalPriceWithTax,4)), 100);
},
handleRateChange(row) {
row.planAmount = this.$calc.div(this.$calc.mul(this.totalPriceWithTax , row.planRate),100);
},
validateReceiptPlanTotals() {
const totalAmount = this.receiptPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
if (totalAmount !== this.totalPriceWithTax) {
this.$modal.msgError(`预计收款金额之和应该等于应收总金额[${this.totalPriceWithTax}]`);
return false;
}
return true;
},
validateReceiptPlans() {
if (this.receiptPlans.length === 0) {
this.$modal.msgError("请至少添加一条收款计划。");
return false;
}
for (let i = 0; i < this.receiptPlans.length; i++) {
const plan = this.receiptPlans[i];
if (!plan.planReceiptDate) {
this.$modal.msgError(`${i + 1} 行收款计划的预计收款时间不能为空。`);
return false;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`${i + 1} 行收款计划的预计收款金额必须大于0。`);
return false;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行收款计划的应收比例不能为空。`);
return false;
}
}
return true;
}
}
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,324 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="150px">
<!-- <el-form-item label="项目编号" prop="projectCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.projectCode"-->
<!-- placeholder="请输入项目编号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="销售-应收单编号" prop="receivableBillCode">
<el-input
v-model="queryParams.receivableBillCode"
placeholder="请输入应收单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="进货商名称" prop="partnerName">
<el-input
v-model="queryParams.partnerName"
placeholder="请输入客户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="产品类型" prop="productType">
<el-select v-model="queryParams.productType" placeholder="请选择产品类型" clearable>
<el-option
v-for="dict in dict.type.product_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="收款状态" prop="collectionStatus">-->
<!-- <el-select v-model="queryParams.collectionStatus" placeholder="请选择收款状态" clearable>-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.collection_status"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="开票状态" prop="invoiceStatus">
<el-select v-model="queryParams.invoiceStatus" placeholder="请选择开票状态" clearable>
<el-option
v-for="dict in dict.type.invoice_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item> -->
<el-form-item label="预计收款时间">
<el-date-picker
v-model="estimatedReceiptDateRange"
style="width: 300px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" >
<el-button type="primary" plain @click="handleMergeAndInitiateReceipt" v-hasPermi="['finance:receipt:generate']">
合并发起收款单
</el-button>
</el-col>
<el-col :span="1.5" >
<el-button type="primary" plain @click="handleMergeAndInitiateInvoice" v-hasPermi="['finance:invoice:generate']">
合并发起开票单
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="receivableList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" />
<!-- <el-table-column label="项目编号" align="center" prop="projectCode" width="120" />-->
<el-table-column label="项目名称" align="center" prop="projectName" width="240" />
<el-table-column label="销售-应收单编号" align="center" prop="receivableBillCode" width="150" />
<el-table-column label="预计收款时间" align="center" prop="planReceiptDate" width="180"/>
<el-table-column label="预计收款金额" align="center" prop="planAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="该进货商是否有预收单" align="center" prop="hasAdvanceReceipt" width="150" >
<template slot-scope="scope">
{{ scope.row.remainingAmount == 0 ? '否' : '是' }}
</template>
</el-table-column>
<el-table-column label="进货商名称" align="center" prop="partnerName" width="150" />
<el-table-column label="产品类型" align="center" prop="productType" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款金额" align="center" prop="unreceivedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票金额" align="center" prop="uninvoicedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['finance:receivable:edit']"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
v-show="scope.row.unreceivedAmount!==0"
@click="handleGeneratedReceipt(scope.row)"
v-hasPermi="['finance:receipt:generate']"
>生成收款单</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
v-show="scope.row.uninvoicedAmount!==0"
@click="handleGeneratedInvoice(scope.row)"
v-hasPermi="['finance:invoice:generate']"
>生成开票单</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 修改弹窗 -->
<edit-form :visible.sync="open" :data="selectedRow" />
<!-- 合并收款单弹窗 -->
<merge-receipt-dialog :visible.sync="isMergeReceiptDialogOpen" :receivable-bills="selectedReceivableRows" @confirm="confirmMergeReceipt" />
<!-- 合并开票单弹窗 -->
<merge-invoice-dialog :visible.sync="isMergeInvoiceDialogOpen" :receivable-bills="selectedReceivableRows" @confirm="confirmMergeInvoice" />
</div>
</template>
<script>
import { listReceivable, mergeAndInitiateReceipt, mergeAndInitiateInvoice } from "@/api/finance/receivable";
import EditForm from './components/EditForm.vue';
import MergeReceiptDialog from './components/MergeReceiptDialog.vue';
import MergeInvoiceDialog from './components/MergeInvoiceDialog.vue';
export default {
name: "Receivable",
components: { EditForm, MergeReceiptDialog, MergeInvoiceDialog },
dicts: ['product_type', 'collection_status', 'invoice_status'],
data() {
return {
//
open: false,
//
selectedRow: {},
//
loading: true,
//
showSearch: true,
//
total: 0,
//
receivableList: [],
//
dateRange: [],
//
estimatedReceiptDateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
receivableBillCode: null,
partnerName: null,
productType: null,
collectionStatus: null,
createTimeStart: null,
createTimeEnd: null,
estimatedReceiptTimeStart: null,
estimatedReceiptTimeEnd: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
selectedReceivableRows: [],
//
isMergeReceiptDialogOpen: false,
//
isMergeInvoiceDialogOpen: false
};
},
created() {
this.getList();
},
methods: {
/** 查询销售应收单列表 */
getList() {
this.loading = true;
let query = { ...this.queryParams };
query = this.addDateRange(query, this.estimatedReceiptDateRange, 'PlanReceiptDate');
listReceivable(query).then(response => {
this.receivableList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.estimatedReceiptDateRange = [];
this.resetForm("queryForm");
this.queryParams.createTimeStart=null;
this.queryParams.createTimeEnd=null;
this.queryParams.estimatedReceiptTimeStart=null;
this.queryParams.estimatedReceiptTimeEnd=null;
this.handleQuery();
},
/** 修改按钮操作 */
handleUpdate(row) {
this.selectedRow = row;
this.open = true;
},
/** 删除按钮操作 */
handleDelete(row) {
this.$modal.confirm('是否确认删除销售应收单编号为"' + row.receivableBillCode + '"的数据项?').then(function() {
return Promise.resolve();
}).then(() => {
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.selectedReceivableRows = selection;
},
handleGeneratedReceipt(row) {
this.selectedReceivableRows=[row]
this.handleMergeAndInitiateReceipt()
},
handleGeneratedInvoice(row) {
this.selectedReceivableRows=[row]
this.handleMergeAndInitiateInvoice()
},
/** 合并并发起收款单按钮操作 */
handleMergeAndInitiateReceipt() {
if (this.selectedReceivableRows.length === 0) {
this.$modal.msgWarning("请选择至少一条应收单进行合并操作");
return;
}
let customerLength = new Set(this.selectedReceivableRows.map(item=>item.customerCode)).size; // Use customerCode to differentiate
// Or check partnerName if code is not available in row, but row usually has it.
if (customerLength > 1) {
this.$modal.msgWarning("请选择同一家客户的应收单进行合并操作");
return;
}
this.isMergeReceiptDialogOpen = true;
},
/** 确认合并收款单操作 */
confirmMergeReceipt(receiptData) {
mergeAndInitiateReceipt(receiptData).then(() => {
this.$modal.msgSuccess("合并收款单发起成功");
this.isMergeReceiptDialogOpen = false;
this.getList(); // Refresh the list
});
},
/** 合并并发起开票单按钮操作 */
handleMergeAndInitiateInvoice() {
if (this.selectedReceivableRows.length === 0) {
this.$modal.msgWarning("请选择至少一条应收单进行合并操作");
return;
}
let customerLength = new Set(this.selectedReceivableRows.map(item=>item.customerCode)).size;
if (customerLength > 1) {
this.$modal.msgWarning("请选择同一家客户的应收单进行合并操作");
return;
}
this.isMergeInvoiceDialogOpen = true;
},
/** 确认合并开票单操作 */
confirmMergeInvoice(invoiceData) {
mergeAndInitiateInvoice(invoiceData).then(() => {
this.$modal.msgSuccess("合并开票单发起成功");
this.isMergeInvoiceDialogOpen = false;
this.getList(); // Refresh the list
});
}
}
};
</script>

View File

@ -0,0 +1,489 @@
<template>
<el-dialog title="新增收款单" :visible.sync="internalVisible" width="1200px" @close="handleClose"
:close-on-click-modal="false" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="24">
<el-form-item label="进货商名称" prop="partnerName">
<el-input v-model="form.partnerName" placeholder="请选择进货商" readonly
@click.native="showPartnerSelector = true">
<el-button slot="append" icon="el-icon-search" @click="showPartnerSelector = true"></el-button>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="receiveBillType">
<el-checkbox v-model="form.receiveBillType" true-label="PRE_RECEIPT" false-label="FROM_RECEIVABLE">
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<!-- <el-row>-->
<!-- <el-col :span="24">-->
<!-- <el-form-item label="预计收款时间" prop="receiptTime">-->
<!-- <el-date-picker-->
<!-- v-model="form.receiptTime"-->
<!-- type="date"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- placeholder="选择日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<!-- </el-row>-->
<el-row v-if="form.receiveBillType === 'PRE_RECEIPT'">
<el-col :span="12">
<el-form-item label="预收金额" prop="totalPriceWithTax">
<el-input-number v-model="form.totalPriceWithTax" :precision="2" :step="100"
style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-row>
<!-- Tables -->
<div>
<div v-if="form.receiveBillType === 'FROM_RECEIVABLE' && form.partnerCode" class="table-container">
<h4>应收单列表</h4>
<el-table
ref="receivableTable"
:data="receivableList"
border
v-loading="receivableLoading"
style="width: 100%"
@selection-change="handleSelectionChange"
max-height="400"
row-key="id"
>
<el-table-column type="selection" width="55" reserve-selection></el-table-column>
<el-table-column label="销售-应收单编号" align="center" prop="receivableBillCode" width="150"/>
<el-table-column label="预计收款时间" align="center" prop="planReceiptDate" width="180"/>
<!-- <el-table-column label="收款计划" align="center" width="100" prop="planAmount">-->
<!-- </el-table-column>-->
<el-table-column label="预期收款金额">
<template slot-scope="scope">
{{ formatCurrency(calculateOrderCurrentReceiptAmount(scope.row).toFixed(2)) }}
</template>
</el-table-column>
<el-table-column label="预期收款比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentReceiptRate(scope.row) }}
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" width="150"/>
<el-table-column label="进货商名称" align="center" prop="partnerName" width="150"/>
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>-->
<!-- <el-table-column label="收款状态" align="center" prop="collectionStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.collection_status" :value="scope.row.collectionStatus"/>
</template>
</el-table-column> -->
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款金额(元)" align="center" prop="unreceivedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收款金额(元)" align="center" prop="receivedAmount" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中金额(元)" align="center" prop="receivedAmount" width="120">
<template slot-scope="scope">
{{ formatCurrency($calc.sub($calc.sub(scope.row.totalPriceWithTax, scope.row.receivedAmount), scope.row.unreceivedAmount)) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100"
fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenReceiptPlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<div class="total-info">
<span style="margin-left: 20px;">计划收款总金额: <el-tag type="success">{{
formatCurrency(totalPlannedAmount.toFixed(2))
}}</el-tag></span>
<span>计划收款比例: <el-tag type="info">{{ totalSelectedReceivableAmount ? this.$calc.mul(this.$calc.div(totalPlannedAmount,totalSelectedReceivableAmount,4),100) : 0 }}%</el-tag></span>
</div>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="loadTableData"
/>
</div>
<div v-if="form.receiveBillType === 'PRE_RECEIPT' && form.partnerCode" class="table-container">
<h4>订单列表</h4>
<el-table
ref="orderTable"
:data="orderList"
border
v-loading="orderLoading"
style="width: 100%"
@selection-change="handleSelectionChange"
@select="handleSelect"
@select-all="handleSelectAll"
max-height="400"
row-key="id"
>
<el-table-column type="selection" width="55" reserve-selection></el-table-column>
<el-table-column prop="projectCode" label="项目编号"></el-table-column>
<el-table-column prop="projectName" label="项目名称"></el-table-column>
<el-table-column prop="createTime" label="下单时间"></el-table-column>
<el-table-column label="订单状态" prop="orderStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.order_status" :value="scope.row.orderStatus"/>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="loadTableData"
/>
</div>
<div v-if="!form.partnerCode" style="text-align: center; color: #909399; padding: 20px;">
请先选择进货商
</div>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
<!-- Receipt Plan Selector Dialog -->
<el-dialog :title="planTitle" :visible.sync="isReceiptPlanSelectorOpen" width="70%"
@close="isReceiptPlanSelectorOpen=false" append-to-body>
<receipt-plan
ref="planSelector"
:receivable-data="chooseReceivable"
:selected-plans="chooseReceivable.receiptPlans"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isReceiptPlanSelectorOpen=false"> </el-button>
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
<select-partner :visible.sync="showPartnerSelector" @partner-selected="handlePartnerSelected" />
</el-dialog>
</template>
<script>
import {listReceivable} from "@/api/finance/receivable";
import {listOrder} from "@/api/project/order";
import ReceiptPlan from "@/views/finance/receivable/components/ReceiptPlan.vue";
import SelectPartner from "@/views/system/partner/selectPartner.vue";
export default {
name: "AddForm",
components: {ReceiptPlan, SelectPartner},
props: {
visible: {
type: Boolean,
default: false
}
},
dicts: ['order_status', 'receive_status', 'collection_status'],
data() {
return {
internalVisible: this.visible,
receivableList: [], // List for Standard/Receivable Bills
orderList: [], // List for Pre-receipt/Sales Orders
selectedRows: [],
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10
},
form: {
receiveBillType: 'FROM_RECEIVABLE',
partnerName: null,
partnerCode: null,
remark: null,
totalPriceWithTax: 0,
actualReceiveTime: null
},
showPartnerSelector: false,
receivableLoading: false,
orderLoading: false,
rules: {
partnerName: [{required: true, message: "代理商不能为空", trigger: "change"}],
actualReceiveTime: [{required: true, message: "实际收款时间不能为空", trigger: "change"}],
receiveBillType: [{required: true, message: "请选择是否预收", trigger: "change"}],
totalPriceWithTax: [{required: false, message: "预收金额不能为空", trigger: "blur"}]
},
// Plan Selector Data
planTitle: '',
isReceiptPlanSelectorOpen: false,
chooseReceivable: {},
currentReceivableOrderIndexForPlan: -1,
};
},
computed: {
totalPlannedAmount() {
if (this.form.receiveBillType === 'FROM_RECEIVABLE') {
// Calculate based on selected rows and their plans/defaults
return this.selectedRows.reduce((sum, row) => {
return sum + this.calculateOrderCurrentReceiptAmount(row);
}, 0);
}
return 0;
},
totalSelectedReceivableAmount() {
if (this.form.receiveBillType === 'FROM_RECEIVABLE') {
return this.selectedRows.reduce((sum, row) => {
return sum + (row.totalPriceWithTax || 0);
}, 0);
}
return 0;
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.resetForm();
}
},
internalVisible(newVal) {
this.$emit("update:visible", newVal);
},
'form.receiveBillType': function (val) {
// Toggle validation for Pre-receipt Amount
if (val === 'PRE_RECEIPT') {
this.rules.totalPriceWithTax[0].required = true;
} else {
this.rules.totalPriceWithTax[0].required = false;
}
this.queryParams.pageNum = 1;
this.selectedRows = [];
this.loadTableData();
}
},
methods: {
handlePartnerSelected(partner) {
this.form.partnerName = partner.partnerName;
this.form.partnerCode = partner.partnerCode;
this.queryParams.pageNum = 1;
this.selectedRows = [];
this.loadTableData();
},
loadTableData() {
this.receivableList = [];
this.orderList = [];
const query = {
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize
};
if (this.form.receiveBillType === 'FROM_RECEIVABLE' && this.form.partnerCode) {
query.partnerCode = this.form.partnerCode;
this.receivableLoading = true
listReceivable(query).then(res => {
this.receivableList = (res.rows || []).map(item => {
const receiptPlans = item.receiptPlans ? [...item.receiptPlans] : [];
// If needed, add logic to initialize default plan similar to payment
if (receiptPlans.length === 0 && item.lastReceiptPlanId) {
receiptPlans.push({
id: item.lastReceiptPlanId,
planAmount: item.planAmount,
planReceiptDate: item.planReceiptDate,
planRate: this.$calc.mul(this.$calc.div(item.planAmount, item.totalPriceWithTax, 4), 100)
});
}
return {
...item,
receiptPlans: receiptPlans,
}
});
this.total = res.total;
this.receivableLoading = false
});
} else if (this.form.receiveBillType === 'PRE_RECEIPT' && this.form.partnerCode) {
// Filter Sales Orders for the agent (partner)
query.partnerCode = this.form.partnerCode;
query.orderStatus = '2'; // Example status
this.orderLoading=true
listOrder(query).then(res => {
this.orderList = res.rows || [];
this.total = res.total;
this.orderLoading=false
});
}
this.$nextTick(() => {
if (this.$refs.receivableTable) {
this.$refs.receivableTable.clearSelection()
}
if (this.$refs.orderTable) {
this.$refs.orderTable.clearSelection()
}
})
},
handleSelect(selection, row) {
if (this.form.receiveBillType === 'PRE_RECEIPT') {
this.$refs.orderTable.clearSelection();
const isSelected = selection.some(item => item.id === row.id); // Use ID for uniqueness
if (isSelected) {
this.$refs.orderTable.toggleRowSelection(row, true);
}
}
},
handleSelectAll(selection) {
if (this.form.receiveBillType === 'PRE_RECEIPT') {
this.$refs.orderTable.clearSelection();
this.$modal.msgWarning("预收单只能选择一个订单");
}
},
handleSelectionChange(selection) {
this.selectedRows = selection;
},
// --- Receipt Plan Logic ---
handleOpenReceiptPlanSelector(row, index) {
this.planTitle = `选择收款计划 - ${row.receivableBillCode}`;
this.chooseReceivable = row;
this.currentReceivableOrderIndexForPlan = index;
this.isReceiptPlanSelectorOpen = true;
},
handleChooseConfirm() {
if (!this.$refs.planSelector) {
this.$modal.msgError('无法获取计划选择器组件');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan || [];
// Update the plans for the specific order
if (this.currentReceivableOrderIndexForPlan !== -1) {
const row = this.receivableList[this.currentReceivableOrderIndexForPlan];
this.$set(row, 'receiptPlans', [...selectedPlans]);
}
this.isReceiptPlanSelectorOpen = false;
this.$modal.msgSuccess(`已更新收款计划选择,共 ${selectedPlans.length}`);
},
calculateOrderCurrentReceiptAmount(order) {
if (order && order.receiptPlans && order.receiptPlans.length > 0) {
return order.receiptPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentReceiptRate(order) {
if (order && order.receiptPlans && order.receiptPlans.length > 0 && order.totalPriceWithTax) {
const currentAmount = this.calculateOrderCurrentReceiptAmount(order);
return this.$calc.mul((this.$calc.div(currentAmount, order.totalPriceWithTax, 4)), 100);
}
return 0;
},
handleClose() {
this.internalVisible = false;
},
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
if (this.form.receiveBillType === 'FROM_RECEIVABLE') {
if (this.selectedRows.length === 0) {
this.$message.warning("请选择至少一条应收单");
return;
}
// Process selected rows
const processedReceivableOrders = this.selectedRows.map(order => {
let finalPlans = order.receiptPlans;
// Add validation if needed: check if plans exist
if (!finalPlans || finalPlans.length === 0) {
// warning handled in previous logic, maybe add here
}
return {
id: order.id,
receivableBillCode: order.receivableBillCode,
taxRate: order.taxRate,
// Map plans
receiptPlans: (finalPlans || []).map(plan => ({
id: plan.id,
planReceiptDate: plan.planReceiptDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
remark: plan.remark
}))
};
});
// Ensure every selected order has plans?
if (processedReceivableOrders.some(o => o.receiptPlans.length === 0)) {
this.$message.warning("选中的应收单必须包含收款计划");
return;
}
const submitData = {
receiptBillType: 'FROM_RECEIVABLE',
receiptTime: this.form.receiptTime,
receivableBills: processedReceivableOrders,
totalMergeReceiptAmount: this.totalPlannedAmount
};
this.$emit("submit", submitData);
} else {
if ((this.form.totalPriceWithTax || 0) <= 0) {
this.$message.warning("预收金额需要大于0");
return;
}
let order = this.selectedRows[0] ?? {};
const submitData = {
receiptBillType: 'PRE_RECEIPT',
receiptTime: this.form.receiptTime,
orderCode: order.orderCode, // Ensure orderCode is available in orderList items
partnerCode: this.form.partnerCode,
partnerName: this.form.partnerName,
projectCode: order.projectCode,
projectName: order.projectName,
totalPriceWithTax: this.form.totalPriceWithTax
};
console.log(submitData)
this.$emit("submit", submitData);
}
}
});
},
resetForm() {
if (this.$refs.form) {
this.$refs.form.resetFields();
}
this.form = {
receiveBillType: 'FROM_RECEIVABLE',
partnerName: null,
partnerCode: null,
remark: null,
totalPriceWithTax: 0,
actualReceiveTime: null
};
this.receivableList = [];
this.orderList = [];
this.selectedRows = [];
this.queryParams = {
pageNum: 1,
pageSize: 10
};
this.total = 0;
}
}
};
</script>
<style scoped>
.table-container {
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,328 @@
<template>
<el-dialog title="申请付款" :visible.sync="visible" width="1000px" append-to-body :before-close="handleClose"
custom-class="apply-payment-dialog">
<el-row :gutter="20">
<!-- Left Side: Form Data -->
<el-col :span="12">
<div class="form-tip">请选择客户的支付方式并确认客户打款的账户信息提交至财务审批</div>
<el-form ref="form" :model="form" :rules="rules" label-width="120px" size="small">
<el-form-item label="支付方式" prop="receiptMethod">
<el-select v-model="form.receiptMethod" placeholder="请选择支付方式" style="width: 100%">
<el-option
v-for="dict in dicts.payment_method"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="账户名称" prop="receiptAccountName">
<el-input v-model="form.receiptAccountName" placeholder="请输入账户名称"/>
</el-form-item>
<el-form-item label="银行账号" prop="receiptBankNumber">
<el-input v-model="form.receiptBankNumber" placeholder="请输入银行账号"/>
</el-form-item>
<el-form-item label="银行开户行" prop="receiptBankOpenAddress">
<el-input v-model="form.receiptBankOpenAddress" placeholder="请输入银行开户行"/>
</el-form-item>
<el-form-item label="银行行号" prop="bankNumber">
<el-input v-model="form.bankNumber" placeholder="请输入银行行号"/>
</el-form-item>
<!-- New Field: Client Payment Image Upload -->
<el-form-item label="客户付款图" prop="file">
<el-upload
ref="upload"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:show-file-list="false"
accept=".jpg,.jpeg,.png,.pdf"
>
<el-button size="mini" type="primary" icon="el-icon-upload2">{{
form.file ? '重新上传' : '点击上传'
}}
</el-button>
<div slot="tip" class="el-upload__tip">支持JPG/PNG/PDF格式</div>
</el-upload>
<div v-if="form.file" class="file-name-tip">
<i class="el-icon-document"></i> {{ form.fileName }}
</div>
</el-form-item>
<el-form-item label="收款金额" prop="totalPriceWithTax">
<el-input v-model="form.totalPriceWithTax" :disabled="true"/>
</el-form-item>
<!-- New Field: Confirm Receipt Amount -->
<el-form-item label="确认收款金额" prop="confirmAmount">
<el-input v-model="form.confirmAmount" placeholder="请输入确认收款金额"/>
</el-form-item>
<!-- New Field: Remarks -->
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" :rows="3" placeholder="请输入备注"/>
</el-form-item>
</el-form>
</el-col>
<!-- Right Side: Preview -->
<el-col :span="12">
<div class="preview-container">
<div v-if="previewUrl" class="preview-content">
<div v-if="isPreviewPdf" class="pdf-preview">
<iframe :src="previewUrl" width="100%" height="100%" frameborder="0"></iframe>
</div>
<div v-else class="image-preview">
<img :src="previewUrl" alt="预览图片"/>
</div>
</div>
<div v-else class="preview-placeholder">
<div class="placeholder-icon">
<i class="el-icon-picture-outline"></i>
</div>
<div class="placeholder-text">上传文件后在此处预览</div>
</div>
</div>
</el-col>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {applyReceipt} from "@/api/finance/receive";
export default {
name: "ApplyPaymentDialog",
props: {
visible: {
type: Boolean,
default: false
},
receiptData: {
type: Object,
default: () => ({})
},
dicts: {
type: Object,
default: () => ({})
}
},
data() {
return {
rules: {
receiptMethod: [
{ required: true, message: "请选择支付方式", trigger: "change" }
],
receiptAccountName: [
{ required: true, message: "请输入账户名称", trigger: "blur" }
],
receiptBankNumber: [
{ required: true, message: "请输入银行账号", trigger: "blur" }
],
receiptBankOpenAddress: [
{ required: true, message: "请输入银行开户行", trigger: "blur" }
],
bankNumber: [
{ required: true, message: "请输入银行行号", trigger: "blur" }
],
file: [
{ required: true, message: "请上传客户付款图", trigger: "change" }
],
confirmAmount: [
{ required: true, message: "请输入确认收款金额", trigger: "blur" }
]
},
form: {
receiptMethod: null,
receiptAccountName: null,
receiptBankNumber: null,
receiptBankOpenAddress: null,
bankNumber: null,
totalPriceWithTax: null,
confirmAmount: null,
remark: null,
file: null,
fileName: '',
id: this.receiptData.id
},
previewUrl: '',
isPreviewPdf: false
};
},
watch: {
visible(val) {
if (val) {
this.reset();
// Initialize form with receiptData
this.form = {
id: this.receiptData.id,
receiptMethod: this.receiptData.receiptMethod,
receiptAccountName: this.receiptData.receiptAccountName,
receiptBankNumber: this.receiptData.receiptBankNumber,
receiptBankOpenAddress: this.receiptData.receiptBankOpenAddress,
bankNumber: this.receiptData.bankNumber,
totalPriceWithTax: this.receiptData.totalPriceWithTax,
confirmAmount: null,
remark: null,
file: null,
fileName: ''
};
}
}
},
methods: {
reset() {
this.form = {
receiptMethod: null,
receiptAccountName: null,
receiptBankNumber: null,
receiptBankOpenAddress: null,
bankNumber: null,
totalPriceWithTax: null,
confirmAmount: null,
remark: null,
file: null,
fileName: ''
};
this.previewUrl = '';
this.isPreviewPdf = false;
if (this.$refs.form) {
this.$refs.form.resetFields();
}
},
handleClose() {
this.$emit("update:visible", false);
},
handleFileChange(file) {
const isLt10M = file.size / 1024 / 1024 < 10;
const isAcceptedType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.raw.type);
if (!isAcceptedType) {
this.$modal.msgError('上传文件只能是 JPG/PNG/PDF 格式!');
// Remove file from upload list if needed, though we use show-file-list="false"
return;
}
if (!isLt10M) {
this.$modal.msgError('上传文件大小不能超过 10MB!');
return;
}
this.form.file = file.raw;
this.form.fileName = file.name;
this.isPreviewPdf = file.raw.type === 'application/pdf';
this.previewUrl = URL.createObjectURL(file.raw);
this.$refs.form.validateField('file');
},
handleFileRemove() {
this.form.file = null;
this.form.fileName = '';
this.previewUrl = '';
this.$refs.form.validateField('file');
},
handleSubmit() {
if (this.$calc.sub(this.form.totalPriceWithTax,this.form.confirmAmount)!=0){
this.$modal.msgError("确认收款金额与收款金额需相同");
return
}
this.$refs["form"].validate(valid => {
if (valid) {
// Construct FormData
const formData = new FormData();
// Append regular fields
Object.keys(this.form).forEach(key => {
if (key !== 'file' && key !== 'fileName' && this.form[key] !== null && this.form[key] !== undefined) {
formData.append(key, this.form[key]);
}
});
// Append file if exists
if (this.form.file) {
formData.append("file", this.form.file);
}
// Since applyPaymentApi usually takes JSON, we might need to verify if backend supports FormData
// Assuming we are sending FormData now.
applyReceipt(formData).then(response => {
this.$modal.msgSuccess("申请付款提交成功");
this.$emit("submit");
this.handleClose();
}).catch(error => {
console.error("申请付款提交失败", error);
});
}
});
}
}
};
</script>
<style scoped>
.form-tip {
color: #1890ff;
font-size: 14px;
margin-bottom: 20px;
padding: 8px 16px;
background-color: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
}
.preview-container {
width: 100%;
height: 500px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background-color: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.preview-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.image-preview img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.pdf-preview {
width: 100%;
height: 100%;
}
.preview-placeholder {
text-align: center;
color: #909399;
}
.placeholder-icon {
font-size: 48px;
margin-bottom: 10px;
}
.placeholder-text {
font-size: 14px;
}
.file-name-tip {
margin-top: 5px;
font-size: 12px;
color: #606266;
}
</style>

View File

@ -0,0 +1,209 @@
<template>
<el-dialog title="申请退款" :visible.sync="visible" width="600px" append-to-body :before-close="handleClose"
custom-class="apply-refund-dialog">
<div class="form-tip">请选择客户的退款方式并确认客户收款的账户信息提交至财务审批</div>
<el-form ref="form" :model="form" :rules="rules" label-width="120px" size="small">
<el-form-item label="退款方式" prop="receiptMethod">
<el-select v-model="form.receiptMethod" placeholder="请选择退款方式" style="width: 100%">
<el-option
v-for="dict in dicts.payment_method"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="账户名称" prop="receiptAccountName">
<el-input v-model="form.receiptAccountName" placeholder="请输入账户名称"/>
</el-form-item>
<el-form-item label="银行账号" prop="receiptBankNumber">
<el-input v-model="form.receiptBankNumber" placeholder="请输入银行账号"/>
</el-form-item>
<el-form-item label="银行开户行" prop="receiptBankOpenAddress">
<el-input v-model="form.receiptBankOpenAddress" placeholder="请输入银行开户行"/>
</el-form-item>
<el-form-item label="银行行号" prop="bankNumber">
<el-input v-model="form.bankNumber" placeholder="请输入银行行号"/>
</el-form-item>
<el-form-item label="含税金额" prop="totalPriceWithTax">
<el-input v-model="form.totalPriceWithTax" :disabled="true"/>
</el-form-item>
<!-- Field: Confirm Refund Amount -->
<el-form-item label="确认退款金额" prop="confirmAmount">
<el-input v-model="form.confirmAmount" placeholder="请输入确认退款金额"/>
</el-form-item>
<!-- Field: Remarks -->
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" :rows="3" placeholder="请输入备注"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {submitRefund} from "@/api/finance/receive";
export default {
name: "ApplyRefundDialog",
props: {
visible: {
type: Boolean,
default: false
},
receiptData: {
type: Object,
default: () => ({})
},
dicts: {
type: Object,
default: () => ({})
}
},
data() {
return {
form: {
receiptMethod: null,
receiptAccountName: null,
receiptBankNumber: null,
receiptBankOpenAddress: null,
bankNumber: null,
totalPriceWithTax: null,
confirmAmount: null,
remark: null,
id: this.receiptData.id
},
rules: {
receiptMethod: [
{ required: true, message: "请选择退款方式", trigger: "change" }
],
receiptAccountName: [
{ required: true, message: "请输入账户名称", trigger: "blur" }
],
receiptBankNumber: [
{ required: true, message: "请输入银行账号", trigger: "blur" }
],
receiptBankOpenAddress: [
{ required: true, message: "请输入银行开户行", trigger: "blur" }
],
bankNumber: [
{ required: true, message: "请输入银行行号", trigger: "blur" }
],
confirmAmount: [
{ required: true, message: "请输入确认退款金额", trigger: "blur" }
]
}
};
},
watch: {
visible(val) {
if (val) {
this.reset();
// Initialize form with receiptData
this.form = {
id: this.receiptData.id,
receiptMethod: this.receiptData.receiptMethod,
receiptAccountName: this.receiptData.receiptAccountName,
receiptBankNumber: this.receiptData.receiptBankNumber,
receiptBankOpenAddress: this.receiptData.receiptBankOpenAddress,
bankNumber: this.receiptData.bankNumber,
totalPriceWithTax: this.receiptData.totalPriceWithTax,
confirmAmount: null,
remark: null
};
}
}
},
methods: {
reset() {
this.form = {
receiptMethod: null,
receiptAccountName: null,
receiptBankNumber: null,
receiptBankOpenAddress: null,
bankNumber: null,
totalPriceWithTax: null,
confirmAmount: null,
remark: null
};
if (this.$refs.form) {
this.$refs.form.resetFields();
}
},
handleClose() {
this.$emit("update:visible", false);
},
handleSubmit() {
this.$refs["form"].validate(valid => {
if (valid) {
const checkFields = ['receiptMethod', 'receiptAccountName', 'receiptBankNumber', 'receiptBankOpenAddress', 'bankNumber'];
let isDiff = false;
let diffMsg = [];
checkFields.forEach(field => {
// Use loose equality to handle null vs undefined or number vs string issues if necessary,
// but stricter is better if types are consistent.
// Given Vue forms often use strings, and receiptData might be from API,
// we'll try to match somewhat loosely or ensure strings.
const formVal = this.form[field];
const originVal = this.receiptData[field];
// Simple comparison
if (formVal != originVal) {
isDiff = true;
// Get label for friendly message
let label = "";
switch(field) {
case 'receiptMethod': label = "退款方式"; break;
case 'receiptAccountName': label = "账户名称"; break;
case 'receiptBankNumber': label = "银行账号"; break;
case 'receiptBankOpenAddress': label = "银行开户行"; break;
case 'bankNumber': label = "银行行号"; break;
}
diffMsg.push(label);
}
});
const doSubmit = () => {
submitRefund(this.form).then(response => {
this.$modal.msgSuccess("申请退款提交成功");
this.$emit("submit");
this.handleClose();
}).catch(error => {
console.error("申请退款提交失败", error);
});
};
if (isDiff) {
this.$modal.confirm(`检测到以下信息与原收款信息不一致:${diffMsg.join('、')}。确认要提交吗?`).then(() => {
doSubmit();
}).catch(() => {});
} else {
doSubmit();
}
}
});
}
}
};
</script>
<style scoped>
.form-tip {
color: #1890ff;
font-size: 14px;
margin-bottom: 20px;
padding: 8px 16px;
background-color: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
}
</style>

View File

@ -0,0 +1,198 @@
<template>
<el-drawer
:title="detail.receiptBillType==='REFUND'?'退款单详情':'收款单详情'"
:visible.sync="visible"
direction="rtl"
size="70%"
@close="handleClose"
>
<div class="dialog-body" v-if="detail">
<div class="section">
<el-divider content-position="left">销售-收款单</el-divider>
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">销售-收款单编号: {{ detail.receiptBillCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">预计收款时间: {{ detail.receiptTime }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">进货商名称: {{ detail.partnerName }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">含税总价():<span :class="{'red-text':detail.receiptBillType==='REFUND'}"> {{ formatCurrency(detail.totalPriceWithTax) }} </span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">未税总价(): <span :class="{'red-text':detail.receiptBillType==='REFUND'}">{{ formatCurrency(detail.totalPriceWithoutTax) }} </span></div>
</el-col>
<el-col :span="8">
<div class="detail-item">税额(): <span :class="{'red-text':detail.receiptBillType==='REFUND'}">{{ formatCurrency(detail.taxAmount) }} </span></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">备注:
<dict-tag :options="dict.type.receipt_bill_type" :value="detail.receiptBillType"/></div>
</el-col>
<el-col :span="8">
<div class="detail-item">预收单剩余额度:{{formatCurrency(detail.remainAmount)}}
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">实际收款时间: {{ detail.actualReceiptTime }}</div>
</el-col>
<!-- <el-col :span="8">-->
<!-- <div class="detail-item">收款图/回执单:-->
<!-- <span v-if="detail.attachment">-->
<!-- <el-button type="text" size="mini" icon="el-icon-view" @click="handlePreview(detail.attachment)"></el-button>-->
<!-- <el-button type="text" size="mini" icon="el-icon-download"-->
<!-- @click="downloadFile(detail.attachment)">下载</el-button>-->
<!-- </span>-->
<!-- <span v-else>-</span>-->
<!-- </div>-->
<!-- </el-col>-->
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">支付方式: <dict-tag :options="dict.type.payment_method" :value="detail.receiptMethod"/></div>
</el-col>
<el-col :span="8">
<div class="detail-item">收款状态: <dict-tag :options="dict.type.receipt_bill_status" :value="detail.receiptStatus"/></div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批节点: {{ detail.approveNode|| '-' }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">审批状态:
<dict-tag :options="dict.type.approve_status" :value="detail.approveStatus"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批通过时间: {{ detail.approveTime || '-'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">账户名称: {{ detail.receiptAccountName }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">银行账号: {{ detail.receiptBankNumber|| '-' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">银行开户行:{{detail.receiptBankOpenAddress}}
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">银行行号: {{ detail.bankNumber }}</div>
</el-col>
</el-row>
</div>
</div>
<div class="section">
<el-divider content-position="left">销售-应收单</el-divider>
<el-table :data="detail.detailDTOList">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column property="projectCode" label="项目编号"></el-table-column>
<el-table-column property="projectName" label="项目名称"></el-table-column>
<el-table-column property="receivableBillCode" label="应收单编号"></el-table-column>
<el-table-column property="totalPriceWithTax" label="含税总价(元)" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column property="receiptAmount" label="本次收款金额">
<template slot-scope="scope">
<span >
{{ formatCurrency(Math.abs(scope.row.receiptAmount)) }}
</span>
</template>
</el-table-column>
<el-table-column property="receiptRate" label="本次收款比例(%)">
<template slot-scope="scope">
<span >
{{ Math.abs(scope.row.receiptRate) }}
</span>
</template>
</el-table-column>
</el-table>
</div>
<GlobalFilePreview ref="filePreview" />
</div>
</el-drawer>
</template>
<script>
import GlobalFilePreview from '@/components/GlobalFilePreview';
export default {
name: "DetailDrawer",
components: {
GlobalFilePreview
},
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => null,
},
},
data() {
return {
};
},
dicts:['receipt_bill_type','approve_status','receipt_bill_status','payment_method'],
methods: {
handleClose() {
this.$emit("update:visible", false);
},
handlePreview(attachment) {
this.$refs.filePreview.handlePreview(attachment);
},
downloadFile(attachment) {
this.$refs.filePreview.downloadFile(attachment);
}
},
};
</script>
<style scoped>
.dialog-body {
max-height: calc(100vh - 50px);
overflow-y: auto;
padding: 0 20px 20px 20px;
}
.details-container {
border: 1px solid #EBEEF5;
padding: 20px;
border-radius: 4px;
}
.detail-item {
display: flex;
border: 1px solid #EBEEF5;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
align-items: center;
}
.red-text{
color: red;
}
.section {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,474 @@
<template>
<el-dialog :title="titleText" :visible.sync="dialogVisible" width="900px" @close="handleClose">
<div v-if="loading" class="loading-spinner">
<i class="el-icon-loading"></i>
</div>
<div v-else class="receipt-dialog-body">
<div v-if="canUpload" class="upload-btn-container">
<el-button type="primary" icon="el-icon-upload" v-hasPermi="['finance:receive:upload']" @click="openUploadDialog">{{ titleText }}</el-button>
</div>
<el-timeline v-if="attachments.length > 0">
<el-timeline-item
v-for="attachment in attachments"
:key="attachment.id"
:timestamp="parseTime(attachment.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
placement="top"
>
<el-card>
<div class="receipt-card-content">
<div class="receipt-details">
<div class="detail-item">
<span class="item-label">付款方式</span>
<span class="item-value"><dict-tag :options="dict.type.payment_method" :value="receiptData.receiptMethod"/></span>
</div>
<div class="detail-item">
<span class="item-label">{{ titleText }}</span>
<div class="item-value">
<div class="image-wrapper">
<el-image
v-if="!isPdf(attachment.filePath)"
:src="getImageUrl(attachment.filePath)"
:preview-src-list="previewList"
style="width: 200px; height: 150px;"
fit="contain"
></el-image>
<div v-else-if="pdfUrls[attachment.filePath]" class="pdf-thumbnail-container" @click="openPdfPreview(pdfUrls[attachment.filePath])">
<iframe :src="pdfUrls[attachment.filePath]" width="100%" height="150px" frameborder="0"></iframe>
<div class="pdf-hover-overlay">
<i class="el-icon-zoom-in"></i>
</div>
</div>
<div v-if="attachment.delFlag === '2'" class="void-overlay"></div>
</div>
<el-button
size="mini"
type="primary"
class="download-btn"
icon="el-icon-download"
@click="downloadFile(attachment)"
>下载{{ titleText }}</el-button>
</div>
</div>
<div class="detail-item">
<span class="item-label">收款总额</span>
<span class="item-value">{{ formatCurrency(receiptData.totalPriceWithTax) }}</span>
</div>
<div class="detail-item">
<span class="item-label">备注</span>
<span class="item-value">{{ attachment.remark }}</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else :description="'暂无' + titleText"></el-empty>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
<!-- PDF Preview Dialog -->
<el-dialog
:visible.sync="pdfPreviewVisible"
width="80%"
top="5vh"
append-to-body
custom-class="pdf-preview-dialog"
>
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<!-- Upload Dialog -->
<el-dialog
:title="'上传' + titleText"
:visible.sync="uploadDialogVisible"
width="70vw"
append-to-body
@close="closeUploadDialog"
custom-class="upload-receipt-dialog"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form :model="uploadForm" ref="uploadForm" :rules="rules" label-width="120px" size="medium" >
<el-form-item label="票据类型" prop="receiptMethod" >
<dict-tag :options="dict.type.payment_method" :value="receiptData.receiptMethod"/>
</el-form-item>
<el-form-item label="收款附件" prop="file" required>
<div style="display: flex; flex-direction: column; align-items: flex-start;">
<el-upload
ref="upload"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:show-file-list="false"
accept=".jpg,.jpeg,.png,.pdf"
>
<el-button size="small" type="primary" icon="el-icon-upload2">{{ uploadForm.file ? '重新上传' : '点击上传' }}</el-button>
</el-upload>
<div class="el-upload__tip" style="line-height: 1.5; margin-top: 5px;">支持上传PNGJPGPDF文件格式</div>
</div>
</el-form-item>
<el-form-item label="含税总价">
<span>{{ receiptData.totalPriceWithTax }}</span>
</el-form-item>
<el-form-item label="确认含税总价">
<el-input v-model="uploadForm.totalPriceWithTax" ></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
type="textarea"
v-model="uploadForm.remark"
:rows="4"
placeholder="此处备注描述..."
></el-input>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<div class="upload-preview-container" style="height: 70vh;">
<div v-if="previewUrl" class="preview-content">
<img v-if="!isPreviewPdf" :src="previewUrl" class="preview-image" />
<iframe v-else :src="previewUrl" width="100%" height="100%" frameborder="0"></iframe>
</div>
<div v-else class="preview-placeholder">
<div class="placeholder-icon">
<i class="el-icon-picture"></i>
</div>
<div class="placeholder-text">点击图片进入预览</div>
</div>
</div>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitNewUpload"></el-button>
<el-button @click="closeUploadDialog"></el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import { getReceiveAttachments, uploadReceiveAttachment } from "@/api/finance/receive";
import request from '@/utils/request';
export default {
name: "ReceiveDialog",
props: {
visible: {
type: Boolean,
default: false,
},
receiptData: {
type: Object,
default: () => {},
},
},
dicts:['payment_method'],
data() {
return {
loading: false,
attachments: [],
// Upload Dialog Data
uploadDialogVisible: false,
uploadForm: {
ticketType: '',
remark: '',
file: null
},
rules: {
},
previewUrl: '',
isPreviewPdf: false,
// PDF Preview Data
pdfUrls: {},
pdfPreviewVisible: false,
currentPdfUrl: '',
};
},
computed: {
dialogVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit("update:visible", val);
},
},
previewList() {
return this.attachments
.filter(att => !this.isPdf(att.filePath))
.map(att => this.getImageUrl(att.filePath));
},
canUpload() {
if (!this.attachments || this.attachments.length === 0) {
return true;
}
return this.attachments.every(att => att.delFlag === '2');
},
titleText() {
return '收款附件';
}
},
watch: {
visible(val) {
if (val && this.receiptData) {
this.fetchAttachments();
}
},
},
methods: {
fetchAttachments() {
if (!this.receiptData.id) return;
this.loading = true;
getReceiveAttachments(this.receiptData.id, { type: 'receipt' })
.then(response => {
const data = response.data || [];
data.sort((a, b) => new Date(b.createTime) - new Date(a.createTime));
this.attachments = data;
this.loadPdfPreviews();
this.loading = false;
})
.catch(() => {
this.attachments = [];
this.loading = false;
});
},
loadPdfPreviews() {
this.attachments.forEach(att => {
if (this.isPdf(att.filePath) && !this.pdfUrls[att.filePath]) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: att.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
this.$set(this.pdfUrls, att.filePath, url);
}).catch(console.error);
}
});
},
openPdfPreview(url) {
if (!url) return;
this.currentPdfUrl = url;
this.pdfPreviewVisible = true;
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
downloadFile(attachment) {
const link = document.createElement('a');
link.href = this.getImageUrl(attachment.filePath);
link.download = attachment.fileName || 'receive_attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
handleClose() {
this.attachments = [];
// Clean up object URLs
Object.values(this.pdfUrls).forEach(url => URL.revokeObjectURL(url));
this.pdfUrls = {};
},
// New Upload Dialog Methods
openUploadDialog() {
this.uploadForm = {
remark: '',
file: null
};
if (this.$refs.uploadForm) {
this.$refs.uploadForm.clearValidate();
}
this.previewUrl = '';
this.isPreviewPdf = false;
this.uploadDialogVisible = true;
},
closeUploadDialog() {
this.uploadDialogVisible = false;
this.uploadForm.file = null;
this.previewUrl = '';
},
handleFileChange(file) {
const isLt2M = file.size / 1024 / 1024 < 2;
const isAcceptedType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.raw.type);
if (!isAcceptedType) {
this.$message.error('上传文件只能是 JPG/PNG/PDF 格式!');
return;
}
if (!isLt2M) {
this.$message.error('上传文件大小不能超过 2MB!');
return;
}
this.uploadForm.file = file.raw;
this.isPreviewPdf = file.raw.type === 'application/pdf';
this.previewUrl = URL.createObjectURL(file.raw);
},
handleFileRemove() {
this.uploadForm.file = null;
this.previewUrl = '';
},
submitNewUpload() {
this.$refs.uploadForm.validate(valid => {
if (valid) {
if (!this.uploadForm.file) {
this.$message.warning("请选择要上传的文件");
return;
}
const formData = new FormData();
formData.append("file", this.uploadForm.file);
formData.append("relatedBillId", this.receiptData.id);
formData.append("remark", this.uploadForm.remark);
uploadReceiveAttachment(formData)
.then(response => {
this.$message.success("上传成功");
this.closeUploadDialog();
this.fetchAttachments();
})
.catch(error => {
this.$message.error("上传失败");
});
}
});
},
},
};
</script>
<style scoped>
.receipt-dialog-body {
max-height: 60vh;
overflow-y: auto;
}
.loading-spinner {
text-align: center;
font-size: 24px;
padding: 20px;
}
.receipt-card-content {
display: flex;
}
.receipt-details {
flex-grow: 1;
}
.detail-item {
display: flex;
margin-bottom: 12px;
font-size: 14px;
}
.item-label {
width: 110px;
color: #606266;
text-align: right;
margin-right: 15px;
flex-shrink: 0;
}
.item-value {
color: #303133;
}
.image-wrapper {
position: relative;
width: 200px;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #DCDFE6;
border-radius: 4px;
margin-bottom: 10px;
}
.void-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-30deg);
color: red;
font-size: 48px;
font-weight: bold;
opacity: 0.7;
pointer-events: none;
}
.download-btn {
display: block;
}
.upload-btn-container {
margin-bottom: 20px;
}
/* New Dialog Styles */
.upload-preview-container {
width: 100%;
height: 300px;
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.preview-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-placeholder {
text-align: center;
color: #909399;
}
.placeholder-icon {
font-size: 64px;
margin-bottom: 10px;
color: #c0c4cc;
}
.placeholder-text {
font-size: 14px;
}
.pdf-thumbnail-container {
position: relative;
width: 100%;
height: 150px;
cursor: pointer;
}
.pdf-hover-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
color: #fff;
font-size: 24px;
pointer-events: auto;
}
.pdf-thumbnail-container:hover .pdf-hover-overlay {
opacity: 1;
}
</style>

View File

@ -0,0 +1,402 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="150px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="销售-收款单编号" prop="receiptBillCode">
<el-input
v-model="queryParams.receiptBillCode"
placeholder="请输入收款单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="进货商名称" prop="partnerName">
<el-input
v-model="queryParams.partnerName"
placeholder="请输入进货商名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="销售-应收单编号" prop="receivableBillCode">
<el-input
v-model="queryParams.receivableBillCode"
placeholder="请输入应收单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="备注" prop="receiptBillType">
<el-select v-model="queryParams.receiptBillType" placeholder="请选择" clearable>
<el-option v-for="dict in dict.type.receipt_bill_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="支付方式" prop="receiptMethod">
<el-select v-model="queryParams.receiptMethod" placeholder="请选择" clearable>
<el-option v-for="dict in dict.type.payment_method" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="approveStatus">
<el-select v-model="queryParams.approveStatus" placeholder="请选择审批状态" clearable>
<el-option v-for="dict in dict.type.approve_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="收款状态" prop="receiveStatus">
<el-select v-model="queryParams.receiptStatus" placeholder="请选择收款状态" clearable>
<el-option v-for="dict in dict.type.receipt_bill_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="审批节点" prop="approveNode">
<el-input
v-model="queryParams.approveNode"
placeholder="请输入审批节点"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="审批通过时间">
<el-date-picker
v-model="dateRangeApproval"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item label="预计收款时间">
<el-date-picker
v-model="dateRangeEstimated"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item label="实际收款时间">
<el-date-picker
v-model="dateRangeActual"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="receiveList">
<el-table-column label="销售-收款单编号" align="center" prop="receiptBillCode" width="180" />
<el-table-column label="预计收款时间" align="center" prop="receiptTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.receiptTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="进货商名称" align="center" prop="partnerName" width="230" />
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="180" >
<template slot-scope="scope">
<span><span :style="scope.row.totalPriceWithTax<0?{color:'red'}:{}">{{ formatCurrency(scope.row.totalPriceWithTax) }} </span></span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="receiptBillType" width="180">
<template slot-scope="scope">
<dict-tag :options="dict.type.receipt_bill_type" :value="scope.row.receiptBillType"/>
</template>
</el-table-column>
<el-table-column label="预收单剩余额度" align="center" prop="remainingAmount" width="180" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款状态" align="center" prop="receiveStatus" width="180">
<template slot-scope="scope">
<dict-tag :options="dict.type.receipt_bill_status" :value="scope.row.receiptStatus"/>
</template>
</el-table-column>
<el-table-column label="审批状态" align="center" prop="approveStatus" width="180">
<template slot-scope="scope">
<dict-tag :options="dict.type.approve_status" :value="scope.row.approveStatus"/>
</template>
</el-table-column>
<el-table-column label="审批通过时间" align="center" prop="approveTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" width="200" fixed="right" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
v-show="(scope.row.receiptBillType==='FROM_RECEIVABLE' && scope.row.approveStatus!=='0') ||
(scope.row.receiptBillType!=='FROM_RECEIVABLE' && scope.row.approveStatus==='2') "
@click="handleReceipt(scope.row)"
>{{scope.row.receiptBillType!=='REFUND'?'客户付款图':'退款回执单'}}</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-money"
v-show=" scope.row.receiptStatus==='1' && scope.row.approveStatus==='0'"
@click="handleApplyPayment(scope.row)"
>申请收款</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-money"
v-show="scope.row.receiptBillType==='FROM_RECEIVABLE' && scope.row.receiptStatus==='2' && scope.row.approveStatus==='2'"
@click="handleApplyRefund(scope.row)"
>申请退款</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
v-show="scope.row.receiptBillType==='FROM_RECEIVABLE' && scope.row.receiptStatus==='1' && scope.row.approveStatus==='0' "
@click="handleReturn(scope.row)"
>退回</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-right"
v-if="(scope.row.approveStatus === '2' || scope.row.approveStatus==='3') && (scope.row.receiptStatus==='1'||scope.row.receiptStatus==='3')"
@click="handleRevoke(scope.row)"
>撤销</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 详情抽屉 -->
<detail-drawer :visible.sync="detailOpen" :detail="detailData"></detail-drawer>
<!-- 新增弹窗 -->
<add-form :visible.sync="addOpen" :dicts="dict.type" @submit="handleAddSubmit"></add-form>
<!-- 收款附件弹窗 -->
<receive-dialog :visible.sync="receiptOpen" :receipt-data="currentRow" :dicts="dict.type"></receive-dialog>
<!-- 申请付款弹窗 -->
<apply-payment-dialog :visible.sync="applyPaymentOpen" :receipt-data="currentRow" :dicts="dict.type" @submit="getList"></apply-payment-dialog>
<!-- 申请退款弹窗 -->
<apply-refund-dialog :visible.sync="applyRefundOpen" :receipt-data="currentRow" :dicts="dict.type" @submit="getList"></apply-refund-dialog>
</div>
</template>
<script>
import {listReceive, getReceive, redRush, mergeReceivable, returnReceive, addReceipt, revokeReceipt} from "@/api/finance/receive";
import { addDateRange } from "@/utils/ruoyi";
import DetailDrawer from "./components/DetailDrawer.vue";
import AddForm from "./components/AddForm.vue";
import ReceiveDialog from "./components/ReceiveDialog.vue";
import ApplyPaymentDialog from "./components/ApplyPaymentDialog.vue";
import ApplyRefundDialog from "./components/ApplyRefundDialog.vue";
export default {
name: "Receive",
components: {
DetailDrawer,
AddForm,
ReceiveDialog,
ApplyPaymentDialog,
ApplyRefundDialog
},
dicts:['receipt_bill_type','approve_status','receipt_bill_status', 'payment_method'],
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
receiveList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
receiptBillCode: null,
partnerName: null,
receivableBillCode: null,
receiptBillType: null,
approveStatus: null,
receiptStatus: null,
approveNode: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
dateRangeApproval: [],
dateRangeEstimated: [],
dateRangeActual: [],
//
detailOpen: false,
detailData: null,
//
addOpen: false,
//
receiptOpen: false,
currentRow: {},
//
applyPaymentOpen: false,
// 退
applyRefundOpen: false,
};
},
created() {
this.getList();
},
methods: {
addDateRange,
getList() {
this.loading = true;
let query = { ...this.queryParams };
query = this.addDateRange(query, this.dateRangeApproval, 'ApproveTime');
query = this.addDateRange(query, this.dateRangeEstimated, 'PlanReceiveTime');
query = this.addDateRange(query, this.dateRangeActual, 'ActualReceiveTime');
listReceive(query).then(response => {
this.receiveList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeApproval = [];
this.dateRangeEstimated = [];
this.dateRangeActual = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.addOpen = true;
},
/** 新增提交 */
handleAddSubmit(form) {
if (form.receiptBillType==='FROM_RECEIVABLE'){
mergeReceivable(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("发起收款单失败", error);
this.$modal.msgError("新增失败");
});
}else{
addReceipt(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("新增收款单失败", error);
this.$modal.msgError("新增失败");
});
}
},
/** 详情按钮操作 */
handleDetail(row) {
getReceive(row.id).then(response => {
this.detailData = response.data;
this.detailData.approveNode = row.approveNode;
this.detailOpen = true;
});
},
/** 申请付款按钮操作 */
handleApplyPayment(row) {
this.currentRow = row;
this.applyPaymentOpen = true;
},
/** 申请退款按钮操作 */
handleApplyRefund(row) {
this.currentRow = row;
this.applyRefundOpen = true;
},
/** 收款附件按钮操作 */
handleReceipt(row) {
this.currentRow = row;
this.receiptOpen = true;
},
/** 撤销按钮操作 */
handleRevoke(row) {
let msg = row.receiptBillType === 'REFUND' ? '是否将该笔销售-退款单撤销,将退款单撤销至收款单' : '是否将该笔销售-收款单撤销,撤销至收款单未审批状态';
this.$modal.confirm(msg).then(() => {
return revokeReceipt(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("撤销成功");
}).catch(() => {});
},
/** 退回按钮操作 */
handleRedRush(row) {
this.$modal.confirm('是否确认收款单编号为"' + row.receiptBillCode + '"的数据项进行红冲,并提交财务审批?').then(function() {
return redRush(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("申请成功");
}).catch(() => {});
},
handleReturn(row) {
this.$modal.confirm('是否将该笔销售-应收单的:销售-收款单与销售-发票单同时退回至销售-应收单表').then(function() {
return returnReceive(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("退回成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,284 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="计收状态" prop="chargeStatus">
<el-select
v-model="queryParams.chargeStatus"
placeholder="请输入计收状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.charge_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="收款状态" prop="receiptStatus">
<el-select
v-model="queryParams.receiptStatus"
placeholder="请输入收款状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_receipt_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="开票状态" prop="invoiceStatus">
<el-select
v-model="queryParams.invoiceStatus"
placeholder="请输入开票状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_invoice_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['finance:report:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="reportList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目" align="center" prop="orderCode" width="180" v-if="columns.projectCode.visible || columns.projectName.visible" key="orderCode">
<el-table-column label="项目编号" align="center" prop="projectCode" width="120" v-if="columns.projectCode.visible" key="receivableWithTax"/>
<el-table-column label="项目名称" align="center" prop="projectName" width="120" v-if="columns.projectName.visible" key="receivableWithoutTax"/>
</el-table-column>
<!-- 应收 -->
<el-table-column label="应收单" align="center" v-if="columns.receivableWithTax.visible || columns.receivableWithoutTax.visible||columns.receivableTax.visible">
<el-table-column label="应收含税总价" align="center" prop="receivableWithTax" width="120" v-if="columns.receivableWithTax.visible" key="receivableWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应收未税总价" align="center" prop="receivableWithoutTax" width="120" v-if="columns.receivableWithoutTax.visible" key="receivableWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应收税额" align="center" prop="receivableTax" width="120" v-if="columns.receivableTax.visible" key="receivableTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 毛利 -->
<el-table-column label="计收统计" align="center" v-if="columns.chargeStatus.visible || columns.chargedWithoutTax.visible || columns.grossProfit.visible ||columns.grossProfitRate.visible">
<el-table-column label="计收状态" align="center" prop="chargeStatus" width="100" v-if="columns.chargeStatus.visible" key="chargeStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.charge_status" :value="scope.row.chargeStatus"/>
</template>
</el-table-column>
<el-table-column label="已计收未税金额" align="center" prop="chargedWithoutTax" width="120" v-if="columns.chargedWithoutTax.visible" key="chargedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="毛利" align="center" prop="grossProfit" width="120" v-if="columns.grossProfit.visible" key="grossProfit" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="毛利率" align="center" prop="grossProfitRate" width="100" v-if="columns.grossProfitRate.visible" key="grossProfitRate"/>
</el-table-column>
<!-- <el-table-column label="业务计收时间" align="center" prop="bizChargeDate" width="180" v-if="columns.bizChargeDate.visible" key="bizChargeDate">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.bizChargeDate, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="财务计收时间" align="center" prop="financeChargeDate" width="180" v-if="columns.financeChargeDate.visible" key="financeChargeDate">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.financeChargeDate, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- 收款 -->
<el-table-column label="收款单" align="center" >
<el-table-column label="收款状态" align="center" prop="receiptStatus" width="100" v-if="columns.receiptStatus.visible" key="receiptStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_receipt_status" :value="scope.row.receiptStatus"/>
</template>
</el-table-column>
<el-table-column label="已收款含税金额" align="center" prop="receivedWithTax" width="120" v-if="columns.receivedWithTax.visible" key="receivedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收款未税金额" align="center" prop="receivedWithoutTax" width="120" v-if="columns.receivedWithoutTax.visible" key="receivedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收款税额" align="center" prop="receivedTax" width="120" v-if="columns.receivedTax.visible" key="receivedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中含税金额" align="center" prop="receivingWithTax" width="120" v-if="columns.receivingWithTax.visible" key="receivingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中未税金额" align="center" prop="receivingWithoutTax" width="120" v-if="columns.receivingWithoutTax.visible" key="receivingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中税额" align="center" prop="receivingTax" width="120" v-if="columns.receivingTax.visible" key="receivingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款含税金额" align="center" prop="unreceivedWithTax" width="120" v-if="columns.unreceivedWithTax.visible" key="unreceivedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款未税金额" align="center" prop="unreceivedWithoutTax" width="120" v-if="columns.unreceivedWithoutTax.visible" key="unreceivedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款税额" align="center" prop="unreceivedTax" width="120" v-if="columns.unreceivedTax.visible" key="unreceivedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 开票 -->
<el-table-column label="开票单" align="center">
<el-table-column label="开票状态" align="center" prop="invoiceStatus" width="100" v-if="columns.invoiceStatus.visible" key="invoiceStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_invoice_status" :value="scope.row.invoiceStatus"/>
</template>
</el-table-column>
<el-table-column label="已开票含税金额" align="center" prop="invoicedWithTax" width="120" v-if="columns.invoicedWithTax.visible" key="invoicedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已开票未税金额" align="center" prop="invoicedWithoutTax" width="120" v-if="columns.invoicedWithoutTax.visible" key="invoicedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已开票税额" align="center" prop="invoicedTax" width="120" v-if="columns.invoicedTax.visible" key="invoicedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="开票中含税金额" align="center" prop="invoicingWithTax" width="120" v-if="columns.invoicingWithTax.visible" key="invoicingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="开票中未税金额" align="center" prop="invoicingWithoutTax" width="120" v-if="columns.invoicingWithoutTax.visible" key="invoicingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="开票中税额" align="center" prop="invoicingTax" width="120" v-if="columns.invoicingTax.visible" key="invoicingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票含税金额" align="center" prop="uninvoicedWithTax" width="120" v-if="columns.uninvoicedWithTax.visible" key="uninvoicedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票未税金额" align="center" prop="uninvoicedWithoutTax" width="120" v-if="columns.uninvoicedWithoutTax.visible" key="uninvoicedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票税额" align="center" prop="uninvoicedTax" width="120" v-if="columns.uninvoicedTax.visible" key="uninvoicedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listReport } from "@/api/finance/report";
export default {
name: "ReceiveReport",
dicts: ['charge_status', 'report_receipt_status', 'report_invoice_status'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
reportList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
orderCode: null,
projectCode: null,
projectName: null,
chargeStatus: null,
receiptStatus: null,
invoiceStatus: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
columns: {
projectCode: { label: "项目编号", visible: true },
projectName: { label: "项目名称", visible: true },
receivableWithTax: { label: "应收含税总价", visible: true },
receivableWithoutTax: { label: "应收未税总价", visible: true },
receivableTax: { label: "应收税额", visible: true },
chargeStatus: { label: "计收状态", visible: true },
chargedWithoutTax: { label: "已计收未税金额", visible: true },
grossProfit: { label: "毛利", visible: true },
grossProfitRate: { label: "毛利率", visible: true },
// bizChargeDate: { label: "", visible: true },
// financeChargeDate: { label: "", visible: true },
receiptStatus: { label: "收款状态", visible: true },
receivedWithTax: { label: "已收款含税金额", visible: true },
receivedWithoutTax: { label: "已收款未税金额", visible: true },
receivedTax: { label: "已收款税额", visible: true },
receivingWithTax: { label: "收款中含税金额", visible: true },
receivingWithoutTax: { label: "收款中未税金额", visible: true },
receivingTax: { label: "收款中税额", visible: true },
unreceivedWithTax: { label: "未收款含税金额", visible: true },
unreceivedWithoutTax: { label: "未收款未税金额", visible: true },
unreceivedTax: { label: "未收款税额", visible: true },
invoiceStatus: { label: "开票状态", visible: true },
invoicedWithTax: { label: "已开票含税金额", visible: true },
invoicedWithoutTax: { label: "已开票未税金额", visible: true },
invoicedTax: { label: "已开票税额", visible: true },
invoicingWithTax: { label: "开票中含税金额", visible: true },
invoicingWithoutTax: { label: "开票中未税金额", visible: true },
invoicingTax: { label: "开票中税额", visible: true },
uninvoicedWithTax: { label: "未开票含税金额", visible: true },
uninvoicedWithoutTax: { label: "未开票未税金额", visible: true },
uninvoicedTax: { label: "未开票税额", visible: true },
}
};
},
created() {
this.getList();
},
methods: {
/** 查询项目报表列表 */
getList() {
this.loading = true;
listReport(this.queryParams).then(response => {
this.reportList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 导出按钮操作 */
handleExport() {
this.download('finance/report/export', {
...this.queryParams
}, `report_${new Date().getTime()}.xlsx`)
}
}
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,382 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
placeholder="请输入项目编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="计收状态" prop="chargeStatus">
<el-select
v-model="queryParams.chargeStatus"
placeholder="请输入计收状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.charge_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="收款状态" prop="receiptStatus">
<el-select
v-model="queryParams.receiptStatus"
placeholder="请输入收款状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_receipt_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="开票状态" prop="invoiceStatus">
<el-select
v-model="queryParams.invoiceStatus"
placeholder="请输入开票状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_invoice_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="付款状态" prop="paymentStatus">
<el-select
v-model="queryParams.paymentStatus"
placeholder="请输入付款状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_payment_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="收票状态" prop="ticketStatus">
<el-select
v-model="queryParams.ticketStatus"
placeholder="请输入收票状态"
clearable
@keyup.enter.native="handleQuery"
>
<el-option
v-for="dict in dict.type.report_ticket_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['finance:report:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="reportList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目" align="center" prop="orderCode" width="180" v-if="columns.projectCode.visible || columns.projectName.visible" key="orderCode">
<el-table-column label="项目编号" align="center" prop="projectCode" width="120" v-if="columns.projectCode.visible" key="receivableWithTax"/>
<el-table-column label="项目名称" align="center" prop="projectName" width="120" v-if="columns.projectName.visible" key="receivableWithoutTax"/>
</el-table-column>
<!-- 应收 -->
<el-table-column label="应收单" align="center" v-if="columns.receivableWithTax.visible || columns.receivableWithoutTax.visible||columns.receivableTax.visible">
<el-table-column label="应收含税总价" align="center" prop="receivableWithTax" width="120" v-if="columns.receivableWithTax.visible" key="receivableWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应收未税总价" align="center" prop="receivableWithoutTax" width="120" v-if="columns.receivableWithoutTax.visible" key="receivableWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应收税额" align="center" prop="receivableTax" width="120" v-if="columns.receivableTax.visible" key="receivableTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 毛利 -->
<el-table-column label="计收统计" align="center" v-if="columns.chargeStatus.visible || columns.chargedWithoutTax.visible || columns.grossProfit.visible ||columns.grossProfitRate.visible">
<el-table-column label="计收状态" align="center" prop="chargeStatus" width="100" v-if="columns.chargeStatus.visible" key="chargeStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.charge_status" :value="scope.row.chargeStatus"/>
</template>
</el-table-column>
<el-table-column label="已计收未税金额" align="center" prop="chargedWithoutTax" width="120" v-if="columns.chargedWithoutTax.visible" key="chargedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="毛利" align="center" prop="grossProfit" width="120" v-if="columns.grossProfit.visible" key="grossProfit" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="毛利率" align="center" prop="grossProfitRate" width="100" v-if="columns.grossProfitRate.visible" key="grossProfitRate"/>
</el-table-column>
<!-- <el-table-column label="业务计收时间" align="center" prop="bizChargeDate" width="180" v-if="columns.bizChargeDate.visible" key="bizChargeDate">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.bizChargeDate, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="财务计收时间" align="center" prop="financeChargeDate" width="180" v-if="columns.financeChargeDate.visible" key="financeChargeDate">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.financeChargeDate, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- 收款 -->
<el-table-column label="收款单" align="center" >
<el-table-column label="收款状态" align="center" prop="receiptStatus" width="100" v-if="columns.receiptStatus.visible" key="receiptStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_receipt_status" :value="scope.row.receiptStatus"/>
</template>
</el-table-column>
<el-table-column label="已收款含税金额" align="center" prop="receivedWithTax" width="120" v-if="columns.receivedWithTax.visible" key="receivedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收款未税金额" align="center" prop="receivedWithoutTax" width="120" v-if="columns.receivedWithoutTax.visible" key="receivedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收款税额" align="center" prop="receivedTax" width="120" v-if="columns.receivedTax.visible" key="receivedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中含税金额" align="center" prop="receivingWithTax" width="120" v-if="columns.receivingWithTax.visible" key="receivingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中未税金额" align="center" prop="receivingWithoutTax" width="120" v-if="columns.receivingWithoutTax.visible" key="receivingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收款中税额" align="center" prop="receivingTax" width="120" v-if="columns.receivingTax.visible" key="receivingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款含税金额" align="center" prop="unreceivedWithTax" width="120" v-if="columns.unreceivedWithTax.visible" key="unreceivedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款未税金额" align="center" prop="unreceivedWithoutTax" width="120" v-if="columns.unreceivedWithoutTax.visible" key="unreceivedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收款税额" align="center" prop="unreceivedTax" width="120" v-if="columns.unreceivedTax.visible" key="unreceivedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 开票 -->
<el-table-column label="开票单" align="center">
<el-table-column label="开票状态" align="center" prop="invoiceStatus" width="100" v-if="columns.invoiceStatus.visible" key="invoiceStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_invoice_status" :value="scope.row.invoiceStatus"/>
</template>
</el-table-column>
<el-table-column label="已开票含税金额" align="center" prop="invoicedWithTax" width="120" v-if="columns.invoicedWithTax.visible" key="invoicedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已开票未税金额" align="center" prop="invoicedWithoutTax" width="120" v-if="columns.invoicedWithoutTax.visible" key="invoicedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已开票税额" align="center" prop="invoicedTax" width="120" v-if="columns.invoicedTax.visible" key="invoicedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="开票中含税金额" align="center" prop="invoicingWithTax" width="120" v-if="columns.invoicingWithTax.visible" key="invoicingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="开票中未税金额" align="center" prop="invoicingWithoutTax" width="120" v-if="columns.invoicingWithoutTax.visible" key="invoicingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="开票中税额" align="center" prop="invoicingTax" width="120" v-if="columns.invoicingTax.visible" key="invoicingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票含税金额" align="center" prop="uninvoicedWithTax" width="120" v-if="columns.uninvoicedWithTax.visible" key="uninvoicedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票未税金额" align="center" prop="uninvoicedWithoutTax" width="120" v-if="columns.uninvoicedWithoutTax.visible" key="uninvoicedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未开票税额" align="center" prop="uninvoicedTax" width="120" v-if="columns.uninvoicedTax.visible" key="uninvoicedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 应付 -->
<el-table-column label="应付单" align="center">
<el-table-column label="应付含税金额" align="center" prop="payableWithTax" width="120" v-if="columns.payableWithTax.visible" key="payableWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应付未税金额" align="center" prop="payableWithoutTax" width="120" v-if="columns.payableWithoutTax.visible" key="payableWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="应付税额" align="center" prop="payableTax" width="120" v-if="columns.payableTax.visible" key="payableTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 付款 -->
<el-table-column label="付款单" align="center">
<el-table-column label="付款状态" align="center" prop="paymentStatus" width="100" v-if="columns.paymentStatus.visible" key="paymentStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_payment_status" :value="scope.row.paymentStatus"/>
</template>
</el-table-column>
<el-table-column label="已付款含税金额" align="center" prop="paidWithTax" width="120" v-if="columns.paidWithTax.visible" key="paidWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已付款未税金额" align="center" prop="paidWithoutTax" width="120" v-if="columns.paidWithoutTax.visible" key="paidWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已付款税额" align="center" prop="paidTax" width="120" v-if="columns.paidTax.visible" key="paidTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="付款中含税金额" align="center" prop="payingWithTax" width="120" v-if="columns.payingWithTax.visible" key="payingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="付款中未税金额" align="center" prop="payingWithoutTax" width="120" v-if="columns.payingWithoutTax.visible" key="payingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="付款中税额" align="center" prop="payingTax" width="120" v-if="columns.payingTax.visible" key="payingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未付款含税金额" align="center" prop="unpaidWithTax" width="120" v-if="columns.unpaidWithTax.visible" key="unpaidWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未付款未税金额" align="center" prop="unpaidWithoutTax" width="120" v-if="columns.unpaidWithoutTax.visible" key="unpaidWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未付款税额" align="center" prop="unpaidTax" width="120" v-if="columns.unpaidTax.visible" key="unpaidTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
<!-- 收票 -->
<el-table-column label="收票单" align="center">
<el-table-column label="收票状态" align="center" prop="ticketStatus" width="100" v-if="columns.ticketStatus.visible" key="ticketStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.report_ticket_status" :value="scope.row.ticketStatus"/>
</template>
</el-table-column>
<el-table-column label="已收票含税金额" align="center" prop="ticketedWithTax" width="120" v-if="columns.ticketedWithTax.visible" key="ticketedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收票未税金额" align="center" prop="ticketedWithoutTax" width="120" v-if="columns.ticketedWithoutTax.visible" key="ticketedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已收票税额" align="center" prop="ticketedTax" width="120" v-if="columns.ticketedTax.visible" key="ticketedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中含税金额" align="center" prop="ticketingWithTax" width="120" v-if="columns.ticketingWithTax.visible" key="ticketingWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中未税金额" align="center" prop="ticketingWithoutTax" width="120" v-if="columns.ticketingWithoutTax.visible" key="ticketingWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="收票中税额" align="center" prop="ticketingTax" width="120" v-if="columns.ticketingTax.visible" key="ticketingTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票含税金额" align="center" prop="unticketedWithTax" width="120" v-if="columns.unticketedWithTax.visible" key="unticketedWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票未税金额" align="center" prop="unticketedWithoutTax" width="120" v-if="columns.unticketedWithoutTax.visible" key="unticketedWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="未收票税额" align="center" prop="unticketedTax" width="120" v-if="columns.unticketedTax.visible" key="unticketedTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listReport } from "@/api/finance/report";
export default {
name: "Report",
dicts: ['charge_status', 'report_receipt_status', 'report_invoice_status', 'report_payment_status', 'report_ticket_status'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
reportList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
orderCode: null,
projectCode: null,
projectName: null,
chargeStatus: null,
receiptStatus: null,
invoiceStatus: null,
paymentStatus: null,
ticketStatus: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
columns: {
projectCode: { label: "项目编号", visible: true },
projectName: { label: "项目名称", visible: true },
receivableWithTax: { label: "应收含税总价", visible: true },
receivableWithoutTax: { label: "应收未税总价", visible: true },
receivableTax: { label: "应收税额", visible: true },
chargeStatus: { label: "计收状态", visible: true },
chargedWithoutTax: { label: "已计收未税金额", visible: true },
grossProfit: { label: "毛利", visible: true },
grossProfitRate: { label: "毛利率", visible: true },
// bizChargeDate: { label: "", visible: true },
// financeChargeDate: { label: "", visible: true },
receiptStatus: { label: "收款状态", visible: true },
receivedWithTax: { label: "已收款含税金额", visible: true },
receivedWithoutTax: { label: "已收款未税金额", visible: true },
receivedTax: { label: "已收款税额", visible: true },
receivingWithTax: { label: "收款中含税金额", visible: true },
receivingWithoutTax: { label: "收款中未税金额", visible: true },
receivingTax: { label: "收款中税额", visible: true },
unreceivedWithTax: { label: "未收款含税金额", visible: true },
unreceivedWithoutTax: { label: "未收款未税金额", visible: true },
unreceivedTax: { label: "未收款税额", visible: true },
invoiceStatus: { label: "开票状态", visible: true },
invoicedWithTax: { label: "已开票含税金额", visible: true },
invoicedWithoutTax: { label: "已开票未税金额", visible: true },
invoicedTax: { label: "已开票税额", visible: true },
invoicingWithTax: { label: "开票中含税金额", visible: true },
invoicingWithoutTax: { label: "开票中未税金额", visible: true },
invoicingTax: { label: "开票中税额", visible: true },
uninvoicedWithTax: { label: "未开票含税金额", visible: true },
uninvoicedWithoutTax: { label: "未开票未税金额", visible: true },
uninvoicedTax: { label: "未开票税额", visible: true },
payableWithTax: { label: "应付含税总价", visible: true },
payableWithoutTax: { label: "应付未税总价", visible: true },
payableTax: { label: "应付税额", visible: true },
paymentStatus: { label: "付款状态", visible: true },
paidWithTax: { label: "已付款含税金额", visible: true },
paidWithoutTax: { label: "已付款未税金额", visible: true },
paidTax: { label: "已付款税额", visible: true },
payingWithTax: { label: "付款中含税金额", visible: true },
payingWithoutTax: { label: "付款中未税金额", visible: true },
payingTax: { label: "付款中税额", visible: true },
unpaidWithTax: { label: "未付款含税金额", visible: true },
unpaidWithoutTax: { label: "未付款未税金额", visible: true },
unpaidTax: { label: "未付款税额", visible: true },
ticketStatus: { label: "收票状态", visible: true },
ticketedWithTax: { label: "已收票含税金额", visible: true },
ticketedWithoutTax: { label: "已收票未税金额", visible: true },
ticketedTax: { label: "已收票税额", visible: true },
ticketingWithTax: { label: "收票中含税金额", visible: true },
ticketingWithoutTax: { label: "收票中未税金额", visible: true },
ticketingTax: { label: "收票中税额", visible: true },
unticketedWithTax: { label: "未收票含税金额", visible: true },
unticketedWithoutTax: { label: "未收票未税金额", visible: true },
unticketedTax: { label: "未收票税额", visible: true },
}
};
},
created() {
this.getList();
},
methods: {
/** 查询项目报表列表 */
getList() {
this.loading = true;
listReport(this.queryParams).then(response => {
this.reportList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 导出按钮操作 */
handleExport() {
this.download('finance/report/export', {
...this.queryParams
}, `report_${new Date().getTime()}.xlsx`)
}
}
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,90 @@
<template>
<el-drawer
title="核销详情"
:visible.sync="visible"
direction="rtl"
size="60%"
@close="handleClose"
>
<div class="drawer-body" v-if="detail">
<div class="section">
<el-divider content-position="left">开票单信息</el-divider>
<el-table :data="[detail]" border stripe>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="writeOffAmount" label="本次核销含税总价(元)" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="partnerName" label="进货商名称" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="invoiceBillCode" label="销售-开票单编号" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="开票单生成时间" align="center" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.invoiceBill.createTime) }}</span>
</template>
</el-table-column>
<!-- <el-table-column prop="invoiceBillType" label="开票单类型" align="center">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.invoice_bill_type" :value="scope.row.invoiceBill.invoiceBillType"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
</el-table>
</div>
<div class="section">
<el-divider content-position="left">应收单明细</el-divider>
<el-table :data="detail.detailList" border stripe>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="receiptAmount" label="本次核销金额(元)" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="partnerName" label="进货商名称" align="center"></el-table-column>
<el-table-column prop="receivableBillCode" label="销售-应收单编号" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="应收单生成时间" align="center"></el-table-column>
</el-table>
</div>
</div>
</el-drawer>
</template>
<script>
export default {
name: "WriteOffDetailDrawer",
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => ({}),
},
},
dicts: ['finance_write_off_type'],
methods: {
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
.drawer-body {
padding: 20px;
height: calc(100vh - 80px);
overflow-y: auto;
}
.details-container {
border: 1px solid #EBEEF5;
padding: 15px;
border-radius: 4px;
background-color: #f8f8f9;
}
.detail-item {
margin-bottom: 12px;
font-size: 14px;
color: #606266;
line-height: 1.5;
}
.section {
margin-bottom: 25px;
}
</style>

View File

@ -0,0 +1,238 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="核销序号" prop="writeOffCode">
<el-input
v-model="queryParams.writeOffCode"
placeholder="请输入核销序号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="销售-应收单编号" prop="receivableBillCode">
<el-input
v-model="queryParams.receivableBillCode"
placeholder="请输入销售应收单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="销售-开票单编号" prop="invoiceBillCode">
<el-input
v-model="queryParams.invoiceBillCode"
placeholder="请输入销售开票单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="进货商名称" prop="partnerName">
<el-input
v-model="queryParams.partnerName"
placeholder="请输入进货商名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="核销类型" prop="writeOffType">
<el-select v-model="queryParams.writeOffType" placeholder="请选择核销类型" clearable>
<el-option
v-for="dict in dict.type.finance_write_off_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="核销时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>反核销</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="writeOffList" @selection-change="handleSelectionChange" row-key="id">
<el-table-column type="selection" width="55" align="center" :reserve-selection="true"/>
<el-table-column label="核销序号" align="center" prop="writeOffCode" />
<el-table-column label="核销人" align="center" prop="createByName" >
<template slot-scope="scope">
<span>{{ scope.row.createByName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="核销时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="销售开票单编号" align="center" prop="invoiceBillCode" />
<el-table-column label="代理商名称" align="center" prop="partnerName" />
<el-table-column label="核销类型" align="center" prop="writeOffType">
<template slot-scope="scope">
<dict-tag :options="dict.type.finance_write_off_type" :value="scope.row.writeOffType"/>
</template>
</el-table-column>
<el-table-column label="核销含税总价(元)" align="center" prop="writeOffAmount" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="核销未税总价(元)" align="center" prop="writeOffAmountWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)" />
<el-table-column label="核销税额(元)" align="center" prop="writeOffTaxAmount" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>反核销</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 详情抽屉 -->
<write-off-detail-drawer :visible.sync="open" :detail="form" />
</div>
</template>
<script>
import { listInvoiceWriteOff, getInvoiceWriteoff, delInvoiceWriteoff } from "@/api/finance/invoiceWriteoff";
import { listPartner } from "@/api/system/partner";
import WriteOffDetailDrawer from "./WriteOffDetailDrawer";
export default {
name: "WriteOffInvoiceRecord",
components: {
WriteOffDetailDrawer
},
dicts: ['finance_write_off_type'],
data() {
return {
//
loading: true,
//
ids: [],
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
writeOffList: [],
//
partnerOptions: [],
//
title: "",
//
open: false,
//
dateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
writeOffCode: null,
receivableBillCode: null,
invoiceBillCode: null,
partnerName: null,
writeOffType: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
form: {}
};
},
created() {
this.getList();
this.getPartnerList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
listInvoiceWriteOff(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.writeOffList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 查询代理商列表 */
getPartnerList() {
listPartner().then(response => {
this.partnerOptions = response.rows;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 查看详情 */
handleDetail(row) {
this.loading = true;
getInvoiceWriteoff(row.id).then(response => {
this.form = response.data;
this.open = true;
this.title = "核销详情";
this.loading = false;
});
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.multiple = !selection.length
},
/** 反核销按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认反核销核销序号为"' + ids + '"的数据项?').then(function() {
return delInvoiceWriteoff(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("反核销成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,89 @@
<template>
<el-drawer
title="核销详情"
:visible.sync="visible"
direction="rtl"
size="60%"
@close="handleClose"
>
<div class="drawer-body" v-if="detail">
<div class="section">
<el-divider content-position="left">付款单信息</el-divider>
<el-table :data="[detail]" border stripe>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="writeOffAmount" label="本次核销含税总价(元)" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="vendorName" label="制造商名称" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="paymentBillCode" label="采购付款单编号" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="paymentCreateTime" label="付款单生成时间" align="center" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.paymentBill.createTime) }}</span>
</template>
</el-table-column>
<el-table-column prop="paymentBillType" label="付款单类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.payment_bill_type" :value="scope.row.paymentBill.paymentBillType"/>
</template>
</el-table-column>
</el-table>
</div>
<div class="section"> <el-divider content-position="left">应付单明细</el-divider>
<el-table :data="detail.detailList" border stripe>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="paymentAmount" label="本次核销含税总价(元)" align="center" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"></el-table-column>
<el-table-column prop="vendorName" label="制造商名称" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="payableBillCode" label="采购应付单编号" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="应付单生成时间" align="center"></el-table-column>
</el-table>
</div>
</div>
</el-drawer>
</template>
<script>
export default {
name: "WriteOffDetailDrawer",
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => ({}),
},
},
dicts: ['finance_write_off_type', 'payment_bill_type'],
methods: {
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
.drawer-body {
padding: 20px;
height: calc(100vh - 80px);
overflow-y: auto;
}
.details-container {
border: 1px solid #EBEEF5;
padding: 15px;
border-radius: 4px;
background-color: #f8f8f9;
}
.detail-item {
margin-bottom: 12px;
font-size: 14px;
color: #606266;
line-height: 1.5;
}
.section {
margin-bottom: 25px;
}
</style>

View File

@ -0,0 +1,567 @@
<template>
<div class="app-container">
<!-- 顶部按钮工具栏 -->
<div style="margin-bottom: 20px;">
<el-button type="primary" icon="el-icon-search" size="small" @click="handleOpenPayableFilter">
</el-button>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleOpenPaymentFilter">
</el-button>
<el-button type="success" icon="el-icon-check" size="small" @click="handleManualWriteOff"></el-button>
</div>
<!-- 应付单过滤弹窗 -->
<el-dialog title="应付单查询条件" :visible.sync="payableFilterVisible" width="500px" append-to-body>
<el-form :model="queryParams" ref="payableQueryForm" size="small" label-width="120px">
<el-form-item label="制造商名称" prop="vendorName">
<el-select v-model="queryParams.vendorName" placeholder="请选择制造商名称" filterable clearable
style="width: 100%">
<el-option
v-for="item in vendorList"
:key="item.vendorCode"
:label="item.vendorName"
:value="item.vendorName"
/>
</el-select>
</el-form-item>
<el-form-item label="采购应付单编号" prop="payableBillCode">
<el-input v-model="queryParams.payableBillCode" placeholder="请输入采购应付单编号" clearable
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="应付单生成时间">
<el-date-picker
v-model="payableDateRange"
style="width: 100%"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button icon="el-icon-refresh" size="small" @click="resetPayableQuery"> </el-button>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery"> </el-button>
</div>
</el-dialog>
<!-- 付款单过滤弹窗 -->
<el-dialog title="付款单查询条件" :visible.sync="paymentFilterVisible" width="500px" append-to-body>
<el-form :model="queryParams" ref="paymentQueryForm" size="small" label-width="120px">
<el-form-item label="制造商名称" prop="vendorName">
<el-select v-model="queryParams.vendorName" placeholder="请选择制造商名称" filterable clearable
style="width: 100%">
<el-option
v-for="item in vendorList"
:key="item.vendorCode"
:label="item.vendorName"
:value="item.vendorName"
/>
</el-select>
</el-form-item>
<el-form-item label="采购付款单编号" prop="paymentCode">
<el-input v-model="queryParams.paymentCode" placeholder="请输入采购付款单编号" clearable
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="付款单生成时间">
<el-date-picker
v-model="paymentDateRange"
style="width: 100%"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button icon="el-icon-refresh" size="small" @click="resetPaymentQuery"> </el-button>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery"> </el-button>
</div>
</el-dialog>
<div v-if="showTables">
<!-- 采购应付单表格 -->
<h3>采购应付单</h3>
<div class="table-summary" style="margin-bottom: 10px;">
<span style="font-weight: bold; margin-right: 20px;">应付单本次核销总额: <span
style="color: #409EFF">{{ formatCurrency(totalPayableWriteOffAmount.toFixed(2)) }}</span></span>
</div>
<el-table
v-loading="loadingPayable"
:data="payableList"
border
@selection-change="handlePayableSelectionChange"
ref="payableTable"
row-key="id"
>
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="本次核销金额(元)" align="center" width="150">
<template slot-scope="scope">
<el-input
v-model="scope.row.currentWriteOffAmount"
placeholder="点击选择计划"
readonly
@click.native="handleOpenPlanSelector(scope.row)"
style="cursor: pointer;"
>
<i slot="suffix" class="el-input__icon el-icon-search"></i>
</el-input>
</template>
</el-table-column>
<el-table-column label="未核销含税总价(元)" align="center" prop="unpaidPaymentAmount" width="150"/>
<el-table-column label="未核销未税总价(元)" align="center" prop="unpaidAmount" width="150">
<template slot-scope="scope">
{{ calculateExcludingTax(scope.row.unpaidPaymentAmount, scope.row.taxRate) }}
</template>
</el-table-column>
<el-table-column label="未核销税额(元)" align="center" width="150">
<template slot-scope="scope">
{{ calculateTax(scope.row.unpaidPaymentAmount, scope.row.taxRate) }}
</template>
</el-table-column>
<el-table-column label="核销中含税总价(元)" align="center" prop="paidAmount" width="150">
<template slot-scope="scope">
{{
formatCurrency($calc.sub($calc.sub(scope.row.totalPriceWithTax, scope.row.paidPaymentAmount), scope.row.unpaidPaymentAmount))
}}
</template>
</el-table-column>
<el-table-column label="已核销含税总价(元)" align="center" prop="paidPaymentAmount" width="150" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已核销未税总价(元)" align="center" width="150">
<template slot-scope="scope">
{{ formatCurrency(calculateExcludingTax(scope.row.paidPaymentAmount, scope.row.taxRate)) }}
</template>
</el-table-column>
<el-table-column label="已核销税额(元)" align="center" width="120">
<template slot-scope="scope">
{{ formatCurrency(calculateTax(scope.row.paidPaymentAmount, scope.row.taxRate)) }}
</template>
</el-table-column>
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" width="120" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="制造商名称" fixed="right" align="center" prop="vendorName" width="150"
show-overflow-tooltip/>
<el-table-column label="采购应付单编号" fixed="right" align="center" prop="payableBillCode" width="150"
show-overflow-tooltip/>
<el-table-column label="生成时间" fixed="right" align="center" prop="createTime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="payableTotal>0"
:total="payableTotal"
:pageSizes="[5,10,20,50]"
:page.sync="payableQueryParams.pageNum"
:limit.sync="payableQueryParams.pageSize"
@pagination="getPayableList"
/>
<!-- 采购付款单表格 -->
<h3 style="margin-top: 30px;">采购付款单</h3>
<div class="table-summary" style="margin-bottom: 10px;">
<span style="font-weight: bold; margin-right: 20px;display: none">付款单本次核销总额: <span
:style="{color: isWriteOffAmountValid ? '#67C23A' : '#F56C6C'}">{{
totalPaymentWriteOffAmount.toFixed(2)
}}</span></span>
<span v-if="!isWriteOffAmountValid" style="color: #F56C6C; font-size: 12px;"><i class="el-icon-warning"></i> </span>
</div>
<el-table v-loading="loadingPayment" :data="paymentList" border highlight-current-row
@current-change="handlePaymentSelectionChange" row-key="id">
<el-table-column width="55" align="center">
<template slot-scope="scope">
<el-radio v-model="selectedPaymentId" :label="scope.row.paymentBillCode"><i></i></el-radio>
</template>
</el-table-column>
<el-table-column label="本次核销含税总价(元)" align="center" width="150">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.currentWriteOffAmount"
:precision="2"
:step="100"
:min="0"
:max="scope.row.unWrittenAmount || scope.row.paymentAmount"
size="small"
style="width: 100%"
:disabled="selectedPaymentId !== scope.row.paymentBillCode"
/>
</template>
</el-table-column>
<el-table-column label="未核销含税总价(元)" align="center" width="150">
<template slot-scope="scope">
{{ formatCurrency(scope.row.preResidueAmount || '0.00') }} <!-- Assuming paymentAmount is total initially -->
</template>
</el-table-column>
<el-table-column label="已核销含税总价(元)" align="center" prop="writeOffAmount" width="150" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已核销未税总价(元)" align="center" width="150" prop="writeOffAmountWithoutTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="已核销税额(元)" align="center" width="150" prop="writeOffTaxAmount" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="含税总价(元)" align="center" prop="totalPriceWithTax" :formatter="(row, column, cellValue)=>formatCurrency(cellValue)"/>
<el-table-column label="制造商名称" fixed="right" align="center" prop="vendorName" width="150"
show-overflow-tooltip/>
<el-table-column label="采购付款单号" fixed="right" align="center" prop="paymentBillCode" width="150"
show-overflow-tooltip/>
<el-table-column label="生成时间" fixed="right" align="center" prop="createTime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="paymentTotal>0"
:total="paymentTotal"
:page.sync="paymentQueryParams.pageNum"
:limit.sync="paymentQueryParams.pageSize"
@pagination="getPaymentList"
:pageSizes="[5,10,20,50]"
/>
</div>
<!-- 弹窗选择应付计划 -->
<el-dialog :title="planDialogTitle" :visible.sync="planDialogVisible" width="70%" append-to-body>
<payment-plan-selector
v-if="planDialogVisible"
ref="planSelector"
:payable-data="currentPayableRow"
:selected-plans="currentPayableRow ? currentPayableRow.selectedPlans : []"
:is-init-edit="false"
/>
<!-- Note: is-init-edit=false makes it read-only for editing plans generally, but we want to SELECT.
PaymentPlanSelector logic: 'selection-change' works.
If PaymentPlanSelector allows selection only in some mode, we need to check.
Looking at PaymentPlan.vue: <el-table ... @selection-change="selectPlan">
It seems selection is always enabled.
-->
<div slot="footer" class="dialog-footer">
<el-button @click="planDialogVisible = false"> </el-button>
<el-button type="primary" @click="handlePlanSelectionConfirm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {listPayable} from "@/api/finance/payable";
import {listPaymentForWriteOff, manualWriteOff} from "@/api/finance/payment";
import {listAllVendors} from "@/api/inventory/info";
import PaymentPlanSelector from "../../payable/components/PaymentPlan.vue";
export default {
name: "ManualWriteOff",
components: {PaymentPlanSelector},
dicts: ['payment_status', 'payment_bill_type'],
data() {
return {
//
payableFilterVisible: false,
paymentFilterVisible: false,
//
loadingPayable: false,
loadingPayment: false,
//
showTables: false,
//
queryParams: {
vendorName: undefined,
payableBillCode: undefined,
paymentCode: undefined,
},
payableDateRange: [],
paymentDateRange: [],
vendorList: [], //
//
payableList: [],
payableTotal: 0,
payableQueryParams: {
pageNum: 1,
pageSize: 5,
vendorName: undefined,
payableBillCode: undefined,
unpaidPaymentAmount: -1,
params: {}
},
selectedPayables: [], //
//
paymentList: [],
paymentTotal: 0,
paymentQueryParams: {
pageNum: 1,
pageSize: 5,
vendorName: undefined,
paymentCode: undefined,
paymentBillType: 'PRE_PAYMENT', //
paymentStatus: '2',
params: {}
},
selectedPaymentId: null, // ID (使 BillCode)
selectedPaymentRow: null, //
//
planDialogVisible: false,
currentPayableRow: null,
planDialogTitle: "选择应付计划"
};
},
watch: {
totalPayableWriteOffAmount(newVal) {
this.updatePaymentWriteOffAmount();
}
},
computed: {
totalPayableWriteOffAmount() {
return this.selectedPayables.reduce((sum, row) => sum + (row.currentWriteOffAmount || 0), 0);
},
totalPaymentWriteOffAmount() {
if (!this.selectedPaymentRow) return 0;
return this.selectedPaymentRow.currentWriteOffAmount || 0;
},
isWriteOffAmountValid() {
// use a small epsilon for float comparison if needed, but direct comparison is usually okay for UI validation
return this.totalPaymentWriteOffAmount <= this.totalPayableWriteOffAmount + 0.01;
}
},
created() {
this.getVendorList();
this.payableFilterVisible = true; //
},
methods: {
getVendorList() {
listAllVendors().then(response => {
// { data: [...] } search_file controller AjaxResult .data .rows
// Ruoyi response.data
this.vendorList = response.data || [];
});
},
handleOpenPayableFilter() {
this.payableFilterVisible = true;
},
handleOpenPaymentFilter() {
this.paymentFilterVisible = true;
},
/** 搜索按钮操作 */
handleQuery() {
if (!this.queryParams.vendorName) {
this.$modal.msgError("请选择制造商名称");
return;
}
this.payableFilterVisible = false;
this.paymentFilterVisible = false;
this.showTables = true;
this.payableQueryParams.pageNum = 1;
this.paymentQueryParams.pageNum = 1;
this.syncParams();
this.getPayableList();
this.getPaymentList();
},
/** 重置按钮操作 */
resetPayableQuery() {
this.payableDateRange = [];
this.queryParams.vendorName = undefined;
this.queryParams.payableBillCode = undefined;
},
resetPaymentQuery() {
this.paymentDateRange = [];
this.queryParams.vendorName = undefined;
this.queryParams.paymentCode = undefined;
},
syncParams() {
this.payableQueryParams.vendorName = this.queryParams.vendorName;
this.payableQueryParams.payableBillCode = this.queryParams.payableBillCode;
this.payableQueryParams.params = {};
if (null != this.payableDateRange && '' != this.payableDateRange) {
this.payableQueryParams.createTimeStart = this.payableDateRange[0];
this.payableQueryParams.createTimeEnd = this.payableDateRange[1];
}
this.paymentQueryParams.vendorName = this.queryParams.vendorName;
this.paymentQueryParams.paymentCode = this.queryParams.paymentCode;
this.paymentQueryParams.paymentBillType = 'PRE_PAYMENT'; //
this.paymentQueryParams.params = {};
if (null != this.paymentDateRange && '' != this.paymentDateRange) {
this.paymentQueryParams.params["beginTime"] = this.paymentDateRange[0];
this.paymentQueryParams.params["endTime"] = this.paymentDateRange[1];
}
},
getPayableList() {
this.loadingPayable = true;
listPayable(this.payableQueryParams).then(response => {
this.payableList = response.rows.map(item => ({
...item,
currentWriteOffAmount: 0, // Initialize
selectedPlans: [] // Initialize
}));
this.payableTotal = response.total;
this.loadingPayable = false;
//
this.$refs.payableTable.clearSelection();
this.selectedPayables = [];
});
},
getPaymentList() {
this.loadingPayment = true;
listPaymentForWriteOff(this.paymentQueryParams).then(response => {
this.paymentList = response.rows.map(item => ({
...item,
currentWriteOffAmount: 0, // Initialize
// Mocking fields not yet in standard listPayment (or assuming defaults)
unWrittenAmount: item.paymentAmount - (item.writtenAmount || 0),
writtenAmount: item.writtenAmount || 0
}));
this.paymentTotal = response.total;
this.loadingPayment = false;
//
this.selectedPaymentId = null;
this.selectedPaymentRow = null;
});
},
handlePayableSelectionChange(selection) {
this.selectedPayables = selection;
},
handlePaymentSelectionChange(currentRow) {
if (currentRow) {
this.selectedPaymentId = currentRow.paymentBillCode;
this.selectedPaymentRow = currentRow;
//
this.updatePaymentWriteOffAmount();
} else {
this.selectedPaymentId = null;
this.selectedPaymentRow = null;
}
},
// Logic for Plan Selection
handleOpenPlanSelector(row) {
this.currentPayableRow = row;
this.planDialogTitle = `选择应付计划 - ${row.payableBillCode}`;
this.planDialogVisible = true;
},
handlePlanSelectionConfirm() {
if (this.$refs.planSelector && this.$refs.planSelector.selectedPlan) {
const selected = this.$refs.planSelector.selectedPlan;
this.currentPayableRow.selectedPlans = selected;
// Calculate sum
const sum = selected.reduce((acc, plan) => acc + (plan.planAmount || 0), 0);
this.currentPayableRow.currentWriteOffAmount = sum;
this.$modal.msgSuccess(`已选择 ${selected.length} 个计划,总金额 ${sum.toFixed(2)}`);
// 1.
if (selected.length > 0) {
this.$refs.payableTable.toggleRowSelection(this.currentPayableRow, true);
} else {
this.$refs.payableTable.toggleRowSelection(this.currentPayableRow, false);
}
}
this.planDialogVisible = false;
},
// Tax Calculations
calculateTax(amount, taxRate) {
let calculateExcludingTax = this.calculateExcludingTax(amount, taxRate);
return this.$calc.sub(amount, calculateExcludingTax)
},
calculateExcludingTax(amount, taxRate) {
if (!amount) return '0.00';
// Amount / (1 + Rate/100)
const rate = taxRate ?? 0.13;
return this.$calc.div(amount, this.$calc.add(1, rate))
},
//
updatePaymentWriteOffAmount() {
if (this.selectedPaymentRow) {
const payableSum = this.totalPayableWriteOffAmount;
const maxAmount = this.selectedPaymentRow.unWrittenAmount || this.selectedPaymentRow.preResidueAmount || 0; // Use preResidueAmount if unWrittenAmount is not reliable or prefered
if (payableSum > maxAmount) {
//
this.selectedPaymentRow.currentWriteOffAmount = maxAmount;
this.$modal.msgWarning(`应付单核销总额(${payableSum.toFixed(2)})超过付款单剩余额度(${maxAmount}),已自动调整为最大可核销额度。`);
} else {
this.selectedPaymentRow.currentWriteOffAmount = payableSum;
}
}
},
//
handleManualWriteOff() {
if (!this.selectedPaymentRow) {
this.$modal.msgError("请选择一个付款单");
return;
}
if (this.selectedPayables.length === 0) {
this.$modal.msgError("请至少选择一个应付单");
return;
}
//
// 使 epsilon
if (Math.abs(this.totalPaymentWriteOffAmount - this.totalPayableWriteOffAmount) > 0.01) {
this.$modal.msgError(`付款单核销金额(${this.totalPaymentWriteOffAmount.toFixed(2)})必须等于应付单核销总额(${this.totalPayableWriteOffAmount.toFixed(2)})`);
return;
}
const detailList = [];
this.selectedPayables.forEach(payable => {
if (payable.selectedPlans && payable.selectedPlans.length > 0) {
payable.selectedPlans.forEach(plan => {
const amount = plan.planAmount;
detailList.push({
paymentPlanId: plan.id,
payableBillId: payable.id,
paymentBillCode: this.selectedPaymentRow.paymentBillCode,
paymentAmount: amount,
paymentRate: plan.planRate,
paymentAmountWithoutTax: this.calculateExcludingTax(amount, payable.taxRate),
paymentAmountTax: this.calculateTax(amount, payable.taxRate)
});
});
}
});
if (detailList.length === 0) {
this.$modal.msgError("请为选中的应付单选择计划(点击放大镜图标)");
return;
}
let vendorCode = this.selectedPaymentRow.vendorCode;
// Fallback for vendorCode if not in row but in list
if (!vendorCode && this.selectedPaymentRow.vendorName) {
const vendor = this.vendorList.find(v => v.vendorName === this.selectedPaymentRow.vendorName);
if (vendor) vendorCode = vendor.vendorCode;
}
const data = {
paymentBillId: this.selectedPaymentRow.paymentBillId || this.selectedPaymentRow.id,
vendorCode: vendorCode,
detailList: detailList
};
manualWriteOff(data).then(response => {
this.$modal.msgSuccess("核销成功");
this.handleQuery();
});
}
}
};
</script>
<style scoped>
.table-summary {
background-color: #f8f8f9;
padding: 10px;
border-radius: 4px;
border: 1px solid #ebeef5;
}
</style>

View File

@ -0,0 +1,236 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="核销序号" prop="writeOffCode">
<el-input
v-model="queryParams.writeOffCode"
placeholder="请输入核销序号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="采购应付单编号" prop="payableBillCode">
<el-input
v-model="queryParams.payableBillCode"
placeholder="请输入采购应付单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="采购付款单编号" prop="paymentBillCode">
<el-input
v-model="queryParams.paymentBillCode"
placeholder="请输入采购付款单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="制造商名称" prop="vendorName">
<el-select v-model="queryParams.vendorName" placeholder="请选择制造商名称" clearable filterable>
<el-option
v-for="item in vendorOptions"
:key="item.vendorName"
:label="item.vendorName"
:value="item.vendorName"
/>
</el-select>
</el-form-item>
<el-form-item label="核销类型" prop="writeOffType">
<el-select v-model="queryParams.writeOffType" placeholder="请选择核销类型" clearable>
<el-option
v-for="dict in dict.type.finance_write_off_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="核销时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>反核销</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="writeOffList" @selection-change="handleSelectionChange" row-key="id">
<el-table-column type="selection" width="55" align="center" :reserve-selection="true"/>
<el-table-column label="核销序号" align="center" prop="writeOffCode" />
<el-table-column label="核销人" align="center" prop="createByName" />
<el-table-column label="核销时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="采购付款单编号" align="center" prop="paymentBillCode" />
<el-table-column label="制造商名称" align="center" prop="vendorName" />
<el-table-column label="核销类型" align="center" prop="writeOffType">
<template slot-scope="scope">
<dict-tag :options="dict.type.finance_write_off_type" :value="scope.row.writeOffType"/>
</template>
</el-table-column>
<el-table-column label="核销含税总价(元)" align="center" prop="writeOffAmount" />
<el-table-column label="核销未税总价(元)" align="center" prop="writeOffAmountWithoutTax" />
<el-table-column label="核销税额(元)" align="center" prop="writeOffTaxAmount" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>查看详情</el-button>
<!-- <el-button-->
<!-- size="mini"-->
<!-- type="text"-->
<!-- icon="el-icon-delete"-->
<!-- @click="handleDelete(scope.row)"-->
<!-- >反核销</el-button>-->
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 详情抽屉 -->
<write-off-detail-drawer :visible.sync="open" :detail="form" />
</div>
</template>
<script>
import { listWriteOff, getWriteOff, delWriteOff } from "@/api/finance/writeoff";
import { listVendor } from "@/api/base/vendor";
import WriteOffDetailDrawer from "./WriteOffDetailDrawer";
export default {
name: "WriteOffRecord",
components: {
WriteOffDetailDrawer
},
dicts: ['finance_write_off_type'],
data() {
return {
//
loading: true,
//
ids: [],
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
writeOffList: [],
//
vendorOptions: [],
//
title: "",
//
open: false,
//
dateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
writeOffCode: null,
payableBillCode: null,
paymentBillCode: null,
vendorName: null,
writeOffType: null,
orderByColumn:'createTime',
isAsc: 'desc'
},
//
form: {}
};
},
created() {
this.getList();
this.getVendorList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
listWriteOff(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.writeOffList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 查询制造商列表 */
getVendorList() {
listVendor().then(response => {
this.vendorOptions = response.rows;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 查看详情 */
handleDetail(row) {
this.loading = true;
getWriteOff(row.id).then(response => {
this.form = response.data;
this.open = true;
this.title = "核销详情";
this.loading = false;
});
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.multiple = !selection.length
},
/** 反核销按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认反核销核销序号为"' + ids + '"的数据项?').then(function() {
return delWriteOff(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("反核销成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,91 @@
<template>
<el-drawer
title="核销详情"
:visible.sync="visible"
direction="rtl"
size="60%"
@close="handleClose"
>
<div class="drawer-body" v-if="detail">
<div class="section">
<el-divider content-position="left">销售-收款单</el-divider>
<el-table :data="[detail]" border stripe>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="writeOffAmount" label="本次核销含税总价(元)" align="center"></el-table-column>
<el-table-column prop="partnerName" label="进货商名称" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="receiptBillCode" label="销售-收款单编号" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="receiptCreateTime" label="收款单生成时间" align="center" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.receiptBill ? scope.row.receiptBill.createTime : '') }}</span>
</template>
</el-table-column>
<el-table-column prop="receiptBillType" label="收款单类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.receipt_bill_type" :value="scope.row.receiptBill ? scope.row.receiptBill.receiptBillType : ''"/>
</template>
</el-table-column>
</el-table>
</div>
<div class="section">
<el-divider content-position="left">销售-应收单</el-divider>
<el-table :data="detail.detailList" border stripe>
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="receiptAmount" label="本次核销含税总价(元)" align="center"></el-table-column>
<el-table-column prop="partnerName" label="进货商名称" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="receivableBillCode" label="销售-应收单编号" align="center"
show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="应收单生成时间" align="center"></el-table-column>
</el-table>
</div>
</div>
</el-drawer>
</template>
<script>
export default {
name: "WriteOffDetailDrawer",
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => ({}),
},
},
dicts: ['finance_write_off_type', 'receipt_bill_type'],
methods: {
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
.drawer-body {
padding: 20px;
height: calc(100vh - 80px);
overflow-y: auto;
}
.details-container {
border: 1px solid #EBEEF5;
padding: 15px;
border-radius: 4px;
background-color: #f8f8f9;
}
.detail-item {
margin-bottom: 12px;
font-size: 14px;
color: #606266;
line-height: 1.5;
}
.section {
margin-bottom: 25px;
}
</style>

Some files were not shown because too many files have changed in this diff Show More