folio-migration-tools 1.9.0rc11__py3-none-any.whl → 1.9.0rc13__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.
- folio_migration_tools/__main__.py +1 -2
- folio_migration_tools/library_configuration.py +21 -1
- folio_migration_tools/mapper_base.py +78 -4
- folio_migration_tools/mapping_file_transformation/courses_mapper.py +2 -1
- folio_migration_tools/mapping_file_transformation/holdings_mapper.py +8 -4
- folio_migration_tools/mapping_file_transformation/item_mapper.py +4 -11
- folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +1 -0
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +3 -19
- folio_migration_tools/mapping_file_transformation/notes_mapper.py +2 -0
- folio_migration_tools/mapping_file_transformation/order_mapper.py +4 -1
- folio_migration_tools/mapping_file_transformation/organization_mapper.py +7 -4
- folio_migration_tools/mapping_file_transformation/user_mapper.py +3 -1
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +7 -14
- folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +1 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +83 -4
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +10 -5
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +39 -33
- folio_migration_tools/migration_tasks/bibs_transformer.py +13 -3
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +42 -21
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +39 -22
- folio_migration_tools/migration_tasks/items_transformer.py +4 -3
- folio_migration_tools/migration_tasks/migration_task_base.py +22 -1
- folio_migration_tools/migration_tasks/orders_transformer.py +2 -0
- folio_migration_tools/migration_tasks/user_transformer.py +1 -0
- folio_migration_tools/transaction_migration/legacy_loan.py +2 -1
- folio_migration_tools/translations/en.json +12 -1
- {folio_migration_tools-1.9.0rc11.dist-info → folio_migration_tools-1.9.0rc13.dist-info}/METADATA +2 -2
- {folio_migration_tools-1.9.0rc11.dist-info → folio_migration_tools-1.9.0rc13.dist-info}/RECORD +31 -31
- {folio_migration_tools-1.9.0rc11.dist-info → folio_migration_tools-1.9.0rc13.dist-info}/WHEEL +1 -1
- {folio_migration_tools-1.9.0rc11.dist-info → folio_migration_tools-1.9.0rc13.dist-info}/LICENSE +0 -0
- {folio_migration_tools-1.9.0rc11.dist-info → folio_migration_tools-1.9.0rc13.dist-info}/entry_points.txt +0 -0
|
@@ -10,7 +10,6 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Dict, Generator, List
|
|
11
11
|
|
|
12
12
|
import i18n
|
|
13
|
-
import pymarc
|
|
14
13
|
from defusedxml.ElementTree import fromstring
|
|
15
14
|
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
16
15
|
from folio_uuid.folio_uuid import FolioUUID
|
|
@@ -45,11 +44,13 @@ class BibsRulesMapper(RulesMapperBase):
|
|
|
45
44
|
folio_client: FolioClient,
|
|
46
45
|
library_configuration: LibraryConfiguration,
|
|
47
46
|
task_configuration: MarcTaskConfigurationBase,
|
|
47
|
+
statistical_codes_map: Dict[str, str] = None,
|
|
48
48
|
):
|
|
49
49
|
super().__init__(
|
|
50
50
|
folio_client,
|
|
51
51
|
library_configuration,
|
|
52
52
|
task_configuration,
|
|
53
|
+
statistical_codes_map,
|
|
53
54
|
self.get_instance_schema(folio_client),
|
|
54
55
|
Conditions(folio_client, self, "bibs", library_configuration.folio_release),
|
|
55
56
|
)
|
|
@@ -144,7 +145,7 @@ class BibsRulesMapper(RulesMapperBase):
|
|
|
144
145
|
self.report_folio_mapping(clean_folio_instance, self.schema)
|
|
145
146
|
return [clean_folio_instance]
|
|
146
147
|
|
|
147
|
-
def simple_bib_map(self,
|
|
148
|
+
def simple_bib_map(self, folio_instance: dict, marc_record: Record, ignored_subsequent_fields: set, legacy_ids: List[str]):
|
|
148
149
|
"""
|
|
149
150
|
This method applies a much simplified MARC-to-instance
|
|
150
151
|
mapping to create a minimal FOLIO Instance record to be
|
|
@@ -152,7 +153,7 @@ class BibsRulesMapper(RulesMapperBase):
|
|
|
152
153
|
than creating SRS records during transformation.
|
|
153
154
|
|
|
154
155
|
Args:
|
|
155
|
-
|
|
156
|
+
folio_instance (dict): _description_
|
|
156
157
|
marc_record (Record): _description_
|
|
157
158
|
legacy_ids (List[str]): _description_
|
|
158
159
|
file_def (FileDefinition): _description_
|
|
@@ -169,9 +170,10 @@ class BibsRulesMapper(RulesMapperBase):
|
|
|
169
170
|
if not main_entry_fields:
|
|
170
171
|
main_entry_fields += marc_record.get_fields("700", "710", "711", "730")
|
|
171
172
|
main_entry_fields.sort(key=lambda x: int(x.tag))
|
|
172
|
-
|
|
173
|
+
if main_entry_fields:
|
|
174
|
+
self.process_marc_field(folio_instance, main_entry_fields[0], ignored_subsequent_fields, legacy_ids)
|
|
173
175
|
try:
|
|
174
|
-
self.process_marc_field(
|
|
176
|
+
self.process_marc_field(folio_instance, marc_record['245'], ignored_subsequent_fields, legacy_ids)
|
|
175
177
|
except KeyError:
|
|
176
178
|
raise TransformationRecordFailedError(
|
|
177
179
|
legacy_ids,
|
|
@@ -205,6 +207,9 @@ class BibsRulesMapper(RulesMapperBase):
|
|
|
205
207
|
folio_instance["modeOfIssuanceId"] = self.get_mode_of_issuance_id(marc_record, legacy_ids)
|
|
206
208
|
self.handle_languages(folio_instance, marc_record, legacy_ids)
|
|
207
209
|
self.handle_suppression(folio_instance, file_def)
|
|
210
|
+
# Map statistical codes from MARC and FileDefinition, then map the IDs
|
|
211
|
+
self.map_statistical_codes(folio_instance, file_def, marc_record)
|
|
212
|
+
self.map_statistical_code_ids(legacy_ids, folio_instance)
|
|
208
213
|
self.handle_holdings(marc_record)
|
|
209
214
|
if prec_titles := folio_instance.get("precedingTitles", []):
|
|
210
215
|
self.migration_report.add("PrecedingSuccedingTitles", f"{len(prec_titles)}")
|
|
@@ -7,6 +7,7 @@ import i18n
|
|
|
7
7
|
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
8
8
|
from folio_uuid.folio_uuid import FolioUUID
|
|
9
9
|
from folioclient import FolioClient
|
|
10
|
+
from pymarc import Optional
|
|
10
11
|
from pymarc.field import Field
|
|
11
12
|
from pymarc.record import Record
|
|
12
13
|
|
|
@@ -39,27 +40,28 @@ class RulesMapperHoldings(RulesMapperBase):
|
|
|
39
40
|
task_configuration,
|
|
40
41
|
library_configuration: LibraryConfiguration,
|
|
41
42
|
parent_id_map: dict,
|
|
42
|
-
|
|
43
|
+
boundwith_relationship_map_rows: List[Dict],
|
|
44
|
+
statistical_codes_map: Optional[Dict] = None,
|
|
43
45
|
):
|
|
44
|
-
self.task_configuration = task_configuration
|
|
45
46
|
self.conditions = Conditions(
|
|
46
47
|
folio_client,
|
|
47
48
|
self,
|
|
48
49
|
"holdings",
|
|
49
50
|
library_configuration.folio_release,
|
|
50
|
-
|
|
51
|
+
task_configuration.default_call_number_type_name,
|
|
51
52
|
)
|
|
52
53
|
self.folio = folio_client
|
|
53
54
|
super().__init__(
|
|
54
55
|
folio_client,
|
|
55
56
|
library_configuration,
|
|
56
57
|
task_configuration,
|
|
58
|
+
statistical_codes_map,
|
|
57
59
|
self.fetch_holdings_schema(folio_client),
|
|
58
60
|
self.conditions,
|
|
59
61
|
parent_id_map,
|
|
60
62
|
)
|
|
61
|
-
self.boundwith_relationship_map = self.setup_boundwith_relationship_map(
|
|
62
|
-
|
|
63
|
+
self.boundwith_relationship_map: Dict = self.setup_boundwith_relationship_map(
|
|
64
|
+
boundwith_relationship_map_rows
|
|
63
65
|
)
|
|
64
66
|
self.location_map = self.validate_location_map(
|
|
65
67
|
location_map,
|
|
@@ -297,6 +299,10 @@ class RulesMapperHoldings(RulesMapperBase):
|
|
|
297
299
|
"",
|
|
298
300
|
)
|
|
299
301
|
self.handle_suppression(folio_holding, file_def, True)
|
|
302
|
+
# First, map statistical codes from MARC fields and FileDefinitions to FOLIO statistical codes.
|
|
303
|
+
# Then, convert the mapped statistical codes to their corresponding code IDs.
|
|
304
|
+
self.map_statistical_codes(folio_holding, file_def, marc_record)
|
|
305
|
+
self.map_statistical_code_ids(legacy_ids, folio_holding)
|
|
300
306
|
self.set_source_id(self.create_source_records, folio_holding, self.holdingssources, file_def)
|
|
301
307
|
|
|
302
308
|
def pick_first_location_if_many(self, folio_holding: Dict, legacy_ids: List[str]):
|
|
@@ -402,20 +408,20 @@ class RulesMapperHoldings(RulesMapperBase):
|
|
|
402
408
|
marc_record (Record): PyMARC record
|
|
403
409
|
folio_holding (Dict): FOLIO holdings record
|
|
404
410
|
"""
|
|
405
|
-
holdings_note_type_tuple = self.conditions.get_ref_data_tuple_by_name(
|
|
406
|
-
self.folio.holding_note_types, "holding_note_types", self.task_configuration.mfhd_mrk_note_type
|
|
407
|
-
)
|
|
408
|
-
try:
|
|
409
|
-
holdings_note_type_id = holdings_note_type_tuple[0]
|
|
410
|
-
except Exception as ee:
|
|
411
|
-
logging.error(ee)
|
|
412
|
-
raise TransformationRecordFailedError(
|
|
413
|
-
legacy_ids,
|
|
414
|
-
f'Holdings note type mapping error.\tNote type name: {self.task_configuration.mfhd_mrk_note_type}\t'
|
|
415
|
-
f"Note type not found in FOLIO.",
|
|
416
|
-
self.task_configuration.mfhd_mrk_note_type,
|
|
417
|
-
) from ee
|
|
418
411
|
if self.task_configuration.include_mfhd_mrk_as_note:
|
|
412
|
+
holdings_note_type_tuple = self.conditions.get_ref_data_tuple_by_name(
|
|
413
|
+
self.folio.holding_note_types, "holding_note_types", self.task_configuration.mfhd_mrk_note_type
|
|
414
|
+
)
|
|
415
|
+
try:
|
|
416
|
+
holdings_note_type_id = holdings_note_type_tuple[0]
|
|
417
|
+
except Exception as ee:
|
|
418
|
+
logging.error(ee)
|
|
419
|
+
raise TransformationRecordFailedError(
|
|
420
|
+
legacy_ids,
|
|
421
|
+
f'Holdings note type mapping error.\tNote type name: {self.task_configuration.mfhd_mrk_note_type}\t'
|
|
422
|
+
f"Note type not found in FOLIO.",
|
|
423
|
+
self.task_configuration.mfhd_mrk_note_type,
|
|
424
|
+
) from ee
|
|
419
425
|
folio_holding["notes"] = folio_holding.get("notes", []) + [
|
|
420
426
|
{
|
|
421
427
|
"note": str(marc_record),
|
|
@@ -433,20 +439,20 @@ class RulesMapperHoldings(RulesMapperBase):
|
|
|
433
439
|
marc_record (Record): PyMARC record
|
|
434
440
|
folio_holding (Dict): FOLIO holdings record
|
|
435
441
|
"""
|
|
436
|
-
holdings_note_type_tuple = self.conditions.get_ref_data_tuple_by_name(
|
|
437
|
-
self.folio.holding_note_types, "holding_note_types", self.task_configuration.mfhd_mrc_note_type
|
|
438
|
-
)
|
|
439
|
-
try:
|
|
440
|
-
holdings_note_type_id = holdings_note_type_tuple[0]
|
|
441
|
-
except Exception as ee:
|
|
442
|
-
logging.error(ee)
|
|
443
|
-
raise TransformationRecordFailedError(
|
|
444
|
-
legacy_ids,
|
|
445
|
-
f'Holdings note type mapping error.\tNote type name: {self.task_configuration.mfhd_mrc_note_type}\t'
|
|
446
|
-
f"Note type not found in FOLIO.",
|
|
447
|
-
self.task_configuration.mfhd_mrc_note_type,
|
|
448
|
-
) from ee
|
|
449
442
|
if self.task_configuration.include_mfhd_mrc_as_note:
|
|
443
|
+
holdings_note_type_tuple = self.conditions.get_ref_data_tuple_by_name(
|
|
444
|
+
self.folio.holding_note_types, "holding_note_types", self.task_configuration.mfhd_mrc_note_type
|
|
445
|
+
)
|
|
446
|
+
try:
|
|
447
|
+
holdings_note_type_id = holdings_note_type_tuple[0]
|
|
448
|
+
except Exception as ee:
|
|
449
|
+
logging.error(ee)
|
|
450
|
+
raise TransformationRecordFailedError(
|
|
451
|
+
legacy_ids,
|
|
452
|
+
f'Holdings note type mapping error.\tNote type name: {self.task_configuration.mfhd_mrc_note_type}\t'
|
|
453
|
+
f"Note type not found in FOLIO.",
|
|
454
|
+
self.task_configuration.mfhd_mrc_note_type,
|
|
455
|
+
) from ee
|
|
450
456
|
folio_holding["notes"] = folio_holding.get("notes", []) + [
|
|
451
457
|
{
|
|
452
458
|
"note": marc_record.as_marc().decode("utf-8"),
|
|
@@ -582,7 +588,7 @@ class RulesMapperHoldings(RulesMapperBase):
|
|
|
582
588
|
"", "Column BIB_ID missing from Boundwith relationship map", ""
|
|
583
589
|
)
|
|
584
590
|
|
|
585
|
-
def setup_boundwith_relationship_map(self,
|
|
591
|
+
def setup_boundwith_relationship_map(self, boundwith_relationship_map_list: List[Dict]):
|
|
586
592
|
"""
|
|
587
593
|
Creates a map of MFHD_ID to BIB_ID for boundwith relationships.
|
|
588
594
|
|
|
@@ -597,7 +603,7 @@ class RulesMapperHoldings(RulesMapperBase):
|
|
|
597
603
|
TransformationRecordFailedError: If BIB_ID is not in the instance id map.
|
|
598
604
|
"""
|
|
599
605
|
new_map = {}
|
|
600
|
-
for idx, entry in enumerate(
|
|
606
|
+
for idx, entry in enumerate(boundwith_relationship_map_list):
|
|
601
607
|
self.verity_boundwith_map_entry(entry)
|
|
602
608
|
mfhd_uuid = str(
|
|
603
609
|
FolioUUID(
|
|
@@ -7,8 +7,6 @@ from pydantic import Field
|
|
|
7
7
|
|
|
8
8
|
from folio_migration_tools.helper import Helper
|
|
9
9
|
from folio_migration_tools.library_configuration import (
|
|
10
|
-
FileDefinition,
|
|
11
|
-
HridHandling,
|
|
12
10
|
IlsFlavour,
|
|
13
11
|
LibraryConfiguration,
|
|
14
12
|
)
|
|
@@ -116,11 +114,23 @@ class BibsTransformer(MigrationTaskBase):
|
|
|
116
114
|
use_logging: bool = True,
|
|
117
115
|
):
|
|
118
116
|
super().__init__(library_config, task_config, folio_client, use_logging)
|
|
117
|
+
self.task_config = task_config
|
|
118
|
+
self.task_configuration = self.task_config
|
|
119
|
+
if self.task_config.statistical_codes_map_file_name:
|
|
120
|
+
statcode_mapping = self.load_ref_data_mapping_file(
|
|
121
|
+
"statisticalCodeIds",
|
|
122
|
+
self.folder_structure.mapping_files_folder
|
|
123
|
+
/ self.task_config.statistical_codes_map_file_name,
|
|
124
|
+
[],
|
|
125
|
+
False,
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
statcode_mapping = None
|
|
119
129
|
self.processor: MarcFileProcessor
|
|
120
130
|
self.check_source_files(
|
|
121
131
|
self.folder_structure.legacy_records_folder, self.task_configuration.files
|
|
122
132
|
)
|
|
123
|
-
self.mapper = BibsRulesMapper(self.folio_client, library_config, self.task_configuration)
|
|
133
|
+
self.mapper = BibsRulesMapper(self.folio_client, library_config, self.task_configuration, statcode_mapping)
|
|
124
134
|
self.bib_ids: set = set()
|
|
125
135
|
if (
|
|
126
136
|
self.task_configuration.reset_hrid_settings
|
|
@@ -160,6 +160,16 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
160
160
|
),
|
|
161
161
|
),
|
|
162
162
|
] = True
|
|
163
|
+
statistical_codes_map_file_name: Annotated[
|
|
164
|
+
Optional[str],
|
|
165
|
+
Field(
|
|
166
|
+
title="Statistical code map file name",
|
|
167
|
+
description=(
|
|
168
|
+
"Path to the file containing the mapping of statistical codes. "
|
|
169
|
+
"The file should be in TSV format with legacy_stat_code and folio_code columns."
|
|
170
|
+
),
|
|
171
|
+
),
|
|
172
|
+
] = ""
|
|
163
173
|
|
|
164
174
|
@staticmethod
|
|
165
175
|
def get_object_type() -> FOLIONamespaces:
|
|
@@ -174,16 +184,27 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
174
184
|
):
|
|
175
185
|
super().__init__(library_config, task_config, folio_client, use_logging)
|
|
176
186
|
self.fallback_holdings_type = None
|
|
187
|
+
self.folio_keys, self.holdings_field_map = self.load_mapped_fields()
|
|
188
|
+
if any(k for k in self.folio_keys if k.startswith("statisticalCodeIds")):
|
|
189
|
+
statcode_mapping = self.load_ref_data_mapping_file(
|
|
190
|
+
"statisticalCodeIds",
|
|
191
|
+
self.folder_structure.mapping_files_folder
|
|
192
|
+
/ self.task_configuration.statistical_codes_map_file_name,
|
|
193
|
+
self.folio_keys,
|
|
194
|
+
False,
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
statcode_mapping = None
|
|
177
198
|
try:
|
|
178
|
-
self.task_config = task_config
|
|
179
199
|
self.bound_with_keys = set()
|
|
180
200
|
self.mapper = HoldingsMapper(
|
|
181
201
|
self.folio_client,
|
|
182
|
-
self.
|
|
202
|
+
self.holdings_field_map,
|
|
183
203
|
self.load_location_map(),
|
|
184
204
|
self.load_call_number_type_map(),
|
|
185
205
|
self.load_instance_id_map(True),
|
|
186
206
|
library_config,
|
|
207
|
+
statcode_mapping,
|
|
187
208
|
)
|
|
188
209
|
self.holdings = {}
|
|
189
210
|
self.total_records = 0
|
|
@@ -196,19 +217,19 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
196
217
|
logging.info("%s\tholdings types in tenant", len(self.holdings_types))
|
|
197
218
|
self.validate_merge_criterias()
|
|
198
219
|
self.check_source_files(
|
|
199
|
-
self.folder_structure.data_folder / "items", self.
|
|
220
|
+
self.folder_structure.data_folder / "items", self.task_configuration.files
|
|
200
221
|
)
|
|
201
222
|
self.fallback_holdings_type = next(
|
|
202
223
|
h
|
|
203
224
|
for h in self.holdings_types
|
|
204
|
-
if h["id"] == self.
|
|
225
|
+
if h["id"] == self.task_configuration.fallback_holdings_type_id
|
|
205
226
|
)
|
|
206
227
|
if not self.fallback_holdings_type:
|
|
207
228
|
raise TransformationProcessError(
|
|
208
229
|
"",
|
|
209
230
|
(
|
|
210
231
|
"Holdings type with ID "
|
|
211
|
-
f"{self.
|
|
232
|
+
f"{self.task_configuration.fallback_holdings_type_id} "
|
|
212
233
|
"not found in FOLIO."
|
|
213
234
|
),
|
|
214
235
|
)
|
|
@@ -216,15 +237,15 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
216
237
|
"%s will be used as default holdings type",
|
|
217
238
|
self.fallback_holdings_type["name"],
|
|
218
239
|
)
|
|
219
|
-
if any(self.
|
|
220
|
-
for file_name in self.
|
|
240
|
+
if any(self.task_configuration.previously_generated_holdings_files):
|
|
241
|
+
for file_name in self.task_configuration.previously_generated_holdings_files:
|
|
221
242
|
logging.info("Processing %s", file_name)
|
|
222
243
|
self.holdings.update(
|
|
223
244
|
HoldingsHelper.load_previously_generated_holdings(
|
|
224
245
|
self.folder_structure.results_folder / file_name,
|
|
225
|
-
self.
|
|
246
|
+
self.task_configuration.holdings_merge_criteria,
|
|
226
247
|
self.mapper.migration_report,
|
|
227
|
-
self.
|
|
248
|
+
self.task_configuration.holdings_type_uuid_for_boundwiths,
|
|
228
249
|
)
|
|
229
250
|
)
|
|
230
251
|
|
|
@@ -260,7 +281,7 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
260
281
|
def load_call_number_type_map(self):
|
|
261
282
|
with open(
|
|
262
283
|
self.folder_structure.mapping_files_folder
|
|
263
|
-
/ self.
|
|
284
|
+
/ self.task_configuration.call_number_type_map_file_name,
|
|
264
285
|
"r",
|
|
265
286
|
) as callnumber_type_map_f:
|
|
266
287
|
return self.load_ref_data_map_from_file(
|
|
@@ -269,7 +290,7 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
269
290
|
|
|
270
291
|
def load_location_map(self):
|
|
271
292
|
with open(
|
|
272
|
-
self.folder_structure.mapping_files_folder / self.
|
|
293
|
+
self.folder_structure.mapping_files_folder / self.task_configuration.location_map_file_name
|
|
273
294
|
) as location_map_f:
|
|
274
295
|
return self.load_ref_data_map_from_file(
|
|
275
296
|
location_map_f, "Found %s rows in location map"
|
|
@@ -283,7 +304,7 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
283
304
|
|
|
284
305
|
def load_mapped_fields(self):
|
|
285
306
|
with open(
|
|
286
|
-
self.folder_structure.mapping_files_folder / self.
|
|
307
|
+
self.folder_structure.mapping_files_folder / self.task_configuration.holdings_map_file_name
|
|
287
308
|
) as holdings_mapper_f:
|
|
288
309
|
holdings_map = json.load(holdings_mapper_f)
|
|
289
310
|
logging.info("%s fields in holdings mapping file map", len(holdings_map["data"]))
|
|
@@ -294,11 +315,11 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
294
315
|
"%s mapped fields in holdings mapping file map",
|
|
295
316
|
len(list(mapped_fields)),
|
|
296
317
|
)
|
|
297
|
-
return holdings_map
|
|
318
|
+
return mapped_fields, holdings_map
|
|
298
319
|
|
|
299
320
|
def do_work(self):
|
|
300
321
|
logging.info("Starting....")
|
|
301
|
-
for file_def in self.
|
|
322
|
+
for file_def in self.task_configuration.files:
|
|
302
323
|
logging.info("Processing %s", file_def.file_name)
|
|
303
324
|
try:
|
|
304
325
|
self.process_single_file(file_def)
|
|
@@ -311,7 +332,7 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
311
332
|
print(f"\n{error_str}\nHalting")
|
|
312
333
|
sys.exit(1)
|
|
313
334
|
logging.info(
|
|
314
|
-
f"processed {self.total_records:,} records in {len(self.
|
|
335
|
+
f"processed {self.total_records:,} records in {len(self.task_configuration.files)} files"
|
|
315
336
|
)
|
|
316
337
|
|
|
317
338
|
def wrap_up(self):
|
|
@@ -357,8 +378,8 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
357
378
|
holdings_schema = self.folio_client.get_holdings_schema()
|
|
358
379
|
properties = holdings_schema["properties"].keys()
|
|
359
380
|
logging.info(properties)
|
|
360
|
-
logging.info(self.
|
|
361
|
-
res = [mc for mc in self.
|
|
381
|
+
logging.info(self.task_configuration.holdings_merge_criteria)
|
|
382
|
+
res = [mc for mc in self.task_configuration.holdings_merge_criteria if mc not in properties]
|
|
362
383
|
if any(res):
|
|
363
384
|
logging.critical(
|
|
364
385
|
(
|
|
@@ -426,7 +447,7 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
426
447
|
raise TransformationRecordFailedError(legacy_id, "No instance id in parsed record", "")
|
|
427
448
|
|
|
428
449
|
for folio_holding in holdings_from_row:
|
|
429
|
-
self.mapper.perform_additional_mappings(folio_holding, file_def)
|
|
450
|
+
self.mapper.perform_additional_mappings(legacy_id, folio_holding, file_def)
|
|
430
451
|
self.merge_holding_in(folio_holding, all_instance_ids, legacy_id)
|
|
431
452
|
self.mapper.report_folio_mapping(folio_holding, self.mapper.schema)
|
|
432
453
|
|
|
@@ -436,7 +457,7 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
436
457
|
self.mapper.create_bound_with_holdings(
|
|
437
458
|
folio_holding,
|
|
438
459
|
folio_holding["instanceId"],
|
|
439
|
-
self.
|
|
460
|
+
self.task_configuration.holdings_type_uuid_for_boundwiths,
|
|
440
461
|
)
|
|
441
462
|
)
|
|
442
463
|
|
|
@@ -480,9 +501,9 @@ class HoldingsCsvTransformer(MigrationTaskBase):
|
|
|
480
501
|
# Regular holding. Merge according to criteria
|
|
481
502
|
new_holding_key = HoldingsHelper.to_key(
|
|
482
503
|
incoming_holding,
|
|
483
|
-
self.
|
|
504
|
+
self.task_configuration.holdings_merge_criteria,
|
|
484
505
|
self.mapper.migration_report,
|
|
485
|
-
self.
|
|
506
|
+
self.task_configuration.holdings_type_uuid_for_boundwiths,
|
|
486
507
|
)
|
|
487
508
|
if self.holdings.get(new_holding_key, None):
|
|
488
509
|
self.mapper.migration_report.add_general_statistics(
|
|
@@ -216,7 +216,16 @@ class HoldingsMarcTransformer(MigrationTaskBase):
|
|
|
216
216
|
):
|
|
217
217
|
csv.register_dialect("tsv", delimiter="\t")
|
|
218
218
|
super().__init__(library_config, task_config, folio_client, use_logging)
|
|
219
|
-
self.
|
|
219
|
+
if self.task_configuration.statistical_codes_map_file_name:
|
|
220
|
+
statcode_mapping = self.load_ref_data_mapping_file(
|
|
221
|
+
"statisticalCodeIds",
|
|
222
|
+
self.folder_structure.mapping_files_folder
|
|
223
|
+
/ self.task_configuration.statistical_codes_map_file_name,
|
|
224
|
+
[],
|
|
225
|
+
False,
|
|
226
|
+
)
|
|
227
|
+
else:
|
|
228
|
+
statcode_mapping = None
|
|
220
229
|
self.holdings_types = list(
|
|
221
230
|
self.folio_client.folio_get_all("/holdings-types", "holdingsTypes")
|
|
222
231
|
)
|
|
@@ -224,7 +233,7 @@ class HoldingsMarcTransformer(MigrationTaskBase):
|
|
|
224
233
|
(
|
|
225
234
|
h
|
|
226
235
|
for h in self.holdings_types
|
|
227
|
-
if h["id"] == self.
|
|
236
|
+
if h["id"] == self.task_configuration.fallback_holdings_type_id
|
|
228
237
|
),
|
|
229
238
|
{"name": ""},
|
|
230
239
|
)
|
|
@@ -232,7 +241,7 @@ class HoldingsMarcTransformer(MigrationTaskBase):
|
|
|
232
241
|
raise TransformationProcessError(
|
|
233
242
|
"",
|
|
234
243
|
(
|
|
235
|
-
f"Holdings type with ID {self.
|
|
244
|
+
f"Holdings type with ID {self.task_configuration.fallback_holdings_type_id}"
|
|
236
245
|
" not found in FOLIO."
|
|
237
246
|
),
|
|
238
247
|
)
|
|
@@ -242,39 +251,47 @@ class HoldingsMarcTransformer(MigrationTaskBase):
|
|
|
242
251
|
)
|
|
243
252
|
|
|
244
253
|
# Load Boundwith relationship map
|
|
245
|
-
self.
|
|
246
|
-
if self.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
self.boundwith_relationship_map_rows = []
|
|
255
|
+
if self.task_configuration.boundwith_relationship_file_path:
|
|
256
|
+
try:
|
|
257
|
+
with open(
|
|
258
|
+
self.folder_structure.legacy_records_folder
|
|
259
|
+
/ self.task_configuration.boundwith_relationship_file_path
|
|
260
|
+
) as boundwith_relationship_file:
|
|
261
|
+
self.boundwith_relationship_map_rows = list(
|
|
262
|
+
csv.DictReader(boundwith_relationship_file, dialect="tsv")
|
|
263
|
+
)
|
|
264
|
+
logging.info(
|
|
265
|
+
"Rows in Bound with relationship map: %s",
|
|
266
|
+
len(self.boundwith_relationship_map_rows),
|
|
267
|
+
)
|
|
268
|
+
except FileNotFoundError:
|
|
269
|
+
raise TransformationProcessError(
|
|
270
|
+
"",
|
|
271
|
+
i18n.t("Provided boundwith relationship file not found"),
|
|
272
|
+
self.task_configuration.boundwith_relationship_file_path,
|
|
253
273
|
)
|
|
254
|
-
logging.info(
|
|
255
|
-
"Rows in Bound with relationship map: %s",
|
|
256
|
-
len(self.boundwith_relationship_map),
|
|
257
|
-
)
|
|
258
274
|
|
|
259
275
|
location_map_path = (
|
|
260
276
|
self.folder_structure.mapping_files_folder
|
|
261
|
-
/ self.
|
|
277
|
+
/ self.task_configuration.location_map_file_name
|
|
262
278
|
)
|
|
263
279
|
with open(location_map_path) as location_map_file:
|
|
264
280
|
self.location_map = list(csv.DictReader(location_map_file, dialect="tsv"))
|
|
265
281
|
logging.info("Locations in map: %s", len(self.location_map))
|
|
266
282
|
|
|
267
283
|
self.check_source_files(
|
|
268
|
-
self.folder_structure.legacy_records_folder, self.
|
|
284
|
+
self.folder_structure.legacy_records_folder, self.task_configuration.files
|
|
269
285
|
)
|
|
270
286
|
self.instance_id_map = self.load_instance_id_map(True)
|
|
271
287
|
self.mapper = RulesMapperHoldings(
|
|
272
288
|
self.folio_client,
|
|
273
289
|
self.location_map,
|
|
274
|
-
self.
|
|
290
|
+
self.task_configuration,
|
|
275
291
|
self.library_configuration,
|
|
276
292
|
self.instance_id_map,
|
|
277
|
-
self.
|
|
293
|
+
self.boundwith_relationship_map_rows,
|
|
294
|
+
statcode_mapping
|
|
278
295
|
)
|
|
279
296
|
self.add_supplemental_mfhd_mappings()
|
|
280
297
|
if (
|
|
@@ -286,12 +303,12 @@ class HoldingsMarcTransformer(MigrationTaskBase):
|
|
|
286
303
|
logging.info("Init done")
|
|
287
304
|
|
|
288
305
|
def add_supplemental_mfhd_mappings(self):
|
|
289
|
-
if self.
|
|
306
|
+
if self.task_configuration.supplemental_mfhd_mapping_rules_file:
|
|
290
307
|
try:
|
|
291
308
|
with open(
|
|
292
309
|
(
|
|
293
310
|
self.folder_structure.mapping_files_folder
|
|
294
|
-
/ self.
|
|
311
|
+
/ self.task_configuration.supplemental_mfhd_mapping_rules_file
|
|
295
312
|
),
|
|
296
313
|
"r",
|
|
297
314
|
) as new_rules_file:
|
|
@@ -306,7 +323,7 @@ class HoldingsMarcTransformer(MigrationTaskBase):
|
|
|
306
323
|
raise TransformationProcessError(
|
|
307
324
|
"",
|
|
308
325
|
"Provided supplemental MFHD mapping rules file not found",
|
|
309
|
-
self.
|
|
326
|
+
self.task_configuration.supplemental_mfhd_mapping_rules_file,
|
|
310
327
|
)
|
|
311
328
|
else:
|
|
312
329
|
new_rules = {}
|
|
@@ -124,10 +124,10 @@ class ItemsTransformer(MigrationTaskBase):
|
|
|
124
124
|
statistical_codes_map_file_name: Annotated[
|
|
125
125
|
Optional[str],
|
|
126
126
|
Field(
|
|
127
|
-
title="Statistical
|
|
127
|
+
title="Statistical code map file name",
|
|
128
128
|
description=(
|
|
129
|
-
"
|
|
130
|
-
"
|
|
129
|
+
"Path to the file containing the mapping of statistical codes. "
|
|
130
|
+
"The file should be in TSV format with legacy_stat_code and folio_code columns."
|
|
131
131
|
),
|
|
132
132
|
),
|
|
133
133
|
] = ""
|
|
@@ -205,6 +205,7 @@ class ItemsTransformer(MigrationTaskBase):
|
|
|
205
205
|
csv.register_dialect("tsv", delimiter="\t")
|
|
206
206
|
super().__init__(library_config, task_config, folio_client, use_logging)
|
|
207
207
|
self.task_config = task_config
|
|
208
|
+
self.task_configuration = self.task_config
|
|
208
209
|
self.check_source_files(
|
|
209
210
|
self.folder_structure.legacy_records_folder, self.task_config.files
|
|
210
211
|
)
|
|
@@ -455,7 +455,7 @@ class MigrationTaskBase:
|
|
|
455
455
|
logging.info("No mapping setup for %s", folio_property_name)
|
|
456
456
|
logging.info("%s will have default mapping if any ", folio_property_name)
|
|
457
457
|
logging.info(
|
|
458
|
-
"Add a file named %s and add the field to the
|
|
458
|
+
"Add a file named %s and add the field to the field mapping json file.",
|
|
459
459
|
map_file_path,
|
|
460
460
|
)
|
|
461
461
|
return None
|
|
@@ -522,6 +522,27 @@ class MarcTaskConfigurationBase(task_configuration.AbstractTaskConfiguration):
|
|
|
522
522
|
),
|
|
523
523
|
),
|
|
524
524
|
] = False
|
|
525
|
+
statistical_codes_map_file_name: Annotated[
|
|
526
|
+
Optional[str],
|
|
527
|
+
Field(
|
|
528
|
+
title="Statistical code map file name",
|
|
529
|
+
description=(
|
|
530
|
+
"Path to the file containing the mapping of statistical codes. "
|
|
531
|
+
"The file should be in TSV format with legacy_stat_code and folio_code columns."
|
|
532
|
+
),
|
|
533
|
+
),
|
|
534
|
+
] = ""
|
|
535
|
+
statistical_code_mapping_fields: Annotated[
|
|
536
|
+
List[str],
|
|
537
|
+
Field(
|
|
538
|
+
title="Statistical code mapping fields",
|
|
539
|
+
description=(
|
|
540
|
+
"List of fields + subfields to be used for mapping statistical codes. "
|
|
541
|
+
"Subfields should be delimited by a \"$\" (eg. 907$a). Single repeating subfields "
|
|
542
|
+
"will be treated as unique values. Multiple subfields will be concatenated together with a space."
|
|
543
|
+
),
|
|
544
|
+
),
|
|
545
|
+
] = []
|
|
525
546
|
|
|
526
547
|
class ExcludeLevelFilter(logging.Filter):
|
|
527
548
|
def __init__(self, level):
|
|
@@ -157,6 +157,7 @@ class OrdersTransformer(MigrationTaskBase):
|
|
|
157
157
|
super().__init__(library_config, task_config, folio_client, use_logging)
|
|
158
158
|
self.object_type_name = self.get_object_type().name
|
|
159
159
|
self.task_config = task_config
|
|
160
|
+
self.task_configuration = self.task_config
|
|
160
161
|
self.files = self.list_source_files()
|
|
161
162
|
self.total_records = 0
|
|
162
163
|
self.current_folio_record: dict = {}
|
|
@@ -175,6 +176,7 @@ class OrdersTransformer(MigrationTaskBase):
|
|
|
175
176
|
self.mapper = CompositeOrderMapper(
|
|
176
177
|
self.folio_client,
|
|
177
178
|
self.library_configuration,
|
|
179
|
+
self.task_configuration,
|
|
178
180
|
self.orders_map,
|
|
179
181
|
self.load_id_map(self.folder_structure.organizations_id_map_path, True),
|
|
180
182
|
self.load_instance_id_map(True),
|
|
@@ -119,6 +119,7 @@ class UserTransformer(MigrationTaskBase):
|
|
|
119
119
|
):
|
|
120
120
|
super().__init__(library_config, task_config, folio_client, use_logging)
|
|
121
121
|
self.task_config = task_config
|
|
122
|
+
self.task_configuration = self.task_config
|
|
122
123
|
self.total_records = 0
|
|
123
124
|
|
|
124
125
|
self.user_map = self.setup_records_map(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import i18n
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from zoneinfo import ZoneInfo
|
|
5
6
|
|
|
@@ -123,7 +124,7 @@ class LegacyLoan(object):
|
|
|
123
124
|
if self.out_date.hour == 0:
|
|
124
125
|
self.out_date = self.out_date.replace(hour=0, minute=1)
|
|
125
126
|
if self.due_date <= self.out_date:
|
|
126
|
-
raise TransformationProcessError(self.row, "Due date is before out date, or date information is missing from both", json.dumps(self.legacy_loan_dict, indent=2))
|
|
127
|
+
raise TransformationProcessError(self.row, i18n.t("Due date is before out date, or date information is missing from both"), json.dumps(self.legacy_loan_dict, indent=2))
|
|
127
128
|
|
|
128
129
|
def to_dict(self):
|
|
129
130
|
return {
|