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
@@ -0,0 +1,203 @@
1
+ import i18n
2
+ from typing import Any
3
+ from typing import Dict
4
+
5
+ from folio_uuid.folio_uuid import FOLIONamespaces
6
+ from folio_uuid.folio_uuid import FolioUUID
7
+ from folioclient import FolioClient
8
+
9
+ from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
10
+ from folio_migration_tools.library_configuration import LibraryConfiguration
11
+ from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
12
+ MappingFileMapperBase,
13
+ )
14
+ from folio_migration_tools.mapping_file_transformation.notes_mapper import NotesMapper
15
+ from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
16
+ RefDataMapping,
17
+ )
18
+
19
+
20
+ class CoursesMapper(MappingFileMapperBase):
21
+ def __init__(
22
+ self,
23
+ folio_client: FolioClient,
24
+ course_map,
25
+ terms_map,
26
+ departments_map,
27
+ library_configuration: LibraryConfiguration,
28
+ task_configuration,
29
+ ):
30
+ self.folio_client: FolioClient = folio_client
31
+ self.user_cache: dict = {}
32
+ self.notes_mapper: NotesMapper = NotesMapper(
33
+ library_configuration,
34
+ None,
35
+ self.folio_client,
36
+ course_map,
37
+ FOLIONamespaces.note,
38
+ True,
39
+ )
40
+ self.composite_course_schema = self.get_composite_course_schema()
41
+ super().__init__(
42
+ folio_client,
43
+ self.composite_course_schema,
44
+ course_map,
45
+ None,
46
+ FOLIONamespaces.course,
47
+ library_configuration,
48
+ task_configuration
49
+ )
50
+ self.course_map = course_map
51
+ if terms_map:
52
+ self.terms_map = RefDataMapping(
53
+ self.folio_client,
54
+ "/coursereserves/terms",
55
+ "terms",
56
+ terms_map,
57
+ "name",
58
+ "TermsMapping",
59
+ )
60
+ else:
61
+ self.terms_map = None
62
+
63
+ if departments_map:
64
+ self.departments_map = RefDataMapping(
65
+ self.folio_client,
66
+ "/coursereserves/departments",
67
+ "departments",
68
+ departments_map,
69
+ "name",
70
+ "DepartmentsMapping",
71
+ )
72
+ else:
73
+ self.departments_map = None
74
+
75
+ def store_objects(self, composite_course):
76
+ try:
77
+ self.extradata_writer.write("courselisting", composite_course[0]["courselisting"])
78
+ self.migration_report.add_general_statistics(i18n.t("Stored courselistings"))
79
+ self.extradata_writer.write("course", composite_course[0]["course"])
80
+ self.migration_report.add_general_statistics(i18n.t("Stored courses"))
81
+ if "instructors" in composite_course[0] and any(composite_course[0]["instructors"]):
82
+ for instructor in composite_course[0]["instructors"]:
83
+ self.extradata_writer.write("instructor", instructor)
84
+ self.migration_report.add_general_statistics(i18n.t("Stored instructors"))
85
+
86
+ except Exception as ee:
87
+ raise TransformationRecordFailedError(
88
+ composite_course[1], "Failed when storing", ee
89
+ ) from ee
90
+
91
+ def perform_additional_mappings(self, composite_course):
92
+ try:
93
+ # Assign deterministic ids to every object
94
+ composite_course[0]["course"]["id"] = self.get_uuid(
95
+ composite_course, FOLIONamespaces.course
96
+ )
97
+ composite_course[0]["courselisting"]["id"] = self.get_uuid(
98
+ composite_course, FOLIONamespaces.course_listing
99
+ )
100
+ if "instructors" in composite_course[0] and any(composite_course[0]["instructors"]):
101
+ for idx, instructor in enumerate(composite_course[0]["instructors"]):
102
+ instructor["id"] = self.get_uuid(
103
+ composite_course, FOLIONamespaces.instructor, idx
104
+ )
105
+ # Link instructor to course listing
106
+ instructor["courseListingId"] = composite_course[0]["courselisting"]["id"]
107
+ if self.task_configuration.look_up_instructor:
108
+ self.populate_instructor_from_users(instructor)
109
+ else:
110
+ self.migration_report.add_general_statistics(i18n.t("Missing Instructors"))
111
+
112
+ # Link course to courselisting
113
+ composite_course[0]["course"]["courseListingId"] = composite_course[0][
114
+ "courselisting"
115
+ ]["id"]
116
+
117
+ except Exception as ee:
118
+ raise TransformationRecordFailedError(
119
+ composite_course[1], "Failed when creating and linking ids", ee
120
+ ) from ee
121
+
122
+ def get_uuid(self, composite_course, object_type: FOLIONamespaces, idx: int = 0):
123
+ return str(
124
+ FolioUUID(
125
+ self.base_string_for_folio_uuid,
126
+ object_type,
127
+ composite_course[1] if idx == 0 else f"{composite_course[1]}_{idx}",
128
+ )
129
+ )
130
+
131
+ def populate_instructor_from_users(self, instructor: dict):
132
+ if instructor["userId"] not in self.user_cache:
133
+ path = "/users"
134
+ query = f'?query=(externalSystemId=="{instructor["userId"]}")'
135
+ if user := next(self.folio_client.folio_get_all(path, "users", query), None):
136
+ self.user_cache[instructor["userId"]] = user
137
+ if user := self.user_cache.get(instructor["userId"], {}):
138
+ instructor["userId"] = user.get("id", "")
139
+ instructor["barcode"] = user.get("barcode", "")
140
+ instructor["patronGroup"] = user.get("patronGroup", "")
141
+ else:
142
+ del instructor["userId"]
143
+
144
+ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_value):
145
+ if folio_prop_name == "courselisting.termId":
146
+ return self.get_mapped_ref_data_value(
147
+ self.terms_map,
148
+ legacy_item,
149
+ folio_prop_name,
150
+ index_or_id,
151
+ False,
152
+ )
153
+ elif folio_prop_name == "course.departmentId":
154
+ return self.get_mapped_ref_data_value(
155
+ self.departments_map,
156
+ legacy_item,
157
+ folio_prop_name,
158
+ index_or_id,
159
+ False,
160
+ )
161
+ elif mapped_value := super().get_prop(
162
+ legacy_item, folio_prop_name, index_or_id, schema_default_value
163
+ ):
164
+ return mapped_value
165
+ else:
166
+ self.migration_report.add("UnmappedProperties", f"{folio_prop_name}")
167
+ return ""
168
+
169
+ def get_composite_course_schema(self) -> Dict[str, Any]:
170
+ if self:
171
+ return {
172
+ "properties": {
173
+ "course": self.folio_client.get_from_github(
174
+ "folio-org", "mod-courses", "/ramls/course.json"
175
+ ),
176
+ "courselisting": self.folio_client.get_from_github(
177
+ "folio-org", "mod-courses", "/ramls/courselisting.json"
178
+ ),
179
+ "instructors": {
180
+ "type": "array",
181
+ "items": self.folio_client.get_from_github(
182
+ "folio-org", "mod-courses", "/ramls/instructor.json"
183
+ ),
184
+ },
185
+ }
186
+ }
187
+ else:
188
+ return {
189
+ "properties": {
190
+ "course": FolioClient.get_latest_from_github(
191
+ "folio-org", "mod-courses", "/ramls/course.json"
192
+ ),
193
+ "courselisting": FolioClient.get_latest_from_github(
194
+ "folio-org", "mod-courses", "/ramls/courselisting.json"
195
+ ),
196
+ "instructors": {
197
+ "type": "array",
198
+ "items": FolioClient.get_latest_from_github(
199
+ "folio-org", "mod-courses", "/ramls/instructor.json"
200
+ ),
201
+ },
202
+ }
203
+ }
@@ -1,17 +1,23 @@
1
1
  import ast
2
+ import json
3
+ import logging
2
4
 
3
- from folioclient import FolioClient
5
+ import i18n
4
6
  from folio_uuid.folio_uuid import FOLIONamespaces
5
- from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
6
- from folio_migration_tools.library_configuration import LibraryConfiguration
7
+ from folioclient import FolioClient
8
+
9
+ from folio_migration_tools.custom_exceptions import TransformationProcessError, TransformationRecordFailedError
10
+ from folio_migration_tools.library_configuration import (
11
+ FileDefinition,
12
+ LibraryConfiguration,
13
+ )
7
14
  from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
8
15
  MappingFileMapperBase,
9
16
  )
10
17
  from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
11
18
  RefDataMapping,
12
19
  )
13
- from folio_migration_tools.report_blurbs import Blurbs
14
-
20
+ from folio_migration_tools.task_configuration import AbstractTaskConfiguration
15
21
 
16
22
  class HoldingsMapper(MappingFileMapperBase):
17
23
  def __init__(
@@ -22,6 +28,7 @@ class HoldingsMapper(MappingFileMapperBase):
22
28
  call_number_type_map,
23
29
  instance_id_map,
24
30
  library_configuration: LibraryConfiguration,
31
+ task_config: AbstractTaskConfiguration,
25
32
  statistical_codes_map=None,
26
33
  ):
27
34
  holdings_schema = folio_client.get_holdings_schema()
@@ -33,6 +40,7 @@ class HoldingsMapper(MappingFileMapperBase):
33
40
  statistical_codes_map,
34
41
  FOLIONamespaces.holdings,
35
42
  library_configuration,
43
+ task_config
36
44
  )
37
45
  self.holdings_map = holdings_map
38
46
 
@@ -42,7 +50,7 @@ class HoldingsMapper(MappingFileMapperBase):
42
50
  "locations",
43
51
  location_map,
44
52
  "code",
45
- Blurbs.LocationMapping,
53
+ "LocationMapping",
46
54
  )
47
55
  if call_number_type_map:
48
56
  self.call_number_mapping = RefDataMapping(
@@ -51,71 +59,77 @@ class HoldingsMapper(MappingFileMapperBase):
51
59
  "callNumberTypes",
52
60
  call_number_type_map,
53
61
  "name",
54
- Blurbs.CallNumberTypeMapping,
62
+ "CallNumberTypeMapping",
55
63
  )
64
+ self.holdings_sources = self.get_holdings_sources()
56
65
 
57
- def get_prop(self, legacy_item, folio_prop_name, index_or_id):
58
-
59
- # Legacy contstruct
60
- if not self.use_map:
61
- return legacy_item[folio_prop_name]
62
-
63
- legacy_item_keys = self.mapped_from_legacy_data.get(folio_prop_name, [])
66
+ def get_holdings_sources(self):
67
+ res = {}
68
+ holdings_sources = list(
69
+ self.folio_client.folio_get_all("/holdings-sources", "holdingsRecordsSources")
70
+ )
71
+ logging.info("Fetched %s holdingsRecordsSources from tenant", len(holdings_sources))
72
+ res = {n["name"].upper(): n["id"] for n in holdings_sources}
73
+ if "FOLIO" not in res:
74
+ raise TransformationProcessError("", "No holdings source with name FOLIO in tenant")
75
+ if "MARC" not in res:
76
+ raise TransformationProcessError("", "No holdings source with name MARC in tenant")
77
+ logging.info(json.dumps(res, indent=4))
78
+ return res
64
79
 
65
- # IF there is a value mapped, return that one
66
- if len(legacy_item_keys) == 1 and folio_prop_name in self.mapped_from_values:
67
- value = self.mapped_from_values.get(folio_prop_name, "")
68
- self.migration_report.add(
69
- Blurbs.DefaultValuesAdded, f"{value} added to {folio_prop_name}"
70
- )
71
- return value
80
+ def perform_additional_mappings(self, legacy_ids, folio_rec, file_def):
81
+ self.handle_suppression(folio_rec, file_def)
82
+ self.map_statistical_codes(folio_rec, file_def)
83
+ self.map_statistical_code_ids(legacy_ids, folio_rec)
72
84
 
73
- legacy_values = MappingFileMapperBase.get_legacy_vals(
74
- legacy_item, legacy_item_keys
85
+ def handle_suppression(self, folio_record, file_def: FileDefinition):
86
+ folio_record["discoverySuppress"] = file_def.discovery_suppressed
87
+ self.migration_report.add(
88
+ "Suppression",
89
+ i18n.t("Suppressed from discovery") + f' = {folio_record["discoverySuppress"]}',
75
90
  )
76
- legacy_value = " ".join(legacy_values).strip()
77
91
 
92
+ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_value):
78
93
  if folio_prop_name == "permanentLocationId":
79
94
  return self.get_location_id(legacy_item, index_or_id, folio_prop_name)
80
- elif folio_prop_name == "temporaryLocationId":
81
- return self.get_location_id(legacy_item, index_or_id, folio_prop_name, True)
82
- elif folio_prop_name == "callNumber":
83
- if legacy_value.startswith("[") and len(legacy_value.split(",")) > 1:
84
- self.migration_report.add_general_statistics(
85
- "Bound-with items callnumber identified"
86
- )
87
- self.migration_report.add(
88
- Blurbs.BoundWithMappings,
89
- (
90
- f"Number of bib-level callnumbers in record: "
91
- f"{len(legacy_value.split(','))}"
92
- ),
93
- )
94
- return legacy_value.removeprefix("[").removesuffix("]")
95
95
  elif folio_prop_name == "callNumberTypeId":
96
- return self.get_call_number_type_id(
97
- legacy_item, folio_prop_name, index_or_id
98
- )
99
- elif folio_prop_name == "statisticalCodeIds":
100
- return self.get_statistical_codes(
101
- legacy_values, folio_prop_name, index_or_id
102
- )
96
+ return self.get_call_number_type_id(legacy_item, folio_prop_name, index_or_id)
97
+ # elif folio_prop_name.startswith("statisticalCodeIds"):
98
+ # return self.get_statistical_code(legacy_item, folio_prop_name, index_or_id)
99
+
100
+ mapped_value = super().get_prop(
101
+ legacy_item, folio_prop_name, index_or_id, schema_default_value
102
+ )
103
+ if folio_prop_name == "callNumber":
104
+ return self.get_call_number(mapped_value)
103
105
  elif folio_prop_name == "instanceId":
104
- return self.get_instance_ids(legacy_value, index_or_id)
105
- elif any(legacy_item_keys):
106
- if len(legacy_item_keys) > 1:
107
- self.migration_report.add(
108
- Blurbs.Details, f"{legacy_item_keys} were concatenated"
109
- )
110
- return legacy_value
106
+ return self.get_instance_ids(mapped_value, index_or_id)
107
+ elif mapped_value:
108
+ return mapped_value
111
109
  else:
112
- # edge case
110
+ self.migration_report.add("UnmappedProperties", f"{folio_prop_name}")
113
111
  return ""
114
112
 
113
+ def get_call_number(self, legacy_value):
114
+ if legacy_value.startswith("[") and len(legacy_value.split(",")) > 1:
115
+ self.migration_report.add_general_statistics(
116
+ i18n.t("Bound-with items callnumber identified")
117
+ )
118
+ self.migration_report.add(
119
+ "BoundWithMappings",
120
+ (f"Number of bib-level callnumbers in record: {len(legacy_value.split(','))}"),
121
+ )
122
+ if legacy_value.startswith("[") and len(legacy_value.split(",")) == 1:
123
+ try:
124
+ legacy_value = ast.literal_eval(str(legacy_value))[0]
125
+ except (SyntaxError, ValueError):
126
+ return legacy_value
127
+ return legacy_value
128
+
115
129
  def get_location_id(
116
130
  self, legacy_item: dict, id_or_index, folio_prop_name, prevent_default=False
117
131
  ):
118
- return self.get_mapped_value(
132
+ return self.get_mapped_ref_data_value(
119
133
  self.location_mapping,
120
134
  legacy_item,
121
135
  id_or_index,
@@ -125,10 +139,10 @@ class HoldingsMapper(MappingFileMapperBase):
125
139
 
126
140
  def get_call_number_type_id(self, legacy_item, folio_prop_name: str, id_or_index):
127
141
  if self.call_number_mapping:
128
- return self.get_mapped_value(
142
+ return self.get_mapped_ref_data_value(
129
143
  self.call_number_mapping, legacy_item, id_or_index, folio_prop_name
130
144
  )
131
- self.migration_report.add(Blurbs.CallNumberTypeMapping, "No mapping")
145
+ self.migration_report.add("CallNumberTypeMapping", i18n.t("No Call Number Type Mapping"))
132
146
  return ""
133
147
 
134
148
  def get_instance_ids(self, legacy_value: str, index_or_id: str):
@@ -136,8 +150,8 @@ class HoldingsMapper(MappingFileMapperBase):
136
150
  return_ids = []
137
151
  legacy_bib_ids = self.get_legacy_bib_ids(legacy_value, index_or_id)
138
152
  self.migration_report.add(
139
- Blurbs.BoundWithMappings,
140
- f"Number of bib records referenced in item: {len(legacy_bib_ids)}",
153
+ "BoundWithMappings",
154
+ i18n.t("Number of bib records referenced in item") + f": {len(legacy_bib_ids)}",
141
155
  )
142
156
  for legacy_instance_id in legacy_bib_ids:
143
157
  new_legacy_value = (
@@ -150,18 +164,18 @@ class HoldingsMapper(MappingFileMapperBase):
150
164
  and legacy_instance_id not in self.instance_id_map
151
165
  ):
152
166
  self.migration_report.add_general_statistics(
153
- "Records not matched to Instances"
167
+ i18n.t("Records not matched to Instances")
154
168
  )
155
169
  s = "Bib id not in instance id map."
156
170
  raise TransformationRecordFailedError(index_or_id, s, new_legacy_value)
157
171
  else:
158
172
  self.migration_report.add_general_statistics(
159
- "Records matched to Instances"
173
+ i18n.t("Records matched to Instances")
174
+ )
175
+ entry = self.instance_id_map.get(new_legacy_value, "") or self.instance_id_map.get(
176
+ legacy_instance_id
160
177
  )
161
- entry = self.instance_id_map.get(
162
- new_legacy_value, ""
163
- ) or self.instance_id_map.get(legacy_instance_id)
164
- return_ids.append(entry["folio_id"])
178
+ return_ids.append(entry[1])
165
179
  if any(return_ids):
166
180
  return return_ids
167
181
  else:
@@ -177,11 +191,11 @@ class HoldingsMapper(MappingFileMapperBase):
177
191
  new_value_len = len(new_legacy_values)
178
192
  if new_value_len > 1:
179
193
  self.migration_report.add_general_statistics(
180
- "Bound-with items identified by bib id"
194
+ i18n.t("Bound-with items identified by bib id")
181
195
  )
182
196
  self.migration_report.add(
183
- Blurbs.GeneralStatistics,
184
- "Bib ids referenced in bound-with items",
197
+ "GeneralStatistics",
198
+ i18n.t("Bib ids referenced in bound-with items"),
185
199
  new_value_len,
186
200
  )
187
201
  return new_legacy_values
@@ -190,4 +204,4 @@ class HoldingsMapper(MappingFileMapperBase):
190
204
  index_or_id,
191
205
  f"Instance ID could not get parsed to array of strings {error}",
192
206
  legacy_value,
193
- )
207
+ ) from error