rpa-erpnext 1.0.1__tar.gz → 1.0.4__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rpa-erpnext
3
- Version: 1.0.1
3
+ Version: 1.0.4
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
@@ -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
@@ -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({
@@ -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({
@@ -417,6 +458,9 @@ class ERPNextLibrary:
417
458
  """
418
459
  self.ensure_supplier_exist(supplier)
419
460
 
461
+ if isinstance(items, str):
462
+ items = json.loads(items)
463
+
420
464
  receipt_items = []
421
465
  for item in items:
422
466
  receipt_items.append({
@@ -438,6 +482,8 @@ class ERPNextLibrary:
438
482
 
439
483
  return self.create_resource("Purchase Receipt", data)
440
484
 
485
+
486
+
441
487
  @keyword("Create Purchase Order")
442
488
  def create_purchase_order(self, supplier: str, items: list, company: str, schedule_date=None):
443
489
  """
@@ -453,6 +499,9 @@ class ERPNextLibrary:
453
499
  """
454
500
  self.ensure_supplier_exist(supplier)
455
501
 
502
+ if isinstance(items, str):
503
+ items = json.loads(items)
504
+
456
505
  po_items = []
457
506
  for item in items:
458
507
  po_items.append({
@@ -601,6 +650,9 @@ class ERPNextLibrary:
601
650
  @keyword("Send RFQ From Material Request")
602
651
  def send_rfq_from_mr(self, mr_name: str, suppliers: list):
603
652
  """Tạo RFQ dựa trên Material Request."""
653
+ if isinstance(suppliers, str):
654
+ suppliers = json.loads(suppliers)
655
+
604
656
  mr_doc = self.get_doc("Material Request", mr_name)
605
657
  company = mr_doc["company"]
606
658
  self.ensure_warehouse_exist(f"Stores - EDU", company)
@@ -633,30 +685,69 @@ class ERPNextLibrary:
633
685
  # ===========================================================
634
686
 
635
687
  @keyword("Receive Supplier Quotation")
636
- def receive_supplier_quotation(self, rfq_name: str, supplier_name: str, quotation_data: list):
688
+ def receive_supplier_quotation(self, rfq_name: str, supplier_name: str, quotation_data):
689
+ """
690
+ Nhận báo giá từ Supplier.
691
+
692
+ Args:
693
+ rfq_name: Mã Request for Quotation
694
+ supplier_name: Tên nhà cung cấp
695
+ quotation_data:
696
+ - Đường dẫn file Excel (.xlsx) chứa các cột: item_code, qty, rate
697
+ - Hoặc List/JSON string chứa thông tin items
698
+ """
637
699
  rfq_doc = self.get_doc("Request for Quotation", rfq_name)
638
700
  company = rfq_doc["company"]
639
701
  self.ensure_supplier_exist(supplier_name)
702
+
703
+ items_list = []
704
+
705
+ # Case 1: Input is Excel file path
706
+ if isinstance(quotation_data, str) and (quotation_data.endswith('.xlsx') or quotation_data.endswith('.xls')):
707
+ df = pd.read_excel(quotation_data)
708
+ # Clean column names (optional: lowercase)
709
+ df.columns = [c.lower().strip() for c in df.columns]
710
+
711
+ for _, row in df.iterrows():
712
+ # Flexible column mapping
713
+ item_code = row.get("item_code") or row.get("itemcode")
714
+ qty = row.get("qty") or row.get("quantity")
715
+ rate = row.get("rate") or row.get("price") or 0
716
+
717
+ items_list.append({
718
+ "item_code": item_code,
719
+ "qty": float(qty),
720
+ "rate": float(rate),
721
+ "uom": "Nos",
722
+ "stock_uom": "Nos",
723
+ "conversion_factor": 1.0,
724
+ "warehouse": f"Stores - EDU"
725
+ })
726
+
727
+ # Case 2: Input is List or JSON String
728
+ else:
729
+ if isinstance(quotation_data, str):
730
+ quotation_data = json.loads(quotation_data)
731
+
732
+ for q in quotation_data:
733
+ items_list.append({
734
+ "item_code": q["item_code"],
735
+ "qty": q["qty"],
736
+ "rate": q["rate"],
737
+ "uom": "Nos",
738
+ "stock_uom": "Nos",
739
+ "conversion_factor": 1.0,
740
+ "warehouse": f"Stores - EDU"
741
+ })
640
742
 
641
743
  sq_data = {
642
744
  "doctype": "Supplier Quotation",
643
745
  "supplier": supplier_name,
644
746
  "company": company,
645
747
  "transaction_date": "2026-01-28",
646
- "items": [],
748
+ "items": items_list,
647
749
  }
648
750
 
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
751
  return self.create_resource("Supplier Quotation", sq_data)
661
752
 
662
753
  # ===========================================================
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rpa-erpnext"
7
- version = "1.0.1"
7
+ version = "1.0.4"
8
8
  description = "Robot Framework library for automating ERPNext using REST API"
9
9
  readme = "readme.md"
10
10
  requires-python = ">=3.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rpa-erpnext
3
- Version: 1.0.1
3
+ Version: 1.0.4
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
File without changes
File without changes
File without changes
File without changes