folio-migration-tools 1.9.0rc3__py3-none-any.whl → 1.9.0rc5__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/mapping_file_transformation/order_mapper.py +8 -4
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +3 -2
- folio_migration_tools/migration_tasks/batch_poster.py +90 -8
- folio_migration_tools/migration_tasks/courses_migrator.py +54 -8
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +102 -14
- folio_migration_tools/migration_tasks/items_transformer.py +133 -20
- folio_migration_tools/migration_tasks/loans_migrator.py +61 -9
- folio_migration_tools/migration_tasks/migration_task_base.py +104 -11
- folio_migration_tools/migration_tasks/orders_transformer.py +107 -14
- folio_migration_tools/migration_tasks/organization_transformer.py +79 -14
- folio_migration_tools/migration_tasks/requests_migrator.py +56 -7
- folio_migration_tools/migration_tasks/reserves_migrator.py +26 -4
- folio_migration_tools/migration_tasks/user_transformer.py +88 -18
- folio_migration_tools/transaction_migration/legacy_loan.py +13 -1
- folio_migration_tools/transaction_migration/legacy_reserve.py +3 -5
- {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/METADATA +1 -1
- {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/RECORD +20 -20
- {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/WHEEL +1 -1
- {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/LICENSE +0 -0
- {folio_migration_tools-1.9.0rc3.dist-info → folio_migration_tools-1.9.0rc5.dist-info}/entry_points.txt +0 -0
|
@@ -6,9 +6,10 @@ import sys
|
|
|
6
6
|
import time
|
|
7
7
|
import traceback
|
|
8
8
|
from datetime import datetime, timedelta
|
|
9
|
-
from typing import Optional
|
|
9
|
+
from typing import Annotated, Optional
|
|
10
10
|
from urllib.error import HTTPError
|
|
11
11
|
from zoneinfo import ZoneInfo
|
|
12
|
+
from pydantic import Field
|
|
12
13
|
|
|
13
14
|
import i18n
|
|
14
15
|
from dateutil import parser as du_parser
|
|
@@ -35,13 +36,64 @@ from folio_migration_tools.transaction_migration.transaction_result import (
|
|
|
35
36
|
|
|
36
37
|
class LoansMigrator(MigrationTaskBase):
|
|
37
38
|
class TaskConfiguration(AbstractTaskConfiguration):
|
|
38
|
-
name:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
name: Annotated[
|
|
40
|
+
str,
|
|
41
|
+
Field(
|
|
42
|
+
title="Task name",
|
|
43
|
+
description="The name of the task.",
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
migration_task_type: Annotated[
|
|
47
|
+
str,
|
|
48
|
+
Field(
|
|
49
|
+
title="Migration task type",
|
|
50
|
+
description="The type of the migration task.",
|
|
51
|
+
),
|
|
52
|
+
]
|
|
53
|
+
open_loans_files: Annotated[
|
|
54
|
+
Optional[list[FileDefinition]],
|
|
55
|
+
Field(
|
|
56
|
+
title="Open loans files",
|
|
57
|
+
description="List of files containing open loan data."
|
|
58
|
+
),
|
|
59
|
+
]
|
|
60
|
+
fallback_service_point_id: Annotated[
|
|
61
|
+
str,
|
|
62
|
+
Field(
|
|
63
|
+
title="Fallback service point ID",
|
|
64
|
+
description="Identifier of the fallback service point.",
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
starting_row: Annotated[
|
|
68
|
+
Optional[int],
|
|
69
|
+
Field(
|
|
70
|
+
title="Starting row",
|
|
71
|
+
description=(
|
|
72
|
+
"The starting row for data processing. "
|
|
73
|
+
"By default is 1."
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
] = 1
|
|
77
|
+
item_files: Annotated[
|
|
78
|
+
Optional[list[FileDefinition]],
|
|
79
|
+
Field(
|
|
80
|
+
title="Item files",
|
|
81
|
+
description=(
|
|
82
|
+
"List of files containing item data. "
|
|
83
|
+
"By default is empty list."
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
] = []
|
|
87
|
+
patron_files: Annotated[
|
|
88
|
+
Optional[list[FileDefinition]],
|
|
89
|
+
Field(
|
|
90
|
+
title="Patron files",
|
|
91
|
+
description=(
|
|
92
|
+
"List of files containing patron data. "
|
|
93
|
+
"By default is empty list."
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
] = []
|
|
45
97
|
|
|
46
98
|
@staticmethod
|
|
47
99
|
def get_object_type() -> FOLIONamespaces:
|
|
@@ -729,7 +781,7 @@ def timings(t0, t0func, num_objects):
|
|
|
729
781
|
|
|
730
782
|
|
|
731
783
|
def print_smtp_warning():
|
|
732
|
-
s = """
|
|
784
|
+
s = r"""
|
|
733
785
|
_____ __ __ _____ ______ ___
|
|
734
786
|
/ ____| | \/ | |_ _| | __ | |__ \\
|
|
735
787
|
| (___ | \ / | | | | |__|_| ) |
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import csv
|
|
2
|
+
import io
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
@@ -119,7 +120,11 @@ class MigrationTaskBase:
|
|
|
119
120
|
TransformationProcessError: _description_
|
|
120
121
|
|
|
121
122
|
"""
|
|
122
|
-
files = [
|
|
123
|
+
files = [
|
|
124
|
+
source_path / f.file_name
|
|
125
|
+
for f in file_defs
|
|
126
|
+
if isfile(source_path / f.file_name)
|
|
127
|
+
]
|
|
123
128
|
ret_str = ", ".join(f.file_name for f in file_defs)
|
|
124
129
|
|
|
125
130
|
if files and len(files) < len(file_defs):
|
|
@@ -141,7 +146,9 @@ class MigrationTaskBase:
|
|
|
141
146
|
@staticmethod
|
|
142
147
|
def load_id_map(map_path, raise_if_empty=False):
|
|
143
148
|
if not isfile(map_path):
|
|
144
|
-
logging.warn(
|
|
149
|
+
logging.warn(
|
|
150
|
+
"No legacy id map found at %s. Will build one from scratch", map_path
|
|
151
|
+
)
|
|
145
152
|
return {}
|
|
146
153
|
id_map = {}
|
|
147
154
|
loaded_rows = 0
|
|
@@ -194,7 +201,9 @@ class MigrationTaskBase:
|
|
|
194
201
|
else:
|
|
195
202
|
logger.setLevel(logging.INFO)
|
|
196
203
|
stream_handler.setLevel(logging.INFO)
|
|
197
|
-
stream_handler.addFilter(
|
|
204
|
+
stream_handler.addFilter(
|
|
205
|
+
ExcludeLevelFilter(30)
|
|
206
|
+
) # Exclude warnings from pymarc
|
|
198
207
|
stream_handler.setFormatter(formatter)
|
|
199
208
|
logger.addHandler(stream_handler)
|
|
200
209
|
|
|
@@ -225,16 +234,22 @@ class MigrationTaskBase:
|
|
|
225
234
|
def setup_records_map(self, mapping_file_path):
|
|
226
235
|
with open(mapping_file_path) as mapping_file:
|
|
227
236
|
field_map = json.load(mapping_file)
|
|
228
|
-
logging.info(
|
|
237
|
+
logging.info(
|
|
238
|
+
"%s fields present in record mapping file", len(field_map["data"])
|
|
239
|
+
)
|
|
229
240
|
mapped_fields = (
|
|
230
241
|
f
|
|
231
242
|
for f in field_map["data"]
|
|
232
243
|
if f["legacy_field"] and f["legacy_field"] != "Not mapped"
|
|
233
244
|
)
|
|
234
|
-
logging.info(
|
|
245
|
+
logging.info(
|
|
246
|
+
"%s fields mapped in record mapping file", len(list(mapped_fields))
|
|
247
|
+
)
|
|
235
248
|
return field_map
|
|
236
249
|
|
|
237
|
-
def log_and_exit_if_too_many_errors(
|
|
250
|
+
def log_and_exit_if_too_many_errors(
|
|
251
|
+
self, error: TransformationRecordFailedError, idx
|
|
252
|
+
):
|
|
238
253
|
self.num_exeptions += 1
|
|
239
254
|
error.log_it()
|
|
240
255
|
if self.num_exeptions / (1 + idx) > 0.2 and self.num_exeptions > 5000:
|
|
@@ -250,7 +265,9 @@ class MigrationTaskBase:
|
|
|
250
265
|
if num_processed > 1 and num_processed % 10000 == 0:
|
|
251
266
|
elapsed = num_processed / (time.time() - start_time)
|
|
252
267
|
elapsed_formatted = "{0:.4g}".format(elapsed)
|
|
253
|
-
logging.info(
|
|
268
|
+
logging.info(
|
|
269
|
+
f"{num_processed:,} records processed. Recs/sec: {elapsed_formatted} "
|
|
270
|
+
)
|
|
254
271
|
|
|
255
272
|
def do_work_marc_transformer(
|
|
256
273
|
self,
|
|
@@ -259,7 +276,9 @@ class MigrationTaskBase:
|
|
|
259
276
|
if self.folder_structure.failed_marc_recs_file.is_file():
|
|
260
277
|
os.remove(self.folder_structure.failed_marc_recs_file)
|
|
261
278
|
logging.info("Removed failed marc records file to prevent duplicating data")
|
|
262
|
-
with open(
|
|
279
|
+
with open(
|
|
280
|
+
self.folder_structure.created_objects_path, "w+"
|
|
281
|
+
) as created_records_file:
|
|
263
282
|
self.processor = MarcFileProcessor(
|
|
264
283
|
self.mapper, self.folder_structure, created_records_file
|
|
265
284
|
)
|
|
@@ -271,13 +290,87 @@ class MigrationTaskBase:
|
|
|
271
290
|
self.folder_structure,
|
|
272
291
|
)
|
|
273
292
|
|
|
293
|
+
@staticmethod
|
|
294
|
+
def validate_ref_data_mapping_lines(lines, num_of_columns):
|
|
295
|
+
"""
|
|
296
|
+
Helper method to validate the structure of individual lines in a mapping file.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
lines (list): List of lines in the mapping file
|
|
300
|
+
num_of_columns (int): Number of columns expected in each line
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
tuple: A tuple containing a list of invalid lines and a list of valid lines
|
|
304
|
+
"""
|
|
305
|
+
invalid_lines = []
|
|
306
|
+
valid_lines = []
|
|
307
|
+
for idx, row in enumerate(lines, start=2):
|
|
308
|
+
if not row.strip():
|
|
309
|
+
if idx == len(lines) + 1:
|
|
310
|
+
continue
|
|
311
|
+
else:
|
|
312
|
+
invalid_lines.append(str(idx))
|
|
313
|
+
else:
|
|
314
|
+
line_length = len(row.split("\t"))
|
|
315
|
+
if line_length != num_of_columns:
|
|
316
|
+
invalid_lines.append(str(idx))
|
|
317
|
+
else:
|
|
318
|
+
valid_lines.append(str(idx))
|
|
319
|
+
return invalid_lines, valid_lines
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def verify_ref_data_mapping_file_structure(map_file: io.TextIOBase):
|
|
323
|
+
"""
|
|
324
|
+
Helper method to validate the structure of a mapping file.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
map_file (io.TextIOBase): The mapping file to validate
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
TransformationProcessError: If the mapping file has rows with different number of columns
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
None
|
|
334
|
+
"""
|
|
335
|
+
current_pos = map_file.tell()
|
|
336
|
+
try:
|
|
337
|
+
map_file.seek(0)
|
|
338
|
+
num_of_columns = len(map_file.readline().split("\t"))
|
|
339
|
+
lines = map_file.readlines()
|
|
340
|
+
invalid_lines, valid_lines = MigrationTaskBase.validate_ref_data_mapping_lines(
|
|
341
|
+
lines, num_of_columns
|
|
342
|
+
)
|
|
343
|
+
if invalid_lines:
|
|
344
|
+
raise TransformationProcessError(
|
|
345
|
+
"",
|
|
346
|
+
(
|
|
347
|
+
f"Mapping file {map_file.name} has rows with different number "
|
|
348
|
+
f"of columns ({'Row' if len(invalid_lines) == 1 else 'Rows'} {', '.join(invalid_lines)})"
|
|
349
|
+
),
|
|
350
|
+
)
|
|
351
|
+
if not valid_lines:
|
|
352
|
+
raise TransformationProcessError(
|
|
353
|
+
"", f"Map has no rows: {map_file.name}"
|
|
354
|
+
)
|
|
355
|
+
finally:
|
|
356
|
+
map_file.seek(current_pos)
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
274
359
|
def load_ref_data_mapping_file(
|
|
275
|
-
self,
|
|
276
360
|
folio_property_name: str,
|
|
277
361
|
map_file_path: Path,
|
|
278
362
|
folio_keys,
|
|
279
363
|
required: bool = True,
|
|
280
364
|
):
|
|
365
|
+
"""
|
|
366
|
+
Helper method to load a reference data mapping file.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
folio_property_name (str): The name of the property in FOLIO
|
|
370
|
+
map_file_path (Path): The path to the mapping file
|
|
371
|
+
folio_keys (list): A list of FOLIO keys
|
|
372
|
+
required (bool): Whether the property is required or not
|
|
373
|
+
"""
|
|
281
374
|
if (
|
|
282
375
|
folio_property_name in folio_keys
|
|
283
376
|
or required
|
|
@@ -286,9 +379,9 @@ class MigrationTaskBase:
|
|
|
286
379
|
):
|
|
287
380
|
try:
|
|
288
381
|
with open(map_file_path) as map_file:
|
|
382
|
+
# Validate the structure of the mapping file
|
|
383
|
+
MigrationTaskBase.verify_ref_data_mapping_file_structure(map_file)
|
|
289
384
|
ref_data_map = list(csv.DictReader(map_file, dialect="tsv"))
|
|
290
|
-
if not ref_data_map:
|
|
291
|
-
raise TransformationProcessError("", f"Map has no rows: {map_file_path}")
|
|
292
385
|
logging.info(
|
|
293
386
|
"Found %s rows in %s map",
|
|
294
387
|
len(ref_data_map),
|
|
@@ -5,7 +5,8 @@ import logging
|
|
|
5
5
|
import sys
|
|
6
6
|
import time
|
|
7
7
|
from os.path import isfile
|
|
8
|
-
from typing import List, Optional
|
|
8
|
+
from typing import List, Optional, Annotated
|
|
9
|
+
from pydantic import Field
|
|
9
10
|
|
|
10
11
|
import i18n
|
|
11
12
|
from deepdiff import DeepDiff
|
|
@@ -26,7 +27,9 @@ from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base
|
|
|
26
27
|
from folio_migration_tools.mapping_file_transformation.order_mapper import (
|
|
27
28
|
CompositeOrderMapper,
|
|
28
29
|
)
|
|
29
|
-
from folio_migration_tools.migration_tasks.migration_task_base import
|
|
30
|
+
from folio_migration_tools.migration_tasks.migration_task_base import (
|
|
31
|
+
MigrationTaskBase,
|
|
32
|
+
)
|
|
30
33
|
from folio_migration_tools.task_configuration import AbstractTaskConfiguration
|
|
31
34
|
|
|
32
35
|
csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
|
|
@@ -35,18 +38,108 @@ csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
|
|
|
35
38
|
# Read files and do some work
|
|
36
39
|
class OrdersTransformer(MigrationTaskBase):
|
|
37
40
|
class TaskConfiguration(AbstractTaskConfiguration):
|
|
38
|
-
name:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
name: Annotated[
|
|
42
|
+
str,
|
|
43
|
+
Field(
|
|
44
|
+
title="Task name",
|
|
45
|
+
description="The name of the task.",
|
|
46
|
+
),
|
|
47
|
+
]
|
|
48
|
+
migration_task_type: Annotated[
|
|
49
|
+
str,
|
|
50
|
+
Field(
|
|
51
|
+
title="Migration task type",
|
|
52
|
+
description="Type of the migration task.",
|
|
53
|
+
),
|
|
54
|
+
]
|
|
55
|
+
files: Annotated[
|
|
56
|
+
List[FileDefinition],
|
|
57
|
+
Field(
|
|
58
|
+
title="Files",
|
|
59
|
+
description="List of the files.",
|
|
60
|
+
),
|
|
61
|
+
]
|
|
62
|
+
orders_mapping_file_name: Annotated[
|
|
63
|
+
str,
|
|
64
|
+
Field(
|
|
65
|
+
title="Orders Mapping File Name",
|
|
66
|
+
description="File name for orders mapping.",
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
organizations_code_map_file_name: Annotated[
|
|
70
|
+
str,
|
|
71
|
+
Field(
|
|
72
|
+
title="Organizations Code Map File Name",
|
|
73
|
+
description="File name for organizations code mapping.",
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
acquisition_method_map_file_name: Annotated[
|
|
77
|
+
str,
|
|
78
|
+
Field(
|
|
79
|
+
title="Acquisition Method Map File Name",
|
|
80
|
+
description="File name for acquisition method mapping.",
|
|
81
|
+
),
|
|
82
|
+
]
|
|
83
|
+
payment_status_map_file_name: Annotated[
|
|
84
|
+
Optional[str],
|
|
85
|
+
Field(
|
|
86
|
+
title="Payment Status Map File Name",
|
|
87
|
+
description=(
|
|
88
|
+
"File name for payment status mapping. "
|
|
89
|
+
"By default is empty string."
|
|
90
|
+
),
|
|
91
|
+
),
|
|
92
|
+
] = ""
|
|
93
|
+
receipt_status_map_file_name: Annotated[
|
|
94
|
+
Optional[str],
|
|
95
|
+
Field(
|
|
96
|
+
title="Receipt Status Map File Name",
|
|
97
|
+
description=(
|
|
98
|
+
"File name for receipt status mapping. "
|
|
99
|
+
"By default is empty string."
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
] = ""
|
|
103
|
+
workflow_status_map_file_name: Annotated[
|
|
104
|
+
Optional[str],
|
|
105
|
+
Field(
|
|
106
|
+
title="Workflow Status Map File Name",
|
|
107
|
+
description=(
|
|
108
|
+
"File name for workflow status mapping. "
|
|
109
|
+
"By default is empty string."
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
] = ""
|
|
113
|
+
location_map_file_name: Annotated[
|
|
114
|
+
Optional[str],
|
|
115
|
+
Field(
|
|
116
|
+
title="Location Map File Name",
|
|
117
|
+
description=(
|
|
118
|
+
"File name for location mapping. "
|
|
119
|
+
"By default is empty string."
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
] = ""
|
|
123
|
+
funds_map_file_name: Annotated[
|
|
124
|
+
Optional[str],
|
|
125
|
+
Field(
|
|
126
|
+
title="Funds Map File Name",
|
|
127
|
+
description=(
|
|
128
|
+
"File name for funds mapping. "
|
|
129
|
+
"By default is empty string."
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
] = ""
|
|
133
|
+
funds_expense_class_map_file_name: Annotated[
|
|
134
|
+
Optional[str],
|
|
135
|
+
Field(
|
|
136
|
+
title="Funds Expense Class Map File Name",
|
|
137
|
+
description=(
|
|
138
|
+
"File name for funds expense class mapping. "
|
|
139
|
+
"By default is empty string."
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
] = ""
|
|
50
143
|
|
|
51
144
|
@staticmethod
|
|
52
145
|
def get_object_type() -> FOLIONamespaces:
|
|
@@ -7,7 +7,8 @@ import time
|
|
|
7
7
|
import uuid
|
|
8
8
|
from hashlib import sha1
|
|
9
9
|
from os.path import isfile
|
|
10
|
-
from typing import List, Optional
|
|
10
|
+
from typing import List, Optional, Annotated
|
|
11
|
+
from pydantic import Field
|
|
11
12
|
|
|
12
13
|
import i18n
|
|
13
14
|
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
@@ -36,14 +37,78 @@ csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
|
|
|
36
37
|
# Read files and do some work
|
|
37
38
|
class OrganizationTransformer(MigrationTaskBase):
|
|
38
39
|
class TaskConfiguration(AbstractTaskConfiguration):
|
|
39
|
-
name:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
name: Annotated[
|
|
41
|
+
str,
|
|
42
|
+
Field(
|
|
43
|
+
description=(
|
|
44
|
+
"Name of this migration task. The name is being used to call the specific "
|
|
45
|
+
"task, and to distinguish tasks of similar types"
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
]
|
|
49
|
+
migration_task_type: Annotated[
|
|
50
|
+
str,
|
|
51
|
+
Field(
|
|
52
|
+
title="Migration task type",
|
|
53
|
+
description=(
|
|
54
|
+
"The type of migration task you want to perform"
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
]
|
|
58
|
+
files: Annotated[
|
|
59
|
+
List[FileDefinition],
|
|
60
|
+
Field(
|
|
61
|
+
title="Source files",
|
|
62
|
+
description=(
|
|
63
|
+
"List of MARC21 files with holdings records"
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
organization_map_path: Annotated[
|
|
68
|
+
str,
|
|
69
|
+
Field(
|
|
70
|
+
title="Organization map path",
|
|
71
|
+
description=(
|
|
72
|
+
"Path to the organization map file"
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
organization_types_map_path: Annotated[
|
|
77
|
+
Optional[str],
|
|
78
|
+
Field(
|
|
79
|
+
title="Organization types map path",
|
|
80
|
+
description=(
|
|
81
|
+
"Path to the organization types map file. By default is empty string"
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
] = ""
|
|
85
|
+
address_categories_map_path: Annotated[
|
|
86
|
+
Optional[str],
|
|
87
|
+
Field(
|
|
88
|
+
title="Address categories map path",
|
|
89
|
+
description=(
|
|
90
|
+
"Path to the address categories map file. By default is empty string"
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
] = ""
|
|
94
|
+
email_categories_map_path: Annotated[
|
|
95
|
+
Optional[str],
|
|
96
|
+
Field(
|
|
97
|
+
title="Email categories map path",
|
|
98
|
+
description=(
|
|
99
|
+
"Path to the email categories map file. By default is empty string"
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
] = ""
|
|
103
|
+
phone_categories_map_path: Annotated[
|
|
104
|
+
Optional[str],
|
|
105
|
+
Field(
|
|
106
|
+
title="Phone categories map path",
|
|
107
|
+
description=(
|
|
108
|
+
"Path to the phone categories map file. By default is empty string"
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
] = ""
|
|
47
112
|
|
|
48
113
|
@staticmethod
|
|
49
114
|
def get_object_type() -> FOLIONamespaces:
|
|
@@ -178,8 +243,8 @@ class OrganizationTransformer(MigrationTaskBase):
|
|
|
178
243
|
self.mapper.handle_transformation_process_error(idx, process_error)
|
|
179
244
|
except TransformationRecordFailedError as error:
|
|
180
245
|
self.mapper.handle_transformation_record_failed_error(idx, error)
|
|
181
|
-
except Exception as
|
|
182
|
-
self.mapper.handle_generic_exception(idx,
|
|
246
|
+
except Exception as exception:
|
|
247
|
+
self.mapper.handle_generic_exception(idx, exception)
|
|
183
248
|
|
|
184
249
|
self.mapper.migration_report.add_general_statistics(
|
|
185
250
|
i18n.t("Number of objects in source data file")
|
|
@@ -227,7 +292,7 @@ class OrganizationTransformer(MigrationTaskBase):
|
|
|
227
292
|
self.folder_structure.migration_reports_file,
|
|
228
293
|
)
|
|
229
294
|
self.mapper.migration_report.write_migration_report(
|
|
230
|
-
i18n.t("
|
|
295
|
+
i18n.t("Organization transformation report"),
|
|
231
296
|
migration_report_file,
|
|
232
297
|
self.start_datetime,
|
|
233
298
|
)
|
|
@@ -269,7 +334,7 @@ class OrganizationTransformer(MigrationTaskBase):
|
|
|
269
334
|
if address.get("isPrimary") is True:
|
|
270
335
|
primary_address_exists = True
|
|
271
336
|
|
|
272
|
-
# If none of the existing addresses is
|
|
337
|
+
# If none of the existing addresses is primary
|
|
273
338
|
# Make the first one primary
|
|
274
339
|
if not primary_address_exists:
|
|
275
340
|
addresses[0]["isPrimary"] = True
|
|
@@ -364,7 +429,7 @@ class OrganizationTransformer(MigrationTaskBase):
|
|
|
364
429
|
|
|
365
430
|
if len(identical_objects) > 0:
|
|
366
431
|
self.mapper.migration_report.add_general_statistics(
|
|
367
|
-
i18n.t("Number of
|
|
432
|
+
i18n.t("Number of reoccurring identical %{type}", type=extradata_object_type)
|
|
368
433
|
)
|
|
369
434
|
Helper.log_data_issue(
|
|
370
435
|
f"{self.legacy_id}",
|
|
@@ -3,7 +3,8 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
-
from typing import Optional
|
|
6
|
+
from typing import Optional, Annotated
|
|
7
|
+
from pydantic import Field
|
|
7
8
|
|
|
8
9
|
import i18n
|
|
9
10
|
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
@@ -24,12 +25,60 @@ from folio_migration_tools.transaction_migration.legacy_request import LegacyReq
|
|
|
24
25
|
|
|
25
26
|
class RequestsMigrator(MigrationTaskBase):
|
|
26
27
|
class TaskConfiguration(AbstractTaskConfiguration):
|
|
27
|
-
name:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
name: Annotated[
|
|
29
|
+
str,
|
|
30
|
+
Field(
|
|
31
|
+
title="Task name",
|
|
32
|
+
description=(
|
|
33
|
+
"Name of this migration task. The name is being used to call "
|
|
34
|
+
"the specific task, and to distinguish tasks of similar types"
|
|
35
|
+
)
|
|
36
|
+
),
|
|
37
|
+
]
|
|
38
|
+
migration_task_type: Annotated[
|
|
39
|
+
str,
|
|
40
|
+
Field(
|
|
41
|
+
title="Migration task type",
|
|
42
|
+
description="The type of migration task you want to perform",
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
open_requests_file: Annotated[
|
|
46
|
+
FileDefinition,
|
|
47
|
+
Field(
|
|
48
|
+
title="Open requests file",
|
|
49
|
+
description="File with list of open requests",
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
starting_row: Annotated[
|
|
53
|
+
Optional[int],
|
|
54
|
+
Field(
|
|
55
|
+
title="Starting row",
|
|
56
|
+
description=(
|
|
57
|
+
"Row number to start processing data from. "
|
|
58
|
+
"Optional, by default is first row"
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
] = 1
|
|
62
|
+
item_files: Annotated[
|
|
63
|
+
Optional[list[FileDefinition]],
|
|
64
|
+
Field(
|
|
65
|
+
title="Item files",
|
|
66
|
+
description=(
|
|
67
|
+
"List of files containing item data. "
|
|
68
|
+
"Optional, by default is empty list"
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
] = []
|
|
72
|
+
patron_files: Annotated[
|
|
73
|
+
Optional[list[FileDefinition]],
|
|
74
|
+
Field(
|
|
75
|
+
title="Patron files",
|
|
76
|
+
description=(
|
|
77
|
+
"List of files containing patron data. "
|
|
78
|
+
"Optional, by default is empty list"
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
] = []
|
|
33
82
|
|
|
34
83
|
@staticmethod
|
|
35
84
|
def get_object_type() -> FOLIONamespaces:
|
|
@@ -4,8 +4,9 @@ import logging
|
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
6
|
import traceback
|
|
7
|
-
from typing import Dict
|
|
7
|
+
from typing import Dict, Annotated
|
|
8
8
|
from urllib.error import HTTPError
|
|
9
|
+
from pydantic import Field
|
|
9
10
|
|
|
10
11
|
import httpx
|
|
11
12
|
import i18n
|
|
@@ -25,9 +26,30 @@ from folio_migration_tools.transaction_migration.legacy_reserve import LegacyRes
|
|
|
25
26
|
|
|
26
27
|
class ReservesMigrator(MigrationTaskBase):
|
|
27
28
|
class TaskConfiguration(AbstractTaskConfiguration):
|
|
28
|
-
name:
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
name: Annotated[
|
|
30
|
+
str,
|
|
31
|
+
Field(
|
|
32
|
+
title="Migration task name",
|
|
33
|
+
description=(
|
|
34
|
+
"Name of this migration task. The name is being used to call the specific "
|
|
35
|
+
"task, and to distinguish tasks of similar types"
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
migration_task_type: Annotated[
|
|
40
|
+
str,
|
|
41
|
+
Field(
|
|
42
|
+
title="Migration task type",
|
|
43
|
+
description="The type of migration task you want to perform",
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
course_reserve_file_path: Annotated[
|
|
47
|
+
FileDefinition,
|
|
48
|
+
Field(
|
|
49
|
+
title="Course reserve file path",
|
|
50
|
+
description="Path to the file with course reserves",
|
|
51
|
+
),
|
|
52
|
+
]
|
|
31
53
|
|
|
32
54
|
@staticmethod
|
|
33
55
|
def get_object_type() -> FOLIONamespaces:
|