ManagerPythonClient 0.0.8__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.
@@ -0,0 +1,461 @@
1
+ from importlib.metadata import files
2
+
3
+ import PythonAPIClientBase
4
+ import json
5
+ from urllib.parse import quote
6
+ from uuid import UUID
7
+ import base64
8
+ import mimetypes
9
+ import os
10
+ import requests
11
+ import copy
12
+ from decimal import Decimal
13
+
14
+ from .SalesInvoiceGenerator import SalesInvoiceGenerator
15
+ from .PurchaseInvoiceGenerator import PurchaseInvoiceGenerator
16
+ from .TransactionReporter import TransactionReporter
17
+
18
+
19
+ def json_dump_s_default_function(obj):
20
+ if isinstance(obj, Decimal):
21
+ return str(obj)
22
+ raise TypeError(
23
+ f"Object of type {type(obj).__name__} is not JSON serializable"
24
+ )
25
+
26
+ def nameMatchFunction(item, name):
27
+ return item["name"] == name
28
+
29
+ def _get_attachment_url(purchaseInvoiceKey, businessName):
30
+ name = businessName.encode()
31
+
32
+ protobuf = b"\xa2\x06" + bytes([len(name)]) + name
33
+
34
+ uuid = UUID(purchaseInvoiceKey)
35
+
36
+ protobuf += b"\x0a" + b"\x12"
37
+ protobuf += b"\x09" + uuid.bytes_le[:8]
38
+ protobuf += b"\x11" + uuid.bytes_le[8:]
39
+
40
+ encoded = base64.urlsafe_b64encode(protobuf).decode().rstrip("=")
41
+
42
+ return f"/new-attachment?{encoded}"
43
+
44
+ class ManagerClient(PythonAPIClientBase.APIClientBase):
45
+ debug = None
46
+ def __init__(self, baseUrl, mock=None, forceOneRequestAtATime=False, verboseLogging=PythonAPIClientBase.VerboseLoggingNullLogClass(), debug=False):
47
+ self.debug = debug
48
+ useBaseUrl = baseUrl
49
+ if useBaseUrl != "MOCK":
50
+ useBaseUrl = useBaseUrl
51
+ super().__init__(baseURL=useBaseUrl, mock=mock, forceOneRequestAtATime=forceOneRequestAtATime, verboseLogging=verboseLogging)
52
+
53
+ def is_online(self):
54
+ try:
55
+ response = self.sendGetRequest(
56
+ url="/",
57
+ loginSession=None
58
+ )
59
+ except PythonAPIClientBase.APIClientException as cli:
60
+ if cli.result.status_code == 401:
61
+ return True # running but not authed as we have no login session
62
+ return False
63
+ except Exception as exp:
64
+ print("Other exp", exp)
65
+ return False
66
+ return True
67
+
68
+ def _get_generator(self, loginSession, apiObj, outputKey=None, enrichParams=None, skipFn=None):
69
+ if outputKey is None:
70
+ outputKey = apiObj
71
+ pageSize = 100
72
+ skip = 0
73
+ while True:
74
+ params = {
75
+ "pageSize": pageSize,
76
+ "skip": skip
77
+ }
78
+ if enrichParams is not None:
79
+ enrichParams(params)
80
+ response = self.sendGetRequest(
81
+ url=f"/{apiObj}",
82
+ loginSession=loginSession,
83
+ params=params
84
+ )
85
+ if response.status_code != 200:
86
+ print("status_code=", response.status_code)
87
+ raise Exception("Error getting " + apiObj)
88
+ data = json.loads(response.text)
89
+ # print("D", data)
90
+ if outputKey not in data:
91
+ print("Possible keys", data.keys())
92
+ raise Exception("given output key not possible - " + outputKey)
93
+ customers = data[outputKey]
94
+ for customer in customers:
95
+ skip = False
96
+ if skipFn is not None:
97
+ skip = skipFn(customer)
98
+ if not skip:
99
+ yield customer
100
+ if len(customers) < pageSize:
101
+ break
102
+ skip += pageSize
103
+
104
+ def _get_single(self, loginSession, name, apiObj, outputKey=None, matchFn=nameMatchFunction):
105
+ if outputKey is None:
106
+ outputKey = apiObj
107
+ response = self.sendGetRequest(
108
+ url=f"/{apiObj}?term={quote(name)}",
109
+ loginSession=loginSession
110
+ )
111
+ if response.status_code != 200:
112
+ raise Exception("Error getting " + apiObj)
113
+ data = json.loads(response.text)
114
+ if len(data[outputKey]) == 0:
115
+ return None
116
+ retItems = []
117
+ for item in data[outputKey]:
118
+ if matchFn(item, name):
119
+ retItems.append(item)
120
+ if len(retItems) == 0:
121
+ return None
122
+ if len(retItems) == 1:
123
+ return retItems[0]
124
+ return None # only return if we have a unique match
125
+
126
+
127
+ def get_customers(self, loginSession):
128
+ return self._get_generator(loginSession, "customers")
129
+ def get_customer(self, loginSession, name):
130
+ return self._get_single(loginSession, name, "customers")
131
+
132
+ def get_suppliers(self, loginSession):
133
+ return self._get_generator(loginSession, "suppliers")
134
+ def get_supplier(self, loginSession, name):
135
+ return self._get_single(loginSession, name, "suppliers")
136
+
137
+ def get_bank_and_cash_accounts(self, loginSession):
138
+ return self._get_generator(loginSession, "bank-and-cash-accounts", "bankAndCashAccounts")
139
+ def get_bank_and_cash_account(self, loginSession, name):
140
+ return self._get_single(loginSession, name, "bank-and-cash-accounts", "bankAndCashAccounts")
141
+
142
+ def get_chart_of_accounts(self, loginSession):
143
+ return self._get_generator(loginSession, "chart-of-accounts", "chartOfAccounts")
144
+ def get_chart_of_account(self, loginSession, name):
145
+ return self._get_single(loginSession, name, "chart-of-accounts", "chartOfAccounts")
146
+
147
+ def get_sales_invoices(self, loginSession):
148
+ return self._get_generator(loginSession, "sales-invoices", "salesInvoices")
149
+ def get_sales_invoices_for_customer(self, loginSession, customerName):
150
+ def skipFn(sales_invoice):
151
+ if customerName != sales_invoice["customer"]:
152
+ return True
153
+ return False
154
+ return self._get_generator(loginSession, "sales-invoices", "salesInvoices", skipFn=skipFn)
155
+ def get_sales_invoice(self, loginSession, description, customerName):
156
+ def nameMatchFunction(item, name):
157
+ if item["customer"] != customerName:
158
+ return False
159
+ if item["description"] != description:
160
+ return False
161
+ return True
162
+ return self._get_single(loginSession, description, "sales-invoices", "salesInvoices", matchFn=nameMatchFunction)
163
+
164
+ def get_inter_account_transfer_for_description(self, loginSession, description):
165
+ def enrichParams(params):
166
+ params["term"] = description
167
+ # def skipFn(rec):
168
+ # if date == rec["date"]:
169
+ # return False
170
+ # return True
171
+ return self._get_generator(
172
+ loginSession,
173
+ "inter-account-transfers",
174
+ "interAccountTransfers",
175
+ enrichParams=enrichParams
176
+ )
177
+
178
+
179
+ def get_purchase_invoices(self, loginSession):
180
+ return self._get_generator(loginSession, "purchase-invoices", "purchaseInvoices")
181
+ def get_purchase_invoices_for_supplier(self, loginSession, supplierName):
182
+ def skipFn(purchase_invoice):
183
+ if supplierName != purchase_invoice["supplier"]:
184
+ return True
185
+ return False
186
+ return self._get_generator(loginSession, "purchase-invoices", "purchaseInvoices", skipFn=skipFn)
187
+ def get_purchase_invoice(self, loginSession, description, supplierName):
188
+ def nameMatchFunction(item, name):
189
+ if item["supplier"] != supplierName:
190
+ return False
191
+ if item["description"] != description:
192
+ return False
193
+ return True
194
+ return self._get_single(loginSession, description, "purchase-invoices", "purchaseInvoices", matchFn=nameMatchFunction)
195
+
196
+ def get_payment_for_description(self, loginSession, description):
197
+ def enrichParams(params):
198
+ params["term"] = description
199
+ # def skipFn(rec):
200
+ # if date == rec["date"]:
201
+ # return False
202
+ # return True
203
+ return self._get_generator(
204
+ loginSession,
205
+ "payments",
206
+ "payments",
207
+ enrichParams=enrichParams
208
+ )
209
+
210
+ def create_sales_invoice_generator(self, issueDate, reference, customerKey, billingAddress, description):
211
+ return SalesInvoiceGenerator(issueDate, reference, customerKey, billingAddress, description)
212
+
213
+ def create_sales_invoice(self, loginSession, generator):
214
+ if not isinstance(generator, SalesInvoiceGenerator):
215
+ raise Exception("Wrong type of generator")
216
+ hasLineDescription = False
217
+ for line in generator.lines:
218
+ accountKey, description, qty, unit_price = line
219
+ if description is not None and description != "":
220
+ hasLineDescription = True
221
+ postData = {
222
+ "IssueDate": generator.issueDate,
223
+ "Reference": generator.reference,
224
+ "Customer": generator.customerKey,
225
+ "BillingAddress": generator.billingAddress,
226
+ "Description": generator.description,
227
+ "HasLineDescription": hasLineDescription,
228
+ "Lines": []
229
+ }
230
+ if len(generator.lines) == 0:
231
+ raise Exception("No lines found")
232
+
233
+ for line in generator.lines:
234
+ accountKey, description, qty, unit_price = line
235
+ postData["Lines"].append({
236
+ "Account": accountKey,
237
+ "LineDescription": description,
238
+ "Qty": qty,
239
+ "SalesUnitPrice": unit_price
240
+ })
241
+ response = self.sendPostRequest(
242
+ url="/sales-invoice-form",
243
+ loginSession=loginSession,
244
+ data=json.dumps(postData, default=json_dump_s_default_function)
245
+ )
246
+ if response.status_code != 201:
247
+ raise Exception("Error creating sales invoice")
248
+ data = json.loads(response.text)
249
+ return data
250
+
251
+ def pay_sales_invoice(self, loginSession, date, recievedAccountKey, description, accountKey, accountRecivablesCustomer, salesInvoiceKey, amount):
252
+ postData = {
253
+ "Date": date,
254
+ "ReceivedIn": recievedAccountKey,
255
+ "Description": description,
256
+ "PaidBy": 1,
257
+ "Customer": accountRecivablesCustomer,
258
+ "Lines": [
259
+ {
260
+ "Account": accountKey,
261
+ "AccountsReceivableCustomer": accountRecivablesCustomer,
262
+ "AccountsReceivableSalesInvoice": salesInvoiceKey,
263
+ "Amount": amount
264
+ }
265
+ ]
266
+ }
267
+ response = self.sendPostRequest(
268
+ url="/receipt-form",
269
+ loginSession=loginSession,
270
+ data=json.dumps(postData, default=json_dump_s_default_function)
271
+ )
272
+ if response.status_code != 201:
273
+ raise Exception("Error creating sales invoice payment")
274
+ data = json.loads(response.text)
275
+ return data
276
+
277
+ def get_receipt_for_description(self, loginSession, description):
278
+ def enrichParams(params):
279
+ params["term"] = description
280
+ # def skipFn(rec):
281
+ # if date == rec["date"]:
282
+ # return False
283
+ # return True
284
+ return self._get_generator(
285
+ loginSession,
286
+ "receipts",
287
+ "receipts",
288
+ enrichParams=enrichParams
289
+ )
290
+
291
+ def create_purchase_invoice_generator(self, issueDate, reference, supplierKey, description):
292
+ return PurchaseInvoiceGenerator(issueDate, reference, supplierKey, description)
293
+
294
+ def create_purchase_invoice(self, loginSession, generator):
295
+ if not isinstance(generator, PurchaseInvoiceGenerator):
296
+ raise Exception("Wrong type of generator")
297
+ hasLineDescription = False
298
+ for line in generator.lines:
299
+ accountKey, description, qty, unit_price = line
300
+ if description is not None and description != "":
301
+ hasLineDescription = True
302
+ postData = {
303
+ "IssueDate": generator.issueDate,
304
+ "Reference": generator.reference,
305
+ "Supplier": generator.supplierKey,
306
+ "Description": generator.description,
307
+ "HasLineDescription": hasLineDescription,
308
+ "Lines": []
309
+ }
310
+ if len(generator.lines) == 0:
311
+ raise Exception("No lines found")
312
+
313
+ for line in generator.lines:
314
+ accountKey, description, qty, unit_price = line
315
+ postData["Lines"].append({
316
+ "Account": accountKey,
317
+ "LineDescription": description,
318
+ "Qty": qty,
319
+ "PurchaseUnitPrice": unit_price
320
+ })
321
+ response = self.sendPostRequest(
322
+ url="/purchase-invoice-form",
323
+ loginSession=loginSession,
324
+ data=json.dumps(postData, default=json_dump_s_default_function)
325
+ )
326
+ if response.status_code != 201:
327
+ raise Exception("Error creating sales invoice")
328
+ data = json.loads(response.text)
329
+ return data
330
+
331
+ def pay_purchase_invoice(self, loginSession, date, paidFromAccountKey, description, accountKey, accountPayableSupplier, purchaseInvoiceKey, amount):
332
+ postData = {
333
+ "Date": date,
334
+ "PaidFrom": paidFromAccountKey,
335
+ "Description": description,
336
+ "Payee": 2,
337
+ "Supplier": accountPayableSupplier,
338
+ "Lines": [
339
+ {
340
+ "Account": accountKey,
341
+ "AccountsPayableSupplier": accountPayableSupplier,
342
+ "PurchaseInvoice": purchaseInvoiceKey,
343
+ "Amount": amount
344
+ }
345
+ ]
346
+ }
347
+ response = self.sendPostRequest(
348
+ url="/payment-form",
349
+ loginSession=loginSession,
350
+ data=json.dumps(postData, default=json_dump_s_default_function)
351
+ )
352
+ if response.status_code != 201:
353
+ raise Exception("Error creating purchase invoice payment")
354
+ data = json.loads(response.text)
355
+ return data
356
+
357
+ def create_inter_account_transfer(
358
+ self,
359
+ loginSession,
360
+ date,
361
+ reference,
362
+ paidFromAccountKey,
363
+ receivedInAccountKey,
364
+ description,
365
+ amount
366
+ ):
367
+ postData = {
368
+ "Date": date,
369
+ "Reference": reference,
370
+ "PaidFrom": paidFromAccountKey,
371
+ "ReceivedIn": receivedInAccountKey,
372
+ "Description": description,
373
+ "CreditAmount": amount,
374
+ "DebitAmount": amount
375
+ }
376
+
377
+ response = self.sendPostRequest(
378
+ url="/inter-account-transfer-form",
379
+ loginSession=loginSession,
380
+ data=json.dumps(postData, default=json_dump_s_default_function)
381
+ )
382
+
383
+ if response.status_code != 201:
384
+ raise Exception(
385
+ f"Error creating inter account transfer: "
386
+ f"{response.status_code} {response.text}"
387
+ )
388
+
389
+ return json.loads(response.text)
390
+
391
+ def get_business(self, loginSession):
392
+ response = self.sendGetRequest(
393
+ url="/access-tokens?pageSize=1",
394
+ loginSession=loginSession
395
+ )
396
+ if response.status_code != 200:
397
+ raise Exception(
398
+ f"Error getting business name: "
399
+ f"{response.status_code} {response.text}"
400
+ )
401
+ data = json.loads(response.text)
402
+ return data["business"]
403
+
404
+ def attach_to_purchase_invoice(
405
+ self,
406
+ loginSession,
407
+ purchaseInvoiceKey,
408
+ filename,
409
+ fileData,
410
+ fileType=None
411
+ ):
412
+ import mimetypes
413
+ import os
414
+ fileNameToSend = os.path.basename(filename)
415
+ fileNameToSend = quote(fileNameToSend, safe="")
416
+ if fileType is None:
417
+ fileType, _ = mimetypes.guess_type(fileNameToSend)
418
+ if fileType is None:
419
+ fileType = "application/octet-stream"
420
+ business = self.get_business(loginSession)
421
+ url = (self.baseURL + _get_attachment_url(
422
+ purchaseInvoiceKey,
423
+ business["name"]
424
+ )).replace("/api2/", "/")
425
+
426
+ headers = {
427
+ "Content-Type": "application/octet-stream",
428
+ "X-File-Name": fileNameToSend,
429
+ "X-File-Type": fileType,
430
+ }
431
+ if loginSession is not None:
432
+ loginSession.injectHeaders(headers)
433
+
434
+ response = requests.post(
435
+ url=url,
436
+ data=fileData, # raw bytes, not files=
437
+ headers=headers
438
+ )
439
+ if response.status_code not in (200, 201):
440
+ raise Exception(
441
+ f"Error uploading attachment: "
442
+ f"{response.status_code} {response.text}"
443
+ )
444
+ data = json.loads(response.text)
445
+ return data
446
+
447
+ def get_transaction_reporter(self, loginSession):
448
+ def enrichParams(params):
449
+ params["fields"] = ["Date", "Transaction", "Reference", "BankOrCashAccount", "Customer",
450
+ "Supplier", "Employee",
451
+ "Description", "Item", "Account", "BalanceSheetAccount", "ProfitAndLossStatementAccount",
452
+ "LineDescription", "Qty", "UnitPrice", "Project", "Division", "TaxCode", "TaxAmount",
453
+ "Debit", "Credit", "Amount", "Timestamp"
454
+ ]
455
+ gen = self._get_generator(
456
+ loginSession,
457
+ "transactions",
458
+ enrichParams=enrichParams
459
+ #,outputKey="balanceSheetTransactions"
460
+ )
461
+ return TransactionReporter(gen)
@@ -0,0 +1,96 @@
1
+
2
+
3
+ class ExpectedEntryBase:
4
+ def __init__(self, companyObj, date, description):
5
+ self.companyObj = companyObj
6
+ self.date = date
7
+ self.description = description
8
+ self.checkRun = False
9
+ self.isPresent = False
10
+ self.correctionProcessWasRun = False
11
+ self.matched_item = None
12
+
13
+ def _get_start_output_line(self):
14
+ retVal = ""
15
+ if self.is_correctable():
16
+ retVal += "CANCOR "
17
+ else:
18
+ if self.isPresent:
19
+ if self.correctionProcessWasRun:
20
+ retVal += "FIXED "
21
+ else:
22
+ retVal += "OK "
23
+ else:
24
+ retVal += " "
25
+ retVal += (self.companyObj["shortName"] + " ")[:3] + " "
26
+ retVal += self.date + " "
27
+ retVal += self.__class__.__name__[13:] + " "
28
+ return retVal
29
+
30
+ def get_user_output_line(self):
31
+ retVal = self._get_start_output_line()
32
+ retVal += self.description + " "
33
+ retVal += str(self.amount) + " "
34
+ return retVal
35
+
36
+ def check(self, client, recheck=False):
37
+ if not recheck:
38
+ if self.checkRun:
39
+ return # Already checked
40
+ res = self._check(client)
41
+ if res is None:
42
+ return
43
+ self.isPresent = res
44
+ self.checkRun = True
45
+
46
+ def _checkOutcomeStr(self):
47
+ if not self.checkRun:
48
+ return ""
49
+ if self.isPresent:
50
+ return "✅ Found Entry"
51
+ return "❌ Missing"
52
+ # Checks the entry to see if it can be found
53
+ def _check(self, client):
54
+ raise Exception("__check Not Overridden in " + self.__class__.__name__)
55
+
56
+ def is_correctable(self):
57
+ if not self.checkRun:
58
+ return False
59
+ if self.isPresent:
60
+ return False # No need to correct
61
+ return self._is_correctable()
62
+
63
+
64
+ def correct(self, client):
65
+ if not self.is_correctable():
66
+ return 0
67
+ if self.isPresent:
68
+ raise Exception("It is correctable but element already present")
69
+ if self.correctionProcessWasRun:
70
+ raise Exception("Trying to correct twice")
71
+ num = self._correct(client)
72
+ self.check(client, recheck=True)
73
+ if not self.isPresent:
74
+ print("ERROR WHEN TRYING CORRECTION")
75
+ print(self.get_user_output_line())
76
+ print("")
77
+ raise Exception("Did not find created entry - bad error")
78
+ self.correctionProcessWasRun = True
79
+ return num
80
+
81
+
82
+ # Override these both together
83
+ def _is_correctable(self):
84
+ return False
85
+ def _correct(self, client):
86
+ raise Exception("Not Overridden")
87
+ #return 1 #num records corrected
88
+
89
+ def _save_matched_item(self, item):
90
+ self.matched_item = item
91
+
92
+ def get_matched_item(self):
93
+ return self.matched_item
94
+
95
+ # def sorting_weight(self):
96
+ # return 100
@@ -0,0 +1,50 @@
1
+
2
+ from .ExpectedEntryBase import ExpectedEntryBase
3
+
4
+
5
+ class ExpectedEntryInterAccountTransfer(ExpectedEntryBase):
6
+ def __init__(self, companyObj, date, paidFromAccount, description, amount, receivedInAccount, reference):
7
+ # reference, billingAddress needed for creation
8
+ super().__init__(companyObj, date, description)
9
+
10
+ # Extra match on fields
11
+ self.paidFromAccount = paidFromAccount
12
+ self.receivedInAccount = receivedInAccount
13
+ self.amount = amount
14
+
15
+ # Other fields needed for creation
16
+ self.reference = reference
17
+
18
+ def get_user_output_line(self):
19
+ retVal = self._get_start_output_line()
20
+ retVal += "From " + self.paidFromAccount["name"]
21
+ retVal += " -> " + self.receivedInAccount["name"] + " "
22
+ retVal += str(self.amount) + " "
23
+ retVal += " " + self._checkOutcomeStr()
24
+ return retVal
25
+
26
+ def _check(self, client):
27
+ transfers = client.get_inter_account_transfer_for_description(self.companyObj["loginSession"], self.description)
28
+ for transfer in transfers:
29
+ # description already matched
30
+ # Has to match date amount paidFromAccount receivedInAccount
31
+ if transfer["date"] == self.date:
32
+ if transfer["amount"]["value"] == self.amount:
33
+ if transfer["paidFrom"] == self.paidFromAccount["name"]:
34
+ if transfer["receivedIn"] == self.receivedInAccount["name"]:
35
+ return True
36
+ return False
37
+
38
+ def _is_correctable(self):
39
+ return True
40
+ def _correct(self, client):
41
+ transfer_result = client.create_inter_account_transfer(
42
+ loginSession=self.companyObj["loginSession"],
43
+ date=self.date,
44
+ reference=self.reference,
45
+ paidFromAccountKey=self.paidFromAccount["key"],
46
+ receivedInAccountKey=self.receivedInAccount["key"],
47
+ description=self.description,
48
+ amount=self.amount
49
+ )
50
+ return 1 #num records corrected
@@ -0,0 +1,109 @@
1
+ from .ExpectedEntryBase import ExpectedEntryBase
2
+
3
+
4
+ class ExpectedEntryPurchaseInvoice(ExpectedEntryBase):
5
+ def __init__(self, companyObj, date, description, supplier, reference, account, lineDescription, qty, unitPrice, requiredAttachements=0, attachements=[]):
6
+ # reference, billingAddress needed for creation
7
+ super().__init__(companyObj, date, description)
8
+
9
+ # Extra match on fields
10
+ self.supplier = supplier
11
+ self.account = account
12
+ self.qty = qty
13
+ self.unitPrice = unitPrice
14
+
15
+ # Other fields needed for creation
16
+ self.reference = reference
17
+ self.lineDescription = lineDescription
18
+
19
+ self.requiredAttachements = requiredAttachements
20
+ self.attachements = attachements
21
+ if not isinstance(self.attachements, list):
22
+ raise Exception("Attachements should be a list")
23
+ for (filename, filedata) in self.attachements:
24
+ if not isinstance(filename, str):
25
+ raise Exception("Attachment Filename should be a string")
26
+ if not isinstance(filedata, bytes):
27
+ print("Got type", filedata.__class__)
28
+ raise Exception("Attachment filedata should be a x")
29
+
30
+
31
+ # purchaseInvoiceGenerator = client.create_purchase_invoice_generator(
32
+ # issueDate="2026-02-25",
33
+ # reference="REF" + random_string,
34
+ # supplierKey=supplier["key"],
35
+ # description=invoiceDescription
36
+ # )
37
+ # purchaseInvoiceGenerator.addLine(
38
+ # accountKey=expenseAccount["key"],
39
+ # description="Thing I really wanted",
40
+ # qty=1,
41
+ # unit_price=purchaseInvoiceAmount
42
+ # )
43
+
44
+ def get_user_output_line(self):
45
+ retVal = self._get_start_output_line()
46
+ retVal += self.supplier["name"] + " "
47
+ retVal += self.description + " "
48
+ retVal += str(self.qty) + "x" + str(self.unitPrice) + " "
49
+ retVal += "(" + self.account["name"] + ") "
50
+ retVal += " " + self._checkOutcomeStr()
51
+ return retVal
52
+
53
+ def _check(self, client):
54
+ # fields to match
55
+ # date
56
+ # description
57
+ # self.supplier = supplier
58
+ # self.account = account # Not going to be able to match on line account
59
+ # self.qty = qty
60
+ # self.unitPrice = unitPrice
61
+ invoices = client.get_purchase_invoices_for_supplier(self.companyObj["loginSession"], self.supplier["name"])
62
+ for invoice in invoices:
63
+ if invoice["issueDate"] == self.date:
64
+ if invoice["description"] == self.description:
65
+ if invoice["invoiceAmount"]["value"] == self.get_total_amount():
66
+ self._save_matched_item(invoice)
67
+ return True
68
+
69
+ return False
70
+
71
+ def get_name(self):
72
+ # Used in payment get_user_output_line display
73
+ return self.supplier["name"]
74
+
75
+ def get_total_amount(self):
76
+ return self.qty * self.unitPrice
77
+
78
+ def _is_correctable(self):
79
+ if len(self.attachements) != self.requiredAttachements:
80
+ return False
81
+ return True # False for now change later
82
+ def _correct(self, client):
83
+ purchaseInvoiceGenerator = client.create_purchase_invoice_generator(
84
+ issueDate=self.date,
85
+ reference=self.reference,
86
+ supplierKey=self.supplier["key"],
87
+ description=self.description
88
+ )
89
+ purchaseInvoiceGenerator.addLine(
90
+ accountKey=self.account["key"],
91
+ description=self.lineDescription,
92
+ qty=self.qty,
93
+ unit_price=self.unitPrice
94
+ )
95
+ purchase_result = client.create_purchase_invoice(self.companyObj["loginSession"], purchaseInvoiceGenerator)
96
+ self._save_matched_item(purchase_result)
97
+
98
+ for (filename, filedata) in self.attachements:
99
+ upload_attach_result = client.attach_to_purchase_invoice(
100
+ loginSession=self.companyObj["loginSession"],
101
+ purchaseInvoiceKey=purchase_result["Key"],
102
+ filename=filename,
103
+ fileData=filedata,
104
+ fileType=None # will guess if none is provided
105
+ )
106
+ return 1 #num records corrected
107
+
108
+ def sorting_weight(self):
109
+ return 50 # must before purchase invoice payments
@@ -0,0 +1,75 @@
1
+ from .ExpectedEntryBase import ExpectedEntryBase
2
+ from .ExpectedEntryPurchaseInvoice import ExpectedEntryPurchaseInvoice
3
+
4
+ class ExpectedEntryPurchaseInvoicePayment(ExpectedEntryBase):
5
+ def __init__(self, companyObj, date, description, paidFromAccount, accountPayableSupplier, account, amount, purchaseInvoice):
6
+ # reference, billingAddress needed for creation
7
+ super().__init__(companyObj, date, description)
8
+
9
+ # Extra match on fields
10
+ self.paidFromAccount = paidFromAccount
11
+ self.accountPayableSupplier = accountPayableSupplier
12
+ self.amount = amount
13
+
14
+ # Other fields needed for creation
15
+ self.account = account # e.g. accounts payable
16
+ self.purchaseInvoice = purchaseInvoice
17
+ if not isinstance(purchaseInvoice, ExpectedEntryPurchaseInvoice):
18
+ raise Exception("Must have a purchase invoice to relate to")
19
+ if self.companyObj["name"] != self.purchaseInvoice.companyObj["name"]:
20
+ raise Exception("Purchase Invoice does not belong to the same company")
21
+ if self.amount != self.purchaseInvoice.get_total_amount():
22
+ raise Exception ("Purchase invoice NOT being paid in full")
23
+
24
+ # purchaseInvoicePaymentResult = client.pay_purchase_invoice(
25
+ # loginSession=testBusinessLoginSesison,
26
+ # date="2026-02-25",
27
+ # paidFromAccountKey=rebloutCashAccount["key"],
28
+ # description="pay Desc",
29
+ # accountKey=accountsPayable["key"],
30
+ # accountPayableSupplier=supplier["key"],
31
+ # purchaseInvoiceKey=purchase_result["Key"],
32
+ # amount=purchaseInvoiceAmount
33
+ # )
34
+
35
+ def get_user_output_line(self):
36
+ retVal = self._get_start_output_line()
37
+ retVal += "Paying " + self.purchaseInvoice.get_name() + " "
38
+ retVal += self.paidFromAccount["name"] + " "
39
+ retVal += self.description + " "
40
+ retVal += str(self.amount) + " "
41
+ retVal += "(" + self.account["name"] + ") "
42
+ retVal += " " + self._checkOutcomeStr()
43
+ return retVal
44
+
45
+ def _check(self, client):
46
+ # fields to match
47
+ # date
48
+ # description
49
+ # self.paidFromAccount = paidFromAccount
50
+ # self.accountPayableSupplier = accountPayableSupplier
51
+ # self.amount = amount
52
+ payments = client.get_payment_for_description(self.companyObj["loginSession"], self.description)
53
+ for payment in payments:
54
+ #print("purchase invoice Payment Match Debug", payment)
55
+ if payment["date"] == self.date:
56
+ if payment["paidFrom"] == self.paidFromAccount["name"]:
57
+ if payment["payee"] == self.accountPayableSupplier["name"]:
58
+ if payment["amount"]["value"] == self.amount:
59
+ return True
60
+ return False
61
+
62
+ def _is_correctable(self):
63
+ return True
64
+ def _correct(self, client):
65
+ purchaseInvoicePaymentResult = client.pay_purchase_invoice(
66
+ loginSession=self.companyObj["loginSession"],
67
+ date=self.date,
68
+ paidFromAccountKey=self.paidFromAccount["key"],
69
+ description=self.description,
70
+ accountKey=self.account["key"],
71
+ accountPayableSupplier=self.accountPayableSupplier["key"],
72
+ purchaseInvoiceKey=self.purchaseInvoice.get_matched_item()["key"],
73
+ amount=self.amount
74
+ )
75
+ return 1 #num records corrected
@@ -0,0 +1,88 @@
1
+ from .ExpectedEntryBase import ExpectedEntryBase
2
+
3
+
4
+ class ExpectedEntrySalesInvoice(ExpectedEntryBase):
5
+ def __init__(self, companyObj, issueDate, description, customer, reference, billingAddress, account, lineDescription, qty, unitPrice):
6
+ # reference, billingAddress needed for creation
7
+ super().__init__(companyObj, date=issueDate, description=description)
8
+
9
+ # Extra match on fields
10
+ self.customer = customer
11
+ self.account = account
12
+ self.qty = qty
13
+ self.unitPrice = unitPrice
14
+
15
+ # Other fields needed for creation
16
+ self.reference = reference
17
+ self.lineDescription = lineDescription
18
+ self.billingAddress = billingAddress
19
+
20
+ # salesInvoiceGenerator = client.create_sales_invoice_generator(
21
+ # issueDate="2026-01-31",
22
+ # reference="REF",
23
+ # customerKey=customer["key"],
24
+ # billingAddress="One the road\nthe street\nth towh, DF3 F45",
25
+ # description=invoiceDescription
26
+ # )
27
+ # salesInvoiceGenerator.addLine(
28
+ # accountKey=account["key"],
29
+ # description="TODO",
30
+ # qty=2,
31
+ # unit_price=12.34
32
+ # )
33
+
34
+ def get_user_output_line(self):
35
+ retVal = self._get_start_output_line()
36
+ retVal += self.customer["name"] + " "
37
+ retVal += self.description + " "
38
+ retVal += str(self.qty) + "x" + str(self.unitPrice) + " "
39
+ retVal += "(" + self.account["name"] + ") "
40
+ retVal += " " + self._checkOutcomeStr()
41
+ return retVal
42
+
43
+ def _check(self, client):
44
+ # fields to match
45
+ # date
46
+ # description
47
+ # self.customer = customer
48
+ # self.account = account # Not going to be able to match on line account
49
+ # self.qty = qty
50
+ # self.unitPrice = unitPrice
51
+ invoices = client.get_sales_invoices_for_customer(self.companyObj["loginSession"], self.customer["name"])
52
+ for invoice in invoices:
53
+ if invoice["issueDate"] == self.date:
54
+ if invoice["description"] == self.description:
55
+ if invoice["invoiceAmount"]["value"] == self.get_total_amount():
56
+ self._save_matched_item(invoice)
57
+ return True
58
+ return False
59
+
60
+ def get_name(self):
61
+ # Used in payment get_user_output_line display
62
+ return self.customer["name"]
63
+
64
+ def get_total_amount(self):
65
+ return self.qty * self.unitPrice
66
+
67
+ def _is_correctable(self):
68
+ return True
69
+ def _correct(self, client):
70
+ salesInvoiceGenerator = client.create_sales_invoice_generator(
71
+ issueDate=self.date,
72
+ reference=self.reference,
73
+ customerKey=self.customer["key"],
74
+ billingAddress=self.billingAddress,
75
+ description=self.description
76
+ )
77
+ salesInvoiceGenerator.addLine(
78
+ accountKey=self.account["key"],
79
+ description=self.lineDescription,
80
+ qty=self.qty,
81
+ unit_price=self.unitPrice
82
+ )
83
+ sales_invoice_info = client.create_sales_invoice(self.companyObj["loginSession"], salesInvoiceGenerator)
84
+ self._save_matched_item(sales_invoice_info)
85
+ return 1
86
+
87
+ def sorting_weight(self):
88
+ return 50 # must before purchase invoice patments
@@ -0,0 +1,76 @@
1
+ from .ExpectedEntryBase import ExpectedEntryBase
2
+ from .ExpectedEntrySalesInvoice import ExpectedEntrySalesInvoice
3
+
4
+ class ExpectedEntrySalesInvoiceReceipt(ExpectedEntryBase):
5
+ def __init__(self, companyObj, date, description, receivedAccount, accountRecivablesCustomer, account, amount, salesInvoice):
6
+ super().__init__(companyObj, date, description)
7
+
8
+ # Extra match on fields
9
+ self.receivedAccount = receivedAccount
10
+ self.accountReceivablesCustomer = accountRecivablesCustomer
11
+ self.amount = amount
12
+
13
+ # Other fields needed for creation
14
+ self.account = account # e.g. accounts payable
15
+ self.salesInvoice = salesInvoice
16
+ if not isinstance(salesInvoice, ExpectedEntrySalesInvoice):
17
+ raise Exception("Must have a sales invoice to relate to")
18
+ if self.companyObj["name"] != self.salesInvoice.companyObj["name"]:
19
+ raise Exception("Sales Invoice does not belong to the same company")
20
+ if self.amount != self.salesInvoice.get_total_amount():
21
+ raise Exception ("Sales invoice NOT being paid in full")
22
+
23
+ # result = client.pay_sales_invoice(
24
+ # loginSession=testBusinessLoginSesison,
25
+ # date="2026-11-30",
26
+ # recievedAccountKey=rebloutCashAccount["key"],
27
+ # description="pay Desc",
28
+ # accountKey=accountsRecievable["key"],
29
+ # accountReceivablesCustomer=customer["key"],
30
+ # salesInvoiceKey=looked_up_sales_invoice["key"],
31
+ # amount=1.01
32
+ # )
33
+
34
+
35
+ def get_user_output_line(self):
36
+ retVal = self._get_start_output_line()
37
+ retVal += "Paying " + self.salesInvoice.get_name() + " "
38
+ retVal += self.receivedAccount["name"] + " "
39
+ retVal += self.description + " "
40
+ retVal += str(self.amount) + " "
41
+ retVal += "(" + self.account["name"] + ") "
42
+ retVal += " " + self._checkOutcomeStr()
43
+ return retVal
44
+
45
+ def _check(self, client):
46
+ # fields to match
47
+ # date
48
+ # description
49
+ # self.receivedAccount = receivedAccount
50
+ # self.accountReceivablesCustomer = accountReceivablesCustomer
51
+ # self.amount = amount
52
+ # Paid By is always going to be None
53
+ payments = client.get_receipt_for_description(self.companyObj["loginSession"], self.description)
54
+ for payment in payments:
55
+ ##print("sales invoice receipt Match Debug", payment)
56
+ if payment["date"] == self.date:
57
+ if payment["paidBy"] == self.accountReceivablesCustomer["name"]:
58
+ if payment["receivedIn"]["key"] == self.receivedAccount["key"]:
59
+ if payment["amount"]["value"] == self.amount:
60
+ return True
61
+ return False
62
+
63
+ def _is_correctable(self):
64
+ return True
65
+ def _correct(self, client):
66
+ result = client.pay_sales_invoice(
67
+ loginSession=self.companyObj["loginSession"],
68
+ date=self.date,
69
+ recievedAccountKey=self.receivedAccount["key"],
70
+ description=self.description,
71
+ accountKey=self.account["key"],
72
+ accountRecivablesCustomer=self.accountReceivablesCustomer["key"],
73
+ salesInvoiceKey=self.salesInvoice.get_matched_item()["key"],
74
+ amount=self.amount
75
+ )
76
+ return 1 #num records corrected
@@ -0,0 +1,48 @@
1
+
2
+
3
+
4
+ class Pattern:
5
+ entries = None
6
+ key = None
7
+ def __init__(self,key):
8
+ self.entries = []
9
+ self.key = key
10
+ def add_entry(self, expected_entry):
11
+ self.entries.append(expected_entry)
12
+
13
+ def get_key(self):
14
+ return self.key
15
+
16
+ def get_user_output(self):
17
+ retVal = ""
18
+
19
+ for entry in self.entries:
20
+ retVal += entry.get_user_output_line() + "\n"
21
+
22
+ return retVal
23
+
24
+ def check_all_enteries(self, client):
25
+ for entry in self.entries:
26
+ entry.check(client)
27
+
28
+ def num_correctable_entries(self, client):
29
+ self.check_all_enteries(client)
30
+ retVal =0
31
+ for entry in self.entries:
32
+ if entry.is_correctable():
33
+ retVal += 1
34
+ return retVal
35
+
36
+ def is_correctable(self, client):
37
+ self.check_all_enteries(client)
38
+ for entry in self.entries:
39
+ if not entry.is_correctable():
40
+ return False
41
+ return True
42
+
43
+ def run_corrections(self, client):
44
+ number_corrected = 0
45
+ ##self.entries.sort(key=lambda entry: entry.sorting_weight())
46
+ for entry in self.entries:
47
+ number_corrected += entry.correct(client)
48
+ return number_corrected
@@ -0,0 +1,6 @@
1
+ from .Pattern import Pattern
2
+ from .ExpectedEntrySalesInvoice import ExpectedEntrySalesInvoice
3
+ from .ExpectedEntryInterAccountTransfer import ExpectedEntryInterAccountTransfer
4
+ from .ExpectedEntryPurchaseInvoice import ExpectedEntryPurchaseInvoice
5
+ from .ExpectedEntryPurchaseInvoicePayment import ExpectedEntryPurchaseInvoicePayment
6
+ from .ExpectedEntrySalesInvoiceReceipt import ExpectedEntrySalesInvoiceReceipt
@@ -0,0 +1,18 @@
1
+ from PythonAPIClientBase import LoginSession
2
+
3
+ class ManagerLoginSession(LoginSession):
4
+ token = None
5
+ def __init__(self, client, token):
6
+ self.client = client
7
+ self.token = token
8
+
9
+ def injectHeaders(self, headers):
10
+ headers["X-API-KEY"] = self.token
11
+
12
+ def refresh(self):
13
+ return False
14
+
15
+
16
+ class ManagerLoginSessionFromAuthToken(ManagerLoginSession):
17
+ def __init__(self, client, access_token):
18
+ super().__init__(client=client, token=access_token)
@@ -0,0 +1,13 @@
1
+
2
+
3
+ class PurchaseInvoiceGenerator:
4
+ lines = None
5
+ def __init__(self, issueDate, reference, supplierKey, description):
6
+ self.issueDate = issueDate
7
+ self.reference = reference
8
+ self.supplierKey = supplierKey
9
+ self.description = description
10
+ self.lines = []
11
+
12
+ def addLine(self, accountKey, description, qty, unit_price):
13
+ self.lines.append((accountKey, description, qty, unit_price))
@@ -0,0 +1,14 @@
1
+
2
+
3
+ class SalesInvoiceGenerator:
4
+ lines = None
5
+ def __init__(self, issueDate, reference, customerKey, billingAddress, description):
6
+ self.issueDate = issueDate
7
+ self.reference = reference
8
+ self.customerKey = customerKey
9
+ self.billingAddress = billingAddress
10
+ self.description = description
11
+ self.lines = []
12
+
13
+ def addLine(self, accountKey, description, qty, unit_price):
14
+ self.lines.append((accountKey, description, qty, unit_price))
@@ -0,0 +1,48 @@
1
+ from decimal import Decimal
2
+
3
+ CASH_ACCOUNT_NAME = "Cash & cash equivalents"
4
+
5
+ class TransactionReporter:
6
+ mainData = None
7
+ # Dict, first key is the name of the bankOrCashAccount
8
+ def __init__(self, generator):
9
+ self.mainData = {}
10
+ for transaction in generator:
11
+ transaction["amount"] = Decimal(str(transaction["amount"]))
12
+ if transaction["bankOrCashAccount"] not in self.mainData:
13
+ self.mainData[transaction["bankOrCashAccount"]] = []
14
+ self.mainData[transaction["bankOrCashAccount"]].append(transaction)
15
+
16
+ def get_bank_or_cash_accounts(self):
17
+ return [account for account in self.mainData if account is not None]
18
+
19
+ def get_bank_or_cash_account_entries(self, bankOrCashAccountName):
20
+ if bankOrCashAccountName not in self.mainData:
21
+ raise Exception("bankOrCashAccountName not found")
22
+ retVal = []
23
+ for entry in self.mainData[bankOrCashAccountName]:
24
+ if entry["account"] == CASH_ACCOUNT_NAME:
25
+ retVal.append(entry)
26
+ return sorted(retVal, key=lambda entry: entry["date"])
27
+
28
+ def get_customer_receipts(self, customerName):
29
+ retVal = []
30
+ for account in self.mainData.keys():
31
+ if account is not None:
32
+ for entry in self.mainData[account]:
33
+ if entry["transaction"] == "Receipt":
34
+ if entry["customer"] == customerName:
35
+ if entry["account"] == CASH_ACCOUNT_NAME:
36
+ retVal.append(entry)
37
+ return sorted(retVal, key=lambda entry: entry["date"])
38
+
39
+ def get_supplier_payments(self, supplierName):
40
+ retVal = []
41
+ for account in self.mainData.keys():
42
+ if account is not None:
43
+ for entry in self.mainData[account]:
44
+ if entry["transaction"] == "Payment":
45
+ if entry["supplier"] == supplierName:
46
+ if entry["account"] == CASH_ACCOUNT_NAME:
47
+ retVal.append(entry)
48
+ return sorted(retVal, key=lambda entry: entry["date"])
@@ -0,0 +1,7 @@
1
+ from .ManagerLoginSession import ManagerLoginSessionFromAuthToken
2
+ from .Client import ManagerClient
3
+ from .ExpectedEntry import Pattern, ExpectedEntrySalesInvoice, ExpectedEntryInterAccountTransfer, ExpectedEntryPurchaseInvoice, ExpectedEntryPurchaseInvoicePayment, ExpectedEntrySalesInvoiceReceipt
4
+
5
+ def hello_world() -> str:
6
+ """Return the library's initial greeting."""
7
+ return "Hello, world!"
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.5
2
+ Name: ManagerPythonClient
3
+ Version: 0.0.8
4
+ Summary: Manager python client for accessing API.
5
+ Project-URL: Homepage, https://github.com/rmetcalf9/ManagerPythonClient
6
+ Project-URL: Repository, https://github.com/rmetcalf9/ManagerPythonClient
7
+ Author: Robert Metcalf
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Robert Metcalf
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3 :: Only
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Requires-Python: >=3.11
39
+ Requires-Dist: inquirerpy==0.3.4
40
+ Requires-Dist: pythonapiclientbase==0.0.19
41
+ Description-Content-Type: text/markdown
42
+
43
+ # Python client for Manager API
44
+
45
+ ## Development
46
+
47
+ Run the tests with:
48
+
49
+ ```bash
50
+ uv run --with pytest pytest
51
+ ```
52
+
53
+ Build the distribution with:
54
+
55
+ ```bash
56
+ uv build
57
+ ```
58
+
59
+ ## Release
60
+
61
+ use release.sh
@@ -0,0 +1,18 @@
1
+ manager_python_client/Client.py,sha256=RkQ5JxRj9kcQPgHM2ZAeDvqqhUL9j9Y2HrKZWXKaGLo,17323
2
+ manager_python_client/ManagerLoginSession.py,sha256=2Yh7sg1rOz5Vl7yreJOg2rs75Ips6DvwflPGFu5I6zc,494
3
+ manager_python_client/PurchaseInvoiceGenerator.py,sha256=tqLfDH2-8cyn5Wdb0meUFkpd7F-m5ZIri0r7eyLsDhs,431
4
+ manager_python_client/SalesInvoiceGenerator.py,sha256=haDXdKEYYFX887CAtSqdQiPBWURIA6X-Gd9Vb94_NI8,489
5
+ manager_python_client/TransactionReporter.py,sha256=G0E6NjnYbkKD-bUZMZN25xoKq5xvQg3QSoajVnmtUjI,2138
6
+ manager_python_client/__init__.py,sha256=1HV81Wo_THzZoRHubyu4muB2j2Qca3hYo-I1F81FIuI,401
7
+ manager_python_client/ExpectedEntry/ExpectedEntryBase.py,sha256=FxQbuvTdQXaO8CvgcRMg5KvnNtG8AWq4oCnzEtfFmos,2921
8
+ manager_python_client/ExpectedEntry/ExpectedEntryInterAccountTransfer.py,sha256=LJte0MT85nFIEh7LSevwehz0QJQGWFNYTHcB_wYesYY,2044
9
+ manager_python_client/ExpectedEntry/ExpectedEntryPurchaseInvoice.py,sha256=T9NPeErOYdo3-ZPbFIO2x9U9FndvBY8FtWKhI54Zizo,4284
10
+ manager_python_client/ExpectedEntry/ExpectedEntryPurchaseInvoicePayment.py,sha256=RizfiVohV5BJ3WWHXW0W1ZQx9IhByCKigw-WKVCzyEs,3416
11
+ manager_python_client/ExpectedEntry/ExpectedEntrySalesInvoice.py,sha256=tun6gMS4hcVg3LZN3qbcxbKLq_WrSfu0uW0QAUwvorg,3276
12
+ manager_python_client/ExpectedEntry/ExpectedEntrySalesInvoiceReceipt.py,sha256=WnsqHKtIQAp3WlyIbJEdfFhfwFL56onxMFLZKisPnGA,3340
13
+ manager_python_client/ExpectedEntry/Pattern.py,sha256=YL3EiCOD9b_IARnDDFtc3UJ1z0DaT-9LmM1P-C3EuGE,1237
14
+ manager_python_client/ExpectedEntry/__init__.py,sha256=ji1JulYKLCLkumsqQG4dZ0v08LwTuREKXcVBF7NiGds,410
15
+ managerpythonclient-0.0.8.dist-info/METADATA,sha256=S2ToKeFgFEbSI9togGQZWvALC8w9GqX72IwTHdudYQ0,2270
16
+ managerpythonclient-0.0.8.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
17
+ managerpythonclient-0.0.8.dist-info/licenses/LICENSE,sha256=ODtATG_0rj1J6X6amg9J_EU-Re2PnJX88IE_vyH6GSA,1071
18
+ managerpythonclient-0.0.8.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robert Metcalf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.