folio-migration-tools 1.9.2__py3-none-any.whl → 1.9.4__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/user_mapper.py +39 -31
- folio_migration_tools/marc_rules_transformation/conditions.py +239 -30
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +99 -65
- folio_migration_tools/marc_rules_transformation/marc_file_processor.py +6 -1
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +2 -2
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +7 -1
- folio_migration_tools/migration_tasks/batch_poster.py +201 -51
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +22 -11
- folio_migration_tools/migration_tasks/items_transformer.py +25 -10
- folio_migration_tools/migration_tasks/loans_migrator.py +238 -77
- folio_migration_tools/migration_tasks/organization_transformer.py +1 -0
- folio_migration_tools/task_configuration.py +3 -1
- folio_migration_tools/test_infrastructure/mocked_classes.py +5 -0
- folio_migration_tools/transaction_migration/legacy_loan.py +30 -10
- folio_migration_tools/translations/en.json +19 -1
- {folio_migration_tools-1.9.2.dist-info → folio_migration_tools-1.9.4.dist-info}/METADATA +2 -1
- {folio_migration_tools-1.9.2.dist-info → folio_migration_tools-1.9.4.dist-info}/RECORD +20 -20
- {folio_migration_tools-1.9.2.dist-info → folio_migration_tools-1.9.4.dist-info}/LICENSE +0 -0
- {folio_migration_tools-1.9.2.dist-info → folio_migration_tools-1.9.4.dist-info}/WHEEL +0 -0
- {folio_migration_tools-1.9.2.dist-info → folio_migration_tools-1.9.4.dist-info}/entry_points.txt +0 -0
|
@@ -59,8 +59,8 @@ class UserMapper(MappingFileMapperBase):
|
|
|
59
59
|
True,
|
|
60
60
|
)
|
|
61
61
|
self.notes_mapper.migration_report = self.migration_report
|
|
62
|
-
self.setup_departments_mapping(departments_mapping)
|
|
63
|
-
self.setup_groups_mapping(groups_map)
|
|
62
|
+
self.departments_mapping = self.setup_departments_mapping(departments_mapping)
|
|
63
|
+
self.groups_mapping = self.setup_groups_mapping(groups_map)
|
|
64
64
|
|
|
65
65
|
for m in self.record_map["data"]:
|
|
66
66
|
if m["folio_field"].startswith("customFields"):
|
|
@@ -120,7 +120,8 @@ class UserMapper(MappingFileMapperBase):
|
|
|
120
120
|
|
|
121
121
|
return clean_folio_object
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
@staticmethod
|
|
124
|
+
def get_users(source_file, file_format: str):
|
|
124
125
|
csv.register_dialect("tsv", delimiter="\t")
|
|
125
126
|
if file_format == "tsv":
|
|
126
127
|
reader = csv.DictReader(source_file, dialect="tsv")
|
|
@@ -156,12 +157,25 @@ class UserMapper(MappingFileMapperBase):
|
|
|
156
157
|
"No Departments mapping set up. Set up a departments mapping file "
|
|
157
158
|
" or remove the mapping of the Departments field",
|
|
158
159
|
)
|
|
159
|
-
|
|
160
|
-
self.departments_mapping,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
if len(self.departments_mapping.mapped_legacy_keys) == 1 and self.library_configuration.multi_field_delimiter in legacy_user.get(self.departments_mapping.mapped_legacy_keys[0], ""):
|
|
161
|
+
split_departments = legacy_user.get(self.departments_mapping.mapped_legacy_keys[0], "").split(
|
|
162
|
+
self.library_configuration.multi_field_delimiter
|
|
163
|
+
)
|
|
164
|
+
return self.library_configuration.multi_field_delimiter.join([
|
|
165
|
+
self.get_mapped_name(
|
|
166
|
+
self.departments_mapping,
|
|
167
|
+
{self.departments_mapping.mapped_legacy_keys[0]: dept},
|
|
168
|
+
index_or_id,
|
|
169
|
+
True,
|
|
170
|
+
) for dept in split_departments
|
|
171
|
+
])
|
|
172
|
+
else:
|
|
173
|
+
return self.get_mapped_name(
|
|
174
|
+
self.departments_mapping,
|
|
175
|
+
legacy_user,
|
|
176
|
+
index_or_id,
|
|
177
|
+
True,
|
|
178
|
+
)
|
|
165
179
|
elif folio_prop_name in ["expirationDate", "enrollmentDate", "personal.dateOfBirth"]:
|
|
166
180
|
return self.get_parsed_date(mapped_value, folio_prop_name)
|
|
167
181
|
return mapped_value
|
|
@@ -184,27 +198,21 @@ class UserMapper(MappingFileMapperBase):
|
|
|
184
198
|
return ""
|
|
185
199
|
|
|
186
200
|
def setup_groups_mapping(self, groups_map):
|
|
187
|
-
|
|
188
|
-
self.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
)
|
|
196
|
-
else:
|
|
197
|
-
self.groups_mapping = None
|
|
201
|
+
return RefDataMapping(
|
|
202
|
+
self.folio_client,
|
|
203
|
+
"/groups",
|
|
204
|
+
"usergroups",
|
|
205
|
+
groups_map,
|
|
206
|
+
"group",
|
|
207
|
+
"UserGroupMapping",
|
|
208
|
+
) if groups_map else None
|
|
198
209
|
|
|
199
210
|
def setup_departments_mapping(self, departments_mapping):
|
|
200
|
-
|
|
201
|
-
self.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
209
|
-
else:
|
|
210
|
-
self.departments_mapping = None
|
|
211
|
+
return RefDataMapping(
|
|
212
|
+
self.folio_client,
|
|
213
|
+
"/departments",
|
|
214
|
+
"departments",
|
|
215
|
+
departments_mapping,
|
|
216
|
+
"name",
|
|
217
|
+
"DepartmentsMapping",
|
|
218
|
+
) if departments_mapping else None
|
|
@@ -90,6 +90,7 @@ class Conditions:
|
|
|
90
90
|
logging.info("%s\tholding_note_types", len(self.folio.holding_note_types)) # type: ignore
|
|
91
91
|
logging.info("%s\tcall_number_types", len(self.folio.call_number_types)) # type: ignore
|
|
92
92
|
self.setup_and_validate_holdings_types()
|
|
93
|
+
self.ill_policies = self.folio.folio_get_all("/ill-policies", "illPolicies")
|
|
93
94
|
# Raise for empty settings
|
|
94
95
|
if not self.folio.holding_note_types:
|
|
95
96
|
raise TransformationProcessError("", "No holding_note_types in FOLIO")
|
|
@@ -466,36 +467,6 @@ class Conditions:
|
|
|
466
467
|
def condition_char_select(self, legacy_id, value, parameter, marc_field: field.Field):
|
|
467
468
|
return value[parameter["from"] : parameter["to"]]
|
|
468
469
|
|
|
469
|
-
def condition_set_receipt_status(self, legacy_id, value, parameter, marc_field: field.Field):
|
|
470
|
-
if len(value) < 7:
|
|
471
|
-
self.mapper.migration_report.add(
|
|
472
|
-
"ReceiptStatusMapping", i18n.t("008 is too short") + f": {value}"
|
|
473
|
-
)
|
|
474
|
-
return ""
|
|
475
|
-
try:
|
|
476
|
-
status_map = {
|
|
477
|
-
"0": "Unknown",
|
|
478
|
-
"1": "Other receipt or acquisition status",
|
|
479
|
-
"2": "Received and complete or ceased",
|
|
480
|
-
"3": "On order",
|
|
481
|
-
"4": "Currently received",
|
|
482
|
-
"5": "Not currently received",
|
|
483
|
-
"6": "External access",
|
|
484
|
-
}
|
|
485
|
-
mapped_value = status_map[value[6]]
|
|
486
|
-
self.mapper.migration_report.add(
|
|
487
|
-
"ReceiptStatusMapping",
|
|
488
|
-
i18n.t(
|
|
489
|
-
"%{value} mapped to %{mapped_value}", value=value[6], mapped_value=mapped_value
|
|
490
|
-
),
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
return
|
|
494
|
-
except Exception:
|
|
495
|
-
self.mapper.migration_report.add(
|
|
496
|
-
"ReceiptStatusMapping", i18n.t("%{value} not found in map.", value=value)
|
|
497
|
-
)
|
|
498
|
-
return "Unknown"
|
|
499
470
|
|
|
500
471
|
def condition_set_identifier_type_id_by_name(
|
|
501
472
|
self, legacy_id, value, parameter, marc_field: field.Field
|
|
@@ -915,3 +886,241 @@ class Conditions:
|
|
|
915
886
|
legacy_id,
|
|
916
887
|
f"Subject source not found for {value} {marc_field}",
|
|
917
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 ""
|
|
@@ -23,18 +23,18 @@ class HoldingsStatementsParser:
|
|
|
23
23
|
"""_summary_
|
|
24
24
|
|
|
25
25
|
Args:
|
|
26
|
-
marc_record (Record):
|
|
27
|
-
pattern_tag (str):
|
|
28
|
-
value_tag (str):
|
|
29
|
-
field_textual (str):
|
|
30
|
-
legacy_ids (List[str]):
|
|
31
|
-
dedupe_results (bool):
|
|
26
|
+
marc_record (Record): pymarc Record object
|
|
27
|
+
pattern_tag (str): MARC tag for the pattern field
|
|
28
|
+
value_tag (str): MARC tag for the value field
|
|
29
|
+
field_textual (str): MARC tag for the textual holdings field
|
|
30
|
+
legacy_ids (List[str]): List of legacy IDs associated with the record
|
|
31
|
+
dedupe_results (bool): Whether to deduplicate the results. Defaults to True.
|
|
32
32
|
|
|
33
33
|
Raises:
|
|
34
|
-
TransformationFieldMappingError:
|
|
34
|
+
TransformationFieldMappingError: If there is an error in mapping the holdings statements.
|
|
35
35
|
|
|
36
36
|
Returns:
|
|
37
|
-
dict:
|
|
37
|
+
dict: A dictionary containing parsed holdings statements and related information.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
40
|
# Textual holdings statements
|
|
@@ -45,21 +45,9 @@ class HoldingsStatementsParser:
|
|
|
45
45
|
|
|
46
46
|
value_fields = marc_record.get_fields(value_tag)
|
|
47
47
|
for pattern_field in marc_record.get_fields(pattern_tag):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
i18n.t(
|
|
52
|
-
"%{tag} subfield %{subfield} not in field",
|
|
53
|
-
tag=pattern_tag,
|
|
54
|
-
subfield="8",
|
|
55
|
-
),
|
|
56
|
-
pattern_field,
|
|
57
|
-
)
|
|
58
|
-
linked_value_fields = [
|
|
59
|
-
value_field
|
|
60
|
-
for value_field in value_fields
|
|
61
|
-
if "8" in value_field and value_field["8"].split(".")[0] == pattern_field["8"]
|
|
62
|
-
]
|
|
48
|
+
linked_value_fields = HoldingsStatementsParser.get_linked_value_fields(
|
|
49
|
+
pattern_tag, legacy_ids, value_fields, pattern_field
|
|
50
|
+
)
|
|
63
51
|
|
|
64
52
|
if not any(linked_value_fields):
|
|
65
53
|
return_dict["migration_report"].append(
|
|
@@ -75,7 +63,7 @@ class HoldingsStatementsParser:
|
|
|
75
63
|
parsed_dict = HoldingsStatementsParser.parse_linked_field(
|
|
76
64
|
pattern_field, linked_value_field
|
|
77
65
|
)
|
|
78
|
-
except KeyError:
|
|
66
|
+
except KeyError as e:
|
|
79
67
|
raise TransformationFieldMappingError(
|
|
80
68
|
legacy_ids,
|
|
81
69
|
i18n.t(
|
|
@@ -84,24 +72,10 @@ class HoldingsStatementsParser:
|
|
|
84
72
|
linked_value_tag=linked_value_field,
|
|
85
73
|
),
|
|
86
74
|
pattern_field,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
return_dict
|
|
90
|
-
|
|
91
|
-
logging.info(
|
|
92
|
-
f"HOLDINGS STATEMENT PATTERN\t{'-'.join(legacy_ids)}\t{pattern_field}"
|
|
93
|
-
f"\t{linked_value_field}"
|
|
94
|
-
f"\t{parsed_dict['statement']['statement']}"
|
|
95
|
-
f"\t{parsed_dict['statement']['note']}"
|
|
96
|
-
f"\t{parsed_dict['statement']['staffNote']}"
|
|
97
|
-
)
|
|
98
|
-
return_dict["migration_report"].append(
|
|
99
|
-
(
|
|
100
|
-
"Holdings statements",
|
|
101
|
-
f"From {pattern_tag}",
|
|
102
|
-
)
|
|
103
|
-
)
|
|
104
|
-
return_dict["statements"].append(parsed_dict["statement"])
|
|
75
|
+
) from e
|
|
76
|
+
HoldingsStatementsParser.prepare_return_dict(
|
|
77
|
+
pattern_tag, legacy_ids, return_dict, pattern_field, linked_value_field, parsed_dict
|
|
78
|
+
)
|
|
105
79
|
|
|
106
80
|
if dedupe_results:
|
|
107
81
|
return_dict["statements"] = HoldingsStatementsParser.dedupe_list_of_dict(
|
|
@@ -109,6 +83,47 @@ class HoldingsStatementsParser:
|
|
|
109
83
|
)
|
|
110
84
|
return return_dict
|
|
111
85
|
|
|
86
|
+
@staticmethod
|
|
87
|
+
def prepare_return_dict(
|
|
88
|
+
pattern_tag, legacy_ids, return_dict, pattern_field, linked_value_field, parsed_dict
|
|
89
|
+
):
|
|
90
|
+
if parsed_dict["hlm_stmt"]:
|
|
91
|
+
return_dict["hlm_stmts"].append(parsed_dict["hlm_stmt"])
|
|
92
|
+
if parsed_dict["statement"]:
|
|
93
|
+
logging.info(
|
|
94
|
+
f"HOLDINGS STATEMENT PATTERN\t{'-'.join(legacy_ids)}\t{pattern_field}"
|
|
95
|
+
f"\t{linked_value_field}"
|
|
96
|
+
f"\t{parsed_dict['statement']['statement']}"
|
|
97
|
+
f"\t{parsed_dict['statement']['note']}"
|
|
98
|
+
f"\t{parsed_dict['statement']['staffNote']}"
|
|
99
|
+
)
|
|
100
|
+
return_dict["migration_report"].append(
|
|
101
|
+
(
|
|
102
|
+
"Holdings statements",
|
|
103
|
+
f"From {pattern_tag}",
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
return_dict["statements"].append(parsed_dict["statement"])
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def get_linked_value_fields(pattern_tag, legacy_ids, value_fields, pattern_field):
|
|
110
|
+
if "8" not in pattern_field:
|
|
111
|
+
raise TransformationFieldMappingError(
|
|
112
|
+
legacy_ids,
|
|
113
|
+
i18n.t(
|
|
114
|
+
"%{tag} subfield %{subfield} not in field",
|
|
115
|
+
tag=pattern_tag,
|
|
116
|
+
subfield="8",
|
|
117
|
+
),
|
|
118
|
+
pattern_field,
|
|
119
|
+
)
|
|
120
|
+
linked_value_fields = [
|
|
121
|
+
value_field
|
|
122
|
+
for value_field in value_fields
|
|
123
|
+
if "8" in value_field and value_field["8"].split(".")[0] == pattern_field["8"]
|
|
124
|
+
]
|
|
125
|
+
return linked_value_fields
|
|
126
|
+
|
|
112
127
|
@staticmethod
|
|
113
128
|
def parse_linked_field(pattern_field: Field, linked_value_fields: Field):
|
|
114
129
|
break_ind = HoldingsStatementsParser.get_break_indicator(linked_value_fields)
|
|
@@ -123,6 +138,20 @@ class HoldingsStatementsParser:
|
|
|
123
138
|
"hlm_stmt": hlm_stmt,
|
|
124
139
|
}
|
|
125
140
|
|
|
141
|
+
_from, _to = HoldingsStatementsParser.format_from_to(_from, _to, cron_from, cron_to)
|
|
142
|
+
span = "-" if is_span or is_cron_span else ""
|
|
143
|
+
stmt = f"{_from}{span}{_to}{break_ind}" if _from else ""
|
|
144
|
+
stmt = stmt.strip()
|
|
145
|
+
if "z" in linked_value_fields:
|
|
146
|
+
return_dict["statement"]["note"] = linked_value_fields["z"]
|
|
147
|
+
if "x" in linked_value_fields:
|
|
148
|
+
return_dict["statement"]["staffNote"] = linked_value_fields["x"]
|
|
149
|
+
stmt = re.sub(" +", " ", stmt)
|
|
150
|
+
return_dict["statement"]["statement"] = stmt
|
|
151
|
+
return return_dict
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def format_from_to(_from, _to, cron_from, cron_to):
|
|
126
155
|
if _from and cron_from:
|
|
127
156
|
_from = f"{_from} ({cron_from})"
|
|
128
157
|
if not _from and cron_from:
|
|
@@ -137,16 +166,7 @@ class HoldingsStatementsParser:
|
|
|
137
166
|
_to = f"({cron_to})"
|
|
138
167
|
if _from and _from == cron_from:
|
|
139
168
|
_from = f"({cron_from})"
|
|
140
|
-
|
|
141
|
-
stmt = f"{_from}{span}{_to}{break_ind}" if _from else ""
|
|
142
|
-
stmt = stmt.strip()
|
|
143
|
-
if "z" in linked_value_fields:
|
|
144
|
-
return_dict["statement"]["note"] = linked_value_fields["z"]
|
|
145
|
-
if "x" in linked_value_fields:
|
|
146
|
-
return_dict["statement"]["staffNote"] = linked_value_fields["x"]
|
|
147
|
-
stmt = re.sub(" +", " ", stmt)
|
|
148
|
-
return_dict["statement"]["statement"] = stmt
|
|
149
|
-
return return_dict
|
|
169
|
+
return _from, _to
|
|
150
170
|
|
|
151
171
|
@staticmethod
|
|
152
172
|
def get_textual_statements(
|
|
@@ -276,12 +296,9 @@ class HoldingsStatementsParser:
|
|
|
276
296
|
elif cron_to.strip() and val:
|
|
277
297
|
val_rest = val
|
|
278
298
|
if year:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
cron_to = f"{cron_to}:{''.join(val_rest)} "
|
|
283
|
-
elif not cron_to and "".join(val_rest):
|
|
284
|
-
cron_to = f"{spill_year}{''.join(val_rest)}"
|
|
299
|
+
cron_from, cron_to = HoldingsStatementsParser.format_year_cron_from_cron_to(
|
|
300
|
+
cron_from, cron_to, hlm_stmt, val, val_rest
|
|
301
|
+
)
|
|
285
302
|
|
|
286
303
|
else:
|
|
287
304
|
if "season" in desc:
|
|
@@ -292,6 +309,16 @@ class HoldingsStatementsParser:
|
|
|
292
309
|
cron_to = f"{cron_to} {''.join(val_rest)}".strip()
|
|
293
310
|
return (f"{cron_from.strip()}", cron_to.strip(), hlm_stmt, is_span)
|
|
294
311
|
|
|
312
|
+
@staticmethod
|
|
313
|
+
def format_year_cron_from_cron_to(cron_from, cron_to, hlm_stmt, val, val_rest):
|
|
314
|
+
spill_year = f"{hlm_stmt}:" if "-" not in hlm_stmt else ""
|
|
315
|
+
cron_from = f"{cron_from.strip()}:{val}"
|
|
316
|
+
if cron_to and "".join(val_rest):
|
|
317
|
+
cron_to = f"{cron_to}:{''.join(val_rest)}"
|
|
318
|
+
elif not cron_to and "".join(val_rest):
|
|
319
|
+
cron_to = f"{spill_year}{''.join(val_rest)}"
|
|
320
|
+
return cron_from, cron_to
|
|
321
|
+
|
|
295
322
|
@staticmethod
|
|
296
323
|
def get_from_to(pattern_field: Field, linked_value_field: Field):
|
|
297
324
|
_from = ""
|
|
@@ -300,11 +327,18 @@ class HoldingsStatementsParser:
|
|
|
300
327
|
for enum_level in [el for el in "abcdef" if el in linked_value_field]:
|
|
301
328
|
desc = pattern_field.get(enum_level, "")
|
|
302
329
|
desc = desc.strip() if "(" not in desc else ""
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
_from = f"{_from}{(':' if _from else '')}{desc}{val}"
|
|
307
|
-
temp_to = "".join(val_rest)
|
|
308
|
-
if temp_to.strip():
|
|
309
|
-
_to = f"{_to}{(':' if _to else '')}{desc}{temp_to}"
|
|
330
|
+
_from, _to, is_span = HoldingsStatementsParser.format_enum_parts(
|
|
331
|
+
linked_value_field, _from, _to, is_span, enum_level, desc
|
|
332
|
+
)
|
|
310
333
|
return (f"{_from.strip()}", _to.strip(), is_span)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def format_enum_parts(linked_value_field, _from, _to, is_span, enum_level, desc):
|
|
337
|
+
if linked_value_field.get(enum_level):
|
|
338
|
+
val, *val_rest = linked_value_field[enum_level].split("-")
|
|
339
|
+
is_span = "-" in linked_value_field[enum_level] or is_span
|
|
340
|
+
_from = f"{_from}{(':' if _from else '')}{desc}{val}"
|
|
341
|
+
temp_to = "".join(val_rest)
|
|
342
|
+
if temp_to.strip():
|
|
343
|
+
_to = f"{_to}{(':' if _to else '')}{desc}{temp_to}"
|
|
344
|
+
return _from, _to, is_span
|
|
@@ -65,7 +65,12 @@ class MarcFileProcessor:
|
|
|
65
65
|
self.records_count += 1
|
|
66
66
|
try:
|
|
67
67
|
# Transform the MARC21 to a FOLIO record
|
|
68
|
-
|
|
68
|
+
try:
|
|
69
|
+
legacy_ids = self.mapper.get_legacy_ids(marc_record, idx)
|
|
70
|
+
except ValueError as e:
|
|
71
|
+
raise TransformationRecordFailedError(
|
|
72
|
+
f"{idx} in {file_def.file_name}", str(e), idx
|
|
73
|
+
) from e
|
|
69
74
|
if not legacy_ids:
|
|
70
75
|
raise TransformationRecordFailedError(
|
|
71
76
|
f"Index in file: {idx}", "No legacy id found", idx
|
|
@@ -174,11 +174,11 @@ class BibsRulesMapper(RulesMapperBase):
|
|
|
174
174
|
self.process_marc_field(folio_instance, main_entry_fields[0], ignored_subsequent_fields, legacy_ids)
|
|
175
175
|
try:
|
|
176
176
|
self.process_marc_field(folio_instance, marc_record['245'], ignored_subsequent_fields, legacy_ids)
|
|
177
|
-
except KeyError:
|
|
177
|
+
except KeyError as ke:
|
|
178
178
|
raise TransformationRecordFailedError(
|
|
179
179
|
legacy_ids,
|
|
180
180
|
"No 245 field in MARC record"
|
|
181
|
-
)
|
|
181
|
+
) from ke
|
|
182
182
|
|
|
183
183
|
def perform_additional_parsing(
|
|
184
184
|
self,
|
|
@@ -196,9 +196,15 @@ class RulesMapperHoldings(RulesMapperBase):
|
|
|
196
196
|
if "004" not in marc_record:
|
|
197
197
|
raise TransformationProcessError(
|
|
198
198
|
"",
|
|
199
|
-
("No 004 in record. The tools only support bib-mfhd linking
|
|
199
|
+
("No 004 in record. The tools only support bib-mfhd linking through 004"),
|
|
200
200
|
legacy_ids,
|
|
201
201
|
)
|
|
202
|
+
if len(marc_record.get_fields("004")) > 1:
|
|
203
|
+
Helper.log_data_issue(
|
|
204
|
+
legacy_ids,
|
|
205
|
+
"More than one linked bib (004) found in record. Using the first one",
|
|
206
|
+
[str(x) for x in marc_record.get_fields("004")],
|
|
207
|
+
)
|
|
202
208
|
legacy_instance_id = marc_record["004"].data.strip()
|
|
203
209
|
folio_holding["formerIds"].append(f"{self.bib_id_template}{legacy_instance_id}")
|
|
204
210
|
if legacy_instance_id in self.parent_id_map:
|