rpa-erpnext 1.0.1__py3-none-any.whl → 1.0.5__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 +133 -31
- {rpa_erpnext-1.0.1.dist-info → rpa_erpnext-1.0.5.dist-info}/METADATA +1 -1
- rpa_erpnext-1.0.5.dist-info/RECORD +7 -0
- rpa_erpnext-1.0.1.dist-info/RECORD +0 -7
- {rpa_erpnext-1.0.1.dist-info → rpa_erpnext-1.0.5.dist-info}/WHEEL +0 -0
- {rpa_erpnext-1.0.1.dist-info → rpa_erpnext-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {rpa_erpnext-1.0.1.dist-info → rpa_erpnext-1.0.5.dist-info}/top_level.txt +0 -0
RPA/ERPNext.py
CHANGED
|
@@ -41,37 +41,72 @@ class ERPNextLibrary:
|
|
|
41
41
|
"""
|
|
42
42
|
Thiết lập kết nối với ERPNext từ file credential JSON.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
Args:
|
|
45
|
+
token_file_path: Đường dẫn đến file token JSON
|
|
46
|
+
|
|
47
|
+
Format 1 (Standard ERPNext):
|
|
46
48
|
{
|
|
47
49
|
"base_url": "...",
|
|
48
50
|
"api_key": "...",
|
|
49
51
|
"api_secret": "..."
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
2
|
|
54
|
+
Format 2 (Google-like / Moodle structure):
|
|
53
55
|
{
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
+
"access_token": "http://erpnext.example.com", # Mapping base_url here
|
|
57
|
+
"refresh_token": "key:secret" # Mapping credentials here
|
|
56
58
|
}
|
|
57
59
|
"""
|
|
60
|
+
if not token_file_path:
|
|
61
|
+
raise Exception("Token file path is required")
|
|
62
|
+
|
|
63
|
+
# Fix for potential argument naming issue from Robot Framework (connection=...)
|
|
64
|
+
if token_file_path.startswith("connection="):
|
|
65
|
+
token_file_path = token_file_path.split("=", 1)[1]
|
|
66
|
+
|
|
58
67
|
with open(token_file_path, 'r', encoding='utf-8') as f:
|
|
59
68
|
data = json.load(f)
|
|
60
69
|
|
|
61
|
-
base_url =
|
|
62
|
-
api_key =
|
|
63
|
-
api_secret =
|
|
64
|
-
access_token =
|
|
70
|
+
base_url = None
|
|
71
|
+
api_key = None
|
|
72
|
+
api_secret = None
|
|
73
|
+
access_token = None
|
|
74
|
+
|
|
75
|
+
# Logic to parse different formats
|
|
76
|
+
if 'access_token' in data and 'refresh_token' in data:
|
|
77
|
+
# Format 2: Google-like structure
|
|
78
|
+
# access_token field holds the URL
|
|
79
|
+
# refresh_token field holds the credentials (key:secret)
|
|
80
|
+
base_url = data.get('access_token')
|
|
81
|
+
creds = data.get('refresh_token')
|
|
82
|
+
|
|
83
|
+
if ":" in creds:
|
|
84
|
+
parts = creds.split(":")
|
|
85
|
+
api_key = parts[0]
|
|
86
|
+
api_secret = parts[1]
|
|
87
|
+
else:
|
|
88
|
+
# If no colon, assume it's a bearer token? Or just raw key?
|
|
89
|
+
# For consistency with user request "y chang moodle", we assume this structure.
|
|
90
|
+
# But ERPNext needs key:secret. If it's a single string, maybe it's bearer?
|
|
91
|
+
# Let's support bearer if it's not key:secret
|
|
92
|
+
access_token = creds
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
# Format 1: Standard
|
|
96
|
+
base_url = data.get("base_url")
|
|
97
|
+
api_key = data.get("api_key")
|
|
98
|
+
api_secret = data.get("api_secret")
|
|
99
|
+
access_token = data.get("access_token") # Real OAuth2 access token
|
|
65
100
|
|
|
66
101
|
if not base_url:
|
|
67
|
-
raise Exception("
|
|
102
|
+
raise Exception("Could not find base_url in token file")
|
|
68
103
|
|
|
69
104
|
if access_token:
|
|
70
105
|
return self.connect(base_url, access_token=access_token)
|
|
71
106
|
elif api_key and api_secret:
|
|
72
107
|
return self.connect(base_url, api_key=api_key, api_secret=api_secret)
|
|
73
108
|
else:
|
|
74
|
-
raise Exception("Token file must contain credentials (api_key+api_secret OR access_token)")
|
|
109
|
+
raise Exception("Token file must contain credentials (api_key+api_secret OR access_token/refresh_token)")
|
|
75
110
|
|
|
76
111
|
# ===========================================================
|
|
77
112
|
# Utility Methods
|
|
@@ -279,7 +314,7 @@ class ERPNextLibrary:
|
|
|
279
314
|
# ===========================================================
|
|
280
315
|
|
|
281
316
|
@keyword("Create Sales Order")
|
|
282
|
-
def create_sales_order(self, customer: str, items
|
|
317
|
+
def create_sales_order(self, customer: str, items, delivery_date: str, company: str):
|
|
283
318
|
"""
|
|
284
319
|
Tạo Sales Order (Đơn hàng bán).
|
|
285
320
|
|
|
@@ -297,6 +332,9 @@ class ERPNextLibrary:
|
|
|
297
332
|
"""
|
|
298
333
|
self.ensure_customer_exist(customer)
|
|
299
334
|
|
|
335
|
+
if isinstance(items, str):
|
|
336
|
+
items = json.loads(items)
|
|
337
|
+
|
|
300
338
|
order_items = []
|
|
301
339
|
for item in items:
|
|
302
340
|
order_items.append({
|
|
@@ -317,7 +355,7 @@ class ERPNextLibrary:
|
|
|
317
355
|
return self.create_resource("Sales Order", data)
|
|
318
356
|
|
|
319
357
|
@keyword("Create Sales Invoice")
|
|
320
|
-
def create_sales_invoice(self, customer: str, items
|
|
358
|
+
def create_sales_invoice(self, customer: str, items, company: str, posting_date=None):
|
|
321
359
|
"""
|
|
322
360
|
Tạo Sales Invoice (Hóa đơn bán hàng).
|
|
323
361
|
|
|
@@ -331,6 +369,9 @@ class ERPNextLibrary:
|
|
|
331
369
|
"""
|
|
332
370
|
self.ensure_customer_exist(customer)
|
|
333
371
|
|
|
372
|
+
if isinstance(items, str):
|
|
373
|
+
items = json.loads(items)
|
|
374
|
+
|
|
334
375
|
invoice_items = []
|
|
335
376
|
for item in items:
|
|
336
377
|
invoice_items.append({
|
|
@@ -356,7 +397,7 @@ class ERPNextLibrary:
|
|
|
356
397
|
# ===========================================================
|
|
357
398
|
|
|
358
399
|
@keyword("Create Stock Entry")
|
|
359
|
-
def create_stock_entry(self, purpose: str, items
|
|
400
|
+
def create_stock_entry(self, purpose: str, items, company: str, posting_date=None):
|
|
360
401
|
"""
|
|
361
402
|
Tạo Stock Entry (Phiếu nhập/xuất kho).
|
|
362
403
|
|
|
@@ -379,6 +420,10 @@ class ERPNextLibrary:
|
|
|
379
420
|
| ${items}= | Create List | {"item_code": "ITEM-001", "qty": 100, "t_warehouse": "Stores - EDU"} |
|
|
380
421
|
| Create Stock Entry | Material Receipt | ${items} | My Company |
|
|
381
422
|
"""
|
|
423
|
+
|
|
424
|
+
if isinstance(items, str):
|
|
425
|
+
items = json.loads(items)
|
|
426
|
+
|
|
382
427
|
stock_items = []
|
|
383
428
|
for item in items:
|
|
384
429
|
stock_items.append({
|
|
@@ -403,7 +448,7 @@ class ERPNextLibrary:
|
|
|
403
448
|
return self.create_resource("Stock Entry", data)
|
|
404
449
|
|
|
405
450
|
@keyword("Create Purchase Receipt")
|
|
406
|
-
def create_purchase_receipt(self, supplier: str, items
|
|
451
|
+
def create_purchase_receipt(self, supplier: str, items, company: str, posting_date=None):
|
|
407
452
|
"""
|
|
408
453
|
Tạo Purchase Receipt (Phiếu nhận hàng mua).
|
|
409
454
|
|
|
@@ -417,6 +462,9 @@ class ERPNextLibrary:
|
|
|
417
462
|
"""
|
|
418
463
|
self.ensure_supplier_exist(supplier)
|
|
419
464
|
|
|
465
|
+
if isinstance(items, str):
|
|
466
|
+
items = json.loads(items)
|
|
467
|
+
|
|
420
468
|
receipt_items = []
|
|
421
469
|
for item in items:
|
|
422
470
|
receipt_items.append({
|
|
@@ -438,8 +486,10 @@ class ERPNextLibrary:
|
|
|
438
486
|
|
|
439
487
|
return self.create_resource("Purchase Receipt", data)
|
|
440
488
|
|
|
489
|
+
|
|
490
|
+
|
|
441
491
|
@keyword("Create Purchase Order")
|
|
442
|
-
def create_purchase_order(self, supplier: str, items
|
|
492
|
+
def create_purchase_order(self, supplier: str, items, company: str, schedule_date=None):
|
|
443
493
|
"""
|
|
444
494
|
Tạo Purchase Order (Đơn hàng mua).
|
|
445
495
|
|
|
@@ -453,6 +503,9 @@ class ERPNextLibrary:
|
|
|
453
503
|
"""
|
|
454
504
|
self.ensure_supplier_exist(supplier)
|
|
455
505
|
|
|
506
|
+
if isinstance(items, str):
|
|
507
|
+
items = json.loads(items)
|
|
508
|
+
|
|
456
509
|
po_items = []
|
|
457
510
|
for item in items:
|
|
458
511
|
po_items.append({
|
|
@@ -479,7 +532,7 @@ class ERPNextLibrary:
|
|
|
479
532
|
# ===========================================================
|
|
480
533
|
|
|
481
534
|
@keyword("Create Purchase Invoice")
|
|
482
|
-
def create_purchase_invoice(self, supplier: str, items
|
|
535
|
+
def create_purchase_invoice(self, supplier: str, items, company: str, posting_date=None):
|
|
483
536
|
"""
|
|
484
537
|
Tạo Purchase Invoice (Hóa đơn mua hàng).
|
|
485
538
|
|
|
@@ -493,6 +546,9 @@ class ERPNextLibrary:
|
|
|
493
546
|
"""
|
|
494
547
|
self.ensure_supplier_exist(supplier)
|
|
495
548
|
|
|
549
|
+
if isinstance(items, str):
|
|
550
|
+
items = json.loads(items)
|
|
551
|
+
|
|
496
552
|
invoice_items = []
|
|
497
553
|
for item in items:
|
|
498
554
|
invoice_items.append({
|
|
@@ -599,8 +655,15 @@ class ERPNextLibrary:
|
|
|
599
655
|
# ===========================================================
|
|
600
656
|
|
|
601
657
|
@keyword("Send RFQ From Material Request")
|
|
602
|
-
def send_rfq_from_mr(self, mr_name: str, suppliers
|
|
658
|
+
def send_rfq_from_mr(self, mr_name: str, suppliers):
|
|
603
659
|
"""Tạo RFQ dựa trên Material Request."""
|
|
660
|
+
if isinstance(suppliers, str):
|
|
661
|
+
try:
|
|
662
|
+
suppliers = json.loads(suppliers)
|
|
663
|
+
except ValueError:
|
|
664
|
+
# Nếu không phải JSON list, coi như là 1 supplier duy nhất
|
|
665
|
+
suppliers = [suppliers]
|
|
666
|
+
|
|
604
667
|
mr_doc = self.get_doc("Material Request", mr_name)
|
|
605
668
|
company = mr_doc["company"]
|
|
606
669
|
self.ensure_warehouse_exist(f"Stores - EDU", company)
|
|
@@ -633,30 +696,69 @@ class ERPNextLibrary:
|
|
|
633
696
|
# ===========================================================
|
|
634
697
|
|
|
635
698
|
@keyword("Receive Supplier Quotation")
|
|
636
|
-
def receive_supplier_quotation(self, rfq_name: str, supplier_name: str, quotation_data
|
|
699
|
+
def receive_supplier_quotation(self, rfq_name: str, supplier_name: str, quotation_data):
|
|
700
|
+
"""
|
|
701
|
+
Nhận báo giá từ Supplier.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
rfq_name: Mã Request for Quotation
|
|
705
|
+
supplier_name: Tên nhà cung cấp
|
|
706
|
+
quotation_data:
|
|
707
|
+
- Đường dẫn file Excel (.xlsx) chứa các cột: item_code, qty, rate
|
|
708
|
+
- Hoặc List/JSON string chứa thông tin items
|
|
709
|
+
"""
|
|
637
710
|
rfq_doc = self.get_doc("Request for Quotation", rfq_name)
|
|
638
711
|
company = rfq_doc["company"]
|
|
639
712
|
self.ensure_supplier_exist(supplier_name)
|
|
713
|
+
|
|
714
|
+
items_list = []
|
|
715
|
+
|
|
716
|
+
# Case 1: Input is Excel file path
|
|
717
|
+
if isinstance(quotation_data, str) and (quotation_data.endswith('.xlsx') or quotation_data.endswith('.xls')):
|
|
718
|
+
df = pd.read_excel(quotation_data)
|
|
719
|
+
# Clean column names (optional: lowercase)
|
|
720
|
+
df.columns = [c.lower().strip() for c in df.columns]
|
|
721
|
+
|
|
722
|
+
for _, row in df.iterrows():
|
|
723
|
+
# Flexible column mapping
|
|
724
|
+
item_code = row.get("item_code") or row.get("itemcode")
|
|
725
|
+
qty = row.get("qty") or row.get("quantity")
|
|
726
|
+
rate = row.get("rate") or row.get("price") or 0
|
|
727
|
+
|
|
728
|
+
items_list.append({
|
|
729
|
+
"item_code": item_code,
|
|
730
|
+
"qty": float(qty),
|
|
731
|
+
"rate": float(rate),
|
|
732
|
+
"uom": "Nos",
|
|
733
|
+
"stock_uom": "Nos",
|
|
734
|
+
"conversion_factor": 1.0,
|
|
735
|
+
"warehouse": f"Stores - EDU"
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
# Case 2: Input is List or JSON String
|
|
739
|
+
else:
|
|
740
|
+
if isinstance(quotation_data, str):
|
|
741
|
+
quotation_data = json.loads(quotation_data)
|
|
742
|
+
|
|
743
|
+
for q in quotation_data:
|
|
744
|
+
items_list.append({
|
|
745
|
+
"item_code": q["item_code"],
|
|
746
|
+
"qty": q["qty"],
|
|
747
|
+
"rate": q["rate"],
|
|
748
|
+
"uom": "Nos",
|
|
749
|
+
"stock_uom": "Nos",
|
|
750
|
+
"conversion_factor": 1.0,
|
|
751
|
+
"warehouse": f"Stores - EDU"
|
|
752
|
+
})
|
|
640
753
|
|
|
641
754
|
sq_data = {
|
|
642
755
|
"doctype": "Supplier Quotation",
|
|
643
756
|
"supplier": supplier_name,
|
|
644
757
|
"company": company,
|
|
645
758
|
"transaction_date": "2026-01-28",
|
|
646
|
-
"items":
|
|
759
|
+
"items": items_list,
|
|
647
760
|
}
|
|
648
761
|
|
|
649
|
-
for q in quotation_data:
|
|
650
|
-
sq_data["items"].append({
|
|
651
|
-
"item_code": q["item_code"],
|
|
652
|
-
"qty": q["qty"],
|
|
653
|
-
"rate": q["rate"],
|
|
654
|
-
"uom": "Nos",
|
|
655
|
-
"stock_uom": "Nos",
|
|
656
|
-
"conversion_factor": 1.0,
|
|
657
|
-
"warehouse": f"Stores - EDU"
|
|
658
|
-
})
|
|
659
|
-
|
|
660
762
|
return self.create_resource("Supplier Quotation", sq_data)
|
|
661
763
|
|
|
662
764
|
# ===========================================================
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
RPA/ERPNext.py,sha256=BzT0W0ren0bizXq4rjVR3DqoY8Tro56-HgQfnd3ytiQ,28815
|
|
2
|
+
RPA/__init__.py,sha256=doHeY54VPWzBDnkSMGvGGSYfkpxt4dFXAV8YOi-Ebig,110
|
|
3
|
+
rpa_erpnext-1.0.5.dist-info/licenses/LICENSE,sha256=sexHbU6pNlqsmC_TQZJLdZ59qC_Bym7mQ0HrNeHHQ3Y,1063
|
|
4
|
+
rpa_erpnext-1.0.5.dist-info/METADATA,sha256=aXYwFzm_EC1MXWKOOiIk3ecFU2TT0FJTAtQTfEDCLXM,800
|
|
5
|
+
rpa_erpnext-1.0.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
rpa_erpnext-1.0.5.dist-info/top_level.txt,sha256=s4yaEXbcdPUcIyAxSvrLKKIMD62cqoNPsOGmdx94U5k,4
|
|
7
|
+
rpa_erpnext-1.0.5.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|