Skip to content

Commit

Permalink
Merge branch 'release/1.9.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
dotchetter committed Feb 6, 2024
2 parents 8b4b5a3 + 0b9f075 commit 3a878c9
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 45 deletions.
74 changes: 42 additions & 32 deletions jarvis/abilities/finance/ability.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ def before_create(self) -> None:
{"no_expenses_matched": "Det finns inga utgifter "
"sparade med angivna "
"kriterier",
"no_users_matches": "Jag hittade ingen "
"användare som matchade. "
"Om du angav ett namn, "
"kontrollera stavningen. "
"Om du inte angav något, "
"kontrollera att du är "
"registrerad."})
"no_users_matches": "Jag hittade ingen "
"användare som matchade. "
"Om du angav ett namn, "
"kontrollera stavningen. "
"Om du inte angav något, "
"kontrollera att du är "
"registrerad."})

def add_expense(self, message: Message):
"""
Expand All @@ -59,6 +59,7 @@ def add_expense(self, message: Message):
expense_name = message.entities.get("expense_name")
expense_value = message.entities.get("expense_value")
store_for_username = extract_username(message, "store_for_username")
recurring = message.entities.get("recurring")

if not expense_value and expense_name:
return Reply("Du måste ange både namn och "
Expand All @@ -71,8 +72,11 @@ def add_expense(self, message: Message):

expense = Expense.objects.create(price=expense_value,
expense_name=expense_name,
user_reference=user)
stream.put(f"Utgiften sparades för {user.username.capitalize()}. ")
user_reference=user,
recurring_monthly=recurring)
stream.put(f"Utgiften sparades för {user.username.capitalize()}.")
if recurring:
stream.put("Utgiften är markerad som återkommande.")
stream.put(expense)
return stream

Expand All @@ -83,6 +87,7 @@ def get_expenses(self, message: Message):
username_for_query = extract_username(message, "username_for_query")
get_latest = message.entities["show_most_recent_expense"]
last_accounting_entry_date = self._get_last_accounting_entry_date()
recurring_expenses_only = message.entities["recurring_expenses_only"]
stream = ReplyStream()

try:
Expand All @@ -92,25 +97,32 @@ def get_expenses(self, message: Message):
return Reply(self.storage["default_replies"]["no_users_matches"])

if get_latest:
latest_expense = Expense.objects.latest(user=user)
return Reply(latest_expense)
return Reply(Expense.objects.latest(user=user))
if recurring_expenses_only:
return ReplyStream(Expense.objects.recurring(user=user))

expenses = Expense.objects.within_period(
range_start=last_accounting_entry_date,
user=user)
recurring_expenses_only = Expense.objects.recurring(user=user)

if not expenses:
return Reply(
self.storage["default_replies"]["no_expenses_matched"])
if not expenses and not recurring_expenses_only:
return Reply(self.storage["default_replies"]["no_expenses_matched"])

if message.entities.get("sum_expenses"):
expenses_sum = expenses.sum("price")
recurring_sum = recurring_expenses_only.sum("price")
period_str = (f"{last_accounting_entry_date.strftime('%Y-%m-%d')} - "
f"{datetime.now().strftime('%Y-%m-%d')}")
stream.put(f"Nuvarande konteringsperiod: {period_str}")
return Reply(f"Summan för {user.username.capitalize()} "
f"under denna konteringsperiod är hittills: "
f"**{expenses_sum}**:-")
f"**{expenses_sum + recurring_sum}**:-.\n"
f"Varav återkommande utgifter: **{recurring_sum}**:-\n"
f"Varav engångsutgifter: **{expenses_sum}**:-")

expenses = list(expenses.all())
expenses.extend(recurring_expenses_only.all())
return ReplyStream(expenses)

@classmethod
Expand Down Expand Up @@ -296,36 +308,34 @@ def calculate_split_expenses(self, message):
reply_stream = ReplyStream()
accounting_entry = AccountingEntry()
dt_fmt = pyttman.app.settings.DATETIME_FORMAT
reply_stream.put(f"Konteringsunderlag: {datetime.now().strftime(dt_fmt)}")
period = f"{query_range_start.strftime('%Y-%m-%d')} - " \
f"{datetime.now().strftime('%Y-%m-%d')}"
reply_stream.put(f"**Konteringsunderlag**: {datetime.now().strftime(dt_fmt)}")
period = f"**{query_range_start.strftime('%Y-%m-%d')} - " \
f"{datetime.now().strftime('%Y-%m-%d')}**"
reply_stream.put(f"Konteringsperiod: {period}")
msg = f"**Konteringsperiod: {period}**\n"
valid = False
while calculations:
calculation = calculations.pop()
if not calculation.quota_of_total:
continue
username = calculation.user.username.capitalize()
accounting_entry.participants.append(calculation.user)

msg = f"**{username}**:\n"
msg += f"{username} har betalat {calculation.paid_amount:.2f}:- " \
f"denna period vilket utgör {calculation.quota_of_total * 100:.2f}% av " \
f"det totala beloppet {calculator.total_expense_sum:.2f}:-.\n"
msg += f"{username} har en månadslön som motsvarar " \
f"{calculation.income_quotient * 100:.2f}:- av den " \
msg = f"\n:moneybag: **{username}:**\n"
msg += f"**Summa:** {calculation.paid_amount:.2f}:- \n" \
f"**Belastning:** {calculation.quota_of_total * 100:.2f}% av " \
f"totalen {calculator.total_expense_sum:.2f}:-.\n"
if calculation.recurring_expenses:
msg += f"**Varav återkommande utgifter:** {calculation.recurring_expenses:.2f}:- \n"

msg += f"**Månadslön:** {calculation.income_quotient * 100:.2f}% av den " \
f"totala inkomsten av deltagarna.\n"

if calculation.ingoing_compensation:
msg += f"{username} har överbetalat sin del och ska bli kompenserad " \
f"med {calculation.ingoing_compensation:.2f}:- från övriga " \
msg += f"**Ska kompenseras med:** {calculation.ingoing_compensation:.2f}:- från övriga " \
f"deltagare."
elif calculation.outgoing_compensation:
msg += f"{username} har inte betalat sin del och ska kompensera " \
f"övriga deltagare med {calculation.outgoing_compensation:.2f}:- "
msg += f"**Ska kompensera andra med** {calculation.outgoing_compensation:.2f}:-"
else:
msg += f"{username} har betalat exakt sin kvot och ska varken " \
msg += f"**{username} har betalat exakt sin kvot och ska varken " \
f"kompenseras eller kompensera andra."

valid = True
Expand All @@ -334,7 +344,7 @@ def calculate_split_expenses(self, message):
accounting_entry.accounting_result = msg
if not valid:
return Reply("Det finns inga utgifter att kontera för, sedan förra konteringen: "
f"{query_range_start.strftime('%Y-%m-%d %H:%M')}")
f"{query_range_start.strftime('%Y-%m-%d %H:%M')}")
if message.entities["close_current_period"]:
accounting_entry.save()
reply_stream.put("Kontering sparad. Utgifter som läggs till från och med nu "
Expand Down
16 changes: 13 additions & 3 deletions jarvis/abilities/finance/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class SharedFinancesCalculator:
The resulting data describes who has to compensate whom, and
by how much, respectively.
"""

def __init__(self):
self.total_expense_sum: Decimal = Decimal(0)

Expand All @@ -30,6 +31,7 @@ class SharedExpenseCalculation:
outgoing_compensation: Decimal = field(default=None)
ingoing_compensation: Decimal = field(default=None)
quota_of_total: Decimal = field(default=None)
recurring_expenses: Decimal = field(default=None)

@classmethod
def enrolled_usernames(cls):
Expand All @@ -56,6 +58,9 @@ def calculate_split(self,
calculations, processed = [], []
total_sum = Decimal(
Expense.objects.within_period(range_start, range_end).sum("price"))
total_sum += Decimal(
Expense.objects.recurring().sum("price")
)

# Get the total combined income of all participants.
try:
Expand All @@ -70,13 +75,15 @@ def calculate_split(self,
for user in participant_users:
ingoing_compensation = outgoing_compensation = Decimal(0)
calculation = self.SharedExpenseCalculation(user=user)
paid_amount = Expense.objects.within_period(
normal_expenses = Expense.objects.within_period(
user=user,
range_start=range_start,
range_end=range_end
).sum("price")

paid_amount = Decimal(paid_amount)
recurring_expenses = Expense.objects.recurring(
user=user
).sum("price")
paid_amount = Decimal(normal_expenses + recurring_expenses)
income_quotient = user.profile.gross_income / combined_income
expected_paid_amount_based_on_income = total_sum * income_quotient

Expand All @@ -90,8 +97,11 @@ def calculate_split(self,
calculation.expected_paid_amount_based_on_income = expected_paid_amount_based_on_income
calculation.ingoing_compensation = ingoing_compensation
calculation.outgoing_compensation = outgoing_compensation
calculation.recurring_expenses = recurring_expenses
if total_sum > 0:
calculation.quota_of_total = calculation.paid_amount / total_sum
else:
calculation.quota_of_total = Decimal(0)
calculations.append(calculation)
self.total_expense_sum = total_sum
return calculations
Expand Down
6 changes: 6 additions & 0 deletions jarvis/abilities/finance/intents.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class AddExpense(Intent):
expense_value = IntEntityField()
store_for_username = TextEntityField(valid_strings=SharedFinancesCalculator
.enrolled_usernames)
recurring = BoolEntityField(
message_contains=("återkommande", "upprepande",
"upprepad", "repeterande"))

def respond(self, message: Message) -> Union[Reply, ReplyStream]:
return self.ability.add_expense(message)
Expand Down Expand Up @@ -53,6 +56,9 @@ class GetExpenses(Intent):
month = TextEntityField(valid_strings=Month.names_as_list, default=None)
username_for_query = TextEntityField(
valid_strings=SharedFinancesCalculator.enrolled_usernames)
recurring_expenses_only = BoolEntityField(
message_contains=("återkommande", "upprepande",
"upprepad", "repeterande"))

def respond(self, message: Message) -> Union[Reply, ReplyStream]:
"""
Expand Down
14 changes: 10 additions & 4 deletions jarvis/abilities/finance/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ def recurring(self, user: User = None) -> QuerySet:
:param user: User owning the Expense documents
:return: QuerySet[Expense]
"""
return self.filter(user_reference=user,
recurring_monthly=True)
query = self.filter(recurring_monthly=True)
if user is not None:
query = query.filter(user_reference=user)
return query

def within_period(self,
range_start: datetime,
Expand All @@ -44,7 +46,8 @@ def within_period(self,
if range_end is None:
range_end = datetime.now() + timedelta(days=1)
query = self.filter(created__gte=range_start,
created__lte=range_end)
created__lte=range_end,
recurring_monthly=False)
if user is not None:
query = query.filter(user_reference=user)
return query
Expand Down Expand Up @@ -84,7 +87,10 @@ def __str__(self):
account_month = f":calendar: **{account_month} {year}**\n"
created_date = self.created.strftime(self.output_date_format)
created_date = f":clock: **{created_date}**\n"
return name + price + created_date + account_month + sep
recurring = ""
if self.recurring_monthly:
recurring = ":repeat: **Upprepande**\n"
return name + price + created_date + account_month + recurring + sep


class Debt(me.Document):
Expand Down
13 changes: 8 additions & 5 deletions jarvis/abilities/timekeeper/ability.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ def save_workshift_from_string(self, message: Message):
from_timestamp = message.entities["from_timestamp"]
to_timestamp = message.entities["to_timestamp"]
project_name = message.entities["project_name"]
until_now = message.entities["until_now"]

if (project := Project.objects.get_by_name_or_default_project(
project_name)) is None:
return self.complain_no_project_was_chosen()

workshifts_entered_as_datetime = from_datetime and to_datetime
workshifts_entered_as_time = from_timestamp and to_timestamp
workshifts_entered_as_time = from_timestamp and (to_timestamp or until_now)

if not (workshifts_entered_as_datetime or workshifts_entered_as_time):
return Reply("Ange när arbetspasset började och slutade. "
Expand All @@ -73,12 +74,14 @@ def save_workshift_from_string(self, message: Message):
dt_format = app.settings.TIMESTAMP_FORMAT
start_datetime = end_datetime = datetime.now()
timestamp_start = datetime.strptime(from_timestamp, dt_format)
timestamp_end = datetime.strptime(to_timestamp, dt_format)

start_datetime = start_datetime.replace(hour=timestamp_start.hour,
minute=timestamp_start.minute)
end_datetime = end_datetime.replace(hour=timestamp_end.hour,
minute=timestamp_end.minute)
if until_now:
end_datetime = datetime.now()
else:
timestamp_end = datetime.strptime(to_timestamp, dt_format)
end_datetime = end_datetime.replace(hour=timestamp_end.hour,
minute=timestamp_end.minute)

try:
if start_datetime > end_datetime:
Expand Down
1 change: 1 addition & 0 deletions jarvis/abilities/timekeeper/intents.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class CreateWorkShiftFromString(Intent):
from_timestamp = StringEntityField(identifier=TimeStampIdentifier)
to_timestamp = StringEntityField(identifier=TimeStampIdentifier)
project_name = StringEntityField(valid_strings=Project.all_project_names)
until_now = BoolEntityField(message_contains=("nu",))

def respond(self, message: Message) -> Reply | ReplyStream:
return self.ability.save_workshift_from_string(message)
Expand Down
2 changes: 1 addition & 1 deletion jarvis/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
LOG_TO_STDOUT = True

APP_NAME = "jarvis"
APP_VERSION = "1.9.1"
APP_VERSION = "1.9.2"
DATETIME_FORMAT = "%Y-%m-%d-%H:%M"
TIMESTAMP_FORMAT = "%H:%M"

Expand Down

0 comments on commit 3a878c9

Please sign in to comment.