producteca 1.0.14__py3-none-any.whl → 2.0.57__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.
@@ -1,83 +1,97 @@
1
1
  from pydantic import BaseModel, Field
2
2
  from typing import List, Optional
3
3
  import requests
4
- from ..config.config import ConfigProducteca
4
+ from producteca.abstract.abstract_dataclass import BaseService
5
+ from producteca.sales_orders.search_sale_orders import SearchSalesOrderParams, SearchSalesOrder
6
+ from producteca.payments.payments import Payment
7
+ from producteca.shipments.shipment import Shipment
8
+ from producteca.utils import clean_model_dump
9
+ from dataclasses import dataclass
5
10
  import logging
6
11
  _logger = logging.getLogger(__name__)
7
12
 
13
+
8
14
  class SaleOrderLocation(BaseModel):
9
- streetName: Optional[str] = None
10
- streetNumber: Optional[str] = None
11
- addressNotes: Optional[str] = None
15
+ street_name: Optional[str] = Field(None, alias="streetName")
16
+ street_number: Optional[str] = Field(None, alias="streetNumber")
17
+ address_notes: Optional[str] = Field(None, alias="addressNotes")
12
18
  state: Optional[str] = None
13
19
  city: Optional[str] = None
14
20
  neighborhood: Optional[str] = None
15
- zipCode: Optional[str] = None
21
+ zip_code: Optional[str] = Field(None, alias="zipCode")
22
+
16
23
 
17
24
  class SaleOrderBillingInfo(BaseModel):
18
- docType: Optional[str] = None
19
- docNumber: Optional[str] = None
20
- streetName: Optional[str] = None
21
- streetNumber: Optional[str] = None
25
+ doc_type: Optional[str] = Field(None, alias="docType")
26
+ doc_number: Optional[str] = Field(None, alias="docNumber")
27
+ street_name: Optional[str] = Field(None, alias="streetName")
28
+ street_number: Optional[str] = Field(None, alias="streetNumber")
22
29
  comment: Optional[str] = None
23
- zipCode: Optional[str] = None
30
+ zip_code: Optional[str] = Field(None, alias="zipCode")
24
31
  city: Optional[str] = None
25
32
  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
33
+ state_registration: Optional[str] = Field(None, alias="stateRegistration")
34
+ tax_payer_type: Optional[str] = Field(None, alias="taxPayerType")
35
+ first_name: Optional[str] = Field(None, alias="firstName")
36
+ last_name: Optional[str] = Field(None, alias="lastName")
37
+ business_name: Optional[str] = Field(None, alias="businessName")
38
+
31
39
 
32
40
  class SaleOrderProfile(BaseModel):
33
41
  app: int
34
- integrationId: str
42
+ integration_id: str = Field(alias="integrationId")
35
43
  nickname: Optional[str] = None
36
44
 
45
+
37
46
  class SaleOrderContact(BaseModel):
38
47
  id: int
39
48
  name: str
40
- contactPerson: Optional[str] = None
49
+ contact_person: Optional[str] = Field(None, alias="contactPerson")
41
50
  mail: Optional[str] = None
42
- phoneNumber: Optional[str] = None
43
- taxId: Optional[str] = None
51
+ phone_number: Optional[str] = Field(None, alias="phoneNumber")
52
+ tax_id: Optional[str] = Field(None, alias="taxId")
44
53
  location: Optional[SaleOrderLocation] = None
45
54
  notes: Optional[str] = None
46
55
  type: Optional[str] = None
47
- priceList: Optional[str] = None
48
- priceListId: Optional[str] = None
56
+ price_list: Optional[str] = Field(None, alias="priceList")
57
+ price_list_id: Optional[str] = Field(None, alias="priceListId")
49
58
  profile: Optional[SaleOrderProfile] = None
50
- billingInfo: Optional[SaleOrderBillingInfo] = None
59
+ billing_info: Optional[SaleOrderBillingInfo] = Field(None, alias="billingInfo")
60
+
51
61
 
52
62
  class SaleOrderIntegrationId(BaseModel):
53
- alternateId: Optional[str] = None
54
- integrationId: str
63
+ alternate_id: Optional[str] = Field(None, alias="alternateId")
64
+ integration_id: str = Field(alias="integrationId")
55
65
  app: int
56
66
 
67
+
57
68
  class SaleOrderVariationPicture(BaseModel):
58
69
  url: str
59
70
  id: Optional[int] = None
60
71
 
72
+
61
73
  class SaleOrderVariationStock(BaseModel):
62
- warehouseId: Optional[int] = None
74
+ warehouse_id: Optional[int] = Field(None, alias="warehouseId")
63
75
  warehouse: str
64
76
  quantity: int
65
77
  reserved: int
66
- lastModified: Optional[str] = None
78
+ last_modified: Optional[str] = Field(None, alias="lastModified")
67
79
  available: int
68
80
 
81
+
69
82
  class SaleOrderVariationAttribute(BaseModel):
70
83
  key: str
71
84
  value: str
72
85
 
86
+
73
87
  class SaleOrderVariation(BaseModel):
74
- supplierCode: Optional[str] = None
88
+ supplier_code: Optional[str] = Field(None, alias="supplierCode")
75
89
  pictures: Optional[List[SaleOrderVariationPicture]] = None
76
90
  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
91
+ integration_id: Optional[int] = Field(None, alias="integrationId")
92
+ attributes_hash: Optional[str] = Field(None, alias="attributesHash")
93
+ primary_color: Optional[str] = Field(None, alias="primaryColor")
94
+ secondary_color: Optional[str] = Field(None, alias="secondaryColor")
81
95
  size: Optional[str] = None
82
96
  thumbnail: Optional[str] = None
83
97
  attributes: Optional[List[SaleOrderVariationAttribute]] = None
@@ -86,57 +100,69 @@ class SaleOrderVariation(BaseModel):
86
100
  sku: str
87
101
  barcode: Optional[str] = None
88
102
 
103
+
89
104
  class SaleOrderProduct(BaseModel):
90
105
  name: str
91
106
  code: str
92
107
  brand: Optional[str] = None
93
108
  id: int
94
109
 
110
+
111
+ class SaleOrderQuestion(BaseModel):
112
+ text: Optional[str] = None
113
+ answer: Optional[str] = None
114
+
115
+
95
116
  class SaleOrderConversation(BaseModel):
96
- questions: Optional[List[str]] = None
117
+ questions: Optional[List[SaleOrderQuestion]] = None
118
+
97
119
 
98
120
  class SaleOrderLine(BaseModel):
99
121
  price: float
100
- originalPrice: Optional[float] = None
101
- transactionFee: Optional[float] = None
122
+ original_price: Optional[float] = Field(None, alias="originalPrice")
123
+ transaction_fee: Optional[float] = Field(None, alias="transactionFee")
102
124
  product: SaleOrderProduct
103
125
  variation: SaleOrderVariation
104
- orderVariationIntegrationId: Optional[str] = None
126
+ order_variation_integration_id: Optional[str] = Field(None, alias="orderVariationIntegrationId")
105
127
  quantity: int
106
128
  conversation: Optional[SaleOrderConversation] = None
107
129
  reserved: Optional[int] = None
108
130
  id: int
109
131
 
132
+
110
133
  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
134
+ payment_network: Optional[str] = Field(None, alias="paymentNetwork")
135
+ first_six_digits: Optional[int] = Field(None, alias="firstSixDigits")
136
+ last_four_digits: Optional[int] = Field(None, alias="lastFourDigits")
137
+ cardholder_identification_number: Optional[str] = Field(None, alias="cardholderIdentificationNumber")
138
+ cardholder_identification_type: Optional[str] = Field(None, alias="cardholderIdentificationType")
139
+ cardholder_name: Optional[str] = Field(None, alias="cardholderName")
140
+
117
141
 
118
142
  class SaleOrderPaymentIntegration(BaseModel):
119
- integrationId: str
143
+ integration_id: str = Field(alias="integrationId")
120
144
  app: int
121
145
 
146
+
122
147
  class SaleOrderPayment(BaseModel):
123
148
  date: Optional[str] = None
124
149
  amount: float
125
- couponAmount: Optional[float] = None
150
+ coupon_amount: Optional[float] = Field(None, alias="couponAmount")
126
151
  status: Optional[str] = None
127
152
  method: Optional[str] = None
128
153
  integration: Optional[SaleOrderPaymentIntegration] = None
129
- transactionFee: Optional[float] = None
154
+ transaction_fee: Optional[float] = Field(None, alias="transactionFee")
130
155
  installments: Optional[int] = None
131
156
  card: Optional[SaleOrderCard] = None
132
157
  notes: Optional[str] = None
133
- authorizationCode: Optional[str] = None
134
- hasCancelableStatus: Optional[bool] = None
158
+ authorization_code: Optional[str] = Field(None, alias="authorizationCode")
159
+ has_cancelable_status: Optional[bool] = Field(None, alias="hasCancelableStatus")
135
160
  id: Optional[int] = None
136
161
 
162
+
137
163
  class SaleOrderShipmentMethod(BaseModel):
138
- trackingNumber: Optional[str] = None
139
- trackingUrl: Optional[str] = None
164
+ tracking_number: Optional[str] = Field(None, alias="trackingNumber")
165
+ tracking_url: Optional[str] = Field(None, alias="trackingUrl")
140
166
  courier: Optional[str] = None
141
167
  mode: Optional[str] = None
142
168
  cost: Optional[float] = None
@@ -144,17 +170,20 @@ class SaleOrderShipmentMethod(BaseModel):
144
170
  eta: Optional[int] = None
145
171
  status: Optional[str] = None
146
172
 
173
+
147
174
  class SaleOrderShipmentProduct(BaseModel):
148
175
  product: int
149
176
  variation: int
150
177
  quantity: int
151
178
 
179
+
152
180
  class SaleOrderShipmentIntegration(BaseModel):
153
181
  app: int
154
- integrationId: str
182
+ integration_id: str = Field(alias="integrationId")
155
183
  status: str
156
184
  id: int
157
185
 
186
+
158
187
  class SaleOrderShipment(BaseModel):
159
188
  date: str
160
189
  products: List[SaleOrderShipmentProduct]
@@ -163,93 +192,238 @@ class SaleOrderShipment(BaseModel):
163
192
  receiver: Optional[dict] = None
164
193
  id: int
165
194
 
166
- class SaleOrderInvoiceIntegration(BaseModel):
195
+
196
+ class SaleOrderInvoiceIntegrationAbstract(BaseModel):
167
197
  id: Optional[int] = None
168
- integrationId: Optional[str] = None
198
+ integration_id: Optional[str] = Field(None, alias="integrationId")
169
199
  app: Optional[int] = None
170
- createdAt: Optional[str] = None
171
- documentUrl: Optional[str] = None
172
- xmlUrl: Optional[str] = None
173
- decreaseStock: Optional[bool] = None
200
+ created_at: Optional[str] = Field(None, alias="createdAt")
201
+ decrease_stock: Optional[bool] = Field(None, alias="decreaseStock")
202
+
203
+
204
+ class SaleOrderInvoiceIntegration(SaleOrderInvoiceIntegrationAbstract):
205
+ document_url: Optional[str] = Field(None, alias="documentUrl")
206
+ xml_url: Optional[str] = Field(None, alias="xmlUrl")
207
+
208
+
209
+ class SaleOrderInvoiceIntegrationPut(SaleOrderInvoiceIntegrationAbstract):
210
+ document_url: Optional[str] = Field(None, alias="documentUrl")
211
+ xml_url: Optional[str] = Field(None, alias="xmlUrl")
212
+
213
+ class SaleOrderWarehouseIntegration(BaseModel):
214
+ app: Optional[int] = None
215
+ status: Optional[str] = None
216
+ integration_id: Optional[str] = Field(None, alias="integrationId")
217
+
174
218
 
175
219
  class SaleOrder(BaseModel):
176
220
  tags: Optional[List[str]] = None
177
221
  integrations: Optional[List[SaleOrderIntegrationId]] = None
178
- invoiceIntegration: Optional[SaleOrderInvoiceIntegration] = None
222
+ invoice_integration: Optional[SaleOrderInvoiceIntegration] = Field(None, alias="invoiceIntegration")
179
223
  channel: Optional[str] = None
180
- piiExpired: Optional[bool] = None
224
+ pii_expired: Optional[bool] = Field(None, alias="piiExpired")
181
225
  contact: Optional[SaleOrderContact] = None
182
226
  lines: Optional[List[SaleOrderLine]] = None
183
227
  warehouse: Optional[str] = None
184
- warehouseId: Optional[int] = None
185
- warehouseIntegration: Optional[str] = None
186
- pickUpStore: Optional[str] = None
228
+ warehouse_id: Optional[int] = Field(None, alias="warehouseId")
229
+ warehouse_integration: Optional[SaleOrderWarehouseIntegration] = Field(None, alias="warehouseIntegration")
230
+ pick_up_store: Optional[str] = Field(None, alias="pickUpStore")
187
231
  payments: Optional[List[SaleOrderPayment]] = None
188
232
  shipments: Optional[List[SaleOrderShipment]] = None
189
233
  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
234
+ shipping_cost: Optional[float] = Field(None, alias="shippingCost")
235
+ financial_cost: Optional[float] = Field(None, alias="financialCost")
236
+ paid_approved: Optional[float] = Field(None, alias="paidApproved")
237
+ payment_status: Optional[str] = Field(None, alias="paymentStatus")
238
+ delivery_status: Optional[str] = Field(None, alias="deliveryStatus")
239
+ payment_fulfillment_status: Optional[str] = Field(None, alias="paymentFulfillmentStatus")
240
+ delivery_fulfillment_status: Optional[str] = Field(None, alias="deliveryFulfillmentStatus")
241
+ delivery_method: Optional[str] = Field(None, alias="deliveryMethod")
242
+ payment_term: Optional[str] = Field(None, alias="paymentTerm")
199
243
  currency: Optional[str] = None
200
- customId: Optional[str] = None
201
- isOpen: Optional[bool] = None
202
- isCanceled: Optional[bool] = None
203
- cartId: Optional[str] = None
244
+ custom_id: Optional[str] = Field(None, alias="customId")
245
+ is_open: Optional[bool] = Field(None, alias="isOpen")
246
+ is_canceled: Optional[bool] = Field(None, alias="isCanceled")
247
+ cart_id: Optional[str] = Field(None, alias="cartId")
204
248
  draft: Optional[bool] = None
205
- promiseDeliveryDate: Optional[str] = None
206
- promiseDispatchDate: Optional[str] = None
207
- hasAnyShipments: Optional[bool] = None
208
- hasAnyPayments: Optional[bool] = None
249
+ promise_delivery_date: Optional[str] = Field(None, alias="promiseDeliveryDate")
250
+ promise_dispatch_date: Optional[str] = Field(None, alias="promiseDispatchDate")
251
+ has_any_shipments: Optional[bool] = Field(None, alias="hasAnyShipments")
252
+ has_any_payments: Optional[bool] = Field(None, alias="hasAnyPayments")
209
253
  date: Optional[str] = None
210
254
  notes: Optional[str] = None
211
255
  id: int
212
256
 
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)
257
+
258
+ class SaleOrderSynchronize(BaseModel):
259
+ id: int
260
+ invoice_integration: SaleOrderInvoiceIntegration = Field(alias="invoiceIntegration")
261
+ notes: Optional[str] = None
262
+ tags: Optional[List[str]] = None
263
+
264
+
265
+ class UpdateStatus(BaseModel):
266
+ updated: bool = False
267
+
268
+
269
+ class SaleOrderSyncResponse(BaseModel):
270
+ basic: UpdateStatus = Field(default_factory=UpdateStatus)
271
+ contact: UpdateStatus = Field(default_factory=UpdateStatus)
272
+ shipments: UpdateStatus = Field(default_factory=UpdateStatus)
273
+ payments: UpdateStatus = Field(default_factory=UpdateStatus)
274
+ invoice_integration: UpdateStatus = Field(alias="invoiceIntegration", default_factory=UpdateStatus)
275
+
276
+
277
+ @dataclass
278
+ class SaleOrderService(BaseService):
279
+ endpoint: str = 'salesorders'
280
+
281
+ def __call__(self, **payload):
282
+ self._record = SaleOrder(**payload)
283
+ return self
284
+
285
+ def __repr__(self):
286
+ return repr(self._record)
287
+
288
+ def get(self, sale_order_id: int) -> "SaleOrderService":
289
+ endpoint = f'{self.endpoint}/{sale_order_id}'
290
+ url = self.config.get_endpoint(endpoint)
291
+ _logger.info(f"GET {url} - Headers: {self.config.headers}")
292
+ response = requests.get(url, headers=self.config.headers)
293
+ if not response.ok:
294
+ raise Exception(f"Order {sale_order_id} could not be fetched. Error {response.status_code} {response.text}")
295
+ response_data = response.json()
296
+ return self(**response_data)
297
+
298
+ def get_shipping_labels(self):
299
+ if not self._record:
300
+ raise Exception("You need to add a record id")
301
+ endpoint = f'{self.endpoint}/{self._record.id}/labels'
302
+ url = self.config.get_endpoint(endpoint)
303
+ _logger.info(f"GET {url} - Headers: {self.config.headers}")
304
+ response = requests.get(url, headers=self.config.headers)
305
+ if not response.ok:
306
+ raise Exception("labels could not be gotten")
225
307
  return response.json()
226
308
 
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()
309
+ def close(self):
310
+ if not self._record:
311
+ raise Exception("You need to add a record id")
312
+ endpoint = f'{self.endpoint}/{self._record.id}/close'
313
+ url = self.config.get_endpoint(endpoint)
314
+ _logger.info(f"POST {url} - Headers: {self.config.headers}")
315
+ response = requests.post(url, headers=self.config.headers)
316
+ if not response.ok:
317
+ raise Exception("Order could not be closed")
318
+
319
+ def cancel(self):
320
+ if not self._record:
321
+ raise Exception("You need to add a record id")
322
+ endpoint = f'{self.endpoint}/{self._record.id}/cancel'
323
+ url = self.config.get_endpoint(endpoint)
324
+ _logger.info(f"POST {url} - Headers: {self.config.headers}")
325
+ response = requests.post(url, headers=self.config.headers)
326
+ if not response.ok:
327
+ raise Exception("Order could not be cancelled")
328
+
329
+ def synchronize(self) -> "SaleOrderService":
330
+ if not self._record:
331
+ raise Exception("You need to add a record by calling the resource and adding info")
332
+ endpoint = f'{self.endpoint}/synchronize'
333
+ url = self.config.get_endpoint(endpoint)
334
+ # TODO: Check what can we sync, and what can we not sync
335
+ sync_body = SaleOrderSynchronize(**clean_model_dump(self._record))
336
+ sync_data = clean_model_dump(sync_body)
337
+ _logger.info(f"POST {url} - Headers: {self.config.headers} - Data: {sync_data}")
338
+ response = requests.post(url, json=sync_data, headers=self.config.headers)
339
+ if not response.ok:
340
+ raise Exception(f"Synchronize error {response.status_code} {response.text}")
341
+ sync_res = SaleOrderSyncResponse(**response.json()) # noqa
342
+ return self
343
+
344
+ def invoice_integration(self):
345
+ if not self._record:
346
+ raise Exception("You need to add a record id")
347
+
348
+ invoice_integration_data = clean_model_dump(self._record.invoice_integration)
349
+
350
+ if self._record.invoice_integration.id:
351
+ endpoint = f'{self.endpoint}/{self._record.id}/invoiceIntegration'
352
+ url = self.config.get_endpoint(endpoint)
353
+ _logger.info(f"PUT {url} - Headers: {self.config.headers} - Data: {invoice_integration_data}")
354
+ response = requests.put(url, headers=self.config.headers,
355
+ json=invoice_integration_data)
356
+ else:
357
+ endpoint = f'{self.endpoint}/synchronize'
358
+ url = self.config.get_endpoint(endpoint)
359
+ sync_data = {"id": self._record.id, "invoiceIntegration": invoice_integration_data}
360
+ _logger.info(f"POST {url} - Headers: {self.config.headers} - Data: {sync_data}")
361
+ response = requests.post(url, headers=self.config.headers,
362
+ json=sync_data)
363
+
364
+ if not response.ok:
365
+ raise Exception(f"Error on resposne {response.text}")
366
+ return response.ok
367
+
368
+ def search(self, params: SearchSalesOrderParams):
369
+ endpoint: str = f"search/{self.endpoint}"
370
+ headers = self.config.headers
371
+ url = self.config.get_endpoint(endpoint)
372
+ new_url = f"{url}?$filter={params.filter}&top={params.top}&skip={params.skip}"
373
+ _logger.info(f"GET {new_url} - Headers: {headers}")
374
+ response = requests.get(
375
+ new_url,
376
+ headers=headers,
377
+ )
378
+ if not response.ok:
379
+ raise Exception(f"Error on resposne {response.status_code} - {response.text}")
380
+ response_data = response.json()
381
+ return SearchSalesOrder(**response_data)
382
+
383
+ def add_payment(self, payload) -> Payment:
384
+ if not self._record:
385
+ raise Exception("You need to add a record id")
386
+ payment = Payment(**payload)
387
+ url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/payments")
388
+ payment_data = clean_model_dump(payment)
389
+ _logger.info(f"POST {url} - Headers: {self.config.headers} - Data: {payment_data}")
390
+ res = requests.post(url, json=payment_data, headers=self.config.headers)
391
+ if not res.ok:
392
+ raise Exception(f"Error on resposne {res.text}")
393
+ return Payment(**res.json())
394
+
395
+ def update_payment(self, payment_id: int, payload) -> "Payment":
396
+ if not self._record:
397
+ raise Exception("You need to add a record id")
398
+ payment = Payment(**payload)
399
+ url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/payments/{payment_id}")
400
+ payment_data = clean_model_dump(payment)
401
+ _logger.info(f"PUT {url} - Headers: {self.config.headers} - Data: {payment_data}")
402
+ res = requests.put(url, json=payment_data, headers=self.config.headers)
403
+ if not res.ok:
404
+ raise Exception(f"Error on payment update {res.text}")
405
+ return Payment(**res.json())
406
+
407
+ def add_shipment(self, payload) -> "Shipment":
408
+ if not self._record:
409
+ raise Exception("You need to add a record id")
410
+ shipment = Shipment(**payload)
411
+ url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/shipments")
412
+ shipment_data = clean_model_dump(shipment)
413
+ _logger.info(f"POST {url} - Headers: {self.config.headers} - Data: {shipment_data}")
414
+ res = requests.post(url, json=shipment_data, headers=self.config.headers)
415
+ if not res.ok:
416
+ raise Exception(f"Error on shipment add {res.text}")
417
+ return Shipment(**res.json())
418
+
419
+ def update_shipment(self, shipment_id: str, payload) -> "Shipment":
420
+ if not self._record:
421
+ raise Exception("You need to add a record id")
422
+ shipment = Shipment(**payload)
423
+ url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/shipments/{shipment_id}")
424
+ shipment_data = clean_model_dump(shipment)
425
+ _logger.info(f"PUT {url} - Headers: {self.config.headers} - Data: {shipment_data}")
426
+ res = requests.put(url, json=shipment_data, headers=self.config.headers)
427
+ if not res.ok:
428
+ raise Exception(f"Error on shipment update {res.text}")
429
+ return Shipment(**res.json())