ms-salesforce-api 2.24.0.dev5__tar.gz → 3.0.0__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.
Files changed (86) hide show
  1. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/PKG-INFO +2 -2
  2. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/account/dto/AccountDTO.py +3 -2
  3. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/__init__.py +1 -2
  4. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/dto/OpportunityDTO.py +17 -7
  5. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/export_data/Bigquery.py +2 -2
  6. ms_salesforce_api-3.0.0/ms_salesforce_api/salesforce/api/project/__init__.py +101 -0
  7. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/__tests__/test_Project.py +142 -144
  8. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/dto/OpportunityDTO.py +3 -1
  9. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/dto/ProjectLineItemDTO.py +15 -1
  10. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/pyproject.toml +1 -1
  11. ms_salesforce_api-2.24.0.dev5/ms_salesforce_api/salesforce/api/project/__init__.py +0 -109
  12. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/LICENSE +0 -0
  13. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/README.md +0 -0
  14. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/__init__.py +0 -0
  15. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/Auth.py +0 -0
  16. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/JWTGenerator.py +0 -0
  17. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/SalesforceQueryExecutor.py +0 -0
  18. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/__init__.py +0 -0
  19. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/__tests__/__init__.py +0 -0
  20. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/__tests__/test_Auth.py +0 -0
  21. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/__tests__/test_JWTGenerator.py +0 -0
  22. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/__tests__/test_SalesforceRequester.py +0 -0
  23. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/__init__.py +0 -0
  24. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/account/__init__.py +0 -0
  25. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/account/constants.py +0 -0
  26. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/account/dto/__init__.py +0 -0
  27. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/account/export_data/Bigquery.py +0 -0
  28. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/account/export_data/__init__.py +0 -0
  29. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/contact/__init__.py +0 -0
  30. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/contact/constants.py +0 -0
  31. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/contact/dto/ContactDTO.py +0 -0
  32. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/contact/dto/__init__.py +0 -0
  33. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/contact/export_data/Bigquery.py +0 -0
  34. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/contact/export_data/__init__.py +0 -0
  35. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/lead/__init__.py +0 -0
  36. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/lead/constants.py +0 -0
  37. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/lead/dto/LeadDTO.py +0 -0
  38. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/lead/dto/__init__.py +0 -0
  39. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/lead/export_data/Bigquery.py +0 -0
  40. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/lead/export_data/__init__.py +0 -0
  41. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/constants.py +0 -0
  42. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/dto/__init__.py +0 -0
  43. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/export_data/CloudSQL.py +0 -0
  44. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/export_data/__init__.py +0 -0
  45. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/export_data/__tests__/__init__.py +0 -0
  46. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/export_data/__tests__/test_CloudSQL.py +0 -0
  47. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity/helpers.py +0 -0
  48. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_contact_role/__init__.py +0 -0
  49. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_contact_role/constants.py +0 -0
  50. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_contact_role/dto/OpportunityContactDTO.py +0 -0
  51. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_contact_role/dto/__init__.py +0 -0
  52. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_contact_role/export_data/Bigquery.py +0 -0
  53. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_contact_role/export_data/__init__.py +0 -0
  54. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_history/__init__.py +0 -0
  55. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_history/constants.py +0 -0
  56. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_history/dto/OpportunityDTO.py +0 -0
  57. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_history/dto/__init__.py +0 -0
  58. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_history/export_data/Bigquery.py +0 -0
  59. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/opportunity_history/export_data/__init__.py +0 -0
  60. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/__init__.py +0 -0
  61. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/__tests__/__init__.py +0 -0
  62. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/__tests__/test_Product.py +0 -0
  63. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/constants.py +0 -0
  64. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/dto/ProductDTO.py +0 -0
  65. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/dto/__init__.py +0 -0
  66. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/export_data/Bigquery.py +0 -0
  67. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/product/export_data/__init__.py +0 -0
  68. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/__init__.py +0 -0
  69. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/__tests__/__init__.py +0 -0
  70. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/__tests__/test_ProfitCenter.py +0 -0
  71. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/constants.py +0 -0
  72. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/dto/ProfitCenterDTO.py +0 -0
  73. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/dto/__init__.py +0 -0
  74. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/export_data/Bigquery.py +0 -0
  75. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/profit_center/export_data/__init__.py +0 -0
  76. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/__tests__/__init__.py +0 -0
  77. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/constants.py +0 -0
  78. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/dto/BillingLineDTO.py +0 -0
  79. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/dto/__init__.py +0 -0
  80. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/export_data/Bigquery.py +0 -0
  81. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/export_data/CloudSQL.py +0 -0
  82. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/export_data/__init__.py +0 -0
  83. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/export_data/__tests__/__init__.py +0 -0
  84. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/api/project/export_data/__tests__/test_CloudSQL.py +0 -0
  85. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/helpers/__init__.py +0 -0
  86. {ms_salesforce_api-2.24.0.dev5 → ms_salesforce_api-3.0.0}/ms_salesforce_api/salesforce/helpers/string.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: ms-salesforce-api
3
- Version: 2.24.0.dev5
3
+ Version: 3.0.0
4
4
  Summary: Python library used to extract data from Salesforce API and migrate it to Bigquery and Postgres.
5
5
  Author: Making Science
6
6
  Requires-Python: >=3.8.1,<4.0.0
@@ -327,9 +327,10 @@ class AccountDTO(object):
327
327
  owner_email=normalize_value(_get_owner_email()),
328
328
  type=normalize_value(record["Type"]),
329
329
  industry=normalize_value(record["Industry"]),
330
-
331
330
  risk_assessment=normalize_value(record["Risk_Assessment__c"]),
332
- risk_assessment_date=normalize_value(record["Risk_Assessment_Date__c"]),
331
+ risk_assessment_date=normalize_value(
332
+ record["Risk_Assessment_Date__c"]
333
+ ),
333
334
  )
334
335
 
335
336
  def to_dict(self):
@@ -37,8 +37,7 @@ class Opportunity(SalesforceQueryExecutor):
37
37
  return []
38
38
 
39
39
  opportunities = [
40
- OpportunityDTO.from_salesforce_record(record)
41
- for record in data
40
+ OpportunityDTO.from_salesforce_record(record) for record in data
42
41
  ]
43
42
  opportunities_list = list(opportunities)
44
43
 
@@ -204,7 +204,7 @@ class OpportunityDTO(object):
204
204
  created_by,
205
205
  last_modified_by,
206
206
  finance_contact,
207
- convertAmount,
207
+ converted_amount_eur,
208
208
  project_code,
209
209
  google_drive_link,
210
210
  project_status,
@@ -286,7 +286,7 @@ class OpportunityDTO(object):
286
286
  self.created_by = created_by
287
287
  self.last_modified_by = last_modified_by
288
288
  self.finance_contact = finance_contact
289
- self.convertAmount = convertAmount
289
+ self.converted_amount_eur = converted_amount_eur
290
290
  self.project_code = project_code
291
291
  self.google_drive_link = google_drive_link
292
292
  self.project_status = project_status
@@ -380,6 +380,16 @@ class OpportunityDTO(object):
380
380
  except (KeyError, TypeError):
381
381
  return ""
382
382
 
383
+ def _get_convert_amount():
384
+ try:
385
+ value = record["convertAmount"]
386
+ clean_value = value.replace("EUR", "")
387
+ clean_value = clean_value.replace(",", "")
388
+
389
+ return float(clean_value)
390
+ except (KeyError, TypeError, ValueError):
391
+ return None
392
+
383
393
  opportunity_line_items = record.get("OpportunityLineItems", {})
384
394
  if opportunity_line_items and isinstance(opportunity_line_items, dict):
385
395
  line_items_records = record.get("OpportunityLineItems", {}).get(
@@ -478,7 +488,9 @@ class OpportunityDTO(object):
478
488
  record.get("Servicios_Asociados__c", "")
479
489
  ),
480
490
  stage_name=normalize_value(record.get("StageName", "")),
481
- start_date=normalize_value(record.get("DT_ProjectStartDate__c", "")),
491
+ start_date=normalize_value(
492
+ record.get("DT_ProjectStartDate__c", "")
493
+ ),
482
494
  tier_short=normalize_value(record.get("Tier_Short__c", "")),
483
495
  total_opportunity_quantity=normalize_value(
484
496
  record.get("TotalOpportunityQuantity", "")
@@ -507,9 +519,8 @@ class OpportunityDTO(object):
507
519
  created_by=_get_created_by(),
508
520
  last_modified_by=_get_last_modified_by(),
509
521
  finance_contact=record["MAIL_FinanceContact__c"],
510
- convertAmount=record["convertAmount"],
522
+ converted_amount_eur=_get_convert_amount(),
511
523
  project_code=record["FRM_ProjectCode__c"],
512
-
513
524
  google_drive_link=record["FRM_GoogleDriveLink__c"],
514
525
  project_status=record["PCK_ProjectStatus__c"],
515
526
  pck_division=record["PCK_Division__c"],
@@ -593,7 +604,7 @@ class OpportunityDTO(object):
593
604
  "created_by": self.created_by,
594
605
  "last_modified_by": self.last_modified_by,
595
606
  "finance_contact": self.finance_contact,
596
- "convertAmount": self.convertAmount,
607
+ "converted_amount_eur": self.converted_amount_eur,
597
608
  "project_code": self.project_code,
598
609
  "google_drive_link": self.google_drive_link,
599
610
  "project_status": self.project_status,
@@ -601,5 +612,4 @@ class OpportunityDTO(object):
601
612
  "out_of_report": self.out_of_report,
602
613
  "billing_info": self.billing_info,
603
614
  "parent_opportunity": self.parent_opportunity,
604
-
605
615
  }
@@ -97,12 +97,12 @@ class BigQueryExporter:
97
97
  "created_by": "STRING",
98
98
  "last_modified_by": "STRING",
99
99
  "finance_contact": "STRING",
100
- "convertAmount": "STRING",
100
+ "converted_amount_eur": "FLOAT",
101
101
  "project_code": "STRING",
102
102
  "google_drive_link": "STRING",
103
103
  "project_status": "STRING",
104
104
  "pck_division": "STRING",
105
- "out_of_report": "STRING",
105
+ "out_of_report": "BOOLEAN",
106
106
  "billing_info": "STRING",
107
107
  "parent_opportunity": "STRING",
108
108
  },
@@ -0,0 +1,101 @@
1
+ import logging
2
+ from concurrent.futures import ThreadPoolExecutor
3
+
4
+ from ms_salesforce_api.salesforce.api.project.constants import (
5
+ DEFAULT_PROJECT_BILLING_LINE_QUERY,
6
+ DEFAULT_PROJECT_OPPORTUNITY_QUERY,
7
+ )
8
+ from ms_salesforce_api.salesforce.api.project.dto.BillingLineDTO import (
9
+ BillingLineDTO,
10
+ )
11
+ from ms_salesforce_api.salesforce.api.project.dto.OpportunityDTO import (
12
+ OpportunityDTO,
13
+ )
14
+ from ms_salesforce_api.salesforce.SalesforceQueryExecutor import (
15
+ SalesforceQueryExecutor,
16
+ )
17
+
18
+ logging.basicConfig(
19
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
20
+ )
21
+
22
+ MAX_PROJECT_IDS_PER_QUERY = 200
23
+
24
+
25
+ class Project(SalesforceQueryExecutor):
26
+ def fetch_billing_lines(self, project_ids):
27
+ billing_lines = []
28
+ query = DEFAULT_PROJECT_BILLING_LINE_QUERY.format(
29
+ project_id="','".join(project_ids)
30
+ )
31
+
32
+ data = self.fetch_data(query)
33
+ if data is not None:
34
+ billing_lines.extend(data)
35
+
36
+ return billing_lines
37
+
38
+ def get_batches(self, lst, n):
39
+ """Yield successive n-sized chunks from lst."""
40
+ for i in range(0, len(lst), n):
41
+ yield lst[i : i + n] # noqa: E203
42
+
43
+ def get_all(
44
+ self,
45
+ last_executed_at: str = None,
46
+ query: str = DEFAULT_PROJECT_OPPORTUNITY_QUERY,
47
+ format: str = "json",
48
+ ):
49
+ if last_executed_at:
50
+ query = query + f"WHERE CreatedDate > {last_executed_at}"
51
+
52
+ data = self.fetch_data(query)
53
+ if data is None:
54
+ logging.error(
55
+ "[ERROR - SalesforceAPI]: No projects data return from Salesforce API" # noqa: E501
56
+ )
57
+ return []
58
+
59
+ opportunities = {
60
+ record["Id"]: OpportunityDTO.from_salesforce_record(record)
61
+ for record in data
62
+ }
63
+
64
+ project_ids = list(opportunities.keys())
65
+
66
+ with ThreadPoolExecutor(max_workers=10) as executor:
67
+ for project_id_batch in self.get_batches(
68
+ project_ids,
69
+ MAX_PROJECT_IDS_PER_QUERY,
70
+ ):
71
+ batch_results = list(
72
+ executor.map(
73
+ self.fetch_billing_lines,
74
+ [project_id_batch],
75
+ )
76
+ )
77
+ for billing_line_data in batch_results:
78
+ for record in billing_line_data:
79
+ project_id = record["Project_Line_Item__r"][
80
+ "Project__c"
81
+ ]
82
+ opportunity = opportunities.get(project_id)
83
+
84
+ billing_line_dto = (
85
+ BillingLineDTO.from_salesforce_record(
86
+ record,
87
+ opportunity.projectcode,
88
+ )
89
+ )
90
+
91
+ if opportunity is not None:
92
+ if not opportunity.billing_lines:
93
+ opportunity.billing_lines = []
94
+ opportunity.billing_lines.append(billing_line_dto)
95
+ opportunities_list = list(opportunities.values())
96
+ if format == "json":
97
+ opportunities_list = [
98
+ opportunity.to_dict() for opportunity in opportunities_list
99
+ ]
100
+
101
+ return opportunities_list
@@ -382,154 +382,152 @@ class TestProject(unittest.TestCase):
382
382
  self.assertEqual(len(opportunities), 1)
383
383
 
384
384
  opportunity = opportunities[0]
385
-
385
+ opportunity_result = {
386
+ "account_assigment_group": "03",
387
+ "account_billing_address": "211 Main Street, Webster, Maine, 01570, United "
388
+ "States",
389
+ "account_billing_city": "Webster",
390
+ "account_billing_country": "US",
391
+ "account_billing_postal_code": "01570",
392
+ "account_billing_state_code": "ME",
393
+ "account_billing_street": "211 Main Street",
394
+ "account_business_function": "BP03",
395
+ "account_business_name": "THE COMMERCE INSURANCE COMPANY",
396
+ "account_cif": "042495247",
397
+ "account_company_invoicing": "",
398
+ "account_created_date": "2020-03-18 15:32:15",
399
+ "account_currency_code": "EUR",
400
+ "account_customer_groupId": "1",
401
+ "account_customer_subgroupId": "5",
402
+ "account_fax": None,
403
+ "account_invoicing_email": "client1@test.com",
404
+ "account_mail_invoicing": "client1@test.com",
405
+ "account_name": "MAPFRE USA",
406
+ "account_office": "Making Science LLC",
407
+ "account_payment_terms": "T060",
408
+ "account_pec_email": None,
409
+ "account_phone": None,
410
+ "account_sap_id": "10000319",
411
+ "account_tax_category": None,
412
+ "account_tax_classification": "0",
413
+ "account_tax_id_type": "US01",
414
+ "account_tier": "T1",
415
+ "account_website": None,
416
+ "amount": 0.0,
417
+ "autorenewal": False,
418
+ "billing_lines": [
419
+ {
420
+ "billing_amount": 6708.0,
421
+ "billing_date": "2022-08-31",
422
+ "billing_period_ending_date": "2022-08-31",
423
+ "billing_period_starting_date": "2022-08-01",
424
+ "billing_plan_amount": "6708",
425
+ "billing_plan_billing_date": "2022-08-31",
426
+ "billing_plan_item": "1",
427
+ "billing_plan_service_end_date": "2022-08-31",
428
+ "billing_plan_service_start_date": "2022-08-01",
429
+ "created_date": "2022-08-09 14:56:23",
430
+ "currency": "USD",
431
+ "hourly_price": 65.0,
432
+ "id": "a0sAX000000I8lgYAC",
433
+ "last_modified_date": "2022-10-16 18:56:34",
434
+ "name": "BL-000175313",
435
+ "project_code": "",
436
+ "project_id": "a00AX000002DVi1YAG",
437
+ "revenue_dedication": 103.2,
438
+ }
439
+ ],
440
+ "comments": "",
441
+ "controller_email": "employee4@test.com",
442
+ "controller_sub_email": "employee3@test.com",
443
+ "cost_center": "",
444
+ "created_at": "2022-08-01 08:53:12",
445
+ "currency": "EUR",
446
+ "end_date": "",
447
+ "group_bqid": "1",
448
+ "group_end_date": "2100-12-12",
449
+ "group_groupid": "a0cAX000000TSWUYA4",
450
+ "group_name": "MAPFRE",
451
+ "group_owner_email": "employee1@test.com",
452
+ "group_pck_type": "Key Account",
453
+ "group_start_date": "2023-06-01",
454
+ "group_supervisor_email": "employee2@test.com",
455
+ "international_deparments": False,
456
+ "invoicing_country_code": "US",
457
+ "jira_task_url": "<a "
458
+ "href=https://makingscience.atlassian.net/browse/ESMSBD0001-7168 "
459
+ "target=_blank>View Jira Task</a>",
460
+ "last_updated_at": "2023-09-01 10:00:54",
461
+ "maintance_project": False,
462
+ "ms_project_id": False,
463
+ "operation_coordinator_email": "employee5@test.com",
464
+ "operation_coordinator_name": "",
465
+ "operation_coordinator_sub": "",
466
+ "operation_coordinator_sub_email": "employee5@test.com",
467
+ "opportunity": "",
468
+ "opportunity_extension": "",
469
+ "opportunity_name": "New Site Mapfre AAA",
470
+ "opportunity_project_code": "",
471
+ "periodicity": "",
472
+ "profit_center": "200018",
473
+ "profitcenter": "",
474
+ "project_account": "",
475
+ "project_id": "a00AX000002DVi1YAG",
476
+ "project_line_items": [
477
+ {
478
+ "country": None,
479
+ "created_date": "2022-08-02 15:44:34",
480
+ "effort": "516",
481
+ "ending_date": "2022-12-31",
482
+ "id": "a0VAX000000EE0b2AG",
483
+ "last_modified_date": "2023-06-20 22:33:36",
484
+ "ms_pli_name": "USA_UX/UI Design_USMSEX05508",
485
+ "opportunity_project_code": None,
486
+ "product_name": "UXUI Project",
487
+ "project_id": "a00AX000002DVi1YAG",
488
+ "quantity": 516.0,
489
+ "starting_date": "2022-08-01",
490
+ "total_price": 33540.0,
491
+ "unit_price": 65.0,
492
+ },
493
+ {
494
+ "country": None,
495
+ "created_date": "2022-08-09 14:54:59",
496
+ "effort": "331",
497
+ "ending_date": "2022-12-31",
498
+ "id": "a0VAX000000ELU52AO",
499
+ "last_modified_date": "2023-06-20 22:33:36",
500
+ "ms_pli_name": "ES_UX/UI Design_USMSEX05508",
501
+ "opportunity_project_code": None,
502
+ "product_name": "UXUI Project",
503
+ "project_id": "a00AX000002DVi1YAG",
504
+ "quantity": 331.0,
505
+ "starting_date": "2022-08-01",
506
+ "total_price": 21515.0,
507
+ "unit_price": 65.0,
508
+ },
509
+ ],
510
+ "project_name": "MapfreAAA",
511
+ "project_start_date": "2022-08-01",
512
+ "project_tier": "Unkown",
513
+ "projectcode": "",
514
+ "quote": "",
515
+ "revenue_details": "",
516
+ "status": "",
517
+ "subgroup_bqid": "5",
518
+ "subgroup_end_date": "2100-12-12",
519
+ "subgroup_groupid": "a0cAX000000TSWUYA4",
520
+ "subgroup_name": "MAPFRE USA",
521
+ "subgroup_owner_email": "employee1@test.com",
522
+ "subgroup_start_date": "2023-06-01",
523
+ "subgroup_subgroupid": "a19AX0000004simYAA",
524
+ }
386
525
  self.assertIsInstance(opportunity, dict)
387
526
 
388
527
  self.assertDictEqual(
389
528
  opportunity,
390
- {
391
- "account_assigment_group": "03",
392
- "account_billing_address": "211 Main Street, Webster, Maine, 01570, United "
393
- "States",
394
- "account_billing_city": "Webster",
395
- "account_billing_country": "US",
396
- "account_billing_postal_code": "01570",
397
- "account_billing_state_code": "ME",
398
- "account_billing_street": "211 Main Street",
399
- "account_business_function": "BP03",
400
- "account_business_name": "THE COMMERCE INSURANCE COMPANY",
401
- "account_cif": "042495247",
402
- "account_company_invoicing": "",
403
- "account_created_date": "2020-03-18 15:32:15",
404
- "account_currency_code": "EUR",
405
- "account_customer_groupId": "1",
406
- "account_customer_subgroupId": "5",
407
- "account_fax": None,
408
- "account_invoicing_email": "client1@test.com",
409
- "account_mail_invoicing": "client1@test.com",
410
- "account_name": "MAPFRE USA",
411
- "account_office": "Making Science LLC",
412
- "account_payment_terms": "T060",
413
- "account_pec_email": None,
414
- "account_phone": None,
415
- "account_sap_id": "10000319",
416
- "account_tax_category": None,
417
- "account_tax_classification": "0",
418
- "account_tax_id_type": "US01",
419
- "account_tier": "T1",
420
- "account_website": None,
421
- "amount": 0.0,
422
- "autorenewal": False,
423
- "billing_lines": [
424
- {
425
- "billing_amount": 6708.0,
426
- "billing_date": "2022-08-31",
427
- "billing_period_ending_date": "2022-08-31",
428
- "billing_period_starting_date": "2022-08-01",
429
- "billing_plan_amount": "6708",
430
- "billing_plan_billing_date": "2022-08-31",
431
- "billing_plan_item": "1",
432
- "billing_plan_service_end_date": "2022-08-31",
433
- "billing_plan_service_start_date": "2022-08-01",
434
- "created_date": "2022-08-09 14:56:23",
435
- "currency": "USD",
436
- "hourly_price": 65.0,
437
- "id": "a0sAX000000I8lgYAC",
438
- "last_modified_date": "2022-10-16 18:56:34",
439
- "name": "BL-000175313",
440
- "project_code": "USMSEX05508",
441
- "project_id": "a00AX000002DVi1YAG",
442
- "revenue_dedication": 103.2,
443
- }
444
- ],
445
- "comments": "",
446
- "controller_email": "employee4@test.com",
447
- "controller_sub_email": "employee3@test.com",
448
- "cost_center": "",
449
- "created_at": "2022-08-01 08:53:12",
450
- "currency": "EUR",
451
- "end_date": "",
452
- "group_bqid": "1",
453
- "group_end_date": "2100-12-12",
454
- "group_groupid": "a0cAX000000TSWUYA4",
455
- "group_name": "MAPFRE",
456
- "group_owner_email": "employee1@test.com",
457
- "group_pck_type": "Key Account",
458
- "group_start_date": "2023-06-01",
459
- "group_supervisor_email": "employee2@test.com",
460
- "international_deparments": False,
461
- "invoicing_country_code": "US",
462
- "jira_task_url": "<a "
463
- "href=https://makingscience.atlassian.net/browse/ESMSBD0001-7168 "
464
- "target=_blank>View Jira Task</a>",
465
- "last_updated_at": "2023-09-01 10:00:54",
466
- "maintance_project": False,
467
- "ms_project_id": False,
468
- "operation_coordinator_email": "employee5@test.com",
469
- "operation_coordinator_name": "",
470
- "operation_coordinator_sub": "",
471
- "operation_coordinator_sub_email": "employee5@test.com",
472
- "opportunity": "",
473
- "opportunity_extension": "",
474
- "opportunity_name": "New Site Mapfre AAA",
475
- "periodicity": "",
476
- "profit_center": "200018",
477
- "profitcenter": "",
478
- "project_account": "",
479
- "project_code": "USMSEX05508",
480
- "project_id": "a00AX000002DVi1YAG",
481
- "project_line_items": [
482
- {
483
- "country": None,
484
- "created_date": "2022-08-02 15:44:34",
485
- "effort": "516",
486
- "ending_date": "2022-12-31",
487
- "id": "a0VAX000000EE0b2AG",
488
- "last_modified_date": "2023-06-20 22:33:36",
489
- "ms_pli_name": "USA_UX/UI Design_USMSEX05508",
490
- "ms_project_code": None,
491
- "product_name": "UXUI Project",
492
- "project_id": "a00AX000002DVi1YAG",
493
- "quantity": 516.0,
494
- "starting_date": "2022-08-01",
495
- "total_price": 33540.0,
496
- "unit_price": 65.0,
497
- },
498
- {
499
- "country": None,
500
- "created_date": "2022-08-09 14:54:59",
501
- "effort": "331",
502
- "ending_date": "2022-12-31",
503
- "id": "a0VAX000000ELU52AO",
504
- "last_modified_date": "2023-06-20 22:33:36",
505
- "ms_pli_name": "ES_UX/UI Design_USMSEX05508",
506
- "ms_project_code": None,
507
- "product_name": "UXUI Project",
508
- "project_id": "a00AX000002DVi1YAG",
509
- "quantity": 331.0,
510
- "starting_date": "2022-08-01",
511
- "total_price": 21515.0,
512
- "unit_price": 65.0,
513
- },
514
- ],
515
- "project_name": "MapfreAAA",
516
- "project_start_date": "2022-08-01",
517
- "project_tier": "Unkown",
518
- "projectcode": "",
519
- "projectid": "",
520
- "quote": "",
521
- "revenue_details": "",
522
- "status": "",
523
- "subgroup_bqid": "5",
524
- "subgroup_end_date": "2100-12-12",
525
- "subgroup_groupid": "a0cAX000000TSWUYA4",
526
- "subgroup_name": "MAPFRE USA",
527
- "subgroup_owner_email": "employee1@test.com",
528
- "subgroup_start_date": "2023-06-01",
529
- "subgroup_subgroupid": "a19AX0000004simYAA",
530
- },
529
+ opportunity_result,
531
530
  )
532
-
533
531
  billing_line = opportunity["billing_lines"][0]
534
532
  self.assertDictEqual(
535
533
  billing_line,
@@ -549,7 +547,7 @@ class TestProject(unittest.TestCase):
549
547
  "id": "a0sAX000000I8lgYAC",
550
548
  "last_modified_date": "2022-10-16 18:56:34",
551
549
  "name": "BL-000175313",
552
- "project_code": "USMSEX05508",
550
+ "project_code": "",
553
551
  "project_id": "a00AX000002DVi1YAG",
554
552
  "revenue_dedication": 103.2,
555
553
  },
@@ -546,7 +546,9 @@ class OpportunityDTO(object):
546
546
 
547
547
  def _get_opportunity_project_code():
548
548
  try:
549
- return normalize_value(record["Opportunity__r"]["FRM_ProjectCode__c"])
549
+ return normalize_value(
550
+ record["Opportunity__r"]["FRM_ProjectCode__c"]
551
+ )
550
552
  except (TypeError, KeyError):
551
553
  return ""
552
554
 
@@ -47,11 +47,25 @@ class ProjectLineItemDTO:
47
47
  except Exception:
48
48
  return ""
49
49
 
50
+ def _get_opportunity_project_code():
51
+ try:
52
+ project = record.get("Project__r")
53
+ opportunity = project.get("Opportunity__r") if project else {}
54
+ opportunity_project_code = opportunity.get(
55
+ "FRM_ProjectCode__c"
56
+ )
57
+
58
+ return opportunity_project_code
59
+
60
+ except AttributeError:
61
+ return None
62
+
50
63
  product_name = (
51
64
  record.get("ProductNew__r", {}).get("Name")
52
65
  if record.get("ProductNew__r")
53
66
  else None
54
67
  )
68
+
55
69
  return cls(
56
70
  id=record.get("Id"),
57
71
  created_date=_parse_created_date(record.get("CreatedDate")),
@@ -68,7 +82,7 @@ class ProjectLineItemDTO:
68
82
  ms_pli_name=record.get("MS_PLI_Name__c"),
69
83
  country=record.get("Country__c"),
70
84
  project_id=project_id,
71
- opportunity_project_code=record.get("Project__r", {}).get("Opportunity__r", {}).get("FRM_ProjectCode__c"),
85
+ opportunity_project_code=_get_opportunity_project_code(),
72
86
  )
73
87
 
74
88
  def to_dict(self):
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ms-salesforce-api"
3
- version = "2.24.0.dev5"
3
+ version = "3.0.0"
4
4
  description = "Python library used to extract data from Salesforce API and migrate it to Bigquery and Postgres."
5
5
  authors = ["Making Science"]
6
6
  readme = "README.md"
@@ -1,109 +0,0 @@
1
- import logging
2
- from concurrent.futures import ThreadPoolExecutor
3
-
4
- from ms_salesforce_api.salesforce.api.project.constants import (
5
- DEFAULT_PROJECT_BILLING_LINE_QUERY,
6
- DEFAULT_PROJECT_OPPORTUNITY_QUERY,
7
- )
8
- from ms_salesforce_api.salesforce.api.project.dto.BillingLineDTO import (
9
- BillingLineDTO,
10
- )
11
- from ms_salesforce_api.salesforce.api.project.dto.OpportunityDTO import (
12
- OpportunityDTO,
13
- )
14
- from ms_salesforce_api.salesforce.SalesforceQueryExecutor import (
15
- SalesforceQueryExecutor,
16
- )
17
-
18
- logging.basicConfig(
19
- level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
20
- )
21
-
22
- MAX_PROJECT_IDS_PER_QUERY = 200
23
-
24
-
25
- class Project(SalesforceQueryExecutor):
26
- def fetch_billing_lines(self, project_ids):
27
- billing_lines = []
28
- query = DEFAULT_PROJECT_BILLING_LINE_QUERY.format(
29
- project_id="','".join(project_ids)
30
- )
31
-
32
- data = self.fetch_data(query)
33
- if data is not None:
34
- billing_lines.extend(data)
35
-
36
- return billing_lines
37
-
38
- def get_batches(self, lst, n):
39
- """Yield successive n-sized chunks from lst."""
40
- for i in range(0, len(lst), n):
41
- yield lst[i : i + n] # noqa: E203
42
-
43
- def get_all(
44
- self,
45
- last_executed_at: str = None,
46
- query: str = DEFAULT_PROJECT_OPPORTUNITY_QUERY,
47
- format: str = "json",
48
- ):
49
- try:
50
- if last_executed_at:
51
- query = query + f"WHERE CreatedDate > {last_executed_at}"
52
-
53
- data = self.fetch_data(query)
54
- if data is None:
55
- logging.error(
56
- "[ERROR - SalesforceAPI]: No projects data return from Salesforce API" # noqa: E501
57
- )
58
- return []
59
-
60
- opportunities = {
61
- record["Id"]: OpportunityDTO.from_salesforce_record(record)
62
- for record in data
63
- }
64
-
65
- project_ids = list(opportunities.keys())
66
-
67
- with ThreadPoolExecutor(max_workers=10) as executor:
68
- for project_id_batch in self.get_batches(
69
- project_ids,
70
- MAX_PROJECT_IDS_PER_QUERY,
71
- ):
72
- batch_results = list(
73
- executor.map(
74
- self.fetch_billing_lines,
75
- [project_id_batch],
76
- )
77
- )
78
- for billing_line_data in batch_results:
79
- for record in billing_line_data:
80
- project_id = record["Project_Line_Item__r"][
81
- "Project__c"
82
- ]
83
- opportunity = opportunities.get(project_id)
84
-
85
- billing_line_dto = (
86
- BillingLineDTO.from_salesforce_record(
87
- record,
88
- opportunity.projectcode,
89
- )
90
- )
91
-
92
- if opportunity is not None:
93
- if not opportunity.billing_lines:
94
- opportunity.billing_lines = []
95
- opportunity.billing_lines.append(
96
- billing_line_dto
97
- )
98
- opportunities_list = list(opportunities.values())
99
- if format == "json":
100
- opportunities_list = [
101
- opportunity.to_dict() for opportunity in opportunities_list
102
- ]
103
-
104
- return opportunities_list
105
- except Exception as e:
106
- logging.error(
107
- f"[ERROR - get_all]: Failed to get opportunities: {e}"
108
- )
109
- return []