1+ from ast import For
2+ from collections import defaultdict
3+ from sqlalchemy .exc import IntegrityError
14from sqlmodel import Session , select
2- from api .db .models import Template , FormSubmission
5+ from api .db .models import (
6+ Template ,
7+ FormSubmission ,
8+ ReportSchema ,
9+ ReportSchemaTemplate ,
10+ SchemaField ,
11+ )
312
4- # Templates
513def create_template (session : Session , template : Template ) -> Template :
14+ try :
15+ session .add (template )
16+ session .commit ()
17+ session .refresh (template )
18+ return template
19+ except IntegrityError :
20+ raise
21+
22+ def get_template (session : Session , template_id : int ) -> Template | None :
23+ return session .get (Template , template_id )
24+
25+ def update_template (session : Session , template_id : int , updates : dict ) -> Template | None :
26+ template = session .get (Template , template_id )
27+ if not template :
28+ return None
29+ for key , value in updates .items ():
30+ setattr (template , key , value )
631 session .add (template )
732 session .commit ()
833 session .refresh (template )
934 return template
1035
11- def get_template (session : Session , template_id : int ) -> Template | None :
12- return session .get (Template , template_id )
36+ def list_templates (session : Session ) -> list [Template ]:
37+ return session .exec (select (Template )).all ()
38+
39+ def delete_template (session : Session , template_id : int ) -> bool :
40+ """Remove template and dependent rows (form submissions, schema links, schema fields)."""
41+ template = session .get (Template , template_id )
42+ if not template :
43+ return False
44+
45+ for form in session .exec (
46+ select (FormSubmission ).where (FormSubmission .template_id == template_id )
47+ ).all ():
48+ session .delete (form )
49+
50+ for junction in session .exec (
51+ select (ReportSchemaTemplate ).where (
52+ ReportSchemaTemplate .template_id == template_id
53+ )
54+ ).all ():
55+ for field in session .exec (
56+ select (SchemaField ).where (
57+ SchemaField .report_schema_id == junction .report_schema_id ,
58+ SchemaField .source_template_id == template_id ,
59+ )
60+ ).all ():
61+ session .delete (field )
62+ session .delete (junction )
63+
64+ session .delete (template )
65+ session .commit ()
66+ return True
1367
14- # Forms
1568def create_form (session : Session , form : FormSubmission ) -> FormSubmission :
1669 session .add (form )
1770 session .commit ()
1871 session .refresh (form )
19- return form
72+ return form
73+
74+ def get_form (session : Session , form_id : int ) -> FormSubmission :
75+ return session .get (FormSubmission , form_id )
76+
77+ def update_form (session : Session , form_id : int , updates : dict ) -> FormSubmission | None :
78+ form = session .get (FormSubmission , form_id )
79+ if not form :
80+ return None
81+ for key , value in updates .items ():
82+ setattr (form , key , value )
83+ session .add (form )
84+ session .commit ()
85+ session .refresh (form )
86+ return form
87+
88+ def delete_form (session : Session , form_id : int ) -> FormSubmission :
89+ form_submission = session .get (FormSubmission , form_id )
90+ if form_submission :
91+ session .delete (form_submission )
92+ session .commit ()
93+ return True
94+ return False
95+
96+ def create_report_schema (session : Session , schema : ReportSchema ) -> ReportSchema :
97+ try :
98+ session .add (schema )
99+ session .commit ()
100+ session .refresh (schema )
101+ return schema
102+ except IntegrityError :
103+ raise
104+
105+ def get_report_schema (session : Session , schema_id : int ) -> ReportSchema | None :
106+ return session .get (ReportSchema , schema_id )
107+
108+ def list_report_schemas (session : Session ) -> list [ReportSchema ]:
109+ return session .exec (select (ReportSchema )).all ()
110+
111+ def update_report_schema (session : Session , schema_id : int , updates : dict ) -> ReportSchema | None :
112+ schema = session .get (ReportSchema , schema_id )
113+ if not schema :
114+ return None
115+ for key , value in updates .items ():
116+ setattr (schema , key , value )
117+ session .add (schema )
118+ session .commit ()
119+ session .refresh (schema )
120+ return schema
121+
122+ def delete_report_schema (session : Session , schema_id : int ) -> bool :
123+ schema = session .get (ReportSchema , schema_id )
124+ if not schema :
125+ return False
126+
127+ fields = session .exec (
128+ select (SchemaField ).where (SchemaField .report_schema_id == schema_id )
129+ ).all ()
130+ for field in fields :
131+ session .delete (field )
132+
133+ junctions = session .exec (
134+ select (ReportSchemaTemplate ).where (
135+ ReportSchemaTemplate .report_schema_id == schema_id
136+ )
137+ ).all ()
138+ for junction in junctions :
139+ session .delete (junction )
140+
141+ session .delete (schema )
142+ session .commit ()
143+ return True
144+
145+
146+ def add_template_to_schema (
147+ session : Session , schema_id : int , template_id : int
148+ ) -> ReportSchemaTemplate :
149+ """Associate a template with a schema.
150+
151+ Looks up `template.fields` and auto-creates a SchemaField for each field,
152+ pre-populated with `field_name` and `source_template_id`.
153+ Other metadata is left as defaults for the user to fill in later.
154+ """
155+ template = session .get (Template , template_id )
156+ if not template :
157+ raise ValueError (f"Template { template_id } not found" )
158+
159+ schema = session .get (ReportSchema , schema_id )
160+ if not schema :
161+ raise ValueError (f"ReportSchema { schema_id } not found" )
162+
163+ # exists = session.exec(select(ReportSchemaTemplate).where(ReportSchemaTemplate.report_schema_id == schema_id, ReportSchemaTemplate.template_id == template_id)).first()
164+ # if exists:
165+ # raise IntegrityError
166+
167+ # Create the junction record (field_mapping starts empty, populated during canonization)
168+ junction = ReportSchemaTemplate (
169+ report_schema_id = schema_id ,
170+ template_id = template_id ,
171+ )
172+
173+ session .add (junction )
174+
175+ # Auto-create a SchemaField for each field in the template
176+ for field_name in template .fields :
177+ schema_field = SchemaField (
178+ report_schema_id = schema_id ,
179+ field_name = field_name ,
180+ source_template_id = template_id ,
181+ )
182+ session .add (schema_field )
183+
184+ session .commit ()
185+ session .refresh (junction )
186+ return junction
187+
188+ def remove_template_from_schema (
189+ session : Session , schema_id : int , template_id : int
190+ ) -> bool :
191+ """Disassociate a template from a schema and remove its SchemaField entries."""
192+ junction = session .exec (
193+ select (ReportSchemaTemplate ).where (
194+ ReportSchemaTemplate .report_schema_id == schema_id ,
195+ ReportSchemaTemplate .template_id == template_id ,
196+ )
197+ ).first ()
198+ if not junction :
199+ return False
200+
201+ fields = session .exec (
202+ select (SchemaField ).where (
203+ SchemaField .report_schema_id == schema_id ,
204+ SchemaField .source_template_id == template_id ,
205+ )
206+ ).all ()
207+ for field in fields :
208+ session .delete (field )
209+
210+ session .delete (junction )
211+ session .commit ()
212+ return True
213+
214+
215+ def get_schema_fields (session : Session , schema_id : int ) -> list [SchemaField ]:
216+ return session .exec (
217+ select (SchemaField ).where (SchemaField .report_schema_id == schema_id )
218+ ).all ()
219+
220+ def get_schema_field (session : Session , field_id : int ) -> SchemaField :
221+ return session .get (SchemaField , field_id )
222+
223+ def update_schema_field (session : Session , schema_id : int , field_id : int , updates : dict ) -> SchemaField | None :
224+ """Update field metadata: description, data_type, word_limit, required, allowed_values.
225+
226+ Validates that the field belongs to the given schema before updating,
227+ so the same template field in different schemas can have independent metadata.
228+ """
229+ field = session .get (SchemaField , field_id )
230+ if not field or field .report_schema_id != schema_id :
231+ return None
232+ for key , value in updates .items ():
233+ setattr (field , key , value )
234+ session .add (field )
235+ session .commit ()
236+ session .refresh (field )
237+ return field
238+
239+
240+ # ── Template mapping (post-canonization) ─────────────────────────────────────
241+
242+ def update_template_mapping (
243+ session : Session , schema_id : int , template_id : int
244+ ) -> ReportSchemaTemplate | None :
245+ """Auto-generate and store the canonical → PDF field mapping after canonization.
246+
247+ Builds the mapping by looking up all SchemaFields for this schema+template pair
248+ and mapping each field's canonical_name → field_name.
249+ """
250+ junction = session .exec (
251+ select (ReportSchemaTemplate ).where (
252+ ReportSchemaTemplate .report_schema_id == schema_id ,
253+ ReportSchemaTemplate .template_id == template_id ,
254+ )
255+ ).first ()
256+ if not junction :
257+ return None
258+
259+ # Build mapping from SchemaFields that have been canonized
260+ fields = session .exec (
261+ select (SchemaField ).where (
262+ SchemaField .report_schema_id == schema_id ,
263+ SchemaField .source_template_id == template_id ,
264+ )
265+ ).all ()
266+
267+ grouped : defaultdict [str , list [str ]] = defaultdict (list )
268+ for field in sorted (fields , key = lambda f : f .field_name ):
269+ key = field .canonical_name if field .canonical_name else field .field_name
270+ grouped [key ].append (field .field_name )
271+
272+ # One PDF field -> store str; several sharing a canonical -> list (distribute handles both).
273+ field_mapping : dict = {}
274+ for key , names in grouped .items ():
275+ field_mapping [key ] = names [0 ] if len (names ) == 1 else names
276+
277+ junction .field_mapping = field_mapping
278+ session .add (junction )
279+ session .commit ()
280+ session .refresh (junction )
281+ return junction
282+
283+ def get_field_mapping (session : Session , schema_id : int , template_id : int ) -> ReportSchemaTemplate :
284+ junction = session .exec (select (ReportSchemaTemplate ).where (ReportSchemaTemplate .report_schema_id == schema_id , ReportSchemaTemplate .template_id == template_id )).first ()
285+ return junction .field_mapping
0 commit comments