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.
- folio_migration_tools/__init__.py +11 -0
- folio_migration_tools/__main__.py +169 -85
- folio_migration_tools/circulation_helper.py +96 -59
- folio_migration_tools/config_file_load.py +66 -0
- folio_migration_tools/custom_dict.py +6 -4
- folio_migration_tools/custom_exceptions.py +21 -19
- folio_migration_tools/extradata_writer.py +46 -0
- folio_migration_tools/folder_structure.py +63 -66
- folio_migration_tools/helper.py +29 -21
- folio_migration_tools/holdings_helper.py +57 -34
- folio_migration_tools/i18n_config.py +9 -0
- folio_migration_tools/library_configuration.py +173 -13
- folio_migration_tools/mapper_base.py +317 -106
- folio_migration_tools/mapping_file_transformation/courses_mapper.py +203 -0
- folio_migration_tools/mapping_file_transformation/holdings_mapper.py +83 -69
- folio_migration_tools/mapping_file_transformation/item_mapper.py +98 -94
- folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +352 -0
- folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +702 -223
- folio_migration_tools/mapping_file_transformation/notes_mapper.py +90 -0
- folio_migration_tools/mapping_file_transformation/order_mapper.py +492 -0
- folio_migration_tools/mapping_file_transformation/organization_mapper.py +389 -0
- folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +38 -27
- folio_migration_tools/mapping_file_transformation/user_mapper.py +149 -361
- folio_migration_tools/marc_rules_transformation/conditions.py +650 -246
- folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +292 -130
- folio_migration_tools/marc_rules_transformation/hrid_handler.py +244 -0
- folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +20846 -0
- folio_migration_tools/marc_rules_transformation/marc_file_processor.py +300 -0
- folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +136 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +241 -0
- folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +681 -201
- folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +395 -429
- folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +531 -100
- folio_migration_tools/migration_report.py +85 -38
- folio_migration_tools/migration_tasks/__init__.py +1 -3
- folio_migration_tools/migration_tasks/authority_transformer.py +119 -0
- folio_migration_tools/migration_tasks/batch_poster.py +911 -198
- folio_migration_tools/migration_tasks/bibs_transformer.py +121 -116
- folio_migration_tools/migration_tasks/courses_migrator.py +192 -0
- folio_migration_tools/migration_tasks/holdings_csv_transformer.py +252 -247
- folio_migration_tools/migration_tasks/holdings_marc_transformer.py +321 -115
- folio_migration_tools/migration_tasks/items_transformer.py +264 -84
- folio_migration_tools/migration_tasks/loans_migrator.py +506 -195
- folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +187 -0
- folio_migration_tools/migration_tasks/migration_task_base.py +364 -74
- folio_migration_tools/migration_tasks/orders_transformer.py +373 -0
- folio_migration_tools/migration_tasks/organization_transformer.py +451 -0
- folio_migration_tools/migration_tasks/requests_migrator.py +130 -62
- folio_migration_tools/migration_tasks/reserves_migrator.py +253 -0
- folio_migration_tools/migration_tasks/user_transformer.py +180 -139
- folio_migration_tools/task_configuration.py +46 -0
- folio_migration_tools/test_infrastructure/__init__.py +0 -0
- folio_migration_tools/test_infrastructure/mocked_classes.py +406 -0
- folio_migration_tools/transaction_migration/legacy_loan.py +148 -34
- folio_migration_tools/transaction_migration/legacy_request.py +65 -25
- folio_migration_tools/transaction_migration/legacy_reserve.py +47 -0
- folio_migration_tools/transaction_migration/transaction_result.py +12 -1
- folio_migration_tools/translations/en.json +476 -0
- folio_migration_tools-1.9.10.dist-info/METADATA +169 -0
- folio_migration_tools-1.9.10.dist-info/RECORD +67 -0
- {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info}/WHEEL +1 -2
- folio_migration_tools-1.9.10.dist-info/entry_points.txt +3 -0
- folio_migration_tools/generate_schemas.py +0 -46
- folio_migration_tools/mapping_file_transformation/mapping_file_mapping_base_impl.py +0 -44
- folio_migration_tools/mapping_file_transformation/user_mapper_base.py +0 -212
- folio_migration_tools/marc_rules_transformation/bibs_processor.py +0 -163
- folio_migration_tools/marc_rules_transformation/holdings_processor.py +0 -284
- folio_migration_tools/report_blurbs.py +0 -219
- folio_migration_tools/transaction_migration/legacy_fee_fine.py +0 -36
- folio_migration_tools-1.2.1.dist-info/METADATA +0 -134
- folio_migration_tools-1.2.1.dist-info/RECORD +0 -50
- folio_migration_tools-1.2.1.dist-info/top_level.txt +0 -1
- {folio_migration_tools-1.2.1.dist-info → folio_migration_tools-1.9.10.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import ctypes
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from os.path import isfile
|
|
8
|
+
from typing import List, Optional, Annotated
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
import i18n
|
|
12
|
+
from deepdiff import DeepDiff
|
|
13
|
+
from folio_uuid.folio_namespaces import FOLIONamespaces
|
|
14
|
+
|
|
15
|
+
from folio_migration_tools.custom_exceptions import (
|
|
16
|
+
TransformationProcessError,
|
|
17
|
+
TransformationRecordFailedError,
|
|
18
|
+
)
|
|
19
|
+
from folio_migration_tools.helper import Helper
|
|
20
|
+
from folio_migration_tools.library_configuration import (
|
|
21
|
+
FileDefinition,
|
|
22
|
+
LibraryConfiguration,
|
|
23
|
+
)
|
|
24
|
+
from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
|
|
25
|
+
MappingFileMapperBase,
|
|
26
|
+
)
|
|
27
|
+
from folio_migration_tools.mapping_file_transformation.order_mapper import (
|
|
28
|
+
CompositeOrderMapper,
|
|
29
|
+
)
|
|
30
|
+
from folio_migration_tools.migration_tasks.migration_task_base import (
|
|
31
|
+
MigrationTaskBase,
|
|
32
|
+
)
|
|
33
|
+
from folio_migration_tools.task_configuration import AbstractTaskConfiguration
|
|
34
|
+
|
|
35
|
+
csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Read files and do some work
|
|
39
|
+
class OrdersTransformer(MigrationTaskBase):
|
|
40
|
+
class TaskConfiguration(AbstractTaskConfiguration):
|
|
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
|
+
] = ""
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def get_object_type() -> FOLIONamespaces:
|
|
146
|
+
return FOLIONamespaces.orders
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
task_config: TaskConfiguration,
|
|
151
|
+
library_config: LibraryConfiguration,
|
|
152
|
+
folio_client,
|
|
153
|
+
use_logging: bool = True,
|
|
154
|
+
):
|
|
155
|
+
csv.register_dialect("tsv", delimiter="\t")
|
|
156
|
+
|
|
157
|
+
super().__init__(library_config, task_config, folio_client, use_logging)
|
|
158
|
+
self.object_type_name = self.get_object_type().name
|
|
159
|
+
self.task_config = task_config
|
|
160
|
+
self.task_configuration = self.task_config
|
|
161
|
+
self.files = self.list_source_files()
|
|
162
|
+
self.total_records = 0
|
|
163
|
+
self.current_folio_record: dict = {}
|
|
164
|
+
self.orders_map = self.setup_records_map(
|
|
165
|
+
self.folder_structure.mapping_files_folder / self.task_config.orders_mapping_file_name
|
|
166
|
+
)
|
|
167
|
+
self.results_path = self.folder_structure.created_objects_path
|
|
168
|
+
self.failed_files: List[str] = []
|
|
169
|
+
|
|
170
|
+
self.folio_keys = []
|
|
171
|
+
self.folio_keys = MappingFileMapperBase.get_mapped_folio_properties_from_map(
|
|
172
|
+
self.orders_map
|
|
173
|
+
)
|
|
174
|
+
self.minted_ids: set = set()
|
|
175
|
+
|
|
176
|
+
self.mapper = CompositeOrderMapper(
|
|
177
|
+
self.folio_client,
|
|
178
|
+
self.library_configuration,
|
|
179
|
+
self.task_configuration,
|
|
180
|
+
self.orders_map,
|
|
181
|
+
self.load_id_map(self.folder_structure.organizations_id_map_path, True),
|
|
182
|
+
self.load_instance_id_map(True),
|
|
183
|
+
self.load_ref_data_mapping_file(
|
|
184
|
+
"acquisitionMethod",
|
|
185
|
+
self.folder_structure.mapping_files_folder
|
|
186
|
+
/ self.task_config.acquisition_method_map_file_name,
|
|
187
|
+
self.folio_keys,
|
|
188
|
+
),
|
|
189
|
+
self.load_ref_data_mapping_file( # Not required, on POL
|
|
190
|
+
"paymentStatus",
|
|
191
|
+
self.folder_structure.mapping_files_folder
|
|
192
|
+
/ self.task_config.payment_status_map_file_name,
|
|
193
|
+
self.folio_keys,
|
|
194
|
+
False,
|
|
195
|
+
),
|
|
196
|
+
self.load_ref_data_mapping_file( # Not required, on POL
|
|
197
|
+
"receiptStatus",
|
|
198
|
+
self.folder_structure.mapping_files_folder
|
|
199
|
+
/ self.task_config.receipt_status_map_file_name,
|
|
200
|
+
self.folio_keys,
|
|
201
|
+
False,
|
|
202
|
+
),
|
|
203
|
+
self.load_ref_data_mapping_file( # Not required
|
|
204
|
+
"workflowStatus",
|
|
205
|
+
self.folder_structure.mapping_files_folder
|
|
206
|
+
/ self.task_config.workflow_status_map_file_name,
|
|
207
|
+
self.folio_keys,
|
|
208
|
+
False,
|
|
209
|
+
),
|
|
210
|
+
self.load_ref_data_mapping_file(
|
|
211
|
+
"locationMap",
|
|
212
|
+
self.folder_structure.mapping_files_folder
|
|
213
|
+
/ self.task_config.location_map_file_name,
|
|
214
|
+
self.folio_keys,
|
|
215
|
+
False,
|
|
216
|
+
),
|
|
217
|
+
self.load_ref_data_mapping_file( # Required if there was is a fund.
|
|
218
|
+
"fundsMap",
|
|
219
|
+
self.folder_structure.mapping_files_folder
|
|
220
|
+
/ self.task_config.funds_map_file_name,
|
|
221
|
+
self.folio_keys,
|
|
222
|
+
True,
|
|
223
|
+
),
|
|
224
|
+
self.load_ref_data_mapping_file( # Todo: The property in the schema has no type
|
|
225
|
+
"fundsExpenseClassMap",
|
|
226
|
+
self.folder_structure.mapping_files_folder
|
|
227
|
+
/ self.task_config.funds_expense_class_map_file_name,
|
|
228
|
+
self.folio_keys,
|
|
229
|
+
False,
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def list_source_files(self):
|
|
234
|
+
files = [
|
|
235
|
+
self.folder_structure.data_folder / self.object_type_name / f.file_name
|
|
236
|
+
for f in self.task_config.files
|
|
237
|
+
if isfile(self.folder_structure.data_folder / self.object_type_name / f.file_name)
|
|
238
|
+
]
|
|
239
|
+
if not any(files):
|
|
240
|
+
ret_str = ",".join(f.file_name for f in self.task_config.files)
|
|
241
|
+
raise TransformationProcessError(
|
|
242
|
+
f"Files {ret_str} not found in"
|
|
243
|
+
"{self.folder_structure.data_folder} / {self.object_type_name}"
|
|
244
|
+
)
|
|
245
|
+
logging.info("Files to process:")
|
|
246
|
+
for filename in files:
|
|
247
|
+
logging.info("\t%s", filename)
|
|
248
|
+
return files
|
|
249
|
+
|
|
250
|
+
def process_single_file(self, filename):
|
|
251
|
+
with open(filename, encoding="utf-8-sig") as records_file, open(
|
|
252
|
+
self.folder_structure.created_objects_path, "w+"
|
|
253
|
+
) as results_file:
|
|
254
|
+
self.mapper.migration_report.add_general_statistics(
|
|
255
|
+
i18n.t("Number of files processed")
|
|
256
|
+
)
|
|
257
|
+
start = time.time()
|
|
258
|
+
records_processed = 0
|
|
259
|
+
for idx, record in enumerate(self.mapper.get_objects(records_file, filename)):
|
|
260
|
+
records_processed += 1
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
# Print first legacy record, then first transformed record
|
|
264
|
+
if idx == 0:
|
|
265
|
+
logging.info("First legacy record:")
|
|
266
|
+
logging.info(json.dumps(record, indent=4))
|
|
267
|
+
|
|
268
|
+
folio_rec, legacy_id = self.mapper.do_map(
|
|
269
|
+
record, f"row {idx}", FOLIONamespaces.orders, True
|
|
270
|
+
)
|
|
271
|
+
self.mapper.perform_additional_mapping(legacy_id, folio_rec)
|
|
272
|
+
|
|
273
|
+
self.mapper.migration_report.add_general_statistics(
|
|
274
|
+
i18n.t("TOTAL Purchase Order Lines created")
|
|
275
|
+
)
|
|
276
|
+
self.mapper.report_folio_mapping(folio_rec, self.mapper.composite_order_schema)
|
|
277
|
+
self.mapper.notes_mapper.map_notes(
|
|
278
|
+
record,
|
|
279
|
+
legacy_id,
|
|
280
|
+
folio_rec["id"],
|
|
281
|
+
FOLIONamespaces.orders,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
self.merge_into_orders_with_embedded_pols(folio_rec, results_file)
|
|
285
|
+
|
|
286
|
+
except TransformationProcessError as process_error:
|
|
287
|
+
self.mapper.handle_transformation_process_error(idx, process_error)
|
|
288
|
+
except TransformationRecordFailedError as error:
|
|
289
|
+
self.mapper.handle_transformation_record_failed_error(idx, error)
|
|
290
|
+
except Exception as excepion:
|
|
291
|
+
self.mapper.handle_generic_exception(idx, excepion)
|
|
292
|
+
|
|
293
|
+
# TODO Rewrite to base % value on number of rows in file
|
|
294
|
+
if idx > 1 and idx % 50 == 0:
|
|
295
|
+
elapsed = idx / (time.time() - start)
|
|
296
|
+
elapsed_formatted = "{0:.4g}".format(elapsed)
|
|
297
|
+
logging.info( # pylint: disable=logging-fstring-interpolation
|
|
298
|
+
f"{idx:,} records processed. Recs/sec: {elapsed_formatted} "
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
self.total_records = records_processed
|
|
302
|
+
|
|
303
|
+
logging.info( # pylint: disable=logging-fstring-interpolation
|
|
304
|
+
f"Done processing {filename} containing {self.total_records:,} records. "
|
|
305
|
+
f"Total records processed: {self.total_records:,}"
|
|
306
|
+
)
|
|
307
|
+
logging.info("Storing last record to disk")
|
|
308
|
+
Helper.write_to_file(results_file, self.current_folio_record)
|
|
309
|
+
self.mapper.migration_report.add_general_statistics(
|
|
310
|
+
i18n.t("TOTAL Purchase Orders created")
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
def do_work(self):
|
|
314
|
+
logging.info("Getting started!")
|
|
315
|
+
for file in self.files:
|
|
316
|
+
logging.info("Processing %s", file)
|
|
317
|
+
try:
|
|
318
|
+
print(file)
|
|
319
|
+
self.process_single_file(file)
|
|
320
|
+
except Exception as ee:
|
|
321
|
+
error_str = (
|
|
322
|
+
f"Processing of {file} failed:\n{ee}."
|
|
323
|
+
"Check source files for empty lines or missing reference data"
|
|
324
|
+
)
|
|
325
|
+
logging.exception(error_str)
|
|
326
|
+
self.mapper.migration_report.add("FailedFiles", f"{file} - {ee}")
|
|
327
|
+
sys.exit()
|
|
328
|
+
|
|
329
|
+
def wrap_up(self):
|
|
330
|
+
logging.info("Done. Wrapping up...")
|
|
331
|
+
with open(self.folder_structure.migration_reports_file, "w") as migration_report_file:
|
|
332
|
+
logging.info(
|
|
333
|
+
"Writing migration- and mapping report to %s",
|
|
334
|
+
self.folder_structure.migration_reports_file,
|
|
335
|
+
)
|
|
336
|
+
self.mapper.migration_report.write_migration_report(
|
|
337
|
+
i18n.t("Pruchase Orders and Purchase Order Lines Transformation Report"),
|
|
338
|
+
migration_report_file,
|
|
339
|
+
self.start_datetime,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
Helper.print_mapping_report(
|
|
343
|
+
migration_report_file,
|
|
344
|
+
self.total_records,
|
|
345
|
+
self.mapper.mapped_folio_fields,
|
|
346
|
+
self.mapper.mapped_legacy_fields,
|
|
347
|
+
)
|
|
348
|
+
logging.info("All done!")
|
|
349
|
+
|
|
350
|
+
def merge_into_orders_with_embedded_pols(self, folio_rec, results_file):
|
|
351
|
+
# Handle merging and storage
|
|
352
|
+
if not self.current_folio_record:
|
|
353
|
+
self.current_folio_record = folio_rec
|
|
354
|
+
if folio_rec["id"] != self.current_folio_record["id"]:
|
|
355
|
+
# Writes record to file
|
|
356
|
+
Helper.write_to_file(results_file, self.current_folio_record)
|
|
357
|
+
self.mapper.migration_report.add_general_statistics(
|
|
358
|
+
i18n.t("TOTAL Purchase Orders created")
|
|
359
|
+
)
|
|
360
|
+
self.current_folio_record = folio_rec
|
|
361
|
+
|
|
362
|
+
else:
|
|
363
|
+
# Merge if possible
|
|
364
|
+
diff = DeepDiff(self.current_folio_record, folio_rec)
|
|
365
|
+
if "compositePoLines" in diff.affected_root_keys:
|
|
366
|
+
self.current_folio_record.get("compositePoLines", []).extend(
|
|
367
|
+
folio_rec.get("compositePoLines", [])
|
|
368
|
+
)
|
|
369
|
+
self.mapper.migration_report.add_general_statistics(
|
|
370
|
+
i18n.t("Rows merged to create Purchase Orders")
|
|
371
|
+
)
|
|
372
|
+
for key in diff.affected_paths:
|
|
373
|
+
self.mapper.migration_report.add("DiffsBetweenOrders", key)
|