wizata-dsapi 2.2.0.dev28__tar.gz → 2.2.0.dev29__tar.gz
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.
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/__init__.py +3 -0
- wizata_dsapi-2.2.0.dev29/wizata_dsapi/planned_movement.py +195 -0
- wizata_dsapi-2.2.0.dev29/wizata_dsapi/version.py +1 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/wizata_dsapi_client.py +64 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi.egg-info/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi.egg-info/SOURCES.txt +1 -0
- wizata_dsapi-2.2.0.dev28/wizata_dsapi/version.py +0 -1
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/LICENSE.txt +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/README.rst +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/setup.cfg +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/setup.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/api_config.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/api_dto.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/api_interface.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/bucket.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/business_label.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/connection.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/connection_discovery.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/connection_policy.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/connection_types.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/context.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/dashboard.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/dataframe_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/datapoint.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/datastore.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/deployment.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/ds_dataframe.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/dsapi_json_encoder.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/edge_config.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/edge_device.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/edge_module.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/edge_package.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/evaluation.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/execution.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/execution_log.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/experiment.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/formula.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/graylog_log.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/group_system.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/ilogger.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/insight.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/inventory.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/mlmodel.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/mobile_asset.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/model_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/models/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/models/common.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/notification.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/paged_query_result.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/pipeline.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/pipeline_image.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/plot.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/plots/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/plots/common.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/plots/theme.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/product.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/python_package.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/request.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/script.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/scripts/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/scripts/common.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/search.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/solution_component.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/streamlit_utils.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/template.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/template_config.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/trigger.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/twin.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/twinregistration.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/user.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/wizard_function.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/wizard_request.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi/words.py +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi.egg-info/dependency_links.txt +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi.egg-info/requires.txt +0 -0
- {wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi.egg-info/top_level.txt +0 -0
|
@@ -81,6 +81,9 @@ from .inventory import (InventoryBalance, InventoryStorage, InventoryStorageData
|
|
|
81
81
|
InventoryHistoryEvent, HISTORY_MOVEMENT, HISTORY_COUNT, round_quantity,
|
|
82
82
|
INVENTORY_MOVEMENTS, INVENTORY_BALANCES, INVENTORY_STOCK,
|
|
83
83
|
PRODUCT_TAG, PRODUCT_MESSAGE_KEY)
|
|
84
|
+
from .planned_movement import (PlannedMovement, PlannedMovementSet, PLANNED_MOVEMENT_TYPES,
|
|
85
|
+
PLANNED_MOVEMENT_SNAPSHOT_TYPES, PLANNED_MOVEMENT_DATED_TYPES,
|
|
86
|
+
planned_movement_direction, is_dated_planned_movement)
|
|
84
87
|
from .dashboard import Dashboard, DashboardType
|
|
85
88
|
from .mobile_asset import MobileAssetTile, MobileAssetTileType, MobileAssetConfig
|
|
86
89
|
from .graylog_log import GraylogLog
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Production Planner planned movements (epic DEV-6044, DEV-6136, release 12.2).
|
|
3
|
+
|
|
4
|
+
A planned movement is a quantity of a product the planner must not count as free stock:
|
|
5
|
+
|
|
6
|
+
- stock the system of record has reserved or blocked, reported by its availability
|
|
7
|
+
snapshot (``allocated``, ``hold``, ``openDemand``: one row per product, no date);
|
|
8
|
+
- or a movement expected to happen (``customerOrder``, ``workOrder``,
|
|
9
|
+
``workOrderComponent``, ``purchaseOrder``, ``forecast``: one row per document line,
|
|
10
|
+
due at a date).
|
|
11
|
+
|
|
12
|
+
When an expected movement happens it becomes a real one (a shipped or production
|
|
13
|
+
movement on an inventoryMovement DataPoint) and the planned one closes. Stock itself is
|
|
14
|
+
unchanged: movements and balances stay what they are.
|
|
15
|
+
|
|
16
|
+
A sync writes the **full set** of one source, type and facility at a moment (``asOf``):
|
|
17
|
+
a changed row gets a new version, a row missing from the set is closed. Every version is
|
|
18
|
+
kept (``validFrom`` / ``validTo``), so what was planned at any past moment can be read
|
|
19
|
+
again.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
from .inventory import _ts_in, _ts_out, _uuid_str
|
|
26
|
+
|
|
27
|
+
#: type -> (direction, dated). Snapshot types carry one row per product and no date.
|
|
28
|
+
PLANNED_MOVEMENT_TYPES = {
|
|
29
|
+
"allocated": ("out", False),
|
|
30
|
+
"hold": ("out", False),
|
|
31
|
+
"openDemand": ("out", False),
|
|
32
|
+
"customerOrder": ("out", True),
|
|
33
|
+
"workOrder": ("in", True),
|
|
34
|
+
"workOrderComponent": ("out", True),
|
|
35
|
+
"purchaseOrder": ("in", True),
|
|
36
|
+
"forecast": ("out", True),
|
|
37
|
+
}
|
|
38
|
+
PLANNED_MOVEMENT_SNAPSHOT_TYPES = tuple(t for t, (_, dated) in PLANNED_MOVEMENT_TYPES.items() if not dated)
|
|
39
|
+
PLANNED_MOVEMENT_DATED_TYPES = tuple(t for t, (_, dated) in PLANNED_MOVEMENT_TYPES.items() if dated)
|
|
40
|
+
PLANNED_MOVEMENT_DIRECTIONS = ("in", "out")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def planned_movement_direction(movement_type: str) -> str:
|
|
44
|
+
"""``in`` or ``out`` for a type; a ValueError for an unknown one."""
|
|
45
|
+
if movement_type not in PLANNED_MOVEMENT_TYPES:
|
|
46
|
+
raise ValueError(f"type must be one of {', '.join(PLANNED_MOVEMENT_TYPES)}, not {movement_type!r}"[:300])
|
|
47
|
+
return PLANNED_MOVEMENT_TYPES[movement_type][0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_dated_planned_movement(movement_type: str) -> bool:
|
|
51
|
+
"""True for a type with one row per document line and a due date."""
|
|
52
|
+
planned_movement_direction(movement_type)
|
|
53
|
+
return PLANNED_MOVEMENT_TYPES[movement_type][1]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class PlannedMovement:
|
|
57
|
+
"""
|
|
58
|
+
One version of one planned movement. PostgreSQL ``"PlannedMovement"`` (alembic 0032).
|
|
59
|
+
|
|
60
|
+
- ``source`` names the system it comes from (e.g. ``asc``), ``type`` one of
|
|
61
|
+
:data:`PLANNED_MOVEMENT_TYPES`, ``facility`` the Twin (hardware id) it applies to.
|
|
62
|
+
- A snapshot type is one row per ``product``. A dated type is one row per
|
|
63
|
+
``document`` (as the source writes it) and ``line`` (its position), due at ``due``.
|
|
64
|
+
- Quantities: ``planned`` in total, ``in_progress`` already started (picked),
|
|
65
|
+
``done`` already happened (shipped). What remains open is ``planned - done``; it
|
|
66
|
+
is never stored.
|
|
67
|
+
- ``unit``: on write, the backend unit id the quantities are sent in (of the
|
|
68
|
+
product's category); they are converted to the product's default unit. On read,
|
|
69
|
+
the unit they are stored in: the product's default unit when the row was written,
|
|
70
|
+
None when it had none.
|
|
71
|
+
- ``valid_from`` / ``valid_to``: when this version was true. ``valid_to`` is None
|
|
72
|
+
while it still is.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def route(cls):
|
|
77
|
+
return "planner/plannedmovements"
|
|
78
|
+
|
|
79
|
+
def __init__(self, product: str = None, planned: float = None, document: str = None, line: int = None,
|
|
80
|
+
due=None, in_progress: float = None, done: float = None, partner: str = None,
|
|
81
|
+
movement_type: str = None, source: str = None, facility: str = None,
|
|
82
|
+
direction: str = None, unit=None, movement_id=None):
|
|
83
|
+
self.id: Optional[str] = _uuid_str(movement_id)
|
|
84
|
+
self.source: Optional[str] = source
|
|
85
|
+
self.type: Optional[str] = movement_type
|
|
86
|
+
self.facility: Optional[str] = facility
|
|
87
|
+
self.document: Optional[str] = document
|
|
88
|
+
self.line: Optional[int] = line
|
|
89
|
+
self.direction: Optional[str] = direction
|
|
90
|
+
self.product: Optional[str] = product
|
|
91
|
+
self.partner: Optional[str] = partner
|
|
92
|
+
self.due: Optional[datetime] = _ts_in(due)
|
|
93
|
+
self.planned: Optional[float] = planned
|
|
94
|
+
self.in_progress: Optional[float] = in_progress
|
|
95
|
+
self.done: Optional[float] = done
|
|
96
|
+
self.unit: Optional[str] = _uuid_str(unit)
|
|
97
|
+
self.valid_from: Optional[datetime] = None
|
|
98
|
+
self.valid_to: Optional[datetime] = None
|
|
99
|
+
self.created_at: Optional[datetime] = None
|
|
100
|
+
self.created_by: Optional[str] = None
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def is_open(self) -> bool:
|
|
104
|
+
"""This version is still true."""
|
|
105
|
+
return self.valid_to is None
|
|
106
|
+
|
|
107
|
+
def key(self) -> tuple:
|
|
108
|
+
"""What identifies the row within its set: the product for a snapshot type, the
|
|
109
|
+
document and line for a dated one."""
|
|
110
|
+
if self.type in PLANNED_MOVEMENT_TYPES and not PLANNED_MOVEMENT_TYPES[self.type][1]:
|
|
111
|
+
return (self.product,)
|
|
112
|
+
return self.document, self.line
|
|
113
|
+
|
|
114
|
+
def to_dict(self) -> dict:
|
|
115
|
+
obj = {}
|
|
116
|
+
for name, value in (("id", self.id), ("source", self.source), ("type", self.type),
|
|
117
|
+
("facility", self.facility), ("document", self.document), ("line", self.line),
|
|
118
|
+
("direction", self.direction), ("product", self.product),
|
|
119
|
+
("partner", self.partner), ("planned", self.planned),
|
|
120
|
+
("inProgress", self.in_progress), ("done", self.done), ("unit", self.unit),
|
|
121
|
+
("createdBy", self.created_by)):
|
|
122
|
+
if value is not None:
|
|
123
|
+
obj[name] = value
|
|
124
|
+
for name, value in (("due", self.due), ("validFrom", self.valid_from), ("validTo", self.valid_to),
|
|
125
|
+
("createdAt", self.created_at)):
|
|
126
|
+
if value is not None:
|
|
127
|
+
obj[name] = _ts_out(value) if isinstance(value, datetime) else value
|
|
128
|
+
return obj
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def from_dict(cls, data: dict) -> "PlannedMovement":
|
|
132
|
+
obj = cls()
|
|
133
|
+
obj.id = _uuid_str(data.get("id"))
|
|
134
|
+
obj.source = data.get("source")
|
|
135
|
+
obj.type = data.get("type")
|
|
136
|
+
obj.facility = data.get("facility")
|
|
137
|
+
obj.document = data.get("document")
|
|
138
|
+
obj.line = data.get("line")
|
|
139
|
+
obj.direction = data.get("direction")
|
|
140
|
+
obj.product = data.get("product")
|
|
141
|
+
obj.partner = data.get("partner")
|
|
142
|
+
obj.due = _ts_in(data.get("due"))
|
|
143
|
+
obj.planned = data.get("planned")
|
|
144
|
+
obj.in_progress = data.get("inProgress", data.get("in_progress"))
|
|
145
|
+
obj.done = data.get("done")
|
|
146
|
+
obj.unit = _uuid_str(data.get("unit"))
|
|
147
|
+
obj.valid_from = _ts_in(data.get("validFrom") or data.get("valid_from"))
|
|
148
|
+
obj.valid_to = _ts_in(data.get("validTo") or data.get("valid_to"))
|
|
149
|
+
obj.created_at = _ts_in(data.get("createdAt") or data.get("created_at"))
|
|
150
|
+
obj.created_by = data.get("createdBy") or data.get("created_by")
|
|
151
|
+
return obj
|
|
152
|
+
|
|
153
|
+
def api_id(self) -> str:
|
|
154
|
+
return self.id
|
|
155
|
+
|
|
156
|
+
def __repr__(self) -> str:
|
|
157
|
+
return (f"PlannedMovement(type={self.type!r}, product={self.product!r}, document={self.document!r}, "
|
|
158
|
+
f"line={self.line!r}, planned={self.planned!r}, done={self.done!r})")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class PlannedMovementSet:
|
|
162
|
+
"""
|
|
163
|
+
The outcome of writing one full set (source, type, facility) at ``as_of``: how many
|
|
164
|
+
rows were ``created``, ``changed`` (a new version), ``unchanged`` and ``closed``
|
|
165
|
+
(missing from the set). ``open`` is how many rows the set holds afterwards.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(self, source: str = None, movement_type: str = None, facility: str = None, as_of=None,
|
|
169
|
+
created: int = 0, changed: int = 0, unchanged: int = 0, closed: int = 0, open_rows: int = 0):
|
|
170
|
+
self.source = source
|
|
171
|
+
self.type = movement_type
|
|
172
|
+
self.facility = facility
|
|
173
|
+
self.as_of: Optional[datetime] = _ts_in(as_of)
|
|
174
|
+
self.created = created
|
|
175
|
+
self.changed = changed
|
|
176
|
+
self.unchanged = unchanged
|
|
177
|
+
self.closed = closed
|
|
178
|
+
self.open = open_rows
|
|
179
|
+
|
|
180
|
+
def to_dict(self) -> dict:
|
|
181
|
+
return {"source": self.source, "type": self.type, "facility": self.facility,
|
|
182
|
+
"asOf": _ts_out(self.as_of) if isinstance(self.as_of, datetime) else self.as_of,
|
|
183
|
+
"created": self.created, "changed": self.changed, "unchanged": self.unchanged,
|
|
184
|
+
"closed": self.closed, "open": self.open}
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def from_dict(cls, data: dict) -> "PlannedMovementSet":
|
|
188
|
+
return cls(source=data.get("source"), movement_type=data.get("type"), facility=data.get("facility"),
|
|
189
|
+
as_of=data.get("asOf"), created=data.get("created", 0), changed=data.get("changed", 0),
|
|
190
|
+
unchanged=data.get("unchanged", 0), closed=data.get("closed", 0),
|
|
191
|
+
open_rows=data.get("open", 0))
|
|
192
|
+
|
|
193
|
+
def __repr__(self) -> str:
|
|
194
|
+
return (f"PlannedMovementSet(type={self.type!r}, facility={self.facility!r}, created={self.created}, "
|
|
195
|
+
f"changed={self.changed}, unchanged={self.unchanged}, closed={self.closed}, open={self.open})")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0.dev29"
|
|
@@ -58,6 +58,7 @@ import ast
|
|
|
58
58
|
from .deployment import Deployment, DeploymentStatus
|
|
59
59
|
from .product import ProductKind, Product, ProductComponent
|
|
60
60
|
from .inventory import InventoryBalance, InventoryStorage, InventoryStock, InventoryCount, InventoryHistory
|
|
61
|
+
from .planned_movement import PlannedMovement, PlannedMovementSet
|
|
61
62
|
from .connection import EdgeDeviceKey
|
|
62
63
|
from .edge_config import EdgeConfig
|
|
63
64
|
from .api_config import _registry
|
|
@@ -3169,6 +3170,69 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
3169
3170
|
response = self._planner_request("POST", "planner/inventory/history", body=body)
|
|
3170
3171
|
return InventoryHistory.from_dict(response.json())
|
|
3171
3172
|
|
|
3173
|
+
# Production Planner - planned movements (DEV-6136)
|
|
3174
|
+
|
|
3175
|
+
def set_planned_movements(self, source: str, movement_type: str, facility: str, as_of,
|
|
3176
|
+
movements: list) -> PlannedMovementSet:
|
|
3177
|
+
"""
|
|
3178
|
+
Write the full set of planned movements of one source, type and facility as it is
|
|
3179
|
+
at ``as_of``, all or none: afterwards the set holds exactly ``movements``. A row
|
|
3180
|
+
whose values changed gets a new version, a row left out is closed at ``as_of``, an
|
|
3181
|
+
empty list closes them all. Every version is kept.
|
|
3182
|
+
|
|
3183
|
+
Sets are written in time order: an ``as_of`` before the set's last write is a 409.
|
|
3184
|
+
Writing the same ``as_of`` again corrects that write.
|
|
3185
|
+
|
|
3186
|
+
:param source: the system the rows come from, e.g. ``asc``.
|
|
3187
|
+
:param movement_type: ``allocated``, ``hold``, ``openDemand`` (one row per product,
|
|
3188
|
+
from the source's availability snapshot), or ``customerOrder``, ``workOrder``,
|
|
3189
|
+
``workOrderComponent``, ``purchaseOrder``, ``forecast`` (one row per
|
|
3190
|
+
``document`` and ``line``, with a ``due`` date).
|
|
3191
|
+
:param facility: hardware id of the Twin the rows apply to.
|
|
3192
|
+
:param as_of: datetime or ISO 8601: when the source held this set.
|
|
3193
|
+
:param movements: PlannedMovement list (or dicts). Quantities: ``planned``, and
|
|
3194
|
+
``in_progress`` / ``done`` for dated types, in the product's default unit unless
|
|
3195
|
+
``unit`` names another unit of its category.
|
|
3196
|
+
:return: PlannedMovementSet: how many rows were created, changed, unchanged, closed.
|
|
3197
|
+
"""
|
|
3198
|
+
if isinstance(as_of, datetime.datetime):
|
|
3199
|
+
if as_of.tzinfo is None:
|
|
3200
|
+
as_of = as_of.replace(tzinfo=datetime.timezone.utc)
|
|
3201
|
+
as_of = as_of.isoformat()
|
|
3202
|
+
body = {"source": source, "type": movement_type, "facility": facility, "asOf": as_of,
|
|
3203
|
+
"movements": [m.to_dict() if isinstance(m, PlannedMovement) else m for m in movements or []]}
|
|
3204
|
+
response = self._planner_request("POST", "planner/plannedmovements/", body=body)
|
|
3205
|
+
return PlannedMovementSet.from_dict(response.json())
|
|
3206
|
+
|
|
3207
|
+
def search_planned_movements(self, search_query=None) -> PagedQueryResult:
|
|
3208
|
+
"""
|
|
3209
|
+
List planned movements: the open rows by default, sorted by due date.
|
|
3210
|
+
|
|
3211
|
+
:param search_query: SearchQuery or dict; filters on ``source``, ``type``,
|
|
3212
|
+
``facility``, ``product``, ``document``, ``partner`` (eq / in), ``due`` (eq / gte /
|
|
3213
|
+
gt / lt / lte), ``asOf`` (the rows as they were at that moment) or ``history``
|
|
3214
|
+
(true: every version).
|
|
3215
|
+
:return: PagedQueryResult with PlannedMovement results.
|
|
3216
|
+
"""
|
|
3217
|
+
if search_query is None:
|
|
3218
|
+
search_query = SearchQuery()
|
|
3219
|
+
elif isinstance(search_query, dict):
|
|
3220
|
+
search_query = SearchQuery.from_dict(search_query)
|
|
3221
|
+
response = self._planner_request("POST", "planner/plannedmovements/search", body=search_query.to_dict())
|
|
3222
|
+
return PagedQueryResult.from_dict(response.json(), dto_class=PlannedMovement)
|
|
3223
|
+
|
|
3224
|
+
def delete_planned_movements(self, source: str, movement_type: str, facility: str) -> int:
|
|
3225
|
+
"""
|
|
3226
|
+
Delete every version of every row of one set (e.g. a sync pointed at the wrong
|
|
3227
|
+
facility). To close the rows and keep their history, write an empty set instead.
|
|
3228
|
+
|
|
3229
|
+
:return: how many rows were deleted; 0 when the set held nothing.
|
|
3230
|
+
"""
|
|
3231
|
+
response = self._planner_request("DELETE", "planner/plannedmovements/",
|
|
3232
|
+
params={"source": source, "type": movement_type, "facility": facility},
|
|
3233
|
+
expected=(200, 404))
|
|
3234
|
+
return response.json().get("deleted", 0) if response.status_code == 200 else 0
|
|
3235
|
+
|
|
3172
3236
|
# ------------------------------------------------------------ edge (DEV-6013/6014)
|
|
3173
3237
|
|
|
3174
3238
|
@staticmethod
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.2.0.dev28"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wizata_dsapi-2.2.0.dev28 → wizata_dsapi-2.2.0.dev29}/wizata_dsapi.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|