rpa-erpnext 1.0.0__py3-none-any.whl → 1.0.1__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.
RPA/ERPNext.py CHANGED
@@ -4,13 +4,77 @@ import requests
4
4
  from robot.api.deco import keyword, library
5
5
  @library(scope='GLOBAL', auto_keywords=False)
6
6
  class ERPNextLibrary:
7
- def __init__(self, base_url, api_key, api_secret):
7
+ def __init__(self, base_url=None, api_key=None, api_secret=None):
8
+ self.base_url = None
9
+ self.session = None
10
+ if base_url and api_key and api_secret:
11
+ self.connect(base_url, api_key, api_secret)
12
+
13
+ def connect(self, base_url, api_key=None, api_secret=None, access_token=None):
14
+ """
15
+ Kết nối tới ERPNext server.
16
+
17
+ Args:
18
+ base_url: URL của ERPNext server
19
+ api_key: API Key (cho Token Auth)
20
+ api_secret: API Secret (cho Token Auth)
21
+ access_token: OAuth2 Access Token (Bearer Auth)
22
+ """
8
23
  self.base_url = base_url.rstrip("/")
9
24
  self.session = requests.Session()
10
- self.session.auth = (api_key, api_secret)
25
+
26
+ headers = {
27
+ "Accept": "application/json",
28
+ "Content-Type": "application/json"
29
+ }
30
+
31
+ if access_token:
32
+ headers["Authorization"] = f"Bearer {access_token}"
33
+ elif api_key and api_secret:
34
+ headers["Authorization"] = f"token {api_key}:{api_secret}"
35
+
36
+ self.session.headers.update(headers)
37
+ return {"status": "connected", "base_url": self.base_url}
38
+
39
+ @keyword("Setup ERPNext Connection")
40
+ def setup_erpnext_connection(self, token_file_path: str):
41
+ """
42
+ Thiết lập kết nối với ERPNext từ file credential JSON.
43
+
44
+ Hỗ trợ 2 format:
45
+ 1. Token Auth (API Key/Secret):
46
+ {
47
+ "base_url": "...",
48
+ "api_key": "...",
49
+ "api_secret": "..."
50
+ }
51
+
52
+ 2. OAuth2 (Access Token):
53
+ {
54
+ "base_url": "...",
55
+ "access_token": "..."
56
+ }
57
+ """
58
+ with open(token_file_path, 'r', encoding='utf-8') as f:
59
+ data = json.load(f)
60
+
61
+ base_url = data.get("base_url")
62
+ api_key = data.get("api_key")
63
+ api_secret = data.get("api_secret")
64
+ access_token = data.get("access_token")
65
+
66
+ if not base_url:
67
+ raise Exception("Token file must contain base_url")
68
+
69
+ if access_token:
70
+ return self.connect(base_url, access_token=access_token)
71
+ elif api_key and api_secret:
72
+ return self.connect(base_url, api_key=api_key, api_secret=api_secret)
73
+ else:
74
+ raise Exception("Token file must contain credentials (api_key+api_secret OR access_token)")
11
75
 
12
76
  # ===========================================================
13
- # Utility
77
+ # Utility Methods
14
78
  # ===========================================================
15
79
 
16
80
  def create_resource(self, doctype, data):
@@ -29,8 +93,90 @@ class ERPNextLibrary:
29
93
  res = self.session.get(f"{self.base_url}/api/resource/{doctype}/{name}")
30
94
  return res.ok
31
95
 
96
+ @keyword("Get Document")
97
+ def get_document(self, doctype, name):
98
+ """
99
+ Lấy thông tin chi tiết của một document.
100
+
101
+ Args:
102
+ doctype: Loại document (Item, Customer, Sales Order, etc.)
103
+ name: Tên/ID của document
104
+
105
+ Example:
106
+ | ${doc}= | Get Document | Customer | CUST-00001 |
107
+ """
108
+ return self.get_doc(doctype, name)
109
+
110
+ @keyword("List Documents")
111
+ def list_documents(self, doctype, filters=None, fields=None, limit=20):
112
+ """
113
+ Lấy danh sách documents với filters.
114
+
115
+ Args:
116
+ doctype: Loại document
117
+ filters: Điều kiện lọc (dict hoặc JSON string)
118
+ fields: Các trường cần lấy (list hoặc JSON string)
119
+ limit: Số lượng kết quả tối đa
120
+
121
+ Example:
122
+ | ${items}= | List Documents | Item | {"item_group": "Products"} | ["item_code", "item_name"] | 10 |
123
+ """
124
+ params = {"limit_page_length": limit}
125
+
126
+ if filters:
127
+ if isinstance(filters, str):
128
+ filters = json.loads(filters)
129
+ params["filters"] = json.dumps(filters)
130
+
131
+ if fields:
132
+ if isinstance(fields, str):
133
+ fields = json.loads(fields)
134
+ params["fields"] = json.dumps(fields)
135
+
136
+ res = self.session.get(f"{self.base_url}/api/resource/{doctype}", params=params)
137
+ res.raise_for_status()
138
+ return res.json().get("data", [])
139
+
140
+ @keyword("Update Document")
141
+ def update_document(self, doctype, name, data):
142
+ """
143
+ Cập nhật một document.
144
+
145
+ Args:
146
+ doctype: Loại document
147
+ name: Tên/ID của document
148
+ data: Dữ liệu cần cập nhật (dict hoặc JSON string)
149
+
150
+ Example:
151
+ | Update Document | Item | ITEM-001 | {"description": "New description"} |
152
+ """
153
+ if isinstance(data, str):
154
+ data = json.loads(data)
155
+
156
+ res = self.session.put(f"{self.base_url}/api/resource/{doctype}/{name}", json=data)
157
+ if not res.ok:
158
+ raise Exception(f"HTTP {res.status_code}: {res.text}")
159
+ return res.json().get("data", res.json())
160
+
161
+ @keyword("Delete Document")
162
+ def delete_document(self, doctype, name):
163
+ """
164
+ Xóa một document.
165
+
166
+ Args:
167
+ doctype: Loại document
168
+ name: Tên/ID của document
169
+
170
+ Example:
171
+ | Delete Document | Item | ITEM-001 |
172
+ """
173
+ res = self.session.delete(f"{self.base_url}/api/resource/{doctype}/{name}")
174
+ if not res.ok:
175
+ raise Exception(f"HTTP {res.status_code}: {res.text}")
176
+ return {"status": "deleted", "doctype": doctype, "name": name}
177
+
32
178
  # ===========================================================
33
- # Ensure Hàm nền tảng
179
+ # Ensure Functions - Master Data
34
180
  # ===========================================================
35
181
 
36
182
  @keyword("Ensure Company Exist")
@@ -104,8 +250,305 @@ class ERPNextLibrary:
104
250
  created_items.append(res)
105
251
  return created_items
106
252
 
253
+ @keyword("Ensure Customer Exist")
254
+ def ensure_customer_exist(self, customer_name: str, customer_group="Commercial", territory="Vietnam"):
255
+ """
256
+ Đảm bảo Customer tồn tại.
257
+
258
+ API: /api/resource/Customer
259
+
260
+ Args:
261
+ customer_name: Tên khách hàng
262
+ customer_group: Nhóm khách hàng (mặc định: Commercial)
263
+ territory: Khu vực (mặc định: Vietnam)
264
+ """
265
+ if self.exists("Customer", customer_name):
266
+ return {"status": "exists", "name": customer_name}
267
+
268
+ data = {
269
+ "doctype": "Customer",
270
+ "customer_name": customer_name,
271
+ "customer_type": "Company",
272
+ "customer_group": customer_group,
273
+ "territory": territory,
274
+ }
275
+ return self.create_resource("Customer", data)
276
+
277
+ # ===========================================================
278
+ # Sales APIs - Quy trình bán hàng
279
+ # ===========================================================
280
+
281
+ @keyword("Create Sales Order")
282
+ def create_sales_order(self, customer: str, items: list, delivery_date: str, company: str):
283
+ """
284
+ Tạo Sales Order (Đơn hàng bán).
285
+
286
+ API: /api/resource/Sales Order
287
+
288
+ Args:
289
+ customer: Tên khách hàng
290
+ items: Danh sách items [{"item_code": "ITEM-001", "qty": 10, "rate": 1000}]
291
+ delivery_date: Ngày giao hàng (format: YYYY-MM-DD)
292
+ company: Tên công ty
293
+
294
+ Example:
295
+ | ${items}= | Create List | {"item_code": "ITEM-001", "qty": 10, "rate": 1000} |
296
+ | Create Sales Order | Customer A | ${items} | 2025-12-01 | My Company |
297
+ """
298
+ self.ensure_customer_exist(customer)
299
+
300
+ order_items = []
301
+ for item in items:
302
+ order_items.append({
303
+ "item_code": item["item_code"],
304
+ "qty": item["qty"],
305
+ "rate": item.get("rate", 0),
306
+ "delivery_date": delivery_date,
307
+ "uom": item.get("uom", "Nos"),
308
+ })
309
+
310
+ data = {
311
+ "doctype": "Sales Order",
312
+ "customer": customer,
313
+ "company": company,
314
+ "delivery_date": delivery_date,
315
+ "items": order_items
316
+ }
317
+ return self.create_resource("Sales Order", data)
318
+
319
+ @keyword("Create Sales Invoice")
320
+ def create_sales_invoice(self, customer: str, items: list, company: str, posting_date=None):
321
+ """
322
+ Tạo Sales Invoice (Hóa đơn bán hàng).
323
+
324
+ API: /api/resource/Sales Invoice
325
+
326
+ Args:
327
+ customer: Tên khách hàng
328
+ items: Danh sách items [{"item_code": "ITEM-001", "qty": 10, "rate": 1000}]
329
+ company: Tên công ty
330
+ posting_date: Ngày lập hóa đơn (mặc định: ngày hiện tại)
331
+ """
332
+ self.ensure_customer_exist(customer)
333
+
334
+ invoice_items = []
335
+ for item in items:
336
+ invoice_items.append({
337
+ "item_code": item["item_code"],
338
+ "qty": item["qty"],
339
+ "rate": item.get("rate", 0),
340
+ "uom": item.get("uom", "Nos"),
341
+ })
342
+
343
+ data = {
344
+ "doctype": "Sales Invoice",
345
+ "customer": customer,
346
+ "company": company,
347
+ "items": invoice_items
348
+ }
349
+ if posting_date:
350
+ data["posting_date"] = posting_date
351
+
352
+ return self.create_resource("Sales Invoice", data)
353
+
354
+ # ===========================================================
355
+ # Stock/Inventory APIs - Quản lý kho
356
+ # ===========================================================
357
+
358
+ @keyword("Create Stock Entry")
359
+ def create_stock_entry(self, purpose: str, items: list, company: str, posting_date=None):
360
+ """
361
+ Tạo Stock Entry (Phiếu nhập/xuất kho).
362
+
363
+ API: /api/resource/Stock Entry
364
+
365
+ Args:
366
+ purpose: Mục đích (Material Receipt, Material Issue, Material Transfer, etc.)
367
+ items: Danh sách items với warehouse
368
+ company: Tên công ty
369
+ posting_date: Ngày lập phiếu
370
+
371
+ Purpose types:
372
+ - Material Receipt: Nhập kho
373
+ - Material Issue: Xuất kho
374
+ - Material Transfer: Chuyển kho
375
+ - Manufacture: Sản xuất
376
+ - Repack: Đóng gói lại
377
+
378
+ Example:
379
+ | ${items}= | Create List | {"item_code": "ITEM-001", "qty": 100, "t_warehouse": "Stores - EDU"} |
380
+ | Create Stock Entry | Material Receipt | ${items} | My Company |
381
+ """
382
+ stock_items = []
383
+ for item in items:
384
+ stock_items.append({
385
+ "item_code": item["item_code"],
386
+ "qty": item["qty"],
387
+ "s_warehouse": item.get("s_warehouse"), # Source warehouse
388
+ "t_warehouse": item.get("t_warehouse"), # Target warehouse
389
+ "uom": item.get("uom", "Nos"),
390
+ "stock_uom": item.get("stock_uom", "Nos"),
391
+ "conversion_factor": item.get("conversion_factor", 1.0),
392
+ })
393
+
394
+ data = {
395
+ "doctype": "Stock Entry",
396
+ "purpose": purpose,
397
+ "company": company,
398
+ "items": stock_items
399
+ }
400
+ if posting_date:
401
+ data["posting_date"] = posting_date
402
+
403
+ return self.create_resource("Stock Entry", data)
404
+
405
+ @keyword("Create Purchase Receipt")
406
+ def create_purchase_receipt(self, supplier: str, items: list, company: str, posting_date=None):
407
+ """
408
+ Tạo Purchase Receipt (Phiếu nhận hàng mua).
409
+
410
+ API: /api/resource/Purchase Receipt
411
+
412
+ Args:
413
+ supplier: Tên nhà cung cấp
414
+ items: Danh sách items [{"item_code": "ITEM-001", "qty": 100, "rate": 1000, "warehouse": "Stores - EDU"}]
415
+ company: Tên công ty
416
+ posting_date: Ngày nhận hàng
417
+ """
418
+ self.ensure_supplier_exist(supplier)
419
+
420
+ receipt_items = []
421
+ for item in items:
422
+ receipt_items.append({
423
+ "item_code": item["item_code"],
424
+ "qty": item["qty"],
425
+ "rate": item.get("rate", 0),
426
+ "warehouse": item.get("warehouse"),
427
+ "uom": item.get("uom", "Nos"),
428
+ })
429
+
430
+ data = {
431
+ "doctype": "Purchase Receipt",
432
+ "supplier": supplier,
433
+ "company": company,
434
+ "items": receipt_items
435
+ }
436
+ if posting_date:
437
+ data["posting_date"] = posting_date
438
+
439
+ return self.create_resource("Purchase Receipt", data)
440
+
441
+ @keyword("Create Purchase Order")
442
+ def create_purchase_order(self, supplier: str, items: list, company: str, schedule_date=None):
443
+ """
444
+ Tạo Purchase Order (Đơn hàng mua).
445
+
446
+ API: /api/resource/Purchase Order
447
+
448
+ Args:
449
+ supplier: Tên nhà cung cấp
450
+ items: Danh sách items [{"item_code": "ITEM-001", "qty": 100, "rate": 1000}]
451
+ company: Tên công ty
452
+ schedule_date: Ngày cần hàng
453
+ """
454
+ self.ensure_supplier_exist(supplier)
455
+
456
+ po_items = []
457
+ for item in items:
458
+ po_items.append({
459
+ "item_code": item["item_code"],
460
+ "qty": item["qty"],
461
+ "rate": item.get("rate", 0),
462
+ "schedule_date": item.get("schedule_date", schedule_date),
463
+ "warehouse": item.get("warehouse"),
464
+ "uom": item.get("uom", "Nos"),
465
+ })
466
+
467
+ data = {
468
+ "doctype": "Purchase Order",
469
+ "supplier": supplier,
470
+ "company": company,
471
+ "items": po_items,
472
+ "schedule_date": schedule_date
473
+ }
474
+
475
+ return self.create_resource("Purchase Order", data)
476
+
477
+ # ===========================================================
478
+ # Accounting APIs - Kế toán
479
+ # ===========================================================
480
+
481
+ @keyword("Create Purchase Invoice")
482
+ def create_purchase_invoice(self, supplier: str, items: list, company: str, posting_date=None):
483
+ """
484
+ Tạo Purchase Invoice (Hóa đơn mua hàng).
485
+
486
+ API: /api/resource/Purchase Invoice
487
+
488
+ Args:
489
+ supplier: Tên nhà cung cấp
490
+ items: Danh sách items [{"item_code": "ITEM-001", "qty": 100, "rate": 1000}]
491
+ company: Tên công ty
492
+ posting_date: Ngày lập hóa đơn
493
+ """
494
+ self.ensure_supplier_exist(supplier)
495
+
496
+ invoice_items = []
497
+ for item in items:
498
+ invoice_items.append({
499
+ "item_code": item["item_code"],
500
+ "qty": item["qty"],
501
+ "rate": item.get("rate", 0),
502
+ "uom": item.get("uom", "Nos"),
503
+ })
504
+
505
+ data = {
506
+ "doctype": "Purchase Invoice",
507
+ "supplier": supplier,
508
+ "company": company,
509
+ "items": invoice_items
510
+ }
511
+ if posting_date:
512
+ data["posting_date"] = posting_date
513
+
514
+ return self.create_resource("Purchase Invoice", data)
515
+
516
+ @keyword("Create Payment Entry")
517
+ def create_payment_entry(self, payment_type: str, party_type: str, party: str,
518
+ paid_amount: float, company: str, posting_date=None):
519
+ """
520
+ Tạo Payment Entry (Phiếu thanh toán).
521
+
522
+ API: /api/resource/Payment Entry
523
+
524
+ Args:
525
+ payment_type: Loại thanh toán (Receive, Pay, Internal Transfer)
526
+ party_type: Loại đối tác (Customer, Supplier, Employee)
527
+ party: Tên đối tác
528
+ paid_amount: Số tiền thanh toán
529
+ company: Tên công ty
530
+ posting_date: Ngày thanh toán
531
+
532
+ Example:
533
+ | Create Payment Entry | Receive | Customer | Customer A | 10000000 | My Company |
534
+ | Create Payment Entry | Pay | Supplier | Supplier B | 5000000 | My Company |
535
+ """
536
+ data = {
537
+ "doctype": "Payment Entry",
538
+ "payment_type": payment_type,
539
+ "party_type": party_type,
540
+ "party": party,
541
+ "paid_amount": paid_amount,
542
+ "received_amount": paid_amount if payment_type == "Receive" else 0,
543
+ "company": company,
544
+ }
545
+ if posting_date:
546
+ data["posting_date"] = posting_date
547
+
548
+ return self.create_resource("Payment Entry", data)
549
+
107
550
  # ===========================================================
108
- # Step 1: Đọc file Excel & tạo Material Request
551
+ # Procurement - Quy trình mua hàng (đã sẵn)
109
552
  # ===========================================================
110
553
 
111
554
  @keyword("Load Excel Request")
@@ -178,7 +621,7 @@ class ERPNextLibrary:
178
621
  rfq_data = {
179
622
  "doctype": "Request for Quotation",
180
623
  "company": company,
181
- "transaction_date": "2025-10-28",
624
+ "transaction_date": mr_doc["schedule_date"],
182
625
  "suppliers": [{"supplier": s} for s in suppliers],
183
626
  "items": items,
184
627
  "message_for_supplier": "Xin vui lòng gửi báo giá."
@@ -199,7 +642,7 @@ class ERPNextLibrary:
199
642
  "doctype": "Supplier Quotation",
200
643
  "supplier": supplier_name,
201
644
  "company": company,
202
- "transaction_date": "2025-10-28",
645
+ "transaction_date": "2026-01-28",
203
646
  "items": [],
204
647
  }
205
648
 
@@ -235,7 +678,7 @@ class ERPNextLibrary:
235
678
  "item_code": d["item_code"],
236
679
  "qty": d["qty"],
237
680
  "rate": d["rate"],
238
- "schedule_date": "2025-11-05",
681
+ "schedule_date": "2026-02-28",
239
682
  "warehouse": f"Stores - EDU",
240
683
  "uom": d.get("uom", "Nos"),
241
684
  "conversion_factor": 1.0
@@ -245,7 +688,7 @@ class ERPNextLibrary:
245
688
  "doctype": "Purchase Order",
246
689
  "supplier": supplier,
247
690
  "company": company,
248
- "schedule_date": "2025-11-05",
691
+ "schedule_date": "2026-02-28",
249
692
  "items": items
250
693
  }
251
694
  return self.create_resource("Purchase Order", po_data)
RPA/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .ERPNext import ERPNextLibrary
2
+
3
+ __version__ = "1.0.0"
4
+ __author__ = "Mahara"
5
+ __all__ = ["ERPNextLibrary"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rpa-erpnext
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Robot Framework library for automating ERPNext using REST API
5
5
  Author-email: Your Name <youremail@example.com>
6
6
  License: MIT
@@ -12,4 +12,16 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: requests>=2.28.0
14
14
  Requires-Dist: robotframework>=6.0
15
+ Requires-Dist: pandas>=2.0.0
16
+ Requires-Dist: openpyxl>=3.1.0
15
17
  Dynamic: license-file
18
+
19
+ # RPA.ERPNext
20
+
21
+ Library for automating [ERPNext](https://erpnext.com/) using REST API, built for [Robot Framework](https://robotframework.org/).
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install rpa-erpnext
27
+ ```
@@ -0,0 +1,7 @@
1
+ RPA/ERPNext.py,sha256=OGunPSMxX9jdsfqi0wpwfcncSBQuekHsSOa_SZc-vhg,24914
2
+ RPA/__init__.py,sha256=doHeY54VPWzBDnkSMGvGGSYfkpxt4dFXAV8YOi-Ebig,110
3
+ rpa_erpnext-1.0.1.dist-info/licenses/LICENSE,sha256=sexHbU6pNlqsmC_TQZJLdZ59qC_Bym7mQ0HrNeHHQ3Y,1063
4
+ rpa_erpnext-1.0.1.dist-info/METADATA,sha256=xRXJi0s8br9c0FbCtksyHZESnkpNofLz4iyen5HE8NY,800
5
+ rpa_erpnext-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
6
+ rpa_erpnext-1.0.1.dist-info/top_level.txt,sha256=s4yaEXbcdPUcIyAxSvrLKKIMD62cqoNPsOGmdx94U5k,4
7
+ rpa_erpnext-1.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
RPA/init.py DELETED
@@ -1,6 +0,0 @@
1
- # src/RPA/__init__.py
2
- from .ERPNext import ERPNext
3
-
4
- __version__ = "1.0.0"
5
- __author__ = "Mahara"
6
- __all__ = ["ERPNext"]
@@ -1,7 +0,0 @@
1
- RPA/ERPNext.py,sha256=2xeclyrQiKgPnEgnqWe1ijmuxASsSejoaWKBcgNqkqk,9129
2
- RPA/init.py,sha256=hhB7KVQqlO3Yo3ScIulQMQ-obZbdGBPe0YIgtVyqSt8,118
3
- rpa_erpnext-1.0.0.dist-info/licenses/LICENSE,sha256=sexHbU6pNlqsmC_TQZJLdZ59qC_Bym7mQ0HrNeHHQ3Y,1063
4
- rpa_erpnext-1.0.0.dist-info/METADATA,sha256=HKy1L1P-k7bIixS1dQk7BllIDEABSXDNGDRWaBHCcaM,541
5
- rpa_erpnext-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- rpa_erpnext-1.0.0.dist-info/top_level.txt,sha256=s4yaEXbcdPUcIyAxSvrLKKIMD62cqoNPsOGmdx94U5k,4
7
- rpa_erpnext-1.0.0.dist-info/RECORD,,