feat: 完善设计报工及生产管理功能

This commit is contained in:
2026-07-22 14:50:28 +08:00
parent 8fdd0b6803
commit f16fb81ed8
15 changed files with 1977 additions and 33 deletions

View File

@@ -0,0 +1,92 @@
USE [YL_MESDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER PROCEDURE [dbo].[_工时校验_查询]
@DID int = NULL,
@ nvarchar(100) = N'',
@ nvarchar(100) = N'',
@ nvarchar(100) = N'',
@ nvarchar(100) = N'',
@ nvarchar(30) = N'',
@ nvarchar(30) = N'',
@ nvarchar(50) = N'未校验',
@ nvarchar(100) = N'',
@ nvarchar(100) = N''
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ datetime = TRY_CONVERT(datetime, NULLIF(@, N''));
DECLARE @ datetime = TRY_CONVERT(datetime, NULLIF(@, N''));
DECLARE @ nvarchar(100) = LTRIM(RTRIM(ISNULL(@, N'')));
DECLARE @ bit = CASE WHEN EXISTS
(
SELECT 1
FROM [dbo].[_角色信息]
WHERE CONVERT(nvarchar(100), [Roles_number]) = @
AND ([Roles_Function] LIKE N'%领导%' OR [Roles_Function] LIKE N'%管理员%')
) THEN 1 ELSE 0 END;
SELECT TOP (1000)
r.[RID],
r.[DID],
t.[],
t.[],
t.[],
t.[],
t.[],
t.[],
t.[],
r.[ID],
r.[],
CONVERT(varchar(19), r.[], 120) AS [],
CONVERT(varchar(19), r.[], 120) AS [],
r.[],
CAST(CAST(ISNULL(r.[], 0) / 3600.0 AS decimal(18, 2)) AS varchar(30)) AS [],
r.[],
r.[],
CASE WHEN r.[] = 1 THEN N'已校验' ELSE N'未校验' END AS [],
r.[],
CONVERT(varchar(19), r.[], 120) AS [],
r.[],
r.[],
t.[]
FROM [dbo].[MES_设计报工_记录] AS r
INNER JOIN [dbo].[MES_设计报工_任务] AS t ON r.[DID] = t.[DID]
WHERE
(@DID IS NULL OR @DID = 0 OR r.[DID] = @DID)
AND (ISNULL(@, N'') = N'' OR t.[] LIKE N'%' + @ + N'%')
AND (ISNULL(@, N'') = N'' OR t.[] LIKE N'%' + @ + N'%')
AND (ISNULL(@, N'') = N'' OR t.[] LIKE N'%' + @ + N'%')
AND (ISNULL(@, N'') = N'' OR r.[] LIKE N'%' + @ + N'%')
AND (@ IS NULL OR r.[] >= @)
AND (@ IS NULL OR r.[] < DATEADD(day, 1, @))
AND
(
ISNULL(@, N'') = N''
OR @ = N'全部'
OR (@ = N'已校验' AND r.[] = 1)
OR (@ = N'未校验' AND r.[] = 0)
)
AND
(
@ = 1
OR
(
NULLIF(@, N'') IS NOT NULL
AND
(
LTRIM(RTRIM(ISNULL(t.[], N''))) = @
OR LTRIM(RTRIM(ISNULL(r.[], N''))) = @
)
)
)
ORDER BY r.[RID] DESC;
END
GO

View File

@@ -0,0 +1,68 @@
USE [YL_MESDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF OBJECT_ID(N'[dbo].[MES_设计报工_附件]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[MES_设计报工_附件]
(
[id] int IDENTITY(1,1) NOT NULL CONSTRAINT [PK_MES_设计报工_附件] PRIMARY KEY,
[num] nvarchar(200) NOT NULL,
[uid] nvarchar(200) NULL,
[name] nvarchar(500) NULL,
[suffix] nvarchar(100) NULL,
[] int NULL,
[worker] nvarchar(200) NULL,
[uploadTime] nvarchar(200) NULL
);
END
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE [object_id] = OBJECT_ID(N'[dbo].[MES_设计报工_附件]')
AND [name] = N'IX_MES_设计报工_附件_任务'
)
BEGIN
CREATE INDEX [IX_MES_设计报工_附件_任务]
ON [dbo].[MES_设计报工_附件]([num], [], [id] DESC);
END
GO
CREATE OR ALTER PROCEDURE [dbo].[_附件_查询]
@DID int
AS
BEGIN
SET NOCOUNT ON;
SELECT
a.[id] AS [ID],
TRY_CONVERT(int, a.[num]) AS [DID],
CASE
WHEN CHARINDEX(N'__', ISNULL(a.[name], N'')) > 0
THEN SUBSTRING(a.[name], CHARINDEX(N'__', a.[name]) + 2, 500)
ELSE ISNULL(a.[name], N'')
END + CASE
WHEN NULLIF(LTRIM(RTRIM(ISNULL(a.[suffix], N''))), N'') IS NULL THEN N''
ELSE N'.' + a.[suffix]
END AS [],
ISNULL(a.[name], N'') + CASE
WHEN NULLIF(LTRIM(RTRIM(ISNULL(a.[suffix], N''))), N'') IS NULL THEN N''
ELSE N'.' + a.[suffix]
END AS [],
LOWER(ISNULL(a.[suffix], N'')) AS [],
ISNULL(a.[worker], N'') AS [],
ISNULL(a.[uploadTime], N'') AS []
FROM [dbo].[MES_设计报工_附件] AS a
WHERE TRY_CONVERT(int, a.[num]) = @DID
AND ISNULL(a.[], 1) = 1
ORDER BY a.[id] DESC;
END
GO

View File

@@ -0,0 +1,225 @@
USE [YL_MESDB];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE OR ALTER PROCEDURE [dbo].[_看板查询]
@ nvarchar(20)
AS
BEGIN
SET NOCOUNT ON;
IF @ NOT IN (N'大厅', N'仓库', N'装配', N'质检')
BEGIN
THROW 50001, N'看板类型必须是大厅、仓库、装配或质检', 1;
END;
;WITH AS
(
SELECT
d.[TaskAID],
STRING_AGG(CONVERT(nvarchar(max), d.[]), N',')
WITHIN GROUP (ORDER BY d.[]) AS []
FROM
(
SELECT DISTINCT
[TaskAID],
NULLIF(LTRIM(RTRIM([])), N'') AS []
FROM [dbo].[YL_加工中心_操作记录表]
WHERE [] = N'开始加工'
AND [] IS NULL
AND [] LIKE N'%装配%'
AND [] IS NOT NULL
AND NULLIF(LTRIM(RTRIM([])), N'') IS NOT NULL
) AS d
GROUP BY d.[TaskAID]
),
AS
(
SELECT
[TaskAID],
SUM(CASE WHEN [] = N'终检' THEN ISNULL([], 0) ELSE 0 END) AS [],
SUM(CASE WHEN [] = N'收检' THEN ISNULL([], 0) ELSE 0 END) AS [],
SUM(CASE WHEN [] = 6 THEN ISNULL([], 0) ELSE 0 END) AS []
FROM [dbo].[View_质量检验_质检记录]
WHERE [] IN (N'收检', N'终检')
GROUP BY [TaskAID]
),
AS
(
SELECT
gs.[],
SUM(gs.[]) AS []
FROM [dbo].[View_生产工时视图] AS gs
INNER JOIN [dbo].[_人员信息] AS ry
ON gs.[] = ry.[Personnel_Name]
WHERE ry.[] = N'装配'
GROUP BY gs.[]
),
AS
(
SELECT
CAST(N'SAP' AS nvarchar(20)) AS [],
c.[DocStatus] AS [],
CONVERT(nvarchar(200), c.[]) AS [],
CONVERT(nvarchar(500), c.[]) AS [],
CONVERT(nvarchar(4000), c.[]) AS [],
c.[],
c.[],
c.[],
a.[TaskAID],
COALESCE(a.[], c.[]) AS [],
COALESCE(
NULLIF(CONVERT(nvarchar(200), a.[]), N''),
NULLIF(CONVERT(nvarchar(200), c.[]), N'')
) AS [],
a.[],
a.[],
a.[],
a.[],
a.[],
a.[],
c.[] AS [],
a.[],
a.[],
a.[],
a.[],
op.[],
ISNULL(hours.[], 0) AS [],
ISNULL(quality.[], 0) AS [],
ISNULL(quality.[], 0) AS [],
ISNULL(quality.[], 0) AS []
FROM [SAP].[SBO_YL].[dbo].[UBT_ODRF_ODLNMES] AS c
LEFT JOIN [dbo].[View_生产订单_MES] AS a
ON c.[] = a.[]
LEFT JOIN AS op ON a.[TaskAID] = op.[TaskAID]
LEFT JOIN AS quality ON a.[TaskAID] = quality.[TaskAID]
LEFT JOIN AS hours ON a.[] = hours.[]
UNION ALL
SELECT
CAST(N'MES优先' AS nvarchar(20)) AS [],
CAST(NULL AS char(1)) AS [],
CONVERT(nvarchar(200), a.[]) AS [],
CONVERT(nvarchar(500), a.[]) AS [],
CONVERT(nvarchar(4000), a.[]) AS [],
CAST(NULL AS numeric(19, 6)) AS [],
CAST(NULL AS datetime) AS [],
CAST(NULL AS int) AS [],
a.[TaskAID],
a.[],
CONVERT(nvarchar(200), a.[]) AS [],
a.[],
a.[],
a.[],
a.[],
a.[],
a.[],
a.[],
a.[],
a.[],
a.[],
a.[],
op.[],
ISNULL(hours.[], 0) AS [],
ISNULL(quality.[], 0) AS [],
ISNULL(quality.[], 0) AS [],
ISNULL(quality.[], 0) AS []
FROM [dbo].[View_生产订单_MES] AS a
LEFT JOIN AS op ON a.[TaskAID] = op.[TaskAID]
LEFT JOIN AS quality ON a.[TaskAID] = quality.[TaskAID]
LEFT JOIN AS hours ON a.[] = hours.[]
WHERE a.[] = N''
AND a.[] LIKE N'%装配%'
AND NOT EXISTS
(
SELECT 1
FROM [SAP].[SBO_YL].[dbo].[UBT_ODRF_ODLNMES] AS c
WHERE c.[] = a.[]
)
),
AS
(
SELECT
source.*,
CONVERT(date, source.[]) AS [],
CASE
WHEN source.[] LIKE N'%3420-YT%'
OR source.[] LIKE N'%3420-HT%'
OR source.[] LIKE N'%3420-YF%'
THEN N'见子任务'
WHEN ISNULL(source.[], 0) = 0 THEN N'齐套'
WHEN source.[] IS NULL THEN N'派发'
WHEN ISNULL(source.[], N'') <> N'1' THEN N'发料'
WHEN ISNULL(source.[], 0) <= 1 THEN N'开工'
WHEN ISNULL(source.[], 0) < source.[] THEN N'装配'
WHEN source.[] + source.[] < source.[] THEN N'质检'
WHEN ISNULL(source.[], 0) + source.[] < source.[] THEN N'待入库'
ELSE N''
END AS []
FROM AS source
)
SELECT
[],
[],
[],
[],
[],
[],
[],
[],
[TaskAID],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
FROM
WHERE
(@ = N'仓库' AND [] = N'SAP')
OR
(
@ = N'大厅'
AND
(
[TaskAID] IS NULL
OR [] IS NULL
OR [] + [] <> []
)
)
OR
(
@ = N'质检'
AND [] < ISNULL([], 0)
)
OR
(
@ = N'装配'
AND ISNULL([], 0) < ISNULL([], 0)
)
ORDER BY
CASE WHEN [] IS NULL THEN 1 ELSE 0 END,
[],
[],
[],
[];
END;
GO

View File

@@ -407,6 +407,7 @@ BEGIN
PARTITION BY
[],
CASE
WHEN [] = N'外购' THEN [] + N':' + CONVERT(nvarchar(30), [SAP行号])
WHEN ISNULL([], N'') <> N'' THEN []
ELSE [] + N':' + CONVERT(nvarchar(30), [SAP行号])
END

View File

@@ -0,0 +1,654 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MES 工时模块全流程技术说明</title>
<style>
@page { size: A4; margin: 16mm 14mm 17mm; }
* { box-sizing: border-box; }
html { font-family: "Microsoft YaHei", "Segoe UI", sans-serif; color: #20262d; font-size: 10.5pt; line-height: 1.62; }
body { margin: 0; background: #fff; }
h1, h2, h3, h4 { color: #153b4f; margin: 0; font-weight: 700; page-break-after: avoid; }
h1 { font-size: 27pt; line-height: 1.22; }
h2 { margin-top: 22px; padding: 7px 0 5px; border-bottom: 2px solid #15968f; font-size: 18pt; }
h3 { margin-top: 18px; font-size: 13.5pt; }
h4 { margin-top: 13px; font-size: 11pt; color: #355b6a; }
p { margin: 7px 0; }
ul, ol { margin: 6px 0 9px 21px; padding: 0; }
li { margin: 3px 0; }
code { font-family: Consolas, "Microsoft YaHei", monospace; font-size: 9.2pt; color: #8b2f3b; background: #f4f6f7; padding: 1px 3px; border-radius: 2px; word-break: break-all; }
pre { white-space: pre-wrap; word-break: break-word; background: #f4f7f8; border-left: 4px solid #15968f; padding: 10px 12px; font-size: 9pt; line-height: 1.5; page-break-inside: avoid; }
table { width: 100%; border-collapse: collapse; margin: 9px 0 14px; font-size: 8.8pt; table-layout: fixed; }
th, td { border: 1px solid #b9c5ca; padding: 5px 6px; vertical-align: top; word-break: break-word; }
th { background: #eaf2f3; color: #153b4f; text-align: left; font-weight: 700; }
tr { page-break-inside: avoid; }
.cover { min-height: 250mm; display: flex; flex-direction: column; justify-content: center; padding: 12mm 8mm; page-break-after: always; border-top: 10px solid #153b4f; border-bottom: 3px solid #15968f; }
.cover .kicker { color: #15968f; font-size: 12pt; font-weight: 700; margin-bottom: 16px; }
.cover .subtitle { margin-top: 16px; font-size: 14pt; color: #4d6069; }
.cover .meta { margin-top: 42px; border-top: 1px solid #b9c5ca; padding-top: 14px; color: #4d6069; }
.page-break { page-break-before: always; }
.note, .warning, .critical { margin: 10px 0; padding: 9px 11px; border-left: 4px solid; page-break-inside: avoid; }
.note { background: #edf7f6; border-color: #15968f; }
.warning { background: #fff6df; border-color: #d59622; }
.critical { background: #fff0f0; border-color: #b43d46; }
.small { font-size: 8.7pt; color: #596970; }
.toc a { color: #153b4f; text-decoration: none; }
.toc li { border-bottom: 1px dotted #aab7bd; padding: 2px 0; }
.flow { display: grid; grid-template-columns: 1fr 28px 1fr 28px 1fr; align-items: stretch; gap: 4px; margin: 12px 0 16px; page-break-inside: avoid; }
.flow .box { border: 1px solid #93a7af; background: #f8fafb; padding: 9px; min-height: 75px; }
.flow .arrow { display: flex; align-items: center; justify-content: center; color: #15968f; font-size: 18pt; font-weight: 700; }
.pill { display: inline-block; border: 1px solid #8ea1a9; padding: 1px 5px; margin: 1px 2px; border-radius: 3px; font-size: 8pt; background: #fff; }
.formula { background: #f4f7f8; border: 1px solid #c4d0d5; padding: 8px 10px; font-family: Consolas, "Microsoft YaHei", monospace; page-break-inside: avoid; }
.muted { color: #64757d; }
.w15 { width: 15%; } .w18 { width: 18%; } .w20 { width: 20%; } .w25 { width: 25%; } .w30 { width: 30%; } .w35 { width: 35%; }
</style>
</head>
<body>
<section class="cover">
<div class="kicker">MES_Manage_View_V20 / 生产工时专题</div>
<h1>MES 工时模块全流程技术说明</h1>
<div class="subtitle">从加工中心、装配中心和外协业务入口,到工时修正、校验、查询、报表、结算及生产计划跟踪</div>
<div class="meta">
<p><strong>整理范围:</strong>页面、交互流程、存储过程、计算方法、数据表、视图、SAP Service Layer、工时查询、报表、结算、外协与生产计划跟踪</p>
<p><strong>代码基线:</strong><code>snapshot/local-save-20260625-154806</code>,提交 <code>8fdd0b6</code></p>
<p><strong>数据库定义核对:</strong>2026-07-21只读查询当前对象定义</p>
<p><strong>文档版本:</strong>V1.0 / 2026-07-21</p>
</div>
</section>
<section class="toc">
<h2>目录</h2>
<ol>
<li><a href="#scope">范围、边界与结论</a></li>
<li><a href="#architecture">总流程与数据分层</a></li>
<li><a href="#pages">页面及功能清单</a></li>
<li><a href="#machining">加工中心工时流程</a></li>
<li><a href="#assembly">装配中心工时流程</a></li>
<li><a href="#maintenance">工时修正与异常工时</a></li>
<li><a href="#validation">工时校验与 SAP 上传</a></li>
<li><a href="#calculation">工时计算方法与公式</a></li>
<li><a href="#downstream">工时查询、工时报表与结算工时</a></li>
<li><a href="#outsource">外协与生产计划跟踪</a></li>
<li><a href="#procedures">存储过程和函数清单</a></li>
<li><a href="#tables">数据表清单与字段说明</a></li>
<li><a href="#views">视图及取数规则</a></li>
<li><a href="#sap">SAP 对接映射</a></li>
<li><a href="#states">状态、操作类别与单位</a></li>
<li><a href="#appendix">调用矩阵与资料来源</a></li>
</ol>
</section>
<section id="scope">
<h2>1. 范围、边界与结论</h2>
<h3>1.1 本文范围</h3>
<p>本文以生产现场产生工时的加工中心、装配中心和外协业务为起点完整覆盖工时修正、工时校验、SAP 上传,以及校验后的工时查询、工时报表、结算工时和生产计划跟踪。直接参与计算的班次、人员与工位绑定也纳入范围。</p>
<table>
<tr><th class="w20">层级</th><th>纳入内容</th><th class="w30">边界说明</th></tr>
<tr><td>现场入口</td><td>加工中心、装配中心的开始、结束、辅助、报工、加班、异常工时</td><td>产生原始操作记录和工时秒数</td></tr>
<tr><td>计算支撑</td><td>人员绑定、班次管理、生产任务、资源主数据、设备状态</td><td>决定参与人员、班次重叠、资源编码</td></tr>
<tr><td>校验前维护</td><td>工时修正、补录、开始/结束时间重算</td><td>仍写入原始操作记录表</td></tr>
<tr><td>校验与外部系统</td><td>工时校验、上传分钟计算、SAP 资源行与工时单据、校验副本</td><td>形成可统计的校验结果</td></tr>
<tr><td>查询与统计</td><td>工时查询、项目/作业者/任务/设备工时报表、结算工时</td><td>按任务、日期、人员、设备、物料等维度汇总</td></tr>
<tr><td>生产跟踪</td><td>外协管理、外协报表、生产计划跟踪</td><td>关联工时事件、工序进度、发收料、质检与供应商信息</td></tr>
</table>
<h3>1.2 明确排除的业务域</h3>
<p><code>设计报工</code><code>设计工时校验</code><code>设计工时报表</code>使用独立任务、报工和校验对象,不属于本生产工时链路,本文不纳入。</p>
<h3>1.3 核心结论</h3>
<ul>
<li>两类现场入口最终都写入 <code>YL_加工中心_操作记录表</code>,这是生产工时的事实源。</li>
<li>数据库工时统一以<strong></strong>存储页面主要以秒编辑、以分钟展示SAP <code>InventoryGenExits.DocumentLines.Quantity</code> 使用<strong>分钟</strong></li>
<li>设备工时来自 <code>实际工时</code>,典型公式为“开始至结束总秒数 - 暂停秒数”;人员工时来自 <code>总时长</code>,并可按人员/班次拆成独立记录。</li>
<li><code>View_生产工时视图</code>把原始记录、校验副本、生产订单、设备绑定数合并成校验页面的统一模型。</li>
<li>批量校验先调用 <code>usp_BatchValidateWorkTime</code>算出 SAP 上传分钟,再逐条补齐 SAP 工序/资源、生成工时单据;只有上传成功后才复制到校验表。</li>
<li>工时查询面向工序报工明细,工时报表提供项目、作业者、任务、设备四种统计口径,结算工时按物料、订单、工位汇总报工与辅助工时。</li>
<li>外协工序不进入标准内部生产工时视图,改用独立的外协任务、发收料、操作记录、采购和质检链路跟踪。</li>
</ul>
</section>
<section id="architecture">
<h2>2. 总流程与数据分层</h2>
<div class="flow">
<div class="box"><strong>现场动作</strong><br>加工中心:开始加工/辅助/报工<br>装配中心:人员开始/结束</div>
<div class="arrow"></div>
<div class="box"><strong>事实记录</strong><br><code>YL_加工中心_操作记录表</code><br>开始、结束、设备秒数、人员秒数</div>
<div class="arrow"></div>
<div class="box"><strong>统一视图</strong><br><code>View_生产工时视图</code><br>叠加订单、绑定、校验状态</div>
</div>
<div class="flow">
<div class="box"><strong>修正</strong><br>调整开始/结束、设备/人员工时、班次标准工时</div>
<div class="arrow"></div>
<div class="box"><strong>计算与 SAP</strong><br>班次内外拆分、日比例系数、资源行、工时单据</div>
<div class="arrow"></div>
<div class="box"><strong>校验结束</strong><br><code>生产管理_工时校验_校验工时表</code><br>形成已校验副本</div>
</div>
<div class="flow">
<div class="box"><strong>查询与报表</strong><br>工序、项目、作业者、任务、设备维度</div>
<div class="arrow"></div>
<div class="box"><strong>结算</strong><br>物料/订单/工位汇总<br>报工、辅助、单件工时</div>
<div class="arrow"></div>
<div class="box"><strong>计划跟踪</strong><br>工序状态、标准工时、外协发收料、质检与进度</div>
</div>
<h3>2.1 五层模型</h3>
<table>
<tr><th class="w18"></th><th class="w25">主要对象</th><th>职责</th></tr>
<tr><td>任务层</td><td><code>MES_接口_生产计划_生产任务</code><br><code>View_生产订单_MES</code></td><td>提供订单、TaskAID、工序、指派工位、分配数、完成数、任务/加工状态。</td></tr>
<tr><td>记录层</td><td><code>YL_加工中心_操作记录表</code><br><code>YL_加工中心_报工记录</code></td><td>记录每次现场动作、起止时间、设备工时、人员工时、加班和描述。</td></tr>
<tr><td>计算层</td><td><code>View_生产工时视图</code><br><code>usp_Calculate*</code></td><td>清洗记录、补齐上下文、按班次和当日标准工时计算上传分钟。</td></tr>
<tr><td>校验/外部层</td><td><code>生产管理_工时校验_校验工时表</code><br>SAP Service Layer</td><td>保存校验副本;补 SAP 工序/资源并提交资源消耗工时单据。</td></tr>
<tr><td>消费与跟踪层</td><td>工时查询/报表/结算过程<br><code>生产管理_生产计划跟踪</code><br><code>View_外协报表视图</code></td><td>把工时转为管理统计、单件结算和生产/外协进度信息。</td></tr>
</table>
</section>
<section id="pages" class="page-break">
<h2>3. 页面及功能清单</h2>
<table>
<tr><th class="w18">页面</th><th class="w25">组件路径</th><th>工时职责</th><th class="w18">阶段</th></tr>
<tr><td>加工中心</td><td><code>ProductionManagement/MachiningCenter/index.vue</code></td><td>设备型任务开始、辅助、结束报工、暂停口径、加班拆分、异常补录和工时修改。</td><td>起点</td></tr>
<tr><td>装配中心</td><td><code>ProductionManagement/AssemblyCenter/index.vue</code></td><td>以当前登录人员为粒度开始/结束任务,记录装配人员工时与加班。</td><td>起点</td></tr>
<tr><td>人员绑定</td><td><code>ProductionManagement/PersonnelBinding/index.vue</code></td><td>维护工位与人员关系;影响人员工时拆分、绑定设备数和 SAP 人员资源。</td><td>支撑</td></tr>
<tr><td>班次管理</td><td><code>ProductionManagement/ShiftManagement/index.vue</code></td><td>维护班次起止时间及班次人员;影响重叠秒数、班次内外拆分和比例系数。</td><td>支撑</td></tr>
<tr><td>工时修正</td><td><code>ProductionManagement/WorkhoursEdit/index.vue</code></td><td>查询当天/指定日期工时,修正开始结束、设备/人员秒数和班次标准工时,可补录操作记录。</td><td>校验前</td></tr>
<tr><td>工时校验</td><td><code>ProductionManagement/WorkHoursCheck/index.vue</code></td><td>查询昨日以前记录、筛选机加/装配/加班、修正、批量计算、上传 SAP、保存校验状态。</td><td>终点</td></tr>
<tr><td>工时查询</td><td><code>ProductionManagement/Workhours/index.vue</code></td><td>按 TaskAID、日期和明细三级展示工序工时、报工数量、单件工时与工艺标准工时。</td><td>查询</td></tr>
<tr><td>工时报表</td><td><code>ProductionManagement/Timesheet/index.vue</code></td><td>提供项目、作业者、任务、设备四类树形统计及对应导出。</td><td>统计</td></tr>
<tr><td>结算工时</td><td><code>ProductionManagement/SettlementWorkhours/index.vue</code></td><td>按物料和订单工位汇总报工、辅助、完成数、合计工时和结算单件工时,可导出 Excel。</td><td>结算</td></tr>
<tr><td>外协管理</td><td><code>ProductionManagement/OutsourceMange/index.vue</code><br><code>SaleOutsourceMange/index.vue</code></td><td>外协任务派工、发料、收料、供应商、备注、打印、历史和质检记录。</td><td>外协入口</td></tr>
<tr><td>外协报表</td><td><code>ProductionManagement/OutsourceSheet/index.vue</code></td><td>按订单和外协组汇总供应商、金额、计划/到货时间、发收料、质检和完成数量并导出。</td><td>外协统计</td></tr>
<tr><td>生产计划跟踪</td><td><code>ProductionManagement/ProductionPlanTrack/index.vue</code></td><td>按订单聚合工序路线、标准工时、开工/结束、外协结束、收料、质检、进度和加急状态。</td><td>计划跟踪</td></tr>
</table>
<div class="note"><strong>菜单范围:</strong>生产工时主链路涵盖“加工中心、装配中心、人员绑定、班次管理、工时修正、工时校验、工时查询、工时报表、结算工时、外协管理、外协报表、生产计划跟踪”。设计报工相关菜单明确排除。</div>
</section>
<section id="machining">
<h2>4. 加工中心工时流程</h2>
<h3>4.1 开始加工</h3>
<ol>
<li>页面检查已选择任务,并用 <code>生产管理_加工中心_设备是否正在加工</code>防止同一指派设备并行加工。</li>
<li>允许的任务状态为“可执行/已开工”,允许的前置加工状态为“报工、暂停、空、辅助结束”。</li>
<li>提交 <code>TaskAID、optor、收料、分配数</code><code>MES_ProductTask_WorkingStart</code></li>
<li>存储过程把任务状态置为已开工,更新开工/操作时间,必要时同步前序合格数为收料数量。</li>
<li>通过 <code>MES_ProductCenter_OptRecord_Save</code>插入“开始加工”记录,开始时间为数据库当前时间。</li>
</ol>
<h3>4.2 辅助开始与辅助结束</h3>
<p><code>MES_ProductTask_HelpStart</code>写入“辅助开始”;<code>MES_ProductTask_HelpEnd</code>更新辅助完成量、任务进度和状态,并调用公共记录过程闭合时间区间。辅助结束允许指定实际调机人员,并可拆出普通加班、休息日加班或法定休息日加班记录。</p>
<h3>4.3 正常报工</h3>
<ol>
<li>页面仅允许加工状态为“开始加工”的任务进入报工。</li>
<li>加载同订单任务、班次配置和最近开始时间;页面根据时间判断原始班次,并收集报工数量、绑定任务、前序不合格数、班次和加班人员。</li>
<li><code>MES_ProductTask_ProdReport</code>校验报工累计数量不得超过收料/指派数量,更新完成数量和任务状态。</li>
<li>主“报工”记录由 <code>MES_ProductCenter_OptRecord_Save</code>闭合:总时长为起止差,暂停总时长为周期内暂停之和,实际工时为两者之差。</li>
<li>工作日调用 <code>计算人员工时</code>按工位绑定人员和班次重叠生成独立人员记录;休息日/法定休息日不在此步骤生成普通人员工时,而由加班记录承担。</li>
</ol>
<h3>4.4 公共记录过程的周期配对</h3>
<table>
<tr><th>结束动作</th><th>寻找的开始动作</th><th>形成字段</th></tr>
<tr><td>报工</td><td>同 TaskAID、同工位的“开始加工”</td><td><code>关联开始记录ID、开始时间、结束时间、总时长、暂停总时长、实际工时</code></td></tr>
<tr><td>辅助结束</td><td>同 TaskAID、同工位的“辅助开始”</td><td>同上,操作类别和 SAP 资源使用“辅助-”前缀</td></tr>
<tr><td>暂停</td><td>最近的“开始加工/辅助开始”</td><td>闭合开始记录,写入暂停关联与暂停时长</td></tr>
</table>
<h3>4.5 加工中心内置维护</h3>
<p>页面还提供异常工时列表、异常工时新增和单条工时修改。修改时间后前端以 <code>round((结束-开始)/1000)</code>重算秒数,并调用 <code>加工中心_修改工时_保存修改记录</code>更新原始记录。</p>
</section>
<section id="assembly">
<h2>5. 装配中心工时流程</h2>
<h3>5.1 人员开始</h3>
<ol>
<li>页面以 <code>TaskAID + 当前用户名</code>检查当前用户是否已有未结束记录。</li>
<li>调用 <code>MES_人员工时记录_开始</code>;可执行任务第一次开始时更新为已开工。</li>
<li>插入操作类别为“开始加工”的记录,工位取任务指派对象,操作人取当前用户名,结束时间为空。</li>
<li>页面随后重新读取 <code>MES_人员工时记录_获取进行中任务</code><code>...获取进行中任务2</code>,保持本地状态与数据库一致。</li>
</ol>
<h3>5.2 人员结束</h3>
<p>结束时必须找到同任务、同用户、操作类别为“开始加工”且结束时间为空的最近记录。用户补充备注、工序说明、异常说明,并选择工作日/休息日/法定休息日、是否加班、普通/特殊工时人员及班次标准工时。</p>
<table>
<tr><th class="w25">场景</th><th>装配结束过程的拆分规则</th></tr>
<tr><td>工作日,不加班</td><td>全部起止秒数作为普通工作时间;更新开始记录的 <code>总时长</code></td></tr>
<tr><td>工作日,加班</td><td>以白班结束时间为切点:班次结束前为普通工作时间,之后为加班;若开始已晚于班次结束,全部为加班。</td></tr>
<tr><td>休息日/法定日,加班</td><td>全部起止秒数作为加班,主开始记录的工时和总时长被置 0另插加班人员记录。</td></tr>
<tr><td>休息日/法定日,不加班</td><td>过程先识别为“休息日工作/法定休息日工作”,但随后主记录在休息日分支中被置 0同时会向 <code>YL_加工中心_报工记录</code>写一条工作记录。此处存在双表口径差异。</td></tr>
</table>
<h3>5.3 装配与机加的主要差异</h3>
<table>
<tr><th>维度</th><th>加工中心</th><th>装配中心</th></tr>
<tr><td>主对象</td><td>设备任务 + 工位绑定人员</td><td>当前登录人员 + 装配任务</td></tr>
<tr><td>开始过程</td><td><code>MES_ProductTask_WorkingStart</code></td><td><code>MES_人员工时记录_开始</code></td></tr>
<tr><td>结束过程</td><td><code>MES_ProductTask_ProdReport</code>或辅助结束</td><td><code>MES_人员工时记录_结束</code></td></tr>
<tr><td>设备工时</td><td>主报工记录通常有 <code>实际工时</code></td><td>人员开始记录通常以 <code>总时长</code>为主</td></tr>
<tr><td>人员拆分</td><td>可由工位绑定和班次交叉生成多条</td><td>以登录用户结束记录,并可另生成加班人员记录</td></tr>
</table>
</section>
<section id="maintenance" class="page-break">
<h2>6. 工时修正与异常工时</h2>
<h3>6.1 工时修正页面</h3>
<p>“工时修正”读取 <code>工时校验_获取操作记录列表</code>,允许在进入最终校验前修正记录。主要筛选包括日期、校验状态、机加/装配性质、异常、任务 ID、生产订单、加班与人员。</p>
<ul>
<li>开始/结束时间变化时,设备工时和人员工时同时按秒重算。</li>
<li>保存调用 <code>工时校验_保存校验记录</code>,实际行为是更新原始操作记录表,并未插入校验表。</li>
<li><code>加工工时</code>映射原表 <code>实际工时</code><code>总时长</code>映射人员时间;班次标准工时写回原表。</li>
<li>可查看指定工位当天每分钟设备状态累计,但当前查看按钮在工时校验页被注释,工时修正页仍保留调用能力。</li>
<li>可补录“报工/辅助结束”,补录过程直接按起止时间计算 <code>实际工时</code>,写入原始操作记录。</li>
</ul>
<h3>6.2 异常工时</h3>
<p>加工中心和装配中心均内嵌异常工时入口。<code>异常工时_新增操作记录</code>根据订单与 TaskAID 补齐物料/工序上下文,按起止时间计算秒数并写入事实表;<code>加工中心_异常工时_获取操作记录列表</code>用于回查;<code>加工中心_修改工时_保存修改记录</code>用于修正。</p>
<div class="warning"><strong>管理原则:</strong>修正和补录都会直接改变事实源。当前过程记录最后编辑人/时间,但没有独立的修改前值历史表;审计主要依赖最后编辑字段和操作描述。</div>
</section>
<section id="validation">
<h2>7. 工时校验与 SAP 上传</h2>
<h3>7.1 查询范围</h3>
<p>默认显示未校验、机加、正常记录。查询过程 <code>工时校验_获取昨日之前操作记录列表</code>固定增加“开始时间小于今天 00:00:00”因此当日记录不会进入终检。页面可按任务 ID、订单、人员、机加/装配、加班与校验状态筛选。</p>
<h3>7.2 一键校验顺序</h3>
<ol>
<li>只处理用户勾选且尚未校验的记录。</li>
<li>生产订单 <code>3886</code>直接复制进校验表,不计算、不上传 SAP。</li>
<li>其他记录传入 <code>usp_BatchValidateWorkTime</code>,逐条得到 <code>UploadMinutes、WorkType、CalculationDetail</code></li>
<li>对计算成功记录,读取 SAP 生产订单和 MES 工序,补齐缺失的工序阶段和资源行。</li>
<li>按操作类别形成设备或人员资源工时行;先更新 SAP 生产订单资源行计划量,再 POST 资源发料单。</li>
<li>只有 SAP 上传返回成功,才调用 <code>工时校验_批量保存校验记录</code>形成已校验副本。</li>
<li>计算失败或 SAP 上传失败的记录保持未校验,便于再次处理。</li>
</ol>
<h3>7.3 页面颜色与人工修正</h3>
<ul>
<li>结束时间为空:红色优先级。</li>
<li>设备工时或人员工时大于 33,000 秒:红色优先级。</li>
<li>结束时间早于昨天 00:00橙色优先级。</li>
<li>未校验记录可修改起止时间、设备/人员秒数和标准工时;已校验记录只读。</li>
</ul>
<h3>7.4 已校验判定</h3>
<p><code>View_生产工时视图</code>通过 <code>生产管理_工时校验_校验工时表.报工记录id = 原始记录.AID</code>关联。存在校验副本即 <code>已校验=1</code>,并优先显示校验表字段。</p>
</section>
<section id="calculation" class="page-break">
<h2>8. 工时计算方法与公式</h2>
<h3>8.1 基础时间</h3>
<div class="formula">总时长秒 = DATEDIFF(SECOND, 开始时间, 结束时间)<br>暂停总时长秒 = 周期内所有已闭合暂停记录的暂停时长之和<br>设备实际工时秒 = 总时长秒 - 暂停总时长秒</div>
<p>在统一视图中:<code>加工工时 = 校验表.工时(有校验副本时)或 原表.实际工时</code><code>总时长 = 原表.总时长</code></p>
<h3>8.2 人员工时拆分</h3>
<p><code>计算人员工时</code>获取工位绑定人员,并与所有有效班次做笛卡尔组合,再调用 <code>计算班次重叠秒数</code>计算每个人在各班次的重叠时间。仅保留人员已绑定的班次和正重叠记录;指定人员若无班次绑定,则保留重叠最长班次,完全无重叠时至少给 1 秒。</p>
<div class="formula">当前生效公式:人员计算工时秒 = 班次重叠秒数<br><span class="muted">历史“重叠秒数 / 绑定设备数”的分摊公式已在存储过程中注释,不再生效。</span></div>
<h3>8.3 当日归一化系数</h3>
<p><code>usp_CalculateDailyRatio</code>按“操作人 + 开始日期”汇总符合条件的总时长:</p>
<ul>
<li><code>总时长 &gt; 300</code>秒;</li>
<li><code>阶段标识 != -1</code></li>
<li>订单不等于 <code>3886</code></li>
<li>排除“加班、休息日工作、法定休息日工作”。</li>
</ul>
<div class="formula">当日总分钟 = Σ有效记录总时长秒 / 60<br>比例系数 Ratio = (标准工时小时 × 60) / 当日总分钟<br>若当日总分钟或标准工时不大于 0则 Ratio = 1</div>
<h3>8.4 班次内外拆分</h3>
<p><code>usp_CalculateShiftTime</code>将一个起止区间逐段分到班次内和班次外,支持跨天班次;如果结束早于开始,会先交换。班次优先按人员名称从 <code>View_设备工位人员</code>匹配,其次按工位;匹配不到则默认 <code>07:45-16:30</code></p>
<h3>8.5 最终 SAP 上传分钟</h3>
<table>
<tr><th class="w25">操作类别</th><th>上传分钟公式</th><th class="w25">工时类型</th></tr>
<tr><td>总时长小于 300 秒</td><td><code>0</code>,跳过 SAP但计算结果按成功处理</td><td>跳过SAP</td></tr>
<tr><td>普通报工、开始加工</td><td><code>(总时长秒 / 60) × Ratio</code></td><td>工作</td></tr>
<tr><td>辅助开始、辅助结束</td><td><code>(总时长秒 / 60) × Ratio</code></td><td>辅助</td></tr>
<tr><td>工作日加班</td><td><code>总时长秒 / 60</code>,不乘系数</td><td>加班</td></tr>
<tr><td>休息日/法定休息日加班</td><td><code>班次内分钟 × Ratio + 班次外分钟</code></td><td>加班</td></tr>
<tr><td>其他类别</td><td><code>(总时长秒 / 60) × Ratio</code></td><td>其他</td></tr>
</table>
<h3>8.6 示例</h3>
<table>
<tr><th>场景</th><th>输入</th><th>结果</th></tr>
<tr><td>普通工时归一化</td><td>某人当日有效总工时 420 分钟,标准工时 8 小时;本条 60 分钟</td><td>Ratio=480/420=1.142857;上传约 68.57 分钟</td></tr>
<tr><td>工作日加班</td><td>加班记录总时长 7,200 秒</td><td>上传 120 分钟,不乘 Ratio</td></tr>
<tr><td>设备报工</td><td>起止 3,600 秒,暂停 600 秒</td><td>总时长 3,600 秒;设备实际工时 3,000 秒SAP 设备 Quantity=50 分钟</td></tr>
<tr><td>短记录</td><td>总时长 299 秒</td><td>UploadMinutes=0WorkType=跳过SAP随后可直接保存为已校验</td></tr>
</table>
</section>
<section id="downstream" class="page-break">
<h2>9. 工时查询、工时报表与结算工时</h2>
<h3>9.1 工时查询</h3>
<p>“工时查询”页面调用 <code>生产管理_工时查询_工序工时_查询</code>,用于从生产报工记录反查具体任务、日期和单次工时。查询参数包括合同号、产品编码、订单号、开始/结束日期和数据条数,默认最多返回 100 条基础工时记录。</p>
<h4>数据范围</h4>
<ul>
<li>主数据源为 <code>View_生产工时视图</code>,只取操作内容或操作类别为“报工/辅助结束”的记录。</li>
<li>要求 <code>加工工时 &gt; 0</code>,因此该页面以设备报工工时为主体。</li>
<li>用订单号和工艺顺序关联 <code>车间生产管理工艺_零件生产工艺_工艺库</code>,补充标准工时。</li>
<li>同 TaskAID、同开始时间从 <code>View_生产工时视图全部</code>汇总加工工时不大于 0 的人员行,拼成操作人列表。</li>
<li>报工数量按 TaskAID + 开始时间取最大值,异常记录取负数;装配工位报工数量固定显示 0。</li>
</ul>
<h4>三级结构</h4>
<table>
<tr><th class="w20">层级</th><th>分组</th><th>展示内容</th></tr>
<tr><td>Level 1</td><td>TaskAID</td><td>任务描述、报工次数、去重报工数量、总工时、单件工时、工艺工时。</td></tr>
<tr><td>Level 2</td><td>TaskAID + 工作日期</td><td>日期报工次数、日期数量、日期总工时和单件工时。</td></tr>
<tr><td>Level 3</td><td>单次开始时间</td><td>班次、操作人、生产订单、正常/调机类型、工位、工时、报工数量和开始时间。</td></tr>
</table>
<div class="formula">工时小时 = 加工工时秒 / 3600<br>工时分钟 = 加工工时秒 / 60<br>单件工时(分钟/件) = 工时分钟 / 报工数量;无有效数量时回退到工艺标准工时</div>
<h3>9.2 工时报表</h3>
<p>“工时报表”页面调用四套统计过程,公共筛选项包括合同号、操作人、产品编码、工序名称、订单号、工时类型和日期范围。页面将结果构造成树形表,顶部只汇总第一层工时,并可按工时类型显示分类合计。</p>
<table>
<tr><th class="w20">页签</th><th class="w30">存储过程</th><th>分组和用途</th></tr>
<tr><td>项目工时</td><td><code>工时统计_项目工时_查询</code></td><td>Level 1 按合同号+产品汇总Level 2 按工序、班组工时类型、操作人和订单展开。</td></tr>
<tr><td>作业者工时</td><td><code>工时统计_作业者工时_查询</code></td><td>Level 1 操作人汇总Level 2 按日期Level 3 展开任务、工序、类别、起止时间、异常/工序说明。</td></tr>
<tr><td>任务工时</td><td><code>工时统计_任务工时_查询</code></td><td>Level 1 按合同、订单、产品、工序、班组工时类型和操作类别汇总Level 2 展开操作人和单次工时。</td></tr>
<tr><td>设备工时</td><td><code>工时统计_设备工时_查询</code></td><td>Level 1 工位汇总Level 2 按日期Level 3 展开订单、产品、工序、班次、报工类型和起止时间。</td></tr>
</table>
<h4>报表有效工时口径</h4>
<table>
<tr><th class="w30">记录条件</th><th>有效工时秒</th></tr>
<tr><td>报工/辅助结束且加工工时大于 0</td><td>加工工时,代表设备工时。</td></tr>
<tr><td>报工/辅助结束且加工工时为 0、总时长大于 0</td><td>总时长,代表人员工时。</td></tr>
<tr><td>加班类别</td><td>总时长。</td></tr>
<tr><td>开始加工且总时长大于 0</td><td>总时长,主要覆盖装配人员开始/结束记录。</td></tr>
<tr><td>作业者报表中的异常</td><td>总时长取负数,用于冲减。</td></tr>
</table>
<p>人员的“工时类型”来自 <code>登录基础数据_人员信息.班组</code>,没有班组时归入“未分类”。项目、作业者和任务报表基于 <code>View_生产工时视图全部</code>,因此可以覆盖已关闭订单;设备报表要求 <code>阶段标识=-1</code>,人员类统计通常排除该阶段。</p>
<h4>导出</h4>
<p>四个页签分别调用对应的 <code>..._查询_导出</code>过程,前端以操作类型 <code>2001</code>触发服务端表格文件生成。导出字段包含合同、订单、产品、工序、人员/设备、指派对象、数量、起止时间和工时。</p>
<h3>9.3 结算工时</h3>
<p>“结算工时”页面调用 <code>生产管理_结算工时_查询</code>,查询参数为合同号、产品编码、订单号、工位、日期范围和数据条数。过程将生产计划、生产任务和工时视图连接,形成物料汇总和订单工位明细两层数据。</p>
<h4>数据条件与分组</h4>
<ul>
<li>生产任务物料类型必须为 <code>pit_Resource</code></li>
<li>生产订单编号大于 6000生产计划单据状态不为空。</li>
<li>工位优先使用生产任务指派对象,未设置时使用工时记录工位名称。</li>
<li>同 TaskAID、同开始时间从全量工时视图拼接人员工时行的操作人。</li>
<li>Level 1 按物料编号/描述汇总Level 2 按物料、订单、工位和工序顺序汇总。</li>
</ul>
<h4>结算公式</h4>
<div class="formula">报工工时 = Σ普通报工加工工时秒 / 3600<br>辅助工时 = Σ辅助结束加工工时秒 / 3600<br>合计工时 = 报工工时 + 辅助工时<br>结算报工单件工时 = 报工工时 / 报工完成数<br>结算辅助单件工时 = 辅助工时 / 辅助完成数</div>
<p>页面同时展示报工完成数、辅助完成数、记录数、操作人和工序顺序,并在浏览器端计算三项总工时。导出使用前端 XLSX 生成“结算工时”工作表。</p>
</section>
<section id="outsource">
<h2>10. 外协与生产计划跟踪</h2>
<h3>10.1 外协在工时体系中的位置</h3>
<p>外协工序仍以生产任务和 <code>TaskAID</code>为主线,并把发料、收料动作写入 <code>YL_加工中心_操作记录表</code>;但 <code>View_生产工时视图</code>明确过滤 <code>工位名称='外协'</code>,因此外协不参与内部设备/人员工时校验、SAP 人员资源工时和常规工时报表。</p>
<div class="flow">
<div class="box"><strong>外协派工</strong><br>连续工序分组、供应商、计划时间、打印与备注</div>
<div class="arrow"></div>
<div class="box"><strong>外协发料</strong><br>更新任务收料数量<br>写“收料”操作记录</div>
<div class="arrow"></div>
<div class="box"><strong>外协收料</strong><br>更新任务完成数量<br>写“报工”操作记录</div>
</div>
<p>外协“报工”代表外协收货/完成数量事件,并非内部人员计时。外协用开始/结束事件时间、发料数量、收料数量、供应商采购到货日期、金额和质检结果完成进度跟踪。</p>
<h3>10.2 外协管理页面</h3>
<table>
<tr><th class="w25">功能</th><th class="w35">过程</th><th>说明</th></tr>
<tr><td>任务查询</td><td><code>生产管理_外协管理_查询外协全部</code><br><code>...查询外协</code></td><td>读取外协资源任务,按订单、产品、工序、派工/发料/完成状态筛选;连续订单行和同一外协分组按组判断状态。</td></tr>
<tr><td>派工</td><td><code>计划排产_派工</code></td><td>设置外协工位、供应商/人员、指派数量、计划时间和外协分组。</td></tr>
<tr><td>外协发料</td><td><code>生产管理_外协管理_外协发料查询</code><br><code>...外协发料保存</code></td><td>增加任务收料数量并写“收料”操作记录;任务可转为已开工/可执行。</td></tr>
<tr><td>外协收料</td><td><code>生产管理_外协管理_外协收料保存</code></td><td>增加完成数量,达到指派数量时任务置完成,并写工位为外协的“报工”操作记录。</td></tr>
<tr><td>历史与质检</td><td><code>...外协历史查询</code><br><code>...查询外协历史记录</code><br><code>...查询质检记录</code></td><td>回查派工、发收料和收检结果。</td></tr>
<tr><td>单据维护</td><td><code>...更新打印次数</code><br><code>...更新备注</code></td><td>保留外协打印和备注信息。</td></tr>
</table>
<h3>10.3 外协报表</h3>
<p>“外协报表”调用 <code>生产管理_外协报表_查询</code>,数据源为 <code>View_外协报表视图</code>。过程按订单号、拆分内码和供应商合并连续外协工序,并分页返回:</p>
<ul>
<li>计划号、合同号、订单、产品、合并工序名称和 TaskAID 列表;</li>
<li>供应商、含税单价/行总计、计划开始/完成、采购预计到货和外协结束时间;</li>
<li>指派数量、发料数量、外协报工收料数量、完成数量;</li>
<li>收检合格/不合格、上序合格数、开票状态、材质和要求完工日期;</li>
<li>首行或上序已有合格数时标记为可执行。</li>
</ul>
<p><code>生产管理_外协报表_导出</code>提供相同口径的服务端导出。</p>
<h3>10.4 生产计划跟踪</h3>
<p>“生产计划跟踪”调用 <code>生产管理_生产计划跟踪</code>,读取包含已关闭订单的生产任务视图,并把订单下各资源工序组织成路线。页面可按订单、合同、产品、工序、计划时间、排产、自制件属性、加急和单据状态筛选,并在前端按是否包含外协工序再次过滤。</p>
<table>
<tr><th class="w25">信息组</th><th>来源与用途</th></tr>
<tr><td>计划与任务</td><td>计划号、计划/指派数量、计划开始/完成、要求完工日期、任务状态、加工状态、进度和标准工时。</td></tr>
<tr><td>工时事件</td><td>按 TaskAID 从操作记录取最早开始、最大结束;外协结束取“报工”记录的开始时间。</td></tr>
<tr><td>质量</td><td>收检合格/不合格按 TaskAID 汇总;终检按计划号判断。</td></tr>
<tr><td>外协</td><td>指派对象为“外协”或工序名含外协即标识外协;连续订单行且外协分组相同的工序按最后一道工序状态统一着色。</td></tr>
<tr><td>路线状态</td><td>已报工/辅助结束/有收检结果显示完成色;外协已发料但收检未齐显示进行色;可执行、已开工和待执行分别显示不同路线状态。</td></tr>
</table>
<p>该页面把工艺标准工时、实际操作起止、外协发收料、质检与任务进度放在同一订单路线中,是工时事实与生产计划执行之间的主要追踪入口。</p>
</section>
<section id="procedures">
<h2>11. 存储过程和函数清单</h2>
<h3>11.1 加工中心核心过程</h3>
<table>
<tr><th class="w30">对象</th><th>作用</th><th class="w30">主要读写对象</th></tr>
<tr><td><code>MES_ProductTask_WorkingStart</code></td><td>开始加工,更新任务状态/收料并生成开始记录。</td><td>生产任务、生产订单视图、质检记录、公共记录过程</td></tr>
<tr><td><code>MES_ProductTask_HelpStart</code></td><td>辅助开始,更新加工状态并生成开始记录。</td><td>生产任务、数据字典、公共记录过程</td></tr>
<tr><td><code>MES_ProductTask_HelpEnd</code></td><td>辅助结束、数量/进度、班次和加班拆分。</td><td>生产任务、班次表、操作记录、报工记录</td></tr>
<tr><td><code>MES_ProductTask_ProdReport</code></td><td>正常报工、绑定任务、完成数量、班次与加班处理。</td><td>生产任务、操作记录、报工记录、班次表</td></tr>
<tr><td><code>MES_ProductCenter_OptRecord_Save</code></td><td>统一插入操作记录,配对开始/暂停/结束,计算总时长、暂停和实际工时。</td><td><code>YL_加工中心_操作记录表</code></td></tr>
<tr><td><code>计算人员工时</code></td><td>按工位人员绑定、班次人员绑定和重叠时间生成人员工时行。</td><td>绑定表、班次表、人员表、操作记录</td></tr>
<tr><td><code>计算班次重叠秒数</code></td><td>标量函数,计算起止区间与班次区间的重叠秒数,最多跨 3 天。</td><td>纯计算</td></tr>
</table>
<h3>11.2 装配中心核心过程</h3>
<table>
<tr><th class="w30">对象</th><th>作用</th></tr>
<tr><td><code>MES_人员工时记录_开始</code></td><td>防止同用户同任务重复开始,更新任务为已开工,插入未闭合“开始加工”记录。</td></tr>
<tr><td><code>MES_人员工时记录_结束</code></td><td>闭合开始记录,拆分工作/加班,写备注和说明,必要时生成普通报工表及加班操作记录。</td></tr>
<tr><td><code>MES_人员工时记录_获取进行中任务</code></td><td>读取当前用户进行中的开始记录。</td></tr>
<tr><td><code>MES_人员工时记录_获取进行中任务2</code></td><td>读取另一组进行中任务数据,供装配页面恢复状态。</td></tr>
</table>
<h3>11.3 修正与补录过程</h3>
<table>
<tr><th class="w30">对象</th><th>作用</th></tr>
<tr><td><code>工时校验_获取操作记录列表</code></td><td>工时修正页面分页查询。</td></tr>
<tr><td><code>工时校验_保存校验记录</code></td><td>名称虽含“保存校验”,当前定义实际更新原始记录起止、总时长、实际工时和班次标准工时。</td></tr>
<tr><td><code>工时校验_新增操作记录</code></td><td>补录报工/辅助记录DATEDIFF 计算实际工时。</td></tr>
<tr><td><code>异常工时_新增操作记录</code></td><td>新增异常工时并补齐任务上下文。</td></tr>
<tr><td><code>加工中心_修改工时_保存修改记录</code></td><td>加工/装配页面内修改异常工时。</td></tr>
<tr><td><code>工时校验_获取设备数据</code></td><td>基于每分钟设备状态,用 LEAD 计算当天运行、待机、关机、报警累计时间。</td></tr>
</table>
<h3>11.4 最终校验与计算过程</h3>
<table>
<tr><th class="w30">对象</th><th>作用</th></tr>
<tr><td><code>工时校验_获取昨日之前操作记录列表</code></td><td>从统一视图分页读取今天以前记录;输出校验/性质/异常/加班/人员筛选结果。</td></tr>
<tr><td><code>usp_BatchValidateWorkTime</code></td><td>拆分 AID逐条计算上传分钟输出成功/失败、详情 JSON 和比例组 JSON。</td></tr>
<tr><td><code>usp_CalculateUploadMinutes</code></td><td>确定类别、班次内外、日比例系数和最终上传分钟。</td></tr>
<tr><td><code>usp_CalculateDailyRatio</code></td><td>计算操作人当日标准工时归一化比例。</td></tr>
<tr><td><code>usp_CalculateShiftTime</code></td><td>逐段累计班次内/外秒数,支持跨天。</td></tr>
<tr><td><code>usp_ValidateWorkTime</code></td><td>单条计算、构造 SAP JSON并在事务中写校验详情当前页面单条调用路径已注释主流程未使用。</td></tr>
<tr><td><code>工时校验_批量保存校验记录</code></td><td>把尚无校验副本的原始记录复制到校验表,作为最终已校验标志。</td></tr>
<tr><td><code>工时校验_获取订单工序</code></td><td>按订单获取工序、工艺顺序、TaskAID、SAP VisCode。</td></tr>
<tr><td><code>工时校验_获取资源</code></td><td>按资源名称读取 SAP <code>UBT_ORSC</code> 的 VisCode 和成本。</td></tr>
</table>
<h3>11.5 查询、报表与结算过程</h3>
<table>
<tr><th class="w30">对象</th><th>作用</th><th class="w25">主要来源</th></tr>
<tr><td><code>生产管理_工时查询_工序工时_查询</code></td><td>生成 TaskAID、日期、单次开始记录三级工序工时树计算数量、小时和单件分钟。</td><td>当前工时视图、全量工时视图、工艺库</td></tr>
<tr><td><code>工时统计_项目工时_查询</code></td><td>按合同产品及工序/班组/人员/订单汇总。</td><td>全量工时视图、人员信息</td></tr>
<tr><td><code>工时统计_作业者工时_查询</code></td><td>按人员、日期和报工明细统计,异常工时负数冲减。</td><td>全量工时视图、人员信息</td></tr>
<tr><td><code>工时统计_任务工时_查询</code></td><td>按订单产品工序任务汇总并展开操作人明细。</td><td>全量工时视图、人员信息</td></tr>
<tr><td><code>工时统计_设备工时_查询</code></td><td>按设备、日期和明细统计阶段标识为 -1 的设备工时。</td><td>全量工时视图</td></tr>
<tr><td><code>工时统计_*_查询_导出</code></td><td>为四类工时报表生成服务端导出数据。</td><td>对应查询口径</td></tr>
<tr><td><code>生产管理_结算工时_查询</code></td><td>按物料、订单、工位汇总报工、辅助、完成数量和单件结算工时。</td><td>生产计划、生产任务、当前/全量工时视图</td></tr>
</table>
<h3>11.6 外协与计划跟踪过程</h3>
<table>
<tr><th class="w30">对象</th><th>作用</th></tr>
<tr><td><code>生产管理_外协管理_查询外协全部</code></td><td>查询全部外协任务,按连续外协分组统一判断派工、发料和完成状态。</td></tr>
<tr><td><code>生产管理_外协管理_外协发料保存</code></td><td>增加任务收料数量并通过公共记录过程写“收料”操作。</td></tr>
<tr><td><code>生产管理_外协管理_外协收料保存</code></td><td>增加完成数量、更新任务完成状态并写外协“报工”操作。</td></tr>
<tr><td><code>生产管理_外协报表_查询</code></td><td>按订单、拆分内码和供应商合并外协工序,返回采购、数量、质检和时间信息。</td></tr>
<tr><td><code>生产管理_外协报表_导出</code></td><td>导出外协报表。</td></tr>
<tr><td><code>生产管理_生产计划跟踪</code></td><td>合并生产任务、标准工时、操作起止、外协结束和质检状态,输出订单工序路线。</td></tr>
</table>
</section>
<section id="tables" class="page-break">
<h2>12. 数据表清单与字段说明</h2>
<h3>12.1 YL_加工中心_操作记录表事实源</h3>
<p>该表同时承载设备动作、人员动作、加班、异常和补录。当前可见索引为 <code>IX_YL_加工中心_操作记录表_TaskAID_操作内容</code>,键列包含 TaskAID、操作内容、开始时间、结束时间、操作人未查询到显式主键索引。</p>
<table>
<tr><th class="w25">字段组</th><th>字段</th><th>说明</th></tr>
<tr><td>身份与分类</td><td>AID、操作类别编号、操作类别、阶段标识、TaskAID</td><td>AID 是记录标识TaskAID 关联生产任务。</td></tr>
<tr><td>业务上下文</td><td>工序名称、工位编号、工位名称、操作人、合同号、订单编号、物料编号、物料描述</td><td>保留报工发生时的任务快照。</td></tr>
<tr><td>动作内容</td><td>操作数值、操作内容、操作描述、备注、工序说明、异常说明</td><td>数量、显示动作和人工说明。</td></tr>
<tr><td>时间与工时</td><td>开始时间、结束时间、工时、总时长、实际工时、报工日期</td><td>核心单位均为秒;实际工时主要作为设备工时。</td></tr>
<tr><td>周期关联</td><td>关联开始记录ID、关联暂停记录ID、关联操作类型、暂停时间、暂停时长、暂停总时长</td><td>支持开始-暂停-恢复-结束的周期闭合。</td></tr>
<tr><td>状态与审计</td><td>已报工、不装配、是否更新、最后编辑人、最后编辑时间</td><td>控制可见性和保留最近编辑信息。</td></tr>
<tr><td>班次</td><td>班次标准工时(默认 8、班次</td><td>供归一化和加班计算使用。</td></tr>
</table>
<h3>12.2 生产管理_工时校验_校验工时表</h3>
<p>该表保存校验副本,大部分业务字段与事实表一致,并增加:</p>
<table>
<tr><th class="w25">字段</th><th>作用</th></tr>
<tr><td><code>报工记录id</code></td><td>关联原始操作记录 AID是“已校验”的判断依据。</td></tr>
<tr><td><code>计算详情</code></td><td>预留保存计算 JSON主批量流程当前未写入。</td></tr>
<tr><td><code>上传工时</code></td><td>预留保存最终 SAP 分钟;主批量流程当前未写入。</td></tr>
<tr><td><code>工时类型</code></td><td>工作、辅助、加班、其他、跳过SAP主批量流程当前未写入。</td></tr>
</table>
<h3>12.3 其他关键表</h3>
<table>
<tr><th class="w30"></th><th>用途</th></tr>
<tr><td><code>MES_接口_生产计划_生产任务</code></td><td>任务状态、加工状态、完成数量、工序、指派工位和 TaskAID。</td></tr>
<tr><td><code>MES_接口_生产计划</code></td><td>订单头/计划数据,为统一视图补合同号、计划数量、入库和单据状态。</td></tr>
<tr><td><code>YL_加工中心_报工记录</code></td><td>保存报工日期、班次、工位、操作人、工时类型、数量、工时和确认/上报状态。</td></tr>
<tr><td><code>班次表</code></td><td>班次编号、名称、开始/结束时间、删除状态。</td></tr>
<tr><td><code>班次人员绑定表</code></td><td>人员属于哪个班次及绑定时间。</td></tr>
<tr><td><code>工位人员绑定表</code></td><td>人员与 SAP 资源工位的绑定关系及有效状态。</td></tr>
<tr><td><code>登录基础数据_人员信息</code></td><td>人员编号和姓名映射。</td></tr>
<tr><td><code>MES_资源主数据</code></td><td>工位 VisCode 与名称,用于绑定和 SAP 资源行。</td></tr>
<tr><td><code>历史_工位设备状态每分钟记录</code></td><td>每分钟设备状态,用于工时修正页面核对设备运行时间。</td></tr>
<tr><td><code>YL_基础表_数据字典</code></td><td>将“开始加工、辅助开始、辅助结束、暂停、报工”等名称映射成编号。</td></tr>
<tr><td><code>YL_质量检验_质检记录</code></td><td>加工开始时取得前序合格数,影响收料数量。</td></tr>
<tr><td><code>车间生产管理工艺_零件生产工艺_工艺库</code></td><td>按订单和工艺顺序提供标准工时,供工时查询和生产计划跟踪对比。</td></tr>
<tr><td><code>SAP.SBO_YL.dbo.UBT_WW_MES_OPORandOPCH</code></td><td>外协采购供应商、价格、预计到货、开票和材质信息。</td></tr>
</table>
</section>
<section id="views">
<h2>13. 视图及取数规则</h2>
<h3>13.1 View_生产工时视图</h3>
<p>面向当前有效生产订单,是工时修正、工时校验、计算过程的核心视图。连接关系:</p>
<pre>YL_加工中心_操作记录表 o
LEFT JOIN 生产管理_工时校验_校验工时表 c ON o.AID = c.报工记录id
LEFT JOIN View_生产订单_MES m ON COALESCE(c.TaskAID, o.TaskAID) = m.TaskAID
LEFT JOIN MES_接口_生产计划 s ON COALESCE(c.订单编号, o.订单编号) = s.订单编号
LEFT JOIN View_工位人员绑定数 b ON o.操作人 = b.人员姓名</pre>
<p>有校验副本时优先取校验表字段;无校验副本时取原始记录。主要过滤:</p>
<ul>
<li>排除工位“外协”。</li>
<li>报工记录必须有结束时间。</li>
<li>未校验的“收料”不显示。</li>
<li>消除已经被报工/辅助结束闭合的开始记录、未闭合的辅助结束和暂停形成的重复记录。</li>
<li>要求操作数值非空,或是尚未闭合的开始动作,或操作描述非空。</li>
<li>当日班次最小标准工时取该操作人、同日、“报工”记录的最小班次标准工时。</li>
</ul>
<h3>13.2 View_生产工时视图全部</h3>
<p>与核心视图结构和过滤基本一致,但任务来源改为 <code>View_生产订单全部包括已关闭</code>,主要供工时统计与历史查询。</p>
<h3>13.3 View_校验后的生产工时</h3>
<p>同样合并原始与校验数据,但强制 <code>c.AID IS NOT NULL</code>,只返回已形成校验副本的记录。</p>
<h3>13.4 绑定与班次视图</h3>
<table>
<tr><th class="w30">视图</th><th>规则</th></tr>
<tr><td><code>View_工位人员绑定数</code></td><td>按人员统计当前有效且能匹配资源主数据的不同工位数。</td></tr>
<tr><td><code>View_设备工位人员</code></td><td>连接人员、工位绑定、班次人员绑定和班次表,提供人员/工位的班次起止时间;仅保留有效绑定。</td></tr>
<tr><td><code>View_生产订单_MES</code></td><td>提供 TaskAID、订单、工序、工位、任务状态、计划与完成数量是所有入口过程的任务上下文。</td></tr>
</table>
<h3>13.5 View_外协报表视图</h3>
<p>该视图仅保留物料类型为 <code>pit_Resource</code>且指派对象为“外协”的生产任务,合并以下信息:</p>
<ul>
<li>生产任务的计划、指派、完成、进度、外协分组和状态;</li>
<li>操作记录的最早开始、最大结束、发料“收料”数量和外协“报工”收料数量;</li>
<li>收检合格/不合格数量;</li>
<li>SAP 外协采购的供应商、价格、预计到货、开票和材质。</li>
</ul>
</section>
<section id="sap" class="page-break">
<h2>14. SAP 对接映射</h2>
<h3>14.1 调用封装</h3>
<p><code>src/api/b1s.js</code>把 GET/POST/PATCH 都封装成向配置的 B1 代理地址发 POST请求体含 <code>func、cmd、content</code>;默认超时 100 秒。</p>
<h3>14.2 校验上传步骤</h3>
<table>
<tr><th class="w18">顺序</th><th class="w30">接口</th><th>用途</th></tr>
<tr><td>1</td><td><code>GET ProductionOrders</code></td><td><code>AbsoluteEntry</code>取得生产订单、阶段和资源行。</td></tr>
<tr><td>2</td><td><code>PATCH ProductionOrders(id)</code></td><td>补齐 MES 工序阶段和当前工序需要的设备/人员资源。</td></tr>
<tr><td>3</td><td><code>PATCH ProductionOrders(id)</code></td><td>把匹配资源行的 <code>PlannedQuantity</code>调整为已发数量加本次 Quantity。</td></tr>
<tr><td>4</td><td><code>POST InventoryGenExits</code></td><td>创建资源消耗单据Comments 记录 MES 工时 AID。</td></tr>
</table>
<h3>14.3 工序和资源规则</h3>
<ul>
<li>当前工序匹配条件MES 工序名称相等且工艺顺序等于工时记录订单行号。</li>
<li>StageID 取 MES 工艺顺序;特定期初物料、<code>Z108-001</code><code>26</code>强制 StageID=1。</li>
<li>普通设备资源名:工位名称;普通人员资源名:操作人。</li>
<li>辅助设备/人员资源:<code>辅助-工位</code><code>辅助-人员</code></li>
<li>加班人员资源:<code>加班-人员</code></li>
<li>资源 VisCode 由 <code>工时校验_获取资源</code>从 SAP <code>UBT_ORSC</code>按精确名称获取。</li>
</ul>
<h3>14.4 DocumentLines 映射</h3>
<table>
<tr><th class="w25">字段</th><th></th></tr>
<tr><td>Quantity</td><td>设备行:<code>加工工时秒/60</code>;人员行:数据库计算的 <code>UploadMinutes</code></td></tr>
<tr><td>BaseType</td><td><code>202</code>(生产订单)。</td></tr>
<tr><td>BaseEntry</td><td>生产订单编号/AbsoluteEntry。</td></tr>
<tr><td>BaseLine</td><td>SAP 生产订单中匹配资源行的 LineNumber。</td></tr>
<tr><td>LineType</td><td><code>dlt_Resource</code></td></tr>
<tr><td>WarehouseCode</td><td>空字符串。</td></tr>
<tr><td>U_ResName</td><td>原始工位名称或操作人名称。</td></tr>
</table>
</section>
<section id="states">
<h2>15. 状态、操作类别与单位</h2>
<h3>15.1 任务状态</h3>
<table>
<tr><th class="w20">值/名称</th><th>业务含义</th><th>变化条件</th></tr>
<tr><td>0 / 待执行</td><td>尚不可开工</td><td>由计划和前序控制</td></tr>
<tr><td>1 / 可执行</td><td>允许开始</td><td>第一次开始加工后转 2</td></tr>
<tr><td>2 / 已开工</td><td>任务处理中</td><td>继续开始仅记录新周期</td></tr>
<tr><td>4 / 已完成</td><td>完成数量达到指派数量</td><td>报工/辅助结束累计完成</td></tr>
</table>
<h3>15.2 操作类别</h3>
<p><span class="pill">收料</span><span class="pill">辅助开始</span><span class="pill">辅助结束</span><span class="pill">开始加工</span><span class="pill">暂停</span><span class="pill">报工</span><span class="pill">加班</span><span class="pill">休息日加班</span><span class="pill">法定休息日加班</span><span class="pill">休息日工作</span><span class="pill">法定休息日工作</span></p>
<h3>15.3 单位约定</h3>
<table>
<tr><th>位置</th><th>字段/显示</th><th>单位</th></tr>
<tr><td>数据库事实表</td><td>工时、总时长、实际工时、暂停时长</td><td></td></tr>
<tr><td>工时修正/校验编辑框</td><td>设备工时、人员工时</td><td></td></tr>
<tr><td>工时校验表格</td><td>设备工时、人员工时</td><td>分钟(秒/60</td></tr>
<tr><td>计算过程</td><td>UploadMinutes</td><td>分钟decimal(18,2)</td></tr>
<tr><td>SAP 资源行</td><td>Quantity</td><td>分钟</td></tr>
<tr><td>班次标准工时</td><td>页面输入与记录字段</td><td>业务上按小时使用,但部分过程注释写“分钟”,需统一</td></tr>
</table>
</section>
<section id="appendix">
<h2>16. 调用矩阵与资料来源</h2>
<h3>16.1 页面到对象调用矩阵</h3>
<table>
<tr><th class="w18">页面</th><th class="w35">直接过程/接口</th><th>核心落点</th></tr>
<tr><td>加工中心</td><td>WorkingStart、HelpStart、HelpEnd、ProdReport、OptRecord_Save、异常新增/修改</td><td>生产任务、操作记录、报工记录</td></tr>
<tr><td>装配中心</td><td>人员工时开始/结束、获取进行中任务、异常新增/修改</td><td>生产任务、操作记录、报工记录</td></tr>
<tr><td>人员绑定</td><td>工位绑定人员查询/保存</td><td>工位人员绑定表、资源主数据</td></tr>
<tr><td>班次管理</td><td>班次查询/保存/删除、班次绑定人员查询/保存</td><td>班次表、班次人员绑定表</td></tr>
<tr><td>工时修正</td><td>获取操作记录、保存校验记录、补录、设备数据</td><td>操作记录、设备分钟状态</td></tr>
<tr><td>工时校验</td><td>昨日之前列表、BatchValidate、Calculate*、获取订单/资源、SAP GET/PATCH/POST、批量保存</td><td>统一视图、SAP、校验表</td></tr>
<tr><td>工时查询</td><td>生产管理_工时查询_工序工时_查询</td><td>当前/全量工时视图、工艺标准工时</td></tr>
<tr><td>工时报表</td><td>项目/作业者/任务/设备查询及四套导出过程</td><td>全量工时视图、人员班组</td></tr>
<tr><td>结算工时</td><td>生产管理_结算工时_查询</td><td>生产计划、任务、当前/全量工时视图</td></tr>
<tr><td>外协管理/报表</td><td>外协任务查询、派工、发料、收料、历史、质检、报表和导出</td><td>外协任务、操作记录、采购、质检、外协报表视图</td></tr>
<tr><td>生产计划跟踪</td><td>生产管理_生产计划跟踪</td><td>全部订单、操作记录、工艺库、质检记录</td></tr>
</table>
<h3>16.2 主要代码来源</h3>
<ul>
<li><code>src/views/ProductionManagement/MachiningCenter/index.vue</code></li>
<li><code>src/views/ProductionManagement/AssemblyCenter/index.vue</code></li>
<li><code>src/views/ProductionManagement/WorkhoursEdit/index.vue</code></li>
<li><code>src/views/ProductionManagement/WorkHoursCheck/index.vue</code></li>
<li><code>src/views/ProductionManagement/PersonnelBinding/index.vue</code></li>
<li><code>src/views/ProductionManagement/ShiftManagement/index.vue</code></li>
<li><code>src/views/ProductionManagement/Workhours/index.vue</code></li>
<li><code>src/views/ProductionManagement/Timesheet/index.vue</code></li>
<li><code>src/views/ProductionManagement/SettlementWorkhours/index.vue</code></li>
<li><code>src/views/ProductionManagement/OutsourceMange/index.vue</code></li>
<li><code>src/views/ProductionManagement/SaleOutsourceMange/index.vue</code></li>
<li><code>src/views/ProductionManagement/OutsourceSheet/index.vue</code></li>
<li><code>src/views/ProductionManagement/ProductionPlanTrack/index.vue</code></li>
<li><code>src/api/b1s.js</code></li>
</ul>
<h3>16.3 数据库核对说明</h3>
<p>本文通过只读查询 SQL Server 系统目录获取当前存储过程参数、模块定义、对象依赖、表字段、索引和班次配置。未执行任何业务存储过程,未新增、更新或删除数据库数据。文档不包含数据库地址、账号或密码。</p>
<div class="note"><strong>使用建议:</strong>本说明适合作为流程评审、工时口径确认、SAP 联调和后续重构的基线。若数据库对象在 2026-07-21 后调整,应重新提取定义并更新版本。</div>
</section>
</body>
</html>

Binary file not shown.

View File

@@ -4807,6 +4807,39 @@ pm run build通过仅有既有警告。
### 结论
- 明日计划已改为8个主时间段、每段6条大工作项。
- 每条记录不再包含子序号和10分钟时间格式。
- 工作项粒度更适合报工填写,持续性任务允许重复。
### 验证结果
- 8个主时间段保留。
- 每段6条记录共48条。
- 子序号和子时间已移除。
- 本次未修改业务资源。
## 2026-07-21 新增四类大屏发货任务堵点查询
### 用户提问
- 用户要求根据桌面 `发货任务堵点.docx` 修改大厅计划、仓库、装配和质检测试大屏,并指定已完成的发货通知页面及 `装配中心任务看板_发货状态查询` 作为参考。
- 经需求确认,装配阶段在完成数量小于计划数量时显示“装配”,相等后继续检查质检和入库;质检大屏使用序检检验数量。
### 执行过程
1. 只读分析需求文档、四个大屏和发货通知参考实现,确认需为不同看板提供不同来源及过滤规则,且不应直接改变发货通知页现有查询口径。
2. 只读核查生产库字段和枚举,确认终检合格、收检不合格的既有业务口径;确认序检数据在质检记录中对应 `检测类型编号=6、质检类型=收检`
3. 新建迁移 `db_backups/create_shipping_bottleneck_dashboard_proc_20260721.sql`,定义独立过程 `dbo.发货任务堵点_看板查询`,参数支持大厅、仓库、装配、质检四种看板类型。
4. 数据源第一部分读取 `[SAP].[SBO_YL].[dbo].[UBT_ODRF_ODLNMES]` 并左连 MES 生产订单;第二部分读取 `View_生产订单_MES.优先级='是'` 的装配任务,并排除已存在于 SAP 的订单。
5. 预先按 TaskAID 汇总当前装配操作人、终检合格数量、收检不合格数量和序检检验数量,并按合同号汇总装配工时,避免一对多连接造成重复和数量放大。
6. 特殊物料编码包含 `3420-YT``3420-HT``3420-YF` 时堵点直接返回“见子任务”;其他记录依次判断齐套、派发、发料、开工、装配、质检、待入库。
7. 大厅过滤“终检合格+收检不合格=计划数量”的记录;仓库仅返回 SAP 来源;装配仅返回完成数量小于计划数量;质检仅返回序检检验数量小于完成数量。
8. 在事务中临时执行迁移并调用四种参数预演结果为大厅16行、仓库17行、装配12行、质检2行随后回滚确认迁移可编译且口径正确。
9. 两个前端项目完成过程切换和堵点字段接入后,分别执行 `npm run build`,构建成功,仅有既有非阻断警告。
10. 首次正式发布在提交前的 PowerShell 数据校验中因 DBNull 转 decimal 失败,异常处理成功回滚事务,未创建生产过程。
11. 修正校验脚本的空值处理后重新执行,事务内验证仓库仅含 SAP、大厅不含已完成质检任务、装配不含已完成装配任务、质检不含已完成序检任务全部通过后提交。
12. 正式发布后过程创建及修改时间为 `2026-07-21 10:06:45`独立复核四种结果仍为16、17、12、2行每种结果包含28个字段和堵点字段。
13. 核对原 `装配中心任务看板_发货状态查询` 修改时间仍为 `2026-07-20 11:29:55`,确认发货通知参考过程没有被本次发布覆盖。
14. 使用本地 Vite 页面连接生产接口在1920×1080下检查大厅、仓库、装配和质检页面字段及实时数据正常显示。
15. 页面复核发现仓库旧汇总过程的发货数量与新SAP明细不一致前端改为从 `发货任务堵点_看板查询` 的仓库结果计算发货任务、小于今日和加急条数,未再修改数据库过程。
### 修改文件
- `db_backups/create_shipping_bottleneck_dashboard_proc_20260721.sql`
- `gptlog-process/gpdlog.md`
- 外部看板项目的四个页面数据接入文件。
@@ -4842,6 +4875,7 @@ pm run build通过仅有既有警告。
- `src/views/ProductionManagement/SaleOutsourceMange/index.vue`
- `db_backups/add_design_report_hours_report_permissions_20260720.sql`
- `db_backups/fix_delivery_notice_list_current_operators_20260720.sql`
- `db_backups/fix_delivery_notice_list_data_merge_20260720.sql`
### 结论
- 当前工作区的全部修改已统一提交并推送到远端当前分支。
@@ -4875,3 +4909,420 @@ pm run build通过仅有既有警告。
### 结论
- 该计划用于报工填写是合理的,任务方向和时间顺序基本正确。
- 该计划作为当天全部交付承诺偏满,主要风险是四个大屏同时开发测试、客户确认时效以及临时线上问题。
- 在需求上午及时冻结、公共逻辑能够复用且没有较大线上问题的情况下,核心任务可以完成;全部扩展内容能否完成不应作刚性承诺。
- 建议以14:20为切换检查点并保留最后40分钟作为修正和归档缓冲。
### 验证结果
- 上午开发范围和未确认口径已识别。
- 下午建档范围和可交付粒度已评估。
- 必须完成项、争取完成项和阶段检查点已给出。
- 本次未修改业务资源。
## 2026-07-21 生成工时模块全流程技术说明 PDF
### 用户提问
- 用户要求详细整理从加工中心、装配中心开始,到工时校验结束的全部工时相关功能,包括页面、存储过程、计算方法、数据表、视图等,并生成 PDF 文件。
- 用户提供了数据库只读查询条件,明确要求只可查询、不可修改。
### 执行过程
1. 检索生产管理前端目录及数据库备份脚本,定位加工中心、装配中心、工时修正、工时校验、人员绑定、班次管理等相关页面。
2. 逐页提取 `CreateData` 调用、前端校验条件、开始/结束动作、加班选择、异常工时、工时编辑和 SAP 调用逻辑。
3. 确认加工中心通过 `MES_ProductTask_WorkingStart``MES_ProductTask_HelpStart``MES_ProductTask_HelpEnd``MES_ProductTask_ProdReport` 产生工时记录。
4. 确认装配中心通过 `MES_人员工时记录_开始``MES_人员工时记录_结束` 产生人员工时和加班记录。
5. 确认工时修正页面可更新起止时间、设备工时、人员工时和班次标准工时,并支持补录操作记录。
6. 确认工时校验页面按昨日以前记录进行筛选,调用批量工时计算过程,并逐条补充 SAP 工序和资源、提交资源工时单据,上传成功后保存校验副本。
7. 使用只读 SQL 查询数据库系统目录,核对当前存储过程参数、对象类型、对象依赖、表字段、索引、视图定义、函数定义和班次配置;未执行任何业务存储过程,未写入或修改数据库数据。
8. 读取 `MES_ProductCenter_OptRecord_Save` 当前定义,确认设备总时长、暂停总时长和实际工时的计算与开始/结束记录配对规则。
9. 读取 `计算人员工时``计算班次重叠秒数` 当前定义,确认人员、工位、班次绑定及重叠秒数的拆分逻辑。
10. 读取 `usp_CalculateDailyRatio``usp_CalculateShiftTime``usp_CalculateUploadMinutes``usp_BatchValidateWorkTime``usp_ValidateWorkTime` 当前定义,整理当日归一化系数、班次内外拆分及 SAP 上传分钟公式。
11. 读取 `View_生产工时视图``View_生产工时视图全部``View_校验后的生产工时``View_工位人员绑定数``View_设备工位人员` 当前定义,整理视图连接、字段优先级和过滤规则。
12. 核对 `YL_加工中心_操作记录表``生产管理_工时校验_校验工时表``YL_加工中心_报工记录`、班次及绑定相关表的字段用途。
13. 核对 `src/api/b1s.js` 和工时校验页面,整理 SAP `ProductionOrders` 查询/更新、资源行计划量调整和 `InventoryGenExits` 工时单据映射。
14. 明确文档边界:以工时校验为终点;工时查询、工时报表、结算工时和设计报工属于校验后统计或其他业务域,不作为主流程展开。
15. 整理当前实现中的重点风险包括班次配置与代码注释不一致、日期筛选参数未生效、300 秒跳过 SAP、批量计算详情未落库、直接校验前端硬编码权限、校验表缺少唯一约束等。
16. 编写详细 HTML 文档,包含目录、总流程、页面清单、加工/装配流程、工时修正、校验流程、计算公式、过程函数矩阵、表视图说明、SAP 映射、状态单位、风险和验收用例。
17. 使用本机 Edge 无头打印生成 PDF保留 HTML 作为可维护源文件。
18. 使用 PDF 文本解析检查成品,共 20 页、约 1.5 万个可检索字符无空白页关键过程、视图、SAP 接口和风险章节均存在。
19. 检查 PDF 字体对象,确认包含嵌入式 TrueType 字体;使用浏览器截图确认 HTML 中文和版式正常。Node 版 PDF.js 画布后端对部分中文子集字体映射不完整,因此该预览图不作为成品显示判定依据。
20. 计算 PDF SHA256 校验值,并将仅用于校验的临时浏览器配置和预览图片移出工作区。
21. 保留工作区中与本任务无关的既有修改,不覆盖、不撤销,也不纳入本次文档内容。
### 新增文件
- `doc/工时模块全流程技术说明_20260721.html`
- `doc/工时模块全流程技术说明_20260721.pdf`
### 结论
- 已完成从加工中心、装配中心到工时校验结束的工时模块全流程整理。
- 文档详细覆盖 6 个直接或支撑页面、核心存储过程和函数、事实表与校验表、统一工时视图、人员/班次绑定、设备与人员工时计算、加班规则、SAP 上传以及现状风险。
- 数据库查询全过程为只读,没有修改数据库对象、配置或业务数据。
- PDF 共 20 页可检索、无空白页HTML 源文件一并保留,便于后续更新。
### 验证结果
- 前端页面和调用链检索:完成。
- 数据库当前对象定义只读核对:完成。
- PDF 文件生成:成功。
- PDF 页数20 页。
- PDF 可检索文本:约 1.5 万字符。
- PDF 空白页0 页。
- PDF SHA256`C2A90033708903EB3AC64AD015369F6D136236A375A315869D8B5EAE8B9F68B8`
## 2026-07-21 扩展工时模块 PDF 范围
### 用户提问
- 用户确认工时查询、工时报表和结算工时也属于工时模块,需要补充外协部分、生产计划跟踪报表等功能。
- 用户确认设计报工不纳入本文,并要求删除“现状风险与核对项”章节。
### 执行过程
1. 检索 `Workhours``Timesheet``SettlementWorkhours``OutsourceMange``SaleOutsourceMange``OutsourceSheet``ProductionPlanTrack` 页面,提取查询参数、树形分组、汇总公式、导出和外协发收料调用。
2. 使用只读 SQL 查询当前数据库定义,核对 `生产管理_工时查询_工序工时_查询`、项目/作业者/任务/设备四类工时统计过程、`生产管理_结算工时_查询``生产管理_生产计划跟踪`、外协管理和外协报表过程。
3. 确认工时查询使用当前生产工时视图,按 TaskAID、日期、单次开始记录形成三级工序工时树并关联工艺库标准工时。
4. 确认工时报表使用包含已关闭订单的全量工时视图,提供项目、作业者、任务和设备四个页签及四套导出过程;人员班组用于工时类型分类。
5. 确认结算工时连接生产计划、资源任务和工时视图,按物料及订单工位形成两层汇总,计算报工、辅助、合计和单件结算工时。
6. 读取 `View_外协报表视图`、外协任务查询、外协发料保存和外协收料保存定义,确认外协发料写“收料”记录、外协收料写“报工”记录。
7. 确认标准生产工时视图排除工位为“外协”的记录,因此外协不参与内部设备/人员工时校验,而通过外协任务、操作事件、采购、发收料和质检链路单独跟踪。
8. 读取生产计划跟踪过程,确认其合并全部订单、工艺标准工时、操作起止、外协结束、收检/终检和任务状态,并由前端生成订单工序路线。
9. 修改文档封面、范围、总流程和页面清单,将查询、报表、结算、外协和生产计划跟踪纳入完整工时模块。
10. 新增“工时查询、工时报表与结算工时”章节,详细记录数据范围、三级结构、有效工时口径、结算公式和导出方式。
11. 新增“外协与生产计划跟踪”章节,详细记录外协在工时体系中的边界、派工、发料、收料、外协报表和生产路线跟踪。
12. 扩展存储过程、数据表、视图、页面调用矩阵和代码来源清单,并统一重排为 16 章。
13. 将设计报工、设计工时校验和设计工时报表明确列为排除业务域。
14. 完整删除“现状风险与核对项”及原验收用例,不保留目录链接或章节残留。
15. 重新使用 Edge 生成 PDF并用 PDF 文本解析检查页数、可检索字符、空白页和新增页面路径。
16. 使用浏览器截图复核新版封面、目录、中文字体和版式,显示正常。
17. 数据库操作全过程仅执行只读查询,没有执行存储过程,没有修改数据库对象、配置或业务数据。
### 修改文件
- `doc/工时模块全流程技术说明_20260721.html`
- `doc/工时模块全流程技术说明_20260721.pdf`
- `gptlog-process/gpdlog.md`
### 结论
- 工时查询、工时报表、结算工时、外协管理、外协报表和生产计划跟踪已纳入工时模块说明。
- 设计报工相关页面和数据库对象明确排除。
- “现状风险与核对项”章节已完整删除。
- 新版文档共 23 页,覆盖从现场报工、外协业务、工时校验到查询、统计、结算和生产计划跟踪的完整链路。
### 验证结果
- HTML 章节数量16 章。
- PDF 页数23 页。
- PDF 可检索文本:约 1.99 万字符。
- PDF 空白页0 页。
- 新增页面路径检查:全部存在。
- 已删除章节残留检查:未发现。
- PDF SHA256`93D33B50BBDF7FBF42D3B70A744C76F494AEF3F9CDC8C90F1FC9237976577567`
## 2026-07-21 根据工作内容生成明日计划
### 用户提问
- 用户提供 8 个明日工作时间段,要求根据生产退库接口、采购申请接口、大屏刷新与设备状态、及时拣配机加/装配报表、标牌打印内容管理及临时调整归档等内容生成明日计划。
- 用户要求每小时拆分为 6 条记录,内容允许重复。
### 执行过程
1. 保留用户给出的 8 个主时间段和工作先后顺序。
2. 按每 10 分钟一条记录拆分:完整 1 小时时间段各生成 6 条。
3. `10:30-11:50` 共 80 分钟,生成 8 条记录;`17:20-18:00` 共 40 分钟,生成 4 条记录。
4. 全天共生成 48 条计划记录。
5. `08:30-09:30` 围绕生产退库接口和采购申请接口的现状核对、参数修改、联调及验证安排。
6. `09:30-10:30` 围绕所有大屏自动刷新方式、刷新时间、机加看板一设备运行状态及明细安排。
7. `10:30-11:50``13:20-14:20` 围绕 SAP 及时拣配机加报表的数据源、字段、接口、页面和联调安排。
8. `13:20-14:20``14:20-15:20` 同时衔接及时拣配装配报表的数据对接、开发、测试和修正。
9. `15:20-17:20` 围绕标牌打印内容管理页面的字段对接、功能开发、测试和修改安排。
10. `17:20-18:00` 安排大屏及报工临时调整、版本整理和当日修改归档。
11. 本次只生成计划文本并追加过程日志,没有修改业务代码、数据库对象或业务数据。
### 修改文件
- `gptlog-process/gpdlog.md`
### 结论
- 已按 8 个主时间段生成 48 条明日计划记录。
- 完整小时均拆分为 6 条,非完整小时按实际每 10 分钟一条拆分。
- 计划覆盖接口修改、大屏调整、及时拣配机加/装配报表、标牌打印内容管理、临时问题和版本归档。
### 验证结果
- 主时间段8 个。
- 完整小时拆分:每段 6 条。
- 80 分钟时段8 条。
- 40 分钟时段4 条。
- 全天计划记录48 条。
## 2026-07-22 检查及时齐套跟踪结果订单 8238 少一条数据
### 用户提问
- 用户反馈“生产管理-及时齐套跟踪结果”查询生产订单(订单编号)`8238` 时,存储过程只返回 6 条数据,而 `[SAP].[SBO_YL].[dbo].[VIEW_Jijiankukc]` 中有 7 条,要求检查原因。
- 用户进一步明确问题位于存储过程而非页面,并提供数据库 `192.168.2.92` 的连接账号和密码;日志不记录明文密码。
### 执行过程
1. 在项目中检索“及时齐套”“齐套跟踪”和 `VIEW_Jijiankukc`,定位查询过程为 `dbo.生产管理_及时齐套跟踪结果_查询`,本地对应脚本为 `db_backups/update_timely_kit_tracking_result_multi_detail_orders_20260630.sql`
2. 检查本地过程脚本,确认过程先从 SAP 视图读取未发货数量大于 0 的缺件,再在最终结果处使用 `ROW_NUMBER()` 分组去重,并仅保留 `rn = 1`
3. 使用用户提供的连接信息,以只读方式连接 `192.168.2.92 / YL_MESDB`;未执行任何数据库写入、对象发布或数据修改。
4. 查询线上过程元数据,确认 `dbo.生产管理_及时齐套跟踪结果_查询` 最后修改时间为 `2026-07-15 14:25:11.830`,线上定义同时包含 SAP 源级去重和最终结果去重逻辑。
5. 查询 `[SAP].[SBO_YL].[dbo].[VIEW_Jijiankukc] WHERE DocEntry = 8238`,确认共有 7 条缺件记录SAP 行号分别为 `2、3、21、28、33、48、75`,且未发货数量均大于 0。
6. 执行 `EXEC dbo.[生产管理_及时齐套跟踪结果_查询] @订单号 = 8238`,确认过程实际返回 6 条SAP 行号为 `2、3、21、28、48、75`,缺少 SAP 行号 `33`
7. 对比缺少的 SAP 行号 `33` 与保留的行号 `48`:行号 33 为物料 `23601-032-A6x6x25`(平键),缺件数量 2行号 48 为物料 `23202-030-M5x12`(螺钉),缺件数量 5两条均为外购件且“在制采购”均为 `4957`
8. 核对过程字段映射,确认外购件的“缺件生产订单号”实际取 `在制采购`。最终去重按“订单号 + 缺件生产订单号”分组;当该字段为空时才改用“缺件物料编码 + SAP 行号”。因此两种不同外购物料因共同的 `在制采购 = 4957` 被错误归到同一组。
9. 使用与过程一致的分组和排序表达式复算 7 条记录SAP 行号 48 因缺件数量 5 较大得到 `rn = 1` 并保留SAP 行号 33 得到 `rn = 2`,被最终 `WHERE rn = 1` 排除;其余 5 条均为各自分组的 `rn = 1`
10. 本次按“检查原因”的范围仅完成只读诊断,未修改业务代码、存储过程或数据库数据。
### 结论
- 页面或 SAP 视图没有少数据,少一条发生在 `dbo.生产管理_及时齐套跟踪结果_查询` 的末级去重。
- 被排除的是 SAP 行号 `33`、物料 `23601-032-A6x6x25`(平键)。它与 SAP 行号 `48`、物料 `23202-030-M5x12`(螺钉)的“在制采购”均为 `4957`,过程误把两条不同外购物料视为同一“缺件生产订单号”分组。
- 分组内按缺件数量倒序,行号 48 的缺件数量为 5得到 `rn = 1`;行号 33 的缺件数量为 2得到 `rn = 2` 并被过滤,所以最终由 7 条变成 6 条。
- 若后续修复,外购件不应仅按“在制采购”去重,应至少将物料编码或 SAP 行号纳入去重键;自制件仍可按实际缺件生产订单号归并。该修复本次未执行。
### 验证结果
- SAP 视图订单 82387 条。
- 存储过程订单 82386 条。
- 明确缺失记录SAP 行号 33平键缺件数量 2。
- 末级去重复算:行号 33 的 `rn = 2`,行号 48 的 `rn = 1`
- 数据库修改:无。
## 2026-07-22 修正及时齐套跟踪结果订单 8238 外购缺件误去重
### 用户提问
- 用户确认上一轮诊断结论正确,要求修正 `dbo.生产管理_及时齐套跟踪结果_查询` 将订单 `8238` 的两种不同外购物料误合并的问题。
### 执行过程
1. 制定执行步骤:核对线上与本地去重逻辑、修改过程脚本、事务预演、正式发布、回归验证和追加日志。
2. 读取本地 `db_backups/update_timely_kit_tracking_result_multi_detail_orders_20260630.sql` 第 379 至 425 行,并读取线上过程定义末段,确认两者均按“订单号 + 缺件生产订单号”执行最终 `ROW_NUMBER()` 去重。
3. 检查该 SQL 脚本的 Git 状态,确认修改前脚本没有用户的未提交改动,可以在现有最新过程脚本上进行最小范围修改。
4. 修改最终去重键:当 `[缺件属性] = N'外购'` 时,优先使用“缺件物料编码 + SAP 行号”;非外购件仍优先使用缺件生产订单号,缺件生产订单号为空时仍回退到“缺件物料编码 + SAP 行号”。
5. 创建临时事务预演脚本,在同一数据库会话中先将线上旧过程对订单 8238 的结果写入临时表,然后开启事务、加载新过程定义、再次执行过程并比较前后结果,最后回滚事务。
6. 事务预演结果:修改前 6 条、修改后 7 条;唯一新增记录为 SAP 单据 `8238`、SAP 行号 `33`、物料 `23601-032-A6x6x25`(平键)、缺件数量 2、缺件属性“外购”、缺件生产订单号 `4957`
7. 事务预演未发现减少记录,也未发现相同 SAP 单据和 SAP 行号的重复结果;事务正常回滚。
8. 删除仅用于预演的临时 SQL 文件,确认线上过程修改时间仍为 `2026-07-15 14:25:11.830` 且尚未包含新分组分支,证明预演没有残留线上修改。
9. 对正式脚本执行 `git diff --check`,未发现空白符或补丁格式错误;差异仅为最终分组 `CASE` 中新增一条外购判断。
10. 使用 `sqlcmd -b` 正式执行 `db_backups/update_timely_kit_tracking_result_multi_detail_orders_20260630.sql`,成功发布 `dbo.生产管理_及时齐套跟踪结果_查询`
11. 发布后通过存储过程方式执行订单号 8238结果为 7 条SAP 行号为 `2、3、21、28、33、48、75`SAP 视图中未发货数量大于 0 的记录同样为 7 条、7 个不同 SAP 行。
12. 发布后确认行号 33 的平键和行号 48 的螺钉均正常返回,虽然两条的缺件生产订单号均为 `4957`,但不再互相覆盖;结果中相同 SAP 单据和 SAP 行号的重复数量为 0。
13. 首次使用 SQL `LIKE` 检查线上定义时,由于方括号在 `LIKE` 模式中具有特殊含义而得到错误的 0改用 `CHARINDEX` 精确检查后,确认线上定义已经包含新的外购分组分支,并仍保留自制件按缺件生产订单号归并的分支。
14. 线上过程最新修改时间为 `2026-07-22 09:00:01.960`
15. 保留工作区内原有的其他未提交修改,没有覆盖或撤销与本任务无关的文件。
### 修改文件
- `db_backups/update_timely_kit_tracking_result_multi_detail_orders_20260630.sql`
- `gptlog-process/gpdlog.md`
### 结论
- 已修正外购物料共用“在制采购”编号时被末级去重误合并的问题。
- 外购件现在按“订单号 + 物料编码 + SAP 行号”区分,自制件仍按“订单号 + 缺件生产订单号”归并,原有自制件逻辑未改变。
- 订单 8238 的平键和螺钉现在都会返回,存储过程结果已由 6 条恢复为与 SAP 视图一致的 7 条。
- 修正后的存储过程已经正式发布到 `192.168.2.92 / YL_MESDB`
### 验证结果
- 事务预演:修改前 6 条,修改后 7 条,唯一新增 SAP 行号 33无减少、无重复随后回滚成功。
- 正式发布:成功。
- 线上新外购分组分支:存在。
- 线上原自制件归并分支:保留。
- SAP 视图订单 82387 条7 个不同 SAP 行。
- 存储过程订单 82387 条SAP 行号完整。
- 存储过程结果重复 SAP 键0。
- `git diff --check`:通过。
## 2026-07-22 修正工时校验人员工时在加工工时为零时的显示
### 用户提问
- 用户要求修改 `src/views/ProductionManagement/WorkHoursCheck/index.vue` 的“人员工时”列,解决 `scope.row.加工工时 = 0` 时的计算问题。
### 执行过程
1. 检查“人员工时”列、设备工时列、编辑表单及 SAP 上传逻辑,确认业务口径为:加工工时大于 0 时属于设备工时,人员工时显示 0加工工时为 0 时,人员工时显示“总时长 / 60”。
2. 检查工作区差异,发现该行已有未提交尝试,条件使用多个 `OR` 判断,导致条件恒为真,且真值分支直接返回加工工时,无法正确处理加工工时为 0 的情况。
3. 首次应用补丁时,该行在读取后发生并行变化,补丁因上下文不一致而未应用;重新读取最新文件后,确认代码恢复为 `scope.row.加工工时 != NULL ? 0 : scope.row.总时长 / 60`
4. 将表达式修改为:先用 `Number(scope.row.加工工时 || 0)` 统一数值类型;等于 0 时显示 `Number(scope.row.总时长 || 0) / 60`,否则显示 0保留原两位小数及末尾零清理规则。
5. 使用 Node 复算边界值:加工工时为数值 0、字符串 `"0"``null` 和空字符串时,人员工时分别按总时长折算;加工工时为正数或正数字符串时显示 0。
6. `git diff --check` 通过。随后启动页面 ESLint但用户切换到新任务时该检查被中断本次表达式本身未产生语法错误。
### 修改文件
- `src/views/ProductionManagement/WorkHoursCheck/index.vue`
### 结论
- 加工工时为 0、`"0"`、空值或空字符串时,人员工时现在正确显示“总时长 / 60”。
- 加工工时大于 0 时,人员工时显示 0。
### 验证结果
- 加工工时 0、总时长 3600显示 60。
- 加工工时 `"0"`、总时长 3600显示 60。
- 加工工时 `null`、总时长 1800显示 30。
- 加工工时 1200、总时长 3600显示 0。
- `git diff --check`:通过。
## 2026-07-22 增加设计报工工时校验数据权限
### 用户提问
- 用户要求修改 `ResearchManagement/DesignReportTask/Check/index` 页面数据权限:普通用户只能查看自己创建任务或自己报工的数据;角色包含“领导”或“管理员”的用户可以查看全部数据。
### 执行过程
1. 检索页面路径,确认项目中的实际文件为 `src/views/ProductionManagement/DesignReportTask/Check/index.vue`,页面查询过程为 `dbo.设计报工_工时校验_查询`
2. 检查审核页面查询参数、当前用户获取方式和存储过程定义。修改前页面未传当前用户或角色,线上校验查询过程只有 8 个筛选参数,并直接调用无权限限制的 `dbo.设计报工_记录_查询`
3. 检查本模块已有的 `add_design_report_hours_report_permissions_20260720.sql` 和工时报表页面,确认现有权限口径使用当前用户名、角色编号及 `登录基础数据_角色信息.Roles_Function`,角色功能包含“领导”或“管理员”时可查看全部。
4. 核对线上表结构:任务表具有 `创建人` 字段;报工记录表具有 `操作人ID``操作人` 字段;角色表包含 `Roles_number``Roles_Function`
5. 查询线上特权角色,确认当前包含超级管理员、质量部领导、管理员、采购部领导、设备管理员和技术部领导等角色,均可按“领导/管理员”关键字识别。
6. 修改审核页面 `buildSearchParams()`,增加 `当前操作人``角色编号` 参数;增加 `currentRoleId()`,沿用模块现有方式从 Vuex `token` getter 取得角色编号。
7. 新增 `db_backups/add_design_report_check_permissions_20260722.sql`,重新定义 `dbo.设计报工_工时校验_查询`。过程保留原 8 个筛选参数和返回字段,并增加 `@当前操作人``@角色编号` 两个可选参数。
8. 新过程通过角色表判断 `Roles_Function` 是否包含“领导”或“管理员”。特权角色不限制数据;普通角色必须满足任务创建人等于当前用户,或报工操作人等于当前用户;当前用户名为空且不是特权角色时不返回数据。
9. 创建临时事务预演脚本,在事务内加载新过程并测试四种身份:普通用户“张立柱”返回 53 条,空用户名返回 0 条,技术部领导返回全部 184 条,超级管理员返回全部 184 条。
10. 对普通用户的 53 条结果逐行核对,越权记录为 0反向检查全部任务创建人或报工人为“张立柱”的记录遗漏记录为 0。
11. 回滚预演事务并删除临时脚本;确认线上过程仍为原 8 个参数且不含新权限参数,证明预演没有残留修改。
12. 对正式 SQL 和页面差异执行 `git diff --check`,检查通过。
13. 正式执行 `add_design_report_check_permissions_20260722.sql`,成功发布 `dbo.设计报工_工时校验_查询``192.168.2.92 / YL_MESDB`
14. 发布后重新调用线上过程:普通用户“张立柱”返回 53 条,与独立 SQL 计算的本人相关记录 53 条一致;空用户名返回 0 条;技术部领导和超级管理员均返回当前全部 184 条。
15. 检查线上过程元数据,确认参数已从 8 个增加到 10 个,包含当前操作人和角色编号,且同时包含“领导”和“管理员”角色判断;线上修改时间为 `2026-07-22 10:22:33.010`
16. 执行页面 ESLint。完整规则检查发现页面原有第 58 行属性顺序警告和第 315 行函数括号空格错误,均不在本次差异中;临时关闭这两条既有规则后重新检查,本次修改通过,仅有依赖数据过期提示。
17. 保留工作区内其他未提交修改,没有覆盖或撤销与本任务无关的文件。
### 修改文件
- `src/views/ProductionManagement/DesignReportTask/Check/index.vue`
- `db_backups/add_design_report_check_permissions_20260722.sql`
- `gptlog-process/gpdlog.md`
### 结论
- 设计报工工时校验页面现在由数据库查询过程执行数据权限,不依赖前端返回后再过滤。
- 普通用户只能查看自己创建任务产生的报工记录,或操作人为自己的报工记录。
- 当前用户名为空的普通角色不返回数据,避免登录信息缺失时放开权限。
- `Roles_Function` 包含“领导”或“管理员”的角色可以查看全部数据。
- 权限过程已正式发布到生产数据库。
### 验证结果
- 普通用户“张立柱”53 条;预期 53 条;越权 0遗漏 0。
- 普通角色且当前用户为空0 条。
- 技术部领导184 条,与全部记录一致。
- 超级管理员184 条,与全部记录一致。
- 线上过程参数10 个,权限参数及角色判断均存在。
- `git diff --check`:通过。
- 页面 ESLint排除两项既有规则通过。
## 2026-07-22 为设计任务和设计报工增加附件功能
### 用户提问
- 用户要求在设计任务和设计报工界面增加“上传附件”和“查看附件”功能,上传和查看方法参考 `ProcessManagement/PartDrawing/index`
- 用户特别说明附件不一定是 PDF需要正确处理不同文件类型的查看方式还要求解决上传文件重名覆盖问题可使用时间戳或更可靠的唯一命名方案。
### 执行过程
1. 检索参考页面和目标页面,确认参考文件为 `src/views/ProcessManagement/PartDrawing/index.vue`,目标文件为 `src/views/ProductionManagement/DesignReportTask/Task/index.vue``Report/index.vue`;两个目标文件修改前没有未提交差异。
2. 拆解参考页面上传链路:使用 `MESUploadFile.ashx` 接收 `file + params`,参数包含业务编号、表名、上传人和上传时间;文件访问根路径为 `/webpage/part/`;参考页的 PDF 查看使用 `seepdf`
3. 查询线上 `物料基础数据_零件图纸` 表结构和近期数据,确认通用上传表具有 `id、num、uid、name、suffix、是否启用、worker、uploadTime` 8 个字段,且上传接口按实际文件名保存物理文件。
4. 查询 `工艺管理_图纸维护_查看图纸` 当前定义并使用只读 HTTP 请求核对文件路径。确认近期文件不能通过 `uid.pdf` 访问,而可以通过 URL 编码后的 `name.pdf` 访问;因此数据库 `uid` 不是当前真实文件路径,物理重名必须在上传前处理。
5. 确定附件按设计任务 `DID` 共享:设计任务页面和设计报工页面查看同一任务时使用同一附件集合,允许一个任务上传多份附件。
6. 新增 `db_backups/create_design_report_attachments_20260722.sql`,创建与通用上传接口兼容的 `dbo.MES_设计报工_附件` 表,字段顺序和基础上传表保持一致,并增加按 `num、是否启用、id` 查询的索引。
7. 在同一脚本中新增 `dbo.设计报工_附件_查询`,按 `DID` 查询启用附件,返回附件 ID、原文件名、物理存储文件名、文件类型、上传人和上传时间。
8. 设计物理文件唯一命名规则:`DESIGN_TASK_<DID>_<年月日时分秒毫秒>_<随机串>__<清洗后的原文件名>.<扩展名>`。时间戳和浏览器密码学随机数共同保证唯一,任务 ID 便于定位;双下划线作为内部名前缀和原文件名的分隔符。
9. 文件名清洗会替换 Windows 文件系统不允许的字符、去除尾部点号和空格、限制总长度;上传时创建真正的新 `File`/`Blob` 并将唯一名称作为 multipart 文件名提交,因此数据库记录和物理目录都不会因同名附件互相覆盖。
10. 查询过程通过双下划线分隔符去掉内部唯一前缀,列表仍向用户显示原文件名;同名附件可以并存,并可通过上传人和上传时间区分。
11. 新增共享组件 `src/views/ProductionManagement/DesignReportTask/components/TaskAttachments.vue`,集中实现附件对话框、最多 10 个文件选择、单文件 100MB 限制、逐个上传、结果统计、附件查询、文件类型显示、预览和下载。
12. 非 PDF 查看采用分流策略PDF、PNG/JPG/JPEG/GIF/BMP/WebP 和 TXT/CSV/JSON/Markdown 等浏览器可安全展示格式在新窗口预览Word、Excel、压缩包及其他格式通过 Blob 下载并使用原文件名保存,由本机程序查看。
13. 安全复核时将 SVG、HTML、HTM 和 XML 从直接预览列表移除,改为下载后查看,避免主动内容在附件站点直接执行。
14. 在设计任务页面操作列增加带上传和查看图标的“上传附件”“查看附件”按钮,将操作列宽度从 340 调整为 540并注册共享附件组件。
15. 在设计报工页面操作列增加相同两个按钮,将操作列宽度从 440 调整为 640并注册同一共享附件组件两个页面调用统一的 `openAttachments(row, mode)`
16. 初次运行共享组件 ESLint 时发现文件名清洗正则的控制字符范围触发 `no-control-regex`;考虑浏览器文件名不会包含该范围,移除控制字符范围并保留 Windows 非法字符处理,随后三个 Vue 文件 ESLint 全部通过。
17. 创建临时事务预演脚本,在事务内创建附件表和过程,插入一个启用的 DOCX 示例和一个停用的 PDF 示例。查询正确返回 `报价附件_最终版.docx`,保留唯一物理文件名,停用附件未返回,索引存在。
18. 回滚事务、删除临时预演脚本,并确认线上附件表和过程均不存在,证明预演没有残留对象或数据。
19. 正式执行 `create_design_report_attachments_20260722.sql`,成功发布 `MES_设计报工_附件``设计报工_附件_查询``192.168.2.92 / YL_MESDB`
20. 发布后检查线上对象:附件表共 8 个兼容字段,任务查询索引存在,查询过程有 1 个 `@DID` 参数,修改时间为 `2026-07-22 13:38:17.420`;新表当前 0 条数据。
21. 执行 `npm run build`Webpack 生产构建成功,仅保留项目原有字体、图片和入口包体积警告。
22. 使用相同随机算法在同一批次生成 1000 个标识1000 个均唯一,重复数为 0实际文件名还同时包含 DID 和毫秒时间戳。
23. 启动本地开发服务器。因 1997 端口已有服务,新实例自动使用 `https://127.0.0.1:1998/`HTTP 状态为 200最后一次热更新编译成功。
24. 最终对 SQL、共享组件和两个页面执行 `git diff --check`,检查通过;未覆盖或撤销工作区内其他用户修改。
25. 没有向生产附件目录上传测试文件:现有上传接口没有提供可确认删除物理文件的测试清理能力,为避免留下孤立测试文件,本次使用线上既有上传行为、兼容表结构、数据库事务、前端 ESLint、生产构建和开发服务器编译完成验证。
### 修改文件
- `src/views/ProductionManagement/DesignReportTask/Task/index.vue`
- `src/views/ProductionManagement/DesignReportTask/Report/index.vue`
- `src/views/ProductionManagement/DesignReportTask/components/TaskAttachments.vue`
- `db_backups/create_design_report_attachments_20260722.sql`
- `gptlog-process/gpdlog.md`
### 结论
- 设计任务和设计报工页面均已增加上传附件和查看附件入口,并共享同一套附件数据和组件逻辑。
- 附件不限制为 PDF浏览器适合展示的安全格式直接预览Office、压缩包和主动内容格式下载后查看。
- 物理文件名采用 DID、毫秒时间戳、密码学随机串和原文件名组合同名附件不会覆盖界面仍显示原文件名。
- 附件数据库表和查询过程已经正式发布到生产数据库。
- 本地开发服务器运行于 `https://127.0.0.1:1998/`
### 验证结果
- 共享组件 ESLint通过。
- 设计任务页面 ESLint通过。
- 设计报工页面 ESLint通过。
- 数据库事务预演通过DOCX 原文件名还原正确,停用附件过滤正确,随后回滚成功。
- 数据库正式发布:成功。
- 线上附件表字段8 个,与通用上传接口参考表兼容。
- 线上任务附件索引:存在。
- 线上附件查询过程:存在,参数 1 个。
- 线上附件表当前数据0 条。
- 1000 次随机标识测试1000 个唯一0 个重复。
- `npm run build`:通过,存在非阻断包体积警告。
- 开发服务器:`https://127.0.0.1:1998/`,状态 200热更新编译成功。
- `git diff --check`:通过。
- 生产物理文件上传测试:未执行,避免遗留无法确认清理的孤立测试文件。
- 安全预览类型调整后的最终 `npm run build`:再次通过,生成 `static/js/app.2f4b894c.js`,仅有原有包体积警告。
## 2026-07-22 增加研发类别立项号必填校验
### 用户提问
- 用户要求修改 `ResearchManagement/DesignReportTask/Task/index`:当类别选择“研发”时,研发立项号必须填写。
### 执行过程
1. 定位项目中的实际页面路径为 `src/views/ProductionManagement/DesignReportTask/Task/index.vue`,并保留该文件中刚增加的附件功能修改。
2. 检查任务表单、规则对象、编辑回填和保存入口,确认保存统一通过 `this.$refs.taskForm.validate()`,但原表单只有任务描述必填规则;类别和研发立项号表单项均未绑定校验属性。
3. 为研发立项号表单项增加 `prop="研发立项号"`,并通过 `:required="formData.类别 === '研发'"` 动态显示必填标识。
4. 在规则对象中增加研发立项号自定义校验器,失焦和内容变化时触发。
5. 新增 `validateResearchProjectNo`:类别严格等于“研发”且研发立项号去除首尾空格后为空时,返回“请输入研发立项号”;其他情况通过。
6. 为类别选择增加 `handleCategoryChange`。从“研发”切换到其他类别时,清除研发立项号已有的校验错误,避免非研发类别残留红色错误状态。
7. 使用独立逻辑复算三种边界:研发且空值为不通过;研发且填写 `RD-2026-001` 为通过;订单类别且空值为通过。
8. 执行该页面 ESLint检查通过仅有项目依赖数据过期提示。
9. 检查本地开发服务器 `https://127.0.0.1:1998/`,状态为 200最终热更新 Webpack 编译成功。
10. 执行 `git diff --check`,检查通过;本次未修改数据库对象或业务数据,也未覆盖工作区内其他修改。
### 修改文件
- `src/views/ProductionManagement/DesignReportTask/Task/index.vue`
- `gptlog-process/gpdlog.md`
### 结论
- 新建或编辑设计任务时,类别为“研发”且研发立项号为空或仅包含空格,保存会被阻止并提示“请输入研发立项号”。
- 类别不是“研发”时,研发立项号仍为可选字段。
- 从研发切换到其他类别后,旧的必填错误会自动清除。
### 验证结果
- 研发 + 空立项号:校验失败。
- 研发 + `RD-2026-001`:校验通过。
- 订单 + 空立项号:校验通过。
- 页面 ESLint通过。
- 开发服务器热更新编译:通过。
- `git diff --check`:通过。
- 数据库修改:无。
## 2026-07-22 为设计报工列表增加五分钟静默刷新
### 用户提问
- 用户要求修改 `ResearchManagement/DesignReportTask/Report/index`,在不影响现有操作的情况下,每 5 分钟自动刷新一次列表。
### 执行过程
1. 定位项目中的实际页面为 `src/views/ProductionManagement/DesignReportTask/Report/index.vue`,并保留该文件中已增加的附件上传和查看功能。
2. 检查页面生命周期、查询方法、开始/结束报工、说明弹窗、附件弹窗和通用操作方法,确认原页面没有定时器及生命周期清理逻辑,`searchTable()` 每次都会显示表格加载遮罩和查询失败消息。
3. 检索项目其他自动刷新实现,发现部分页面直接使用 `setInterval(this.searchTable, 300000)`,该方式会在用户操作和弹窗期间刷新,也无法处理请求返回顺序,因此本页面未直接照搬。
4. 定义 `AUTO_REFRESH_INTERVAL = 5 * 60 * 1000`,即 300000 毫秒;在页面数据中增加 `refreshTimer``querySequence`
5. 新增 `startAutoRefresh``stopAutoRefresh`。组件挂载或从 keep-alive 恢复时启动定时器,组件停用或销毁时清除定时器;启动方法具备幂等判断,避免 mounted 和 activated 重复创建定时器。
6. 新增 `shouldSkipAutoRefresh`,当页面不可见、正在手工查询、正在执行报工/说明操作、结束报工弹窗打开、说明弹窗打开或附件对话框打开时,自动刷新直接跳过。
7. 新增 `refreshTableSilently`,只在页面空闲时调用 `searchTable({ silent: true })`,自动刷新不显示表格加载遮罩,也不会弹出查询失败消息。
8. 扩展 `searchTable(options)`,保留原手工查询调用方式;只有明确传入 `silent: true` 才进入静默模式,所以查询按钮、回车查询和业务操作后的主动刷新行为保持不变。
9. 每次查询递增 `querySequence` 并保存本次请求编号。响应返回时只有编号仍为最新的请求才允许更新列表,防止较早发起的自动刷新覆盖之后发起的手工查询结果。
10. 静默请求返回时再次检查操作状态。如果自动请求发出后用户打开弹窗或开始操作,该响应会被忽略,不会在操作过程中替换表格行。
11. 执行页面 ESLint检查通过仅有项目依赖数据过期提示。
12. 执行 `npm run build`,生产构建成功,生成 `static/js/app.074e6ff6.js`;仅保留项目原有包体积警告。
13. 复算定时周期和跳过条件:周期为 300000 毫秒;操作中、结束弹窗、说明弹窗、附件弹窗和页面隐藏场景均返回跳过。
14. 检查本地开发服务器 `https://127.0.0.1:1998/`,状态为 200最终热更新 Webpack 编译成功。
15. 执行 `git diff --check`,检查通过;本次未修改数据库对象或业务数据,也未覆盖工作区内其他修改。
### 修改文件
- `src/views/ProductionManagement/DesignReportTask/Report/index.vue`
- `gptlog-process/gpdlog.md`
### 结论
- 设计报工列表现在每 5 分钟执行一次后台静默刷新。
- 手工查询、开始/结束报工、保存说明、结束/说明弹窗和附件上传/查看均不会被自动刷新打断。
- 页面隐藏、离开或销毁时不会继续无效刷新;返回页面后会自动恢复定时器。
- 并发请求使用序号保护,旧自动请求不会覆盖较新的手工查询结果。
### 验证结果
- 自动刷新周期300000 毫秒。
- 空闲状态:允许静默刷新。
- 操作中:跳过自动刷新。
- 结束报工弹窗打开:跳过自动刷新。

View File

@@ -218,7 +218,9 @@ export default {
['操作人', this.searchForm.operator || ''],
['开始日期', this.searchForm.startDate || ''],
['结束日期', this.searchForm.endDate || ''],
['校验状态', this.searchForm.checkStatus || '全部']
['校验状态', this.searchForm.checkStatus || '全部'],
['当前操作人', this.currentUserName()],
['角色编号', this.currentRoleId()]
]
},
async searchTable() {
@@ -340,6 +342,9 @@ export default {
currentUserName() {
return (this.$store && this.$store.getters && this.$store.getters.name) || ''
},
currentRoleId() {
return (this.$store && this.$store.getters && this.$store.getters.token) || ''
},
formatMaterialLabel(item) {
return item.物料名称 ? `${item.物料编码} | ${item.物料名称}` : item.物料编码
},

View File

@@ -148,7 +148,7 @@
<el-table-column align="left" label="备注" prop="备注" width="240" show-overflow-tooltip />
<el-table-column align="center" label="最后开始" prop="最后开始时间" width="180" />
<el-table-column align="center" label="最后结束" prop="最后结束时间" width="180" />
<el-table-column align="center" fixed="right" label="操作" width="440">
<el-table-column align="center" fixed="right" label="操作" width="640">
<template slot-scope="scope">
<div class="design-action-buttons">
<el-button type="text" size="mini" :disabled="actionLoading || !canStart(scope.row)" @click="startTask(scope.row)">开始</el-button>
@@ -156,6 +156,8 @@
<el-button v-if="canUseLeaderInstruction" type="text" size="mini" @click="openNoteDialog(scope.row, '领导批示')">领导批示</el-button>
<el-button type="text" size="mini" @click="openNoteDialog(scope.row, '异常说明')">异常说明</el-button>
<el-button type="text" size="mini" @click="openNoteDialog(scope.row, '奇思妙想')">奇思妙想</el-button>
<el-button type="text" size="mini" icon="el-icon-upload2" @click="openAttachments(scope.row, 'upload')">上传附件</el-button>
<el-button type="text" size="mini" icon="el-icon-view" @click="openAttachments(scope.row, 'view')">查看附件</el-button>
</div>
</template>
</el-table-column>
@@ -229,12 +231,18 @@
<el-button type="primary" :loading="actionLoading" @click="confirmNote">保存</el-button>
</div>
</el-dialog>
<task-attachments ref="taskAttachments" />
</div>
</template>
<script>
import TaskAttachments from '../components/TaskAttachments'
const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000
export default {
name: 'DesignReportWork',
components: { TaskAttachments },
data() {
return {
loading: false,
@@ -242,6 +250,8 @@ export default {
endDialogVisible: false,
noteDialogVisible: false,
canUseLeaderInstruction: false,
refreshTimer: null,
querySequence: 0,
tableData: [],
categoryOptions: ['实验', '研发', '方案评审', '售前', '售中', '订单', '售后信息确认', '售后问题指导', '售后物料准备', '返修品处理', '其他'],
projectOptions: [],
@@ -278,7 +288,39 @@ export default {
this.loadDefaultMaterialOptions()
this.searchTable()
},
mounted() {
this.startAutoRefresh()
},
activated() {
this.startAutoRefresh()
},
deactivated() {
this.stopAutoRefresh()
},
beforeDestroy() {
this.stopAutoRefresh()
},
methods: {
startAutoRefresh() {
if (this.refreshTimer) return
this.refreshTimer = window.setInterval(() => {
this.refreshTableSilently()
}, AUTO_REFRESH_INTERVAL)
},
stopAutoRefresh() {
if (!this.refreshTimer) return
window.clearInterval(this.refreshTimer)
this.refreshTimer = null
},
shouldSkipAutoRefresh() {
const attachmentDialogOpen = this.$refs.taskAttachments && this.$refs.taskAttachments.visible
return document.hidden || this.loading || this.actionLoading || this.endDialogVisible ||
this.noteDialogVisible || attachmentDialogOpen
},
refreshTableSilently() {
if (this.shouldSkipAutoRefresh()) return
this.searchTable({ silent: true })
},
loadDefaultProjectOptions() {
this.queryProjects('')
},
@@ -309,17 +351,22 @@ export default {
['当前操作人', this.currentUserName()]
]
},
async searchTable() {
this.loading = true
async searchTable(options = {}) {
const silent = options && options.silent === true
if (silent && this.shouldSkipAutoRefresh()) return
const requestId = ++this.querySequence
if (!silent) this.loading = true
const data = this.CreateData('11', '设计报工_任务_查询', this.buildSearchParams())
try {
const response = await this.ExecDatabase(data)
if (requestId !== this.querySequence) return
if (silent && this.shouldSkipAutoRefresh()) return
this.tableData = response.data || []
} catch (error) {
console.error('设计报工任务查询失败:', error)
this.$message.error('查询失败')
if (!silent && requestId === this.querySequence) this.$message.error('查询失败')
} finally {
this.loading = false
if (!silent && requestId === this.querySequence) this.loading = false
}
},
async queryProjects(query) {
@@ -411,6 +458,9 @@ export default {
}
this.noteDialogVisible = true
},
openAttachments(row, mode) {
this.$refs.taskAttachments.open(row, mode)
},
async confirmNote() {
const content = String(this.noteForm.说明内容 || '').trim()
if (!content) {

View File

@@ -185,12 +185,14 @@
<el-table-column align="center" label="创建日期" prop="创建日期" width="180" />
<el-table-column align="left" label="创建人" prop="创建人" width="140" show-overflow-tooltip />
<el-table-column align="left" label="备注" prop="备注" width="240" show-overflow-tooltip />
<el-table-column align="center" fixed="right" label="操作" width="340">
<el-table-column align="center" fixed="right" label="操作" width="540">
<template slot-scope="scope">
<div class="design-action-buttons">
<el-button type="text" size="mini" @click="openEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" :disabled="actionLoading || !canComplete(scope.row)" @click="completeTask(scope.row)">完成</el-button>
<el-button v-if="canUseLeaderInstruction" type="text" size="mini" @click="openNoteDialog(scope.row)">领导批示</el-button>
<el-button type="text" size="mini" icon="el-icon-upload2" @click="openAttachments(scope.row, 'upload')">上传附件</el-button>
<el-button type="text" size="mini" icon="el-icon-view" @click="openAttachments(scope.row, 'view')">查看附件</el-button>
<el-button type="text" size="mini" class="danger-action" @click="deleteTask(scope.row)">删除</el-button>
</div>
</template>
@@ -219,7 +221,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="类别">
<el-select v-model="formData.类别" clearable style="width: 100%">
<el-select v-model="formData.类别" clearable style="width: 100%" @change="handleCategoryChange">
<el-option v-for="item in categoryOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
@@ -365,7 +367,7 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="研发立项号">
<el-form-item label="研发立项号" prop="研发立项号" :required="formData.类别 === '研发'">
<el-input v-model="formData.研发立项号" />
</el-form-item>
</el-col>
@@ -418,12 +420,16 @@
<el-button type="primary" :loading="noteLoading" @click="confirmNote">保存</el-button>
</div>
</el-dialog>
<task-attachments ref="taskAttachments" />
</div>
</template>
<script>
import TaskAttachments from '../components/TaskAttachments'
export default {
name: 'DesignReportTask',
components: { TaskAttachments },
data() {
return {
loading: false,
@@ -465,7 +471,8 @@ export default {
},
formData: this.getEmptyForm(),
rules: {
任务描述: [{ required: true, message: '请输入任务描述', trigger: 'blur' }]
任务描述: [{ required: true, message: '请输入任务描述', trigger: 'blur' }],
研发立项号: [{ validator: this.validateResearchProjectNo, trigger: ['blur', 'change'] }]
}
}
},
@@ -483,6 +490,18 @@ export default {
this.searchTable()
},
methods: {
validateResearchProjectNo(rule, value, callback) {
if (this.formData.类别 === '研发' && !String(value || '').trim()) {
callback(new Error('请输入研发立项号'))
return
}
callback()
},
handleCategoryChange(category) {
if (category !== '研发' && this.$refs.taskForm) {
this.$refs.taskForm.clearValidate('研发立项号')
}
},
getEmptyForm() {
return {
DID: '',
@@ -578,6 +597,9 @@ export default {
}
this.noteDialogVisible = true
},
openAttachments(row, mode) {
this.$refs.taskAttachments.open(row, mode)
},
async confirmNote() {
const content = String(this.noteForm.说明内容 || '').trim()
if (!content) {

View File

@@ -0,0 +1,375 @@
<template>
<el-dialog
:visible.sync="visible"
:title="dialogTitle"
append-to-body
center
width="860px"
:close-on-click-modal="false"
@closed="resetDialog"
>
<div class="attachment-context">
<span class="attachment-did">DID {{ task.DID }}</span>
<span class="attachment-description">{{ task.任务描述 || '' }}</span>
</div>
<div v-if="showUploader" class="attachment-upload-section">
<el-upload
ref="attachmentUpload"
action="#"
drag
multiple
:auto-upload="false"
:file-list="pendingFiles"
:limit="10"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:on-exceed="handleFileExceed"
>
<i class="el-icon-upload" />
<div class="el-upload__text">将文件拖到此处<em>点击选择</em></div>
<div slot="tip" class="el-upload__tip">单个文件不超过 100MB</div>
</el-upload>
<el-button
type="primary"
icon="el-icon-upload2"
:loading="uploading"
:disabled="pendingFiles.length === 0"
@click="uploadAttachments"
style="width: auto;"
>
上传所选附件
</el-button>
</div>
<div class="attachment-list-title">已上传附件</div>
<el-table v-loading="loading" :data="attachments" border size="mini" height="320px">
<el-table-column align="left" label="文件名" min-width="310" show-overflow-tooltip>
<template slot-scope="scope">
<i :class="getFileIcon(scope.row.文件类型)" class="attachment-file-icon" />
<span>{{ scope.row.原文件名 }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="类型" width="90">
<template slot-scope="scope">
<el-tag size="mini" type="info">{{ formatFileType(scope.row.文件类型) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="上传人" prop="上传人" width="110" />
<el-table-column align="center" label="上传时间" prop="上传时间" width="160" />
<el-table-column align="center" label="操作" width="90">
<template slot-scope="scope">
<el-tooltip :content="getActionTooltip(scope.row)" placement="top">
<el-button
type="text"
:icon="isPreviewable(scope.row.文件类型) ? 'el-icon-view' : 'el-icon-download'"
@click="openAttachment(scope.row)"
>
{{ isPreviewable(scope.row.文件类型) ? '预览' : '下载' }}
</el-button>
</el-tooltip>
</template>
</el-table-column>
<template slot="empty">
<span>暂无附件</span>
</template>
</el-table>
<div slot="footer">
<el-button @click="visible = false">关闭</el-button>
</div>
</el-dialog>
</template>
<script>
import { MESDownFile, MESUploadFile } from '@/utils/request'
const MAX_FILE_SIZE = 100 * 1024 * 1024
const PREVIEWABLE_TYPES = new Set([
'pdf', 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp',
'txt', 'csv', 'json', 'md'
])
export default {
name: 'DesignTaskAttachments',
data() {
return {
visible: false,
showUploader: false,
loading: false,
uploading: false,
task: {},
pendingFiles: [],
attachments: [],
uploadUrl: MESUploadFile,
downloadBaseUrl: MESDownFile
}
},
computed: {
dialogTitle() {
return this.showUploader ? '上传设计任务附件' : '查看设计任务附件'
}
},
methods: {
open(row, mode = 'view') {
if (!row || !row.DID) {
this.$message.error('任务ID不能为空')
return
}
this.task = {
DID: row.DID,
任务描述: row.任务描述 || ''
}
this.showUploader = mode === 'upload'
this.visible = true
this.pendingFiles = []
this.$nextTick(() => {
if (this.$refs.attachmentUpload) this.$refs.attachmentUpload.clearFiles()
})
this.loadAttachments()
},
resetDialog() {
this.task = {}
this.pendingFiles = []
this.attachments = []
this.uploading = false
},
async loadAttachments() {
this.loading = true
const data = this.CreateData('11', '设计报工_附件_查询', [['DID', this.task.DID]])
try {
const response = await this.ExecDatabase(data)
this.attachments = response.data || []
} catch (error) {
console.error('设计任务附件查询失败:', error)
this.attachments = []
this.$message.error('附件查询失败')
} finally {
this.loading = false
}
},
handleFileChange(file, fileList) {
if (file.size > MAX_FILE_SIZE) {
this.$message.error(`文件“${file.name}”超过 100MB`)
this.pendingFiles = fileList.filter(item => item.uid !== file.uid)
return
}
this.pendingFiles = fileList
},
handleFileRemove(file, fileList) {
this.pendingFiles = fileList
},
handleFileExceed() {
this.$message.warning('一次最多选择 10 个附件')
},
async uploadAttachments() {
const files = this.pendingFiles.map(item => item.raw).filter(Boolean)
if (files.length === 0 || this.uploading) return
this.uploading = true
let successCount = 0
const failedNames = []
for (const file of files) {
try {
await this.uploadSingleFile(file)
successCount += 1
} catch (error) {
console.error(`附件上传失败: ${file.name}`, error)
failedNames.push(file.name)
}
}
if (successCount > 0) {
this.$message.success(`成功上传 ${successCount} 个附件`)
this.pendingFiles = []
if (this.$refs.attachmentUpload) this.$refs.attachmentUpload.clearFiles()
await this.loadAttachments()
}
if (failedNames.length > 0) {
this.$message.error(`${failedNames.length} 个附件上传失败:${failedNames.join('、')}`)
}
this.uploading = false
},
async uploadSingleFile(rawFile) {
const storedFile = this.createStoredFile(rawFile)
const formData = new FormData()
formData.append('file', storedFile, storedFile.name)
formData.append('params', JSON.stringify({
num: String(this.task.DID),
tableName: 'MES_设计报工_附件',
worker: this.currentUserName(),
uploadTime: this.formatDateTime(new Date()),
department: this.currentUserName()
}))
const response = await fetch(this.uploadUrl, {
method: 'POST',
body: formData
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const payload = await response.json()
const result = Array.isArray(payload) ? payload[0] : payload
if (!result || String(result.result) !== '1') {
throw new Error((result && result.message) || '上传接口返回失败')
}
},
createStoredFile(rawFile) {
const originalName = String(rawFile.name || 'attachment')
const dotIndex = originalName.lastIndexOf('.')
const hasExtension = dotIndex > 0 && dotIndex < originalName.length - 1
const extension = hasExtension
? `.${originalName.slice(dotIndex + 1).replace(/[^A-Za-z0-9]/g, '').slice(0, 20)}`
: ''
const originalBase = hasExtension ? originalName.slice(0, dotIndex) : originalName
const safeBase = originalBase
.replace(/[<>:"/\\|?*]/g, '_')
.replace(/[. ]+$/g, '')
.trim() || 'attachment'
const prefix = `DESIGN_TASK_${this.task.DID}_${this.compactTimestamp()}_${this.randomToken()}__`
const maxBaseLength = Math.max(1, 180 - prefix.length - extension.length)
const storedName = `${prefix}${safeBase.slice(0, maxBaseLength)}${extension}`
try {
return new File([rawFile], storedName, {
type: rawFile.type || 'application/octet-stream',
lastModified: rawFile.lastModified || Date.now()
})
} catch (error) {
const blob = rawFile.slice(0, rawFile.size, rawFile.type || 'application/octet-stream')
blob.name = storedName
blob.lastModified = rawFile.lastModified || Date.now()
return blob
}
},
compactTimestamp() {
const now = new Date()
const pad = (value, length = 2) => String(value).padStart(length, '0')
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +
`${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}${pad(now.getMilliseconds(), 3)}`
},
randomToken() {
if (window.crypto && window.crypto.getRandomValues) {
const values = new Uint32Array(2)
window.crypto.getRandomValues(values)
return Array.from(values).map(value => value.toString(36)).join('').slice(0, 12)
}
return Math.random().toString(36).slice(2, 14)
},
formatDateTime(date) {
const pad = value => String(value).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
},
currentUserName() {
return (this.$store && this.$store.getters && this.$store.getters.name) || ''
},
getAttachmentUrl(row) {
return `${this.downloadBaseUrl}${encodeURIComponent(row.存储文件名 || '')}`
},
isPreviewable(type) {
return PREVIEWABLE_TYPES.has(String(type || '').toLowerCase())
},
openAttachment(row) {
if (this.isPreviewable(row.文件类型)) {
window.open(this.getAttachmentUrl(row), '_blank', 'noopener,noreferrer')
return
}
this.downloadAttachment(row)
},
async downloadAttachment(row) {
const url = this.getAttachmentUrl(row)
try {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const blob = await response.blob()
const objectUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = objectUrl
link.download = row.原文件名 || row.存储文件名
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000)
} catch (error) {
console.error('附件下载失败:', error)
window.open(url, '_blank', 'noopener,noreferrer')
}
},
formatFileType(type) {
return String(type || '文件').toUpperCase()
},
getFileIcon(type) {
const extension = String(type || '').toLowerCase()
if (extension === 'pdf') return 'el-icon-document'
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg'].includes(extension)) return 'el-icon-picture-outline'
return 'el-icon-document-copy'
},
getActionTooltip(row) {
return this.isPreviewable(row.文件类型) ? '在新窗口中预览' : '下载后查看'
}
}
}
</script>
<style scoped>
.attachment-context {
display: flex;
align-items: baseline;
gap: 12px;
margin-bottom: 14px;
min-width: 0;
}
.attachment-did {
flex: none;
font-weight: 600;
color: #303133;
}
.attachment-description {
overflow: hidden;
color: #606266;
text-overflow: ellipsis;
white-space: nowrap;
}
.attachment-upload-section {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 18px;
margin-bottom: 18px;
}
.attachment-upload-section ::v-deep .el-upload-dragger {
width: 430px;
height: 130px;
}
.attachment-upload-section ::v-deep .el-icon-upload {
margin: 24px 0 8px;
font-size: 46px;
}
.attachment-list-title {
margin-bottom: 8px;
font-weight: 600;
color: #303133;
}
.attachment-file-icon {
margin-right: 7px;
color: #409eff;
}
@media (max-width: 760px) {
.attachment-upload-section {
align-items: stretch;
flex-direction: column;
}
.attachment-upload-section ::v-deep .el-upload,
.attachment-upload-section ::v-deep .el-upload-dragger {
width: 100%;
}
}
</style>

View File

@@ -975,7 +975,7 @@ export default {
"DocDate": this.formatDate(this.currentTime),
"DocDueDate": this.formatDate(this.currentTime),
"TaxDate": this.formatDate(this.currentTime),
"RequriedDate":this.formatDate(this.currentTime),
"RequriedDate":this.formatDate(item.计划完成时间),
"Comments": " ",
"DocumentLines": [
{

View File

@@ -1037,7 +1037,6 @@ export default {
var arrid= item.TaskAID.split(',')
var lastprocessid = arrid[arrid.length - 1]
if(item.派工状态 < 1){
return
postB1(
"/PurchaseRequests",
{

View File

@@ -110,7 +110,7 @@
</el-table-column>
<el-table-column align="center" label="人员工时" width="100">
<template slot-scope="scope">
{{ (scope.row.加工工时 != NULL ? 0 : scope.row.总时长 / 60).toFixed(2).replace(/\.?0+$/, '') }}
{{ (Number(scope.row.加工工时 || 0) === 0 ? Number(scope.row.总时长 || 0) / 60 : 0).toFixed(2).replace(/\.?0+$/, '') }}
</template>
</el-table-column>
<el-table-column align="center" label="绑定设备数" width="100">
@@ -2011,4 +2011,4 @@ if (normalRecords.length > 0) {
::v-deep .el-table__body tr.row-time-old:hover > td {
background-color: #ff8c00 !important;
}*/
</style>
</style>

View File

@@ -906,6 +906,7 @@ export default {
{
base_type: "202",
external_num: datarow.计划号,
external_line_id:datarow.行号,
item_code: datarow.零件编码,
item_name: datarow.零件名称,
whs_code: "",
@@ -926,35 +927,36 @@ export default {
}
]
};
console.log(result)
this.MaterialRequisitionpop = false
const result2 = await posttm('/stocktask/task/receiptordertask',result)
this.$message.success('请求成功')
console.log(result2)
},
arrayToJSON(arr) {
const result = {};
for (const [key, value] of arr) {
if (Array.isArray(value)) {
// 如果是数组,递归处理
if (value.length > 0 && Array.isArray(value[0])) {
// 判断是否为键值对数组
if (value[0].length === 2 && typeof value[0][0] === 'string') {
// 转换为对象数组
result[key] = value.map(item => this.arrayToJSON(item));
const result = {};
for (const [key, value] of arr) {
if (Array.isArray(value)) {
// 如果是数组,递归处理
if (value.length > 0 && Array.isArray(value[0])) {
// 判断是否为键值对数组
if (value[0].length === 2 && typeof value[0][0] === 'string') {
// 转换为对象数组
result[key] = value.map(item => this.arrayToJSON(item));
} else {
// 普通数组
result[key] = value;
}
} else {
result[key] = value;
}
} else {
// 普通数组
result[key] = value;
}
} else {
result[key] = value;
}
} else {
result[key] = value;
}
}
return result;
},
return result;
},
transformData(rawArray) {
console.log("原始数据:", rawArray);
// 初始化结果对象