Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle timezones for conversion rate timestamps #35

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions oda_wd_client/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# As defined by API docs
WORKDAY_DATE_FORMAT = "%m/%d/%Y"
WORKDAY_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S" # i.e. 2024-02-28T12:34:56Z


def get_id_from_list(id_list: list, id_type: str) -> str | None:
Expand All @@ -25,3 +26,36 @@ def parse_workday_date(val: str | date | None) -> date | None:
if isinstance(val, str):
return datetime.strptime(val, WORKDAY_DATE_FORMAT).date()
return val


def localize_datetime_to_string(dt: datetime) -> str:
"""
Localize and format datetime for Workday

From the WD docs:
When you enter or load currency conversion rates, Workday automatically converts the timestamp to the Pacific
time zone. Workday therefore recommends that you enter the timestamp to adjust for the difference between your
time and Pacific time. Example: When you enter a conversion rate to be effective at 12.00 am Greenwich Mean
Time (GMT) which is 8 hours ahead of Pacific time, adjust your timestamp to 8.00 AM Pacific time of the same
calendar day. The timestamp visible on the Historic Currency Rates report will display as 8.00 AM, however,
Workday converts it to recognize it as 12.00 AM Pacific time.

https://doc.workday.com/admin-guide/en-us/manage-workday/tenant-configuration/globalization/currencies/dan1370796986531.html # noqa

Testing has shown us that not including the timezone in the timestamp will make the timestamp correct on the
Workday side. For instance, the datetime 2024-02-04T00:00:00+01:00 is shown as 2024-02-04T00:00:00,
but effective at 2024-02-03T15:0:00:00. The datetime 2024-02-04T00:00:00, however, will get shown as
2024-02-04T09:00:00 and is effective from 2024-02-04T00:00:00.

Since the internal Workday logic run in Pacific time (-08:00), we have to make sure that the effective timestamp
is midnight, as that is the Pacific time representation. If we were to send in timezone as part of the timestamp,
we would have to add the difference between Pacific time and the timezone of the input timestamp.

Just sending the datetime, at midnight, without timezone appears to yield the correct result.

https://youtu.be/-5wpm-gesOY

:param dt: Timezone-aware datetime object
:return: String representation for Workday payload
"""
return dt.strftime(WORKDAY_DATETIME_FORMAT)
18 changes: 14 additions & 4 deletions oda_wd_client/service/financial_management/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from oda_wd_client.service.financial_management.utils import (
get_business_process_parameters,
make_conversion_rate_reference_object,
pydantic_accounting_journal_to_workday,
pydantic_conversion_rate_to_workday,
workday_company_to_pydantic,
Expand Down Expand Up @@ -53,10 +54,19 @@ def get_currency_rate_types(
)

def put_currency_rate(self, rate: ConversionRate) -> sudsobject.Object:
data_object = pydantic_conversion_rate_to_workday(rate, client=self)
return self._request(
"Put_Currency_Conversion_Rate", Currency_Conversion_Rate_Data=data_object
)
request_kwargs = {
"Currency_Conversion_Rate_Data": pydantic_conversion_rate_to_workday(
rate, client=self
),
}

# If we're updated an existing rate, we need to reference that in the request
if rate.workday_id:
request_kwargs[
"Currency_Conversion_Rate_Reference"
] = make_conversion_rate_reference_object(self, rate.workday_id)

return self._request("Put_Currency_Conversion_Rate", **request_kwargs)

def get_cost_centers(
self, return_suds_object=False
Expand Down
26 changes: 19 additions & 7 deletions oda_wd_client/service/financial_management/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from suds import sudsobject

from oda_wd_client.base.api import WorkdayClient
from oda_wd_client.base.utils import get_id_from_list
from oda_wd_client.base.utils import get_id_from_list, localize_datetime_to_string
from oda_wd_client.service.financial_management.types import (
AccountingJournalData,
Company,
Expand All @@ -23,11 +23,7 @@ def workday_conversion_rate_to_pydantic(data: dict) -> ConversionRate:
workday_id = get_id_from_list(
data["Currency_Conversion_Rate_Reference"]["ID"], "WID"
)
assert len(data["Currency_Conversion_Rate_Data"]) == 1, (
"Code is written expecting that we only have one currency "
"rate data per object, but that is not the case here"
)
sub_data = data["Currency_Conversion_Rate_Data"][0]
sub_data = data["Currency_Conversion_Rate_Data"]
from_ref = get_id_from_list(
sub_data["From_Currency_Reference"]["ID"], "Currency_ID"
)
Expand Down Expand Up @@ -163,6 +159,20 @@ def workday_project_to_pydantic(data: dict) -> ProjectWorktag:
)


def make_conversion_rate_reference_object(
client: WorkdayClient, rate_id: str
) -> sudsobject.Object:
"""
Creating a reference object used to update a specific conversion rate
"""
ref_obj = client.factory("ns0:Currency_Conversion_RateObjectType")
ref = client.factory("ns0:Currency_Conversion_RateObjectIDType")
ref._type = "WID"
ref.value = rate_id
ref_obj.ID.append(ref)
return ref_obj


def pydantic_conversion_rate_to_workday(
rate: ConversionRate, client: WorkdayClient
) -> sudsobject.Object:
Expand All @@ -175,7 +185,9 @@ def pydantic_conversion_rate_to_workday(
rate_type_id = client.factory("ns0:Currency_Rate_TypeObjectIDType")

# Populate objects
rate_data.Effective_Timestamp = rate.effective_timestamp
rate_data.Effective_Timestamp = localize_datetime_to_string(
rate.effective_timestamp
)
rate_data.Currency_Rate = rate.rate
from_currency_id.value = rate.from_currency_iso
target_currency_id.value = rate.to_currency_iso
Expand Down
Loading
Loading