def warnForImcompleteTimesheet(warnSurbooking=False, days=None, month=None): """Warn users and admin for incomplete timesheet after due date @param warnSurbooking: Warn for surbooking days (default is false) @param day: only check n first days. If none, check all month""" emailTemplate = get_template("batch/timesheet_warning_email.txt") if month == "current": nextMonth = (date.today().replace(day=1) + timedelta(days=40)).replace(day=1) currentMonth = date.today().replace(day=1) else: # Checking past month nextMonth = date.today().replace(day=1) currentMonth = (nextMonth - timedelta(days=5)).replace(day=1) mails = [] # List of mail to be sent for consultant in Consultant.objects.filter(active=True, subcontractor=False): recipients = [] if not [ m for m in consultant.forecasted_missions(currentMonth) if m.nature == "PROD" ]: # No productive mission forecasted on current month # Consultant may have just started # No check needed. Skip it continue missions = consultant.timesheet_missions(month=currentMonth) timesheetData, timesheetTotal, warning = gatherTimesheetData( consultant, missions, currentMonth) url = pydici.settings.PYDICI_HOST + urlresolvers.reverse( "people.views.consultant_home", args=[consultant.id]) url += "?year=%s;month=%s" % (currentMonth.year, currentMonth.month) url += "#tab-timesheet" # Truncate if day parameter was given if days: warning = warning[:days] warning = [i for i in warning if i] # Remove None if sum(warning) > 0: surbookingDays = warning.count(1) incompleteDays = warning.count(2) if not warnSurbooking and not incompleteDays: continue # Don't cry if user only have surbooking issue user = consultant.getUser() if user and user.email: recipients.append(user.email) if consultant.manager: managerUser = consultant.manager.getUser() if managerUser and managerUser.email: recipients.append(managerUser.email) if recipients: msgText = emailTemplate.render( Context({ "month": currentMonth, "surbooking_days": surbookingDays, "incomplete_days": incompleteDays, "consultant": consultant, "days": days, "url": url })) mails.append( ((_("[pydici] Your timesheet is not correct"), msgText, pydici.settings.LEADS_MAIL_FROM, recipients))) else: mails.append((( _("[pydici] User has no email"), _("User %s has an incomplete timesheet but cannot be warned because he has no email." % consultant), pydici.settings.LEADS_MAIL_FROM, [ pydici.settings.LEADS_MAIL_FROM, ]))) # Send all emails in one time send_mass_mail(mails, fail_silently=False)
def pre_billing(request, year=None, month=None, mine=False): """Pre billing page: help to identify bills to send""" if year and month: month = date(int(year), int(month), 1) else: month = previousMonth(date.today()) next_month = nextMonth(month) timeSpentBilling = {} # Key is lead, value is total and dict of mission(total, Mission billingData) rates = {} # Key is mission, value is Consultant rates dict try: billing_consultant = Consultant.objects.get(trigramme__iexact=request.user.username) except Consultant.DoesNotExist: billing_consultant = None mine = False # Check consultant timesheet to hint if billing could be done based on a clean state timesheet_ok = {} for consultant in Consultant.objects.filter(active=True, subcontractor=False): missions = consultant.timesheet_missions(month=month) timesheetData, timesheetTotal, warning = gatherTimesheetData(consultant, missions, month) days = sum([v for (k,v) in timesheetTotal.items() if k!="ticket"]) # Compute timesheet days. Remove lunch ticket count if days == working_days(month, holidayDays(month=month)): timesheet_ok[consultant.id] = True else: timesheet_ok[consultant.id] = False fixedPriceMissions = Mission.objects.filter(nature="PROD", billing_mode="FIXED_PRICE", timesheet__working_date__gte=month, timesheet__working_date__lt=next_month) undefinedBillingModeMissions = Mission.objects.filter(nature="PROD", billing_mode=None, timesheet__working_date__gte=month, timesheet__working_date__lt=next_month) if mine: fixedPriceMissions = fixedPriceMissions.filter(Q(lead__responsible=billing_consultant) | Q(responsible=billing_consultant)) undefinedBillingModeMissions = undefinedBillingModeMissions.filter(Q(lead__responsible=billing_consultant) | Q(responsible=billing_consultant)) fixedPriceMissions = fixedPriceMissions.order_by("lead").distinct() undefinedBillingModeMissions = undefinedBillingModeMissions.order_by("lead").distinct() timesheets = Timesheet.objects.filter(working_date__gte=month, working_date__lt=next_month, mission__nature="PROD", mission__billing_mode="TIME_SPENT") if mine: timesheets = timesheets.filter(Q(mission__lead__responsible=billing_consultant) | Q(mission__responsible=billing_consultant)) timesheet_data = timesheets.order_by("mission__lead", "consultant").values_list("mission", "consultant").annotate(Sum("charge")) for mission_id, consultant_id, charge in timesheet_data: mission = Mission.objects.select_related("lead").get(id=mission_id) if mission.lead: lead = mission.lead else: # Bad data, mission with nature prod without lead... This should not happened continue consultant = Consultant.objects.get(id=consultant_id) if not mission in rates: rates[mission] = mission.consultant_rates() if not lead in timeSpentBilling: timeSpentBilling[lead] = [0.0, {}] # Lead Total and dict of mission if not mission in timeSpentBilling[lead][1]: timeSpentBilling[lead][1][mission] = [0.0, []] # Mission Total and detail per consultant total = charge * rates[mission][consultant][0] timeSpentBilling[lead][0] += total timeSpentBilling[lead][1][mission][0] += total timeSpentBilling[lead][1][mission][1].append([consultant, to_int_or_round(charge, 2), rates[mission][consultant][0], total, timesheet_ok.get(consultant_id, True)]) # Sort data timeSpentBilling = timeSpentBilling.items() timeSpentBilling.sort(key=lambda x: x[0].deal_id) return render(request, "billing/pre_billing.html", {"time_spent_billing": timeSpentBilling, "fixed_price_missions": fixedPriceMissions, "undefined_billing_mode_missions": undefinedBillingModeMissions, "month": month, "mine": mine, "user": request.user})
def pre_billing(request, year=None, month=None, mine=False): """Pre billing page: help to identify bills to send""" if year and month: month = date(int(year), int(month), 1) else: month = previousMonth(date.today()) next_month = nextMonth(month) timeSpentBilling = { } # Key is lead, value is total and dict of mission(total, Mission billingData) rates = {} # Key is mission, value is Consultant rates dict try: billing_consultant = Consultant.objects.get( trigramme__iexact=request.user.username) except Consultant.DoesNotExist: billing_consultant = None mine = False # Check consultant timesheet to hint if billing could be done based on a clean state timesheet_ok = {} for consultant in Consultant.objects.filter(active=True, subcontractor=False): missions = consultant.timesheet_missions(month=month) timesheetData, timesheetTotal, warning = gatherTimesheetData( consultant, missions, month) days = sum([v for (k, v) in timesheetTotal.items() if k != "ticket" ]) # Compute timesheet days. Remove lunch ticket count if days == working_days(month, holidayDays(month=month)): timesheet_ok[consultant.id] = True else: timesheet_ok[consultant.id] = False fixedPriceMissions = Mission.objects.filter( nature="PROD", billing_mode="FIXED_PRICE", timesheet__working_date__gte=month, timesheet__working_date__lt=next_month) undefinedBillingModeMissions = Mission.objects.filter( nature="PROD", billing_mode=None, timesheet__working_date__gte=month, timesheet__working_date__lt=next_month) if mine: fixedPriceMissions = fixedPriceMissions.filter( Q(lead__responsible=billing_consultant) | Q(responsible=billing_consultant)) undefinedBillingModeMissions = undefinedBillingModeMissions.filter( Q(lead__responsible=billing_consultant) | Q(responsible=billing_consultant)) fixedPriceMissions = fixedPriceMissions.order_by("lead").distinct() undefinedBillingModeMissions = undefinedBillingModeMissions.order_by( "lead").distinct() timesheets = Timesheet.objects.filter(working_date__gte=month, working_date__lt=next_month, mission__nature="PROD", mission__billing_mode="TIME_SPENT") if mine: timesheets = timesheets.filter( Q(mission__lead__responsible=billing_consultant) | Q(mission__responsible=billing_consultant)) timesheet_data = timesheets.order_by( "mission__lead", "consultant").values_list("mission", "consultant").annotate(Sum("charge")) for mission_id, consultant_id, charge in timesheet_data: mission = Mission.objects.select_related("lead").get(id=mission_id) if mission.lead: lead = mission.lead else: # Bad data, mission with nature prod without lead... This should not happened continue consultant = Consultant.objects.get(id=consultant_id) if not mission in rates: rates[mission] = mission.consultant_rates() if not lead in timeSpentBilling: timeSpentBilling[lead] = [0.0, {}] # Lead Total and dict of mission if not mission in timeSpentBilling[lead][1]: timeSpentBilling[lead][1][mission] = [ 0.0, [] ] # Mission Total and detail per consultant total = charge * rates[mission][consultant][0] timeSpentBilling[lead][0] += total timeSpentBilling[lead][1][mission][0] += total timeSpentBilling[lead][1][mission][1].append([ consultant, to_int_or_round(charge, 2), rates[mission][consultant][0], total, timesheet_ok.get(consultant_id, True) ]) # Sort data timeSpentBilling = timeSpentBilling.items() timeSpentBilling.sort(key=lambda x: x[0].deal_id) return render( request, "billing/pre_billing.html", { "time_spent_billing": timeSpentBilling, "fixed_price_missions": fixedPriceMissions, "undefined_billing_mode_missions": undefinedBillingModeMissions, "month": month, "mine": mine, "user": request.user })
def warnForImcompleteTimesheet(warnSurbooking=False, days=None, month=None): """Warn users and admin for incomplete timesheet after due date @param warnSurbooking: Warn for surbooking days (default is false) @param day: only check n first days. If none, check all month""" emailTemplate = get_template("batch/timesheet_warning_email.txt") if month == "current": nextMonth = (date.today().replace(day=1) + timedelta(days=40)).replace(day=1) currentMonth = date.today().replace(day=1) else: # Checking past month nextMonth = date.today().replace(day=1) currentMonth = (nextMonth - timedelta(days=5)).replace(day=1) mails = [] # List of mail to be sent for consultant in Consultant.objects.filter(active=True, subcontractor=False): recipients = [] if not [m for m in consultant.forecasted_missions(currentMonth) if m.nature == "PROD"]: # No productive mission forecasted on current month # Consultant may have just started # No check needed. Skip it continue missions = consultant.timesheet_missions(month=currentMonth) timesheetData, timesheetTotal, warning = gatherTimesheetData(consultant, missions, currentMonth) url = pydici.settings.PYDICI_HOST + urlresolvers.reverse("people.views.consultant_home", args=[consultant.id]) url += "?year=%s;month=%s" % (currentMonth.year, currentMonth.month) # Truncate if day parameter was given if days: warning = warning[:days] warning = [i for i in warning if i] # Remove None if sum(warning) > 0: surbookingDays = warning.count(1) incompleteDays = warning.count(2) if not warnSurbooking and not incompleteDays: continue # Don't cry if user only have surbooking issue user = consultant.getUser() if user and user.email: recipients.append(user.email) if consultant.manager: managerUser = consultant.manager.getUser() if managerUser and managerUser.email: recipients.append(managerUser.email) if recipients: msgText = emailTemplate.render(Context( {"month": currentMonth, "surbooking_days": surbookingDays, "incomplete_days": incompleteDays, "consultant": consultant, "days": days, "url": url})) mails.append(((_("[pydici] Your timesheet is not correct"), msgText, pydici.settings.LEADS_MAIL_FROM, recipients))) else: mails.append(((_("[pydici] User has no email"), _("User %s has an incomplete timesheet but cannot be warned because he has no email." % consultant), pydici.settings.LEADS_MAIL_FROM, [pydici.settings.LEADS_MAIL_FROM, ]))) # Send all emails in one time send_mass_mail(mails, fail_silently=False)