Skip to content

Commit ca65296

Browse files
committed
Allow SPV users to update protocol excerpt to proposal dossier
1 parent e7b6c10 commit ca65296

13 files changed

Lines changed: 194 additions & 2 deletions

File tree

docs/public/dev-manual/api/api_changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Other Changes
1414
^^^^^^^^^^^^^
1515
- ``@membership-notes``: Add endpoint to update a note on a membership.
1616
- ``@ris-return-excerpt``: Add endpoint to allow users from spv to create a proposal excerpt in a dossier they do not have view permission in.
17+
- ``@ris-update-excerpt``: Add endpoint to allow users from spv to update a proposal excerpt in a dossier they do not have view permission in.
1718

1819

1920
2025.8.0 (2025-08-22)

docs/public/dev-manual/api/proposals.rst

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,10 @@ Protokollauszug im Antragsdossier ablegen
165165
=========================================
166166

167167
Mit dem ``@ris-return-excerpt`` Endpoint können Protokollauszüge aus der SPV
168-
ins Antragsdossier eingereichtwerden. Der Endpoint erwartet als Pfad Parameter:
168+
ins Antragsdossier eingereicht werden. Der Endpoint erwartet als Pfad Parameter:
169169

170170
- Mandant ID
171171
- relative Dossierpfad
172-
- Vermerk als String
173172

174173

175174
**Beispiel-Request**:
@@ -187,6 +186,44 @@ ins Antragsdossier eingereichtwerden. Der Endpoint erwartet als Pfad Parameter:
187186
**Beispiel-Response**:
188187

189188

189+
.. sourcecode:: http
190+
191+
HTTP/1.1 200 OK
192+
Content-Type: application/json
193+
194+
{
195+
"path": "ordnungssystem/dossier-2/document-2",
196+
"intid": 3,
197+
"url": "http://gever.onegovgever.ch/fd/ordnungssystem/dossier-2/document-2",
198+
"current_version_id": 1,
199+
}
200+
201+
202+
Protokollauszug im Antragsdossier aktualisieren
203+
===============================================
204+
205+
Mit dem ``@ris-update-excerpt`` Endpoint können Protokollauszüge aus der SPV
206+
im Antragsdossier aktualisiert werden. Der Endpoint erwartet als Pfad Parameter:
207+
208+
- Mandant ID
209+
- relative Dokumentpfad vom Protokollauszug
210+
211+
212+
**Beispiel-Request**:
213+
214+
.. sourcecode:: http
215+
216+
POST ordnungssystem/dossier-1/document-1/@ris-update-excerpt HTTP/1.1
217+
Accept: application/json
218+
219+
{
220+
"target_admin_unit_id": "fd",
221+
"target_document_relative_path": "ordnungssystem/dossier-1/document-1"
222+
}
223+
224+
**Beispiel-Response**:
225+
226+
190227
.. sourcecode:: http
191228

192229
HTTP/1.1 200 OK

opengever/core/profiles/default/rolemap.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@
290290
<role name="Member" />
291291
</permission>
292292

293+
<permission name="opengever.ris: Update Excerpt" acquire="False">
294+
<role name="Manager" />
295+
<role name="Member" />
296+
</permission>
297+
293298
<!-- SHARING -->
294299
<permission name="Sharing page: Delegate Administrator role" acquire="True">
295300
<role name="Administrator" />

opengever/core/upgrades/20251023104506_add_ris_update_excerpt_permission/__init__ .py

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<rolemap>
2+
<permissions>
3+
4+
<permission name="opengever.ris: Update Excerpt" acquire="True">
5+
<role name="Manager" />
6+
<role name="Member" />
7+
</permission>
8+
9+
</permissions>
10+
</rolemap>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from ftw.upgrade import UpgradeStep
2+
3+
4+
class AddRisUpdateExcerptPermission(UpgradeStep):
5+
"""Add ris update excerpt permission.
6+
"""
7+
8+
def __call__(self):
9+
self.install_upgrade_profile()

opengever/ris/browser/configure.zcml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@
5050
layer="opengever.base.interfaces.IOpengeverBaseLayer"
5151
/>
5252

53+
<plone:service
54+
name="@ris-update-excerpt"
55+
for="opengever.document.document.IDocumentSchema"
56+
method="POST"
57+
factory="opengever.ris.browser.ris_excerpt.RISUpdateExcerptService"
58+
permission="opengever.ris.UpdateExcerpt"
59+
layer="opengever.base.interfaces.IOpengeverBaseLayer"
60+
/>
61+
5362
<browser:page
5463
name="receive-ris-return-excerpt"
5564
for="opengever.dossier.behaviors.dossier.IDossierMarker"
@@ -58,4 +67,12 @@
5867
layer="opengever.ogds.base.interfaces.IInternalOpengeverRequestLayer"
5968
/>
6069

70+
<browser:page
71+
name="receive-ris-update-excerpt"
72+
for="opengever.document.document.IDocumentSchema"
73+
class="opengever.ris.browser.ris_excerpt.RISUpdateExcerptReceive"
74+
permission="zope.Public"
75+
layer="opengever.ogds.base.interfaces.IInternalOpengeverRequestLayer"
76+
/>
77+
6178
</configure>

opengever/ris/browser/ris_excerpt.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
from Acquisition import aq_inContextOf
22
from Acquisition import aq_inner
3+
from opengever.base.interfaces import IDataCollector
34
from opengever.base.json_response import JSONResponse
45
from opengever.base.security import elevated_privileges
56
from opengever.base.transport import PrivilegedReceiveObject
67
from opengever.base.transport import Transporter
8+
from opengever.document.versioner import Versioner
9+
from opengever.journal.handlers import journal_entry_factory
10+
from opengever.ogds.base.utils import encode_after_json
11+
from opengever.ris import _
712
from plone import api
813
from plone.protect.interfaces import IDisableCSRFProtection
914
from plone.restapi.deserializer import json_body
1015
from plone.restapi.services import Service
1116
from z3c.relationfield.relation import RelationValue
1217
from zExceptions import BadRequest
18+
from zope.component import getAdapters
1319
from zope.component import getUtility
1420
from zope.interface import alsoProvides
1521
from zope.intid.interfaces import IIntIds
@@ -52,6 +58,39 @@ def reply(self):
5258
return result
5359

5460

61+
class RISUpdateExcerptService(Service):
62+
63+
def reply(self):
64+
alsoProvides(self.request, IDisableCSRFProtection)
65+
66+
if not api.user.has_permission("View", obj=self.context):
67+
return JSONResponse(self.request).error("Forbidden", status=403).dump()
68+
69+
data = json_body(self.request) or {}
70+
target_cid = data.get("target_admin_unit_id")
71+
container_path = data.get("target_doc_relative_path")
72+
73+
if not target_cid or not container_path:
74+
return (
75+
JSONResponse(self.request)
76+
.error(
77+
"Target admin_unit_id and paths are required.",
78+
status=400,
79+
)
80+
.dump()
81+
)
82+
83+
container_path = container_path.lstrip("/")
84+
85+
result = Transporter().transport_to(
86+
obj=self.context,
87+
target_cid=target_cid,
88+
container_path=container_path,
89+
view="receive-ris-update-excerpt",
90+
)
91+
return result
92+
93+
5594
class RISReturnExcerptReceive(PrivilegedReceiveObject):
5695
"""Receiver on the target dossier. Runs with elevated privileges."""
5796

@@ -127,3 +166,56 @@ def receive(self):
127166
pass
128167

129168
return document
169+
170+
171+
class RISUpdateExcerptReceive(RISReturnExcerptReceive):
172+
"""Receiver on the target dossier. Runs with elevated privileges."""
173+
174+
def _apply_payload_as_new_version(self, gever_doc, payload):
175+
Versioner(gever_doc).create_initial_version()
176+
177+
data = encode_after_json(payload)
178+
179+
for name, collector in getAdapters((gever_doc,), IDataCollector):
180+
if name in data:
181+
collector.insert(data[name])
182+
183+
file = data["field-data"]["IDocumentSchema"].get("file")
184+
if file:
185+
gever_doc.update_file(
186+
file["data"],
187+
content_type=file.get("content-type"),
188+
filename=file.get("filename"),
189+
create_version=True,
190+
)
191+
192+
journal_entry_factory(
193+
context=gever_doc,
194+
action="Update proposal excerpt from SPV",
195+
title=_("excerpt_was_replaced", default="Excerpt was replaced"),
196+
)
197+
api.content.transition(obj=gever_doc, transition="document-transition-finalize")
198+
199+
return gever_doc
200+
201+
def receive(self):
202+
document = self.container
203+
transporter = Transporter()
204+
payload = transporter._extract_data(self.request)
205+
206+
if document is None:
207+
raise BadRequest("Invalid 'doc_relative_path' (object not found).")
208+
209+
try:
210+
with elevated_privileges():
211+
if document.is_final_document():
212+
api.content.transition(
213+
obj=document, transition="document-transition-reopen"
214+
)
215+
216+
document = self._apply_payload_as_new_version(document, payload)
217+
218+
except Exception:
219+
pass
220+
221+
return document

opengever/ris/locales/de/LC_MESSAGES/opengever.ris.po

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,8 @@ msgstr "Thema"
112112
#: ./opengever/ris/viewlets/byline.py
113113
msgid "label_workflow_state"
114114
msgstr "Status"
115+
116+
#. Default: "Excerpt was replaced"
117+
#: ./opengever/ris/browser/ris_excerpt.py
118+
msgid "excerpt_was_replaced"
119+
msgstr "Protokollauszug ersetzt"

opengever/ris/locales/en/LC_MESSAGES/opengever.ris.po

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,8 @@ msgstr "Topic"
112112
#: ./opengever/ris/viewlets/byline.py
113113
msgid "label_workflow_state"
114114
msgstr "State"
115+
116+
#. Default: "Excerpt was replaced"
117+
#: ./opengever/ris/browser/ris_excerpt.py
118+
msgid "excerpt_was_replaced"
119+
msgstr "Excerpt was replaced"

0 commit comments

Comments
 (0)