feat: automatic product archiving

This commit is contained in:
imperosol
2025-09-14 01:36:46 +02:00
parent b767079c5a
commit 5274f1f0f0
6 changed files with 164 additions and 22 deletions

View File

@@ -1,13 +1,19 @@
import math
from django import forms
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django_celery_beat.models import ClockedSchedule, PeriodicTask
from phonenumber_field.widgets import RegionalPhoneNumberWidget
from club.widgets.ajax_select import AutoCompleteSelectClub
from core.models import User
from core.views.forms import NFCTextInput, SelectDate, SelectDateTime
from core.views.forms import (
NFCTextInput,
SelectDate,
SelectDateTime,
)
from core.views.widgets.ajax_select import (
AutoCompleteSelect,
AutoCompleteSelectMultipleGroup,
@@ -158,6 +164,66 @@ class CounterEditForm(forms.ModelForm):
}
class ProductArchiveForm(forms.Form):
"""Form for automatic product archiving."""
enabled = forms.BooleanField(
label=_("Enabled"),
widget=forms.CheckboxInput(attrs={"class": "switch"}),
required=False,
)
archive_at = forms.DateTimeField(
label=_("Date and time of archiving"), widget=SelectDateTime, required=False
)
def __init__(self, *args, product: Product, **kwargs):
self.product = product
self.instance = PeriodicTask.objects.filter(
task="counter.tasks.archive_product", args=f"[{product.id}]"
).first()
super().__init__(*args, **kwargs)
if self.instance:
self.fields["enabled"].initial = self.instance.enabled
self.fields["archive_at"].initial = self.instance.clocked.clocked_time
def clean(self):
cleaned_data = super().clean()
if cleaned_data["enabled"] is True and cleaned_data["archive_at"] is None:
raise ValidationError(
_(
"Automatic archiving cannot be enabled "
"without providing a archiving date."
)
)
def save(self):
if not self.changed_data:
return
if not self.instance:
PeriodicTask.objects.create(
task="counter.tasks.archive_product",
args=f"[{self.product.id}]",
name=f"Archive product {self.product}",
clocked=ClockedSchedule.objects.create(
clocked_time=self.cleaned_data["archive_at"]
),
enabled=self.cleaned_data["enabled"],
one_off=True,
)
return
if (
"archive_at" in self.changed_data
and self.cleaned_data["archive_at"] is None
):
self.instance.delete()
elif "archive_at" in self.changed_data:
self.instance.clocked.clocked_time = self.cleaned_data["archive_at"]
self.instance.clocked.save()
self.instance.enabled = self.cleaned_data["enabled"]
self.instance.save()
return self.instance
class ProductEditForm(forms.ModelForm):
error_css_class = "error"
required_css_class = "required"
@@ -199,22 +265,19 @@ class ProductEditForm(forms.ModelForm):
queryset=Counter.objects.all(),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, *args, instance=None, **kwargs):
super().__init__(*args, instance=instance, **kwargs)
if self.instance.id:
self.fields["counters"].initial = self.instance.counters.all()
self.archive_form = ProductArchiveForm(*args, product=self.instance, **kwargs)
def is_valid(self):
return super().is_valid() and self.archive_form.is_valid()
def save(self, *args, **kwargs):
ret = super().save(*args, **kwargs)
if self.fields["counters"].initial:
# Remove the product from all counter it was added to
# It will then only be added to selected counters
for counter in self.fields["counters"].initial:
counter.products.remove(self.instance)
counter.save()
for counter in self.cleaned_data["counters"]:
counter.products.add(self.instance)
counter.save()
self.instance.counters.set(self.cleaned_data["counters"])
self.archive_form.save()
return ret

View File

@@ -445,7 +445,8 @@ class Product(models.Model):
buying_groups = list(self.buying_groups.all())
if not buying_groups:
return True
return any(user.is_in_group(pk=group.id) for group in buying_groups)
res = any(user.is_in_group(pk=group.id) for group in buying_groups)
return res
@property
def profit(self):

13
counter/tasks.py Normal file
View File

@@ -0,0 +1,13 @@
# Create your tasks here
from celery import shared_task
from counter.models import Product
@shared_task
def archive_product(product_id):
product = Product.objects.get(id=product_id)
product.archived = True
product.save()
product.counters.clear()

View File

@@ -0,0 +1,31 @@
{% extends "core/base.jinja" %}
{% block content %}
{% if object %}
<h2>{% trans name=object %}Edit product {{ name }}{% endtrans %}</h2>
{% else %}
<h2>{% trans %}Product creation{% endtrans %}</h2>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p() }}
<br />
<h3>{% trans %}Automatic archiving{% endtrans %}</h3>
<p>
<em>
{%- trans trimmed -%}
Automatic archiving allows you to mark a product as archived
and remove it from all its counters at a specified time and date.
{%- endtrans -%}
</em>
</p>
<fieldset x-data="{enabled: {{ form.archive_form.enabled.initial|tojson }}}">
{{ form.archive_form.as_p() }}
</fieldset>
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form>
{% endblock %}

View File

@@ -147,7 +147,7 @@ class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
model = Product
form_class = ProductEditForm
template_name = "core/create.jinja"
template_name = "counter/product_form.jinja"
current_tab = "products"
@@ -157,7 +157,7 @@ class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
model = Product
form_class = ProductEditForm
pk_url_kwarg = "product_id"
template_name = "core/edit.jinja"
template_name = "counter/product_form.jinja"
current_tab = "products"