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 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
- Hỗ trợ 2 format:
45
- 1. Token Auth (API Key/Secret):
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. OAuth2 (Access Token):
54
+ Format 2 (Google-like / Moodle structure):
53
55
  {
54
- "base_url": "...",
55
- "access_token": "..."
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 = 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")
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("Token file must contain base_url")
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: list, delivery_date: str, company: str):
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: list, company: str, posting_date=None):
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: list, company: str, posting_date=None):
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: list, company: str, posting_date=None):
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: list, company: str, schedule_date=None):
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: list, company: str, posting_date=None):
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: list):
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: list):
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
  # ===========================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rpa-erpnext
3
- Version: 1.0.1
3
+ Version: 1.0.5
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
@@ -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,,