This commit is contained in:
@@ -14,6 +14,7 @@ from django.core.files.base import ContentFile
|
||||
from django.db import close_old_connections
|
||||
|
||||
from django.db.models import Case, ExpressionWrapper, F, IntegerField, Sum, Value, When
|
||||
from django.db.models import Q
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
@@ -23,7 +24,12 @@ from django.views.generic import FormView, ListView, TemplateView, UpdateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.utils import timezone
|
||||
|
||||
from warehouse.models import Material, MaterialCategory, SteelGrade
|
||||
from manufacturing.models import ProductEntity
|
||||
|
||||
from warehouse.models import Location, Material, MaterialCategory, SteelGrade, StockItem, TransferLine, TransferRecord
|
||||
from warehouse.services.transfers import receive_transfer
|
||||
|
||||
from shiftflow.services.closing import apply_closing
|
||||
|
||||
from .forms import ProductionTaskCreateForm
|
||||
from .models import Company, Deal, DxfPreviewJob, DxfPreviewSettings, EmployeeProfile, Item, Machine, ProductionTask
|
||||
@@ -1193,9 +1199,11 @@ class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||||
template_name = 'shiftflow/item_detail.html'
|
||||
# Перечисляем поля, которые можно редактировать в сменке
|
||||
fields = [
|
||||
'machine', 'quantity_plan', 'quantity_fact',
|
||||
'status', 'is_synced_1c',
|
||||
'material_taken', 'usable_waste', 'scrap_weight'
|
||||
'machine',
|
||||
'quantity_plan',
|
||||
'quantity_fact',
|
||||
'status',
|
||||
'is_synced_1c',
|
||||
]
|
||||
context_object_name = 'item'
|
||||
|
||||
@@ -1296,15 +1304,6 @@ class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||||
self.object.quantity_fact = int(quantity_fact)
|
||||
|
||||
self.object.is_synced_1c = bool(request.POST.get('is_synced_1c'))
|
||||
self.object.material_taken = request.POST.get('material_taken', self.object.material_taken)
|
||||
self.object.usable_waste = request.POST.get('usable_waste', self.object.usable_waste)
|
||||
|
||||
scrap_weight = request.POST.get('scrap_weight')
|
||||
if scrap_weight is not None and scrap_weight != '':
|
||||
try:
|
||||
self.object.scrap_weight = float(scrap_weight)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Действия закрытия для админа/технолога
|
||||
if action == 'close_done' and self.object.status == 'work':
|
||||
@@ -1340,88 +1339,26 @@ class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||||
|
||||
if role in ['operator', 'master']:
|
||||
action = request.POST.get('action', 'save')
|
||||
material_taken = (request.POST.get('material_taken') or '').strip()
|
||||
usable_waste = (request.POST.get('usable_waste') or '').strip()
|
||||
scrap_weight_raw = (request.POST.get('scrap_weight') or '').strip()
|
||||
|
||||
if action == 'save':
|
||||
qf = request.POST.get('quantity_fact')
|
||||
if qf and qf.isdigit():
|
||||
self.object.quantity_fact = int(qf)
|
||||
machine_changed = False
|
||||
if role == 'master':
|
||||
machine_id = request.POST.get('machine')
|
||||
if machine_id and machine_id.isdigit():
|
||||
self.object.machine_id = int(machine_id)
|
||||
machine_changed = True
|
||||
fields = ['quantity_fact']
|
||||
if machine_changed:
|
||||
fields.append('machine')
|
||||
self.object.save(update_fields=fields)
|
||||
if action != 'save':
|
||||
return redirect_back()
|
||||
|
||||
if self.object.status != 'work':
|
||||
return redirect_back()
|
||||
qf = request.POST.get('quantity_fact')
|
||||
if qf and qf.isdigit():
|
||||
self.object.quantity_fact = int(qf)
|
||||
|
||||
errors = []
|
||||
if not material_taken:
|
||||
errors.append('Заполни поле "Взятый материал"')
|
||||
if not usable_waste:
|
||||
errors.append('Заполни поле "Остаток ДО"')
|
||||
if scrap_weight_raw == '':
|
||||
errors.append('Заполни поле "Лом (кг)" (можно 0)')
|
||||
machine_changed = False
|
||||
if role == 'master':
|
||||
machine_id = request.POST.get('machine')
|
||||
if machine_id and machine_id.isdigit():
|
||||
self.object.machine_id = int(machine_id)
|
||||
machine_changed = True
|
||||
|
||||
scrap_weight = None
|
||||
if scrap_weight_raw != '':
|
||||
try:
|
||||
scrap_weight = float(scrap_weight_raw)
|
||||
except ValueError:
|
||||
errors.append('Поле "Лом (кг)" должно быть числом')
|
||||
|
||||
if errors:
|
||||
context = self.get_context_data()
|
||||
context['errors'] = errors
|
||||
return self.render_to_response(context)
|
||||
|
||||
self.object.material_taken = material_taken
|
||||
self.object.usable_waste = usable_waste
|
||||
if scrap_weight is not None:
|
||||
self.object.scrap_weight = scrap_weight
|
||||
|
||||
if action == 'close_done':
|
||||
self.object.quantity_fact = self.object.quantity_plan
|
||||
self.object.status = 'done'
|
||||
self.object.save()
|
||||
return redirect_back()
|
||||
|
||||
if action == 'close_partial':
|
||||
try:
|
||||
fact = int(request.POST.get('quantity_fact', '0'))
|
||||
except ValueError:
|
||||
fact = 0
|
||||
if fact <= 0:
|
||||
context = self.get_context_data()
|
||||
context['errors'] = ['При частичном закрытии укажи, сколько сделано (больше 0)']
|
||||
return self.render_to_response(context)
|
||||
fact = max(0, min(fact, self.object.quantity_plan))
|
||||
residual = self.object.quantity_plan - fact
|
||||
|
||||
self.object.quantity_fact = fact
|
||||
self.object.status = 'partial'
|
||||
self.object.save()
|
||||
|
||||
if residual > 0:
|
||||
Item.objects.create(
|
||||
task=self.object.task,
|
||||
date=self.object.date,
|
||||
machine=self.object.machine,
|
||||
quantity_plan=residual,
|
||||
quantity_fact=0,
|
||||
status='leftover',
|
||||
is_synced_1c=False,
|
||||
)
|
||||
return redirect_back()
|
||||
fields = ['quantity_fact']
|
||||
if machine_changed:
|
||||
fields.append('machine')
|
||||
|
||||
self.object.save(update_fields=fields)
|
||||
return redirect_back()
|
||||
|
||||
if role == 'clerk':
|
||||
@@ -1434,4 +1371,434 @@ class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||||
return redirect_back()
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('registry')
|
||||
return reverse_lazy('registry')
|
||||
|
||||
|
||||
class WarehouseStocksView(LoginRequiredMixin, TemplateView):
|
||||
template_name = 'shiftflow/warehouse_stocks.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
profile = getattr(request.user, 'profile', None)
|
||||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||||
if role not in ['admin', 'technologist', 'master', 'clerk', 'observer']:
|
||||
return redirect('registry')
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
profile = getattr(self.request.user, 'profile', None)
|
||||
role = profile.role if profile else ('admin' if self.request.user.is_superuser else 'operator')
|
||||
ctx['user_role'] = role
|
||||
|
||||
ship_loc = (
|
||||
Location.objects.filter(
|
||||
Q(name__icontains='отгруж')
|
||||
| Q(name__icontains='Отгруж')
|
||||
| Q(name__icontains='отгруз')
|
||||
| Q(name__icontains='Отгруз')
|
||||
)
|
||||
.order_by('id')
|
||||
.first()
|
||||
)
|
||||
ship_loc_id = ship_loc.id if ship_loc else None
|
||||
|
||||
locations_qs = Location.objects.all().order_by('name')
|
||||
if ship_loc_id:
|
||||
locations_qs = locations_qs.exclude(id=ship_loc_id)
|
||||
locations = list(locations_qs)
|
||||
ctx['locations'] = locations
|
||||
|
||||
q = (self.request.GET.get('q') or '').strip()
|
||||
location_id = (self.request.GET.get('location_id') or '').strip()
|
||||
kind = (self.request.GET.get('kind') or '').strip()
|
||||
|
||||
start_date = (self.request.GET.get('start_date') or '').strip()
|
||||
end_date = (self.request.GET.get('end_date') or '').strip()
|
||||
filtered = self.request.GET.get('filtered')
|
||||
reset = self.request.GET.get('reset')
|
||||
is_default = (not filtered) or bool(reset)
|
||||
|
||||
if is_default:
|
||||
today = timezone.localdate()
|
||||
start = today - timezone.timedelta(days=21)
|
||||
ctx['start_date'] = start.strftime('%Y-%m-%d')
|
||||
ctx['end_date'] = today.strftime('%Y-%m-%d')
|
||||
else:
|
||||
ctx['start_date'] = start_date
|
||||
ctx['end_date'] = end_date
|
||||
|
||||
qs = StockItem.objects.select_related('location', 'material', 'material__category', 'entity', 'deal').all()
|
||||
if ship_loc_id:
|
||||
qs = qs.exclude(location_id=ship_loc_id)
|
||||
|
||||
if location_id.isdigit():
|
||||
qs = qs.filter(location_id=int(location_id))
|
||||
|
||||
start_val = ctx.get('start_date')
|
||||
end_val = ctx.get('end_date')
|
||||
if start_val:
|
||||
qs = qs.filter(created_at__date__gte=start_val)
|
||||
if end_val:
|
||||
qs = qs.filter(created_at__date__lte=end_val)
|
||||
|
||||
if kind == 'raw':
|
||||
qs = qs.filter(material__isnull=False, entity__isnull=True)
|
||||
elif kind == 'finished':
|
||||
qs = qs.filter(entity__isnull=False)
|
||||
elif kind == 'remnant':
|
||||
qs = qs.filter(is_remnant=True)
|
||||
|
||||
if q:
|
||||
qs = qs.filter(
|
||||
Q(material__full_name__icontains=q)
|
||||
| Q(material__name__icontains=q)
|
||||
| Q(entity__name__icontains=q)
|
||||
| Q(entity__drawing_number__icontains=q)
|
||||
| Q(unique_id__icontains=q)
|
||||
| Q(location__name__icontains=q)
|
||||
)
|
||||
|
||||
ctx['items'] = qs.order_by('-created_at', '-id')
|
||||
|
||||
ctx['selected_location_id'] = location_id
|
||||
ctx['selected_kind'] = kind
|
||||
ctx['q'] = q
|
||||
|
||||
ctx['can_transfer'] = role in ['admin', 'technologist', 'master', 'clerk']
|
||||
ctx['can_receive'] = role in ['admin', 'technologist', 'master', 'clerk']
|
||||
|
||||
ctx['materials'] = Material.objects.select_related('category').all().order_by('full_name')
|
||||
ctx['entities'] = ProductEntity.objects.all().order_by('drawing_number', 'name')
|
||||
ctx['deals'] = Deal.objects.select_related('company').all().order_by('-id')
|
||||
|
||||
ctx['shipping_location_id'] = ship_loc_id or ''
|
||||
ctx['shipping_location_label'] = ship_loc.name if ship_loc else ''
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class WarehouseTransferCreateView(LoginRequiredMixin, View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
profile = getattr(request.user, 'profile', None)
|
||||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||||
if role not in ['admin', 'technologist', 'master', 'clerk']:
|
||||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||||
|
||||
stock_item_id = (request.POST.get('stock_item_id') or '').strip()
|
||||
to_location_id = (request.POST.get('to_location_id') or '').strip()
|
||||
qty_raw = (request.POST.get('quantity') or '').strip().replace(',', '.')
|
||||
|
||||
next_url = (request.POST.get('next') or '').strip()
|
||||
if not next_url.startswith('/'):
|
||||
next_url = reverse_lazy('warehouse_stocks')
|
||||
|
||||
if not (stock_item_id.isdigit() and to_location_id.isdigit()):
|
||||
messages.error(request, 'Заполни корректно: позиция склада и склад назначения.')
|
||||
return redirect(next_url)
|
||||
|
||||
try:
|
||||
qty = float(qty_raw)
|
||||
except ValueError:
|
||||
qty = 0.0
|
||||
|
||||
if qty <= 0:
|
||||
messages.error(request, 'Количество должно быть больше 0.')
|
||||
return redirect(next_url)
|
||||
|
||||
si = get_object_or_404(StockItem.objects.select_related('location'), pk=int(stock_item_id))
|
||||
if int(to_location_id) == si.location_id:
|
||||
messages.error(request, 'Склад назначения должен отличаться от склада-источника.')
|
||||
return redirect(next_url)
|
||||
|
||||
tr = TransferRecord.objects.create(
|
||||
from_location_id=si.location_id,
|
||||
to_location_id=int(to_location_id),
|
||||
sender=request.user,
|
||||
receiver=request.user,
|
||||
occurred_at=timezone.now(),
|
||||
status='received',
|
||||
received_at=timezone.now(),
|
||||
is_applied=False,
|
||||
)
|
||||
TransferLine.objects.create(transfer=tr, stock_item=si, quantity=qty)
|
||||
|
||||
try:
|
||||
receive_transfer(tr.id, request.user.id)
|
||||
messages.success(request, 'Операция применена.')
|
||||
except Exception as e:
|
||||
messages.error(request, f'Ошибка: {e}')
|
||||
|
||||
return redirect(next_url)
|
||||
|
||||
|
||||
class WarehouseReceiptCreateView(LoginRequiredMixin, View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
profile = getattr(request.user, 'profile', None)
|
||||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||||
if role not in ['admin', 'technologist', 'master', 'clerk']:
|
||||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||||
|
||||
next_url = (request.POST.get('next') or '').strip()
|
||||
if not next_url.startswith('/'):
|
||||
next_url = reverse_lazy('warehouse_stocks')
|
||||
|
||||
kind = (request.POST.get('kind') or '').strip()
|
||||
location_id = (request.POST.get('location_id') or '').strip()
|
||||
deal_id = (request.POST.get('deal_id') or '').strip()
|
||||
quantity_raw = (request.POST.get('quantity') or '').strip().replace(',', '.')
|
||||
|
||||
if not location_id.isdigit():
|
||||
messages.error(request, 'Выбери склад.')
|
||||
return redirect(next_url)
|
||||
|
||||
try:
|
||||
qty = float(quantity_raw)
|
||||
except ValueError:
|
||||
qty = 0.0
|
||||
|
||||
if qty <= 0:
|
||||
messages.error(request, 'Количество должно быть больше 0.')
|
||||
return redirect(next_url)
|
||||
|
||||
if kind == 'raw':
|
||||
material_id = (request.POST.get('material_id') or '').strip()
|
||||
is_customer_supplied = bool(request.POST.get('is_customer_supplied'))
|
||||
|
||||
if not material_id.isdigit():
|
||||
messages.error(request, 'Выбери материал.')
|
||||
return redirect(next_url)
|
||||
|
||||
length_raw = (request.POST.get('current_length') or '').strip().replace(',', '.')
|
||||
width_raw = (request.POST.get('current_width') or '').strip().replace(',', '.')
|
||||
|
||||
current_length = None
|
||||
current_width = None
|
||||
|
||||
if length_raw:
|
||||
try:
|
||||
current_length = float(length_raw)
|
||||
except ValueError:
|
||||
current_length = None
|
||||
|
||||
if width_raw:
|
||||
try:
|
||||
current_width = float(width_raw)
|
||||
except ValueError:
|
||||
current_width = None
|
||||
|
||||
obj = StockItem(
|
||||
material_id=int(material_id),
|
||||
location_id=int(location_id),
|
||||
deal_id=(int(deal_id) if deal_id.isdigit() else None),
|
||||
quantity=float(qty),
|
||||
is_customer_supplied=is_customer_supplied,
|
||||
current_length=current_length,
|
||||
current_width=current_width,
|
||||
)
|
||||
|
||||
try:
|
||||
obj.full_clean()
|
||||
obj.save()
|
||||
messages.success(request, 'Приход сырья добавлен.')
|
||||
except Exception as e:
|
||||
messages.error(request, f'Ошибка прихода: {e}')
|
||||
|
||||
return redirect(next_url)
|
||||
|
||||
if kind == 'entity':
|
||||
entity_id = (request.POST.get('entity_id') or '').strip()
|
||||
if not entity_id.isdigit():
|
||||
messages.error(request, 'Выбери КД (изделие/деталь).')
|
||||
return redirect(next_url)
|
||||
|
||||
obj = StockItem(
|
||||
entity_id=int(entity_id),
|
||||
location_id=int(location_id),
|
||||
deal_id=(int(deal_id) if deal_id.isdigit() else None),
|
||||
quantity=float(qty),
|
||||
)
|
||||
|
||||
try:
|
||||
obj.full_clean()
|
||||
obj.save()
|
||||
messages.success(request, 'Приход изделия добавлен.')
|
||||
except Exception as e:
|
||||
messages.error(request, f'Ошибка прихода: {e}')
|
||||
|
||||
return redirect(next_url)
|
||||
|
||||
messages.error(request, 'Выбери тип прихода.')
|
||||
return redirect(next_url)
|
||||
|
||||
|
||||
class ClosingView(LoginRequiredMixin, TemplateView):
|
||||
template_name = 'shiftflow/closing.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
profile = getattr(request.user, 'profile', None)
|
||||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||||
if role not in ['admin', 'master', 'operator', 'observer']:
|
||||
return redirect('registry')
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
profile = getattr(self.request.user, 'profile', None)
|
||||
role = profile.role if profile else ('admin' if self.request.user.is_superuser else 'operator')
|
||||
ctx['user_role'] = role
|
||||
|
||||
if role == 'operator' and profile:
|
||||
machines = list(profile.machines.all().order_by('name'))
|
||||
else:
|
||||
machines = list(Machine.objects.all().order_by('name'))
|
||||
|
||||
ctx['machines'] = machines
|
||||
ctx['materials'] = list(Material.objects.select_related('category').order_by('full_name'))
|
||||
|
||||
machine_id = (self.request.GET.get('machine_id') or '').strip()
|
||||
material_id = (self.request.GET.get('material_id') or '').strip()
|
||||
|
||||
ctx['selected_machine_id'] = machine_id
|
||||
ctx['selected_material_id'] = material_id
|
||||
|
||||
items = []
|
||||
stock_items = []
|
||||
|
||||
if machine_id.isdigit() and material_id.isdigit():
|
||||
items = list(
|
||||
Item.objects.select_related('task', 'task__deal', 'task__material', 'machine')
|
||||
.filter(machine_id=int(machine_id), status='work', task__material_id=int(material_id))
|
||||
.order_by('date', 'task__deal__number', 'task__drawing_name')
|
||||
)
|
||||
|
||||
machine = Machine.objects.select_related('workshop', 'workshop__location', 'location').filter(pk=int(machine_id)).first()
|
||||
work_location_id = None
|
||||
if machine and getattr(machine, 'workshop_id', None) and getattr(machine.workshop, 'location_id', None):
|
||||
work_location_id = machine.workshop.location_id
|
||||
elif machine and getattr(machine, 'location_id', None):
|
||||
work_location_id = machine.location_id
|
||||
|
||||
if work_location_id:
|
||||
stock_items = list(
|
||||
StockItem.objects.select_related('location', 'material')
|
||||
.filter(location_id=work_location_id, material_id=int(material_id), entity__isnull=True)
|
||||
.order_by('created_at', 'id')
|
||||
)
|
||||
|
||||
ctx['items'] = items
|
||||
ctx['stock_items'] = stock_items
|
||||
ctx['can_edit'] = role in ['admin', 'master', 'operator']
|
||||
return ctx
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
profile = getattr(request.user, 'profile', None)
|
||||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||||
if role not in ['admin', 'master', 'operator']:
|
||||
return redirect('closing')
|
||||
|
||||
machine_id = (request.POST.get('machine_id') or '').strip()
|
||||
material_id = (request.POST.get('material_id') or '').strip()
|
||||
|
||||
if not (machine_id.isdigit() and material_id.isdigit()):
|
||||
messages.error(request, 'Выбери станок и материал.')
|
||||
return redirect('closing')
|
||||
|
||||
item_actions = {}
|
||||
for k, v in request.POST.items():
|
||||
if not k.startswith('close_action_'):
|
||||
continue
|
||||
item_id = k.replace('close_action_', '')
|
||||
if not item_id.isdigit():
|
||||
continue
|
||||
action = (v or '').strip()
|
||||
if action not in ['done', 'partial']:
|
||||
continue
|
||||
fact_raw = (request.POST.get(f'fact_{item_id}') or '').strip()
|
||||
try:
|
||||
fact = int(fact_raw)
|
||||
except ValueError:
|
||||
fact = 0
|
||||
item_actions[int(item_id)] = {'action': action, 'fact': fact}
|
||||
|
||||
consumptions = {}
|
||||
for k, v in request.POST.items():
|
||||
if not k.startswith('consume_'):
|
||||
continue
|
||||
sid = k.replace('consume_', '')
|
||||
if not sid.isdigit():
|
||||
continue
|
||||
raw = (v or '').strip().replace(',', '.')
|
||||
if not raw:
|
||||
continue
|
||||
try:
|
||||
qty = float(raw)
|
||||
except ValueError:
|
||||
qty = 0.0
|
||||
if qty > 0:
|
||||
consumptions[int(sid)] = qty
|
||||
|
||||
remnants = []
|
||||
idx = 0
|
||||
while True:
|
||||
has_any = (
|
||||
f'remnant_qty_{idx}' in request.POST
|
||||
or f'remnant_len_{idx}' in request.POST
|
||||
or f'remnant_wid_{idx}' in request.POST
|
||||
)
|
||||
if not has_any:
|
||||
break
|
||||
|
||||
qty_raw = (request.POST.get(f'remnant_qty_{idx}') or '').strip().replace(',', '.')
|
||||
len_raw = (request.POST.get(f'remnant_len_{idx}') or '').strip().replace(',', '.')
|
||||
wid_raw = (request.POST.get(f'remnant_wid_{idx}') or '').strip().replace(',', '.')
|
||||
|
||||
if qty_raw:
|
||||
try:
|
||||
rq = float(qty_raw)
|
||||
except ValueError:
|
||||
rq = 0.0
|
||||
|
||||
if rq > 0:
|
||||
rl = None
|
||||
rw = None
|
||||
|
||||
if len_raw:
|
||||
try:
|
||||
rl = float(len_raw)
|
||||
except ValueError:
|
||||
rl = None
|
||||
|
||||
if wid_raw:
|
||||
try:
|
||||
rw = float(wid_raw)
|
||||
except ValueError:
|
||||
rw = None
|
||||
|
||||
remnants.append({'quantity': rq, 'current_length': rl, 'current_width': rw})
|
||||
|
||||
idx += 1
|
||||
if idx > 200:
|
||||
break
|
||||
|
||||
if not item_actions:
|
||||
messages.error(request, 'Выбери хотя бы один пункт сменки и режим закрытия (полностью/частично).')
|
||||
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
||||
|
||||
if not consumptions:
|
||||
messages.error(request, 'Заполни списание: укажи, какие единицы на складе использованы и в каком количестве.')
|
||||
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
||||
|
||||
try:
|
||||
apply_closing(
|
||||
user_id=request.user.id,
|
||||
machine_id=int(machine_id),
|
||||
material_id=int(material_id),
|
||||
item_actions=item_actions,
|
||||
consumptions=consumptions,
|
||||
remnants=remnants,
|
||||
)
|
||||
messages.success(request, 'Закрытие выполнено.')
|
||||
except Exception as e:
|
||||
messages.error(request, f'Ошибка закрытия: {e}')
|
||||
|
||||
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
||||
Reference in New Issue
Block a user