def test_parameter(self): self.assertRaises(Exception, get_parameter, "foo") p = Parameter(key="testT", value="valueT", type="TEXT", desc="test") p.save() self.assertEquals(get_parameter(p.key), p.value) p = Parameter(key="testF", value=0.666, type="FLOAT", desc="test") p.save() self.assertEquals(get_parameter(p.key), p.value) p.value = 0.777 p.save() self.assertEquals(get_parameter(p.key), p.value)
def test_parameter(self): self.assertRaises(Exception, get_parameter, "foo") p = Parameter(key="testT", value="valueT", type="TEXT", desc="test") p.save() self.assertEqual(get_parameter(p.key), p.value) p = Parameter(key="testF", value=0.666, type="FLOAT", desc="test") p.save() self.assertEqual(get_parameter(p.key), p.value) p.value=0.777 p.save() self.assertEqual(get_parameter(p.key), p.value)
def alert_consultant(context): """Randomly alert consultant about important stuff to do""" close_old_connections() if outside_business_hours(): return consultants = Consultant.objects.exclude(telegram_id=None).filter(active=True) if not consultants: logger.warning("No consultant have telegram id defined. Alerting won't be possible. Bye") return consultant = random.choice(consultants) if consultant.is_in_holidays(): # don't bother people during holidays return cache_key = "BOT_ALERT_CONSULTANT_LAST_24H_%s" % consultant.trigramme if cache.get(cache_key): # don't persecute people :-) return cache.set(cache_key, 1, 3600*24) # Keep track 24 hours that this user has been alerted tasks = compute_consultant_tasks(consultant) if tasks: task_name, task_count, task_link, task_priority = random.choice(tasks) url = get_parameter("HOST") + task_link msg = _("Hey, what about thinking about that: %(task_name)s (x%(task_count)s)\n%(link)s") % {"task_name": task_name, "task_count": task_count, "link": url} context.bot.send_message(chat_id=consultant.telegram_id, text=msg)
def leads_pivotable(request, year=None): """Pivot table for all leads of given year""" data = [] leads = Lead.objects.passive() derivedAttributes = """{'%s': $.pivotUtilities.derivers.bin('%s', 20),}""" % ( _("sales (interval)"), _("sales")) month = int(get_parameter("FISCAL_YEAR_MONTH")) if not leads: return HttpResponse() years = get_fiscal_years(leads, "creation_date") if year is None and years: year = years[-1] if year != "all": year = int(year) start = date(year, month, 1) end = date(year + 1, month, 1) leads = leads.filter(creation_date__gte=start, creation_date__lt=end) leads = leads.select_related("responsible", "client__contact", "client__organisation__company", "subsidiary", "business_broker__company", "business_broker__contact") for lead in leads: data.append({ _("deal id"): lead.deal_id, _("name"): lead.name, _("client organisation"): unicode(lead.client.organisation), _("client company"): unicode(lead.client.organisation.company), _(u"sales (k€)"): int(lead.sales or 0), _("date"): lead.creation_date.strftime("%Y-%m"), _("responsible"): unicode(lead.responsible), _("broker"): unicode(lead.business_broker), _("state"): lead.get_state_display(), _(u"billed (€)"): int( lead.clientbill_set.filter(state__in=( "1_SENT", "2_PAID")).aggregate(Sum("amount")).values()[0] or 0), _("subsidiary"): unicode(lead.subsidiary) }) return render( request, "leads/leads_pivotable.html", { "data": json.dumps(data), "derivedAttributes": derivedAttributes, "years": years, "selected_year": year })
def main(): token = os.environ.get("TELEGRAM_TOKEN", settings.TELEGRAM_TOKEN) updater = Updater(token, use_context=True) dispatcher = updater.dispatcher conv_handler = ConversationHandler( entry_points=[CommandHandler('time', declare_time), CommandHandler("hello", hello), CommandHandler("start", hello), CommandHandler("help", help)], states={ # used for timesheet session only MISSION_SELECT: [ CallbackQueryHandler(select_mission, pattern="NONPROD"), CallbackQueryHandler(end_timesheet, pattern="END"), CallbackQueryHandler(mission_timesheet), ], MISSION_TIMESHEET: [ CallbackQueryHandler(select_mission), ], }, fallbacks=[CommandHandler('help', help)], ) # Add ConversationHandler to dispatcher dispatcher.add_handler(conv_handler) # Add alert job updater.job_queue.run_repeating(alert_consultant, get_parameter("BOT_ALERT_INTERVAL")) # Add call for timesheet alert try: timesheet_time = time(*[int(i) for i in get_parameter("BOT_CALL_TIME_FOR_TIMESHEET").split(":")], tzinfo=pytz.timezone(settings.TIME_ZONE)) except (TypeError, ValueError): logger.error("Cannot parse timesheet time. Defaulting to 19:00") timesheet_time = time(19, tzinfo=pytz.timezone(settings.TIME_ZONE)) updater.job_queue.run_daily(call_for_timesheet, timesheet_time) # Start the Bot updater.start_polling() # Run the bot until Ctrl-C or SIGINT,SIGTERM or SIGABRT. updater.idle()
def mail_lead(request, lead_id=0): try: lead = Lead.objects.get(id=lead_id) except Lead.DoesNotExist: raise Http404 try: send_lead_mail(lead) return HttpResponse(_("Lead %(id)s was sent to %(mail)s !") % {"id": lead_id, "mail": get_parameter("LEAD_MAIL_TO")}) except Exception as e: return HttpResponse(_("Failed to send mail: %s") % e)
def graph_yearly_billing(request): """Fiscal year billing per subsidiary""" bills = ClientBill.objects.filter(state__in=("1_SENT", "2_PAID")) years = get_fiscal_years_from_qs(bills, "creation_date") month = int(get_parameter("FISCAL_YEAR_MONTH")) data = {} graph_data = [] labels = [] growth = [] subsidiaries = Subsidiary.objects.all() for subsidiary in subsidiaries: data[subsidiary.name] = [] for year in years: turnover = {} for subsidiary_name, amount in bills.filter(creation_date__gte=date(year, month, 1), creation_date__lt=date(year + 1, month, 1)).values_list("lead__subsidiary__name").annotate(Sum("amount")): turnover[subsidiary_name] = float(amount) for subsidiary in subsidiaries: data[subsidiary.name].append(turnover.get(subsidiary.name, 0)) last_turnover = 0 for current_turnover in [sum(i) for i in zip(*list(data.values()))]: # Total per year if last_turnover > 0: growth.append(round(100 * (current_turnover - last_turnover) / last_turnover, 1)) else: growth.append(None) last_turnover = current_turnover if years[-1] == date.today().year: growth.pop() # Don't compute for on-going year. graph_data.append(["x"] + years) # X (years) axis # Add turnover per subsidiary for key, value in list(data.items()): if sum(value) == 0: continue value.insert(0, key) graph_data.append(value) labels.append(key) # Add growth graph_data.append([_("growth")] + growth) labels.append(_("growth")) return render(request, "billing/graph_yearly_billing.html", {"graph_data": json.dumps(graph_data), "years": years, "subsidiaries" : json.dumps(labels), "series_colors": COLORS, "user": request.user})
def leads_pivotable(request, year=None): """Pivot table for all leads of given year""" data = [] leads = Lead.objects.passive() derivedAttributes = """{'%s': $.pivotUtilities.derivers.bin('%s', 20),}""" % (_("sales (interval)"), _("sales (k€)")) month = int(get_parameter("FISCAL_YEAR_MONTH")) if not leads: return HttpResponse() years = get_fiscal_years(leads, "creation_date") if year is None and years: year = years[-1] if year != "all": year = int(year) start = date(year, month, 1) end = date(year + 1, month, 1) leads = leads.filter(creation_date__gte=start, creation_date__lt=end) leads = leads.select_related("responsible", "client__contact", "client__organisation__company", "subsidiary", "business_broker__company", "business_broker__contact") for lead in leads: data.append({_("deal id"): lead.deal_id, _("name"): lead.name, _("client organisation"): str(lead.client.organisation), _("client company"): str(lead.client.organisation.company), _("sales (k€)"): int(lead.sales or 0), _("date"): lead.creation_date.strftime("%Y-%m"), _("responsible"): str(lead.responsible), _("broker"): str(lead.business_broker), _("state"): lead.get_state_display(), _("billed (€)"): int(list(lead.clientbill_set.filter(state__in=("1_SENT", "2_PAID")).aggregate(Sum("amount")).values())[0] or 0), _("Over budget margin (k€)"): lead.margin(), _("subsidiary"): str(lead.subsidiary)}) return render(request, "leads/leads_pivotable.html", { "data": json.dumps(data), "derivedAttributes": derivedAttributes, "years": years, "selected_year": year})
from people.urls import people_urls from staffing.urls import staffing_urls from billing.urls import billing_urls from actionset.urls import actionset_urls from expense.urls import expense_urls from leads.urls import leads_urls from core.urls import core_urls # Overide internal server error view handler500 = "core.views.internal_error" pydici_patterns = [url(r'^admin/', admin.site.urls),] # Help page try: help_page_url = get_parameter("HELP_PAGE") except: # Corner case, during initial migration Parameter table does not exist yet help_page_url = "" if settings.DEBUG: import debug_toolbar pydici_patterns.append(url(r'^__debug__/', include(debug_toolbar.urls))) pydici_patterns.extend([ # Direct to template and direct pages url(r'^help', RedirectView.as_view(url=help_page_url, permanent=True), name='help'), # Media url(r'^media/(?P<path>.*)$', django.views.static.serve, {'document_root': os.path.join(settings.PYDICI_ROOTDIR, 'media')}),
def postSaveLead(request, lead, updated_fields, created=False, state_changed=False, sync=False): mail = False if lead.send_email: mail = True lead.send_email = False lead.save() # Log it LogEntry.objects.log_action( user_id = request.user.pk, content_type_id = ContentType.objects.get_for_model(lead).pk, object_id = lead.pk, object_repr = force_text(lead), action_flag = ADDITION, change_message = ", ".join(updated_fields), ) if mail: try: fromAddr = request.user.email or "*****@*****.**" send_lead_mail(lead, request, fromAddr=fromAddr, fromName="%s %s" % (request.user.first_name, request.user.last_name)) messages.add_message(request, messages.INFO, ugettext("Lead sent to business mailing list")) except Exception as e: messages.add_message(request, messages.ERROR, ugettext("Failed to send mail: %s") % e) if settings.TELEGRAM_IS_ENABLED: try: bot = telegram.bot.Bot(token=settings.TELEGRAM_TOKEN) sticker = None url = get_parameter("HOST") + reverse("leads:detail", args=[lead.id, ]) if created: msg = ugettext("New Lead !\n%(lead)s\n%(url)s") % {"lead": lead, "url":url } sticker = settings.TELEGRAM_STICKERS.get("happy") chat_group = "new_leads" elif state_changed: # Only notify when lead state changed to avoid useless spam try: change = "%s (%s)" % (lead.get_change_history()[0].change_message, lead.get_change_history()[0].user) except: change = "" msg = ugettext("Lead %(lead)s has been updated\n%(url)s\n%(change)s") % {"lead": lead, "url": url, "change": change} if lead.state == "WON": sticker = settings.TELEGRAM_STICKERS.get("happy") elif lead.state in ("LOST", "FORGIVEN"): sticker = settings.TELEGRAM_STICKERS.get("sad") chat_group = "leads_update" else: # No notification chat_group = "" for chat_id in settings.TELEGRAM_CHAT.get(chat_group, []): bot.sendMessage(chat_id=chat_id, text=msg, disable_web_page_preview=True) if sticker: bot.sendSticker(chat_id=chat_id, sticker=sticker) except Exception as e: messages.add_message(request, messages.ERROR, ugettext("Failed to send telegram notification: %s") % e) # Compute leads probability if sync: compute = compute_leads_state.now # Select synchronous flavor of computation function else: compute = compute_leads_state if lead.state in ("WON", "LOST", "SLEEPING", "FORGIVEN"): # Remove leads proba, no more needed lead.stateproba_set.all().delete() # Learn again. This new lead will now be used to training compute(relearn=True) else: # Just update proba for this lead with its new features compute(relearn=False, leads_id=[lead.id,]) # Update lead tags compute_leads_tags() # update lead similarity model compute_lead_similarity() # Create or update mission if needed if lead.mission_set.count() == 0: if lead.state in ("OFFER_SENT", "NEGOTIATION", "WON"): create_default_mission(lead) messages.add_message(request, messages.INFO, ugettext("A mission has been initialized for this lead.")) for mission in lead.mission_set.all(): if mission.subsidiary != lead.subsidiary: mission.subsidiary = lead.subsidiary mission.save() if lead.state == "WON": mission.probability = 100 mission.active = True mission.save() messages.add_message(request, messages.INFO, ugettext("Mission's probability has been set to 100%")) elif lead.state in ("LOST", "FORGIVEN", "SLEEPING"): mission.probability = 0 mission.active = False mission.save() messages.add_message(request, messages.INFO, ugettext("According mission has been archived"))
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 = get_parameter("HOST") + urlresolvers.reverse("people:consultant_home", args=[consultant.trigramme]) 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, get_parameter("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), get_parameter("MAIL_FROM"), [get_parameter("MAIL_FROM"), ]))) # Send all emails in one time send_mass_mail(mails, fail_silently=False)
def postSaveLead(request, lead, updated_fields, created=False, state_changed=False, sync=False): mail = False if lead.send_email: mail = True lead.send_email = False lead.save() # Log it LogEntry.objects.log_action( user_id=request.user.pk, content_type_id=ContentType.objects.get_for_model(lead).pk, object_id=lead.pk, object_repr=force_text(lead), action_flag=ADDITION, change_message=", ".join(updated_fields), ) if mail: try: fromAddr = request.user.email or "*****@*****.**" send_lead_mail(lead, request, fromAddr=fromAddr, fromName="%s %s" % (request.user.first_name, request.user.last_name)) messages.add_message( request, messages.INFO, ugettext("Lead sent to business mailing list")) except Exception as e: messages.add_message(request, messages.ERROR, ugettext("Failed to send mail: %s") % e) if settings.TELEGRAM_IS_ENABLED: try: bot = telegram.bot.Bot(token=settings.TELEGRAM_TOKEN) sticker = None url = get_parameter("HOST") + reverse("leads:detail", args=[ lead.id, ]) if created: msg = ugettext("New Lead !\n%(lead)s\n%(url)s") % { "lead": lead, "url": url } sticker = settings.TELEGRAM_STICKERS.get("happy") chat_group = "new_leads" elif state_changed: # Only notify when lead state changed to avoid useless spam try: change = "%s (%s)" % ( lead.get_change_history()[0].change_message, lead.get_change_history()[0].user) except: change = "" msg = ugettext( "Lead %(lead)s has been updated\n%(url)s\n%(change)s") % { "lead": lead, "url": url, "change": change } if lead.state == "WON": sticker = settings.TELEGRAM_STICKERS.get("happy") elif lead.state in ("LOST", "FORGIVEN"): sticker = settings.TELEGRAM_STICKERS.get("sad") chat_group = "leads_update" else: # No notification chat_group = "" for chat_id in settings.TELEGRAM_CHAT.get(chat_group, []): bot.sendMessage(chat_id=chat_id, text=msg, disable_web_page_preview=True) if sticker: bot.sendSticker(chat_id=chat_id, sticker=sticker) except Exception as e: messages.add_message( request, messages.ERROR, ugettext("Failed to send telegram notification: %s") % e) # Compute leads probability if sync: compute = compute_leads_state.now # Select synchronous flavor of computation function else: compute = compute_leads_state if lead.state in ("WON", "LOST", "SLEEPING", "FORGIVEN"): # Remove leads proba, no more needed lead.stateproba_set.all().delete() # Learn again. This new lead will now be used to training compute(relearn=True) else: # Just update proba for this lead with its new features compute(relearn=False, leads_id=[ lead.id, ]) # Update lead tags compute_leads_tags() # update lead similarity model compute_lead_similarity() # Create or update mission if needed if lead.mission_set.count() == 0: if lead.state in ("OFFER_SENT", "NEGOTIATION", "WON"): create_default_mission(lead) messages.add_message( request, messages.INFO, ugettext("A mission has been initialized for this lead.")) for mission in lead.mission_set.all(): if mission.subsidiary != lead.subsidiary: mission.subsidiary = lead.subsidiary mission.save() if lead.state == "WON": mission.probability = 100 mission.active = True mission.save() messages.add_message( request, messages.INFO, ugettext("Mission's probability has been set to 100%")) elif lead.state in ("LOST", "FORGIVEN", "SLEEPING"): mission.probability = 0 mission.active = False mission.save() messages.add_message( request, messages.INFO, ugettext("According mission has been archived"))
request, fromAddr=fromAddr, fromName="%s %s" % (request.user.first_name, request.user.last_name)) messages.add_message( request, messages.INFO, ugettext("Lead sent to business mailing list")) except Exception, e: messages.add_message(request, messages.ERROR, ugettext("Failed to send mail: %s") % e) if TELEGRAM_IS_ENABLED: try: bot = telegram.bot.Bot(token=TELEGRAM_TOKEN) sticker = None url = get_parameter("HOST") + urlresolvers.reverse( "leads.views.detail", args=[ lead.id, ]) if created: msg = ugettext(u"New Lead !\n%(lead)s\n%(url)s") % { "lead": lead, "url": url } sticker = TELEGRAM_STICKERS.get("happy") chat_group = "new_leads" elif state_changed: # Only notify when lead state changed to avoid useless spam try: change = u"%s (%s)" % ( lead.get_change_history()[0].change_message,
handler500 = "core.views.internal_error" pydici_patterns = patterns('', (r'^admin/', include(admin.site.urls))) if pydici.settings.DEBUG: import debug_toolbar pydici_patterns += patterns( '', url(r'^__debug__/', include(debug_toolbar.urls)), ) pydici_patterns += patterns( '', # Direct to template and direct pages url(r'^help', RedirectView.as_view(url=get_parameter("HELP_PAGE"), permanent=True), name='help'), # Media (r'^media/(?P<path>.*)$', 'django.views.static.serve', { 'document_root': os.path.join(pydici.settings.PYDICI_ROOTDIR, 'media') }), # Feeds url(r'^feeds/latest/?$', LatestLeads(), name='latest'), url(r'^feeds/new/?$', NewLeads(), name='new'), url(r'^feeds/won/?$', WonLeads(), name='won'), url(r'^feeds/mine/?$', MyLatestLeads(), name='mine'), url(r'^feeds/latestStaffing/?$', LatestStaffing(), name='latestStaffing'), url(r'^feeds/myLatestStaffing/?$', MyLatestStaffing(),
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 = get_parameter("HOST") + urlresolvers.reverse( "people:consultant_home", args=[consultant.trigramme]) 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, get_parameter("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), get_parameter("MAIL_FROM"), [ get_parameter("MAIL_FROM"), ]))) # Send all emails in one time send_mass_mail(mails, fail_silently=False)
from expense.urls import expense_urls from leads.urls import leads_urls from core.urls import core_urls from core.views import PydiciSelect2View, PydiciSelect2SubcontractorView # Overide internal server error view handler500 = "core.views.internal_error" pydici_patterns = [ url(r'^admin/', admin.site.urls), ] # Help page try: help_page_url = get_parameter("HELP_PAGE") except: # Corner case, during initial migration Parameter table does not exist yet help_page_url = "" if settings.DEBUG: import debug_toolbar pydici_patterns.append(url(r'^__debug__/', include(debug_toolbar.urls))) pydici_patterns.extend([ # Direct to template and direct pages url(r'^help', RedirectView.as_view(url=help_page_url, permanent=True), name='help'), # Media