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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. folio_migration_tools/__init__.py +11 -0
  2. folio_migration_tools/__main__.py +169 -85
  3. folio_migration_tools/circulation_helper.py +96 -59
  4. folio_migration_tools/config_file_load.py +66 -0
  5. folio_migration_tools/custom_dict.py +6 -4
  6. folio_migration_tools/custom_exceptions.py +21 -19
  7. folio_migration_tools/extradata_writer.py +46 -0
  8. folio_migration_tools/folder_structure.py +63 -66
  9. folio_migration_tools/helper.py +29 -21
  10. folio_migration_tools/holdings_helper.py +57 -34
  11. folio_migration_tools/i18n_config.py +9 -0
  12. folio_migration_tools/library_configuration.py +173 -13
  13. folio_migration_tools/mapper_base.py +317 -106
  14. folio_migration_tools/mapping_file_transformation/courses_mapper.py +203 -0
  15. folio_migration_tools/mapping_file_transformation/holdings_mapper.py +83 -69
  16. folio_migration_tools/mapping_file_transformation/item_mapper.py +98 -94
  17. folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +352 -0
  18. folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +702 -223
  19. folio_migration_tools/mapping_file_transformation/notes_mapper.py +90 -0
  20. folio_migration_tools/mapping_file_transformation/order_mapper.py +492 -0
  21. folio_migration_tools/mapping_file_transformation/organization_mapper.py +389 -0
  22. folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +38 -27
  23. folio_migration_tools/mapping_file_transformation/user_mapper.py +149 -361
  24. folio_migration_tools/marc_rules_transformation/conditions.py +650 -246
  25. folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +292 -130
  26. folio_migration_tools/marc_rules_transformation/hrid_handler.py +244 -0
  27. folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +20846 -0
  28. folio_migration_tools/marc_rules_transformation/marc_file_processor.py +300 -0
  29. folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +136 -0
  30. folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +241 -0
  31. folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +681 -201
  32. folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +395 -429
  33. folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +531 -100
  34. folio_migration_tools/migration_report.py +85 -38
  35. folio_migration_tools/migration_tasks/__init__.py +1 -3
  36. folio_migration_tools/migration_tasks/authority_transformer.py +119 -0
  37. folio_migration_tools/migration_tasks/batch_poster.py +911 -198
  38. folio_migration_tools/migration_tasks/bibs_transformer.py +121 -116
  39. folio_migration_tools/migration_tasks/courses_migrator.py +192 -0
  40. folio_migration_tools/migration_tasks/holdings_csv_transformer.py +252 -247
  41. folio_migration_tools/migration_tasks/holdings_marc_transformer.py +321 -115
  42. folio_migration_tools/migration_tasks/items_transformer.py +264 -84
  43. folio_migration_tools/migration_tasks/loans_migrator.py +506 -195
  44. folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +187 -0
  45. folio_migration_tools/migration_tasks/migration_task_base.py +364 -74
  46. folio_migration_tools/migration_tasks/orders_transformer.py +373 -0
  47. folio_migration_tools/migration_tasks/organization_transformer.py +451 -0
  48. folio_migration_tools/migration_tasks/requests_migrator.py +130 -62
  49. folio_migration_tools/migration_tasks/reserves_migrator.py +253 -0
  50. folio_migration_tools/migration_tasks/user_transformer.py +180 -139
  51. folio_migration_tools/task_configuration.py +46 -0
  52. folio_migration_tools/test_infrastructure/__init__.py +0 -0
  53. folio_migration_tools/test_infrastructure/mocked_classes.py +406 -0
  54. folio_migration_tools/transaction_migration/legacy_loan.py +148 -34
  55. folio_migration_tools/transaction_migration/legacy_request.py +65 -25
  56. folio_migration_tools/transaction_migration/legacy_reserve.py +47 -0
  57. folio_migration_tools/transaction_migration/transaction_result.py +12 -1
  58. folio_migration_tools/translations/en.json +476 -0
  59. folio_migration_tools-1.9.10.dist-info/METADATA +169 -0
  60. folio_migration_tools-1.9.10.dist-info/RECORD +67 -0
  61. {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info}/WHEEL +1 -2
  62. folio_migration_tools-1.9.10.dist-info/entry_points.txt +3 -0
  63. folio_migration_tools/generate_schemas.py +0 -46
  64. folio_migration_tools/mapping_file_transformation/mapping_file_mapping_base_impl.py +0 -44
  65. folio_migration_tools/mapping_file_transformation/user_mapper_base.py +0 -212
  66. folio_migration_tools/marc_rules_transformation/bibs_processor.py +0 -163
  67. folio_migration_tools/marc_rules_transformation/holdings_processor.py +0 -284
  68. folio_migration_tools/report_blurbs.py +0 -219
  69. folio_migration_tools/transaction_migration/legacy_fee_fine.py +0 -36
  70. folio_migration_tools-1.2.1.dist-info/METADATA +0 -134
  71. folio_migration_tools-1.2.1.dist-info/RECORD +0 -50
  72. folio_migration_tools-1.2.1.dist-info/top_level.txt +0 -1
  73. {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info/licenses}/LICENSE +0 -0
@@ -1,11 +1,18 @@
1
1
  import datetime
2
2
  import logging
3
3
  import uuid
4
+ from zoneinfo import ZoneInfo
5
+
6
+ from dateutil import tz
4
7
  from dateutil.parser import parse
5
8
 
9
+ from folio_migration_tools.custom_exceptions import TransformationRecordFailedError
10
+
11
+ utc = ZoneInfo("UTC")
12
+
6
13
 
7
14
  class LegacyRequest(object):
8
- def __init__(self, legacy_request_dict, utc_difference=0, row=0):
15
+ def __init__(self, legacy_request_dict, tenant_timezone=utc, row=0):
9
16
  # validate
10
17
  correct_headers = [
11
18
  "item_barcode",
@@ -26,59 +33,70 @@ class LegacyRequest(object):
26
33
 
27
34
  self.item_barcode = legacy_request_dict["item_barcode"].strip()
28
35
  self.patron_id = ""
29
- self.utc_difference = utc_difference
36
+ self.tenant_timezone = tenant_timezone
30
37
  self.item_id = ""
38
+ self.instance_id = ""
39
+ self.holdings_record_id = ""
31
40
  self.patron_barcode = legacy_request_dict["patron_barcode"].strip()
32
41
  self.comment = legacy_request_dict["comment"].strip()
33
42
  self.request_type = legacy_request_dict["request_type"].strip()
34
- self.pickup_servicepoint_id = legacy_request_dict[
35
- "pickup_servicepoint_id"
36
- ].strip()
43
+ self.pickup_servicepoint_id = legacy_request_dict["pickup_servicepoint_id"].strip()
37
44
  self.fulfillment_preference = "Hold Shelf"
38
45
 
39
46
  if self.request_type not in ["Hold", "Recall", "Page"]:
40
47
  self.errors.append((f"{self.request_type} not allowd", "request_type"))
41
48
 
42
49
  try:
43
- temp_request_date: datetime.datetime = parse(
44
- legacy_request_dict["request_date"]
45
- )
50
+ temp_request_date: datetime.datetime = parse(legacy_request_dict["request_date"])
51
+ if temp_request_date.tzinfo != tz.UTC:
52
+ temp_request_date = temp_request_date.replace(tzinfo=self.tenant_timezone)
46
53
  except Exception:
47
54
  self.errors.append(("Parse date failure. Setting UTC NOW", "request_date"))
48
- temp_request_date = datetime.now(datetime.timezone.utc)
55
+ temp_request_date = datetime.now(ZoneInfo("UTC"))
49
56
  try:
50
57
  temp_expiration_date: datetime.datetime = parse(
51
58
  legacy_request_dict["request_expiration_date"]
52
59
  )
60
+ if temp_expiration_date.tzinfo != tz.UTC:
61
+ temp_expiration_date = temp_expiration_date.replace(tzinfo=self.tenant_timezone)
53
62
  except Exception:
54
- temp_expiration_date = datetime.now(datetime.timezone.utc)
55
- self.errors.append(
56
- ("Parse date failure. Setting UTC NOW", "request_expiration_date")
57
- )
63
+ temp_expiration_date = datetime.now(ZoneInfo("UTC"))
64
+ self.errors.append(("Parse date failure. Setting UTC NOW", "request_expiration_date"))
65
+ if temp_expiration_date.hour == 0 and temp_expiration_date.minute == 0:
66
+ temp_expiration_date = temp_expiration_date.replace(hour=23, minute=59)
58
67
 
59
68
  self.request_date: datetime.datetime = temp_request_date
60
69
  self.request_expiration_date: datetime.datetime = temp_expiration_date
70
+ self.correct_for_1_day_requests()
71
+
72
+ def correct_for_1_day_requests(self):
61
73
  try:
62
- self.make_request_utc()
63
- if self.request_expiration_date <= self.request_date:
64
- if self.request_expiration_date.hour == 0:
74
+ if self.request_expiration_date.date() <= self.request_date.date():
75
+ if (
76
+ self.request_expiration_date.hour == 0
77
+ and self.request_expiration_date.minute == 0
78
+ ):
65
79
  self.request_expiration_date = self.request_expiration_date.replace(
66
80
  hour=23, minute=59
67
81
  )
68
- if self.request_date.hour == 0:
82
+ if self.request_date.hour == 0 and self.request_date.minute == 0:
69
83
  self.request_date = self.request_date.replace(hour=0, minute=1)
84
+ self.make_request_utc()
70
85
  except Exception as ee:
71
86
  logging.error(ee)
72
87
  self.errors.append(("Time alignment issues", "both dates"))
73
88
 
74
89
  def to_dict(self):
75
90
  return {
91
+ "requestLevel": "Item",
76
92
  "requestType": self.request_type,
77
93
  "fulfilmentPreference": self.fulfillment_preference,
78
94
  "requester": {"barcode": self.patron_barcode},
79
95
  "requesterId": self.patron_id,
80
96
  "item": {"barcode": self.item_barcode},
81
97
  "itemId": self.item_id,
98
+ "instanceId": self.instance_id,
99
+ "holdingsRecordId": self.holdings_record_id,
82
100
  "requestExpirationDate": self.request_expiration_date.isoformat(),
83
101
  "patronComments": self.comment,
84
102
  "pickupServicePointId": self.pickup_servicepoint_id,
@@ -86,6 +104,28 @@ class LegacyRequest(object):
86
104
  "id": str(uuid.uuid4()),
87
105
  }
88
106
 
107
+ def serialize(self):
108
+ req = self.to_dict()
109
+ required = [
110
+ "instanceId",
111
+ "requesterId",
112
+ "requestType",
113
+ "requestLevel",
114
+ "requestDate",
115
+ "holdingsRecordId",
116
+ "itemId",
117
+ "fulfilmentPreference",
118
+ "pickupServicePointId",
119
+ ]
120
+ if req["requestLevel"] == "Title":
121
+ required = [r for r in required if r not in ["itemId", "holdingsRecordId"]]
122
+ missing = [r for r in required if not req.get(r, "")]
123
+ if any(missing):
124
+ raise TransformationRecordFailedError(
125
+ "", "Required properties missing:" ", ".join(missing)
126
+ )
127
+ return req
128
+
89
129
  def to_source_dict(self):
90
130
  return {
91
131
  "item_barcode": self.item_barcode,
@@ -98,11 +138,11 @@ class LegacyRequest(object):
98
138
  }
99
139
 
100
140
  def make_request_utc(self):
101
- if self.utc_difference != 0:
102
- self.request_date = self.request_date + datetime.timedelta(
103
- hours=self.utc_difference
104
- )
105
- self.request_expiration_date = (
106
- self.request_expiration_date
107
- + datetime.timedelta(hours=self.utc_difference)
108
- )
141
+ try:
142
+ if self.tenant_timezone != ZoneInfo("UTC"):
143
+ self.request_date = self.request_date.astimezone(ZoneInfo("UTC"))
144
+ self.request_expiration_date = self.request_expiration_date.astimezone(
145
+ ZoneInfo("UTC")
146
+ )
147
+ except Exception:
148
+ self.errors.append(("UTC correction issues", "both dates"))
@@ -0,0 +1,47 @@
1
+ import uuid
2
+ from typing import Dict, List, Tuple
3
+
4
+ from folio_uuid.folio_namespaces import FOLIONamespaces
5
+ from folio_uuid.folio_uuid import FolioUUID
6
+ from folioclient import FolioClient
7
+
8
+ from folio_migration_tools.custom_exceptions import TransformationProcessError
9
+
10
+
11
+ class LegacyReserve(object):
12
+ def __init__(self, legacy_request_dict: Dict, folio_client: FolioClient, row: int = 0):
13
+ # validate
14
+ correct_headers = ["legacy_identifier", "item_barcode"]
15
+ for h in correct_headers:
16
+ if h not in legacy_request_dict:
17
+ raise TransformationProcessError(
18
+ row,
19
+ "Missing header in file. The following are required:",
20
+ ", ".join(correct_headers),
21
+ )
22
+ self.errors: List[Tuple[str, str]] = [
23
+ ("Missing properties in legacy data", prop)
24
+ for prop in correct_headers
25
+ if prop not in legacy_request_dict
26
+ ]
27
+ self.id = str(uuid.uuid4())
28
+ self.item_barcode: str = legacy_request_dict["item_barcode"].strip()
29
+ if not self.item_barcode:
30
+ self.errors.append(("Missing data.", "item_barcode"))
31
+ self.legacy_identifier: str = legacy_request_dict["legacy_identifier"].strip()
32
+ if not self.legacy_identifier:
33
+ self.errors.append(("Missing data.", "legacy_identifier"))
34
+ self.course_listing_id: str = str(
35
+ FolioUUID(
36
+ folio_client.gateway_url,
37
+ FOLIONamespaces.course_listing,
38
+ legacy_request_dict["legacy_identifier"],
39
+ )
40
+ )
41
+
42
+ def to_dict(self):
43
+ return {
44
+ "courseListingId": self.course_listing_id,
45
+ "copiedItem": {"barcode": self.item_barcode},
46
+ "id": self.id,
47
+ }
@@ -1,9 +1,20 @@
1
+ from typing import Any
2
+
3
+
1
4
  class TransactionResult(object):
5
+ __slots__ = [
6
+ "was_successful",
7
+ "folio_loan",
8
+ "should_be_retried",
9
+ "error_message",
10
+ "migration_report_message",
11
+ ]
12
+
2
13
  def __init__(
3
14
  self,
4
15
  was_successful: bool,
5
16
  should_be_retried: bool,
6
- folio_loan: str,
17
+ folio_loan: Any,
7
18
  error_message: str,
8
19
  migration_report_message: str,
9
20
  ):