добавил детальный вид итема, пока недопиленый
All checks were successful
Deploy MES Core / deploy (push) Successful in 8s
All checks were successful
Deploy MES Core / deploy (push) Successful in 8s
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -61,6 +61,8 @@ cover/
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
# Media
|
||||
media/
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
|
||||
120
shiftflow/templates/shiftflow/item_detail copy.html
Normal file
120
shiftflow/templates/shiftflow/item_detail copy.html
Normal file
@@ -0,0 +1,120 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}ShiftFlow | {{ item.drawing_name|default:"Б/ч" }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-xl-8">
|
||||
|
||||
<div class="card shadow bg-dark text-light border-secondary mb-4">
|
||||
|
||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center py-3">
|
||||
<h3 class="text-accent mb-0">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Деталь: {{ item.drawing_name|default:"Без чертежа" }}
|
||||
</h3>
|
||||
<span class="badge bg-secondary opacity-75 fw-normal">ID: {{ item.id }}</span>
|
||||
</div>
|
||||
|
||||
<form method="post" class="card-body p-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-3 mb-4 border-bottom border-secondary pb-4">
|
||||
<div class="col-md-4 col-6">
|
||||
<label class="small text-muted">Дата задания:</label>
|
||||
<div class="fw-bold">{{ item.date|date:"d.m.Y" }}</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-6">
|
||||
<label class="small text-muted">Станок:</label>
|
||||
<div class="fw-bold"><i class="bi bi-cpu me-1"></i>{{ item.machine.name }}</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-12">
|
||||
<label class="small text-muted">Сделка/Заказ:</label>
|
||||
<div class="fw-bold text-accent fs-5">№ {{ item.deal.number }}</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="small text-muted">Материал / Габариты:</label>
|
||||
<div class="fw-bold small">{{ item.material.name }} (s{{ item.size_value|default:"-" }})</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5 class="text-accent mb-3"><i class="bi bi-files me-2"></i>Файлы задания</h5>
|
||||
<div class="d-flex gap-2">
|
||||
{% if item.drawing_file %}
|
||||
<a href="{{ item.drawing_file.url }}" target="_blank" class="btn btn-outline-info">
|
||||
<i class="bi bi-file-earmark-code me-2"></i>Открыть DXF/STEP
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary p-1" disabled><i class="bi bi-file-earmark-code me-1"></i>DXF: нет файла</button>
|
||||
{% endif %}
|
||||
|
||||
{% if item.extra_drawing %}
|
||||
<a href="{{ item.extra_drawing.url }}" target="_blank" class="btn btn-outline-danger">
|
||||
<i class="bi bi-file-pdf me-2"></i>Открыть Чертеж PDF
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary p-1" disabled><i class="bi bi-file-pdf me-1"></i>PDF: нет файла</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5 class="text-accent mb-3"><i class="bi bi-vector-pen me-2"></i>Фактическое исполнение</h5>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4 col-6">
|
||||
<label class="form-label small text-muted">Заказано штук (План):</label>
|
||||
<input type="number" class="form-control form-control-lg fw-bold bg-secondary text-info" value="{{ item.quantity_plan }}" readonly disabled>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-6">
|
||||
<label for="id_quantity_fact" class="form-label small text-muted">Изготовлено штук (Факт):</label>
|
||||
<input type="number" name="quantity_fact" id="id_quantity_fact" class="form-control form-control-lg bg-dark text-light border-secondary" value="{{ item.quantity_fact }}" min="0" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-12 d-flex align-items-end">
|
||||
<div class="form-check form-switch bg-dark border border-secondary p-3 rounded shadow-sm w-100 h-100 d-flex align-items-center justify-content-between">
|
||||
<label class="form-check-label ms-1 text-light small" for="id_is_synced_1c">Списано в 1С?</label>
|
||||
<input class="form-check-input form-switch-lg stop-prop" type="checkbox" name="is_synced_1c" id="id_is_synced_1c" {% if item.is_synced_1c %}checked{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-5">
|
||||
<label for="id_status" class="form-label small text-muted">Текущий статус задания:</label>
|
||||
<select name="status" id="id_status" class="form-select bg-dark text-light border-secondary form-select-lg">
|
||||
{% for choice_val, choice_label in form.fields.status.choices %}
|
||||
<option value="{{ choice_val }}" {% if item.status == choice_val %}selected{% endif %}>
|
||||
{{ choice_label }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="card-footer border-secondary bg-dark p-0 pt-4 d-flex justify-content-between">
|
||||
<a href="{% url 'registry' %}" class="btn btn-outline-secondary btn-lg px-4">
|
||||
<i class="bi bi-arrow-left me-2"></i>В реестр
|
||||
</a>
|
||||
<button type="submit" class="btn btn-outline-accent btn-lg px-5 fw-bold">
|
||||
<i class="bi bi-save me-2"></i>Сохранить изменения
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger mt-4 small mb-0 p-3 shadow-sm border-danger">
|
||||
<h6 class="fw-bold mb-2">Обнаружены ошибки:</h6>
|
||||
<ul class="mb-0">
|
||||
{% for field, errors in form.errors.items %}
|
||||
{% for error in errors %}
|
||||
<li>{{ field|upper }}: {{ error }}</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
126
shiftflow/templates/shiftflow/item_detail.html
Normal file
126
shiftflow/templates/shiftflow/item_detail.html
Normal file
@@ -0,0 +1,126 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-xl-8">
|
||||
<div class="card shadow-sm border-secondary mb-4">
|
||||
|
||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center py-3">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-info-circle me-2"></i>{{ item.drawing_name|default:"Без названия" }}</h3>
|
||||
<span class="badge bg-secondary">Сделка № {{ item.deal.number }}</span>
|
||||
</div>
|
||||
|
||||
<form method="post" id="mainForm" class="card-body p-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-3 mb-4 border-bottom border-secondary pb-3 text-body">
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Станок</small>
|
||||
<strong>{{ item.machine.name }}</strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Материал</small>
|
||||
<strong>{{ item.material.name }}</strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">План</small>
|
||||
<strong class="text-info fs-5">{{ item.quantity_plan }} шт.</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 d-flex gap-2">
|
||||
{% if item.drawing_file %}<a href="{{ item.drawing_file.url }}" target="_blank" class="btn btn-outline-info btn-sm">DXF</a>{% endif %}
|
||||
{% if item.extra_drawing %}<a href="{{ item.extra_drawing.url }}" target="_blank" class="btn btn-outline-danger btn-sm">PDF</a>{% endif %}
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="status" id="id_status" value="{{ item.status }}">
|
||||
|
||||
{% if user_role in 'operator,master' %}
|
||||
{% if item.status == 'work' %}
|
||||
<div class="bg-body-tertiary p-3 rounded border mb-4 text-center">
|
||||
<h5 class="mb-3">Закрыть задание:</h5>
|
||||
<div class="btn-group btn-group-lg w-100">
|
||||
<button type="button" class="btn btn-success" onclick="closeTask('done')">
|
||||
<i class="bi bi-check-all"></i> Выполнено
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning" onclick="showPartial()">
|
||||
Частично
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="partialInput" class="mt-3 d-none">
|
||||
<label class="small text-muted">Сколько сделано?</label>
|
||||
<input type="number" name="quantity_fact" id="id_quantity_fact" class="form-control form-control-lg text-center mx-auto" style="max-width: 200px;" value="{{ item.quantity_fact }}" max="{{ item.quantity_plan }}">
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-success">Статус: {{ item.get_status_display }}. Сделано: {{ item.quantity_fact }} шт.</div>
|
||||
<input type="hidden" name="quantity_fact" value="{{ item.quantity_fact }}">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if user_role == 'admin' %}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="small text-muted">Статус задания (Админ)</label>
|
||||
<select name="status" class="form-select border-secondary">
|
||||
{% for val, name in form.fields.status.choices %}
|
||||
<option value="{{ val }}" {% if item.status == val %}selected{% endif %}>{{ name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="small text-muted">Факт (шт)</label>
|
||||
<input type="number" name="quantity_fact" class="form-control border-secondary" value="{{ item.quantity_fact }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if user_role in 'admin,clerk' %}
|
||||
{% if item.status == 'done' or item.quantity_fact > 0 %}
|
||||
<div class="form-check form-switch p-3 rounded border border-warning mb-4 bg-body-tertiary d-flex justify-content-between align-items-center">
|
||||
<label class="form-check-label fw-bold ms-2" for="sync1c">Списано в 1С</label>
|
||||
<input class="form-check-input ms-0" style="width: 3em; height: 1.5em;" type="checkbox" name="is_synced_1c" id="sync1c" {% if item.is_synced_1c %}checked{% endif %}>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-muted small mb-4"><i class="bi bi-info-circle me-1"></i>Списание будет доступно после выполнения.</div>
|
||||
{% endif %}
|
||||
{% if user_role == 'clerk' %}<input type="hidden" name="quantity_fact" value="{{ item.quantity_fact }}">{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="{% url 'registry' %}" class="btn btn-outline-secondary">Назад</a>
|
||||
<button type="submit" class="btn btn-outline-accent px-5 fw-bold">
|
||||
<i class="bi bi-save me-2"></i>
|
||||
{% if user_role in 'operator,master' %}Закрыть задание{% else %}Сохранить{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function closeTask(status) {
|
||||
document.getElementById('id_status').value = status;
|
||||
// Если "Выполнено", автоматом ставим факт = плану
|
||||
if(status === 'done') {
|
||||
const factInput = document.getElementById('id_quantity_fact');
|
||||
if(factInput) factInput.value = "{{ item.quantity_plan }}";
|
||||
else {
|
||||
let hiddenFact = document.createElement('input');
|
||||
hiddenFact.type = 'hidden';
|
||||
hiddenFact.name = 'quantity_fact';
|
||||
hiddenFact.value = "{{ item.quantity_plan }}";
|
||||
document.getElementById('mainForm').appendChild(hiddenFact);
|
||||
}
|
||||
}
|
||||
document.getElementById('mainForm').submit();
|
||||
}
|
||||
|
||||
function showPartial() {
|
||||
document.getElementById('partialInput').classList.remove('d-none');
|
||||
document.getElementById('id_status').value = 'part'; // Статус Частично
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,65 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'shiftflow/partials/_filter.html' %}
|
||||
|
||||
{% if not selected_machines %}
|
||||
<div class="alert alert-warning text-center mt-4">
|
||||
<i class="bi bi-info-circle me-2"></i> Станки не выбраны. Выберите станки в фильтре для отображения позиций.
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
{% if in_work is not None %}
|
||||
<h6 class="text-primary mt-4 fw-bold"><i class="bi bi-play-circle me-2"></i>В РАБОТЕ</h6>
|
||||
<div class="table-responsive rounded border shadow-sm mb-4">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-custom-header small fw-bold text-uppercase">
|
||||
<tr>
|
||||
<th style="width: 80px;">Сделка</th>
|
||||
<th>Деталь / Чертеж</th>
|
||||
<th>Материал</th>
|
||||
<th class="text-center">План</th>
|
||||
<th class="text-center">Факт</th>
|
||||
{% if user_role in 'admin,technologist,master,operator' %}
|
||||
<th class="text-end">Действия</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in in_work %}
|
||||
<tr>
|
||||
<td class="small">{{ item.deal.number }}</td>
|
||||
<td><strong>{{ item.drawing_name }}</strong> <small class="text-muted">({{ item.machine.name }})</small></td>
|
||||
<td><span class="badge bg-secondary opacity-75">{{ item.material.name }}</span></td>
|
||||
<td class="text-center fw-bold">{{ item.quantity_plan }}</td>
|
||||
<td class="text-center" style="width: 100px;">
|
||||
<input type="number" class="form-control form-control-sm text-center" value="{{ item.quantity_fact|default:0 }}" {% if user_role == 'clerk' %}readonly{% endif %}>
|
||||
</td>
|
||||
{% if user_role in 'admin,technologist,master,operator' %}
|
||||
<td class="text-end">
|
||||
<button class="btn btn-sm btn-outline-success"><i class="bi bi-check2-all"></i></button>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center py-4 text-muted small">Активных заданий нет</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if backlog is not None %}
|
||||
<h6 class="text-danger fw-bold"><i class="bi bi-exclamation-triangle me-2"></i>НЕДОДЕЛЫ (ХВОСТЫ)</h6>
|
||||
<div class="table-responsive rounded border shadow-sm mb-4">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if done_items is not None %}
|
||||
<h6 class="text-success fw-bold"><i class="bi bi-check-circle me-2"></i>ЗАВЕРШЕНО</h6>
|
||||
<div class="table-responsive rounded border shadow-sm">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,38 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow bg-dark text-light border-secondary">
|
||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0">Реестр деталей</h3>
|
||||
{% if user_role == 'admin' or user_role == 'technologist' %}
|
||||
<button class="btn btn-sm btn-outline-accent">+ Добавить заказ</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-dark table-hover">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>ID</th>
|
||||
<th>Наименование</th>
|
||||
<th>Кол-во</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td><span class="badge bg-secondary">{{ item.status }}</span></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted">Деталей пока нет</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex-center-center">
|
||||
<div class="text-center">
|
||||
<h1 class="text-accent mb-4 display-3 fw-bold">
|
||||
<i class="bi bi-gear-fill me-3"></i>ShiftFlow
|
||||
@@ -9,4 +10,5 @@
|
||||
ВОЙТИ В СИСТЕМУ
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
86
shiftflow/templates/shiftflow/registry.html
Normal file
86
shiftflow/templates/shiftflow/registry.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary">
|
||||
<div class="card-header border-secondary py-3">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-list-task me-2"></i>Реестр заданий</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Дата</th>
|
||||
<th>Сделка</th>
|
||||
<th>Станок</th>
|
||||
<th>Наименование</th>
|
||||
<th>Габариты</th>
|
||||
<th>План / Факт</th>
|
||||
<th>Материал</th>
|
||||
<th class="text-center">Файлы</th>
|
||||
<th class="text-center">1С</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr class="clickable-row" data-href="{% url 'item_detail' item.pk %}">
|
||||
<td class="small">{{ item.date|date:"d.m.y" }}</td>
|
||||
<td><span class="text-accent fw-bold">{{ item.deal.number }}</span></td>
|
||||
<td><span class="badge bg-dark border border-secondary">{{ item.machine.name }}</span></td>
|
||||
<td class="fw-bold">{{ item.drawing_name }}</td>
|
||||
<td class="small">{{ item.size_value }}</td>
|
||||
<td>
|
||||
<span class="text-info fw-bold">{{ item.quantity_plan }}</span> /
|
||||
<span class="text-success">{{ item.quantity_fact }}</span>
|
||||
</td>
|
||||
<td class="small text-muted">{{ item.material.name }}</td>
|
||||
<td class="text-center">
|
||||
{% if item.drawing_file %}
|
||||
<a href="{{ item.drawing_file.url }}" target="_blank" class="btn btn-sm btn-outline-info p-1 stop-prop" title="DXF/STEP">
|
||||
<i class="bi bi-file-earmark-code"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if item.extra_drawing %}
|
||||
<a href="{{ item.extra_drawing.url }}" target="_blank" class="btn btn-sm btn-outline-danger p-1 stop-prop" title="Чертеж PDF">
|
||||
<i class="bi bi-file-pdf"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if item.is_synced_1c %}
|
||||
<i class="bi bi-check-circle-fill text-success" title="Учтено"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-clock-history text-muted" title="Ожидает"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {% if item.status == 'work' %}bg-primary{% elif item.status == 'done' %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{{ item.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="10" class="text-center p-5 text-muted">Заданий не найдено</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const rows = document.querySelectorAll(".clickable-row");
|
||||
rows.forEach(row => {
|
||||
row.addEventListener("click", function(e) {
|
||||
// Если нажали на ссылку файла (класс stop-prop), не переходим на страницу деталей
|
||||
if (e.target.closest('.stop-prop')) return;
|
||||
// Иначе переходим по ссылке из data-href
|
||||
window.location.href = this.dataset.href;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.urls import path
|
||||
from .views import IndexView, RegistryView
|
||||
from .views import IndexView, ItemUpdateView, RegistryView
|
||||
|
||||
urlpatterns = [
|
||||
# Главная страница (путь пустой)
|
||||
@@ -7,4 +7,5 @@ urlpatterns = [
|
||||
|
||||
# Реестр
|
||||
path('registry/', RegistryView.as_view(), name='registry'),
|
||||
path('item/<int:pk>/', ItemUpdateView.as_view(), name='item_detail'),
|
||||
]
|
||||
@@ -1,72 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
from .models import Item, Machine
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
|
||||
def items_list_view(request):
|
||||
# Если не авторизован, просто отдаем пустой контекст для страницы входа
|
||||
if not request.user.is_authenticated:
|
||||
return render(request, 'shiftflow/items_list.html', {})
|
||||
|
||||
# ОПРЕДЕЛЕНИЕ РОЛИ (Ищем в группах Джанго или ставим суперюзера)
|
||||
user_role = 'guest'
|
||||
if request.user.is_superuser:
|
||||
user_role = 'admin'
|
||||
elif request.user.groups.filter(name='Технолог').exists():
|
||||
user_role = 'technologist'
|
||||
elif request.user.groups.filter(name='Мастер').exists():
|
||||
user_role = 'master'
|
||||
elif request.user.groups.filter(name='Оператор').exists():
|
||||
user_role = 'operator'
|
||||
elif request.user.groups.filter(name='Учетчик').exists():
|
||||
user_role = 'clerk'
|
||||
|
||||
# ФИЛЬТРЫ
|
||||
all_machines = Machine.objects.all()
|
||||
all_machine_ids = list(all_machines.values_list('id', flat=True))
|
||||
|
||||
# Проверяем, был ли нажат фильтр (есть ли параметр 'filtered' в URL)
|
||||
is_filtered = 'filtered' in request.GET
|
||||
|
||||
if is_filtered:
|
||||
selected_machines = [int(x) for x in request.GET.getlist('m_ids')]
|
||||
selected_statuses = request.GET.getlist('statuses')
|
||||
start_date = request.GET.get('start_date')
|
||||
end_date = request.GET.get('end_date')
|
||||
else:
|
||||
# Значения по умолчанию (при первом заходе или сбросе)
|
||||
selected_machines = all_machine_ids
|
||||
selected_statuses = ['work', 'partial'] # В работе и недоделы
|
||||
start_date = timezone.now().strftime('%Y-%m-%d')
|
||||
end_date = timezone.now().strftime('%Y-%m-%d')
|
||||
|
||||
# ВЫБОРКА ДАННЫХ
|
||||
items = Item.objects.select_related('deal', 'material', 'machine')
|
||||
|
||||
# Защита логики: если станки не выбраны — список пуст
|
||||
if not selected_machines:
|
||||
items = Item.objects.none()
|
||||
else:
|
||||
items = items.filter(
|
||||
machine_id__in=selected_machines,
|
||||
status__in=selected_statuses,
|
||||
date__range=[start_date, end_date]
|
||||
)
|
||||
|
||||
# Разбиваем по статусам для вывода в разные таблицы (если они выбраны в фильтре)
|
||||
in_work = items.filter(status='work') if 'work' in selected_statuses else None
|
||||
backlog = items.filter(status='partial') if 'partial' in selected_statuses else None
|
||||
done_items = items.filter(status='done') if 'done' in selected_statuses else None
|
||||
|
||||
context = {
|
||||
'user_role': user_role,
|
||||
'machines': all_machines,
|
||||
'selected_machines': selected_machines,
|
||||
'selected_statuses': selected_statuses,
|
||||
'start_date': start_date,
|
||||
'end_date': end_date,
|
||||
'in_work': in_work,
|
||||
'backlog': backlog,
|
||||
'done_items': done_items,
|
||||
}
|
||||
return render(request, 'shiftflow/items_list.html', context)
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic import TemplateView, ListView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import TemplateView, ListView, UpdateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from .models import Item # Проверь, как точно называется твоя модель деталей/заказов
|
||||
|
||||
@@ -17,12 +18,14 @@ class IndexView(TemplateView):
|
||||
# Класс реестра деталей (защищен LoginRequiredMixin)
|
||||
class RegistryView(LoginRequiredMixin, ListView):
|
||||
model = Item
|
||||
template_name = 'shiftflow/items_list.html'
|
||||
template_name = 'shiftflow/registry.html'
|
||||
context_object_name = 'items'
|
||||
|
||||
def get_queryset(self):
|
||||
# Позже здесь добавим: .filter(machine__in=request.user.profile.machines.all())
|
||||
return Item.objects.all().order_by('-id')
|
||||
# Сортируем: сначала статус (по алфавиту или логике choices),
|
||||
# затем по дате (свежие сверху), по станку и по номеру сделки
|
||||
return Item.objects.all().order_by('status', '-date', 'machine__name', 'deal__number')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@@ -30,3 +33,25 @@ class RegistryView(LoginRequiredMixin, ListView):
|
||||
if hasattr(self.request.user, 'profile'):
|
||||
context['user_role'] = self.request.user.profile.role
|
||||
return context
|
||||
|
||||
# Вьюха детального вида и редактирования
|
||||
class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Item
|
||||
template_name = 'shiftflow/item_detail.html'
|
||||
# Перечисляем поля, которые можно редактировать (укажи нужные)
|
||||
fields = [
|
||||
'drawing_name', 'machine', 'quantity_plan', 'quantity_fact',
|
||||
'material', 'size_value', 'status', 'is_synced_1c', 'extra_drawing'
|
||||
]
|
||||
context_object_name = 'item'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# Обязательно добавляем роль в контекст этого шаблона!
|
||||
if hasattr(self.request.user, 'profile'):
|
||||
context['user_role'] = self.request.user.profile.role
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
# После сохранения возвращаемся в реестр
|
||||
return reverse_lazy('registry')
|
||||
@@ -1,38 +1,105 @@
|
||||
/* Акцентные цвета для темной темы */
|
||||
[data-bs-theme="dark"] {
|
||||
--bs-body-bg: #121212;
|
||||
--bs-body-color: #e9ecef;
|
||||
--bs-accent: #ffc107; /* Тот самый желтый */
|
||||
/* --- ГЛОБАЛЬНЫЕ НАСТРОЙКИ --- */
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
/* Убрали общее центрирование, чтобы реестр был сверху */
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table-custom-header {
|
||||
/* Навбар и Футер: жестко фиксируем цвет для обеих тем */
|
||||
.navbar, .footer-custom {
|
||||
/* Темный графит, который хорошо смотрится и там, и там */
|
||||
background-color: #2c3034 !important;
|
||||
border-bottom: 1px solid #3d4246 !important;
|
||||
border-top: 1px solid #3d4246 !important; /* Для футера */
|
||||
}
|
||||
|
||||
/* Принудительно светлый текст для футера и навбара */
|
||||
.navbar .nav-link,
|
||||
.navbar .navbar-brand,
|
||||
.footer-custom span,
|
||||
.footer-custom strong {
|
||||
color: #e9ecef !important;
|
||||
}
|
||||
|
||||
/* Состояние активной ссылки в меню */
|
||||
.nav-link.active {
|
||||
color: var(--bs-accent) !important;
|
||||
border-bottom: 2px solid var(--bs-accent);
|
||||
}
|
||||
|
||||
/* Цвет ссылок в темном навбаре, чтобы не сливались */
|
||||
.navbar .nav-link, .navbar .navbar-brand, .navbar .text-reset {
|
||||
color: #e9ecef !important;
|
||||
}
|
||||
|
||||
/* --- РЕЕСТР --- */
|
||||
|
||||
/* Делаем строку таблицы визуально кликабельной */
|
||||
.clickable-row {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Подсветка при наведении */
|
||||
.clickable-row:hover {
|
||||
background-color: rgba(255, 193, 7, 0.05) !important; /* Легкий отсвет нашего акцента */
|
||||
}
|
||||
|
||||
/* --- ТЕМЫ --- */
|
||||
|
||||
[data-bs-theme="dark"] {
|
||||
--bs-body-bg: #121212; /* Глубокий черный фон */
|
||||
--bs-body-color: #e9ecef; /* Светло-серый текст */
|
||||
--bs-accent: #ffc107; /* Желтый акцент (Amber) */
|
||||
}
|
||||
|
||||
[data-bs-theme="light"] {
|
||||
--bs-body-bg: #f8f9fa; /* Почти белый фон */
|
||||
--bs-body-color: #212529; /* Темный текст */
|
||||
--bs-accent: #0d6efd; /* Синий акцент для светлой темы */
|
||||
}
|
||||
|
||||
/* --- ТАБЛИЦА И КАРТОЧКИ --- */
|
||||
|
||||
/* Заголовок таблицы: всегда темный с акцентным текстом */
|
||||
.table-custom-header {
|
||||
background-color: #1e1e1e !important;
|
||||
color: var(--bs-accent) !important;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase; /* Все буквы заглавные */
|
||||
}
|
||||
|
||||
/* Акцентные цвета для светлой темы */
|
||||
[data-bs-theme="light"] {
|
||||
--bs-body-bg: #e2e2e2;
|
||||
--bs-body-color: #212529;
|
||||
--bs-accent: #0f5132; /* Темно-зеленый */
|
||||
/* Фикс для таблиц в светлой теме */
|
||||
[data-bs-theme="light"] .table {
|
||||
--bs-table-bg: #ffffff;
|
||||
--bs-table-color: #212529;
|
||||
--bs-table-hover-bg: #f1f3f5;
|
||||
}
|
||||
|
||||
/* Общие классы */
|
||||
.text-accent {
|
||||
color: var(--bs-accent) !important;
|
||||
}
|
||||
/* --- ВСПОМОГАТЕЛЬНЫЕ КЛАССЫ --- */
|
||||
|
||||
/* Текст акцентного цвета */
|
||||
.text-accent { color: var(--bs-accent) !important; }
|
||||
|
||||
/* Кнопка с контуром акцентного цвета */
|
||||
.btn-outline-accent {
|
||||
color: var(--bs-accent) !important;
|
||||
border-color: var(--bs-accent) !important;
|
||||
}
|
||||
|
||||
/* Состояние кнопки при наведении */
|
||||
.btn-outline-accent:hover {
|
||||
background-color: var(--bs-accent) !important;
|
||||
color: #000 !important;
|
||||
color: #000 !important; /* Текст становится черным для контраста */
|
||||
}
|
||||
|
||||
/* Фикс для навигации */
|
||||
.nav-link.active {
|
||||
border-bottom: 2px solid var(--bs-accent);
|
||||
/* Специальный класс для центрирования окна логина (вернем его только там) */
|
||||
.flex-center-center {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center; /* Центр по вертикали */
|
||||
justify-content: center; /* Центр по горизонтали */
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
{% include 'components/_navbar.html' %}
|
||||
{% endif %}
|
||||
|
||||
<main class="container-fluid py-3 flex-grow-1 d-flex flex-column justify-content-center">
|
||||
<main class="container-fluid py-3 flex-grow-1 d-flex flex-column">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<footer class="mt-auto py-3 bg-body-tertiary border-top">
|
||||
<footer class="footer-custom mt-auto py-3">
|
||||
<div class="container-fluid text-center">
|
||||
<span class="text-muted small">
|
||||
Система учета сменных заданий, разработана <strong>ACK</strong> © 2026
|
||||
<i class="bi bi-cpu ms-2 text-accent"></i>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
61
templates/components/_navbar copy.html
Normal file
61
templates/components/_navbar copy.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<nav class="navbar navbar-expand-lg border-bottom shadow-sm">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-bold text-accent" href="/">
|
||||
<i class="bi bi-gear-fill me-2"></i>ShiftFlow
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler text-light border-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon" style="filter: invert(1);"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'items_list' %}active{% endif %}" href="{% url 'registry' %}">Реестр</a>
|
||||
</li>
|
||||
|
||||
{% if user_role in 'admin,technologist' %}
|
||||
<li class="nav-item"><a class="nav-link" href="#">Планирование</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if user_role in 'admin,technologist,master,operator' %}
|
||||
<li class="nav-item"><a class="nav-link" href="#">Закрытие</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if user_role in 'admin,technologist,clerk' %}
|
||||
<li class="nav-item"><a class="nav-link" href="#">Списание</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="badge bg-secondary opacity-75 px-3 py-2 fw-normal">
|
||||
<i class="bi bi-person-circle me-1"></i>
|
||||
{% if user_role == 'admin' %}Админ
|
||||
{% elif user_role == 'technologist' %}Технолог
|
||||
{% elif user_role == 'master' %}Мастер
|
||||
{% elif user_role == 'operator' %}Оператор
|
||||
{% elif user_role == 'clerk' %}Учетчик
|
||||
{% endif %}
|
||||
({{ request.user.username|upper }})
|
||||
</span>
|
||||
|
||||
{% if user_role == 'admin' %}
|
||||
<a href="/admin/" class="btn btn-link text-decoration-none text-reset p-0" title="Админка">
|
||||
<i class="bi bi-shield-lock fs-5"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<button class="btn btn-link text-reset p-0" onclick="toggleTheme()" title="Сменить тему">
|
||||
<i id="theme-icon" class="bi fs-5"></i>
|
||||
</button>
|
||||
|
||||
<form action="{% url 'logout' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-link text-danger p-0 ms-2" title="Выйти">
|
||||
<i class="bi bi-box-arrow-right fs-5"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -4,10 +4,14 @@
|
||||
<i class="bi bi-gear-fill me-2"></i>ShiftFlow
|
||||
</a>
|
||||
|
||||
<div class="collapse navbar-collapse">
|
||||
<button class="navbar-toggler border-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon" style="filter: invert(1);"></span> </button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'items_list' %}active{% endif %}" href="{% url 'registry' %}">Реестр</a>
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'registry' %}active{% endif %}" href="{% url 'registry' %}">Реестр</a>
|
||||
</li>
|
||||
|
||||
{% if user_role in 'admin,technologist' %}
|
||||
@@ -23,26 +27,19 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="badge bg-secondary opacity-75 px-3 py-2 fw-normal">
|
||||
<i class="bi bi-person-circle me-1"></i>
|
||||
{% if user_role == 'admin' %}Админ
|
||||
{% elif user_role == 'technologist' %}Технолог
|
||||
{% elif user_role == 'master' %}Мастер
|
||||
{% elif user_role == 'operator' %}Оператор
|
||||
{% elif user_role == 'clerk' %}Учетчик
|
||||
{% endif %}
|
||||
({{ request.user.username|upper }})
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2 mt-lg-0 mt-3">
|
||||
|
||||
{% if user_role == 'admin' %}
|
||||
<a href="/admin/" class="btn btn-link text-decoration-none text-reset p-0" title="Админка">
|
||||
<i class="bi bi-shield-lock fs-5"></i>
|
||||
</a>
|
||||
<a href="/admin/" class="btn btn-link text-decoration-none text-reset p-0 me-1" title="Админка">
|
||||
<i class="bi bi-shield-lock fs-5 text-accent"></i> </a>
|
||||
{% endif %}
|
||||
|
||||
<span class="badge bg-secondary opacity-75 px-3 py-2 fw-normal">
|
||||
<i class="bi bi-person-circle me-1"></i> ({{ request.user.username|upper }})
|
||||
</span>
|
||||
|
||||
<button class="btn btn-link text-reset p-0" onclick="toggleTheme()" title="Сменить тему">
|
||||
<i id="theme-icon" class="bi fs-5"></i>
|
||||
<i id="theme-icon" class="bi fs-5 text-accent"></i>
|
||||
</button>
|
||||
|
||||
<form action="{% url 'logout' %}" method="post" class="d-inline">
|
||||
|
||||
Reference in New Issue
Block a user