producteca 1.0.0__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.
- producteca/__init__.py +5 -0
- producteca/abstract/__init__.py +0 -0
- producteca/abstract/abstract_dataclass.py +23 -0
- producteca/config/__init__.py +1 -0
- producteca/config/config.py +28 -0
- producteca/payments/__init__.py +0 -0
- producteca/payments/payments.py +45 -0
- producteca/products/__init__.py +1 -0
- producteca/products/products.py +182 -0
- producteca/sales_orders/__init__.py +1 -0
- producteca/sales_orders/sales_orders.py +255 -0
- producteca/search/__init__.py +0 -0
- producteca/search/search.py +149 -0
- producteca/search/search_sale_orders.py +170 -0
- producteca/shipments/__init__.py +1 -0
- producteca/shipments/shipment.py +47 -0
- producteca/shipments/test_shipment.py +55 -0
- producteca-1.0.0.dist-info/METADATA +15 -0
- producteca-1.0.0.dist-info/RECORD +21 -0
- producteca-1.0.0.dist-info/WHEEL +4 -0
- producteca-1.0.0.dist-info/entry_points.txt +6 -0
producteca/__init__.py
ADDED
File without changes
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from pydantic import BaseModel, PrivateAttr
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from ..config.config import ConfigProducteca, APIConfig
|
4
|
+
|
5
|
+
|
6
|
+
class AbstractProductecaModel(BaseModel, ABC):
|
7
|
+
_config: APIConfig = PrivateAttr()
|
8
|
+
|
9
|
+
@property
|
10
|
+
@abstractmethod
|
11
|
+
def endpoint(self) -> str:
|
12
|
+
pass
|
13
|
+
|
14
|
+
def dict(self, *args, **kwargs):
|
15
|
+
return super().dict(*args, exclude_none=True, **kwargs)
|
16
|
+
|
17
|
+
|
18
|
+
class AbstractProductecaV1Model(AbstractProductecaModel):
|
19
|
+
_config: ConfigProducteca = PrivateAttr()
|
20
|
+
|
21
|
+
@property
|
22
|
+
def endpoint_url(self) -> str:
|
23
|
+
return self._config.get_endpoint(self.endpoint)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .config import ConfigProducteca
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from pydantic import BaseModel, PrivateAttr
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
|
4
|
+
|
5
|
+
class APIConfig(BaseModel, ABC):
|
6
|
+
base_url: str = 'https://api-external.producteca.com'
|
7
|
+
|
8
|
+
@property
|
9
|
+
@abstractmethod
|
10
|
+
def headers(self) -> dict:
|
11
|
+
pass
|
12
|
+
|
13
|
+
def get_endpoint(self, endpoint: str) -> str:
|
14
|
+
return f'{self.base_url}/{endpoint}'
|
15
|
+
|
16
|
+
|
17
|
+
class ConfigProducteca(APIConfig):
|
18
|
+
token: str
|
19
|
+
api_key: str
|
20
|
+
|
21
|
+
@property
|
22
|
+
def headers(self) -> dict:
|
23
|
+
return {
|
24
|
+
"Content-Type": "application/json",
|
25
|
+
"authorization": f"Bearer {self.token}",
|
26
|
+
"x-api-key": self.api_key,
|
27
|
+
"Accept": "*/*"
|
28
|
+
}
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from typing import Optional
|
3
|
+
import requests
|
4
|
+
from ..config.config import ConfigProducteca
|
5
|
+
|
6
|
+
|
7
|
+
class PaymentCard(BaseModel):
|
8
|
+
paymentNetwork: Optional[str] = None
|
9
|
+
firstSixDigits: Optional[int] = None
|
10
|
+
lastFourDigits: Optional[int] = None
|
11
|
+
cardholderIdentificationNumber: Optional[str] = None
|
12
|
+
cardholderIdentificationType: Optional[str] = None
|
13
|
+
cardholderName: Optional[str] = None
|
14
|
+
|
15
|
+
|
16
|
+
class PaymentIntegration(BaseModel):
|
17
|
+
integrationId: str
|
18
|
+
app: int
|
19
|
+
|
20
|
+
|
21
|
+
class Payment(BaseModel):
|
22
|
+
date: str
|
23
|
+
amount: float
|
24
|
+
couponAmount: Optional[float] = None
|
25
|
+
status: str
|
26
|
+
method: str
|
27
|
+
integration: Optional[PaymentIntegration] = None
|
28
|
+
transactionFee: Optional[float] = None
|
29
|
+
installments: Optional[int] = None
|
30
|
+
card: Optional[PaymentCard] = None
|
31
|
+
notes: Optional[str] = None
|
32
|
+
hasCancelableStatus: bool
|
33
|
+
id: Optional[int] = None
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def create(cls, config: ConfigProducteca, sale_order_id: int, payload: "Payment") -> "Payment":
|
37
|
+
url = config.get_endpoint(f"salesorders/{sale_order_id}/payments")
|
38
|
+
res = requests.post(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
|
39
|
+
return cls(**res.json())
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def update(cls, config: ConfigProducteca, sale_order_id: int, payment_id: int, payload: "Payment") -> "Payment":
|
43
|
+
url = config.get_endpoint(f"salesorders/{sale_order_id}/payments/{payment_id}")
|
44
|
+
res = requests.put(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
|
45
|
+
return cls(**res.json())
|
@@ -0,0 +1 @@
|
|
1
|
+
from . import products
|
@@ -0,0 +1,182 @@
|
|
1
|
+
from typing import List, Optional, Union
|
2
|
+
from pydantic import BaseModel, Field
|
3
|
+
import requests
|
4
|
+
from ..config.config import ConfigProducteca
|
5
|
+
import logging
|
6
|
+
|
7
|
+
_logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
# Models for nested structures
|
10
|
+
|
11
|
+
class Attribute(BaseModel):
|
12
|
+
key: str
|
13
|
+
value: str
|
14
|
+
|
15
|
+
class Tag(BaseModel):
|
16
|
+
tag: str
|
17
|
+
|
18
|
+
class Dimensions(BaseModel):
|
19
|
+
weight: Optional[float] = None
|
20
|
+
width: Optional[float] = None
|
21
|
+
height: Optional[float] = None
|
22
|
+
length: Optional[float] = None
|
23
|
+
pieces: Optional[int] = None
|
24
|
+
|
25
|
+
class Deal(BaseModel):
|
26
|
+
campaign: str
|
27
|
+
regular_price: Optional[float] = Field(default=None, alias='regularPrice')
|
28
|
+
deal_price: Optional[float] = Field(default=None, alias='dealPrice')
|
29
|
+
|
30
|
+
class Stock(BaseModel):
|
31
|
+
quantity: Optional[int] = None
|
32
|
+
available_quantity: Optional[int] = Field(default=None, alias='availableQuantity')
|
33
|
+
warehouse: Optional[str] = None
|
34
|
+
warehouse_id: Optional[int] = Field(default=None, alias='warehouseId')
|
35
|
+
reserved: Optional[int] = None
|
36
|
+
available: Optional[int] = None
|
37
|
+
|
38
|
+
class Price(BaseModel):
|
39
|
+
amount: Optional[float] = None
|
40
|
+
currency: str
|
41
|
+
price_list: str = Field(alias='priceList')
|
42
|
+
price_list_id: Optional[int] = Field(default=None, alias='priceListId')
|
43
|
+
|
44
|
+
class Picture(BaseModel):
|
45
|
+
url: str
|
46
|
+
|
47
|
+
class Integration(BaseModel):
|
48
|
+
app: Optional[int] = None
|
49
|
+
integration_id: Optional[str] = Field(default=None, alias='integrationId')
|
50
|
+
permalink: Optional[str] = None
|
51
|
+
status: Optional[str] = None
|
52
|
+
listing_type: Optional[str] = Field(default=None, alias='listingType')
|
53
|
+
safety_stock: Optional[int] = Field(default=None, alias='safetyStock')
|
54
|
+
synchronize_stock: Optional[bool] = Field(default=None, alias='synchronizeStock')
|
55
|
+
is_active: Optional[bool] = Field(default=None, alias='isActive')
|
56
|
+
is_active_or_paused: Optional[bool] = Field(default=None, alias='isActiveOrPaused')
|
57
|
+
id: Optional[int] = None
|
58
|
+
parent_integration: Optional[str] = Field(default=None, alias='parentIntegration')
|
59
|
+
|
60
|
+
class Variation(BaseModel):
|
61
|
+
variation_id: Optional[int] = Field(default=None, alias='variationId')
|
62
|
+
components: Optional[List] = None
|
63
|
+
pictures: Optional[List[Picture]] = None
|
64
|
+
stocks: Optional[List[Stock]] = None
|
65
|
+
attributes_hash: Optional[str] = Field(default=None, alias='attributesHash')
|
66
|
+
primary_color: Optional[str] = Field(default=None, alias='primaryColor')
|
67
|
+
thumbnail: Optional[str] = None
|
68
|
+
attributes: Optional[List[Attribute]] = None
|
69
|
+
integrations: Optional[List[Integration]] = None
|
70
|
+
id: Optional[int] = None
|
71
|
+
sku: Optional[str] = None
|
72
|
+
barcode: Optional[str] = None
|
73
|
+
|
74
|
+
# Model base para los productos
|
75
|
+
class Product(BaseModel):
|
76
|
+
config: Optional[ConfigProducteca] = Field(default=None, exclude=True)
|
77
|
+
endpoint: str = Field(default='products', exclude=True)
|
78
|
+
create_if_it_doesnt_exist: bool = Field(default=False, exclude=True)
|
79
|
+
sku: Optional[str] = None
|
80
|
+
variation_id: Optional[int] = None
|
81
|
+
code: Optional[str] = None
|
82
|
+
name: Optional[str] = None
|
83
|
+
barcode: Optional[str] = None
|
84
|
+
attributes: Optional[List[Attribute]] = None
|
85
|
+
tags: Optional[List[str]] = None
|
86
|
+
buying_price: Optional[float] = None
|
87
|
+
dimensions: Optional[Dimensions] = None
|
88
|
+
category: Optional[Union[str, dict]] = None # Puede ser string en POST o dict en GET Meli
|
89
|
+
brand: Optional[str] = None
|
90
|
+
notes: Optional[str] = None
|
91
|
+
deals: Optional[List[Deal]] = None
|
92
|
+
stocks: Optional[List[Stock]] = None
|
93
|
+
prices: Optional[List[Price]] = None
|
94
|
+
pictures: Optional[List[Picture]] = None
|
95
|
+
|
96
|
+
|
97
|
+
def create(self):
|
98
|
+
endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
|
99
|
+
headers = self.config.headers.copy()
|
100
|
+
headers.update({"createifitdoesntexist": str(self.create_if_it_doesnt_exist).lower()})
|
101
|
+
data = self.model_dump_json(by_alias=True, exclude_none=True)
|
102
|
+
response = requests.post(endpoint_url, data=data, headers=headers)
|
103
|
+
if response.status_code == 204:
|
104
|
+
final_response = {"Message":"Product does not exist and the request cant create if it does not exist"}
|
105
|
+
else:
|
106
|
+
final_response = response.json()
|
107
|
+
return final_response, response.status_code
|
108
|
+
|
109
|
+
def update(self):
|
110
|
+
endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
|
111
|
+
headers = self.config.headers.copy()
|
112
|
+
data = self.model_dump_json(by_alias=True, exclude_none=True)
|
113
|
+
if not self.code and not self.sku:
|
114
|
+
return {"Message":"Sku or code should be provided to update the product"}, 204
|
115
|
+
response = requests.post(endpoint_url, data=data, headers=headers)
|
116
|
+
if response.status_code == 204:
|
117
|
+
final_response = {"Message":"Product does not exist and the request cant create if it does not exist"}
|
118
|
+
else:
|
119
|
+
final_response = response.json()
|
120
|
+
return final_response, response.status_code
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def get(cls, config: ConfigProducteca, product_id: int):
|
124
|
+
endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}')
|
125
|
+
headers = config.headers
|
126
|
+
response = requests.get(endpoint_url, headers=headers)
|
127
|
+
response_data = response.json()
|
128
|
+
return response_data, response.status_code
|
129
|
+
|
130
|
+
@classmethod
|
131
|
+
def get_bundle(cls, config: ConfigProducteca, product_id: int):
|
132
|
+
endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}/bundles')
|
133
|
+
headers = config.headers
|
134
|
+
response = requests.get(endpoint_url, headers=headers)
|
135
|
+
return cls(config=config, **response.json()), response.status_code
|
136
|
+
|
137
|
+
@classmethod
|
138
|
+
def get_ml_integration(cls, config: ConfigProducteca, product_id: int):
|
139
|
+
endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}/listintegration')
|
140
|
+
headers = config.headers
|
141
|
+
response = requests.get(endpoint_url, headers=headers)
|
142
|
+
return cls(config=config, **response.json()), response.status_code
|
143
|
+
|
144
|
+
# Modelo con campos extra de la vista Meli
|
145
|
+
class MeliCategory(BaseModel):
|
146
|
+
meli_id: Optional[str] = Field(default=None, alias='meliId')
|
147
|
+
accepts_mercadoenvios: Optional[bool] = Field(default=None, alias='acceptsMercadoenvios')
|
148
|
+
suggest: Optional[bool] = None
|
149
|
+
fixed: Optional[bool] = None
|
150
|
+
|
151
|
+
class Shipping(BaseModel):
|
152
|
+
local_pickup: Optional[bool] = Field(default=None, alias='localPickup')
|
153
|
+
mode: Optional[str] = None
|
154
|
+
free_shipping: Optional[bool] = Field(default=None, alias='freeShipping')
|
155
|
+
free_shipping_cost: Optional[float] = Field(default=None, alias='freeShippingCost')
|
156
|
+
mandatory_free_shipping: Optional[bool] = Field(default=None, alias='mandatoryFreeShipping')
|
157
|
+
free_shipping_method: Optional[str] = Field(default=None, alias='freeShippingMethod')
|
158
|
+
|
159
|
+
class MShopsShipping(BaseModel):
|
160
|
+
enabled: Optional[bool] = None
|
161
|
+
|
162
|
+
class AttributeCompletion(BaseModel):
|
163
|
+
product_identifier_status: Optional[str] = Field(default=None, alias='productIdentifierStatus')
|
164
|
+
data_sheet_status: Optional[str] = Field(default=None, alias='dataSheetStatus')
|
165
|
+
status: Optional[str] = None
|
166
|
+
count: Optional[int] = None
|
167
|
+
total: Optional[int] = None
|
168
|
+
|
169
|
+
class MeliProduct(Product):
|
170
|
+
product_id: Optional[int] = Field(default=None, alias='productId')
|
171
|
+
has_custom_shipping_costs: Optional[bool] = Field(default=None, alias='hasCustomShippingCosts')
|
172
|
+
shipping: Optional[Shipping] = None
|
173
|
+
mshops_shipping: Optional[MShopsShipping] = Field(default=None, alias='mShopsShipping')
|
174
|
+
add_free_shipping_cost_to_price: Optional[bool] = Field(default=None, alias='addFreeShippingCostToPrice')
|
175
|
+
category: Optional[MeliCategory] = None
|
176
|
+
attribute_completion: Optional[AttributeCompletion] = Field(default=None, alias='attributeCompletion')
|
177
|
+
catalog_products: Optional[List[str]] = Field(default=None, alias='catalogProducts')
|
178
|
+
warranty: Optional[str] = None
|
179
|
+
domain: Optional[str] = None
|
180
|
+
listing_type_id: Optional[str] = Field(default=None, alias='listingTypeId')
|
181
|
+
catalog_products_status: Optional[str] = Field(default=None, alias='catalogProductsStatus')
|
182
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
from . import sales_orders
|
@@ -0,0 +1,255 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
from typing import List, Optional
|
3
|
+
import requests
|
4
|
+
from ..config.config import ConfigProducteca
|
5
|
+
import logging
|
6
|
+
_logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
class SaleOrderLocation(BaseModel):
|
9
|
+
streetName: Optional[str] = None
|
10
|
+
streetNumber: Optional[str] = None
|
11
|
+
addressNotes: Optional[str] = None
|
12
|
+
state: Optional[str] = None
|
13
|
+
city: Optional[str] = None
|
14
|
+
neighborhood: Optional[str] = None
|
15
|
+
zipCode: Optional[str] = None
|
16
|
+
|
17
|
+
class SaleOrderBillingInfo(BaseModel):
|
18
|
+
docType: Optional[str] = None
|
19
|
+
docNumber: Optional[str] = None
|
20
|
+
streetName: Optional[str] = None
|
21
|
+
streetNumber: Optional[str] = None
|
22
|
+
comment: Optional[str] = None
|
23
|
+
zipCode: Optional[str] = None
|
24
|
+
city: Optional[str] = None
|
25
|
+
state: Optional[str] = None
|
26
|
+
stateRegistration: Optional[str] = None
|
27
|
+
taxPayerType: Optional[str] = None
|
28
|
+
firstName: Optional[str] = None
|
29
|
+
lastName: Optional[str] = None
|
30
|
+
businessName: Optional[str] = None
|
31
|
+
|
32
|
+
class SaleOrderProfile(BaseModel):
|
33
|
+
app: int
|
34
|
+
integrationId: str
|
35
|
+
nickname: Optional[str] = None
|
36
|
+
|
37
|
+
class SaleOrderContact(BaseModel):
|
38
|
+
id: int
|
39
|
+
name: str
|
40
|
+
contactPerson: Optional[str] = None
|
41
|
+
mail: Optional[str] = None
|
42
|
+
phoneNumber: Optional[str] = None
|
43
|
+
taxId: Optional[str] = None
|
44
|
+
location: Optional[SaleOrderLocation] = None
|
45
|
+
notes: Optional[str] = None
|
46
|
+
type: Optional[str] = None
|
47
|
+
priceList: Optional[str] = None
|
48
|
+
priceListId: Optional[str] = None
|
49
|
+
profile: Optional[SaleOrderProfile] = None
|
50
|
+
billingInfo: Optional[SaleOrderBillingInfo] = None
|
51
|
+
|
52
|
+
class SaleOrderIntegrationId(BaseModel):
|
53
|
+
alternateId: Optional[str] = None
|
54
|
+
integrationId: str
|
55
|
+
app: int
|
56
|
+
|
57
|
+
class SaleOrderVariationPicture(BaseModel):
|
58
|
+
url: str
|
59
|
+
id: Optional[int] = None
|
60
|
+
|
61
|
+
class SaleOrderVariationStock(BaseModel):
|
62
|
+
warehouseId: Optional[int] = None
|
63
|
+
warehouse: str
|
64
|
+
quantity: int
|
65
|
+
reserved: int
|
66
|
+
lastModified: Optional[str] = None
|
67
|
+
available: int
|
68
|
+
|
69
|
+
class SaleOrderVariationAttribute(BaseModel):
|
70
|
+
key: str
|
71
|
+
value: str
|
72
|
+
|
73
|
+
class SaleOrderVariation(BaseModel):
|
74
|
+
supplierCode: Optional[str] = None
|
75
|
+
pictures: Optional[List[SaleOrderVariationPicture]] = None
|
76
|
+
stocks: Optional[List[SaleOrderVariationStock]] = None
|
77
|
+
integrationId: Optional[int] = None
|
78
|
+
attributesHash: Optional[str] = None
|
79
|
+
primaryColor: Optional[str] = None
|
80
|
+
secondaryColor: Optional[str] = None
|
81
|
+
size: Optional[str] = None
|
82
|
+
thumbnail: Optional[str] = None
|
83
|
+
attributes: Optional[List[SaleOrderVariationAttribute]] = None
|
84
|
+
integrations: Optional[List[SaleOrderIntegrationId]] = None
|
85
|
+
id: int
|
86
|
+
sku: str
|
87
|
+
barcode: Optional[str] = None
|
88
|
+
|
89
|
+
class SaleOrderProduct(BaseModel):
|
90
|
+
name: str
|
91
|
+
code: str
|
92
|
+
brand: Optional[str] = None
|
93
|
+
id: int
|
94
|
+
|
95
|
+
class SaleOrderConversation(BaseModel):
|
96
|
+
questions: Optional[List[str]] = None
|
97
|
+
|
98
|
+
class SaleOrderLine(BaseModel):
|
99
|
+
price: float
|
100
|
+
originalPrice: Optional[float] = None
|
101
|
+
transactionFee: Optional[float] = None
|
102
|
+
product: SaleOrderProduct
|
103
|
+
variation: SaleOrderVariation
|
104
|
+
orderVariationIntegrationId: Optional[str] = None
|
105
|
+
quantity: int
|
106
|
+
conversation: Optional[SaleOrderConversation] = None
|
107
|
+
reserved: Optional[int] = None
|
108
|
+
id: int
|
109
|
+
|
110
|
+
class SaleOrderCard(BaseModel):
|
111
|
+
paymentNetwork: Optional[str] = None
|
112
|
+
firstSixDigits: Optional[int] = None
|
113
|
+
lastFourDigits: Optional[int] = None
|
114
|
+
cardholderIdentificationNumber: Optional[str] = None
|
115
|
+
cardholderIdentificationType: Optional[str] = None
|
116
|
+
cardholderName: Optional[str] = None
|
117
|
+
|
118
|
+
class SaleOrderPaymentIntegration(BaseModel):
|
119
|
+
integrationId: str
|
120
|
+
app: int
|
121
|
+
|
122
|
+
class SaleOrderPayment(BaseModel):
|
123
|
+
date: Optional[str] = None
|
124
|
+
amount: float
|
125
|
+
couponAmount: Optional[float] = None
|
126
|
+
status: Optional[str] = None
|
127
|
+
method: Optional[str] = None
|
128
|
+
integration: Optional[SaleOrderPaymentIntegration] = None
|
129
|
+
transactionFee: Optional[float] = None
|
130
|
+
installments: Optional[int] = None
|
131
|
+
card: Optional[SaleOrderCard] = None
|
132
|
+
notes: Optional[str] = None
|
133
|
+
authorizationCode: Optional[str] = None
|
134
|
+
hasCancelableStatus: Optional[bool] = None
|
135
|
+
id: Optional[int] = None
|
136
|
+
|
137
|
+
class SaleOrderShipmentMethod(BaseModel):
|
138
|
+
trackingNumber: Optional[str] = None
|
139
|
+
trackingUrl: Optional[str] = None
|
140
|
+
courier: Optional[str] = None
|
141
|
+
mode: Optional[str] = None
|
142
|
+
cost: Optional[float] = None
|
143
|
+
type: Optional[str] = None
|
144
|
+
eta: Optional[int] = None
|
145
|
+
status: Optional[str] = None
|
146
|
+
|
147
|
+
class SaleOrderShipmentProduct(BaseModel):
|
148
|
+
product: int
|
149
|
+
variation: int
|
150
|
+
quantity: int
|
151
|
+
|
152
|
+
class SaleOrderShipmentIntegration(BaseModel):
|
153
|
+
app: int
|
154
|
+
integrationId: str
|
155
|
+
status: str
|
156
|
+
id: int
|
157
|
+
|
158
|
+
class SaleOrderShipment(BaseModel):
|
159
|
+
date: str
|
160
|
+
products: List[SaleOrderShipmentProduct]
|
161
|
+
method: Optional[SaleOrderShipmentMethod] = None
|
162
|
+
integration: Optional[SaleOrderShipmentIntegration] = None
|
163
|
+
receiver: Optional[dict] = None
|
164
|
+
id: int
|
165
|
+
|
166
|
+
class SaleOrderInvoiceIntegration(BaseModel):
|
167
|
+
id: Optional[int] = None
|
168
|
+
integrationId: Optional[str] = None
|
169
|
+
app: Optional[int] = None
|
170
|
+
createdAt: Optional[str] = None
|
171
|
+
documentUrl: Optional[str] = None
|
172
|
+
xmlUrl: Optional[str] = None
|
173
|
+
decreaseStock: Optional[bool] = None
|
174
|
+
|
175
|
+
class SaleOrder(BaseModel):
|
176
|
+
tags: Optional[List[str]] = None
|
177
|
+
integrations: Optional[List[SaleOrderIntegrationId]] = None
|
178
|
+
invoiceIntegration: Optional[SaleOrderInvoiceIntegration] = None
|
179
|
+
channel: Optional[str] = None
|
180
|
+
piiExpired: Optional[bool] = None
|
181
|
+
contact: Optional[SaleOrderContact] = None
|
182
|
+
lines: Optional[List[SaleOrderLine]] = None
|
183
|
+
warehouse: Optional[str] = None
|
184
|
+
warehouseId: Optional[int] = None
|
185
|
+
warehouseIntegration: Optional[str] = None
|
186
|
+
pickUpStore: Optional[str] = None
|
187
|
+
payments: Optional[List[SaleOrderPayment]] = None
|
188
|
+
shipments: Optional[List[SaleOrderShipment]] = None
|
189
|
+
amount: Optional[float] = None
|
190
|
+
shippingCost: Optional[float] = None
|
191
|
+
financialCost: Optional[float] = None
|
192
|
+
paidApproved: Optional[float] = None
|
193
|
+
paymentStatus: Optional[str] = None
|
194
|
+
deliveryStatus: Optional[str] = None
|
195
|
+
paymentFulfillmentStatus: Optional[str] = None
|
196
|
+
deliveryFulfillmentStatus: Optional[str] = None
|
197
|
+
deliveryMethod: Optional[str] = None
|
198
|
+
paymentTerm: Optional[str] = None
|
199
|
+
currency: Optional[str] = None
|
200
|
+
customId: Optional[str] = None
|
201
|
+
isOpen: Optional[bool] = None
|
202
|
+
isCanceled: Optional[bool] = None
|
203
|
+
cartId: Optional[str] = None
|
204
|
+
draft: Optional[bool] = None
|
205
|
+
promiseDeliveryDate: Optional[str] = None
|
206
|
+
promiseDispatchDate: Optional[str] = None
|
207
|
+
hasAnyShipments: Optional[bool] = None
|
208
|
+
hasAnyPayments: Optional[bool] = None
|
209
|
+
date: Optional[str] = None
|
210
|
+
notes: Optional[str] = None
|
211
|
+
id: int
|
212
|
+
|
213
|
+
@classmethod
|
214
|
+
def get(cls, config: ConfigProducteca, sale_order_id: int) -> "SaleOrder":
|
215
|
+
endpoint = f'salesorders/{sale_order_id}'
|
216
|
+
url = config.get_endpoint(endpoint)
|
217
|
+
response = requests.get(url, headers=config.headers)
|
218
|
+
return cls(**response.json())
|
219
|
+
|
220
|
+
@classmethod
|
221
|
+
def get_shipping_labels(cls, config: ConfigProducteca, sale_order_id: int):
|
222
|
+
endpoint = f'salesorders/{sale_order_id}/labels'
|
223
|
+
url = config.get_endpoint(endpoint)
|
224
|
+
response = requests.get(url, headers=config.headers)
|
225
|
+
return response.json()
|
226
|
+
|
227
|
+
@classmethod
|
228
|
+
def close(cls, config: ConfigProducteca, sale_order_id: int):
|
229
|
+
endpoint = f'salesorders/{sale_order_id}/close'
|
230
|
+
url = config.get_endpoint(endpoint)
|
231
|
+
response = requests.post(url, headers=config.headers)
|
232
|
+
return response.status_code, response.json()
|
233
|
+
|
234
|
+
@classmethod
|
235
|
+
def cancel(cls, config: ConfigProducteca, sale_order_id: int):
|
236
|
+
endpoint = f'salesorders/{sale_order_id}/cancel'
|
237
|
+
url = config.get_endpoint(endpoint)
|
238
|
+
response = requests.post(url, headers=config.headers)
|
239
|
+
return response.status_code, response.json()
|
240
|
+
|
241
|
+
@classmethod
|
242
|
+
def synchronize(cls, config: ConfigProducteca, payload: "SaleOrder") -> tuple[int, "SaleOrder"]:
|
243
|
+
endpoint = 'salesorders/synchronize'
|
244
|
+
url = config.get_endpoint(endpoint)
|
245
|
+
response = requests.post(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
|
246
|
+
return response.status_code, cls(**response.json())
|
247
|
+
|
248
|
+
@classmethod
|
249
|
+
def invoice_integration(cls, config: ConfigProducteca, sale_order_id: int, payload: "SaleOrder"):
|
250
|
+
endpoint = f'salesorders/{sale_order_id}/invoiceIntegration'
|
251
|
+
url = config.get_endpoint(endpoint)
|
252
|
+
response = requests.put(url, headers=config.headers, data=payload.model_dump_json(exclude_none=True))
|
253
|
+
if response.status_code == 200:
|
254
|
+
return response.status_code, {}
|
255
|
+
return response.status_code, response.json()
|
File without changes
|
@@ -0,0 +1,149 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
from pydantic import BaseModel, Field
|
3
|
+
import requests
|
4
|
+
from ..config.config import ConfigProducteca
|
5
|
+
|
6
|
+
|
7
|
+
class FacetValue(BaseModel):
|
8
|
+
count: int
|
9
|
+
value: str
|
10
|
+
label: str
|
11
|
+
|
12
|
+
|
13
|
+
class Facet(BaseModel):
|
14
|
+
key: str
|
15
|
+
value: List[FacetValue]
|
16
|
+
is_collection: bool
|
17
|
+
translate: bool
|
18
|
+
|
19
|
+
|
20
|
+
class SearchStocks(BaseModel):
|
21
|
+
warehouse: str
|
22
|
+
quantity: int
|
23
|
+
reserved: int
|
24
|
+
|
25
|
+
|
26
|
+
class SearchPrices(BaseModel):
|
27
|
+
price_list_id: int
|
28
|
+
price_list: str
|
29
|
+
amount: float
|
30
|
+
currency: str
|
31
|
+
|
32
|
+
|
33
|
+
class SearchIntegration(BaseModel):
|
34
|
+
app: Optional[int]
|
35
|
+
integration_id: Optional[str]
|
36
|
+
permalink: Optional[str]
|
37
|
+
status: Optional[str]
|
38
|
+
listing_type: Optional[str]
|
39
|
+
safety_stock: Optional[int]
|
40
|
+
synchronize_stock: Optional[bool]
|
41
|
+
is_active: Optional[bool]
|
42
|
+
is_active_or_paused: Optional[bool]
|
43
|
+
id: Optional[int]
|
44
|
+
|
45
|
+
|
46
|
+
class SearchDeals(BaseModel):
|
47
|
+
campaign: str
|
48
|
+
product: int
|
49
|
+
variation: str
|
50
|
+
deal_price: float
|
51
|
+
discount: float
|
52
|
+
regular_price: float
|
53
|
+
enabled: bool
|
54
|
+
currency: str
|
55
|
+
id: str
|
56
|
+
|
57
|
+
|
58
|
+
class SearchResultItem(BaseModel):
|
59
|
+
search_score: float = Field(..., alias='@search.score')
|
60
|
+
id: int
|
61
|
+
product_id: int
|
62
|
+
company_id: int
|
63
|
+
name: str
|
64
|
+
code: str
|
65
|
+
skus: List[str]
|
66
|
+
brand: str
|
67
|
+
category: str
|
68
|
+
thumbnail: str
|
69
|
+
stocks: List[SearchStocks]
|
70
|
+
warehouses_with_stock: List[str]
|
71
|
+
total_stock: int
|
72
|
+
has_pictures: bool
|
73
|
+
buying_price: float
|
74
|
+
prices: List[SearchPrices]
|
75
|
+
integration_ids: List[str]
|
76
|
+
integration_apps: List[str]
|
77
|
+
integrations: List[SearchIntegration]
|
78
|
+
campaigns: List[str]
|
79
|
+
app: Optional[int]
|
80
|
+
status: Optional[str]
|
81
|
+
synchronize_stock: Optional[bool]
|
82
|
+
listing_type: Optional[str]
|
83
|
+
price_amount: Optional[float]
|
84
|
+
price_currency: Optional[str]
|
85
|
+
category_id: Optional[str]
|
86
|
+
category_base_id: Optional[str]
|
87
|
+
category_l1: Optional[str]
|
88
|
+
category_l2: Optional[str]
|
89
|
+
category_l3: Optional[str]
|
90
|
+
category_l4: Optional[str]
|
91
|
+
category_l5: Optional[str]
|
92
|
+
category_l6: Optional[str]
|
93
|
+
has_category: Optional[bool]
|
94
|
+
category_fixed: Optional[bool]
|
95
|
+
accepts_mercadoenvios: Optional[bool]
|
96
|
+
shipping_mode: Optional[str]
|
97
|
+
local_pickup: Optional[bool]
|
98
|
+
mandatory_free_shipping: Optional[bool]
|
99
|
+
free_shipping: Optional[bool]
|
100
|
+
free_shipping_cost: Optional[float]
|
101
|
+
template: Optional[str]
|
102
|
+
youtube_id: Optional[str]
|
103
|
+
warranty: Optional[str]
|
104
|
+
permalink: Optional[str]
|
105
|
+
domain: Optional[str]
|
106
|
+
attribute_completion_status: Optional[str]
|
107
|
+
attribute_completion_count: Optional[int]
|
108
|
+
attribute_completion_total: Optional[int]
|
109
|
+
deals: Optional[SearchDeals]
|
110
|
+
campaign_status: Optional[List[str]]
|
111
|
+
size_chart: Optional[str]
|
112
|
+
channel_status: Optional[List[str]]
|
113
|
+
channel_category_l1: Optional[List[str]]
|
114
|
+
channel_category_l2: Optional[List[str]]
|
115
|
+
channel_category_l3: Optional[List[str]]
|
116
|
+
channel_category_id: Optional[List[str]]
|
117
|
+
channel_synchronizes_stock: Optional[List[str]]
|
118
|
+
channel_has_category: Optional[List[str]]
|
119
|
+
catalog_products_status: Optional[List[str]]
|
120
|
+
metadata: Optional[List[str]]
|
121
|
+
integration_tags: Optional[List[str]]
|
122
|
+
variations_integration_ids: Optional[List[str]]
|
123
|
+
channel_pictures_templates: Optional[List[str]]
|
124
|
+
channel_pictures_templates_apps: Optional[List[str]]
|
125
|
+
|
126
|
+
|
127
|
+
class SearchProductResponse(BaseModel):
|
128
|
+
count: int
|
129
|
+
facets: List[Facet]
|
130
|
+
results: List[SearchResultItem]
|
131
|
+
|
132
|
+
|
133
|
+
class SearchProductParams(BaseModel):
|
134
|
+
top: Optional[int]
|
135
|
+
skip: Optional[int]
|
136
|
+
filter: Optional[str] = Field(default=None, alias='$filter')
|
137
|
+
search: Optional[str]
|
138
|
+
sales_channel: Optional[str] = Field(default='2', alias='salesChannel')
|
139
|
+
|
140
|
+
|
141
|
+
class SearchProduct:
|
142
|
+
endpoint: str = 'search/products'
|
143
|
+
|
144
|
+
@classmethod
|
145
|
+
def search_product(cls, config: ConfigProducteca, params: SearchProductParams) -> SearchProductResponse:
|
146
|
+
headers = config.headers
|
147
|
+
url = config.get_endpoint(cls.endpoint)
|
148
|
+
response = requests.get(url, headers=headers, params=params.dict(by_alias=True, exclude_none=True))
|
149
|
+
return SearchProductResponse(**response.json())
|
@@ -0,0 +1,170 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
from pydantic import BaseModel, Field
|
3
|
+
import requests
|
4
|
+
from ..config.config import ConfigProducteca
|
5
|
+
import logging
|
6
|
+
|
7
|
+
_logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
class SalesOrderProduct(BaseModel):
|
11
|
+
id: int
|
12
|
+
name: str
|
13
|
+
code: str
|
14
|
+
brand: str
|
15
|
+
|
16
|
+
|
17
|
+
class SalesOrderVariationAttribute(BaseModel):
|
18
|
+
key: str
|
19
|
+
value: str
|
20
|
+
|
21
|
+
|
22
|
+
class SalesOrderVariation(BaseModel):
|
23
|
+
id: int
|
24
|
+
attributes: List[SalesOrderVariationAttribute]
|
25
|
+
sku: str
|
26
|
+
thumbnail: str
|
27
|
+
|
28
|
+
|
29
|
+
class SalesOrderLine(BaseModel):
|
30
|
+
product: SalesOrderProduct
|
31
|
+
variation: SalesOrderVariation
|
32
|
+
quantity: int
|
33
|
+
price: float
|
34
|
+
|
35
|
+
|
36
|
+
class SalesOrderCard(BaseModel):
|
37
|
+
payment_network: str
|
38
|
+
first_six_digits: int
|
39
|
+
last_four_digits: int
|
40
|
+
cardholder_identification_number: str
|
41
|
+
cardholder_identification_type: str
|
42
|
+
cardholder_name: str
|
43
|
+
|
44
|
+
|
45
|
+
class SalesOrderPaymentIntegration(BaseModel):
|
46
|
+
integration_id: str
|
47
|
+
app: int
|
48
|
+
|
49
|
+
|
50
|
+
class SalesOrderPayment(BaseModel):
|
51
|
+
date: str
|
52
|
+
amount: float
|
53
|
+
coupon_amount: float
|
54
|
+
status: str
|
55
|
+
method: str
|
56
|
+
integration: SalesOrderPaymentIntegration
|
57
|
+
transaction_fee: float
|
58
|
+
installments: int
|
59
|
+
card: SalesOrderCard
|
60
|
+
notes: str
|
61
|
+
has_cancelable_status: bool
|
62
|
+
id: int
|
63
|
+
|
64
|
+
|
65
|
+
class SalesOrderIntegration(BaseModel):
|
66
|
+
alternate_id: str
|
67
|
+
integration_id: int
|
68
|
+
app: int
|
69
|
+
|
70
|
+
|
71
|
+
class SalesOrderShipmentProduct(BaseModel):
|
72
|
+
product: int
|
73
|
+
variation: int
|
74
|
+
quantity: int
|
75
|
+
|
76
|
+
|
77
|
+
class SalesOrderShipmentMethod(BaseModel):
|
78
|
+
tracking_number: str
|
79
|
+
tracking_url: str
|
80
|
+
courier: str
|
81
|
+
mode: str
|
82
|
+
cost: float
|
83
|
+
type: str
|
84
|
+
eta: str
|
85
|
+
status: str
|
86
|
+
|
87
|
+
|
88
|
+
class SalesOrderShipmentIntegration(BaseModel):
|
89
|
+
id: int
|
90
|
+
integration_id: str
|
91
|
+
app: int
|
92
|
+
status: str
|
93
|
+
|
94
|
+
|
95
|
+
class SalesOrderShipment(BaseModel):
|
96
|
+
date: str
|
97
|
+
products: List[SalesOrderShipmentProduct]
|
98
|
+
method: SalesOrderShipmentMethod
|
99
|
+
integration: SalesOrderShipmentIntegration
|
100
|
+
|
101
|
+
|
102
|
+
class SalesOrderResultItem(BaseModel):
|
103
|
+
codes: List[str]
|
104
|
+
contact_id: int
|
105
|
+
currency: str
|
106
|
+
date: str
|
107
|
+
delivery_method: str
|
108
|
+
delivery_status: str
|
109
|
+
id: str
|
110
|
+
integration_ids: List[str]
|
111
|
+
integrations: List[SalesOrderIntegration]
|
112
|
+
invoice_integration_app: int
|
113
|
+
invoice_integration_id: str
|
114
|
+
lines: List[SalesOrderLine]
|
115
|
+
payments: List[SalesOrderPayment]
|
116
|
+
payment_status: str
|
117
|
+
payment_term: str
|
118
|
+
product_names: List[str]
|
119
|
+
reserving_product_ids: str
|
120
|
+
sales_channel: int
|
121
|
+
shipments: List[SalesOrderShipment]
|
122
|
+
tracking_number: str
|
123
|
+
skus: List[str]
|
124
|
+
status: str
|
125
|
+
tags: List[str]
|
126
|
+
warehouse: str
|
127
|
+
company_id: int
|
128
|
+
shipping_cost: float
|
129
|
+
contact_phone: str
|
130
|
+
brands: List[str]
|
131
|
+
courier: str
|
132
|
+
order_id: int
|
133
|
+
updated_at: str
|
134
|
+
invoice_integration_created_at: str
|
135
|
+
invoice_integration_document_url: str
|
136
|
+
has_document_url: bool
|
137
|
+
integration_alternate_ids: str
|
138
|
+
cart_id: str
|
139
|
+
amount: float
|
140
|
+
has_any_shipments: bool
|
141
|
+
|
142
|
+
|
143
|
+
class SearchSalesOrderResponse(BaseModel):
|
144
|
+
count: int
|
145
|
+
results: List[SalesOrderResultItem]
|
146
|
+
|
147
|
+
|
148
|
+
class SearchSalesOrderParams(BaseModel):
|
149
|
+
top: Optional[int]
|
150
|
+
skip: Optional[int]
|
151
|
+
filter: Optional[str] = Field(default=None, alias="$filter")
|
152
|
+
class Config:
|
153
|
+
validate_by_name = True
|
154
|
+
|
155
|
+
class SearchSalesOrder:
|
156
|
+
endpoint: str = "search/salesorders"
|
157
|
+
|
158
|
+
|
159
|
+
@classmethod
|
160
|
+
def search_saleorder(cls, config: ConfigProducteca, params: SearchSalesOrderParams):
|
161
|
+
headers = config.headers
|
162
|
+
url = config.get_endpoint(cls.endpoint)
|
163
|
+
new_url = f"{url}?$filter={params.filter}&top={params.top}&skip={params.skip}"
|
164
|
+
response = requests.get(
|
165
|
+
new_url,
|
166
|
+
headers=headers,
|
167
|
+
)
|
168
|
+
return response.json(), response.status_code
|
169
|
+
|
170
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
from . import shipment
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
from pydantic import BaseModel
|
3
|
+
import requests
|
4
|
+
from ..config.config import ConfigProducteca
|
5
|
+
|
6
|
+
|
7
|
+
class ShipmentProduct(BaseModel):
|
8
|
+
product: int
|
9
|
+
variation: Optional[int] = None
|
10
|
+
quantity: int
|
11
|
+
|
12
|
+
|
13
|
+
class ShipmentMethod(BaseModel):
|
14
|
+
trackingNumber: Optional[str] = None
|
15
|
+
trackingUrl: Optional[str] = None
|
16
|
+
courier: Optional[str] = None
|
17
|
+
mode: Optional[str] = None
|
18
|
+
cost: Optional[float] = None
|
19
|
+
type: Optional[str] = None
|
20
|
+
eta: Optional[int] = None
|
21
|
+
status: Optional[str] = None
|
22
|
+
|
23
|
+
|
24
|
+
class ShipmentIntegration(BaseModel):
|
25
|
+
id: Optional[int] = None
|
26
|
+
integrationId: Optional[str] = None
|
27
|
+
app: Optional[int] = None
|
28
|
+
status: str
|
29
|
+
|
30
|
+
|
31
|
+
class Shipment(BaseModel):
|
32
|
+
date: Optional[str] = None
|
33
|
+
products: Optional[List[ShipmentProduct]] = None
|
34
|
+
method: Optional[ShipmentMethod] = None
|
35
|
+
integration: Optional[ShipmentIntegration] = None
|
36
|
+
|
37
|
+
@classmethod
|
38
|
+
def create(cls, config: ConfigProducteca, sale_order_id: int, payload: "Shipment") -> "Shipment":
|
39
|
+
url = config.get_endpoint(f"salesorders/{sale_order_id}/shipments")
|
40
|
+
res = requests.post(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
|
41
|
+
return res.status_code, res.json()
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def update(cls, config: ConfigProducteca, sale_order_id: int, shipment_id: str, payload: "Shipment") -> "Shipment":
|
45
|
+
url = config.get_endpoint(f"salesorders/{sale_order_id}/shipments/{shipment_id}")
|
46
|
+
res = requests.put(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
|
47
|
+
return res.status_code, res.json()
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import unittest
|
2
|
+
from unittest.mock import patch, MagicMock
|
3
|
+
from .shipment import Shipment, ShipmentProduct, ShipmentMethod, ShipmentIntegration, ConfigProducteca
|
4
|
+
|
5
|
+
class TestShipment(unittest.TestCase):
|
6
|
+
|
7
|
+
@patch('requests.post')
|
8
|
+
def test_create_shipment(self, mock_post):
|
9
|
+
# Arrange
|
10
|
+
config = ConfigProducteca()
|
11
|
+
sale_order_id = 123
|
12
|
+
products = [ShipmentProduct(product=1, variation=2, quantity=3)]
|
13
|
+
method = ShipmentMethod(trackingNumber="TN123", trackingUrl="http://track.url", courier="DHL", mode="air", cost=10.5, type="express", eta=5, status="shipped")
|
14
|
+
integration = ShipmentIntegration(id=1, integrationId="int123", app=10, status="active")
|
15
|
+
payload = Shipment(date="2023-01-01", products=products, method=method, integration=integration)
|
16
|
+
|
17
|
+
mock_response = MagicMock()
|
18
|
+
mock_response.status_code = 201
|
19
|
+
mock_response.json.return_value = {'success': True}
|
20
|
+
mock_post.return_value = mock_response
|
21
|
+
|
22
|
+
# Act
|
23
|
+
status_code, response_json = Shipment.create(config, sale_order_id, payload)
|
24
|
+
|
25
|
+
# Assert
|
26
|
+
self.assertEqual(status_code, 201)
|
27
|
+
self.assertEqual(response_json, {'success': True})
|
28
|
+
mock_post.assert_called_once()
|
29
|
+
|
30
|
+
@patch('requests.put')
|
31
|
+
def test_update_shipment(self, mock_put):
|
32
|
+
# Arrange
|
33
|
+
config = ConfigProducteca()
|
34
|
+
sale_order_id = 123
|
35
|
+
shipment_id = 'abc'
|
36
|
+
products = [ShipmentProduct(product=4, quantity=7)]
|
37
|
+
method = ShipmentMethod(courier="FedEx", cost=15.0)
|
38
|
+
integration = ShipmentIntegration(status="pending")
|
39
|
+
payload = Shipment(date="2023-02-02", products=products, method=method, integration=integration)
|
40
|
+
|
41
|
+
mock_response = MagicMock()
|
42
|
+
mock_response.status_code = 200
|
43
|
+
mock_response.json.return_value = {'updated': True}
|
44
|
+
mock_put.return_value = mock_response
|
45
|
+
|
46
|
+
# Act
|
47
|
+
status_code, response_json = Shipment.update(config, sale_order_id, shipment_id, payload)
|
48
|
+
|
49
|
+
# Assert
|
50
|
+
self.assertEqual(status_code, 200)
|
51
|
+
self.assertEqual(response_json, {'updated': True})
|
52
|
+
mock_put.assert_called_once()
|
53
|
+
|
54
|
+
if __name__ == '__main__':
|
55
|
+
unittest.main()
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: producteca
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary:
|
5
|
+
Author: Chroma Agency, Matias Rivera
|
6
|
+
Author-email: mrivera@chroma.agency
|
7
|
+
Requires-Python: >=3.13,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
10
|
+
Requires-Dist: coverage (>=7.6.3,<8.0.0)
|
11
|
+
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
12
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
|
15
|
+
# Very small module to connect with producteca
|
@@ -0,0 +1,21 @@
|
|
1
|
+
producteca/__init__.py,sha256=pX_QNJAeP-LJaRdArww3O8MiOp2yTAX1OCjzvzAjU3E,118
|
2
|
+
producteca/abstract/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
producteca/abstract/abstract_dataclass.py,sha256=Q9BaVb34_2MBLWaOOeGuzd1diOePHxxFWBEr8Jw8t40,617
|
4
|
+
producteca/config/__init__.py,sha256=ZELnRKNOj0erqtCLRtZURqUhttVbFfqUrHrgg6M5p-M,36
|
5
|
+
producteca/config/config.py,sha256=uTRaHLI9L7P_LPuFmuYgV50Aedz9MfDbXOZVZPFkOuI,658
|
6
|
+
producteca/payments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
producteca/payments/payments.py,sha256=bc9556954GRsvm4XAsTbbdP4-rS275MWGfhuF4MYVsY,1594
|
8
|
+
producteca/products/__init__.py,sha256=kIkYF7_BcLUzkGvuRnUBPAELyG9QxUb5D6UiRg4XQCg,22
|
9
|
+
producteca/products/products.py,sha256=IagCPEvQBUuUaw9yBQwBu7eJw3HtdAjxiWHEnpLDXwM,8046
|
10
|
+
producteca/sales_orders/__init__.py,sha256=9NbTrJhvhtGYX2DFd7CCZvYVKXx6jJCV2L70XPma5_c,26
|
11
|
+
producteca/sales_orders/sales_orders.py,sha256=1VzFSou2wOEmZ-uhehVcsj9Q4Gk5Iam4YEIu-l-xEgc,8792
|
12
|
+
producteca/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
producteca/search/search.py,sha256=4u_Y5h6ffxxD4_iaSVpKv6ZIOoxU_vZ2cuHvygclmRw,4137
|
14
|
+
producteca/search/search_sale_orders.py,sha256=JXxohAhEokUQ_bxBIQesLO_FzpN35lKfpT3fB8-O_Fs,3724
|
15
|
+
producteca/shipments/__init__.py,sha256=YZTSiaXNQHTtTrYjnl2bn_E6mcQyS76XVbB7fpCEC2Q,22
|
16
|
+
producteca/shipments/shipment.py,sha256=IELAYWVNT862CBvDHOTotqLMXrm6sFECic2E7RTSI-A,1625
|
17
|
+
producteca/shipments/test_shipment.py,sha256=QBi6Ee4UcLQgiVLPz3sfug5bZ5F655UTomde0xnDfKk,2215
|
18
|
+
producteca-1.0.0.dist-info/METADATA,sha256=ISMhh36r2JSZEHDclGbdjrPeLDmFeHxc5mp6Vz5D83s,478
|
19
|
+
producteca-1.0.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
20
|
+
producteca-1.0.0.dist-info/entry_points.txt,sha256=Fk7pn09epcdztiFc7kuYBBlB_e6R2C_c1hmPHsookHI,143
|
21
|
+
producteca-1.0.0.dist-info/RECORD,,
|