IT-менеджер: каталог моделей оборудования + перевод choices на справочники
- EquipmentModel (каталог: тип+бренд+модель заводятся раз) отделён от Equipment (экземпляр: серийник+локация); миграция сворачивает данные. Массовый ввод серийников, массовая выдача на локации/сотрудников - Enum-choices (тип/статус/приоритет заявок, вид работ, каналы, расходники, договоры) переведены на core.Directory с quick-add из формы - Фронт-страница /it-manager/: вкладки оборудование/заявки/работы/ расходники/провайдеры/обращения; searchable-select локаций - Тесты (10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.it_manager.models import (
|
||||
Consumable, Equipment, EquipmentAssignment, EquipmentModel,
|
||||
InternetContract, InternetProvider,
|
||||
ServiceCall, Ticket, TicketComment, WorkLog,
|
||||
)
|
||||
from apps.locations.models.base import Location
|
||||
from core.models.directories import Directory
|
||||
from core.utils.htmx import render_page
|
||||
from core.utils.pagination import paginate
|
||||
|
||||
Employee = get_user_model()
|
||||
|
||||
|
||||
def _is_it_staff(user):
|
||||
return user.is_superuser or user.groups.filter(name='IT').exists()
|
||||
|
||||
|
||||
def _directory_choices(type_code):
|
||||
"""(code, name)-кортежі активних записів довідника — та сама форма,
|
||||
що й колишні CharField(choices=...), для сумісності з шаблонами."""
|
||||
return list(
|
||||
Directory.objects.filter(directory_type__code=type_code, is_active=True)
|
||||
.order_by('name').values_list('code', 'name')
|
||||
)
|
||||
|
||||
|
||||
def _resolve_directory(type_code, code):
|
||||
"""Резолвить код у Directory.pk для запису у FK-поле з форми (POST шле код,
|
||||
не pk — так шаблони лишаються незмінними)."""
|
||||
if not code:
|
||||
return None
|
||||
return Directory.objects.filter(directory_type__code=type_code, code=code).values_list('id', flat=True).first()
|
||||
|
||||
|
||||
def _it_manager_context(request):
|
||||
equipment_qs = (
|
||||
Equipment.objects.select_related(
|
||||
'equipment_model__brand', 'equipment_model__equipment_type',
|
||||
'assigned_to', 'location', 'status',
|
||||
)
|
||||
.order_by('equipment_model__equipment_type__name', 'equipment_model__name', 'serial_number')
|
||||
)
|
||||
equipment = list(equipment_qs)
|
||||
equipment_statuses = _directory_choices('equipment_status')
|
||||
equipment_counts = {code: 0 for code, _ in equipment_statuses}
|
||||
for item in equipment:
|
||||
equipment_counts[item.status.code] = equipment_counts.get(item.status.code, 0) + 1
|
||||
equipment_page = paginate(request, equipment_qs, page_param='equipment_page', size_param='equipment_per_page')
|
||||
|
||||
equipment_models = list(
|
||||
EquipmentModel.objects.select_related('equipment_type', 'brand')
|
||||
.annotate(units_total=models.Count('units'))
|
||||
.order_by('equipment_type__name', 'brand__name', 'name')
|
||||
)
|
||||
|
||||
tickets_qs = (
|
||||
Ticket.objects.select_related('requester', 'assignee', 'equipment', 'ticket_type', 'priority', 'status')
|
||||
.prefetch_related('comments__author')
|
||||
.order_by('-created_at')
|
||||
)
|
||||
tickets = list(tickets_qs)
|
||||
ticket_statuses = _directory_choices('ticket_status')
|
||||
ticket_counts = {code: 0 for code, _ in ticket_statuses}
|
||||
for ticket in tickets:
|
||||
ticket_counts[ticket.status.code] = ticket_counts.get(ticket.status.code, 0) + 1
|
||||
ticket_overdue_count = sum(1 for t in tickets if t.is_overdue)
|
||||
tickets_page = paginate(request, tickets_qs, page_param='tickets_page', size_param='tickets_per_page')
|
||||
|
||||
month_start = timezone.localdate().replace(day=1)
|
||||
worklogs_qs = (
|
||||
WorkLog.objects.select_related('technician', 'location', 'equipment', 'ticket', 'work_type')
|
||||
.order_by('-date', '-created_at')
|
||||
)
|
||||
worklogs_page = paginate(request, worklogs_qs, page_param='worklogs_page', size_param='worklogs_per_page')
|
||||
month_hours = WorkLog.objects.filter(date__gte=month_start).aggregate(total=Sum('hours_spent'))['total'] or 0
|
||||
month_count = WorkLog.objects.filter(date__gte=month_start).count()
|
||||
|
||||
consumables_qs = (
|
||||
Consumable.objects.select_related('consumable_type', 'color', 'status', 'equipment', 'location')
|
||||
.order_by('equipment', 'consumable_type__name', 'color__name')
|
||||
)
|
||||
consumables = list(consumables_qs)
|
||||
consumable_low_count = sum(1 for c in consumables if c.is_low)
|
||||
consumables_page = paginate(request, consumables_qs, page_param='consumables_page', size_param='consumables_per_page')
|
||||
printers = [e for e in equipment if e.equipment_type.code == 'printer']
|
||||
|
||||
providers = list(InternetProvider.objects.order_by('name'))
|
||||
|
||||
contracts_qs = (
|
||||
InternetContract.objects.select_related('provider', 'location', 'service_type', 'connection_type', 'status')
|
||||
.order_by('location__address', 'provider__name')
|
||||
)
|
||||
contracts = list(contracts_qs)
|
||||
contracts_expiring_count = sum(1 for c in contracts if c.is_expiring_soon)
|
||||
contracts_page = paginate(request, contracts_qs, page_param='contracts_page', size_param='contracts_per_page')
|
||||
|
||||
service_calls_qs = (
|
||||
ServiceCall.objects.select_related('caller', 'handler', 'location', 'channel', 'ticket')
|
||||
.order_by('-date_time')
|
||||
)
|
||||
service_calls_page = paginate(
|
||||
request, service_calls_qs, page_param='servicecalls_page', size_param='servicecalls_per_page',
|
||||
)
|
||||
service_calls_count = service_calls_qs.count()
|
||||
|
||||
return {
|
||||
'equipment_list': equipment,
|
||||
'equipment_page': equipment_page,
|
||||
'equipment_counts': equipment_counts,
|
||||
'equipment_types': _directory_choices('equipment_type'),
|
||||
'equipment_statuses': equipment_statuses,
|
||||
'equipment_brands': Directory.objects.filter(directory_type__code='equipment_brand', is_active=True).order_by('name'),
|
||||
'equipment_models': equipment_models,
|
||||
'it_locations': (
|
||||
Location.objects.filter(is_active=True)
|
||||
.select_related('location_region')
|
||||
.prefetch_related('pharmacy')
|
||||
.order_by('location_type', 'address')
|
||||
),
|
||||
'it_employees': Employee.objects.filter(is_active=True).order_by('last_name', 'first_name'),
|
||||
|
||||
'tickets': tickets,
|
||||
'tickets_page': tickets_page,
|
||||
'ticket_counts': ticket_counts,
|
||||
'ticket_overdue_count': ticket_overdue_count,
|
||||
'ticket_statuses': ticket_statuses,
|
||||
'ticket_priorities': _directory_choices('ticket_priority'),
|
||||
'ticket_types': _directory_choices('ticket_type'),
|
||||
'it_staff': Employee.objects.filter(groups__name='IT', is_active=True).order_by('last_name'),
|
||||
|
||||
'worklogs_page': worklogs_page,
|
||||
'worklog_types': _directory_choices('worklog_type'),
|
||||
'month_hours': month_hours,
|
||||
'month_count': month_count,
|
||||
|
||||
'consumables_page': consumables_page,
|
||||
'consumables_count': len(consumables),
|
||||
'consumable_low_count': consumable_low_count,
|
||||
'consumable_types': _directory_choices('consumable_type'),
|
||||
'consumable_colors': _directory_choices('consumable_color'),
|
||||
'consumable_statuses': _directory_choices('consumable_status'),
|
||||
'printers': printers,
|
||||
|
||||
'providers': providers,
|
||||
'contracts_page': contracts_page,
|
||||
'contracts_count': len(contracts),
|
||||
'contracts_expiring_count': contracts_expiring_count,
|
||||
'contract_service_types': _directory_choices('contract_service_type'),
|
||||
'contract_connection_types': _directory_choices('contract_connection_type'),
|
||||
'contract_statuses': _directory_choices('contract_status'),
|
||||
|
||||
'service_calls_page': service_calls_page,
|
||||
'service_calls_count': service_calls_count,
|
||||
'servicecall_channels': _directory_choices('servicecall_channel'),
|
||||
}
|
||||
|
||||
|
||||
@login_required
|
||||
def it_manager_view(request):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
return render_page(request, 'it-manager.html', _it_manager_context(request))
|
||||
|
||||
|
||||
@login_required
|
||||
def equipment_model_create_view(request):
|
||||
"""Додати модель у каталог (тип + бренд + назва). Далі за нею
|
||||
заводяться екземпляри лише з серійниками."""
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
context_extra = {}
|
||||
if request.method == 'POST':
|
||||
equipment_type_id = _resolve_directory('equipment_type', request.POST.get('equipment_type', ''))
|
||||
name = request.POST.get('name', '').strip()
|
||||
if equipment_type_id and name:
|
||||
warranty_months = request.POST.get('warranty_months', '').strip()
|
||||
EquipmentModel.objects.get_or_create(
|
||||
equipment_type_id=equipment_type_id,
|
||||
brand_id=request.POST.get('brand_id') or None,
|
||||
name=name,
|
||||
defaults={
|
||||
'warranty_months': int(warranty_months) if warranty_months.isdigit() else None,
|
||||
'notes': request.POST.get('notes', '').strip(),
|
||||
},
|
||||
)
|
||||
else:
|
||||
context_extra['equipment_form_error'] = 'Вкажіть тип і назву моделі.'
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'equipment'
|
||||
context.update(context_extra)
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def equipment_bulk_create_view(request):
|
||||
"""Масовий ввід екземплярів: обираємо модель з каталогу і вставляємо
|
||||
список серійників (по одному в рядку) — створюється N одиниць на склад.
|
||||
Так більше не треба щоразу переписувати тип/бренд/модель."""
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
context_extra = {}
|
||||
if request.method == 'POST':
|
||||
model_id = request.POST.get('equipment_model_id') or None
|
||||
raw_serials = request.POST.get('serial_numbers', '')
|
||||
# серійники — по одному в рядку (або через кому); чистимо й дедуплікуємо
|
||||
serials, seen = [], set()
|
||||
for chunk in raw_serials.replace(',', '\n').splitlines():
|
||||
s = chunk.strip()
|
||||
if s and s not in seen:
|
||||
seen.add(s)
|
||||
serials.append(s)
|
||||
|
||||
model = EquipmentModel.objects.filter(pk=model_id).first() if model_id else None
|
||||
if not model:
|
||||
context_extra['equipment_form_error'] = 'Оберіть модель з каталогу.'
|
||||
elif not serials:
|
||||
context_extra['equipment_form_error'] = 'Вставте хоча б один серійний номер.'
|
||||
else:
|
||||
purchase_date = request.POST.get('purchase_date') or None
|
||||
warranty_until = request.POST.get('warranty_until') or None
|
||||
# авто-розрахунок гарантії від дати покупки, якщо в моделі є термін
|
||||
if purchase_date and not warranty_until and model.warranty_months:
|
||||
try:
|
||||
d = datetime.strptime(purchase_date, '%Y-%m-%d').date()
|
||||
month = d.month - 1 + model.warranty_months
|
||||
year = d.year + month // 12
|
||||
from calendar import monthrange
|
||||
day = min(d.day, monthrange(year, month % 12 + 1)[1])
|
||||
warranty_until = d.replace(year=year, month=month % 12 + 1, day=day)
|
||||
except ValueError:
|
||||
warranty_until = None
|
||||
location_id = request.POST.get('location_id') or None
|
||||
# якщо одразу видаємо на локацію — статус assigned, інакше stock
|
||||
status_code = 'assigned' if location_id else 'stock'
|
||||
status_id = _resolve_directory('equipment_status', status_code) \
|
||||
or _resolve_directory('equipment_status', 'stock')
|
||||
|
||||
existing = set(
|
||||
Equipment.objects.filter(serial_number__in=serials)
|
||||
.values_list('serial_number', flat=True)
|
||||
)
|
||||
created = 0
|
||||
for s in serials:
|
||||
if s in existing:
|
||||
continue
|
||||
Equipment.objects.create(
|
||||
equipment_model=model,
|
||||
serial_number=s,
|
||||
status_id=status_id,
|
||||
location_id=location_id,
|
||||
purchase_date=purchase_date,
|
||||
warranty_until=warranty_until,
|
||||
)
|
||||
created += 1
|
||||
skipped = len(serials) - created
|
||||
msg = f'Додано {created} од. «{model}»'
|
||||
if skipped:
|
||||
msg += f'; пропущено {skipped} (серійник вже існує)'
|
||||
context_extra['equipment_form_message'] = msg
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'equipment'
|
||||
context.update(context_extra)
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def equipment_bulk_assign_view(request):
|
||||
"""Масова видача: відмічені екземпляри → локація (та/або співробітник)
|
||||
за один крок, з записом у історію переміщень."""
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
context_extra = {}
|
||||
if request.method == 'POST':
|
||||
ids = request.POST.getlist('equipment_ids')
|
||||
location_id = request.POST.get('location_id') or None
|
||||
assigned_to_id = request.POST.get('assigned_to_id') or None
|
||||
|
||||
if not ids:
|
||||
context_extra['equipment_form_error'] = 'Відмітьте хоча б один екземпляр.'
|
||||
elif not location_id and not assigned_to_id:
|
||||
context_extra['equipment_form_error'] = 'Оберіть локацію або співробітника для видачі.'
|
||||
else:
|
||||
assigned_status_id = _resolve_directory('equipment_status', 'assigned')
|
||||
units = Equipment.objects.filter(pk__in=ids)
|
||||
count = 0
|
||||
for unit in units:
|
||||
# закриваємо попередню активну видачу, якщо була
|
||||
if unit.assigned_to_id:
|
||||
EquipmentAssignment.objects.filter(
|
||||
equipment=unit, assigned_to_id=unit.assigned_to_id, returned_at__isnull=True,
|
||||
).update(returned_at=timezone.now())
|
||||
if location_id:
|
||||
unit.location_id = location_id
|
||||
if assigned_to_id:
|
||||
unit.assigned_to_id = assigned_to_id
|
||||
if assigned_status_id:
|
||||
unit.status_id = assigned_status_id
|
||||
unit.save()
|
||||
EquipmentAssignment.objects.create(
|
||||
equipment=unit,
|
||||
assigned_to_id=assigned_to_id,
|
||||
location_id=location_id,
|
||||
assigned_by=request.user,
|
||||
)
|
||||
count += 1
|
||||
context_extra['equipment_form_message'] = f'Видано {count} од. обладнання'
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'equipment'
|
||||
context.update(context_extra)
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def equipment_update_view(request, pk):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
equipment = get_object_or_404(Equipment, pk=pk)
|
||||
|
||||
new_status_id = _resolve_directory('equipment_status', request.POST.get('status'))
|
||||
if new_status_id:
|
||||
equipment.status_id = new_status_id
|
||||
|
||||
new_assigned_to = request.POST.get('assigned_to_id') or None
|
||||
if str(equipment.assigned_to_id or '') != str(new_assigned_to or ''):
|
||||
if equipment.assigned_to_id:
|
||||
EquipmentAssignment.objects.filter(
|
||||
equipment=equipment, assigned_to_id=equipment.assigned_to_id, returned_at__isnull=True,
|
||||
).update(returned_at=timezone.now())
|
||||
equipment.assigned_to_id = new_assigned_to
|
||||
if new_assigned_to:
|
||||
EquipmentAssignment.objects.create(
|
||||
equipment=equipment, assigned_to_id=new_assigned_to,
|
||||
location=equipment.location, assigned_by=request.user,
|
||||
)
|
||||
|
||||
equipment.save()
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'equipment'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def ticket_update_view(request, pk):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
ticket = get_object_or_404(Ticket, pk=pk)
|
||||
|
||||
new_status_id = _resolve_directory('ticket_status', request.POST.get('status'))
|
||||
if new_status_id:
|
||||
ticket.status_id = new_status_id
|
||||
|
||||
ticket.assignee_id = request.POST.get('assignee_id') or None
|
||||
ticket.save()
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'tickets'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def ticket_comment_view(request, pk):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
ticket = get_object_or_404(Ticket, pk=pk)
|
||||
text = request.POST.get('text', '').strip()
|
||||
if text:
|
||||
TicketComment.objects.create(
|
||||
ticket=ticket, author=request.user, text=text,
|
||||
is_internal=bool(request.POST.get('is_internal')),
|
||||
)
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'tickets'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def worklog_create_view(request):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
description = request.POST.get('description', '').strip()
|
||||
work_type_id = _resolve_directory('worklog_type', request.POST.get('work_type', ''))
|
||||
|
||||
if description and work_type_id:
|
||||
try:
|
||||
log_date = datetime.strptime(request.POST.get('date', ''), '%Y-%m-%d').date()
|
||||
except ValueError:
|
||||
log_date = timezone.localdate()
|
||||
try:
|
||||
hours_spent = float(request.POST.get('hours_spent', '').strip() or 0) or None
|
||||
except ValueError:
|
||||
hours_spent = None
|
||||
|
||||
WorkLog.objects.create(
|
||||
date=log_date,
|
||||
technician=request.user,
|
||||
work_type_id=work_type_id,
|
||||
location_id=request.POST.get('location_id') or None,
|
||||
equipment_id=request.POST.get('equipment_id') or None,
|
||||
ticket_id=request.POST.get('ticket_id') or None,
|
||||
description=description,
|
||||
result=request.POST.get('result', '').strip(),
|
||||
hours_spent=hours_spent,
|
||||
)
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'worklog'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def consumable_create_view(request):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
model_number = request.POST.get('model_number', '').strip()
|
||||
consumable_type_id = _resolve_directory('consumable_type', request.POST.get('consumable_type', ''))
|
||||
color_id = _resolve_directory('consumable_color', request.POST.get('color', ''))
|
||||
|
||||
if model_number and consumable_type_id and color_id:
|
||||
Consumable.objects.create(
|
||||
consumable_type_id=consumable_type_id,
|
||||
brand=request.POST.get('brand', '').strip(),
|
||||
model_number=model_number,
|
||||
color_id=color_id,
|
||||
equipment_id=request.POST.get('equipment_id') or None,
|
||||
location_id=request.POST.get('location_id') or None,
|
||||
page_yield=request.POST.get('page_yield') or None,
|
||||
purchase_date=request.POST.get('purchase_date') or None,
|
||||
notes=request.POST.get('notes', '').strip(),
|
||||
)
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'consumables'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def consumable_update_view(request, pk):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
consumable = get_object_or_404(Consumable, pk=pk)
|
||||
new_status_code = request.POST.get('status')
|
||||
new_status_id = _resolve_directory('consumable_status', new_status_code)
|
||||
if new_status_id:
|
||||
consumable.status_id = new_status_id
|
||||
if new_status_code == 'installed' and not consumable.installed_date:
|
||||
consumable.installed_date = timezone.localdate()
|
||||
consumable.save()
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'consumables'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def provider_create_view(request):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.POST.get('name', '').strip()
|
||||
if name:
|
||||
InternetProvider.objects.get_or_create(
|
||||
name=name,
|
||||
defaults={
|
||||
'phone': request.POST.get('phone', '').strip(),
|
||||
'email': request.POST.get('email', '').strip(),
|
||||
'website': request.POST.get('website', '').strip(),
|
||||
'support_phone': request.POST.get('support_phone', '').strip(),
|
||||
'support_email': request.POST.get('support_email', '').strip(),
|
||||
'notes': request.POST.get('notes', '').strip(),
|
||||
},
|
||||
)
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'providers'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def contract_create_view(request):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
provider_id = request.POST.get('provider_id') or None
|
||||
location_id = request.POST.get('location_id') or None
|
||||
contract_number = request.POST.get('contract_number', '').strip()
|
||||
start_date = request.POST.get('start_date') or None
|
||||
service_type_id = _resolve_directory('contract_service_type', request.POST.get('service_type', ''))
|
||||
|
||||
if provider_id and location_id and contract_number and start_date and service_type_id:
|
||||
InternetContract.objects.create(
|
||||
provider_id=provider_id,
|
||||
location_id=location_id,
|
||||
contract_number=contract_number,
|
||||
service_type_id=service_type_id,
|
||||
connection_type_id=_resolve_directory('contract_connection_type', request.POST.get('connection_type', '')),
|
||||
start_date=start_date,
|
||||
end_date=request.POST.get('end_date') or None,
|
||||
monthly_cost=request.POST.get('monthly_cost') or None,
|
||||
bandwidth_down=request.POST.get('bandwidth_down') or None,
|
||||
bandwidth_up=request.POST.get('bandwidth_up') or None,
|
||||
account_number=request.POST.get('account_number', '').strip(),
|
||||
static_ips=request.POST.get('static_ips', '').strip() if request.POST.get('dedicated_ip') else '',
|
||||
notes=request.POST.get('notes', '').strip(),
|
||||
)
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'providers'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def contract_update_view(request, pk):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
contract = get_object_or_404(InternetContract, pk=pk)
|
||||
new_status_id = _resolve_directory('contract_status', request.POST.get('status'))
|
||||
if new_status_id:
|
||||
contract.status_id = new_status_id
|
||||
contract.save()
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'providers'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def servicecall_create_view(request):
|
||||
if not _is_it_staff(request.user):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
caller_id = request.POST.get('caller_id') or None
|
||||
description = request.POST.get('description', '').strip()
|
||||
channel_id = _resolve_directory('servicecall_channel', request.POST.get('channel', ''))
|
||||
|
||||
if caller_id and description and channel_id:
|
||||
ServiceCall.objects.create(
|
||||
caller_id=caller_id,
|
||||
handler=request.user,
|
||||
channel_id=channel_id,
|
||||
location_id=request.POST.get('location_id') or None,
|
||||
description=description,
|
||||
resolution=request.POST.get('resolution', '').strip(),
|
||||
minutes_spent=request.POST.get('minutes_spent') or None,
|
||||
ticket_id=request.POST.get('ticket_id') or None,
|
||||
)
|
||||
|
||||
context = _it_manager_context(request)
|
||||
context['it_active_tab'] = 'servicecalls'
|
||||
return render_page(request, 'it-manager.html', context)
|
||||
Reference in New Issue
Block a user