bb-integrations-library 3.0.11__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.
- bb_integrations_lib/__init__.py +0 -0
- bb_integrations_lib/converters/__init__.py +0 -0
- bb_integrations_lib/gravitate/__init__.py +0 -0
- bb_integrations_lib/gravitate/base_api.py +20 -0
- bb_integrations_lib/gravitate/model.py +29 -0
- bb_integrations_lib/gravitate/pe_api.py +122 -0
- bb_integrations_lib/gravitate/rita_api.py +552 -0
- bb_integrations_lib/gravitate/sd_api.py +572 -0
- bb_integrations_lib/gravitate/testing/TTE/sd/models.py +1398 -0
- bb_integrations_lib/gravitate/testing/TTE/sd/tests/test_models.py +2987 -0
- bb_integrations_lib/gravitate/testing/__init__.py +0 -0
- bb_integrations_lib/gravitate/testing/builder.py +55 -0
- bb_integrations_lib/gravitate/testing/openapi.py +70 -0
- bb_integrations_lib/gravitate/testing/util.py +274 -0
- bb_integrations_lib/mappers/__init__.py +0 -0
- bb_integrations_lib/mappers/prices/__init__.py +0 -0
- bb_integrations_lib/mappers/prices/model.py +106 -0
- bb_integrations_lib/mappers/prices/price_mapper.py +127 -0
- bb_integrations_lib/mappers/prices/protocol.py +20 -0
- bb_integrations_lib/mappers/prices/util.py +61 -0
- bb_integrations_lib/mappers/rita_mapper.py +523 -0
- bb_integrations_lib/models/__init__.py +0 -0
- bb_integrations_lib/models/dtn_supplier_invoice.py +487 -0
- bb_integrations_lib/models/enums.py +28 -0
- bb_integrations_lib/models/pipeline_structs.py +76 -0
- bb_integrations_lib/models/probe/probe_event.py +20 -0
- bb_integrations_lib/models/probe/request_data.py +431 -0
- bb_integrations_lib/models/probe/resume_token.py +7 -0
- bb_integrations_lib/models/rita/audit.py +113 -0
- bb_integrations_lib/models/rita/auth.py +30 -0
- bb_integrations_lib/models/rita/bucket.py +17 -0
- bb_integrations_lib/models/rita/config.py +188 -0
- bb_integrations_lib/models/rita/constants.py +19 -0
- bb_integrations_lib/models/rita/crossroads_entities.py +293 -0
- bb_integrations_lib/models/rita/crossroads_mapping.py +428 -0
- bb_integrations_lib/models/rita/crossroads_monitoring.py +78 -0
- bb_integrations_lib/models/rita/crossroads_network.py +41 -0
- bb_integrations_lib/models/rita/crossroads_rules.py +80 -0
- bb_integrations_lib/models/rita/email.py +39 -0
- bb_integrations_lib/models/rita/issue.py +63 -0
- bb_integrations_lib/models/rita/mapping.py +227 -0
- bb_integrations_lib/models/rita/probe.py +58 -0
- bb_integrations_lib/models/rita/reference_data.py +110 -0
- bb_integrations_lib/models/rita/source_system.py +9 -0
- bb_integrations_lib/models/rita/workers.py +76 -0
- bb_integrations_lib/models/sd/bols_and_drops.py +241 -0
- bb_integrations_lib/models/sd/get_order.py +301 -0
- bb_integrations_lib/models/sd/orders.py +18 -0
- bb_integrations_lib/models/sd_api.py +115 -0
- bb_integrations_lib/pipelines/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/order_by_site_product_parser.py +50 -0
- bb_integrations_lib/pipelines/parsers/distribution_report/tank_configs_parser.py +47 -0
- bb_integrations_lib/pipelines/parsers/dtn/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/dtn/dtn_price_parser.py +102 -0
- bb_integrations_lib/pipelines/parsers/dtn/model.py +79 -0
- bb_integrations_lib/pipelines/parsers/price_engine/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/price_engine/parse_accessorials_prices_parser.py +67 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/__init__.py +0 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_merge_parser.py +111 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/price_sync_parser.py +107 -0
- bb_integrations_lib/pipelines/parsers/price_engine/price_file_upload/shared.py +81 -0
- bb_integrations_lib/pipelines/parsers/tank_reading_parser.py +155 -0
- bb_integrations_lib/pipelines/parsers/tank_sales_parser.py +144 -0
- bb_integrations_lib/pipelines/shared/__init__.py +0 -0
- bb_integrations_lib/pipelines/shared/allocation_matching.py +227 -0
- bb_integrations_lib/pipelines/shared/bol_allocation.py +2793 -0
- bb_integrations_lib/pipelines/steps/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/create_accessorials_step.py +80 -0
- bb_integrations_lib/pipelines/steps/distribution_report/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/distribution_report/distribution_report_datafram_to_raw_data.py +33 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_model_history_step.py +50 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_order_by_site_product_step.py +62 -0
- bb_integrations_lib/pipelines/steps/distribution_report/get_tank_configs_step.py +40 -0
- bb_integrations_lib/pipelines/steps/distribution_report/join_distribution_order_dos_step.py +85 -0
- bb_integrations_lib/pipelines/steps/distribution_report/upload_distribution_report_datafram_to_big_query.py +47 -0
- bb_integrations_lib/pipelines/steps/echo_step.py +14 -0
- bb_integrations_lib/pipelines/steps/export_dataframe_to_rawdata_step.py +28 -0
- bb_integrations_lib/pipelines/steps/exporting/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/exporting/bbd_export_payroll_file_step.py +107 -0
- bb_integrations_lib/pipelines/steps/exporting/bbd_export_readings_step.py +236 -0
- bb_integrations_lib/pipelines/steps/exporting/cargas_wholesale_bundle_upload_step.py +33 -0
- bb_integrations_lib/pipelines/steps/exporting/dataframe_flat_file_export.py +29 -0
- bb_integrations_lib/pipelines/steps/exporting/gcs_bucket_export_file_step.py +34 -0
- bb_integrations_lib/pipelines/steps/exporting/keyvu_export_step.py +356 -0
- bb_integrations_lib/pipelines/steps/exporting/pe_price_export_step.py +238 -0
- bb_integrations_lib/pipelines/steps/exporting/platform_science_order_sync_step.py +500 -0
- bb_integrations_lib/pipelines/steps/exporting/save_rawdata_to_disk.py +15 -0
- bb_integrations_lib/pipelines/steps/exporting/sftp_export_file_step.py +60 -0
- bb_integrations_lib/pipelines/steps/exporting/sftp_export_many_files_step.py +23 -0
- bb_integrations_lib/pipelines/steps/exporting/update_exported_orders_table_step.py +64 -0
- bb_integrations_lib/pipelines/steps/filter_step.py +22 -0
- bb_integrations_lib/pipelines/steps/get_latest_sync_date.py +34 -0
- bb_integrations_lib/pipelines/steps/importing/bbd_import_payroll_step.py +30 -0
- bb_integrations_lib/pipelines/steps/importing/get_order_numbers_to_export_step.py +138 -0
- bb_integrations_lib/pipelines/steps/importing/load_file_to_dataframe_step.py +46 -0
- bb_integrations_lib/pipelines/steps/importing/load_imap_attachment_step.py +172 -0
- bb_integrations_lib/pipelines/steps/importing/pe_bulk_sync_price_structure_step.py +68 -0
- bb_integrations_lib/pipelines/steps/importing/pe_price_merge_step.py +86 -0
- bb_integrations_lib/pipelines/steps/importing/sftp_file_config_step.py +124 -0
- bb_integrations_lib/pipelines/steps/importing/test_exact_file_match.py +57 -0
- bb_integrations_lib/pipelines/steps/null_step.py +15 -0
- bb_integrations_lib/pipelines/steps/pe_integration_job_step.py +32 -0
- bb_integrations_lib/pipelines/steps/processing/__init__.py +0 -0
- bb_integrations_lib/pipelines/steps/processing/archive_gcs_step.py +76 -0
- bb_integrations_lib/pipelines/steps/processing/archive_sftp_step.py +48 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_format_tank_readings_step.py +492 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_prices_step.py +54 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_tank_sales_step.py +124 -0
- bb_integrations_lib/pipelines/steps/processing/bbd_upload_tankreading_step.py +80 -0
- bb_integrations_lib/pipelines/steps/processing/convert_bbd_order_to_cargas_step.py +226 -0
- bb_integrations_lib/pipelines/steps/processing/delete_sftp_step.py +33 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/__init__.py +2 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/convert_dtn_invoice_to_sd_model.py +145 -0
- bb_integrations_lib/pipelines/steps/processing/dtn/parse_dtn_invoice_step.py +38 -0
- bb_integrations_lib/pipelines/steps/processing/file_config_parser_step.py +720 -0
- bb_integrations_lib/pipelines/steps/processing/file_config_parser_step_v2.py +418 -0
- bb_integrations_lib/pipelines/steps/processing/get_sd_price_price_request.py +105 -0
- bb_integrations_lib/pipelines/steps/processing/keyvu_upload_deliveryplan_step.py +39 -0
- bb_integrations_lib/pipelines/steps/processing/mark_orders_exported_in_bbd_step.py +185 -0
- bb_integrations_lib/pipelines/steps/processing/pe_price_rows_processing_step.py +174 -0
- bb_integrations_lib/pipelines/steps/processing/send_process_report_step.py +47 -0
- bb_integrations_lib/pipelines/steps/processing/sftp_renamer_step.py +61 -0
- bb_integrations_lib/pipelines/steps/processing/tank_reading_touchup_steps.py +75 -0
- bb_integrations_lib/pipelines/steps/processing/upload_supplier_invoice_step.py +16 -0
- bb_integrations_lib/pipelines/steps/send_attached_in_rita_email_step.py +44 -0
- bb_integrations_lib/pipelines/steps/send_rita_email_step.py +34 -0
- bb_integrations_lib/pipelines/steps/sleep_step.py +24 -0
- bb_integrations_lib/pipelines/wrappers/__init__.py +0 -0
- bb_integrations_lib/pipelines/wrappers/accessorials_transformation.py +104 -0
- bb_integrations_lib/pipelines/wrappers/distribution_report.py +191 -0
- bb_integrations_lib/pipelines/wrappers/export_tank_readings.py +237 -0
- bb_integrations_lib/pipelines/wrappers/import_tank_readings.py +192 -0
- bb_integrations_lib/pipelines/wrappers/wrapper.py +81 -0
- bb_integrations_lib/protocols/__init__.py +0 -0
- bb_integrations_lib/protocols/flat_file.py +210 -0
- bb_integrations_lib/protocols/gravitate_client.py +104 -0
- bb_integrations_lib/protocols/pipelines.py +697 -0
- bb_integrations_lib/provider/__init__.py +0 -0
- bb_integrations_lib/provider/api/__init__.py +0 -0
- bb_integrations_lib/provider/api/cargas/__init__.py +0 -0
- bb_integrations_lib/provider/api/cargas/client.py +43 -0
- bb_integrations_lib/provider/api/cargas/model.py +49 -0
- bb_integrations_lib/provider/api/cargas/protocol.py +23 -0
- bb_integrations_lib/provider/api/dtn/__init__.py +0 -0
- bb_integrations_lib/provider/api/dtn/client.py +128 -0
- bb_integrations_lib/provider/api/dtn/protocol.py +9 -0
- bb_integrations_lib/provider/api/keyvu/__init__.py +0 -0
- bb_integrations_lib/provider/api/keyvu/client.py +30 -0
- bb_integrations_lib/provider/api/keyvu/model.py +149 -0
- bb_integrations_lib/provider/api/macropoint/__init__.py +0 -0
- bb_integrations_lib/provider/api/macropoint/client.py +28 -0
- bb_integrations_lib/provider/api/macropoint/model.py +40 -0
- bb_integrations_lib/provider/api/pc_miler/__init__.py +0 -0
- bb_integrations_lib/provider/api/pc_miler/client.py +130 -0
- bb_integrations_lib/provider/api/pc_miler/model.py +6 -0
- bb_integrations_lib/provider/api/pc_miler/web_services_apis.py +131 -0
- bb_integrations_lib/provider/api/platform_science/__init__.py +0 -0
- bb_integrations_lib/provider/api/platform_science/client.py +147 -0
- bb_integrations_lib/provider/api/platform_science/model.py +82 -0
- bb_integrations_lib/provider/api/quicktrip/__init__.py +0 -0
- bb_integrations_lib/provider/api/quicktrip/client.py +52 -0
- bb_integrations_lib/provider/api/telapoint/__init__.py +0 -0
- bb_integrations_lib/provider/api/telapoint/client.py +68 -0
- bb_integrations_lib/provider/api/telapoint/model.py +178 -0
- bb_integrations_lib/provider/api/warren_rogers/__init__.py +0 -0
- bb_integrations_lib/provider/api/warren_rogers/client.py +207 -0
- bb_integrations_lib/provider/aws/__init__.py +0 -0
- bb_integrations_lib/provider/aws/s3/__init__.py +0 -0
- bb_integrations_lib/provider/aws/s3/client.py +126 -0
- bb_integrations_lib/provider/ftp/__init__.py +0 -0
- bb_integrations_lib/provider/ftp/client.py +140 -0
- bb_integrations_lib/provider/ftp/interface.py +273 -0
- bb_integrations_lib/provider/ftp/model.py +76 -0
- bb_integrations_lib/provider/imap/__init__.py +0 -0
- bb_integrations_lib/provider/imap/client.py +228 -0
- bb_integrations_lib/provider/imap/model.py +3 -0
- bb_integrations_lib/provider/sqlserver/__init__.py +0 -0
- bb_integrations_lib/provider/sqlserver/client.py +106 -0
- bb_integrations_lib/secrets/__init__.py +4 -0
- bb_integrations_lib/secrets/adapters.py +98 -0
- bb_integrations_lib/secrets/credential_models.py +222 -0
- bb_integrations_lib/secrets/factory.py +85 -0
- bb_integrations_lib/secrets/providers.py +160 -0
- bb_integrations_lib/shared/__init__.py +0 -0
- bb_integrations_lib/shared/exceptions.py +25 -0
- bb_integrations_lib/shared/model.py +1039 -0
- bb_integrations_lib/shared/shared_enums.py +510 -0
- bb_integrations_lib/storage/README.md +236 -0
- bb_integrations_lib/storage/__init__.py +0 -0
- bb_integrations_lib/storage/aws/__init__.py +0 -0
- bb_integrations_lib/storage/aws/s3.py +8 -0
- bb_integrations_lib/storage/defaults.py +72 -0
- bb_integrations_lib/storage/gcs/__init__.py +0 -0
- bb_integrations_lib/storage/gcs/client.py +8 -0
- bb_integrations_lib/storage/gcsmanager/__init__.py +0 -0
- bb_integrations_lib/storage/gcsmanager/client.py +8 -0
- bb_integrations_lib/storage/setup.py +29 -0
- bb_integrations_lib/util/__init__.py +0 -0
- bb_integrations_lib/util/cache/__init__.py +0 -0
- bb_integrations_lib/util/cache/custom_ttl_cache.py +75 -0
- bb_integrations_lib/util/cache/protocol.py +9 -0
- bb_integrations_lib/util/config/__init__.py +0 -0
- bb_integrations_lib/util/config/manager.py +391 -0
- bb_integrations_lib/util/config/model.py +41 -0
- bb_integrations_lib/util/exception_logger/__init__.py +0 -0
- bb_integrations_lib/util/exception_logger/exception_logger.py +146 -0
- bb_integrations_lib/util/exception_logger/test.py +114 -0
- bb_integrations_lib/util/utils.py +364 -0
- bb_integrations_lib/workers/__init__.py +0 -0
- bb_integrations_lib/workers/groups.py +13 -0
- bb_integrations_lib/workers/rpc_worker.py +50 -0
- bb_integrations_lib/workers/topics.py +20 -0
- bb_integrations_library-3.0.11.dist-info/METADATA +59 -0
- bb_integrations_library-3.0.11.dist-info/RECORD +217 -0
- bb_integrations_library-3.0.11.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from abc import abstractmethod, ABC
|
|
3
|
+
from datetime import datetime, date
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import Union, Optional, TextIO, Literal, Any, Generator
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, conlist
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RecordType(StrEnum):
|
|
11
|
+
"""Possible types of rows in DTN supplier invoice CSVs."""
|
|
12
|
+
HEADER = "BEGIN"
|
|
13
|
+
ITEM = "ITM"
|
|
14
|
+
RINS = "ITMRIN"
|
|
15
|
+
TAX = "ITMTAX"
|
|
16
|
+
SUMMARY_TAX = "SUMTAX"
|
|
17
|
+
DEFERRED_TAX = "DEFTAX"
|
|
18
|
+
FOOTER = "END"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ParsableRow(ABC):
|
|
22
|
+
"""Abstract class for parsable DTN CSV rows, additionally providing some common helper functions."""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def _try_parse_date(date: str, format: str) -> date | None:
|
|
26
|
+
if date is None:
|
|
27
|
+
return None
|
|
28
|
+
try:
|
|
29
|
+
return datetime.strptime(date, format).date()
|
|
30
|
+
except ValueError:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _try_parse_datetime(date: str, format: str) -> datetime | None:
|
|
35
|
+
try:
|
|
36
|
+
return datetime.strptime(date, format)
|
|
37
|
+
except ValueError:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def _optional_float(val: str) -> float | None:
|
|
42
|
+
return None if not val else float(val)
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _pad_row(row: list, to: int) -> list:
|
|
46
|
+
row_copy = list(row)
|
|
47
|
+
if len(row_copy) < to:
|
|
48
|
+
row_copy.extend([None] * (to - len(row_copy)))
|
|
49
|
+
return row_copy
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _parse_bool(val: str) -> bool:
|
|
53
|
+
match val:
|
|
54
|
+
case "Y":
|
|
55
|
+
return True
|
|
56
|
+
case "N":
|
|
57
|
+
return False
|
|
58
|
+
case _:
|
|
59
|
+
raise ValueError(f"Failed to parse boolean field: {val} is not Y or N")
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def parse(cls, row: list[str]):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Header(ParsableRow, BaseModel):
|
|
68
|
+
"""
|
|
69
|
+
Header row. Contains summary data for the invoice.
|
|
70
|
+
Only 1 allowed, per the file format specification (though we don't enforce this).
|
|
71
|
+
"""
|
|
72
|
+
record_type: RecordType = RecordType.HEADER
|
|
73
|
+
dtn_transaction_number: str
|
|
74
|
+
dtn_version_number: str
|
|
75
|
+
transmission_datetime: datetime
|
|
76
|
+
invoice_number: str
|
|
77
|
+
invoice_date: date
|
|
78
|
+
document_type: str
|
|
79
|
+
seller_name: str
|
|
80
|
+
sold_to_name: str
|
|
81
|
+
sold_to_cust_no: Optional[str] = None
|
|
82
|
+
purchase_order_no: Optional[str] = None
|
|
83
|
+
terms_description: str
|
|
84
|
+
document_grand_total: float
|
|
85
|
+
invoice_due_date: Optional[date] = None
|
|
86
|
+
total_invoice_amount: float
|
|
87
|
+
discount_due_date: Optional[date] = None
|
|
88
|
+
discount: float
|
|
89
|
+
discount_amount: float
|
|
90
|
+
sender_id: str
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def parse(cls, row: list[str]) -> "Header":
|
|
94
|
+
row = cls._pad_row(row, 20)
|
|
95
|
+
common_date_fmt = "%Y%m%d"
|
|
96
|
+
trans_datetime_str = row[3] + "T" + row[4]
|
|
97
|
+
trans_datetime = datetime.strptime(trans_datetime_str, "%Y%m%dT%H%M")
|
|
98
|
+
|
|
99
|
+
return cls(
|
|
100
|
+
dtn_transaction_number=row[1],
|
|
101
|
+
dtn_version_number=row[2],
|
|
102
|
+
transmission_datetime=trans_datetime,
|
|
103
|
+
invoice_number=row[5],
|
|
104
|
+
invoice_date=cls._try_parse_date(row[6], common_date_fmt),
|
|
105
|
+
document_type=row[7],
|
|
106
|
+
seller_name=row[8],
|
|
107
|
+
sold_to_name=row[9],
|
|
108
|
+
sold_to_cust_no=row[10],
|
|
109
|
+
purchase_order_no=row[11],
|
|
110
|
+
terms_description=row[12],
|
|
111
|
+
document_grand_total=float(row[13]),
|
|
112
|
+
invoice_due_date=cls._try_parse_date(row[14], common_date_fmt),
|
|
113
|
+
total_invoice_amount=cls._optional_float(row[15]),
|
|
114
|
+
discount_due_date=cls._try_parse_date(row[16], common_date_fmt),
|
|
115
|
+
discount=cls._optional_float(row[17]),
|
|
116
|
+
discount_amount=cls._optional_float(row[18]),
|
|
117
|
+
sender_id=row[19],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class BilledQuantityIndicator(StrEnum):
|
|
122
|
+
NET = "N"
|
|
123
|
+
GROSS = "G"
|
|
124
|
+
UNKNOWN = "U"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Item(ParsableRow, BaseModel):
|
|
128
|
+
"""Inventory item data row."""
|
|
129
|
+
record_type: RecordType = RecordType.ITEM
|
|
130
|
+
invoice_number: str
|
|
131
|
+
bol_number: Optional[str] = None
|
|
132
|
+
description: str
|
|
133
|
+
dtn_product_code: Optional[str] = None
|
|
134
|
+
supplier_product_code: Optional[str] = None
|
|
135
|
+
quantity_billed: float
|
|
136
|
+
billed_quantity_indicator: BilledQuantityIndicator
|
|
137
|
+
gross_quantity: Optional[float] = None
|
|
138
|
+
net_quantity: Optional[float] = None
|
|
139
|
+
unit_of_measure: str
|
|
140
|
+
rate: Optional[float] = None
|
|
141
|
+
line_total: float
|
|
142
|
+
ship_datetime: Optional[datetime] = None
|
|
143
|
+
ship_from_name: str
|
|
144
|
+
ship_from_address: Optional[str] = None
|
|
145
|
+
ship_from_address_2: Optional[str] = None
|
|
146
|
+
ship_from_city: Optional[str] = None
|
|
147
|
+
ship_from_state: Optional[str] = None
|
|
148
|
+
ship_from_zip: Optional[str] = None
|
|
149
|
+
dtn_splc: Optional[str] = None
|
|
150
|
+
ship_to_number: Optional[str] = None
|
|
151
|
+
ship_to_address: Optional[str] = None
|
|
152
|
+
ship_to_address_2: Optional[str] = None
|
|
153
|
+
ship_to_city: Optional[str] = None
|
|
154
|
+
ship_to_state: Optional[str] = None
|
|
155
|
+
ship_to_zip: Optional[str] = None
|
|
156
|
+
carrier_description: Optional[str] = None
|
|
157
|
+
carrier_fein_number: Optional[str] = None
|
|
158
|
+
original_invoice_number: Optional[str] = None
|
|
159
|
+
contract_number: Optional[str] = None
|
|
160
|
+
order_number: Optional[str] = None
|
|
161
|
+
vehicle_or_tank_number: Optional[str] = None
|
|
162
|
+
rins_records: Optional[list["ItemRins"]] = None
|
|
163
|
+
tax_records: Optional[list["ItemTax"]] = None
|
|
164
|
+
|
|
165
|
+
def add_rins_record(self, rins_record: "ItemRins") -> None:
|
|
166
|
+
if self.rins_records is None:
|
|
167
|
+
self.rins_records = []
|
|
168
|
+
self.rins_records.append(rins_record)
|
|
169
|
+
|
|
170
|
+
def add_tax_record(self, tax_record: "ItemTax") -> None:
|
|
171
|
+
if self.tax_records is None:
|
|
172
|
+
self.tax_records = []
|
|
173
|
+
self.tax_records.append(tax_record)
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def parse(cls, row: list[str]) -> "Item":
|
|
177
|
+
row = cls._pad_row(row, 34)
|
|
178
|
+
common_date_fmt = "%Y%m%d"
|
|
179
|
+
# Note that this is the only field in the invoice where date/time are both optional.
|
|
180
|
+
# If the date is unavailable, time is useless, so we return None.
|
|
181
|
+
# If time is not available, we can still return a datetime with hours/mins/seconds defaulted to 0.
|
|
182
|
+
# If both are available, we'll parse them.
|
|
183
|
+
date_str = row[13]
|
|
184
|
+
time_str = row[14]
|
|
185
|
+
if not date_str:
|
|
186
|
+
ship_datetime = None
|
|
187
|
+
elif date_str and not time_str:
|
|
188
|
+
ship_datetime = datetime.strptime(date_str, common_date_fmt)
|
|
189
|
+
else:
|
|
190
|
+
ship_datetime = datetime.strptime(date_str + "T" + time_str, f"{common_date_fmt}T%H%M")
|
|
191
|
+
return cls(
|
|
192
|
+
invoice_number=row[1],
|
|
193
|
+
bol_number=row[2],
|
|
194
|
+
description=row[3],
|
|
195
|
+
dtn_product_code=row[4],
|
|
196
|
+
supplier_product_code=row[5],
|
|
197
|
+
quantity_billed=cls._optional_float(row[6]),
|
|
198
|
+
billed_quantity_indicator=BilledQuantityIndicator(row[7]),
|
|
199
|
+
gross_quantity=cls._optional_float(row[8]),
|
|
200
|
+
net_quantity=cls._optional_float(row[9]),
|
|
201
|
+
unit_of_measure=row[10],
|
|
202
|
+
rate=cls._optional_float(row[11]),
|
|
203
|
+
line_total=float(row[12]),
|
|
204
|
+
ship_datetime=ship_datetime,
|
|
205
|
+
ship_from_name=row[15],
|
|
206
|
+
ship_from_address=row[16],
|
|
207
|
+
ship_from_address_2=row[17],
|
|
208
|
+
ship_from_city=row[18],
|
|
209
|
+
ship_from_state=row[19],
|
|
210
|
+
ship_from_zip=row[20],
|
|
211
|
+
dtn_splc=row[21],
|
|
212
|
+
ship_to_number=row[22],
|
|
213
|
+
ship_to_address=row[23],
|
|
214
|
+
ship_to_address_2=row[24],
|
|
215
|
+
ship_to_city=row[25],
|
|
216
|
+
ship_to_state=row[26],
|
|
217
|
+
ship_to_zip=row[27],
|
|
218
|
+
carrier_description=row[28],
|
|
219
|
+
carrier_fein_number=row[29],
|
|
220
|
+
original_invoice_number=row[30],
|
|
221
|
+
contract_number=row[31],
|
|
222
|
+
order_number=row[32],
|
|
223
|
+
vehicle_or_tank_number=row[33],
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ItemRins(ParsableRow, BaseModel):
|
|
228
|
+
"""EPA Renewable Information Number record row - follows a specific invoice item, but may be omitted."""
|
|
229
|
+
record_type: RecordType = RecordType.RINS
|
|
230
|
+
rins: str
|
|
231
|
+
supporting_document_number: Optional[str] = None
|
|
232
|
+
reserved_1: str
|
|
233
|
+
reserved_2: str
|
|
234
|
+
reserved_3: str
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def parse(cls, row: list[str]) -> "ItemRins":
|
|
238
|
+
row = cls._pad_row(row, 6)
|
|
239
|
+
return cls(
|
|
240
|
+
rins=row[1],
|
|
241
|
+
supporting_document_number=row[2],
|
|
242
|
+
reserved_1=row[3],
|
|
243
|
+
reserved_2=row[4],
|
|
244
|
+
reserved_3=row[5],
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class ItemTax(ParsableRow, BaseModel):
|
|
249
|
+
"""Tax record for the preceding item. May be omitted, and may occur up to 100 times."""
|
|
250
|
+
record_type: RecordType = RecordType.TAX
|
|
251
|
+
invoice_number: str
|
|
252
|
+
bol_number: Optional[str] = None
|
|
253
|
+
description: str
|
|
254
|
+
quantity_billed: Optional[float] = None
|
|
255
|
+
unit_of_measure: Optional[str] = None
|
|
256
|
+
deferred: bool
|
|
257
|
+
rate: Optional[float] = None
|
|
258
|
+
line_total: float
|
|
259
|
+
reserved: Optional[str] = None
|
|
260
|
+
tax_code: Optional[str] = None
|
|
261
|
+
deferred_due_date: Optional[date] = None
|
|
262
|
+
deferred_invoice_number: Optional[str] = None
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def parse(cls, row: list[str]) -> "ItemTax":
|
|
266
|
+
row = cls._pad_row(row, 13)
|
|
267
|
+
common_date_fmt = "%Y%m%d"
|
|
268
|
+
return cls(
|
|
269
|
+
invoice_number=row[1],
|
|
270
|
+
bol_number=row[2],
|
|
271
|
+
description=row[3],
|
|
272
|
+
quantity_billed=cls._optional_float(row[4]),
|
|
273
|
+
unit_of_measure=row[5],
|
|
274
|
+
deferred=cls._parse_bool(row[6]),
|
|
275
|
+
rate=cls._optional_float(row[7]),
|
|
276
|
+
line_total=cls._optional_float(row[8]),
|
|
277
|
+
reserved=row[9],
|
|
278
|
+
tax_code=row[10],
|
|
279
|
+
deferred_due_date=cls._try_parse_date(row[11], common_date_fmt),
|
|
280
|
+
deferred_invoice_number=row[12],
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class SummaryTax(ParsableRow, BaseModel):
|
|
285
|
+
"""Summary tax invoice rows."""
|
|
286
|
+
record_type: RecordType = RecordType.SUMMARY_TAX
|
|
287
|
+
invoice_number: str
|
|
288
|
+
description: str
|
|
289
|
+
quantity_billed: Optional[float] = None
|
|
290
|
+
unit_of_measure: Optional[str] = None
|
|
291
|
+
deferred: bool
|
|
292
|
+
rate: Optional[float] = None
|
|
293
|
+
line_total: float
|
|
294
|
+
tax_code: Optional[str] = None
|
|
295
|
+
deferred_due_date: Optional[date] = None
|
|
296
|
+
deferred_invoice_number: Optional[str] = None
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def parse(cls, row: list[str]) -> "SummaryTax":
|
|
300
|
+
row = cls._pad_row(row, 11)
|
|
301
|
+
common_date_fmt = "%Y%m%d"
|
|
302
|
+
return cls(
|
|
303
|
+
invoice_number=row[1],
|
|
304
|
+
description=row[2],
|
|
305
|
+
quantity_billed=cls._optional_float(row[3]),
|
|
306
|
+
unit_of_measure=row[4],
|
|
307
|
+
deferred=cls._parse_bool(row[5]),
|
|
308
|
+
rate=cls._optional_float(row[6]),
|
|
309
|
+
line_total=float(row[7]),
|
|
310
|
+
tax_code=row[8],
|
|
311
|
+
deferred_due_date=cls._try_parse_date(row[9], common_date_fmt),
|
|
312
|
+
deferred_invoice_number=row[10]
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class DeferredTaxItem(BaseModel):
|
|
317
|
+
index: int
|
|
318
|
+
amount: Optional[float] = None
|
|
319
|
+
description: Optional[Literal["STATE", "FEDERAL", "UNKNOWN"]] = None
|
|
320
|
+
deferred_date: Optional[date] = None
|
|
321
|
+
deferred_invoice_number: Optional[str] = None
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def all_none(self) -> bool:
|
|
325
|
+
return (
|
|
326
|
+
self.amount is None
|
|
327
|
+
and self.description is None
|
|
328
|
+
and self.deferred_date is None
|
|
329
|
+
and self.deferred_invoice_number is None
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class DeferredTax(ParsableRow, BaseModel):
|
|
334
|
+
"""Deferred tax invoice row. Only expected once per invoice."""
|
|
335
|
+
record_type: RecordType = RecordType.DEFERRED_TAX
|
|
336
|
+
items: conlist(DeferredTaxItem, min_length=1)
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def parse(cls, row: list[str]) -> "DeferredTax":
|
|
340
|
+
row = cls._pad_row(row, 41)
|
|
341
|
+
common_date_fmt = "%Y%m%d"
|
|
342
|
+
|
|
343
|
+
# Iterate over the groups of columns of data that represent each item and extract them
|
|
344
|
+
items = []
|
|
345
|
+
chunked = [row[i:i + 4] for i in range(1, 41, 4)]
|
|
346
|
+
index = 1
|
|
347
|
+
# We pad the row with blank spaces which do not parse as Optionals.
|
|
348
|
+
# 'or None' converts them to a NoneType so they do.
|
|
349
|
+
for group in chunked:
|
|
350
|
+
items.append(
|
|
351
|
+
DeferredTaxItem(
|
|
352
|
+
index=index,
|
|
353
|
+
amount=cls._optional_float(group[0]),
|
|
354
|
+
description=group[1] or None,
|
|
355
|
+
deferred_date=cls._try_parse_date(group[2], common_date_fmt),
|
|
356
|
+
deferred_invoice_number=group[3] or None,
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
index += 1
|
|
360
|
+
if len(items) < 1:
|
|
361
|
+
raise InvalidDTNInvoiceException("Malformed deferred tax item - must have at least 1 entry")
|
|
362
|
+
items = list(filter(lambda x: not x.all_none, items))
|
|
363
|
+
return DeferredTax(
|
|
364
|
+
items=items
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class Footer(ParsableRow, BaseModel):
|
|
369
|
+
"""Footer row. Indicates the record is complete."""
|
|
370
|
+
record_type: RecordType = RecordType.FOOTER
|
|
371
|
+
dtn_transaction_number: str
|
|
372
|
+
record_count: int
|
|
373
|
+
|
|
374
|
+
@classmethod
|
|
375
|
+
def parse(cls, row: list[str]) -> "Footer":
|
|
376
|
+
return cls(
|
|
377
|
+
dtn_transaction_number=row[1],
|
|
378
|
+
record_count=int(row[2]),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
CSVRow: Union[Header, Item, ItemRins, ItemTax, SummaryTax, DeferredTax, Footer] = Field(discriminator="record_type")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class DTNSupplierInvoice(BaseModel):
|
|
386
|
+
header: Header
|
|
387
|
+
items: list[Item]
|
|
388
|
+
summary_taxes: list[SummaryTax]
|
|
389
|
+
deferred_taxes: Optional[DeferredTax] = None
|
|
390
|
+
footer: Footer
|
|
391
|
+
original_filename: Optional[str] = None
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class InvalidDTNInvoiceException(BaseException):
|
|
395
|
+
pass
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class Parser:
|
|
399
|
+
_record_cls = {
|
|
400
|
+
RecordType.HEADER: Header,
|
|
401
|
+
RecordType.ITEM: Item,
|
|
402
|
+
RecordType.RINS: ItemRins,
|
|
403
|
+
RecordType.TAX: ItemTax,
|
|
404
|
+
RecordType.SUMMARY_TAX: SummaryTax,
|
|
405
|
+
RecordType.DEFERRED_TAX: DeferredTax,
|
|
406
|
+
RecordType.FOOTER: Footer,
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
def __init__(self):
|
|
410
|
+
pass
|
|
411
|
+
|
|
412
|
+
def _parse_row(self, row: list[str]) -> CSVRow:
|
|
413
|
+
record_type = RecordType(row[0])
|
|
414
|
+
return self._record_cls[record_type].parse(row)
|
|
415
|
+
|
|
416
|
+
def _map_rows_by_type(self, rows: list[CSVRow]) -> dict[RecordType, list[CSVRow]]:
|
|
417
|
+
types_map = {k: [] for k in self._record_cls.keys()}
|
|
418
|
+
for row in rows:
|
|
419
|
+
types_map[row.record_type].append(row)
|
|
420
|
+
return types_map
|
|
421
|
+
|
|
422
|
+
def parse_all(self, csv_file: TextIO) -> Generator[DTNSupplierInvoice, Any, None]:
|
|
423
|
+
reader = csv.reader(csv_file, delimiter=",")
|
|
424
|
+
abs_line = 0
|
|
425
|
+
while True: # Until EOF is reached (StopIteration raised from next(reader) call)
|
|
426
|
+
header = None
|
|
427
|
+
footer = None
|
|
428
|
+
items = []
|
|
429
|
+
summary_taxes = []
|
|
430
|
+
deferred_taxes = None
|
|
431
|
+
last_item: Item | None = None
|
|
432
|
+
line = 0
|
|
433
|
+
while footer is None:
|
|
434
|
+
abs_line += 1
|
|
435
|
+
try:
|
|
436
|
+
row = next(reader)
|
|
437
|
+
except StopIteration:
|
|
438
|
+
# If we've reached EOF but parsed any number of lines for an additional invoice, then the file is
|
|
439
|
+
# malformed.
|
|
440
|
+
if line > 0:
|
|
441
|
+
raise InvalidDTNInvoiceException("Unexpected EOF")
|
|
442
|
+
return
|
|
443
|
+
line += 1
|
|
444
|
+
parsed = self._parse_row(row)
|
|
445
|
+
if line > 1 and header is None:
|
|
446
|
+
raise InvalidDTNInvoiceException(f"Header row not found before data rows started")
|
|
447
|
+
match parsed.record_type:
|
|
448
|
+
case RecordType.HEADER:
|
|
449
|
+
if header is not None:
|
|
450
|
+
raise InvalidDTNInvoiceException(
|
|
451
|
+
f"Only one header row is allowed (got another at line {abs_line})")
|
|
452
|
+
header = parsed
|
|
453
|
+
case RecordType.FOOTER:
|
|
454
|
+
if footer is not None:
|
|
455
|
+
raise InvalidDTNInvoiceException(
|
|
456
|
+
f"Only one footer row is allowed (got another at line {abs_line})")
|
|
457
|
+
footer = parsed
|
|
458
|
+
case RecordType.ITEM:
|
|
459
|
+
items.append(parsed)
|
|
460
|
+
last_item = parsed
|
|
461
|
+
case RecordType.RINS:
|
|
462
|
+
if last_item is None:
|
|
463
|
+
raise InvalidDTNInvoiceException(
|
|
464
|
+
f"Unattached RINS record at line {abs_line} (encountered before any invoice items were)")
|
|
465
|
+
last_item.add_rins_record(parsed)
|
|
466
|
+
case RecordType.TAX:
|
|
467
|
+
if last_item is None:
|
|
468
|
+
raise InvalidDTNInvoiceException(
|
|
469
|
+
f"Unattached item tax record at line {abs_line} (encountered before any invoice items were)")
|
|
470
|
+
last_item.add_tax_record(parsed)
|
|
471
|
+
case RecordType.SUMMARY_TAX:
|
|
472
|
+
summary_taxes.append(parsed)
|
|
473
|
+
case RecordType.DEFERRED_TAX:
|
|
474
|
+
if deferred_taxes is not None:
|
|
475
|
+
raise InvalidDTNInvoiceException(
|
|
476
|
+
f"Only one deferred tax row is allowed (got another at line {abs_line})")
|
|
477
|
+
deferred_taxes = parsed
|
|
478
|
+
yield DTNSupplierInvoice(
|
|
479
|
+
header=header,
|
|
480
|
+
items=items,
|
|
481
|
+
summary_taxes=summary_taxes,
|
|
482
|
+
deferred_taxes=deferred_taxes,
|
|
483
|
+
footer=footer,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
def parse(self, csv_file: TextIO) -> list[DTNSupplierInvoice]:
|
|
487
|
+
return list(self.parse_all(csv_file))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ProbeEventType(str, Enum):
|
|
5
|
+
create = "create"
|
|
6
|
+
update = "update"
|
|
7
|
+
delete = "delete"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CrossroadsEntityType(str, Enum):
|
|
11
|
+
"""Supported entities for crossroads integrations."""
|
|
12
|
+
# For watching orders in a generic manner (i.e. report when created). Planned for use with Telapoint integration
|
|
13
|
+
order = "order"
|
|
14
|
+
|
|
15
|
+
# For watching orders specifically for the carrier order integration (which will only be kicked off when an order is assigned to a carrier)
|
|
16
|
+
carrier_order = "carrier order integration"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CrossroadsTaskStatus(str, Enum):
|
|
20
|
+
created = "created"
|
|
21
|
+
started = "started"
|
|
22
|
+
succeeded = "succeeded"
|
|
23
|
+
failed = "failed"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ConnectorAction(str, Enum):
|
|
27
|
+
carrier_integration_create_order = "carrier integration - create order for carrier"
|
|
28
|
+
carrier_integration_update_order_status = "carrier integration - update order status for customer"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Any, List, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from bb_integrations_lib.models.rita.config import FileConfig, MaxSync
|
|
6
|
+
from bb_integrations_lib.models.rita.issue import IssueBase
|
|
7
|
+
from bb_integrations_lib.protocols.flat_file import Integration
|
|
8
|
+
from bb_integrations_lib.secrets import IntegrationSecretProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StopBranch(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class StopPipeline(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class NoPipelineData(Exception):
|
|
18
|
+
"""
|
|
19
|
+
To be raised when a pipeline step is unable to find any data to operate on.
|
|
20
|
+
This would cause an error alert.
|
|
21
|
+
A.K.A hard alert/exception.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
class NoPipelineSourceData(Exception):
|
|
26
|
+
"""
|
|
27
|
+
To be raised when a pipeline step is unable to find any data to operate on.
|
|
28
|
+
This would cause an error alert.
|
|
29
|
+
A.K.A hard alert/exception.
|
|
30
|
+
"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
class UploadResult(BaseModel):
|
|
34
|
+
succeeded: int = 0
|
|
35
|
+
failed: int = 0
|
|
36
|
+
succeeded_items: list = []
|
|
37
|
+
|
|
38
|
+
class BBDUploadResult(UploadResult):
|
|
39
|
+
"""Includes info on uploaded data """
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BolExportResults(BaseModel):
|
|
44
|
+
orders: List[Dict[str, Any]]
|
|
45
|
+
errors: List[Dict[str, Any]]
|
|
46
|
+
file_name: str
|
|
47
|
+
order_number_key: str = "OrderNumber"
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def is_empty(self) -> bool:
|
|
51
|
+
return len(self.orders) == 0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PipelineContext(BaseModel):
|
|
55
|
+
"""
|
|
56
|
+
PipelineContext is a general storage for any additional data that a step might want to add for other steps to use
|
|
57
|
+
as desired. The primary use is for steps to add entries to the "included" list for process reports.
|
|
58
|
+
|
|
59
|
+
ALL properties in this class may be unset, because only some steps will set them. If you plan to use a property to
|
|
60
|
+
accomplish anything be sure to test that it has been set beforehand.
|
|
61
|
+
"""
|
|
62
|
+
# Core pipeline tech.
|
|
63
|
+
previous_output: Any = None # this is set if a step has alt_input and wants to access the original output
|
|
64
|
+
logs: List[str] = [] # Logs captured since the last time this field was cleared.
|
|
65
|
+
|
|
66
|
+
# issue reporting
|
|
67
|
+
issues: List[IssueBase] = []
|
|
68
|
+
|
|
69
|
+
# Process report tech.
|
|
70
|
+
file_config: FileConfig | None = None # Usually set by SFTPFileConfigStep
|
|
71
|
+
included: list[dict[str, str]] = [] # Copied into the end-of-run process report, if reporting is enabled.
|
|
72
|
+
included_files: dict[str, str] = {} # Copied into the end-of-run process report, uploaded like logs to GCS
|
|
73
|
+
snapshot_id: str | None = None # Copied into the end-of-run process report, if reporting is enabled
|
|
74
|
+
indexed_field: str | None = None
|
|
75
|
+
extra_data: Optional[Dict] = {}
|
|
76
|
+
max_sync: MaxSync | None = None
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Optional, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from bb_integrations_lib.models.enums import ProbeEventType
|
|
6
|
+
from bb_integrations_lib.models.probe.request_data import RequestData, CreateOrderFromSDOrder, UpdateStatusFromSD, \
|
|
7
|
+
CancelOrderFromSD, SaveBOLsAndDropsFromSD, ErrorRequestData, MacropointLocationUpdate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProbeEvent(BaseModel):
|
|
11
|
+
source_probe: str
|
|
12
|
+
type: ProbeEventType
|
|
13
|
+
timestamp: str # Datetime in ISOformat, UTC
|
|
14
|
+
data: Union[CreateOrderFromSDOrder, UpdateStatusFromSD, CancelOrderFromSD, SaveBOLsAndDropsFromSD, ErrorRequestData, MacropointLocationUpdate] = Field(discriminator="request_type")
|
|
15
|
+
|
|
16
|
+
record_id: Optional[str] = Field(description="The DB ID of the relevant record picked up by the probe. Must be set for all Crossroads operations.", default=None)
|
|
17
|
+
record_id_field: Optional[str] = Field(description="The DB field containing the record_id. Must be set for all Crossroads operations.", default=None)
|
|
18
|
+
record_collection: Optional[str] = Field(description="The DB collection this record belongs to. Must be set for all Crossroads operations.", default=None)
|
|
19
|
+
|
|
20
|
+
|