Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
internal_regulation_manager_new
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
internal_regulation_manager_new
Commits
e7a7bf9d
Commit
e7a7bf9d
authored
Sep 30, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 修复制度发文自引用与历史版本筛选
parent
d0b00c39
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
299 additions
and
7 deletions
+299
-7
regulation-revision.test.mjs
scripts/regulation-revision.test.mjs
+242
-0
RegulationGraph.jsx
src/pages/internal_regulation_manager/RegulationGraph.jsx
+5
-2
internal-regulation.business-flow.md
...l_regulation_manager/internal-regulation.business-flow.md
+4
-0
internalRegulationService.js
src/services/internalRegulationService.js
+30
-5
record-api.md
src/services/record-api.md
+2
-0
regulationHistory.js
src/services/regulationHistory.js
+16
-0
No files found.
scripts/regulation-revision.test.mjs
0 → 100644
View file @
e7a7bf9d
import
assert
from
'node:assert/strict'
;
import
fs
from
'node:fs/promises'
;
import
test
from
'node:test'
;
import
vm
from
'node:vm'
;
import
dayjs
from
'dayjs'
;
import
ts
from
'typescript'
;
import
*
as
historyHelpers
from
'../src/services/regulationHistory.js'
;
import
*
as
graphHelpers
from
'../src/services/regulationGraphData.js'
;
const
{
isRegulationHistory
}
=
historyHelpers
;
const
source
=
await
fs
.
readFile
(
new
URL
(
'../src/services/internalRegulationService.js'
,
import
.
meta
.
url
),
'utf8'
);
const
compiled
=
ts
.
transpileModule
(
source
,
{
compilerOptions
:
{
module
:
ts
.
ModuleKind
.
CommonJS
,
target
:
ts
.
ScriptTarget
.
ES2022
,
esModuleInterop
:
true
},
}).
outputText
;
const
serviceFactory
=
new
vm
.
Script
(
`(function(require, module, exports) {
${
compiled
}
\n})`
).
runInNewContext
();
const
clone
=
(
value
)
=>
value
===
undefined
?
undefined
:
JSON
.
parse
(
JSON
.
stringify
(
value
));
const
valueAt
=
(
record
,
path
)
=>
path
.
split
(
'.'
).
reduce
((
value
,
key
)
=>
value
?.[
key
],
record
);
function
createService
(
records
,
{
ignoreSelfExclusion
=
false
}
=
{})
{
const
database
=
clone
(
records
);
const
calls
=
[];
let
sequence
=
0
;
const
dataService
=
{
async
loadRecord
(
identity
)
{
return
clone
(
database
.
find
((
record
)
=>
record
.
_id
===
identity
||
record
.
name
===
identity
));
},
async
createRecord
(
payload
)
{
calls
.
push
({
action
:
'create'
,
payload
:
clone
(
payload
)
});
sequence
+=
1
;
const
record
=
{
...
clone
(
payload
),
_id
:
`history-id-
${
sequence
}
`
,
name
:
`history-
${
sequence
}
`
};
database
.
push
(
record
);
return
clone
(
record
);
},
async
updateRecord
(
identity
,
payload
)
{
calls
.
push
({
action
:
'update'
,
identity
,
payload
:
clone
(
payload
)
});
const
record
=
database
.
find
((
item
)
=>
item
.
_id
===
identity
||
item
.
name
===
identity
);
assert
.
ok
(
record
,
`Record
${
identity
}
must exist`
);
const
data
=
clone
(
payload
);
// Match the backend's PUT behavior: omitted metadata survives, null clears it.
Object
.
assign
(
record
,
{
...
data
,
metadata
:
{
...
record
.
metadata
,
...
data
.
metadata
}
});
return
clone
(
record
);
},
async
queryRecords
(
query
)
{
calls
.
push
({
action
:
'query'
,
query
:
clone
(
query
)
});
const
records
=
database
.
filter
((
record
)
=>
record
.
subject
===
query
.
subject
&&
Object
.
entries
(
query
.
filter
||
{}).
every
(([
field
,
expected
])
=>
{
const
actual
=
valueAt
(
record
,
field
);
if
(
expected
&&
typeof
expected
===
'object'
)
{
if
(
'$in'
in
expected
)
return
expected
.
$in
.
includes
(
actual
);
if
(
'$ne'
in
expected
)
return
ignoreSelfExclusion
||
actual
!==
expected
.
$ne
;
if
(
'$regex'
in
expected
)
return
new
RegExp
(
expected
.
$regex
).
test
(
actual
||
''
);
}
return
actual
===
expected
;
}));
if
(
query
.
sort
?.
startsWith
(
'-'
))
{
const
field
=
query
.
sort
.
slice
(
1
);
records
.
sort
((
left
,
right
)
=>
String
(
valueAt
(
right
,
field
)
||
''
).
localeCompare
(
String
(
valueAt
(
left
,
field
)
||
''
)));
}
const
pageSize
=
query
.
page_size
||
1000
;
const
start
=
(
query
.
page
||
0
)
*
pageSize
;
return
{
records
:
clone
(
records
.
slice
(
start
,
start
+
pageSize
)),
total_records
:
records
.
length
};
},
};
const
testModule
=
{
exports
:
{}
};
const
dependencies
=
{
dayjs
,
'./DataService'
:
dataService
,
'./regulationGraphData'
:
graphHelpers
,
'./regulationHistory'
:
historyHelpers
,
};
serviceFactory
((
name
)
=>
{
assert
.
ok
(
name
in
dependencies
,
`Unexpected dependency
${
name
}
`
);
return
dependencies
[
name
];
},
testModule
,
testModule
.
exports
);
return
{
service
:
testModule
.
exports
,
database
,
calls
};
}
function
currentRegulation
(
metadata
=
{})
{
return
{
_id
:
'current-id'
,
name
:
'current'
,
title
:
'现行制度'
,
subject
:
'internal_regulation'
,
content
:
'原正文'
,
metadata
:
{
regulation_name
:
'现行制度'
,
effective_status
:
'现行有效'
,
public_date
:
'2025-01-01T00:00:00.000Z'
,
level
:
'1级'
,
tags
:
[
'保留标签'
],
related_regulations
:
[
'related-id'
],
category
:
[
'category-id'
],
...
metadata
,
},
};
}
function
revisionPlan
(
name
=
'revision'
,
metadata
=
{})
{
return
{
_id
:
`
${
name
}
-id`
,
name
,
subject
:
'internal_regulation_revision'
,
content
:
'修订正文'
,
metadata
:
{
regulation_name
:
'修订制度'
,
type
:
'修订'
,
status
:
'已提交'
,
origin_regulation
:
'current-id'
,
level
:
'2级'
,
departments
:
[
'法律合规部'
],
approve
:
[
'董事会'
],
comment
:
'修订备注'
,
regulation_name_oa
:
'OA制度名称'
,
regulation_name_oa_submitted
:
'提交名称'
,
match_status
:
'完全匹配'
,
remark
:
'计划批注'
,
created_at
:
'2026-09-01T00:00:00.000Z'
,
...
metadata
,
},
};
}
const
publication
=
{
public_date
:
'2026-09-30'
,
content
:
'发文正文'
,
attachments
:
[
'document-id'
],
document
:
{
title
:
'发文结构'
}
};
for
(
const
type
of
[
'新增'
,
'修订'
,
'废止'
])
{
test
(
`
${
type
}
publication copies business fields without revision workflow fields`
,
async
()
=>
{
const
current
=
currentRegulation
();
const
plan
=
revisionPlan
(
'revision'
,
{
type
,
...(
type
===
'新增'
?
{
origin_regulation
:
undefined
}
:
{})
});
const
{
service
,
database
}
=
createService
([
current
,
plan
]);
const
published
=
await
service
.
publishRevision
(
plan
,
publication
);
for
(
const
field
of
[
'type'
,
'status'
,
'regulation_name_oa'
,
'regulation_name_oa_submitted'
,
'match_status'
,
'remark'
,
'created_at'
,
'origin_regulation'
])
{
assert
.
equal
(
Object
.
hasOwn
(
published
.
metadata
,
field
),
false
,
field
);
}
assert
.
equal
(
published
.
metadata
.
regulation_name
,
plan
.
metadata
.
regulation_name
);
assert
.
equal
(
published
.
metadata
.
level
,
'2级'
);
assert
.
deepEqual
(
published
.
metadata
.
approve
,
[
'董事会'
]);
assert
.
equal
(
published
.
metadata
.
effective_status
,
type
===
'废止'
?
'已失效'
:
'现行有效'
);
const
finalPlan
=
database
.
find
((
record
)
=>
record
.
name
===
plan
.
name
);
assert
.
equal
(
finalPlan
.
metadata
.
status
,
'已发文'
);
assert
.
equal
(
finalPlan
.
metadata
.
origin_regulation
,
plan
.
metadata
.
origin_regulation
);
if
(
type
===
'修订'
)
{
const
history
=
database
.
find
((
record
)
=>
record
.
name
.
startsWith
(
'history-'
));
assert
.
equal
(
history
.
metadata
.
origin_regulation
,
current
.
_id
);
assert
.
equal
(
history
.
metadata
.
effective_status
,
'已失效'
);
assert
.
equal
(
history
.
content
,
current
.
content
);
assert
.
deepEqual
(
published
.
metadata
.
tags
,
current
.
metadata
.
tags
);
assert
.
deepEqual
(
published
.
metadata
.
related_regulations
,
current
.
metadata
.
related_regulations
);
assert
.
deepEqual
(
published
.
metadata
.
category
,
current
.
metadata
.
category
);
}
});
}
test
(
'consecutive revisions keep one current record and link both snapshots to it'
,
async
()
=>
{
const
current
=
currentRegulation
();
const
firstPlan
=
revisionPlan
();
const
secondPlan
=
revisionPlan
(
'second-plan'
,
{
regulation_name
:
'第二次修订'
});
const
{
service
,
database
}
=
createService
([
current
,
firstPlan
,
secondPlan
]);
await
service
.
publishRevision
(
firstPlan
,
publication
);
await
service
.
publishRevision
(
secondPlan
,
{
...
publication
,
public_date
:
'2026-10-01'
,
content
:
'第二版正文'
});
const
published
=
database
.
find
((
record
)
=>
record
.
_id
===
current
.
_id
);
const
history
=
await
service
.
loadRegulationHistory
(
published
);
assert
.
equal
(
published
.
metadata
.
origin_regulation
,
undefined
);
assert
.
equal
(
published
.
content
,
'第二版正文'
);
assert
.
equal
(
history
.
length
,
2
);
assert
.
deepEqual
(
Array
.
from
(
history
,
(
record
)
=>
record
.
content
),
[
'发文正文'
,
'原正文'
]);
assert
.
ok
(
history
.
every
((
record
)
=>
record
.
metadata
.
origin_regulation
===
current
.
_id
&&
record
.
_id
!==
current
.
_id
));
});
for
(
const
type
of
[
'修订'
,
'废止'
])
{
test
(
`
${
type
}
clears existing self references by ID, name and populated object`
,
async
()
=>
{
for
(
const
reference
of
[
'current-id'
,
'current'
,
{
_id
:
'current-id'
,
name
:
'current'
,
title
:
'现行制度'
}])
{
const
current
=
currentRegulation
({
origin_regulation
:
reference
});
const
plan
=
revisionPlan
(
'revision'
,
{
type
});
const
{
service
,
database
,
calls
}
=
createService
([
current
,
plan
]);
await
service
.
publishRevision
(
plan
,
publication
);
const
update
=
calls
.
find
((
call
)
=>
call
.
action
===
'update'
&&
call
.
identity
===
current
.
name
);
assert
.
equal
(
update
.
payload
.
metadata
.
origin_regulation
,
null
);
assert
.
equal
(
database
.
find
((
record
)
=>
record
.
_id
===
current
.
_id
).
metadata
.
origin_regulation
,
null
);
if
(
type
===
'修订'
)
{
assert
.
equal
(
database
.
find
((
record
)
=>
record
.
name
.
startsWith
(
'history-'
)).
metadata
.
origin_regulation
,
current
.
_id
);
}
}
});
}
test
(
'publication preserves a non-self origin relationship and copies only its ID'
,
async
()
=>
{
const
current
=
currentRegulation
({
origin_regulation
:
{
_id
:
'other-id'
,
name
:
'other'
,
title
:
'其他制度'
}
});
const
plan
=
revisionPlan
();
const
{
service
}
=
createService
([
current
,
plan
]);
const
published
=
await
service
.
publishRevision
(
plan
,
publication
);
assert
.
equal
(
published
.
metadata
.
origin_regulation
,
'other-id'
);
});
test
(
'history matching excludes self by both ID and name and accepts populated references'
,
()
=>
{
const
current
=
currentRegulation
({
origin_regulation
:
'current-id'
});
const
records
=
[
current
,
{
name
:
'current'
,
metadata
:
{
origin_regulation
:
'current-id'
}
},
{
_id
:
'old-id'
,
name
:
'old'
,
metadata
:
{
origin_regulation
:
{
_id
:
'current-id'
,
name
:
'current'
}
}
},
{
_id
:
'old-name'
,
metadata
:
{
origin_regulation
:
'current'
}
},
{
_id
:
'unrelated'
,
metadata
:
{
origin_regulation
:
'another-current'
}
},
{
_id
:
'missing'
,
metadata
:
{}
},
];
assert
.
deepEqual
(
records
.
filter
((
record
)
=>
isRegulationHistory
(
record
,
current
)).
map
((
record
)
=>
record
.
_id
),
[
'old-id'
,
'old-name'
]);
assert
.
equal
(
isRegulationHistory
(
current
,
current
.
name
),
false
);
assert
.
equal
(
isRegulationHistory
(
records
[
2
],
current
.
name
),
true
);
assert
.
equal
(
isRegulationHistory
(
records
[
2
],
null
),
false
);
});
test
(
'detail history excludes legacy self references in the query and returned data'
,
async
()
=>
{
const
current
=
currentRegulation
({
origin_regulation
:
'current-id'
});
const
historical
=
{
...
current
,
_id
:
'old-id'
,
name
:
'old'
,
metadata
:
{
...
current
.
metadata
,
effective_status
:
'已失效'
,
origin_regulation
:
current
.
_id
}
};
for
(
const
ignoreSelfExclusion
of
[
false
,
true
])
{
const
{
service
,
calls
}
=
createService
([
current
,
historical
],
{
ignoreSelfExclusion
});
const
history
=
await
service
.
loadRegulationHistory
(
current
);
assert
.
deepEqual
(
Array
.
from
(
history
,
(
record
)
=>
record
.
_id
),
[
'old-id'
]);
assert
.
deepEqual
(
calls
[
0
].
query
.
filter
.
_id
,
{
$ne
:
current
.
_id
});
}
});
test
(
'name-only detail history also excludes the current record'
,
async
()
=>
{
const
current
=
{
name
:
'named-current'
,
subject
:
'internal_regulation'
,
metadata
:
{
origin_regulation
:
'named-current'
}
};
const
historical
=
{
name
:
'named-history'
,
subject
:
'internal_regulation'
,
metadata
:
{
origin_regulation
:
'named-current'
}
};
const
{
service
,
calls
}
=
createService
([
current
,
historical
]);
const
history
=
await
service
.
loadRegulationHistory
(
current
);
assert
.
deepEqual
(
Array
.
from
(
history
,
(
record
)
=>
record
.
name
),
[
'named-history'
]);
assert
.
deepEqual
(
calls
[
0
].
query
.
filter
.
name
,
{
$ne
:
current
.
name
});
});
test
(
'directory histories exclude legacy self references before the next publication'
,
async
()
=>
{
const
current
=
currentRegulation
({
origin_regulation
:
'current-id'
});
const
historical
=
{
...
current
,
_id
:
'old-id'
,
name
:
'old'
,
metadata
:
{
...
current
.
metadata
,
effective_status
:
'已失效'
}
};
const
{
service
}
=
createService
([
current
,
historical
]);
const
result
=
await
service
.
loadRegulations
();
const
row
=
result
.
records
.
find
((
record
)
=>
record
.
_id
===
current
.
_id
);
assert
.
deepEqual
(
Array
.
from
(
row
.
revisions
,
(
record
)
=>
record
.
_id
),
[
'old-id'
]);
});
test
(
'topology histories exclude self for nodes linked by record ID or name'
,
async
()
=>
{
const
graphSource
=
await
fs
.
readFile
(
new
URL
(
'../src/pages/internal_regulation_manager/RegulationGraph.jsx'
,
import
.
meta
.
url
),
'utf8'
);
const
start
=
graphSource
.
indexOf
(
'function enrichGraph('
);
const
end
=
graphSource
.
indexOf
(
'function parseXmindJson('
,
start
);
assert
.
ok
(
start
>=
0
&&
end
>
start
);
const
enrichGraph
=
new
vm
.
Script
(
`
${
graphSource
.
slice
(
start
,
end
)}
\nenrichGraph`
).
runInNewContext
({
normalizeGraph
:
(
graph
)
=>
graph
,
normalizeNode
:
(
node
)
=>
node
,
metadataOf
:
(
record
)
=>
record
?.
metadata
||
{},
referenceId
:
(
value
)
=>
value
&&
typeof
value
===
'object'
?
value
.
_id
||
value
.
name
:
value
,
...
graphHelpers
,
...
historyHelpers
,
});
const
current
=
currentRegulation
({
origin_regulation
:
'current-id'
});
const
historical
=
{
...
current
,
_id
:
'old-id'
,
name
:
'old'
,
metadata
:
{
...
current
.
metadata
,
effective_status
:
'已失效'
}
};
const
unrelated
=
currentRegulation
({
regulation_name
:
'其他制度'
,
origin_regulation
:
'unrelated-id'
});
unrelated
.
_id
=
'unrelated-id'
;
unrelated
.
name
=
'unrelated'
;
for
(
const
reference
of
[
current
.
_id
,
current
.
name
])
{
const
graph
=
{
nodes
:
[{
id
:
'node'
,
title
:
current
.
title
,
relatedRegulation
:
reference
,
historyRegulation
:
[
current
,
historical
]
}],
edges
:
[]
};
const
enriched
=
enrichGraph
(
graph
,
[
current
,
historical
,
unrelated
],
[]);
assert
.
deepEqual
(
Array
.
from
(
enriched
.
nodes
[
0
].
historyRegulation
,
(
record
)
=>
record
.
_id
),
[
historical
.
_id
]);
assert
.
equal
(
enriched
.
nodes
[
0
].
relatedRegulation
,
reference
);
}
});
src/pages/internal_regulation_manager/RegulationGraph.jsx
View file @
e7a7bf9d
...
@@ -23,6 +23,7 @@ import ProcessCascade from '@/components/ProcessCascade';
...
@@ -23,6 +23,7 @@ import ProcessCascade from '@/components/ProcessCascade';
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanAutocomplete
from
'@/components/XinyuanAutocomplete'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
XinyuanModal
from
'@/components/XinyuanModal'
;
import
{
attachGraphAncestors
}
from
'@/services/regulationGraphData'
;
import
{
attachGraphAncestors
}
from
'@/services/regulationGraphData'
;
import
{
isRegulationHistory
,
isSameRegulation
}
from
'@/services/regulationHistory'
;
import
{
createGraphXmindArchive
}
from
'@/services/regulationGraphXmind'
;
import
{
createGraphXmindArchive
}
from
'@/services/regulationGraphXmind'
;
import
GraphNode
from
'./GraphNode'
;
import
GraphNode
from
'./GraphNode'
;
import
{
getGraphRecordId
,
getNextGraphRecordNode
,
getSelectedGraphRecordId
}
from
'./graphSelection'
;
import
{
getGraphRecordId
,
getNextGraphRecordNode
,
getSelectedGraphRecordId
}
from
'./graphSelection'
;
...
@@ -144,8 +145,10 @@ function enrichGraph(nextGraph, regulations, revisions) {
...
@@ -144,8 +145,10 @@ function enrichGraph(nextGraph, regulations, revisions) {
const
revisionType
=
metadataOf
(
revision
).
type
;
const
revisionType
=
metadataOf
(
revision
).
type
;
status
=
revisionType
===
'新增'
?
'addition'
:
revisionType
===
'废止'
?
'forbidden'
:
'revise'
;
status
=
revisionType
===
'新增'
?
'addition'
:
revisionType
===
'废止'
?
'forbidden'
:
'revise'
;
}
}
const
history
=
relationId
?
regulations
.
filter
((
record
)
=>
String
(
referenceId
(
metadataOf
(
record
).
origin_regulation
))
===
String
(
relationId
))
:
node
.
historyRegulation
;
const
relatedRecord
=
relationId
const
relatedRecord
=
relationId
?
regulationById
.
get
(
String
(
relationId
))
:
titleMatch
?.
record
;
?
regulationById
.
get
(
String
(
relationId
))
||
regulations
.
find
((
record
)
=>
isSameRegulation
(
record
,
relationId
))
:
titleMatch
?.
record
;
const
history
=
relationId
?
regulations
.
filter
((
record
)
=>
isRegulationHistory
(
record
,
relatedRecord
||
relationId
))
:
node
.
historyRegulation
;
return
normalizeNode
({
...
node
,
relatedRegulation
:
relationId
||
node
.
relatedRegulation
,
relatedRegulationType
:
status
||
'active'
,
historyRegulation
:
history
,
title
:
node
.
title
||
metadataOf
(
relatedRecord
).
regulation_name
||
relatedRecord
?.
title
});
return
normalizeNode
({
...
node
,
relatedRegulation
:
relationId
||
node
.
relatedRegulation
,
relatedRegulationType
:
status
||
'active'
,
historyRegulation
:
history
,
title
:
node
.
title
||
metadataOf
(
relatedRecord
).
regulation_name
||
relatedRecord
?.
title
});
});
});
return
{
nodes
:
attachGraphAncestors
(
nodes
),
edges
:
graph
.
edges
};
return
{
nodes
:
attachGraphAncestors
(
nodes
),
edges
:
graph
.
edges
};
...
...
src/pages/internal_regulation_manager/internal-regulation.business-flow.md
View file @
e7a7bf9d
...
@@ -25,6 +25,10 @@
...
@@ -25,6 +25,10 @@
发文时:新增制度拒绝重复名称;修订制度先生成失效历史版本,再更新现行版本;废止制度更新现行制度为“已失效”。日期统一转换为 ISO 8601,附件仅保存引用 ID。
发文时:新增制度拒绝重复名称;修订制度先生成失效历史版本,再更新现行版本;废止制度更新现行制度为“已失效”。日期统一转换为 ISO 8601,附件仅保存引用 ID。
-
修订发文先保存原制度快照,快照为“已失效”,
`origin_regulation`
仅保存原制度 ID;现行制度从修订计划接收制度 Subject 定义中的业务字段,不复制计划的
`origin_regulation/type/status`
、OA 匹配信息或发起日期。新增、废止发文使用同一字段白名单,避免计划关联写入制度。
-
更新现行制度时,若已有
`origin_regulation`
指向自身(兼容 ID、name 或展开的引用对象),显式写入
`null`
清空;指向其他制度的既有关联保留。此清理在该制度下一次修订或废止发文时完成,不自动批量修改存量记录。
-
制度目录的历史集合、详情“历史沿革”和拓扑节点的历史版本统一按原制度关联筛选,并按 ID/name 排除现行记录自身;详情查询也增加排除自身的服务端条件,因此存量自引用数据在清理前不会展示为自己的历史版本。历史读取或发文请求失败时沿用现有错误提示。
## 归档与拓扑
## 归档与拓扑
-
制度拓扑图与制度目录是两个平级入口;进入拓扑图后不呈现制度目录层级,也不提供返回制度目录操作,用户可通过左侧一级菜单在两个模块之间切换。
-
制度拓扑图与制度目录是两个平级入口;进入拓扑图后不呈现制度目录层级,也不提供返回制度目录操作,用户可通过左侧一级菜单在两个模块之间切换。
...
...
src/services/internalRegulationService.js
View file @
e7a7bf9d
import
dayjs
from
'dayjs'
;
import
dayjs
from
'dayjs'
;
import
{
serializeGraph
}
from
'./regulationGraphData'
;
import
{
serializeGraph
}
from
'./regulationGraphData'
;
import
{
isRegulationHistory
,
isSameRegulation
}
from
'./regulationHistory'
;
import
{
import
{
createDocument
,
createDocument
,
createRecord
,
createRecord
,
...
@@ -18,6 +19,14 @@ export const GRAPH_SUBJECT = 'internal_regulation_graph';
...
@@ -18,6 +19,14 @@ export const GRAPH_SUBJECT = 'internal_regulation_graph';
const
REGULATION_LEVELS
=
[
'1级'
,
'2级'
,
'3级'
,
'4级'
];
const
REGULATION_LEVELS
=
[
'1级'
,
'2级'
,
'3级'
,
'4级'
];
const
REVISION_TYPES
=
[
'新增'
,
'修订'
,
'废止'
];
const
REVISION_TYPES
=
[
'新增'
,
'修订'
,
'废止'
];
// Only internal_regulation fields may be copied from a revision plan.
// origin_regulation belongs to the plan/history relationship, not this update.
const
REGULATION_PUBLICATION_FIELDS
=
[
'regulation_name'
,
'effective_status'
,
'version'
,
'level'
,
'effective_date'
,
'departments'
,
'tags'
,
'attachments'
,
'related_regulations'
,
'reference_regulation'
,
'document_file'
,
'document'
,
'public_date'
,
'approve'
,
'comment'
,
'attachment_pdf'
,
'processes'
,
'category'
,
'citing_regulations'
,
];
function
recordsOf
(
response
)
{
function
recordsOf
(
response
)
{
if
(
Array
.
isArray
(
response
?.
records
))
return
response
.
records
;
if
(
Array
.
isArray
(
response
?.
records
))
return
response
.
records
;
...
@@ -58,6 +67,12 @@ function metadataOf(record) {
...
@@ -58,6 +67,12 @@ function metadataOf(record) {
return
record
?.
metadata
||
{};
return
record
?.
metadata
||
{};
}
}
function
publicationMetadata
(
metadata
)
{
return
Object
.
fromEntries
(
REGULATION_PUBLICATION_FIELDS
.
filter
((
field
)
=>
Object
.
hasOwn
(
metadata
,
field
))
.
map
((
field
)
=>
[
field
,
metadata
[
field
]]));
}
function
isoDate
(
value
)
{
function
isoDate
(
value
)
{
if
(
!
value
)
return
value
;
if
(
!
value
)
return
value
;
const
date
=
dayjs
(
value
);
const
date
=
dayjs
(
value
);
...
@@ -139,7 +154,7 @@ export async function loadRegulations({ page = 0, pageSize = 20, keyword = '', f
...
@@ -139,7 +154,7 @@ export async function loadRegulations({ page = 0, pageSize = 20, keyword = '', f
const
metadata
=
metadataOf
(
record
);
const
metadata
=
metadataOf
(
record
);
const
relatedRevisions
=
revisions
.
filter
((
revision
)
=>
referenceId
(
metadataOf
(
revision
).
origin_regulation
)
===
record
.
_id
);
const
relatedRevisions
=
revisions
.
filter
((
revision
)
=>
referenceId
(
metadataOf
(
revision
).
origin_regulation
)
===
record
.
_id
);
const
histories
=
history
const
histories
=
history
.
filter
((
item
)
=>
referenceId
(
metadataOf
(
item
).
origin_regulation
)
===
record
.
_id
)
.
filter
((
item
)
=>
isRegulationHistory
(
item
,
record
)
)
.
sort
((
left
,
right
)
=>
String
(
metadataOf
(
right
).
public_date
||
''
).
localeCompare
(
String
(
metadataOf
(
left
).
public_date
||
''
)));
.
sort
((
left
,
right
)
=>
String
(
metadataOf
(
right
).
public_date
||
''
).
localeCompare
(
String
(
metadataOf
(
left
).
public_date
||
''
)));
return
{
return
{
...
record
,
...
record
,
...
@@ -156,7 +171,11 @@ export async function loadRegulations({ page = 0, pageSize = 20, keyword = '', f
...
@@ -156,7 +171,11 @@ export async function loadRegulations({ page = 0, pageSize = 20, keyword = '', f
export
async
function
loadRegulationHistory
(
record
)
{
export
async
function
loadRegulationHistory
(
record
)
{
const
id
=
record
?.
_id
||
record
?.
name
;
const
id
=
record
?.
_id
||
record
?.
name
;
if
(
!
id
)
return
[];
if
(
!
id
)
return
[];
return
queryAll
(
REGULATION_SUBJECT
,
{
filter
:
{
'metadata.origin_regulation'
:
id
},
sort
:
'-metadata.public_date'
});
const
filter
=
{
'metadata.origin_regulation'
:
id
};
if
(
record
.
_id
)
filter
.
_id
=
{
$ne
:
record
.
_id
};
else
if
(
record
.
name
)
filter
.
name
=
{
$ne
:
record
.
name
};
const
history
=
await
queryAll
(
REGULATION_SUBJECT
,
{
filter
,
sort
:
'-metadata.public_date'
});
return
history
.
filter
((
item
)
=>
isRegulationHistory
(
item
,
record
));
}
}
export
async
function
loadRevisions
({
page
=
0
,
pageSize
=
20
,
status
=
'起草中'
,
keyword
=
''
}
=
{})
{
export
async
function
loadRevisions
({
page
=
0
,
pageSize
=
20
,
status
=
'起草中'
,
keyword
=
''
}
=
{})
{
...
@@ -235,6 +254,7 @@ export async function setRevisionStatus(name, status) {
...
@@ -235,6 +254,7 @@ export async function setRevisionStatus(name, status) {
export
async
function
publishRevision
(
revision
,
values
=
{})
{
export
async
function
publishRevision
(
revision
,
values
=
{})
{
const
metadata
=
metadataOf
(
revision
);
const
metadata
=
metadataOf
(
revision
);
const
regulationMetadata
=
publicationMetadata
(
metadata
);
const
publicDate
=
isoDate
(
values
.
public_date
||
new
Date
().
toISOString
());
const
publicDate
=
isoDate
(
values
.
public_date
||
new
Date
().
toISOString
());
const
attachmentIds
=
(
values
.
attachments
||
[]).
map
((
item
)
=>
referenceId
(
item
)).
filter
(
Boolean
);
const
attachmentIds
=
(
values
.
attachments
||
[]).
map
((
item
)
=>
referenceId
(
item
)).
filter
(
Boolean
);
const
content
=
values
.
content
??
revision
.
content
??
''
;
const
content
=
values
.
content
??
revision
.
content
??
''
;
...
@@ -246,17 +266,22 @@ export async function publishRevision(revision, values = {}) {
...
@@ -246,17 +266,22 @@ export async function publishRevision(revision, values = {}) {
subject
:
REGULATION_SUBJECT
,
subject
:
REGULATION_SUBJECT
,
title
:
metadata
.
regulation_name
,
title
:
metadata
.
regulation_name
,
content
,
content
,
metadata
:
normalizeMetadata
({
...
metadata
,
effective_status
:
'现行有效'
,
public_date
:
publicDate
,
effective_date
:
values
.
effective_date
||
publicDate
,
attachments
:
attachmentIds
,
document
:
values
.
document
,
status
:
undefined
}),
metadata
:
normalizeMetadata
({
...
regulationMetadata
,
effective_status
:
'现行有效'
,
public_date
:
publicDate
,
effective_date
:
values
.
effective_date
||
publicDate
,
attachments
:
attachmentIds
,
document
:
values
.
document
}),
});
});
}
else
if
(
metadata
.
origin_regulation
)
{
}
else
if
(
metadata
.
origin_regulation
)
{
const
originId
=
referenceId
(
metadata
.
origin_regulation
);
const
originId
=
referenceId
(
metadata
.
origin_regulation
);
const
origin
=
await
loadRecord
(
originId
);
const
origin
=
await
loadRecord
(
originId
);
if
(
!
origin
)
throw
new
Error
(
'原制度不存在,无法发文'
);
if
(
!
origin
)
throw
new
Error
(
'原制度不存在,无法发文'
);
const
originMetadata
=
{
...
metadataOf
(
origin
)
};
if
(
isSameRegulation
(
originMetadata
.
origin_regulation
,
origin
))
{
// PUT merges metadata on the server, so omission cannot clear a legacy value.
originMetadata
.
origin_regulation
=
null
;
}
if
(
metadata
.
type
===
'修订'
)
{
if
(
metadata
.
type
===
'修订'
)
{
await
createRecord
({
subject
:
REGULATION_SUBJECT
,
title
:
`
${
origin
.
title
||
metadata
.
regulation_name
}
${
dayjs
(
metadataOf
(
origin
).
public_date
||
metadataOf
(
origin
).
effective_date
).
format
(
'YYYY-MM-DD'
)}
`
,
content
:
origin
.
content
||
''
,
metadata
:
normalizeMetadata
({
...
metadataOf
(
origin
),
origin_regulation
:
origin
.
_id
,
effective_status
:
'已失效'
})
});
await
createRecord
({
subject
:
REGULATION_SUBJECT
,
title
:
`
${
origin
.
title
||
metadata
.
regulation_name
}
${
dayjs
(
metadataOf
(
origin
).
public_date
||
metadataOf
(
origin
).
effective_date
).
format
(
'YYYY-MM-DD'
)}
`
,
content
:
origin
.
content
||
''
,
metadata
:
normalizeMetadata
({
...
metadataOf
(
origin
),
origin_regulation
:
origin
.
_id
,
effective_status
:
'已失效'
})
});
regulation
=
await
updateRecord
(
origin
.
name
||
origin
.
_id
,
{
name
:
origin
.
name
,
content
,
metadata
:
normalizeMetadata
({
...
metadataOf
(
origin
),
...
m
etadata
,
effective_status
:
'现行有效'
,
public_date
:
publicDate
,
attachments
:
attachmentIds
,
document
:
values
.
document
})
});
regulation
=
await
updateRecord
(
origin
.
name
||
origin
.
_id
,
{
name
:
origin
.
name
,
content
,
metadata
:
normalizeMetadata
({
...
originMetadata
,
...
regulationM
etadata
,
effective_status
:
'现行有效'
,
public_date
:
publicDate
,
attachments
:
attachmentIds
,
document
:
values
.
document
})
});
}
else
{
}
else
{
regulation
=
await
updateRecord
(
origin
.
name
||
origin
.
_id
,
{
name
:
origin
.
name
,
metadata
:
normalizeMetadata
({
...
metadataOf
(
origin
),
...
m
etadata
,
effective_status
:
'已失效'
,
public_date
:
publicDate
})
});
regulation
=
await
updateRecord
(
origin
.
name
||
origin
.
_id
,
{
name
:
origin
.
name
,
metadata
:
normalizeMetadata
({
...
originMetadata
,
...
regulationM
etadata
,
effective_status
:
'已失效'
,
public_date
:
publicDate
})
});
}
}
}
}
await
updateRecord
(
revision
.
name
,
{
name
:
revision
.
name
,
content
,
metadata
:
normalizeMetadata
({
...
metadata
,
status
:
'已发文'
,
public_date
:
publicDate
,
attachments
:
attachmentIds
,
document
:
values
.
document
})
});
await
updateRecord
(
revision
.
name
,
{
name
:
revision
.
name
,
content
,
metadata
:
normalizeMetadata
({
...
metadata
,
status
:
'已发文'
,
public_date
:
publicDate
,
attachments
:
attachmentIds
,
document
:
values
.
document
})
});
...
...
src/services/record-api.md
View file @
e7a7bf9d
...
@@ -129,6 +129,8 @@
...
@@ -129,6 +129,8 @@
请求体使用完整 Record payload。当前用于更新模型、批次、隐藏状态和审批数据;一次性同步的模型结果行不调用更新接口。
请求体使用完整 Record payload。当前用于更新模型、批次、隐藏状态和审批数据;一次性同步的模型结果行不调用更新接口。
普通
`normal`
Subject 的 PUT 更新会将请求中的
`metadata`
与已有字段合并;省略字段不会清空旧值。内部制度修订/废止发文发现现行制度自引用时,通过
`metadata.origin_regulation: null`
显式清空该可选 Reference,不传入引用对象。
### 3.6 删除 Record
### 3.6 删除 Record
`DELETE /record/:name`
`DELETE /record/:name`
...
...
src/services/regulationHistory.js
0 → 100644
View file @
e7a7bf9d
function
recordIdentities
(
value
)
{
const
ids
=
value
&&
typeof
value
===
'object'
?
[
value
.
_id
,
value
.
id
,
value
.
name
]
:
[
value
];
return
ids
.
filter
((
id
)
=>
id
!==
null
&&
id
!==
undefined
&&
id
!==
''
).
map
(
String
);
}
export
function
isSameRegulation
(
record
,
reference
)
{
const
referenceIds
=
new
Set
(
recordIdentities
(
reference
));
return
recordIdentities
(
record
).
some
((
id
)
=>
referenceIds
.
has
(
id
));
}
export
function
isRegulationHistory
(
record
,
currentRegulation
)
{
return
!
isSameRegulation
(
record
,
currentRegulation
)
&&
isSameRegulation
(
record
?.
metadata
?.
origin_regulation
,
currentRegulation
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment