Исправил закрытие сделки. добавил черновик страницы для списания в 1С
All checks were successful
Deploy MES Core / deploy (push) Successful in 13s
All checks were successful
Deploy MES Core / deploy (push) Successful in 13s
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from manufacturing.models import ProductEntity
|
from manufacturing.models import ProductEntity
|
||||||
|
|
||||||
@@ -76,7 +77,9 @@ def close_cutting_session(session_id: int) -> None:
|
|||||||
|
|
||||||
si.quantity = float(si.quantity) - need
|
si.quantity = float(si.quantity) - need
|
||||||
if si.quantity == 0:
|
if si.quantity == 0:
|
||||||
si.delete()
|
si.is_archived = True
|
||||||
|
si.archived_at = timezone.now()
|
||||||
|
si.save(update_fields=['quantity', 'is_archived', 'archived_at'])
|
||||||
else:
|
else:
|
||||||
si.save(update_fields=['quantity'])
|
si.save(update_fields=['quantity'])
|
||||||
|
|
||||||
@@ -126,7 +129,9 @@ def close_cutting_session(session_id: int) -> None:
|
|||||||
raise RuntimeError('Недостаточно сырья для списания.')
|
raise RuntimeError('Недостаточно сырья для списания.')
|
||||||
|
|
||||||
if used.quantity == 0:
|
if used.quantity == 0:
|
||||||
used.delete()
|
used.is_archived = True
|
||||||
|
used.archived_at = timezone.now()
|
||||||
|
used.save(update_fields=['quantity', 'is_archived', 'archived_at'])
|
||||||
else:
|
else:
|
||||||
used.save(update_fields=['quantity'])
|
used.save(update_fields=['quantity'])
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr class="table-custom-header">
|
<tr class="table-custom-header">
|
||||||
<th>Поступление</th>
|
<th>Поступление</th>
|
||||||
|
<th>Сделка</th>
|
||||||
<th>Единица</th>
|
<th>Единица</th>
|
||||||
<th>Доступно</th>
|
<th>Доступно</th>
|
||||||
<th data-sort="false">Использовано</th>
|
<th data-sort="false">Использовано</th>
|
||||||
@@ -98,6 +99,13 @@
|
|||||||
{% for s in stock_items %}
|
{% for s in stock_items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="small">{% if s.created_at %}{{ s.created_at|date:"d.m.Y H:i" }}{% endif %}</td>
|
<td class="small">{% if s.created_at %}{{ s.created_at|date:"d.m.Y H:i" }}{% endif %}</td>
|
||||||
|
<td>
|
||||||
|
{% if s.deal_id %}
|
||||||
|
<span class="text-accent fw-bold">{{ s.deal.number }}</span>
|
||||||
|
{% else %}
|
||||||
|
—
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ s }}</td>
|
<td>{{ s }}</td>
|
||||||
<td>{{ s.quantity }}</td>
|
<td>{{ s.quantity }}</td>
|
||||||
<td style="max-width:140px;">
|
<td style="max-width:140px;">
|
||||||
@@ -105,7 +113,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr><td colspan="4" class="text-center text-muted py-4">Нет единиц на складе для выбранного материала</td></tr>
|
<tr><td colspan="5" class="text-center text-muted py-4">Нет единиц на складе для выбранного материала</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -198,14 +198,6 @@
|
|||||||
<a href="{{ back_url }}" class="btn btn-outline-secondary">Назад</a>
|
<a href="{{ back_url }}" class="btn btn-outline-secondary">Назад</a>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<input type="hidden" name="action" id="actionField" value="save">
|
<input type="hidden" name="action" id="actionField" value="save">
|
||||||
{% if item.status == 'work' and user_role == 'admin' %}
|
|
||||||
<button type="submit" class="btn btn-success px-4" onclick="document.getElementById('actionField').value='close_done'">
|
|
||||||
<i class="bi bi-check-all me-2"></i>Выполнено
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-outline-warning px-4" onclick="document.getElementById('actionField').value='close_partial'">
|
|
||||||
Частично
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
<button type="submit" class="btn btn-outline-accent px-4 fw-bold" onclick="document.getElementById('actionField').value='save'">
|
<button type="submit" class="btn btn-outline-accent px-4 fw-bold" onclick="document.getElementById('actionField').value='save'">
|
||||||
<i class="bi bi-save me-2"></i>Сохранить
|
<i class="bi bi-save me-2"></i>Сохранить
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
161
shiftflow/templates/shiftflow/writeoffs.html
Normal file
161
shiftflow/templates/shiftflow/writeoffs.html
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="card border-secondary mb-3 shadow-sm">
|
||||||
|
<div class="card-body py-2">
|
||||||
|
<form method="get" class="row g-2 align-items-end">
|
||||||
|
<div class="col-md-auto">
|
||||||
|
<label class="small text-muted mb-1 fw-bold">Период (с):</label>
|
||||||
|
<input type="date" name="start_date" class="form-control form-control-sm bg-body text-body border-secondary" value="{{ start_date }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-auto">
|
||||||
|
<label class="small text-muted mb-1 fw-bold">Период (по):</label>
|
||||||
|
<input type="date" name="end_date" class="form-control form-control-sm bg-body text-body border-secondary" value="{{ end_date }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-auto">
|
||||||
|
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||||
|
<i class="bi bi-funnel me-1"></i>Показать
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-auto">
|
||||||
|
<a href="{% url 'writeoffs' %}?reset=1" class="btn btn-outline-secondary btn-sm">
|
||||||
|
<i class="bi bi-arrow-counterclockwise me-1"></i>Сброс
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow border-secondary mb-3">
|
||||||
|
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<h3 class="text-accent mb-0"><i class="bi bi-journal-text me-2"></i>Списание / Производство</h3>
|
||||||
|
<div class="small text-muted">По производственным отчетам</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
{% for card in report_cards %}
|
||||||
|
<div class="border border-secondary rounded p-3 mb-3">
|
||||||
|
<div class="d-flex flex-wrap justify-content-between gap-2">
|
||||||
|
<div class="fw-bold">
|
||||||
|
{{ card.report.date|date:"d.m.Y" }} — {{ card.report.machine }} — {{ card.report.operator }}
|
||||||
|
<span class="text-muted small ms-2">#{{ card.report.id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 mt-1">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="small text-muted fw-bold mb-1">Списано</div>
|
||||||
|
{% if card.consumed %}
|
||||||
|
<ul class="mb-0">
|
||||||
|
{% for k,v in card.consumed.items %}
|
||||||
|
<li>{{ k }}: {{ v }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-muted small">—</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="small text-muted fw-bold mb-1">Произведено</div>
|
||||||
|
{% if card.produced %}
|
||||||
|
<ul class="mb-0">
|
||||||
|
{% for k,v in card.produced.items %}
|
||||||
|
<li>{{ k }}: {{ v }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-muted small">—</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="small text-muted fw-bold mb-1">ДО</div>
|
||||||
|
{% if card.remnants %}
|
||||||
|
<ul class="mb-0">
|
||||||
|
{% for k,v in card.remnants.items %}
|
||||||
|
<li>{{ k }}: {{ v }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-muted small">—</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="text-muted">За выбранный период отчётов нет.</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow border-secondary">
|
||||||
|
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<h3 class="text-accent mb-0"><i class="bi bi-check2-square me-2"></i>Сменные задания (1С)</h3>
|
||||||
|
<div class="small text-muted">Отметка «Списано в 1С»</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="mb-0">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="start_date" value="{{ start_date }}">
|
||||||
|
<input type="hidden" name="end_date" value="{{ end_date }}">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0 align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr class="table-custom-header">
|
||||||
|
<th data-sort="false"></th>
|
||||||
|
<th>Дата</th>
|
||||||
|
<th>Сделка</th>
|
||||||
|
<th>Станок</th>
|
||||||
|
<th>Деталь</th>
|
||||||
|
<th>Статус</th>
|
||||||
|
<th>Факт</th>
|
||||||
|
<th>1С</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for it in items %}
|
||||||
|
<tr>
|
||||||
|
<td style="width:40px;">
|
||||||
|
{% if can_edit and not it.is_synced_1c %}
|
||||||
|
<input class="form-check-input" type="checkbox" name="item_ids" value="{{ it.id }}">
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="small">{{ it.date|date:"d.m.Y" }}</td>
|
||||||
|
<td>
|
||||||
|
{% if it.task.deal_id %}
|
||||||
|
<span class="text-accent fw-bold">{{ it.task.deal.number }}</span>
|
||||||
|
{% else %}
|
||||||
|
—
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ it.machine }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'item_detail' it.id %}" class="text-decoration-none">{{ it.task.drawing_name }}</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ it.get_status_display }}</td>
|
||||||
|
<td>{{ it.quantity_fact }}</td>
|
||||||
|
<td>
|
||||||
|
{% if it.is_synced_1c %}
|
||||||
|
<span class="badge bg-success">Да</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">Нет</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr><td colspan="8" class="text-center text-muted py-4">Нет закрытых заданий за период</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer border-secondary d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-outline-accent" {% if not can_edit %}disabled{% endif %}>
|
||||||
|
Отметить выбранные как «Списано в 1С»
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -21,6 +21,7 @@ from .views import (
|
|||||||
SteelGradeUpsertView,
|
SteelGradeUpsertView,
|
||||||
TaskItemsView,
|
TaskItemsView,
|
||||||
ClosingView,
|
ClosingView,
|
||||||
|
WriteOffsView,
|
||||||
WarehouseReceiptCreateView,
|
WarehouseReceiptCreateView,
|
||||||
WarehouseStocksView,
|
WarehouseStocksView,
|
||||||
WarehouseTransferCreateView,
|
WarehouseTransferCreateView,
|
||||||
@@ -58,4 +59,5 @@ urlpatterns = [
|
|||||||
path('warehouse/receipt/', WarehouseReceiptCreateView.as_view(), name='warehouse_receipt'),
|
path('warehouse/receipt/', WarehouseReceiptCreateView.as_view(), name='warehouse_receipt'),
|
||||||
|
|
||||||
path('closing/', ClosingView.as_view(), name='closing'),
|
path('closing/', ClosingView.as_view(), name='closing'),
|
||||||
|
path('writeoffs/', WriteOffsView.as_view(), name='writeoffs'),
|
||||||
]
|
]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -32,7 +32,21 @@ from warehouse.services.transfers import receive_transfer
|
|||||||
from shiftflow.services.closing import apply_closing
|
from shiftflow.services.closing import apply_closing
|
||||||
|
|
||||||
from .forms import ProductionTaskCreateForm
|
from .forms import ProductionTaskCreateForm
|
||||||
from .models import Company, Deal, DxfPreviewJob, DxfPreviewSettings, EmployeeProfile, Item, Machine, ProductionTask
|
from .models import (
|
||||||
|
Company,
|
||||||
|
CuttingSession,
|
||||||
|
Deal,
|
||||||
|
DxfPreviewJob,
|
||||||
|
DxfPreviewSettings,
|
||||||
|
EmployeeProfile,
|
||||||
|
Item,
|
||||||
|
Machine,
|
||||||
|
ProductionReportConsumption,
|
||||||
|
ProductionReportRemnant,
|
||||||
|
ProductionReportStockResult,
|
||||||
|
ProductionTask,
|
||||||
|
ShiftItem,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_dxf_preview_settings() -> DxfPreviewSettings:
|
def _get_dxf_preview_settings() -> DxfPreviewSettings:
|
||||||
@@ -1427,7 +1441,7 @@ class WarehouseStocksView(LoginRequiredMixin, TemplateView):
|
|||||||
ctx['start_date'] = start_date
|
ctx['start_date'] = start_date
|
||||||
ctx['end_date'] = end_date
|
ctx['end_date'] = end_date
|
||||||
|
|
||||||
qs = StockItem.objects.select_related('location', 'material', 'material__category', 'entity', 'deal').all()
|
qs = StockItem.objects.select_related('location', 'material', 'material__category', 'entity', 'deal').filter(is_archived=False)
|
||||||
if ship_loc_id:
|
if ship_loc_id:
|
||||||
qs = qs.exclude(location_id=ship_loc_id)
|
qs = qs.exclude(location_id=ship_loc_id)
|
||||||
|
|
||||||
@@ -1681,7 +1695,8 @@ class ClosingView(LoginRequiredMixin, TemplateView):
|
|||||||
if work_location_id:
|
if work_location_id:
|
||||||
stock_items = list(
|
stock_items = list(
|
||||||
StockItem.objects.select_related('location', 'material')
|
StockItem.objects.select_related('location', 'material')
|
||||||
.filter(location_id=work_location_id, material_id=int(material_id), entity__isnull=True)
|
.filter(location_id=work_location_id, material_id=int(material_id), entity__isnull=True, is_archived=False)
|
||||||
|
.filter(quantity__gt=0)
|
||||||
.order_by('created_at', 'id')
|
.order_by('created_at', 'id')
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1784,6 +1799,116 @@ class ClosingView(LoginRequiredMixin, TemplateView):
|
|||||||
messages.error(request, 'Выбери хотя бы один пункт сменки и режим закрытия (полностью/частично).')
|
messages.error(request, 'Выбери хотя бы один пункт сменки и режим закрытия (полностью/частично).')
|
||||||
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
||||||
|
|
||||||
|
|
||||||
|
class WriteOffsView(LoginRequiredMixin, TemplateView):
|
||||||
|
template_name = 'shiftflow/writeoffs.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', '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
|
||||||
|
ctx['can_edit'] = role in ['admin', 'clerk']
|
||||||
|
|
||||||
|
start_date = (self.request.GET.get('start_date') or '').strip()
|
||||||
|
end_date = (self.request.GET.get('end_date') or '').strip()
|
||||||
|
reset = self.request.GET.get('reset')
|
||||||
|
|
||||||
|
if not start_date or not end_date or reset:
|
||||||
|
today = timezone.localdate()
|
||||||
|
start = today - timedelta(days=21)
|
||||||
|
start_date = start.strftime('%Y-%m-%d')
|
||||||
|
end_date = today.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
ctx['start_date'] = start_date
|
||||||
|
ctx['end_date'] = end_date
|
||||||
|
|
||||||
|
reports_qs = (
|
||||||
|
CuttingSession.objects.select_related('machine', 'operator')
|
||||||
|
.filter(is_closed=True, date__gte=start_date, date__lte=end_date)
|
||||||
|
.order_by('-date', '-id')
|
||||||
|
)
|
||||||
|
|
||||||
|
reports = list(
|
||||||
|
reports_qs.prefetch_related(
|
||||||
|
'tasks__task__deal',
|
||||||
|
'tasks__task__material',
|
||||||
|
'consumptions__material',
|
||||||
|
'consumptions__stock_item__material',
|
||||||
|
'results__stock_item__material',
|
||||||
|
'results__stock_item__entity',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
report_cards = []
|
||||||
|
for r in reports:
|
||||||
|
consumed = {}
|
||||||
|
for c in list(getattr(r, 'consumptions', []).all() if hasattr(getattr(r, 'consumptions', None), 'all') else []):
|
||||||
|
mat = None
|
||||||
|
if getattr(c, 'material_id', None):
|
||||||
|
mat = c.material
|
||||||
|
elif getattr(c, 'stock_item_id', None) and getattr(c.stock_item, 'material_id', None):
|
||||||
|
mat = c.stock_item.material
|
||||||
|
|
||||||
|
label = str(mat) if mat else '—'
|
||||||
|
key = getattr(mat, 'id', None) or label
|
||||||
|
consumed[key] = consumed.get(key, 0.0) + float(c.quantity)
|
||||||
|
|
||||||
|
produced = {}
|
||||||
|
remnants = {}
|
||||||
|
for res in list(getattr(r, 'results', []).all() if hasattr(getattr(r, 'results', None), 'all') else []):
|
||||||
|
si = res.stock_item
|
||||||
|
if res.kind == 'finished':
|
||||||
|
label = str(getattr(si, 'entity', None) or '—')
|
||||||
|
produced[label] = produced.get(label, 0.0) + float(si.quantity)
|
||||||
|
elif res.kind == 'remnant':
|
||||||
|
label = str(getattr(si, 'material', None) or '—')
|
||||||
|
remnants[label] = remnants.get(label, 0.0) + float(si.quantity)
|
||||||
|
|
||||||
|
report_cards.append({
|
||||||
|
'report': r,
|
||||||
|
'consumed': consumed,
|
||||||
|
'produced': produced,
|
||||||
|
'remnants': remnants,
|
||||||
|
'tasks': list(getattr(r, 'tasks', []).all() if hasattr(getattr(r, 'tasks', None), 'all') else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx['report_cards'] = report_cards
|
||||||
|
|
||||||
|
items_qs = (
|
||||||
|
Item.objects.select_related('task', 'task__deal', 'machine')
|
||||||
|
.filter(status__in=['done', 'partial'], date__gte=start_date, date__lte=end_date)
|
||||||
|
.order_by('-date', '-id')
|
||||||
|
)
|
||||||
|
ctx['items'] = list(items_qs)
|
||||||
|
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', 'clerk']:
|
||||||
|
return redirect('writeoffs')
|
||||||
|
|
||||||
|
ids = request.POST.getlist('item_ids')
|
||||||
|
item_ids = [int(x) for x in ids if x.isdigit()]
|
||||||
|
if not item_ids:
|
||||||
|
messages.error(request, 'Не выбрано ни одного сменного задания.')
|
||||||
|
return redirect('writeoffs')
|
||||||
|
|
||||||
|
Item.objects.filter(id__in=item_ids).update(is_synced_1c=True)
|
||||||
|
messages.success(request, f'Отмечено в 1С: {len(item_ids)}.')
|
||||||
|
start_date = (request.POST.get('start_date') or '').strip()
|
||||||
|
end_date = (request.POST.get('end_date') or '').strip()
|
||||||
|
return redirect(f"{reverse_lazy('writeoffs')}?start_date={start_date}&end_date={end_date}")
|
||||||
|
|
||||||
if not consumptions:
|
if not consumptions:
|
||||||
messages.error(request, 'Заполни списание: укажи, какие единицы на складе использованы и в каком количестве.')
|
messages.error(request, 'Заполни списание: укажи, какие единицы на складе использованы и в каком количестве.')
|
||||||
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user_role in 'admin,clerk,observer' %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.resolver_match.url_name == 'writeoffs' %}active{% endif %}" href="{% url 'writeoffs' %}">Списание</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if user_role == 'admin' %}
|
{% if user_role == 'admin' %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 6.0.3 on 2026-04-06 17:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('warehouse', '0012_stockitem_deal'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='stockitem',
|
||||||
|
name='archived_at',
|
||||||
|
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата архивации'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='stockitem',
|
||||||
|
name='is_archived',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='В архиве'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -95,7 +95,6 @@ class StockItem(models.Model):
|
|||||||
location = models.ForeignKey(Location, on_delete=models.PROTECT, verbose_name="Где находится")
|
location = models.ForeignKey(Location, on_delete=models.PROTECT, verbose_name="Где находится")
|
||||||
quantity = models.FloatField("Количество (шт/м/кг/лист)")
|
quantity = models.FloatField("Количество (шт/м/кг/лист)")
|
||||||
created_at = models.DateTimeField("Поступление", default=timezone.now, editable=False)
|
created_at = models.DateTimeField("Поступление", default=timezone.now, editable=False)
|
||||||
created_at = models.DateTimeField("Поступление", default=timezone.now, editable=False)
|
|
||||||
|
|
||||||
is_remnant = models.BooleanField("Деловой остаток", default=False)
|
is_remnant = models.BooleanField("Деловой остаток", default=False)
|
||||||
is_customer_supplied = models.BooleanField('Давальческий', default=False)
|
is_customer_supplied = models.BooleanField('Давальческий', default=False)
|
||||||
@@ -103,6 +102,9 @@ class StockItem(models.Model):
|
|||||||
current_width = models.FloatField("Текущая ширина, мм", null=True, blank=True)
|
current_width = models.FloatField("Текущая ширина, мм", null=True, blank=True)
|
||||||
unique_id = models.CharField("ID/Маркировка (для ДО)", max_length=50, unique=True, null=True, blank=True)
|
unique_id = models.CharField("ID/Маркировка (для ДО)", max_length=50, unique=True, null=True, blank=True)
|
||||||
|
|
||||||
|
is_archived = models.BooleanField('В архиве', default=False)
|
||||||
|
archived_at = models.DateTimeField('Дата архивации', null=True, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Единица на складе"
|
verbose_name = "Единица на складе"
|
||||||
verbose_name_plural = "Единицы на складе"
|
verbose_name_plural = "Единицы на складе"
|
||||||
|
|||||||
Reference in New Issue
Block a user