Unless you’re bored. Then please share.
I have started
I have a script writing the GET requests for all BO methods, but it’s a lot of methods and most of them will likely never be used. Also, the POST requests will be very tricky and I’ll probably only add them as needed, because we’ll need to use Epicor’s methods to add data instead of pushing directly to the tables. This is because I’m afraid of data corruption and I’ll probably need to study Epicor processes a lot before pushing any data.
For example, this is what I’ve generated (one service from many):
### Customers START ###
def get_Customers(self):
payload = ""
headers = {"Authorization": "Bearer " + str(self.token)}
self.conn.request("GET", self.epicor_env + self.api_v + "/Erp.BO.CustomerSvc/Customers", payload, headers)
res = self.conn.getresponse()
data = res.read()
return data.decode("utf-8")
def get_Customer_by_id(self, company, id):
payload = ""
headers = {"Authorization": "Bearer " + str(self.token)}
self.conn.request("GET", self.epicor_env + self.api_v + "/Erp.BO.CustomerSvc/Customers(" + company + ", " + id + ")", payload, headers)
res = self.conn.getresponse()
data = res.read()
return data.decode("utf-8")
### Customers END ###
I’m only doing the main functions because I don’t see a reason to be using the other ones. They won’t be hard to add though
If you use Functions, you’ll only need two scripts: BAQs and Functions.
We’re doing this next year, and I’m probably younger than you, by a (grey) whisker
Also for some reason, pulling data via BAQSvc is WAY faster than (for example) using the PartSvc directly. Like 1000x.
I imagine the reason is that the GetList and GetRows methods of Business Objects like PartSvc is building a tableset (or dataset) with a bunch of columns we don’t need. And with BAQs and Functions, you can shape your data closer to your needs saving work at the client.
Probably, yes. Amazing difference though.
Also you just query data from database in BAQ. And GetRow/Get List may have additional logic for processing data after database retrieval - filling additional fields., making some validations etc.
Pulling data via BAQSvc is WAY faster than (for example) using the PartSvc directly. Like 1000x.
This makes me want to use BaqSvc only. I’ve managed to format the params and filters properly, so should work out well. I might post after the weekend
Remember, you’ll get those same benefits with the dynamic query service as well, just at a lower level.
So either way you go, you’re good.
Technically BaqSvc is DynamicQuery.ExecuteById, nothing fancy.
For anyone that’s interested, I’ll share my final Python code…
Keep in mind the filters and parameters are different within the URL.
Here’s a sample URL:
https://domain.name/Environment/api/v1/BaqSvc/RandomBAQName/?last_date=2023-10-04&name=ag&%24filter=OrderHed_OrderNum%20ge%2016000%20and%20OrderHed_OrderDate%20ge%20datetime'2023-10-10'
urllib.parse.urlencode takes care of the params, but the filters needed a different solution. Therefore, I have to replace characters that cannot go into the URL with strings that do.
import http.client
import urllib.parse
class EpicorConnection:
def __init__(self, username, password, url, epicor_env, api_v):
self.username = username
self.password = password
self.url = url
self.epicor_env = epicor_env
self.api_v = api_v
self.conn = http.client.HTTPSConnection(self.url)
self.token = self.__set_token()
self.filter_convert_list = {
# operators
'==': 'eq',
'<>': 'ne',
'!=': 'ne',
'>=': 'ge',
'<=': 'le',
'=': 'eq',
'>': 'gt',
'<': 'lt',
# special characters
' ': '%20'
}
def __set_token(self):
payload = ""
headers = {
"username": self.username,
"password": self.password
}
self.conn.request("POST", "/" + self.epicor_env + "/TokenResource.svc/", payload, headers)
res = self.conn.getresponse()
data = res.read()
# Return only the token, not the tags
return data.decode("utf-8").split("<AccessToken>")[1].split("</AccessToken>")[0]
# BAQ
def execute_baq(self, baq_name, params, filters):
headers = {"Authorization": "Bearer " + str(self.token)}
params_str = self.get_epicor_params(params)
filter_str = self.get_epicor_filters(filters)
complete_url = self.epicor_env + self.api_v +"/BaqSvc/"+ baq_name +"/"+ ("?" if len(params_str)>0 or len(filter_str)>0 else "") + params_str + ("&" if len(params_str)>0 and len(filter_str)>0 else "") + filter_str
self.conn.request("GET", complete_url, None, headers)
res = self.conn.getresponse()
data = res.read()
return data.decode("utf-8") # return complete_url if you want the url
# Messy logic, but it works... so far...
def get_epicor_filters(self, filters_dict):
out = ""
index = 0
for filter in filters_dict:
if index > 0:
out += "%20" + "and" + "%20"
out += filter + filters_dict[filter]
index += 1
for operator in self.filter_convert_list:
while str(operator) in out:
print (str(self.filter_convert_list[operator]))
out = out.replace(str(operator), "%20" + str(self.filter_convert_list[operator]) + "%20")
return (("%24" + "filter=") if len(out) > 0 else "") + out
def get_epicor_params(self, params_dict):
return urllib.parse.urlencode(params_dict)
I would pass something like the following:
connection = EpicorConnection(username, password, url, environment, api_v)
today = date.today()
filters = {
'OrderHed_OrderNum': '>=16000',
'OrderHed_OrderDate': '>=' + "datetime'"+str(today)+"'"
}
params = {
'last_date': '2023-10-04',
'name': 'ag'
}
print(connection.execute_baq("OrderStatusHead", params, filters))