diff --git a/caremyway/api/models.py b/caremyway/api/models.py index dd79e78..42a33d7 100644 --- a/caremyway/api/models.py +++ b/caremyway/api/models.py @@ -45,7 +45,7 @@ class Manage(models.Model): deleted = models.BooleanField(default=False) def __str__(self): - return self.client.user.username + ' manages ' + self.provider.user.username + return self.client.user.first_name + ' manages ' + self.provider.user.first_name class Price(models.Model): uuid = models.UUIDField(default=uuid.uuid4, editable=False) diff --git a/caremyway/api/serializers.py b/caremyway/api/serializers.py index a91bf30..b9a3037 100644 --- a/caremyway/api/serializers.py +++ b/caremyway/api/serializers.py @@ -276,9 +276,36 @@ class TimeSheetSerializer(serializers.Serializer): amount = serializers.DecimalField(max_digits=8, decimal_places=2) work = serializers.CharField() + management = serializers.CharField() client = serializers.CharField() provider = serializers.CharField() payday = serializers.DateField() paystart = serializers.DateField() payend = serializers.DateField() shifts = ShiftSerializer(many=True) + +class RecordSheetSerializer(serializers.Serializer): + class RecordSerializer(serializers.Serializer): + payday = serializers.DateField() + reg_pay = serializers.DecimalField(max_digits=10, decimal_places=2) + vac_pay = serializers.DecimalField(max_digits=10, decimal_places=2) + earnings = serializers.DecimalField(max_digits=10, decimal_places=2) + cpp_ytd = serializers.DecimalField(max_digits=10, decimal_places=2) + cpp = serializers.DecimalField(max_digits=10, decimal_places=2) + ei_ytd = serializers.DecimalField(max_digits=10, decimal_places=2) + ei = serializers.DecimalField(max_digits=10, decimal_places=2) + taxable_income = serializers.DecimalField(max_digits=10, decimal_places=2) + fed_tax = serializers.DecimalField(max_digits=10, decimal_places=2) + fed_tax_payable = serializers.DecimalField(max_digits=10, decimal_places=2) + prov_tax = serializers.DecimalField(max_digits=10, decimal_places=2) + prov_tax_deduction = serializers.DecimalField(max_digits=10, decimal_places=2) + income_tax = serializers.DecimalField(max_digits=10, decimal_places=2) + total_deductions = serializers.DecimalField(max_digits=10, decimal_places=2) + net_pay = serializers.DecimalField(max_digits=10, decimal_places=2) + + management = serializers.CharField() + client = serializers.CharField() + provider = serializers.CharField() + phone_number = serializers.CharField() + sin = serializers.CharField() + record = RecordSerializer(many=True) diff --git a/caremyway/api/views.py b/caremyway/api/views.py index 42e1822..d756dd6 100644 --- a/caremyway/api/views.py +++ b/caremyway/api/views.py @@ -12,7 +12,7 @@ from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.renderers import StaticHTMLRenderer -from caremyway.api.serializers import UserSerializer, UserInfoSerializer, ClientSerializer, ProviderSerializer, WorkTypeSerializer, EmployeeSerializer, EmployerSerializer, PriceSerializer, CShiftSerializer, PShiftSerializer, TimeSheetSerializer +from caremyway.api.serializers import UserSerializer, UserInfoSerializer, ClientSerializer, ProviderSerializer, WorkTypeSerializer, EmployeeSerializer, EmployerSerializer, PriceSerializer, CShiftSerializer, PShiftSerializer, TimeSheetSerializer, RecordSheetSerializer class UserViewSet(viewsets.ModelViewSet): lookup_field = 'username' @@ -158,9 +158,10 @@ def get_paystart(payday): def get_payend(payday): return payday - timedelta(days=4) -def get_timesheet(user, payday, manage=None): +def gen_timesheets(user, payday, manage=None): paystart = get_paystart(payday) payend = get_payend(payday) + timesheets = [] shifts = Shift.objects.filter(deleted=False) \ .exclude(actual_end__isnull=True) \ @@ -175,9 +176,9 @@ def get_timesheet(user, payday, manage=None): manage_shifts = groupby(shifts, lambda x: x.price.management) - timesheets = [] for management, shifts in manage_shifts: timesheet = {} + timesheet['management'] = management timesheet['client'] = management.client timesheet['provider'] = management.provider timesheet['payday'] = payday @@ -200,6 +201,220 @@ def get_timesheet(user, payday, manage=None): return timesheets +def get_paydays_todate(payday): + # Assumes payday is a valided date obj + year = payday.year + paydays = [] + + if payday.day == 15: + paydays.append(payday) + elif payday.day == calendar.monthrange(payday.year, payday.month)[1]: + paydays.append(payday) + paydays.append(datetime.date(year, payday.month, 15)) + else: + raise serializers.ValidationError("Date is not a valid payday.") + + for month in reversed(range(1, payday.month)): + end_of_month = calendar.monthrange(year, month)[1] + paydays.append(datetime.date(year, month, end_of_month)) + paydays.append(datetime.date(year, month, 15)) + + return list(reversed(paydays)) + +def get_cpp(cpp_ytd, earnings): + D = cpp_ytd + PI = earnings + P = 24 + + C_i = Decimal(2564.10) - D + C_ii = Decimal(0.0495) * (PI - Decimal(3500 / P)) + C = min(C_i, C_ii) + + if C < 0: + C = 0 + + return C + +def get_ei(ei_ytd, earnings): + D1 = ei_ytd + IE = earnings + + EI_i = Decimal(836.19) - D1 + EI_ii = Decimal(0.0163) * IE + EI = min(EI_i, EI_ii) + + if EI < 0: + EI = 0 + + return EI + +def get_taxable_income(earnings): + P = 24 + I = earnings + F = 0 + F2 = 0 + U1 = 0 + HD = 0 + F1 = 0 + + A = (P * (I - F - F2 - U1)) - HD - F1 + + return A + +def get_fed_tax(taxable_income, cpp, ei, cpp_ytd, ei_ytd): + table = [(0, 0.150, 0), + (45916, 0.205, 2525), + (91831, 0.260, 7576), + (142352, 0.290, 11847), + (202800, 0.330, 19959)] + + A = taxable_income + R = Decimal([row for row in table if row[0] < A][-1][1]) + K = [row for row in table if row[0] < A][-1][2] + + TC = 11635 + K1 = Decimal(0.15 * TC) + + P = 24 + if cpp_ytd + cpp >= 2564.10: + PxC = Decimal(2564.10) + else: + C = cpp + PxC = P * C + if ei_ytd + ei >= 836.19: + PxEI = Decimal(836.19) + else: + EI = ei + PxEI = P * EI + K2 = (Decimal(0.15) * PxC) + (Decimal(0.15) * PxEI) + + K3 = 0 + + K4_i = Decimal(0.15) * A + K4_ii = Decimal(0.15 * 1178) + K4 = min(K4_i, K4_ii) + + T3 = (R * A) - K - K1 - K2 - K3 - K4 + + if T3 < 0: + T3 = 0 + + return T3 + +def get_fed_tax_payable(fed_tax): + T3 = fed_tax + LCF = 0 + + T1 = T3 - LCF + + if T1 < 0: + T1 = 0 + + return T1 + +def get_prov_tax(taxable_income, cpp, ei, cpp_ytd, ei_ytd): + table = [(0, 0.10, 0), + (126625, 0.12, 2533), + (151950, 0.13, 4052), + (202600, 0.14, 6078), + (303900, 0.15, 9117)] + + A = taxable_income + V = Decimal([row for row in table if row[0] < A][-1][1]) + KP = [row for row in table if row[0] < A][-1][2] + + TCP = 18690 + K1P = Decimal(0.1 * TCP) + + P = 24 + if cpp_ytd + cpp >= 2564.10: + PxC = Decimal(2564.10) + else: + C = cpp + PxC = P * C + if ei_ytd + ei >= 836.19: + PxEI = Decimal(836.19) + else: + EI = ei + PxEI = P * EI + K2P = (Decimal(0.10) * PxC) + (Decimal(0.10) * PxEI) + + K3P = 0 + + T4 = (V * A) - KP - K1P - K2P - K3P + + if T4 < 0: + T4 = 0 + + return T4 + +def get_prov_tax_deduction(prov_tax): + T4 = prov_tax + V1 = 0 + S = 0 + LCP = 0 + + T2 = T4 - V1 - S - LCP + + if T2 < 0: + T2 = 0 + + return T2 + +def get_income_tax(fed_tax_payable, prov_tax_deduction): + T1 = fed_tax_payable + T2 = prov_tax_deduction + P = 24 + L = 0 + + T = Decimal((T1 + T2) / P) + L + + return T + +def gen_recordsheets(user, payday, manage): + paydays = get_paydays_todate(payday) + recordsheets = [] + flat_records = [] + + for payday in paydays: + timesheets = gen_timesheets(user, payday, manage) + + for timesheet in timesheets: + record = {} + record['payday'] = payday + record['management'] = timesheet['management'] + record['reg_pay'] = sum(shift['amount'] for shift in timesheet['shifts']) + record['vac_pay'] = record['reg_pay'] * Decimal(0.04) + record['earnings'] = record['reg_pay'] + record['vac_pay'] + record['cpp_ytd'] = sum(r['cpp'] for r in flat_records if r['management'] == timesheet['management']) + record['cpp'] = get_cpp(record['cpp_ytd'], record['earnings']) + record['ei_ytd'] = sum(r['ei'] for r in flat_records if r['management'] == timesheet['management']) + record['ei'] = get_ei(record['ei_ytd'], record['earnings']) + record['taxable_income'] = get_taxable_income(record['earnings']) + record['fed_tax'] = get_fed_tax(record['taxable_income'], record['cpp'], record['ei'], record['cpp_ytd'], record['ei_ytd']) + record['fed_tax_payable'] = get_fed_tax_payable(record['fed_tax']) + record['prov_tax'] = get_prov_tax(record['taxable_income'], record['cpp'], record['ei'], record['cpp_ytd'], record['ei_ytd']) + record['prov_tax_deduction'] = get_prov_tax_deduction(record['prov_tax']) + record['income_tax'] = get_income_tax(record['fed_tax_payable'], record['prov_tax_deduction']) + record['total_deductions'] = record['income_tax'] + record['cpp'] + record['ei'] + record['net_pay'] = record['earnings'] - record['total_deductions'] + flat_records.append(record) + + flat_records = sorted(flat_records, key=lambda x: x['management'].uuid) + grouped_records = groupby(flat_records, lambda x: x['management']) + + for management, record in grouped_records: + recordsheet = {} + recordsheet['management'] = management + recordsheet['client'] = management.client + recordsheet['provider'] = management.provider + recordsheet['phone_number'] = management.provider.user.userinfo.phone_number + recordsheet['sin'] = management.provider.sin + recordsheet['record'] = list(record) + recordsheets.append(recordsheet) + + return list(recordsheets) + class ReportView(APIView): def get(self, request, *args, **kwargs): response = [] @@ -211,9 +426,17 @@ class ReportView(APIView): payday = validate_param(qp.get('payday'), serializers.DateField()) manage = validate_param(qp.get('manage'), serializers.UUIDField(allow_null=True)) - timesheet = get_timesheet(request.user, payday, manage) + timesheets = gen_timesheets(request.user, payday, manage) + + response = TimeSheetSerializer(timesheets, many=True).data + + elif report_type == 'record': + payday = validate_param(qp.get('payday'), serializers.DateField()) + manage = validate_param(qp.get('manage'), serializers.UUIDField(allow_null=True)) + + recordsheets = gen_recordsheets(request.user, payday, manage) - response = TimeSheetSerializer(timesheet, many=True).data + response = RecordSheetSerializer(recordsheets, many=True).data return Response(response, status=status.HTTP_200_OK)