folio-migration-tools 1.2.1__py3-none-any.whl → 1.9.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. folio_migration_tools/__init__.py +11 -0
  2. folio_migration_tools/__main__.py +169 -85
  3. folio_migration_tools/circulation_helper.py +96 -59
  4. folio_migration_tools/config_file_load.py +66 -0
  5. folio_migration_tools/custom_dict.py +6 -4
  6. folio_migration_tools/custom_exceptions.py +21 -19
  7. folio_migration_tools/extradata_writer.py +46 -0
  8. folio_migration_tools/folder_structure.py +63 -66
  9. folio_migration_tools/helper.py +29 -21
  10. folio_migration_tools/holdings_helper.py +57 -34
  11. folio_migration_tools/i18n_config.py +9 -0
  12. folio_migration_tools/library_configuration.py +173 -13
  13. folio_migration_tools/mapper_base.py +317 -106
  14. folio_migration_tools/mapping_file_transformation/courses_mapper.py +203 -0
  15. folio_migration_tools/mapping_file_transformation/holdings_mapper.py +83 -69
  16. folio_migration_tools/mapping_file_transformation/item_mapper.py +98 -94
  17. folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +352 -0
  18. folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +702 -223
  19. folio_migration_tools/mapping_file_transformation/notes_mapper.py +90 -0
  20. folio_migration_tools/mapping_file_transformation/order_mapper.py +492 -0
  21. folio_migration_tools/mapping_file_transformation/organization_mapper.py +389 -0
  22. folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +38 -27
  23. folio_migration_tools/mapping_file_transformation/user_mapper.py +149 -361
  24. folio_migration_tools/marc_rules_transformation/conditions.py +650 -246
  25. folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +292 -130
  26. folio_migration_tools/marc_rules_transformation/hrid_handler.py +244 -0
  27. folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +20846 -0
  28. folio_migration_tools/marc_rules_transformation/marc_file_processor.py +300 -0
  29. folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +136 -0
  30. folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +241 -0
  31. folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +681 -201
  32. folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +395 -429
  33. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +531 -100
  34. folio_migration_tools/migration_report.py +85 -38
  35. folio_migration_tools/migration_tasks/__init__.py +1 -3
  36. folio_migration_tools/migration_tasks/authority_transformer.py +119 -0
  37. folio_migration_tools/migration_tasks/batch_poster.py +911 -198
  38. folio_migration_tools/migration_tasks/bibs_transformer.py +121 -116
  39. folio_migration_tools/migration_tasks/courses_migrator.py +192 -0
  40. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +252 -247
  41. folio_migration_tools/migration_tasks/holdings_marc_transformer.py +321 -115
  42. folio_migration_tools/migration_tasks/items_transformer.py +264 -84
  43. folio_migration_tools/migration_tasks/loans_migrator.py +506 -195
  44. folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +187 -0
  45. folio_migration_tools/migration_tasks/migration_task_base.py +364 -74
  46. folio_migration_tools/migration_tasks/orders_transformer.py +373 -0
  47. folio_migration_tools/migration_tasks/organization_transformer.py +451 -0
  48. folio_migration_tools/migration_tasks/requests_migrator.py +130 -62
  49. folio_migration_tools/migration_tasks/reserves_migrator.py +253 -0
  50. folio_migration_tools/migration_tasks/user_transformer.py +180 -139
  51. folio_migration_tools/task_configuration.py +46 -0
  52. folio_migration_tools/test_infrastructure/__init__.py +0 -0
  53. folio_migration_tools/test_infrastructure/mocked_classes.py +406 -0
  54. folio_migration_tools/transaction_migration/legacy_loan.py +148 -34
  55. folio_migration_tools/transaction_migration/legacy_request.py +65 -25
  56. folio_migration_tools/transaction_migration/legacy_reserve.py +47 -0
  57. folio_migration_tools/transaction_migration/transaction_result.py +12 -1
  58. folio_migration_tools/translations/en.json +476 -0
  59. folio_migration_tools-1.9.10.dist-info/METADATA +169 -0
  60. folio_migration_tools-1.9.10.dist-info/RECORD +67 -0
  61. {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info}/WHEEL +1 -2
  62. folio_migration_tools-1.9.10.dist-info/entry_points.txt +3 -0
  63. folio_migration_tools/generate_schemas.py +0 -46
  64. folio_migration_tools/mapping_file_transformation/mapping_file_mapping_base_impl.py +0 -44
  65. folio_migration_tools/mapping_file_transformation/user_mapper_base.py +0 -212
  66. folio_migration_tools/marc_rules_transformation/bibs_processor.py +0 -163
  67. folio_migration_tools/marc_rules_transformation/holdings_processor.py +0 -284
  68. folio_migration_tools/report_blurbs.py +0 -219
  69. folio_migration_tools/transaction_migration/legacy_fee_fine.py +0 -36
  70. folio_migration_tools-1.2.1.dist-info/METADATA +0 -134
  71. folio_migration_tools-1.2.1.dist-info/RECORD +0 -50
  72. folio_migration_tools-1.2.1.dist-info/top_level.txt +0 -1
  73. {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info/licenses}/LICENSE +0 -0
@@ -1,52 +1,68 @@
1
1
  import logging
2
2
  import re
3
+ import traceback
4
+ from typing import Union
3
5
 
4
- import pymarc
6
+ import i18n
5
7
  from folioclient import FolioClient
8
+ from pymarc import field
9
+
6
10
  from folio_migration_tools.custom_exceptions import (
7
11
  TransformationFieldMappingError,
8
12
  TransformationProcessError,
9
13
  TransformationRecordFailedError,
10
14
  )
15
+ from folio_migration_tools.helper import Helper
16
+ from folio_migration_tools.library_configuration import FolioRelease
11
17
  from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
12
18
  RulesMapperBase,
13
19
  )
14
- from pymarc import field
15
- from folio_migration_tools.helper import Helper
16
20
 
17
- from folio_migration_tools.report_blurbs import Blurbs
21
+ # flake8: noqa: s
18
22
 
19
23
 
20
24
  class Conditions:
25
+ holdings_type_map = {
26
+ "u": "Unknown",
27
+ "v": "Multi-part monograph",
28
+ "x": "Monograph",
29
+ "y": "Serial",
30
+ }
31
+
21
32
  def __init__(
22
33
  self,
23
34
  folio: FolioClient,
24
35
  mapper: RulesMapperBase,
25
36
  object_type,
37
+ folio_release: FolioRelease,
26
38
  default_call_number_type_name="",
27
39
  ):
28
40
  self.filter_chars = r"[.,\/#!$%\^&\*;:{}=\-_`~()]"
29
41
  self.filter_chars_dop = r"[.,\/#!$%\^&\*;:{}=\_`~()]"
42
+ self.folio_release: FolioRelease = folio_release
30
43
  self.filter_last_chars = r",$"
31
44
  self.folio = folio
32
- self.default_contributor_type = ""
33
- self.mapper = mapper
45
+ self.default_contributor_type: dict = {}
46
+ self.mapper: RulesMapperBase = mapper
34
47
  self.ref_data_dicts = {}
35
- self.setup_reference_data_for_all()
36
48
  if object_type == "bibs":
49
+ self.setup_reference_data_for_all()
37
50
  self.setup_reference_data_for_bibs()
51
+ elif object_type == "auth":
52
+ self.setup_reference_data_for_auth()
38
53
  else:
39
- self.setup_reference_data_for_items_and_holdings(
40
- default_call_number_type_name
41
- )
42
- self.condition_cache = {}
54
+ self.setup_reference_data_for_all()
55
+ self.setup_reference_data_for_items_and_holdings(default_call_number_type_name)
56
+ self.object_type = object_type
57
+ self.condition_cache: dict = {}
43
58
 
44
59
  def setup_reference_data_for_bibs(self):
45
60
  logging.info("Setting up reference data for bib transformation")
46
- logging.info("%s\tcontrib_name_types", len(self.folio.contrib_name_types))
47
- logging.info("%s\tcontributor_types", len(self.folio.contributor_types))
48
- logging.info("%s\talt_title_types", len(self.folio.alt_title_types))
49
- logging.info("%s\tidentifier_types", len(self.folio.identifier_types))
61
+ logging.info("%s\tcontrib_name_types", len(self.folio.contrib_name_types)) # type: ignore
62
+ logging.info("%s\tcontributor_types", len(self.folio.contributor_types)) # type: ignore
63
+ logging.info("%s\talt_title_types", len(self.folio.alt_title_types)) # type: ignore
64
+ logging.info("%s\tidentifier_types", len(self.folio.identifier_types)) # type: ignore
65
+ logging.info("%s\tsubject_types", len(self.folio.subject_types)) # type: ignore
50
66
  # Raise for empty settings
51
67
  if not self.folio.contributor_types:
52
68
  raise TransformationProcessError("", "No contributor_types in FOLIO")
@@ -56,43 +72,39 @@ class Conditions:
56
72
  raise TransformationProcessError("", "No identifier_types in FOLIO")
57
73
  if not self.folio.alt_title_types:
58
74
  raise TransformationProcessError("", "No alt_title_types in FOLIO")
75
+ if not self.folio.subject_types:
76
+ raise TransformationProcessError("", "No subject_types in FOLIO")
59
77
 
60
78
  # Set defaults
61
79
  logging.info("Setting defaults")
62
- self.default_contributor_name_type = self.folio.contrib_name_types[0]["id"]
80
+ self.default_contributor_name_type: str = self.folio.contrib_name_types[0]["id"] # type: ignore
63
81
  logging.info("Contributor name type:\t%s", self.default_contributor_name_type)
64
82
  self.default_contributor_type = next(
65
- ct for ct in self.folio.contributor_types if ct["code"] == "ctb"
83
+ ct for ct in self.folio.contributor_types if ct["code"] == "ctb" # type: ignore
66
84
  )
67
85
  logging.info("Contributor type:\t%s", self.default_contributor_type["id"])
68
86
 
69
- def setup_reference_data_for_items_and_holdings(
70
- self, default_call_number_type_name
71
- ):
72
- logging.info(f"{len(self.folio.locations)}\tlocations")
87
+ def setup_reference_data_for_items_and_holdings(self, default_call_number_type_name):
88
+ logging.info(f"{len(self.folio.locations)}\tlocations") # type: ignore
73
89
  self.default_call_number_type = {}
74
- logging.info("%s\tholding_note_types", len(self.folio.holding_note_types))
75
- logging.info("%s\tcall_number_types", len(self.folio.call_number_types))
76
- self.holdings_types = list(
77
- self.folio.folio_get_all("/holdings-types", "holdingsTypes", "", 1000)
78
- )
79
- logging.info("%s\tholdings types", len(self.holdings_types))
90
+ logging.info("%s\tholding_note_types", len(self.folio.holding_note_types)) # type: ignore
91
+ logging.info("%s\tcall_number_types", len(self.folio.call_number_types)) # type: ignore
92
+ self.setup_and_validate_holdings_types()
93
+ self.ill_policies = self.folio.folio_get_all("/ill-policies", "illPolicies")
80
94
  # Raise for empty settings
81
95
  if not self.folio.holding_note_types:
82
96
  raise TransformationProcessError("", "No holding_note_types in FOLIO")
83
97
  if not self.folio.call_number_types:
84
98
  raise TransformationProcessError("", "No call_number_types in FOLIO")
85
- if not self.holdings_types:
86
- raise TransformationProcessError("", "No holdings_types in FOLIO")
87
99
  if not self.folio.locations:
88
100
  raise TransformationProcessError("", "No locations in FOLIO")
89
101
 
90
102
  # Set defaults
91
103
  logging.info("Defaults")
92
- self.default_call_number_type = next(
104
+ self.default_call_number_type: dict = next(
93
105
  (
94
106
  ct
95
- for ct in self.folio.call_number_types
107
+ for ct in self.folio.call_number_types # type: ignore
96
108
  if ct["name"] == default_call_number_type_name
97
109
  ),
98
110
  None,
@@ -106,36 +118,51 @@ class Conditions:
106
118
  "Please specify another UUID as the default Callnumber Type"
107
119
  ),
108
120
  )
109
- logging.info(
110
- "Default Callnumber type Name:\t%s", self.default_call_number_type["name"]
111
- )
121
+ logging.info("Default Callnumber type Name:\t%s", self.default_call_number_type["name"])
122
+
123
+ def setup_and_validate_holdings_types(self):
124
+ self.holdings_types = self.folio.holdings_types
125
+ if not self.holdings_types:
126
+ raise TransformationProcessError("", "No holdings_types in FOLIO")
127
+ missing_holdings_types = [
128
+ ht
129
+ for ht in self.holdings_type_map.values()
130
+ if ht not in [ht_ref["name"] for ht_ref in self.holdings_types] # type: ignore
131
+ ]
132
+ if any(missing_holdings_types):
133
+ raise TransformationProcessError(
134
+ "",
135
+ "Holdings types are missing from the tenant. Please set them up",
136
+ missing_holdings_types,
137
+ )
138
+ logging.info("%s\tholdings types", len(self.holdings_types)) # type: ignore
112
139
 
113
140
  def setup_reference_data_for_all(self):
141
+ logging.info(f"{len(self.folio.class_types)}\tclass_types") # type: ignore
114
142
  logging.info(
115
- f"{len(self.folio.electronic_access_relationships)}\telectronic_access_relationships"
143
+ f"{len(self.folio.electronic_access_relationships)}\telectronic_access_relationships" # type: ignore
116
144
  )
117
- logging.info(f"{len(self.folio.class_types)}\tclass_types")
118
- self.statistical_codes = list(
119
- self.folio.folio_get_all(
120
- "/statistical-codes",
121
- "statisticalCodes",
122
- "?query=cql.allRecords=1",
123
- 1000,
124
- )
125
- )
126
- logging.info(f"{len(self.statistical_codes)} \tstatistical_codes")
145
+ self.statistical_codes = self.folio.statistical_codes
146
+ logging.info(f"{len(self.statistical_codes)} \tstatistical_codes") # type: ignore
127
147
 
128
148
  # Raise for empty settings
129
149
  if not self.folio.class_types:
130
150
  raise TransformationProcessError("", "No class_types in FOLIO")
131
151
 
152
+ def setup_reference_data_for_auth(self):
153
+ self.authority_note_types = list(
154
+ self.folio.folio_get_all(
155
+ "/authority-note-types", "authorityNoteTypes", self.folio.cql_all, 1000
156
+ )
157
+ )
158
+ logging.info(f"{len(self.authority_note_types)} \tAuthority note types")
159
+ logging.info(f"{len(self.folio.identifier_types)} \tidentifier types") # type: ignore
160
+
132
161
  def get_condition(
133
- self, name, legacy_id, value, parameter=None, marc_field: field.Field = None
162
+ self, name, legacy_id, value, parameter=None, marc_field: Union[None, field.Field] = None
134
163
  ):
135
164
  try:
136
- return self.condition_cache.get(name)(
137
- legacy_id, value, parameter, marc_field
138
- )
165
+ return self.condition_cache.get(name)(legacy_id, value, parameter, marc_field) # type: ignore
139
166
  # Exception should only handle the missing condition from the cache.
140
167
  # All other exceptions should propagate up
141
168
  except Exception:
@@ -143,19 +170,113 @@ class Conditions:
143
170
  self.condition_cache[name] = attr
144
171
  return attr(legacy_id, value, parameter, marc_field)
145
172
 
146
- def condition_trim_period(
147
- self, legacy_id, value, parameter, marc_field: field.Field
148
- ):
173
+ def condition_trim_punctuation(self, legacy_id, value, parameter, marc_field: field.Field):
174
+ """
175
+ Strip leading and trailing whitespace, as well as any trailing commas or periods, unless
176
+ the period is preceded by a single alpha character (eg. "John D."). Also preserves any
177
+ trailing "-" (eg. "1981-"). This condition was introduced in Poppy.
178
+ """
179
+ pattern1 = re.compile(r"^(.*?)\s.[.]$")
180
+ pattern2 = re.compile(r"^(.*?)\s.,[.]$")
181
+ value = value.strip()
182
+ if pattern1.match(value) or value.endswith("-"):
183
+ return value
184
+ elif pattern2.match(value):
185
+ return value.rstrip(",")
186
+ elif value.endswith(".") or value.endswith(","):
187
+ return value[:-1]
188
+ return value
189
+
190
+ def condition_trim_period(self, legacy_id, value, parameter, marc_field: field.Field):
149
191
  return value.strip().rstrip(".").rstrip(",")
150
192
 
151
193
  def condition_trim(self, legacy_id, value, parameter, marc_field: field.Field):
152
194
  return value.strip()
153
195
 
196
+ def condition_set_contributor_type_id_by_code_or_name(
197
+ self, legacy_id, value, parameter, marc_field: field.Field
198
+ ):
199
+ contributor_code_subfield = parameter.get("contributorCodeSubfield", "4")
200
+ for subfield in marc_field.get_subfields(contributor_code_subfield):
201
+ normalized_subfield = re.sub(r"[^A-Za-z0-9 ]+", "", subfield.strip())
202
+ t = self.get_ref_data_tuple_by_code(
203
+ self.folio.contributor_types, "contrib_types_c", normalized_subfield
204
+ )
205
+ if not t:
206
+ self.mapper.migration_report.add(
207
+ "ContributorTypeMapping",
208
+ (
209
+ f'Mapping failed for ${contributor_code_subfield} "{subfield}" '
210
+ f"({normalized_subfield}) "
211
+ ),
212
+ )
213
+ Helper.log_data_issue(
214
+ legacy_id,
215
+ f"Mapping failed for ${contributor_code_subfield}",
216
+ f'{subfield}" ({normalized_subfield}) ',
217
+ )
218
+ else:
219
+ self.mapper.migration_report.add(
220
+ "ContributorTypeMapping",
221
+ (
222
+ i18n.t(
223
+ 'Contributor type code "%{code}" found for $%{code_subfield}',
224
+ code=t[1],
225
+ code_subfield=contributor_code_subfield,
226
+ )
227
+ + f' "{subfield}" ({normalized_subfield}))'
228
+ ),
229
+ )
230
+ return t[0]
231
+ fallback_name_field = "j" if marc_field.tag in ["111", "711"] else "e"
232
+ contributor_name_subfield = parameter.get("contributorNameSubfield", fallback_name_field)
233
+ for subfield in marc_field.get_subfields(contributor_name_subfield):
234
+ normalized_subfield = re.sub(r"[^A-Za-z0-9 ]+", "", subfield.strip())
235
+ t = self.get_ref_data_tuple_by_name(
236
+ self.folio.contributor_types, "contrib_types_n", normalized_subfield
237
+ )
238
+
239
+ if not t:
240
+ self.mapper.migration_report.add(
241
+ "ContributorTypeMapping",
242
+ (
243
+ f"Mapping failed for {marc_field.tag} ${contributor_name_subfield} "
244
+ f"{subfield} (Normalized: {normalized_subfield}) "
245
+ ),
246
+ )
247
+ Helper.log_data_issue(
248
+ legacy_id,
249
+ f"Mapping failed for {marc_field.tag} ${contributor_name_subfield}",
250
+ f'{subfield}" ({normalized_subfield}) ',
251
+ )
252
+ else:
253
+ self.mapper.migration_report.add(
254
+ "ContributorTypeMapping",
255
+ (
256
+ f"Contributor type name {t[1]} found for {marc_field.tag} "
257
+ f"${contributor_name_subfield} {normalized_subfield} ({subfield}) "
258
+ ),
259
+ )
260
+ return t[0]
261
+ return ""
262
+
263
+ def condition_set_holdings_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
264
+ self.mapper.migration_report.add("HoldingsTypeMapping", i18n.t("Condition in rules hit"))
265
+ return ""
266
+
154
267
  def condition_concat_subfields_by_name(
155
268
  self, legacy_id, value, parameter, marc_field: field.Field
156
269
  ):
157
270
  subfields_to_concat = parameter.get("subfieldsToConcat", [])
158
- concat_string = " ".join(marc_field.get_subfields(*subfields_to_concat))
271
+ subfields_to_stop_concat = parameter.get("subfieldsToStopConcat", [])
272
+ concat_subfields = []
273
+ subfields = marc_field.subfields
274
+ for t in subfields:
275
+ if t[0] in subfields_to_stop_concat:
276
+ break
277
+ elif t[0] in subfields_to_concat:
278
+ concat_subfields.append(t[1])
279
+ concat_string = " ".join(concat_subfields)
159
280
  return f"{value} {concat_string}"
160
281
 
161
282
  def condition_get_value_if_subfield_is_empty(
@@ -164,14 +285,14 @@ class Conditions:
164
285
  if value.strip():
165
286
  return value.strip()
166
287
  self.mapper.migration_report.add(
167
- Blurbs.AddedValueFromParameter,
168
- f"Tag: {marc_field.tag}. Added value: {parameter['value']}",
288
+ "AddedValueFromParameter",
289
+ i18n.t(
290
+ "Tag: %{tag}. Added value: %{value}", tag=marc_field.tag, value=parameter["value"]
291
+ ),
169
292
  )
170
293
  return parameter["value"]
171
294
 
172
- def condition_remove_ending_punc(
173
- self, legacy_id, value, parameter, marc_field: field.Field
174
- ):
295
+ def condition_remove_ending_punc(self, legacy_id, value, parameter, marc_field: field.Field):
175
296
  v = value
176
297
  chars = ".;:,/+=- "
177
298
  while any(v) > 0 and v[-1] in chars:
@@ -188,14 +309,14 @@ class Conditions:
188
309
  self.folio.instance_formats, "instance_formats_code", value
189
310
  )
190
311
  self.mapper.migration_report.add(
191
- Blurbs.InstanceFormat,
192
- f'Successful match - "{value}"->{t[1]}',
312
+ "InstanceFormat",
313
+ i18n.t("Successful match") + f' - "{value}"->{t[1]}',
193
314
  )
194
315
  return t[0]
195
316
  except Exception:
196
317
  self.mapper.migration_report.add(
197
- Blurbs.InstanceFormat,
198
- f'Code from 338$b NOT found in FOLIO: "{value}"',
318
+ "InstanceFormat",
319
+ i18n.t('Code from 338$b NOT found in FOLIO: "%{value}"', value=value),
199
320
  )
200
321
 
201
322
  return ""
@@ -212,25 +333,17 @@ class Conditions:
212
333
  num_take = int(ind2)
213
334
  return re.sub(reg_str, "", value[num_take:])
214
335
 
215
- def condition_capitalize(
216
- self, legacy_id, value, parameter, marc_field: field.Field
217
- ):
336
+ def condition_capitalize(self, legacy_id, value, parameter, marc_field: field.Field):
218
337
  return value.capitalize()
219
338
 
220
- def condition_clean_isbn(
221
- self, legacy_id, value, parameter, marc_field: field.Field
222
- ):
339
+ def condition_clean_isbn(self, legacy_id, value, parameter, marc_field: field.Field):
223
340
  return value
224
341
 
225
- def condition_set_issuance_mode_id(
226
- self, legacy_id, value, parameter, marc_field: field.Field
227
- ):
342
+ def condition_set_issuance_mode_id(self, legacy_id, value, parameter, marc_field: field.Field):
228
343
  # mode of issuance is handled elsewhere in the mapping.
229
344
  return ""
230
345
 
231
- def condition_set_publisher_role(
232
- self, legacy_id, value, parameter, marc_field: field.Field
233
- ):
346
+ def condition_set_publisher_role(self, legacy_id, value, parameter, marc_field: field.Field):
234
347
  roles = {
235
348
  "0": "Production",
236
349
  "1": "Publication",
@@ -240,7 +353,7 @@ class Conditions:
240
353
  }
241
354
  role = roles.get(marc_field.indicator2, "")
242
355
  self.mapper.migration_report.add(
243
- Blurbs.MappedPublisherRoleFromIndicator2,
356
+ "MappedPublisherRoleFromIndicator2",
244
357
  f"{marc_field.tag} ind2 {marc_field.indicator2}->{role}",
245
358
  )
246
359
  return role
@@ -262,33 +375,50 @@ class Conditions:
262
375
  parameter["names"][0],
263
376
  )
264
377
  self.mapper.migration_report.add(
265
- Blurbs.MappedIdentifierTypes, f"{marc_field.tag} -> {t[1]}"
378
+ "MappedIdentifierTypes", f"{marc_field.tag} -> {t[1]}"
266
379
  )
267
380
  return t[0]
268
- identifier_type = next(
269
- (f for f in self.folio.identifier_types if f["name"] in parameter["names"]),
381
+ identifier_type: dict = next(
382
+ (
383
+ f
384
+ for f in self.folio.identifier_types # type: ignore
385
+ if (
386
+ f["name"] in parameter.get("names", "non existant")
387
+ or f["name"] in parameter.get("name", "non existant")
388
+ )
389
+ ),
270
390
  None,
271
391
  )
272
- self.mapper.migration_report.add(
273
- Blurbs.MappedIdentifierTypes, identifier_type["name"]
274
- )
392
+ self.mapper.migration_report.add("MappedIdentifierTypes", identifier_type["name"])
275
393
  my_id = identifier_type["id"]
276
394
  if not my_id:
277
395
  raise TransformationFieldMappingError(
278
396
  legacy_id,
279
- f"no matching identifier_types in {parameter['names']}",
397
+ i18n.t("no matching identifier_types in %{names}", names=parameter["names"]),
280
398
  marc_field,
281
399
  )
282
400
  return my_id
283
401
 
284
402
  def condition_set_holding_note_type_id_by_name(
285
403
  self, legacy_id, value, parameter, marc_field: field.Field
404
+ ):
405
+ self.mapper.migration_report.add(
406
+ "Exceptions",
407
+ (
408
+ "Condition set_holding_note_type_id_by_name is deprecated. "
409
+ "Use set_holdings_note_type_id instead"
410
+ ),
411
+ )
412
+ return self.condition_set_holdings_note_type_id(legacy_id, value, parameter, marc_field)
413
+
414
+ def condition_set_holdings_note_type_id(
415
+ self, legacy_id, value, parameter, marc_field: field.Field
286
416
  ):
287
417
  try:
288
418
  t = self.get_ref_data_tuple_by_name(
289
419
  self.folio.holding_note_types, "holding_note_types", parameter["name"]
290
420
  )
291
- self.mapper.migration_report.add(Blurbs.MappedNoteTypes, t[1])
421
+ self.mapper.migration_report.add("MappedNoteTypes", t[1])
292
422
  return t[0]
293
423
  except Exception as ee:
294
424
  logging.error(ee)
@@ -297,7 +427,25 @@ class Conditions:
297
427
  f'Holdings note type mapping error.\tParameter: {parameter.get("name", "")}\t'
298
428
  f"MARC Field: {marc_field}. Is mapping rules and ref data aligned?",
299
429
  parameter.get("name", ""),
430
+ ) from ee
431
+
432
+ def condition_set_authority_note_type_id(
433
+ self, legacy_id, _, parameter, marc_field: field.Field
434
+ ):
435
+ try:
436
+ t = self.get_ref_data_tuple_by_name(
437
+ self.authority_note_types, "authority_note_types", parameter["name"]
300
438
  )
439
+ self.mapper.migration_report.add("MappedNoteTypes", t[1])
440
+ return t[0]
441
+ except Exception as ee:
442
+ logging.error(ee)
443
+ raise TransformationProcessError(
444
+ legacy_id,
445
+ f'Authority note type mapping error.\tParameter: {parameter.get("name", "")}\t'
446
+ f"MARC Field: {marc_field}. Is mapping rules and ref data aligned?",
447
+ parameter.get("name", ""),
448
+ ) from ee
301
449
 
302
450
  def condition_set_classification_type_id(
303
451
  self, legacy_id, value, parameter, marc_field: field.Field
@@ -306,7 +454,7 @@ class Conditions:
306
454
  t = self.get_ref_data_tuple_by_name(
307
455
  self.folio.class_types, "class_types", parameter["name"]
308
456
  )
309
- self.mapper.migration_report.add(Blurbs.MappedClassificationTypes, t[1])
457
+ self.mapper.migration_report.add("MappedClassificationTypes", t[1])
310
458
  return t[0]
311
459
  except Exception:
312
460
  raise TransformationRecordFailedError(
@@ -316,40 +464,9 @@ class Conditions:
316
464
  parameter.get("name", ""),
317
465
  )
318
466
 
319
- def condition_char_select(
320
- self, legacy_id, value, parameter, marc_field: field.Field
321
- ):
467
+ def condition_char_select(self, legacy_id, value, parameter, marc_field: field.Field):
322
468
  return value[parameter["from"] : parameter["to"]]
323
469
 
324
- def condition_set_receipt_status(
325
- self, legacy_id, value, parameter, marc_field: field.Field
326
- ):
327
- if len(value) < 7:
328
- self.mapper.migration_report.add(
329
- Blurbs.ReceiptStatusMapping, f"008 is too short: {value}"
330
- )
331
- return ""
332
- try:
333
- status_map = {
334
- "0": "Unknown",
335
- "1": "Other receipt or acquisition status",
336
- "2": "Received and complete or ceased",
337
- "3": "On order",
338
- "4": "Currently received",
339
- "5": "Not currently received",
340
- "6": "External access",
341
- }
342
- mapped_value = status_map[value[6]]
343
- self.mapper.migration_report.add(
344
- Blurbs.ReceiptStatusMapping, f"{value[6]} mapped to {mapped_value}"
345
- )
346
-
347
- return
348
- except Exception:
349
- self.mapper.migration_report.add(
350
- Blurbs.ReceiptStatusMapping, f"{value[6]} not found in map."
351
- )
352
- return "Unknown"
353
470
 
354
471
  def condition_set_identifier_type_id_by_name(
355
472
  self, legacy_id, value, parameter, marc_field: field.Field
@@ -359,16 +476,17 @@ class Conditions:
359
476
  self.folio.identifier_types, "identifier_types", parameter["name"]
360
477
  )
361
478
  self.mapper.migration_report.add(
362
- Blurbs.MappedIdentifierTypes, f"{marc_field.tag} -> {t[1]}"
479
+ "MappedIdentifierTypes", f"{marc_field.tag} -> {t[1]}"
363
480
  )
364
481
  return t[0]
365
- except Exception:
366
- raise TransformationRecordFailedError(
482
+ except Exception as ee:
483
+ logging.exception("Identifier type")
484
+ raise TransformationProcessError(
367
485
  legacy_id,
368
- f'Unmapped identifier name type: "{parameter["name"]}"\tMARC Field: {marc_field}'
369
- f"MARC Field: {marc_field}. Is mapping rules and ref data aligned?",
486
+ f'Unmapped identifier type : "{parameter["name"]}"\tMARC Field: {marc_field}'
487
+ f"MARC Field: {marc_field}. Is mapping rules and ref data aligned? ",
370
488
  {parameter["name"]},
371
- )
489
+ ) from ee
372
490
 
373
491
  def condition_set_contributor_name_type_id(
374
492
  self, legacy_id, value, parameter, marc_field: field.Field
@@ -378,31 +496,25 @@ class Conditions:
378
496
  self.folio.contrib_name_types, "contrib_name_types", parameter["name"]
379
497
  )
380
498
  self.mapper.migration_report.add(
381
- Blurbs.MappedContributorNameTypes, f"{marc_field.tag} -> {t[1]}"
499
+ "MappedContributorNameTypes", f"{marc_field.tag} -> {t[1]}"
382
500
  )
383
501
  return t[0]
384
502
  except Exception:
385
- self.mapper.migration_report.add(
386
- Blurbs.UnmappedContributorNameTypes, parameter["name"]
387
- )
503
+ self.mapper.migration_report.add("UnmappedContributorNameTypes", parameter["name"])
388
504
  return self.default_contributor_name_type
389
505
 
390
- def condition_set_note_type_id(
391
- self, legacy_id, value, parameter, marc_field: field.Field
392
- ):
506
+ def condition_set_note_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
393
507
  try:
394
508
  t = self.get_ref_data_tuple_by_name(
395
509
  self.folio.instance_note_types, "instance_not_types", parameter["name"]
396
510
  )
397
511
  self.mapper.migration_report.add(
398
- Blurbs.MappedNoteTypes,
512
+ "MappedNoteTypes",
399
513
  f"{marc_field.tag} ({parameter.get('name', '')}) -> {t[1]}",
400
514
  )
401
515
  return t[0]
402
516
  except Exception:
403
- raise ValueError(
404
- f"Instance note type not found for {marc_field} {parameter}"
405
- )
517
+ raise ValueError(f"Instance note type not found for {marc_field} {parameter}")
406
518
 
407
519
  def condition_set_contributor_type_id(
408
520
  self, legacy_id, value, parameter, marc_field: field.Field
@@ -414,8 +526,13 @@ class Conditions:
414
526
  )
415
527
  if not t:
416
528
  self.mapper.migration_report.add(
417
- Blurbs.ContributorTypeMapping,
418
- f'Mapping failed for $4 "{subfield}" ({normalized_subfield}) ',
529
+ "ContributorTypeMapping",
530
+ i18n.t(
531
+ 'Mapping failed for %{tag} "%{subfield}" (%{normalized_subfield})',
532
+ tag="$4",
533
+ subfield=subfield,
534
+ normalized_subfield=normalized_subfield,
535
+ ),
419
536
  )
420
537
  Helper.log_data_issue(
421
538
  legacy_id,
@@ -424,8 +541,14 @@ class Conditions:
424
541
  )
425
542
  else:
426
543
  self.mapper.migration_report.add(
427
- Blurbs.ContributorTypeMapping,
428
- f'Contributor type code {t[1]} found for $4 "{subfield}" ({normalized_subfield}))',
544
+ "ContributorTypeMapping",
545
+ i18n.t(
546
+ 'Contributor type code "%{code}" found for $%{code_subfield}',
547
+ code=t[1],
548
+ code_subfield="4",
549
+ normalized_subfield=normalized_subfield,
550
+ )
551
+ + f' "%{subfield}" (%{normalized_subfield}))',
429
552
  )
430
553
  return t[0]
431
554
  subfield_code = "j" if marc_field.tag in ["111", "711"] else "e"
@@ -437,50 +560,49 @@ class Conditions:
437
560
 
438
561
  if not t:
439
562
  self.mapper.migration_report.add(
440
- Blurbs.ContributorTypeMapping,
441
- f'Mapping failed for $e "{subfield}" ({normalized_subfield}) ',
563
+ "ContributorTypeMapping",
564
+ i18n.t(
565
+ 'Mapping failed for %{tag} "%{subfield}" (Normalized: %{normalized_subfield})',
566
+ tag=f"{marc_field.tag} $e",
567
+ subfield=subfield,
568
+ normalized_subfield=normalized_subfield,
569
+ ),
442
570
  )
443
571
  Helper.log_data_issue(
444
572
  legacy_id,
445
- "Mapping failed for $e",
573
+ f"Mapping failed for {marc_field.tag} $e",
446
574
  f'{subfield}" ({normalized_subfield}) ',
447
575
  )
448
576
  else:
449
577
  self.mapper.migration_report.add(
450
- Blurbs.ContributorTypeMapping,
451
- f'Contributor type name {t[1]} found for $e "{normalized_subfield}" ({subfield}) ',
578
+ "ContributorTypeMapping",
579
+ i18n.t(
580
+ "Contributor type name %{name} found for %{tag}",
581
+ name=t[1],
582
+ tag=marc_field.tag,
583
+ )
584
+ + f" $e {normalized_subfield} ({subfield}) ",
452
585
  )
453
586
  return t[0]
454
587
  return self.default_contributor_type["id"]
455
588
 
456
- def condition_set_instance_id_by_map(
457
- self, legacy_id, value, parameter, marc_field: field.Field
458
- ):
459
- try:
460
- if value:
461
- if value.strip() not in self.mapper.instance_id_map:
462
- raise ValueError()
463
- return self.mapper.instance_id_map[value.strip()]["folio_id"]
464
- Helper.log_data_issue(
465
- "", "No instance id provided", marc_field.format_field()
466
- )
467
- return ""
468
- except Exception:
469
- raise TransformationRecordFailedError(
470
- legacy_id,
471
- "Old instance id not in map",
472
- f"{marc_field.format_field()}",
473
- )
589
+ def condition_set_url_relationship(self, legacy_id, value, parameter, marc_field: field.Field):
590
+ return self._extracted_from_condition_set_electronic_access_relations_id_2("8", marc_field)
474
591
 
475
- def condition_set_url_relationship(
592
+ def condition_set_call_number_type_by_indicator(
476
593
  self, legacy_id, value, parameter, marc_field: field.Field
477
594
  ):
478
- return self._extracted_from_condition_set_electronic_access_relations_id_2(
479
- "8", marc_field
595
+ self.mapper.migration_report.add(
596
+ "Exceptions",
597
+ (
598
+ "Condition set_call_number_type_by_indicator is deprecated. "
599
+ "Change to set_call_number_type_id"
600
+ ),
480
601
  )
602
+ return self.condition_set_call_number_type_id(legacy_id, value, parameter, marc_field)
481
603
 
482
- def condition_set_call_number_type_by_indicator(
483
- self, legacy_id, value, parameter, marc_field: pymarc.Field
604
+ def condition_set_call_number_type_id(
605
+ self, legacy_id, value, parameter, marc_field: field.Field
484
606
  ):
485
607
  first_level_map = {
486
608
  "0": "Library of Congress classification",
@@ -497,8 +619,8 @@ class Conditions:
497
619
  # CallNumber type specified in $2. This needs further mapping
498
620
  if marc_field.indicator1 == "7" and "2" in marc_field:
499
621
  self.mapper.migration_report.add(
500
- Blurbs.CallNumberTypeMapping,
501
- f"Unhandled call number type in $2 (ind1 == 7) {marc_field['2']}",
622
+ "CallNumberTypeMapping",
623
+ i18n.t("Unhandled call number type in $2 (ind1 == 7)" + str(marc_field["2"])),
502
624
  )
503
625
  return self.default_call_number_type["id"]
504
626
 
@@ -506,10 +628,13 @@ class Conditions:
506
628
  call_number_type_name_temp = first_level_map.get(marc_field.indicator1, "")
507
629
  if not call_number_type_name_temp:
508
630
  self.mapper.migration_report.add(
509
- Blurbs.CallNumberTypeMapping,
631
+ "CallNumberTypeMapping",
510
632
  (
511
- f'Unhandled call number type in ind1: "{marc_field.indicator1}". '
512
- f' Returning default Callnumber type: {self.default_call_number_type["name"]}'
633
+ i18n.t(
634
+ 'Unhandled call number type in ind1: "%{ind1}".\n Returning default Callnumber type: %{type}',
635
+ ind1=marc_field.indicator1,
636
+ type=self.default_call_number_type["name"],
637
+ )
513
638
  ),
514
639
  )
515
640
  return self.default_call_number_type["id"]
@@ -518,13 +643,13 @@ class Conditions:
518
643
  )
519
644
  if t:
520
645
  self.mapper.migration_report.add(
521
- Blurbs.CallNumberTypeMapping,
522
- f"Mapped from Indicator 1 {marc_field.indicator1} -> {t[1]}",
646
+ "CallNumberTypeMapping",
647
+ i18n.t("Mapped from Indicator 1") + f" {marc_field.indicator1} -> {t[1]}",
523
648
  )
524
649
  return t[0]
525
650
 
526
651
  self.mapper.migration_report.add(
527
- Blurbs.CallNumberTypeMapping,
652
+ "CallNumberTypeMapping",
528
653
  (
529
654
  "Mapping failed. Setting default CallNumber type: "
530
655
  f'{self.default_call_number_type["name"]}'
@@ -538,19 +663,21 @@ class Conditions:
538
663
  ):
539
664
  for subfield in marc_field.get_subfields("4", "e"):
540
665
  normalized_subfield = re.sub(r"[^A-Za-z0-9 ]+", "", subfield.strip())
541
- for cont_type in self.folio.contributor_types:
666
+ for cont_type in self.folio.contributor_types: # type: ignore
542
667
  if normalized_subfield in [cont_type["code"], cont_type["name"]]:
543
668
  return cont_type["name"]
544
- return self.default_contributor_type["name"]
669
+ try:
670
+ return value
671
+ except IndexError:
672
+ logging.debug("Exception occurred: %s", traceback.format_exc())
673
+ return ""
545
674
 
546
- def condition_set_alternative_title_type_id(
547
- self, legacy_id, value, parameter, marc_field
548
- ):
675
+ def condition_set_alternative_title_type_id(self, legacy_id, value, parameter, marc_field):
549
676
  try:
550
677
  t = self.get_ref_data_tuple_by_name(
551
678
  self.folio.alt_title_types, "alt_title_types", parameter["name"]
552
679
  )
553
- self.mapper.migration_report.add(Blurbs.MappedAlternativeTitleTypes, t[1])
680
+ self.mapper.migration_report.add("MappedAlternativeTitleTypes", t[1])
554
681
  return t[0]
555
682
  except Exception:
556
683
  raise TransformationProcessError(
@@ -558,85 +685,70 @@ class Conditions:
558
685
  f"Alternative title type not found for {parameter['name']} {marc_field}",
559
686
  )
560
687
 
561
- def setup_location_code_from_second_column(self):
562
- try:
563
- other_columns = [
564
- f for f in self.mapper.location_map[0].keys() if f not in ["folio_code"]
565
- ]
566
- if len(other_columns) > 1:
567
- raise TransformationProcessError(
568
- "",
569
- "Other location map columns could not be used since more than one",
570
- other_columns,
571
- )
572
- elif len(other_columns) == 1:
573
- logging.info(f"{other_columns[0]} will be used for location mapping")
574
- return {
575
- lm[other_columns[0]]: lm["folio_code"]
576
- for lm in self.mapper.location_map
577
- }
578
- except Exception as ee:
579
- raise TransformationProcessError("", f"{ee}", self.mapper.location_map)
580
-
581
688
  def condition_set_location_id_by_code(
582
689
  self, legacy_id, value, parameter, marc_field: field.Field
583
690
  ):
584
691
  self.mapper.migration_report.add(
585
- Blurbs.Exceptions,
692
+ "Exceptions",
586
693
  (
587
694
  "set_location_id_by_code condition used in rules. "
588
695
  "Deprecated condition. Switch to set_permanent_location_id"
589
696
  ),
590
697
  )
591
- return self.condition_set_permanent_location_id(
592
- legacy_id, value, parameter, marc_field
593
- )
698
+ return self.condition_set_permanent_location_id(legacy_id, value, parameter, marc_field)
594
699
 
595
700
  def condition_set_permanent_location_id(
596
701
  self, legacy_id, value, parameter, marc_field: field.Field
597
702
  ):
598
- # Setup mapping if not already set up
599
703
  if "legacy_locations" not in self.ref_data_dicts:
600
704
  try:
601
- d = {
602
- lm["legacy_code"]: lm["folio_code"]
603
- for lm in self.mapper.location_map
604
- }
705
+ d = {lm["legacy_code"]: lm["folio_code"] for lm in self.mapper.location_map} # type: ignore
605
706
  self.ref_data_dicts["legacy_locations"] = d
707
+ for folio_code in d.values():
708
+ t = self.get_ref_data_tuple_by_code(
709
+ self.folio.locations, "locations", folio_code
710
+ )
711
+ if not t:
712
+ raise TransformationProcessError(
713
+ "", "No FOLIO location found for code", folio_code
714
+ )
715
+ if "*" not in d:
716
+ raise TransformationProcessError(
717
+ "",
718
+ (
719
+ "Fallback location mapping missing. Add a row with a * in the "
720
+ "legacy_code column and a location code to map unmapped locations to"
721
+ ),
722
+ "",
723
+ )
606
724
  except KeyError as ke:
607
725
  if "folio_code" in str(ke):
608
726
  raise TransformationProcessError(
609
727
  legacy_id, "Your location map lacks the column folio_code"
610
- )
728
+ ) from ke
611
729
  if "legacy_code" in str(ke):
612
- logging.info(
613
- "legacy_code column not found. "
614
- "Trying to use other columns from location map."
615
- )
616
- self.ref_data_dicts[
617
- "legacy_locations"
618
- ] = self.setup_location_code_from_second_column()
730
+ raise TransformationProcessError(
731
+ legacy_id, "Your location map lacks the column legacy_code"
732
+ ) from ke
733
+
619
734
  # Get the right code from the location map
620
- if self.mapper.location_map and any(self.mapper.location_map):
621
- mapped_code = (
622
- self.ref_data_dicts["legacy_locations"].get(value.strip(), "").strip()
623
- )
624
- else: # IF there is no map, assume legacy code is the same as FOLIO code
625
- mapped_code = value.strip()
735
+ mapped_code = self.ref_data_dicts["legacy_locations"].get(value.strip(), "").strip()
736
+ if not mapped_code:
737
+ mapped_code = self.ref_data_dicts["legacy_locations"].get("*", "").strip()
738
+ if mapped_code:
739
+ self.mapper.migration_report.add(
740
+ "LocationMapping", i18n.t("Fallback mapping") + f": {value}->{mapped_code}"
741
+ )
626
742
  # Get the FOLIO UUID for the code and return it
627
- t = self.get_ref_data_tuple_by_code(
628
- self.folio.locations, "locations", mapped_code
629
- )
743
+ t = self.get_ref_data_tuple_by_code(self.folio.locations, "locations", mapped_code)
630
744
  if not t:
631
745
  self.mapper.migration_report.add(
632
- Blurbs.LocationMapping, f"Unmapped code: '{value}'"
746
+ "LocationMapping", i18n.t("Unmapped code") + f": '{value}'"
633
747
  )
634
748
  raise TransformationRecordFailedError(
635
749
  legacy_id, "Could not map location from legacy code", value
636
750
  )
637
- self.mapper.migration_report.add(
638
- Blurbs.LocationMapping, f"'{value}' ({mapped_code}) -> {t[1]}"
639
- )
751
+ self.mapper.migration_report.add("LocationMapping", f"'{value}' ({mapped_code}) -> {t[1]}")
640
752
  return t[0]
641
753
 
642
754
  def get_ref_data_tuple_by_code(self, ref_data, ref_name, code):
@@ -657,17 +769,13 @@ class Conditions:
657
769
  self.ref_data_dicts[dict_key] = d
658
770
  return self.ref_data_dicts.get(dict_key, {}).get(key_value.lower(), ())
659
771
 
660
- def condition_remove_substring(
661
- self, legacy_id, value, parameter, marc_field: field.Field
662
- ):
772
+ def condition_remove_substring(self, legacy_id, value, parameter, marc_field: field.Field):
663
773
  return value.replace(parameter["substring"], "")
664
774
 
665
- def condition_set_instance_type_id(
666
- self, legacy_id, value, parameter, marc_field: field.Field
667
- ):
775
+ def condition_set_instance_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
668
776
  if marc_field.tag not in ["008", "336"]:
669
777
  self.mapper.migration_report.add(
670
- Blurbs.InstanceTypeMapping,
778
+ "InstanceTypeMapping",
671
779
  (
672
780
  f"Unhandled MARC tag {marc_field.tag}. Instance Type ID is only mapped "
673
781
  "from 336 "
@@ -678,14 +786,31 @@ class Conditions:
678
786
  def condition_set_electronic_access_relations_id(
679
787
  self, legacy_id, value, parameter, marc_field: field.Field
680
788
  ):
681
- return self._extracted_from_condition_set_electronic_access_relations_id_2(
682
- "3", marc_field
683
- )
789
+ """
790
+ This method handles the mapping of electronic access relationship IDs.
791
+ If the record type being mapped is FOLIO holdings, it provides an (optional) alternative
792
+ mapping based on a provided name parameter, bypassing the FOLIO MARC-to-Holdings mapping
793
+ engine behavior. This requires use of a supplemental mapping rules file in the
794
+ HoldingsMarcTransformer task definition containing the name parameter.
795
+ """
796
+ if self.object_type == "holdings" and "name" in parameter:
797
+ try:
798
+ t = self.get_ref_data_tuple_by_name(
799
+ self.folio.electronic_access_relationships,
800
+ "electronic_access_relationships",
801
+ parameter["name"],
802
+ )
803
+ self.mapper.migration_report.add("MappedElectronicRelationshipTypes", t[1])
804
+ return t[0]
805
+ except Exception:
806
+ raise TransformationProcessError(
807
+ legacy_id,
808
+ f"Electronic access relationship not found for {parameter['name']} {marc_field}",
809
+ )
810
+ return self._extracted_from_condition_set_electronic_access_relations_id_2("3", marc_field)
684
811
 
685
812
  # TODO Rename this here and in `condition_set_url_relationship` and `condition_set_electronic_access_relations_id`
686
- def _extracted_from_condition_set_electronic_access_relations_id_2(
687
- self, arg0, marc_field
688
- ):
813
+ def _extracted_from_condition_set_electronic_access_relations_id_2(self, arg0, marc_field):
689
814
  enum = {
690
815
  "0": "resource",
691
816
  "1": "version of resource",
@@ -703,7 +828,7 @@ class Conditions:
703
828
  name,
704
829
  )
705
830
 
706
- self.mapper.migration_report.add(Blurbs.MappedElectronicRelationshipTypes, t[1])
831
+ self.mapper.migration_report.add("MappedElectronicRelationshipTypes", t[1])
707
832
 
708
833
  return t[0]
709
834
 
@@ -714,9 +839,288 @@ class Conditions:
714
839
  # https://www.loc.gov/marc/bibliographic/bd541.html
715
840
  ind1 = marc_field.indicator1
716
841
  self.mapper.migration_report.add(
717
- Blurbs.StaffOnlyViaIndicator,
718
- f"{marc_field.tag} indicator1: {ind1} (1 is public, all other values are Staff only)",
842
+ "StaffOnlyViaIndicator",
843
+ f"{marc_field.tag} indicator1: {ind1} ("
844
+ + i18n.t("0 is staff-only, all other values are public")
845
+ + ")",
719
846
  )
720
- if ind1 != "1":
847
+ if ind1 == "0":
721
848
  return "true"
722
849
  return "false"
850
+
851
+ def condition_set_subject_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
852
+ try:
853
+ t = self.get_ref_data_tuple_by_name(
854
+ self.folio.subject_types, "subject_types", parameter["name"]
855
+ )
856
+ self.mapper.migration_report.add("MappedSubjectTypes", t[1])
857
+ return t[0]
858
+ except Exception:
859
+ raise TransformationProcessError(
860
+ legacy_id,
861
+ f"Subject type not found for {parameter['name']} {marc_field}",
862
+ )
863
+
864
+ def condition_set_subject_source_id(self, legacy_id, value, parameter, marc_field: field.Field):
865
+ try:
866
+ t = self.get_ref_data_tuple_by_name(
867
+ self.folio.folio_get_all("/subject-sources", "subjectSources"), "subject_sources", parameter["name"]
868
+ )
869
+ self.mapper.migration_report.add("MappedSubjectSources", t[1])
870
+ return t[0]
871
+ except Exception:
872
+ raise TransformationProcessError(
873
+ legacy_id,
874
+ f"Subject source not found for {parameter['name']} {marc_field}",
875
+ )
876
+
877
+ def condition_set_subject_source_id_by_code(self, legacy_id, value, parameter, marc_field: field.Field):
878
+ try:
879
+ t = self.get_ref_data_tuple_by_code(
880
+ self.folio.folio_get_all("/subject-sources", "subjectSources"), "subject_sources", value
881
+ )
882
+ self.mapper.migration_report.add("MappedSubjectSources", t[1])
883
+ return t[0]
884
+ except Exception:
885
+ raise TransformationProcessError(
886
+ legacy_id,
887
+ f"Subject source not found for {value} {marc_field}",
888
+ )
889
+
890
+ def condition_set_receipt_status(
891
+ self, legacy_id, value, parameter, marc_field: field.Field
892
+ ):
893
+ """
894
+ This method maps the receipt status based on the 008 field.
895
+ This condition is not available in FOLIO's MARC mapping engine and
896
+ will require use of a supplemental mapping rules file in the
897
+ HoldingsMarcTransformer task definition.
898
+ """
899
+ if len(value) < 7:
900
+ self.mapper.migration_report.add(
901
+ "ReceiptStatusMapping", i18n.t("008 is too short") + f": {value}"
902
+ )
903
+ return ""
904
+
905
+ status_map = {
906
+ "0": "Unknown",
907
+ "1": "Other receipt or acquisition status",
908
+ "2": "Received and complete or ceased",
909
+ "3": "On order",
910
+ "4": "Currently received",
911
+ "5": "Not currently received",
912
+ "6": "External access",
913
+ }
914
+
915
+ try:
916
+ mapped_value = status_map[value[6]]
917
+ self.mapper.migration_report.add(
918
+ "ReceiptStatusMapping",
919
+ i18n.t(
920
+ "%{value} mapped to %{mapped_value}",
921
+ value=value[6],
922
+ mapped_value=mapped_value,
923
+ ),
924
+ )
925
+ return mapped_value
926
+ except Exception:
927
+ self.mapper.migration_report.add(
928
+ "ReceiptStatusMapping", i18n.t("%{value} not found in map.", value=value[6])
929
+ )
930
+ return ""
931
+
932
+ def condition_set_acquisition_method(
933
+ self, legacy_id, value, parameter, marc_field: field.Field
934
+ ):
935
+ """
936
+ This method maps the acquisition method based on the 008 field.
937
+ This condition is not available in FOLIO's MARC mapping engine and
938
+ will require use of a supplemental mapping rules file in the
939
+ HoldingsMarcTransformer task definition.
940
+ """
941
+ if len(value) < 8:
942
+ self.mapper.migration_report.add(
943
+ "ReceiptStatusMapping", i18n.t("008 is too short") + f": {value}"
944
+ )
945
+ return ""
946
+
947
+ try:
948
+ acq_methods = {
949
+ "c": "Cooperative or consortial purchase",
950
+ "d": "Deposit",
951
+ "e": "Exchange",
952
+ "f": "Free",
953
+ "g": "Gift",
954
+ "l": "Legal deposit",
955
+ "m": "Membership",
956
+ "n": "Non-library purchase",
957
+ "p": "Purchase",
958
+ "q": "Lease",
959
+ "u": "Unknown",
960
+ "z": "Other method of acquisition",
961
+ }
962
+ mapped_value = acq_methods[value[7]]
963
+ self.mapper.migration_report.add(
964
+ "MethodOfAcquisitionMapping",
965
+ i18n.t(
966
+ "%{value} mapped to %{mapped_value}", value=value[7], mapped_value=mapped_value
967
+ ),
968
+ )
969
+ return mapped_value
970
+ except Exception:
971
+ self.mapper.migration_report.add(
972
+ "MethodOfAcquisitionMapping", i18n.t("%{value} not found in map.", value=value[8])
973
+ )
974
+ return ""
975
+
976
+ def condition_set_retention_policy(
977
+ self, legacy_id, value, parameter, marc_field: field.Field
978
+ ):
979
+ """
980
+ This method maps the retention policy based on the 008 field.
981
+ This condition is not available in FOLIO's MARC mapping engine and
982
+ will require use of a supplemental mapping rules file in the
983
+ HoldingsMarcTransformer task definition.
984
+ """
985
+ if len(value) < 13:
986
+ self.mapper.migration_report.add(
987
+ "RetentionPolicyMapping", i18n.t("008 is too short") + f": {value}"
988
+ )
989
+ return ""
990
+ value = value.replace("|", " ").replace("#", " ") # Replace pipe with space for mapping consistency
991
+ try:
992
+ retention_policies = {
993
+ "0": "Unknown",
994
+ "1": "Other general retention policy",
995
+ "2": "Retained except as replaced by updates",
996
+ "3": "Sample issue retained",
997
+ "4": "Retained until replaced by microform",
998
+ "5": "Retained until replaced by cumulation, replacement volume, or revision",
999
+ "6": "Retained for a limited period",
1000
+ "7": "Not retained",
1001
+ "8": "Permanently retained",
1002
+ }
1003
+ mapped_value = retention_policies[value[12]]
1004
+ self.mapper.migration_report.add(
1005
+ "RetentionPolicyMapping",
1006
+ i18n.t(
1007
+ "%{value} mapped to %{mapped_value}",
1008
+ value=value[12],
1009
+ mapped_value=mapped_value,
1010
+ ),
1011
+ )
1012
+ if value[12] == "6":
1013
+ policy_types = {
1014
+ "l": "Latest",
1015
+ "p": "Previous",
1016
+ }
1017
+ unit_types = {
1018
+ "m": "Day",
1019
+ "w": "Month",
1020
+ "y": "Year",
1021
+ "e": "Edition",
1022
+ "i": "Issue",
1023
+ "s": "Supplement"
1024
+ }
1025
+ try:
1026
+ specific_retention_policy = ""
1027
+ if value[13].strip() or value[15].strip():
1028
+ if value[14].strip() and int(value[14]) > 1:
1029
+ specific_retention_policy = f"{policy_types.get(value[13], '')} {value[14]} {unit_types.get(value[15], '')}s retained".strip()
1030
+ else:
1031
+ specific_retention_policy = f"{policy_types.get(value[13], '')} {unit_types.get(value[15], '')} retained".strip()
1032
+ if specific_retention_policy:
1033
+ self.mapper.migration_report.add(
1034
+ "RetentionPolicyMapping",
1035
+ i18n.t(
1036
+ "Retention policy 6 indicates a limited period. Specific retention period will be mapped from 008/13-15",
1037
+ )
1038
+ )
1039
+ return specific_retention_policy
1040
+ else:
1041
+ raise ValueError(
1042
+ "Specific retention policy is empty or invalid in 008/13-15"
1043
+ )
1044
+ except ValueError:
1045
+ self.mapper.migration_report.add(
1046
+ "RetentionPolicyMapping",
1047
+ i18n.t("Invalid specific retention policy in 008/13-15: %{value}", value=value[13:16]),
1048
+ )
1049
+ return mapped_value
1050
+ except Exception:
1051
+ self.mapper.migration_report.add(
1052
+ "RetentionPolicyMapping", i18n.t("%{value} not found in map.", value=value[12])
1053
+ )
1054
+ return ""
1055
+
1056
+ def condition_set_ill_policy(
1057
+ self, legacy_id, value, parameter, marc_field: field.Field
1058
+ ):
1059
+ """
1060
+ This method maps the ILL policy based on the 008 field.
1061
+ This condition is not available in FOLIO's MARC mapping engine and
1062
+ will require use of a supplemental mapping rules file in the
1063
+ HoldingsMarcTransformer task definition."""
1064
+ if len(value) < 21:
1065
+ self.mapper.migration_report.add(
1066
+ "ILLPolicyMapping", i18n.t("008 is too short") + f": {value}"
1067
+ )
1068
+ return ""
1069
+ try:
1070
+ ill_policies = {
1071
+ "a": "Will lend",
1072
+ "b": "Will not lend",
1073
+ "c": "Will lend hard copy only",
1074
+ "l": "Limited lending policy",
1075
+ "u": "Unknown",
1076
+ }
1077
+ mapped_value = ill_policies[value[20]]
1078
+ self.mapper.migration_report.add(
1079
+ "ILLPolicyMapping",
1080
+ i18n.t("%{value} mapped to %{mapped_value}", value=value[20], mapped_value=mapped_value),
1081
+ )
1082
+ ill_policy_id = self.get_ref_data_tuple_by_name(
1083
+ self.ill_policies, "ill_policies", mapped_value
1084
+ )
1085
+ return ill_policy_id[0] if ill_policy_id else ""
1086
+ except Exception:
1087
+ self.mapper.migration_report.add(
1088
+ "ILLPolicyMapping", i18n.t("%{value} not found in map.", value=value[20])
1089
+ )
1090
+ return ""
1091
+
1092
+ def condition_set_digitization_policy(
1093
+ self, legacy_id, value, parameter, marc_field: field.Field
1094
+ ):
1095
+ """
1096
+ This method maps the digitization policy based on the 008 field.
1097
+ This condition is not available in FOLIO's MARC mapping engine and
1098
+ will require use of a supplemental mapping rules file in the
1099
+ HoldingsMarcTransformer task definition.
1100
+ """
1101
+ if len(value) < 22:
1102
+ self.mapper.migration_report.add(
1103
+ "DigitizationPolicyMapping", i18n.t("008 is too short") + f": {value}"
1104
+ )
1105
+ return ""
1106
+ try:
1107
+ digitization_policies = {
1108
+ "a": "Will reproduce",
1109
+ "b": "Will not reproduce",
1110
+ "u": "Unknown",
1111
+ }
1112
+ mapped_value = digitization_policies[value[21]]
1113
+ self.mapper.migration_report.add(
1114
+ "DigitizationPolicyMapping",
1115
+ i18n.t(
1116
+ "%{value} mapped to %{mapped_value}",
1117
+ value=value[21],
1118
+ mapped_value=mapped_value,
1119
+ ),
1120
+ )
1121
+ return mapped_value
1122
+ except Exception:
1123
+ self.mapper.migration_report.add(
1124
+ "DigitizationPolicyMapping", i18n.t("%{value} not found in map.", value=value[21])
1125
+ )
1126
+ return ""