fixed crud list

fixed payments
upload -a
master
kikimor 7 months ago
parent a4a91e0053
commit 4603953458

@ -8,48 +8,55 @@
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/DepoT/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/accounts/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/forms/company.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/forms/company.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/forms/line.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/forms/line.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/booking/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/templatetags/filters.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/templatetags/filters.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/utils/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/client_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/barrier_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common_api/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/services.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/barrier_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/containers/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/damages_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/damages_api/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docker-compose.yml" beforeDir="false" afterPath="$PROJECT_DIR$/docker-compose.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/dockerfile" beforeDir="false" afterPath="$PROJECT_DIR$/dockerfile" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/models.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/services.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/payments/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/employee_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/populate_database.txt" beforeDir="false" afterPath="$PROJECT_DIR$/populate_database.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/forms.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/preinfo/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/client_views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/images/k-depot-logo.svg" beforeDir="false" afterPath="$PROJECT_DIR$/static/images/k-depot-logo.svg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/js/container_validation.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/container_validation.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/js/crud-list.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/crud-list.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/forms.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/forms.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/sidebar.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/sidebar.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/styles.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/styles.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/styles/tables.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/tables.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-sidebar.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/line-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-create.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/line-update.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-update.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/client/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/preinfo-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-expedition.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-expedition.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-photos.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-photos.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/container-receive.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-receive.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/email.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/email.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-base.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee-dashboard-content.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-dashboard-content.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-sidebar.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/booking-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/company-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/company-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/container-search.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/container-search.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/containers-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/containers-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/line-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-create.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/line-update.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-update.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/payment-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-create.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/payment-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/employee/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/preinfo-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/landing-page.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/landing-page.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/list-crud.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/list-crud.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/registration/user-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/user-list.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/registration/login.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/login.html" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -63,8 +70,8 @@
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="JavaScript File" />
<option value="CSS File" />
<option value="JavaScript File" />
<option value="HTML File" />
<option value="Python Script" />
</list>
@ -81,32 +88,32 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"DefaultHtmlFileTemplate": "HTML File",
"Django Server.DepoT.executor": "Debug",
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.pycharm.django.structure.promotion.once.per.project": "true",
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW",
"git-widget-placeholder": "master",
"ignore.virus.scanning.warn.message": "true",
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/employee",
"list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"vue.rearranger.settings.migration": "true"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;,
&quot;Django Server.DepoT.executor&quot;: &quot;Debug&quot;,
&quot;RunOnceActivity.OpenDjangoStructureViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.pycharm.django.structure.promotion.once.per.project&quot;: &quot;true&quot;,
&quot;django.template.preview.state&quot;: &quot;SHOW_EDITOR_AND_PREVIEW&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/dev_projects/python/Django/DepoT/templates/employee&quot;,
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
"keyToStringList": {
"DatabaseDriversLRU": [
"postgresql"
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
]
}
}]]></component>
}</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
@ -116,11 +123,11 @@
<recent name="C:\dev_projects\python\Django\DepoT\static\styles" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" />
<recent name="C:\dev_projects\python\Django\DepoT\common\views" />
<recent name="C:\dev_projects\python\Django\DepoT\booking\views" />
<recent name="C:\dev_projects\python\Django\DepoT\containers\views" />
</key>
</component>
<component name="RunManager">
@ -168,7 +175,11 @@
<updated>1750784740296</updated>
<workItem from="1750784741367" duration="240087000" />
<workItem from="1752078951079" duration="106000" />
<workItem from="1752079161329" duration="97669000" />
<workItem from="1752079161329" duration="189445000" />
<workItem from="1753174764499" duration="271000" />
<workItem from="1753175068058" duration="4635000" />
<workItem from="1753179863298" duration="8357000" />
<workItem from="1753197869497" duration="15872000" />
</task>
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files&#10;&#10;This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
<option name="closed" value="true" />
@ -293,16 +304,6 @@
<line>118</line>
<option name="timeStamp" value="61" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/damages_api/views.py</url>
<line>15</line>
<option name="timeStamp" value="62" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/damages_api/views.py</url>
<line>39</line>
<option name="timeStamp" value="63" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/common/utils/owncloud_utls.py</url>
<line>29</line>
@ -310,8 +311,18 @@
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url>
<line>40</line>
<option name="timeStamp" value="65" />
<line>41</line>
<option name="timeStamp" value="98" />
</line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url>
<line>56</line>
<option name="timeStamp" value="99" />
</line-breakpoint>
<line-breakpoint enabled="true" type="javascript">
<url>file://$PROJECT_DIR$/static/js/container_validation.js</url>
<line>4</line>
<option name="timeStamp" value="95" />
</line-breakpoint>
</breakpoints>
<default-breakpoints>

@ -11,25 +11,21 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
import os
import environ
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
env = environ.Env()
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-g%187p84o9^rr)3#9@r3n^o2v1i%@6=+puxm7hlodg+kbsk%n#"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['192.168.24.43', '127.0.0.1', 'localhost', ]
# Application definition
PROJECT_APPS = [
'accounts',
"booking",
@ -95,11 +91,11 @@ WSGI_APPLICATION = "DepoT.wsgi.application"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "depot",
"USER": "postgres",
"PASSWORD": "admin",
"HOST": "127.0.0.1",
"PORT": "5432",
"NAME": os.environ.get("DB_NAME", env("DB_NAME")),
"USER": os.environ.get("DB_USER", env("DB_USER")),
"PASSWORD": os.environ.get("DB_PASSWORD", env("DB_PASSWORD")),
"HOST": os.environ.get("DB_HOST", env("DB_HOST")),
"PORT": os.environ.get("DB_PORT", env("DB_PORT")),
}
}
@ -157,12 +153,27 @@ TEMP_FILE_FOLDER = "/tmp/damages_photos"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
import environ
env = environ.Env()
import os
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
OWNCLOUD_URL = env('OWNCLOUD_URL')
OWNCLOUD_USER = env('OWNCLOUD_USER')
OWNCLOUD_PASSWORD = env('OWNCLOUD_PASSWORD')
OWNCLOUD_DAMAGES_FOLDER = env('OWNCLOUD_DAMAGES_FOLDER')
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = env("EMAIL_HOST", cast=str, default=None)
EMAIL_PORT = env("EMAIL_PORT", cast=str, default='587') # Recommended
EMAIL_HOST_USER = env("EMAIL_HOST_USER", cast=str, default=None)
EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD", cast=str, default=None)
EMAIL_USE_TLS = env("EMAIL_USE_TLS", cast=bool, default=True) # Use EMAIL_PORT 587 for TLS
EMAIL_USE_SSL = env("EMAIL_USE_SSL", cast=bool, default=False) # EUse MAIL_PORT 465 for SSL
ADMIN_USER_NAME=env("ADMIN_USER_NAME", default="Admin user")
ADMIN_USER_EMAIL=env("ADMIN_USER_EMAIL", default=None)
MANAGERS=[]
ADMINS=[]
if all([ADMIN_USER_NAME, ADMIN_USER_EMAIL]):
ADMINS +=[
(f'{ADMIN_USER_NAME}', f'{ADMIN_USER_EMAIL}')
]
MANAGERS=ADMINS

@ -26,5 +26,6 @@ urlpatterns = [
path('container/', include('containers.urls')),
path('booking/', include('booking.urls')),
path('payment/', include('payments.urls')),
path('damages/', include('damages_api.urls')),
path('api/damages/', include('damages_api.urls')),
path('api/common/', include('common_api.urls')),
]

@ -27,7 +27,7 @@ class RegisterForm(ModelForm):
super().__init__(*args, **kwargs)
self.fields['password1'].widget.attrs.update({'autocomplete': 'new-password'})
self.fields['password2'].widget.attrs.update({'autocomplete': 'new-password'})
self.fields['user_type'].widget.attrs['disabled'] = True
self.fields['user_type'].widget.attrs['readonly'] = True
self.fields['company_permissions'].widget.attrs['disabled'] = True
self.fields['employee_permissions'].widget.attrs['disabled'] = True

@ -97,6 +97,17 @@ class UserListView(ListView):
context['base_template'] = self.base_template
return context
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
# Filter users based on permissions
if user.is_superuser:
return queryset.all()
elif user.user_type == DepotUser.UserType.COMPANY_ADMIN:
return queryset.filter(company=user.company)
else:
return queryset.none()
class UserUpdateView(UpdateView):
template_name = 'registration/register.html'

@ -17,9 +17,20 @@ class BookingCreateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']
widgets = {
'container_number': forms.TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}
class BookingUpdateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']
widgets = {
'container_number': forms.TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}

@ -0,0 +1,20 @@
from django import template
from django.template.defaultfilters import date
from common.utils.utils import check_container_number_validity
register = template.Library()
@register.filter
def bg_date(value):
if value:
return date(value, "y.m.d h:m")
return ""
@register.simple_tag
def container_validity_class(container_number):
if not check_container_number_validity(container_number):
return 'invalid-container'
return ''

@ -0,0 +1,40 @@
import os
import uuid
from datetime import datetime
from django.conf import settings
from rest_framework import status
from rest_framework.response import Response
import owncloud
from owncloud import (HTTPResponseError)
class Owncloud:
@staticmethod
def upload_damage_photo(filename, depot_id):
_, ext = os.path.splitext(filename)
oc = owncloud.Client(settings.OWNCLOUD_URL)
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
path = f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}"
try:
oc.file_info(path)
except HTTPResponseError:
_ = oc.mkdir(path)
owncloud_filename = f"{datetime.today().strftime('%Y%m%d')}_{uuid.uuid4()}{ext}"
owncloud_fullpath = path + "/" + owncloud_filename
oc.put_file(owncloud_fullpath, filename)
file_info = oc.share_file_with_link(owncloud_fullpath)
return file_info.get_link()
@staticmethod
def get_damages(depot_id):
oc = owncloud.Client(settings.OWNCLOUD_URL)
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
files = oc.list(f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}")
damages = []
for file_info in files:
if file_info.is_dir():
continue # Пропускаме поддиректории
# Създаване на публичен линк за всеки файл
link_info = oc.share_file_with_link(file_info.path)
damages.append(link_info.get_link())
return Response(damages, status=status.HTTP_200_OK)

@ -1,5 +1,12 @@
import re
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from containers.models import Container
from preinfo.models import Preinfo
from django.conf import settings
from django.core.mail import send_mail, EmailMessage
def filter_queryset_by_user(queryset, user):
@ -41,3 +48,42 @@ def get_container_by_number(number):
return Container.objects.get(number=number, expedited=False)
except Container.DoesNotExist:
return None
CALC_STRING = "0123456789A.BCDEFGHIJK.LMNOPQRSTU.VWXYZ"
def check_container_number_validity(container_number):
if not re.search(r"[A-Z]{4}[0-9]{7}", container_number):
return False
subtotal = 0
delta = 1
for i in range(0, 10):
subtotal = subtotal + CALC_STRING.find(container_number[i]) * delta
delta = delta * 2
subtotal = (subtotal % 11) % 10
if not subtotal == CALC_STRING.find(container_number[10]):
return False
return True
ADMIN_USER_EMAIL= settings.ADMIN_USER_EMAIL
EMAIL_HOST_USER = settings.EMAIL_HOST_USER
def send_test_email(user_email, urls):
subject='Welcome to Our Platform'
html_message = render_to_string('email.html', {
'site_name': 'Our Platform',
'user_email': user_email,
'link1': urls['pay_epay'],
'link2': urls['pay_credit_card'],
})
plain_message = strip_tags(html_message)
email = EmailMessage(
subject=subject,
body=html_message,
from_email=EMAIL_HOST_USER,
to=[user_email],
)
email.content_subtype = 'html' # This tells Django to send HTML email
email.send()

@ -1,14 +1,17 @@
from django.shortcuts import render
from django.views.generic import TemplateView
from containers.models import Container
class BarrierDashboardView(TemplateView):
template_name = 'barrier/barrier-dashboard.html'
extra_context = {
'title': 'Client Dashboard',
'description': 'This is the client dashboard page.',
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
recent_containers = Container.objects.select_related('line', 'booking').order_by('-expedited_on', '-received_on')[:10]
context['recent_containers'] = recent_containers
return context
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.extra_context)
return render(request, self.template_name, self.get_context_data())

@ -3,5 +3,5 @@ from .views import validate_container
urlpatterns = [
# ... your other urls
path('api/validate-container/<str:number>/', validate_container, name='validate_container'),
path('validate-container/<str:number>/', validate_container, name='validate_container'),
]

@ -1,4 +1,4 @@
from django.forms import ModelForm
from django.forms import ModelForm, TextInput
from containers.models import Container
@ -7,7 +7,12 @@ class ContainerBaseForm(ModelForm):
class Meta:
model = Container
fields = '__all__'
widgets = {
'number': TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}
class ContainerReceiveForm(ContainerBaseForm):
"""
Form for creating a new Container instance.
@ -16,14 +21,28 @@ class ContainerReceiveForm(ContainerBaseForm):
class Meta(ContainerBaseForm.Meta):
fields = ['number', 'receive_vehicle', 'damages', 'heavy_damaged', 'position',]
def __init__(self, *args, preinfo=None, **kwargs):
super().__init__(*args, **kwargs)
# Add form-control class to all fields
for field in self.fields.values():
field.widget.attrs.update({'class': 'form-control'})
# Checkbox specific styling
self.fields['heavy_damaged'].widget.attrs.update({'class': 'form-check-input'})
# If preinfo is provided, set initial values and make fields readonly
if preinfo:
...
# self.fields['preinfo'].initial = preinfo.container_number
# self.fields['number'].initial = preinfo.number
# self.fields['container_number'].widget.attrs['readonly'] = True
# self.fields['number'].widget.attrs['readonly'] = True
# self.fields['booking'].disabled = True
# self.fields['number'].disabled = True
class ContainerExpeditionForm(ContainerBaseForm):
"""
Form for updating an existing Container instance.
Inherits from ContainerBaseForm.
"""
class Meta(ContainerBaseForm.Meta):
fields = ['number', 'expedition_vehicle', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
@ -32,4 +51,4 @@ class ContainerExpeditionForm(ContainerBaseForm):
readonly_fields = ['number', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
for field in readonly_fields:
self.fields[field].widget.attrs['readonly'] = True
self.fields[field].disabled = True
# self.fields[field].disabled = True

@ -1,5 +1,7 @@
from django.db import models
from common.models import LinesModel, ContainerTypeModel
# from payments.models import ContainerTariffPeriod, AdditionalFees
# Create your models here.
class Container(models.Model):
@ -42,6 +44,11 @@ class Container(models.Model):
blank=True,
null=True
)
preinfo = models.ForeignKey(
'preinfo.Preinfo',
on_delete=models.CASCADE,
related_name='container_preinfo',
)
booking = models.ForeignKey(
'booking.Booking',
on_delete=models.CASCADE,
@ -61,6 +68,7 @@ class Container(models.Model):
expedition_vehicle = models.CharField(max_length=100, blank=True, null=True)
class ContainerHistory(Container):
operation = models.ForeignKey(
'common.OperationModel',

@ -1,11 +1,11 @@
from booking.models import Booking
from containers.models import Container
def get_container_for_booking(booking_number):
def get_container_for_booking(booking):
filters = {
'expedited': False,
}
booking = Booking.objects.get(number=booking_number)
# booking = Booking.objects.get(number=booking_number)
if not booking:
return None
if booking.container_number:

@ -1,14 +1,18 @@
from django.urls import include, path
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
from containers.views.barrier_views import ContainerReceive, ContainerExpedition, ContainerSearchView
from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \
ContainerPhotosView
urlpatterns = [
path('search/', ContainerSearchView.as_view(), name='container_search'),
path('container-search/', ContainerSearchView.as_view(), name='container_search'),
path('container-search/', ContainerSearchView.as_view(), name='barrier_photos'),
# path('search/', ContainerSearchView.as_view(), name='container_search'),
path('employee/', ContainersListView.as_view(), name='employee_containers'),
path('not-paid', ReportContainersUnpaidListView.as_view(), name='not_paid'),
path('barrier/receive/', ContainerReceiveView.as_view(), name='container_receive'),
path('barrier/expedition/', ContainerExpedition.as_view(), name='container_expedition'),
path('barrier/<int:pk>/', include([
path('receive/', ContainerReceive.as_view(), name='container_receive'),
path('expedition/', ContainerExpedition.as_view(), name='container_expedition'),
path('photos/', ContainerPhotosView.as_view(), name='container_photos'),
])),
]

@ -7,53 +7,69 @@ from django.views.generic import CreateView, UpdateView, FormView, ListView
from booking.models import Booking
from common.utils.utils import get_container_by_number
from containers.forms import ContainerReceiveForm, ContainerExpeditionForm
from containers.models import Container
from containers.models import Container, ContainerPhotos
from containers.services import get_container_for_booking
from preinfo.models import Preinfo
# Create your views here.
class ContainerReceive(CreateView):
class ContainerReceiveView(FormView):
template_name = 'container-receive.html'
model = Container
form_class = ContainerReceiveForm
success_url = reverse_lazy('dashboard')
success_url = reverse_lazy('container_photos')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk')
try:
preinfo =Preinfo.objects.filter(pk=pk, received=False).first()
except Preinfo.DoesNotExist:
preinfo = None
context['show_search'] = True
search_number = self.request.GET.get('number')
if search_number:
preinfo = Preinfo.objects.filter(container_number=search_number).first()
if preinfo:
context['preinfo'] = preinfo
context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
form = self.form_class(initial={'number': preinfo.container_number})
context.update({
'form': form,
'preinfo': preinfo,
'search_number': search_number,
'show_search': False
})
else:
context.update({
'error': 'Preinfo for this container not found',
'search_number': search_number
})
return context
return redirect(reverse_lazy('container_search'))
def form_valid(self, form):
# Get the preinfo_id from the POST data
preinfo_id = self.request.POST.get('preinfo_id')
try:
preinfo = Preinfo.objects.get(id=preinfo_id)
except Preinfo.DoesNotExist:
preinfo = None
# validate if data is correct, comparing user data with preinfo data
if preinfo and preinfo.container_number == form.cleaned_data.get('number') and not preinfo.received:
container = form.save(commit=False)
preinfo = Preinfo.objects.filter(container_number=self.request.POST.get('number')).first()
preinfo.received = True
preinfo.save()
# Set required fields from preinfo
container.container_type = preinfo.container_type
container.line = preinfo.line
container.received_by = self.request.user
container.preinfo = preinfo
container.save()
form.instance.received_by = self.request.user
form.instance.line = preinfo.line
form.instance.container_type = preinfo.container_type
else:
form.add_error('number', 'Invalid data')
return redirect('container_photos', pk=container.pk)
return super().form_valid(form)
def get_form(self, form_class=None):
form = super().get_form(form_class)
search_number = self.request.GET.get('number') # Changed from search_number
if search_number:
preinfo = Preinfo.objects.filter(container_number=search_number).first()
if preinfo:
form.initial['number'] = preinfo.container_number
form.fields['number'].widget.readonly = True
return form
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(
form=form,
search_number=self.request.POST.get('number'),
show_search=False
))
class ContainerSearchView(View):
@ -83,30 +99,131 @@ class ContainerSearchView(View):
return redirect(next_url, pk=booking.pk)
class ContainerExpedition(UpdateView):
# class ContainerExpedition(UpdateView):
# template_name = 'container-expedition.html'
# model = Container
# form_class = ContainerExpeditionForm
# success_url = reverse_lazy('dashboard')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# pk = self.kwargs.get('pk')
# booking = Booking.objects.get(pk=pk)
# filters = {
# 'expedited': False,
# }
# if booking.container_number:
# filters['container_number'] = booking.container_number
# else:
# filters['container_type'] = booking.container_type
# filters['line'] = booking.line
#
# container = Container.objects.filter(**filters).order_by('received_on').first()
# context['container'] = container
# context['booking'] = booking
# return context
#
# def form_valid(self, form):
# booking_id = self.request.POST.get('booking_id')
# container_id = self.request.POST.get('container_id')
#
# try:
# booking = Booking.objects.get(id=booking_id)
# container = Container.objects.get(id=container_id)
# vehicle = form.cleaned_data['expedition_vehicle']
# # Update container
# container.expedited = True
# container.expedited_on = timezone.now()
# container.expedited_by = self.request.user
# container.expedition_vehicle = vehicle
# container.booking = booking
# container.save()
#
# # Update booking status if needed
# booking.container_expedited_count += 1
# if booking.vehicles:
# vehicles_list = booking.vehicles.split(',')
# if vehicle in vehicles_list:
# vehicles_list.remove(vehicle)
# booking.vehicles = ','.join(vehicles_list)
# if booking.container_expedited_count >= booking.container_count:
# booking.status = 'completed' # or any other status you want to set
# booking.save()
#
# return redirect(self.success_url)
#
# except (Booking.DoesNotExist, Container.DoesNotExist):
# form.add_error(None, 'Invalid booking or container')
# return self.form_invalid(form)
#
#
# # try:
# # booking = Booking.objects.get(id=booking_id)
# # except Booking.DoesNotExist:
# # booking = None
# # # validate if data is correct, comparing user data with booking data
# # if booking and booking.container_number == form.cleaned_data.get('container_number') and not booking.expedited:
# # booking.expedited = True
# # booking.save()
# #
# # form.instance.expedited_by = self.request.user
# # form.instance.line = booking.line
# # form.instance.container_type = booking.container_type
# # else:
# # form.add_error('container_number', 'Invalid data')
#
# # if preinfo:
# # context['preinfo'] = preinfo
# # context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
# # return context
# # return redirect(reverse_lazy('container_search'))
class ContainerExpedition(FormView):
template_name = 'container-expedition.html'
model = Container
form_class = ContainerExpeditionForm
success_url = reverse_lazy('dashboard')
success_url = reverse_lazy('barrier_dashboard')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk')
booking = Booking.objects.get(pk=pk)
filters = {
'expedited': False,
}
if booking.container_number:
filters['container_number'] = booking.container_number
else:
filters['container_type'] = booking.container_type
filters['line'] = booking.line
context['show_search'] = True
container = Container.objects.filter(**filters).order_by('received_on').first()
context['container'] = container
context['booking'] = booking
search_number = self.request.GET.get('number')
if search_number:
booking = Booking.objects.filter(number=search_number).first()
if booking:
container = get_container_for_booking(booking)
if container:
# Initialize form with container data
form = self.form_class(initial={
'number': container.number,
'position': container.position,
'line': container.line.id,
'container_type': container.container_type.id,
'damages': container.damages,
'heavy_damaged': container.heavy_damaged,
'expedition_vehicle': container.expedition_vehicle
})
context.update({
'form': form,
'booking': booking,
'container': container,
'search_number': search_number,
'show_search': False
})
else:
context.update({
'error': 'No available container found for this booking',
'search_number': search_number
})
else:
context.update({
'error': 'Booking not found',
'search_number': search_number
})
return context
def form_valid(self, form):
booking_id = self.request.POST.get('booking_id')
container_id = self.request.POST.get('container_id')
@ -114,51 +231,74 @@ class ContainerExpedition(UpdateView):
try:
booking = Booking.objects.get(id=booking_id)
container = Container.objects.get(id=container_id)
vehicle = form.cleaned_data['expedition_vehicle']
# Update container
container.expedited = True
container.expedited_on = timezone.now()
container.expedited_by = self.request.user
container.expedition_vehicle = vehicle
container.expedition_vehicle = form.cleaned_data['expedition_vehicle']
container.booking = booking
container.save()
# Update booking status if needed
# Update booking
booking.container_expedited_count += 1
if booking.vehicles:
vehicles_list = booking.vehicles.split(',')
if vehicle in vehicles_list:
vehicles_list.remove(vehicle)
if container.expedition_vehicle in vehicles_list:
vehicles_list.remove(container.expedition_vehicle)
booking.vehicles = ','.join(vehicles_list)
if booking.container_expedited_count >= booking.container_count:
booking.status = 'completed' # or any other status you want to set
booking.status = 'completed'
booking.save()
return redirect(self.success_url)
except (Booking.DoesNotExist, Container.DoesNotExist):
form.add_error(None, 'Invalid booking or container')
return self.form_invalid(form)
def form_invalid(self, form):
print("Form errors:", form.errors) # Debug print
print("Form data:", form.data) # Debug print
return self.render_to_response(self.get_context_data(
form=form,
search_number=self.request.POST.get('number'),
show_search=False
))
# try:
# booking = Booking.objects.get(id=booking_id)
# except Booking.DoesNotExist:
# booking = None
# # validate if data is correct, comparing user data with booking data
# if booking and booking.container_number == form.cleaned_data.get('container_number') and not booking.expedited:
# booking.expedited = True
# booking.save()
#
# form.instance.expedited_by = self.request.user
# form.instance.line = booking.line
# form.instance.container_type = booking.container_type
# else:
# form.add_error('container_number', 'Invalid data')
# if preinfo:
# context['preinfo'] = preinfo
# context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
# return context
# return redirect(reverse_lazy('container_search'))
def get_form(self, form_class=None):
form_class = self.get_form_class()
if self.request.method == 'POST':
search_number = self.request.POST.get('search_number')
booking = Booking.objects.filter(number=search_number).first()
if booking:
container = get_container_for_booking(booking)
if container:
data = self.request.POST.copy()
data.update({
'number': container.number,
'position': container.position,
'line': container.line,
'container_type': container.container_type,
'damages': container.damages,
'heavy_damaged': container.heavy_damaged,
})
return form_class(data)
return form_class()
# containers/views/barrier_views.py
class ContainerPhotosView(View):
template_name = 'container-photos.html'
def get(self, request, pk):
container = Container.objects.get(pk=pk)
photos = ContainerPhotos.objects.filter(container=container)
return render(request, self.template_name, {
'container': container,
'photos': photos,
'photos_count': photos.count()
})

@ -1,6 +1,9 @@
from django.shortcuts import render, redirect
from django.views import View
from django.views.generic import ListView
from common.models import CompanyModel
from common.utils.utils import get_preinfo_by_number, get_container_by_number
from containers.models import Container
@ -50,3 +53,29 @@ class ReportContainersUnpaidListView(ListView):
queryset = queryset.filter(payment_containers__isnull=True)
return queryset.order_by('-expedited_on')
class ContainerSearchView(View):
template_name = 'barrier/container-search.html' # Single template for all searches
def get(self, request):
search_type = request.GET.get('param') # container_receive or container_expedition
return render(request, self.template_name, {'search_type': search_type})
def post(self, request):
number = request.POST.get('number')
search_type = request.POST.get('search_type')
if search_type == 'container_receive':
preinfo = get_preinfo_by_number(number)
if preinfo:
return redirect('receive_container', pk=preinfo.pk)
else: # container_expedition
container = get_container_by_number(number)
if container:
return redirect('expedite_container', pk=container.pk)
return render(request, self.template_name, {
'error': 'Not found',
'search_type': search_type
})

@ -3,5 +3,5 @@ from django.urls import path
from damages_api.views import Damages
urlpatterns = [
path('<int:depot_id>', Damages.as_view(), name='damages_list'),
path('<int:depot_id>/', Damages.as_view(), name='damages_list'),
]

@ -9,34 +9,43 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from common.utils.owncloud_utls import Owncloud
from containers.models import ContainerPhotos
from containers.models import ContainerPhotos, Container
class Damages(APIView):
def post(self, request, depot_id):
data = request.get_json()
photo = data.pop("photo")
extension = data.pop("photo_extension")
photo = request.data.get("photo")
extension = request.data.get("photo_extension")
with tempfile.NamedTemporaryFile(suffix=f'.{extension}', delete=False) as temp_file:
try:
temp_file.write(base64.b64decode(photo.encode("utf-8")))
temp_file.flush()
temp_file.close() # Close the file before uploading
own_filename = Owncloud.upload_damage_photo(temp_file.name, depot_id)
container_photo = ContainerPhotos()
container_photo.container.pk = depot_id
container_photo.container = Container.objects.get(pk=depot_id)
container_photo.photo = own_filename
container_photo.uploaded_on = datetime.now()
container_photo.uploaded_by = request.user
container_photo.save()
return Response(status=status.HTTP_201_CREATED)
except Exception as ex:
raise BadRequest("Invalid photo encoding")
return Response(
{"error": "Failed to process photo"},
status=status.HTTP_400_BAD_REQUEST
)
finally:
if temp_file:
try:
os.unlink(temp_file.name)
return Response(status=status.HTTP_201_CREATED)
except OSError:
pass # Ignore deletion errors
def get(self, request, depot_id):
return Owncloud.get_damages(depot_id)

@ -0,0 +1,11 @@
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
env_file:
- development.env
volumes:
- .:/DepoT

@ -0,0 +1,7 @@
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /DepoT
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD python manage.py migrate && python manage.py runserver 0.0.0.0:8000

@ -7,7 +7,7 @@ from payments.models import Payment
class PaymentCreateForm(ModelForm):
class Meta:
model = Payment
fields = ['total_amount', 'company', 'description']
fields = ['company', 'total_amount', 'description']
widgets = {
'containers': CheckboxSelectMultiple(),
}

@ -0,0 +1,94 @@
import base64
import hashlib
import hmac
import urllib.parse
from datetime import datetime, timedelta
import requests
from DepoT.settings import env
class EPay:
payment_types = {
"pay_credit_card": "credit_paydirect",
"pay_epay": "paylogin",
}
@staticmethod
def encode_message(message):
encoded_data = base64.b64encode(message.encode("utf-8")).decode("utf-8")
secret_key = env("EPAY_SECURITY_KEY")
checksum = hmac.new(
secret_key.encode("utf-8"), encoded_data.encode("utf-8"), hashlib.sha1
).hexdigest()
return encoded_data, checksum
@classmethod
def generate_urls(self, invoice, amount):
client_id = env("EPAY_CLIENT_ID")
description = f"Invoice number: {invoice} with amount of {amount}"
currency = "BGN"
expiry = (
datetime.today() + timedelta(days=int(env("INVOICE_EXPIRE_PERIOD")))
).strftime("%d.%m.%Y")
if amount == 0:
amount = 1000
message = f"MIN={client_id}\nINVOICE={invoice}\nAMOUNT={amount}\nCURRENCY={currency}\nEXP_TIME={expiry}\nDESCR={description}"
encoded_data, checksum = EPay.encode_message(message)
result = {}
for pay_type, keyword in EPay.payment_types.items():
url = f"https://demo.epay.bg/?PAGE={keyword}&ENCODED={urllib.parse.quote(encoded_data)}&CHECKSUM={urllib.parse.quote(checksum)}"
result[pay_type] = url
return result
@staticmethod
def payment_result(encoded_data, checksum_received):
secret_key = env("EPAY_SECURITY_KEY")
decoded_data = base64.b64decode(encoded_data).decode("utf-8")
checksum_calculated = hmac.new(
secret_key.encode("utf-8"), encoded_data.encode("utf-8"), hashlib.sha1
).hexdigest()
checksum_result = checksum_calculated == checksum_received
fields = decoded_data.split(":")
fields_dict = {}
for field in fields:
try:
name, value = field.split("=")
except ValueError:
print(fields)
print(field)
fields_dict[name] = value
return fields_dict, checksum_result
@staticmethod
def send_response(encoded_data, checksum):
url = f"https://demo.epay.bg/?ENCODED={urllib.parse.quote(encoded_data)}&CHECKSUM={urllib.parse.quote(checksum)}"
print(url)
res = requests.post(url)
@staticmethod
def send_ok(invoice):
message = f"INVOICE={invoice}:STATUS=OK"
encoded_data, checksum = EPay.encode_message(message)
print(f"OK {invoice}")
EPay.send_response(encoded_data, checksum)
@staticmethod
def send_no(invoice):
message = f"INVOICE={invoice}:STATUS=NO"
encoded_data, checksum = EPay.encode_message(message)
print(f"NO {invoice}")
EPay.send_response(encoded_data, checksum)
@staticmethod
def send_err(invoice):
message = f"INVOICE={invoice}:STATUS=ERR:ERR=BAD_CHECKSUM"
encoded_data, checksum = EPay.encode_message(message)
print(f"ERR {invoice}")
EPay.send_response(encoded_data, checksum)

@ -1,7 +1,8 @@
from django.urls import path
from payments.views import PaymentCreateView
from payments.views import PaymentCreateView, some_view
urlpatterns = [
path("create/", PaymentCreateView.as_view(), name="payments_create"),
path('invoice/', some_view, name='payments_invoice'),
]

@ -0,0 +1,36 @@
from django.db.models import Q
from payments.models import ContainerTariffPeriod, AdditionalFees
def calculate_charges(container):
days = (container.expedited_on.date() - container.received_on.date()).days + 1
total = 0
size = '20' if container.container_type.length == 20 else '40'
tariff = ContainerTariffPeriod.objects.get(
Q(to_days__gt=days) | Q(to_days__isnull=True),
container_size=size,
from_days__lte=days,
)
fees = AdditionalFees.objects.first()
storage_charge = days * float(tariff.daily_rate)
if container.container_type.container_type == 'reefer':
storage_charge += days * float(fees.reefer_daily_supplement)
if container.swept:
total += float(fees.sweeping_fee)
if container.washed:
total += float(fees.fumigation_fee)
total += storage_charge
return {
'days': days,
'storage_charge': storage_charge,
'services_charge': total - storage_charge,
'total': total,
}

@ -1,25 +1,72 @@
from datetime import datetime
from django.db.models import Sum
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import ListView, CreateView
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import Table, TableStyle, Spacer, Paragraph, SimpleDocTemplate
from common.models import CompanyModel
from common.utils.utils import send_test_email
from containers.models import Container
from payments.forms import PaymentCreateForm
from payments.models import Payment
from payments.models import Payment, PaymentItem
from payments.services import EPay
from payments.utils import calculate_charges
import io
from django.http import FileResponse, HttpResponse, response
from reportlab.pdfgen import canvas
# Create your views here.
class PaymentCreateView(CreateView):
model = Payment
form_class = PaymentCreateForm
template_name = 'employee/payment-create.html'
success_url = reverse_lazy('payment-list')
success_url = reverse_lazy('not_paid')
def form_valid(self, form):
container_ids = self.request.POST.getlist('containers')
last_payment = Payment.objects.order_by('-invoice_number').first()
today = datetime.now().strftime('%Y%m%d')
if last_payment and last_payment.invoice_number.startswith(f'INV-{today}'):
last_number = int(last_payment.invoice_number.split('-')[-1])
new_number = str(last_number + 1).zfill(4)
else:
new_number = '0001'
invoice_number = f'INV-{today}-{new_number}'
payment = form.save(commit=False)
payment.invoice_number = invoice_number
payment.created_by = self.request.user
payment.updated_by = self.request.user
payment.total_amount = 0
payment.save()
container_ids = self.request.POST.getlist('containers')
containers = Container.objects.filter(id__in=container_ids)
for container in containers:
charges = calculate_charges(container)
payment.total_amount += charges['total']
PaymentItem.objects.create(
payment=payment,
container=container,
amount=charges['total'] # Using total charge for the container
)
# 0000000000000000000000000000000000#
urls = EPay.generate_urls(payment.invoice_number, payment.total_amount)
print("Generated URLs:", urls)
payment.save()
payment.containers.set(container_ids)
send_test_email('kikimor@gmail.com', urls) # invoice and
# 0000000000000000000000000000000000#
return super().form_valid(form)
def get_context_data(self, **kwargs):
@ -42,6 +89,145 @@ class PaymentCreateView(CreateView):
company_pk = self.request.GET.get('company', '')
form.fields['company'].initial = company_pk
# container_ids = [int(id) for id in container_ids if id.strip().isdigit()]
# form.fields['containers'].initial = container_ids
container_ids = self.request.GET.get('containers', '').split(',')
container_ids = [int(_id) for _id in container_ids if _id.strip().isdigit()]
total_charges = {}
if container_ids:
containers = Container.objects.filter(
id__in=container_ids,
expedited=True,
payment_containers__isnull=True
)
for container in containers:
charges = calculate_charges(container)
total_charges['days'] = total_charges.get('days', 0) + charges.get('days', 0)
total_charges['storage_charge'] = total_charges.get('storage_charge', 0) + charges.get('storage_charge', 0)
total_charges['services_charge'] = total_charges.get('services_charge', 0) + charges.get('services_charge', 0)
total_charges['total'] = total_charges.get('total', 0) + charges.get('total', 0)
form.fields['total_amount'].initial = total_charges['total']
return form
def some_view(request):
buffer = io.BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4)
story = []
styles = getSampleStyleSheet()
# Custom styles
styles.add(ParagraphStyle(
name='RightAlign',
parent=styles['Normal'],
alignment=2 # right alignment
))
# Header data
depot_data = [
['ABC Depot Ltd.'],
['123 Depot Street'],
['Sofia, Bulgaria'],
['VAT: BG123456789'],
['Phone: +359 2 123 4567']
]
customer_data = [
['Customer Company Ltd.'],
['456 Customer Ave.'],
['London, UK'],
['VAT: GB987654321'],
['Contact: John Doe']
]
# Create header table
header_data = [[
Table(depot_data, colWidths=[200]),
Table(customer_data, colWidths=[200])
]]
header_table = Table(header_data, colWidths=[250, 250])
header_table.setStyle(TableStyle([
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
story.append(header_table)
story.append(Spacer(1, 30))
# Invoice title and number
story.append(Paragraph('INVOICE', styles['Heading1']))
story.append(Paragraph('Invoice No: INV-20240220-0001', styles['Normal']))
story.append(Paragraph('Date: 20.02.2024', styles['Normal']))
story.append(Spacer(1, 20))
# Invoice table data
table_data = [
['Container Type', 'Days', 'Swept', 'Washed', 'Amount'],
['20\' Container', '15', 'Yes', 'No', '€450.00'],
['40\' Container', '10', 'Yes', 'Yes', '€750.00'],
]
# Create invoice table
invoice_table = Table(table_data, colWidths=[120, 80, 80, 80, 100])
invoice_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('GRID', (0, 0), (-1, -1), 1, colors.black),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
]))
story.append(invoice_table)
story.append(Spacer(1, 20))
# Total amount
total_text = f'Total Amount: €1,200.00'
story.append(Paragraph(total_text, styles['RightAlign']))
# Build PDF
doc.build(story, onFirstPage=add_watermark)
pdf = buffer.getvalue()
buffer.close()
# Create response
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'inline; filename="invoice.pdf"'
response.write(pdf)
return response
def add_watermark(canvas, doc):
canvas.saveState()
canvas.setFillColorRGB(0.9, 0.9, 0.9)
canvas.setFont('Helvetica', 80)
canvas.rotate(45)
canvas.drawString(200, 000, 'ORIGINAL')
canvas.restoreState()
return response
def on_payment_result(encoded, checksum):
fields_dict, checksum_result = EPay.payment_result(encoded, checksum)
status = fields_dict["STATUS"]
invoice = fields_dict["INVOICE"]
if not checksum_result:
return f"INVOICE={invoice}:STATUS=ERR"
payment = Payment.objects.get(invoice_number=invoice)
if not payment:
return f"INVOICE={invoice}:STATUS=NO"
match status:
case "PAID":
payment.status = "paid"
payment.paid_on = datetime.now()
payment.pay_time = fields_dict["PAY_TIME"]
payment.pay_stan = fields_dict["STAN"]
payment.pay_bcode = fields_dict["BCODE"]
case "DENIED":
payment.status = "denied"
case "EXPIRED":
payment.status = "expired"
payment.save()
return f"INVOICE={invoice}:STATUS=OK"

@ -37,4 +37,24 @@ insert into accounts_clientpermission (id, codename, name) values (5, 'can_view_
insert into accounts_clientpermission (id, codename, name) values (6, 'can_manage_payment', 'Can manage payment');
insert into accounts_clientpermission (id, codename, name) values (7, 'can_manage_company_users', 'Can manage company users');
INSERT INTO payments_containertariffperiod
(container_size, from_days, to_days, daily_rate)
VALUES
('20', 1, 3, 5.00),
('20', 4, 7, 10.00),
('20', 8, 15, 15.00),
('20', 16, NULL, 20.00);
-- For 40/45 feet containers
INSERT INTO payments_containertariffperiod
(container_size, from_days, to_days, daily_rate)
VALUES
('40', 1, 3, 10.00),
('40', 4, 7, 15.00),
('40', 8, 15, 20.00),
('40', 16, NULL, 25.00);
INSERT INTO payments_additionalfees
(reefer_daily_supplement, sweeping_fee, fumigation_fee)
VALUES
(3.00, 35.00, 75.00);

@ -1,4 +1,4 @@
from django.forms import ModelForm
from django.forms import ModelForm, TextInput
from django.urls import reverse_lazy
from preinfo.models import Preinfo
@ -8,7 +8,12 @@ class PreinfoBaseForm(ModelForm):
class Meta:
model = Preinfo
fields = '__all__'
widgets = {
'container_number': TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}
# success_url = reverse_lazy('client_preinfo')
class PreinfoCreateForm(PreinfoBaseForm):
"""
@ -24,7 +29,6 @@ class PreinfoCreateForm(PreinfoBaseForm):
'received'] # Exclude fields that should not be set by the user
class PreinfoEditForm(ModelForm):
class PreinfoEditForm(PreinfoBaseForm):
class Meta:
model = Preinfo
fields = ['container_number', 'container_type', 'line']

@ -6,7 +6,8 @@ from django.views import View
from django.views.generic import CreateView, ListView, UpdateView
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number, send_test_email
from payments.services import EPay
from preinfo.forms import PreinfoCreateForm, PreinfoEditForm
from preinfo.models import Preinfo

@ -44,5 +44,5 @@
<text x="100" y="200" font-family="monospace" font-size="20" text-anchor="middle" fill="#5B2C06" font-weight="bold">K-DepoT</text>
<!-- Slogan -->
<text x="100" y="220" font-family="monospace" font-size="12" text-anchor="middle" fill="#5B2C06" letter-spacing="2">SAFE AND READY</text>
<text x="100" y="220" font-family="monospace" font-size="20" text-anchor="middle" fill="#5B2C06" letter-spacing="2">SAFE AND READY</text>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,35 @@
function validateContainerNumber(input) {
const containerNumber = input.value.trim();
const feedbackElement = input.nextElementSibling;
if (!containerNumber) {
input.classList.remove('invalid-container', 'incomplete-container');
if (feedbackElement) feedbackElement.remove();
return;
}
if (containerNumber.length > 0 && containerNumber.length < 11) {
input.classList.remove('invalid-container');
input.classList.add('incomplete-container');
if (feedbackElement) feedbackElement.remove();
return;
}
fetch(`${validateContainerUrl}${containerNumber}/`)
.then(response => response.json())
.then(data => {
input.classList.remove('incomplete-container');
if (!data.valid) {
input.classList.add('invalid-container');
if (!feedbackElement) {
const feedback = document.createElement('div');
feedback.className = 'validation-feedback';
feedback.textContent = 'Invalid container number';
input.parentNode.insertBefore(feedback, input.nextSibling);
}
} else {
input.classList.remove('invalid-container');
if (feedbackElement) feedbackElement.remove();
}
});
}

@ -12,12 +12,10 @@ document.addEventListener('DOMContentLoaded', function() {
table.addEventListener('click', function(e) {
const row = e.target.closest('.selectable-row');
if (!row) return;
const checkbox = row.querySelector('input[type="checkbox"]');
if (!checkbox) return;
if (selectionMode === 'single') {
// Deselect all other rows
table.querySelectorAll('.selected-row').forEach(selectedRow => {
if (selectedRow !== row) {
selectedRow.classList.remove('selected-row');
@ -25,22 +23,15 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
}
// Toggle current row
row.classList.toggle('selected-row');
checkbox.checked = !checkbox.checked;
// Update buttons state
const selectedRows = table.querySelectorAll('.selected-row');
document.querySelectorAll('[data-requires-selection]').forEach(button => {
button.disabled = selectedRows.length === 0;
});
// Handle edit/delete buttons from original crud-list.js
const objectId = row.dataset.id;
const editBtn = document.getElementById('editBtn');
const deleteBtn = document.getElementById('deleteBtn');
if (editBtn) {
editBtn.removeAttribute('disabled');
editBtn.href = editBtn.dataset.url?.replace('0', objectId);
@ -54,19 +45,14 @@ document.addEventListener('DOMContentLoaded', function() {
toggleSelectAllBtn.addEventListener('click', function() {
const rows = table.querySelectorAll('.selectable-row');
allSelected = !allSelected;
rows.forEach(row => {
const checkbox = row.querySelector('input[type="checkbox"]');
checkbox.checked = allSelected;
row.classList.toggle('selected-row', allSelected);
});
// Update other buttons state
document.querySelectorAll('[data-requires-selection]').forEach(button => {
button.disabled = !allSelected;
});
// Update button text
this.textContent = allSelected ? 'Unselect All' : 'Select All';
});

@ -36,7 +36,23 @@ input[type="email"],
input[type="password"],
input[type="date"],
input[type="number"],
select,
select {
width: 100%;
padding: 8px;
margin-bottom: 0px;
border: 1px solid #a57d52;
border-radius: 4px;
box-sizing: border-box;
font-family: "Noto Sans", sans-serif;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-size: 1.1rem;
font-variation-settings:
"wdth" 100;
}
textarea {
width: 100%;
padding: 8px;
@ -52,21 +68,28 @@ textarea {
font-size: 1.1rem;
font-variation-settings:
"wdth" 100;
height: 100px; /* Adjust height as needed */
min-height: 100px; /* Minimum height */
resize: vertical; /* Allow vertical resizing */
}
/* Submit button styling */
button[type="submit"] {
background-color: #a57d52;
color: white;
button[type="submit"],
button.btn-primary,
button.btn-secondary {
background-color: #F2E8DB;
padding: 10px 20px;
border: none;
border: 1px solid #a57d52;
border-radius: 4px;
cursor: pointer;
/*margin-top: 20px;*/
}
button[type="submit"]:hover {
background-color: #a67744;
button[type="submit"]:hover,
button.btn-primary:hover,
button.btn-secondary:hover{
background-color: #EDDECB;
}
/* Error messages */
@ -89,7 +112,7 @@ button[type="submit"]:hover {
.card {
margin-top: 24px;
background-color: white;
background-color: #EDDECB;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
@ -279,3 +302,33 @@ button[type="submit"]:hover {
align-items: center;
width: 100%;
}
@media print {
/* Hide sidebar */
.sidebar {
display: none !important;
}
/* Make main content full width */
.main-content {
margin-left: 0 !important;
width: 100% !important;
}
/* Hide other non-printable elements if needed */
.no-print {
display: none !important;
}
/* Ensure white background and black text */
body {
background: white !important;
color: black !important;
}
/* Remove shadows and borders */
* {
box-shadow: none !important;
border: none !important;
}
}

@ -7,6 +7,7 @@ body {
align-items: center;
justify-content: center;
padding: 1rem;
background-color: #ead9cb;
}
.wave {
@ -22,7 +23,7 @@ body {
}
.login-container {
background-color: white;
background-color: #ead9cb;
border-radius: 0.75rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
overflow: hidden;
@ -31,6 +32,8 @@ body {
display: flex;
flex-direction: column;
animation: fadeIn 0.8s ease-in-out;
margin: 2rem; /* Add margin to see the background */
}
@media (min-width: 768px) {

@ -1,76 +1,3 @@
/*.table {*/
/* width: 100%;*/
/* border-collapse: separate;*/
/* border-spacing: 0;*/
/* margin: 10px auto;*/
/* border: 1px solid #a57d52;*/
/* border-radius: 10px;*/
/* overflow: hidden;*/
/*}*/
/*.table th {*/
/* text-align: left;*/
/* padding: 0.75rem;*/
/* font-size: 0.75rem;*/
/* text-transform: uppercase;*/
/* font-weight: 600;*/
/* border-bottom: 1px solid #a57d52;*/
/* background-color: #F2E8DB;*/
/*}*/
/*.table td {*/
/* padding: 0.75rem;*/
/* font-size: 0.875rem;*/
/* border-bottom: 1px solid #a57d52;*/
/*}*/
/*!* Last row shouldn't have bottom border *!*/
/*.table tr:last-child td {*/
/* border-bottom: none;*/
/*}*/
/*!* Selection styles *!*/
/*.selectable-row {*/
/* cursor: pointer;*/
/*}*/
/*.selected-row {*/
/* background-color: rgba(165, 125, 82, 0.1);*/
/*}*/
/*.status-received {*/
/* background-color: #e8f5e9;*/
/* color: #2e7d32;*/
/*}*/
/*.status-pending {*/
/* background-color: #fff3e0;*/
/* color: #ef6c00;*/
/*}*/
/*.status-overdue {*/
/* background-color: #ffebee;*/
/* color: #c62828;*/
/*}*/
table {
width: 100%;
border-collapse: collapse;
@ -208,4 +135,33 @@ table tr:last-child td {
color: #990000;
}
.status-ok {
color: #afddc0; /* green */
font-weight: 600;
}
.status-deleted {
color: #e49486; /* green */
font-weight: 600;
}
.status-inactive {
color: #b2b5b3; /* green */
font-weight: 600;
}
.invalid-container {
color: #df6161; /* red */
font-weight: 600;
}
.validation-feedback {
color: #dc2626;
font-size: 0.875rem;
margin-top: 0.25rem;
}
input.invalid-container {
border-color: #dc2626;
background-color: #fee2e2;
}
input.incomplete-container {
background-color: #fbf3a2; /* red */
border-color: #dc2626;
}

@ -5,19 +5,66 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Operator Dashboard | Container Depot</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<title>Barrier Dashboard | Container Depot</title>
{# <link rel="preconnect" href="https://fonts.googleapis.com">#}
{# <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>#}
{# <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;600&display=swap" rel="stylesheet">#}
<link rel="stylesheet" href="{% static 'styles/forms.css' %}?v={% now 'U' %}">
<link rel="stylesheet" href="{% static 'styles/base.css' %}?v={% now 'U' %}">
<link rel="stylesheet" href="{% static 'styles/tables.css' %}?v={% now 'U' %}">
<style>
.barrier-container {
display: flex;
flex-direction: column;
gap: 20px;
padding: 20px;
max-width: 600px;
margin: 0 auto;
height: 100vh;
justify-content: center;
}
.barrier-button {
background-color: #F2E8DB;
border: 2px solid #a57d52;
border-radius: 8px;
padding: 40px;
font-size: 24px;
font-weight: 600;
color: #a57d52;
cursor: pointer;
text-align: center;
text-decoration: none;
display: flex;
align-items: center;
justify-content: center;
min-height: 75px;
}
.barrier-button:hover {
background-color: #EDDECB;
}
{# <script src="https://cdn.tailwindcss.com"></script>#}
@media (max-height: 600px) {
.barrier-button {
min-height: 100px;
padding: 20px;
}
}
</style>
</head>
<body>
{% block content %}
{% endblock content %}
{% block extra_js %}
<script>
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
</script>
<script src="{% static 'js/container_validation.js' %}"></script>
{% endblock %}
</body>
</html>

@ -1,73 +1,42 @@
{% extends 'barrier/barrier-base.html' %}
{% extends "barrier/barrier-base.html" %}
{% load static %}
{% block content %}
<div class="sidebar p-5 text-white border-b border-blue-700">
<h1 class="text-xl font-bold">Container Depot</h1>
<p class="text-sm text-blue-200">Line Operator Portal</p>
</div>
<div class="flex min-h-screen items-center justify-center">
<div class="sidebar w-[400px] h-[600px] text-white flex flex-col rounded-lg overflow-hidden">
<div class="sidebar w-[100%] h-full text-white flex flex-col">
<div class="p-5 border-b border-blue-700">
<h1 class="text-xl font-bold">Container Depot</h1>
<p class="text-sm text-blue-200">Line Operator Portal</p>
</div>
<nav class="flex-grow py-4">
<div class="px-4 py-2 text-xs text-blue-300 uppercase tracking-wider">Main</div>
<a href="{% url 'barrier_dashboard' %}" class="nav-item active flex items-center px-6 py-10 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
Dashboard
</a>
<a href="{% url 'preinfo_search' %}?param=container_receive" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
</svg>
Receive container
<div class="barrier-container">
<a href="{% url 'container_receive' %}" class="barrier-button">
Receive Container
</a>
<a href="{% url 'container_search' %}?param=container_expedition" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
</svg>
Expedite container
<a href="{% url 'container_expedition' %}" class="barrier-button">
Expedite Container
</a>
<a href="#" class="nav-item flex items-center px-6 py-3 text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
Photos
<a href="{% url 'barrier_photos' %}" class="barrier-button">
Take Photos
</a>
{# <div class="px-4 py-2 mt-6 text-xs text-blue-300 uppercase tracking-wider">Account</div>#}
{# <a href="#" class="nav-item flex items-center px-6 py-3 text-white">#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />#}
{# </svg>#}
{# Settings#}
{# </a>#}
</nav>
{# <div class="p-4 border-t border-blue-700">#}
{# <div class="flex items-center">#}
{# <div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold">#}
{# LO#}
{# </div>#}
{# <div class="ml-3">#}
{# <p class="text-sm font-medium">Maersk Line</p>#}
{# <p class="text-xs text-blue-300">Line Operator</p>#}
{# </div>#}
{# <button class="ml-auto text-white">#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />#}
{# </svg>#}
{# </button>#}
{# </div>#}
{# </div>#}
</div>
</div>
<h2 class="text-2xl font-bold mb-4">Recent History</h2>
<table>
<thead>
<tr>
<th>Date</th>
<th>Container Number</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for container in recent_containers %}
<tr>
<td>{{ container.number }}</td>
{% if container.expedited %}
<td>{{ container.expedited_on }}</td>
<td>Expedited</td>
{% else %}
<td>{{ container.received_on }}</td>
<td>Received</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

@ -24,6 +24,23 @@
{% endblock content %}
</div>
</main>
{% block custom_styles %}
{% endblock custom_styles %}
{% block extra_js %}
<script>
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
</script>
<script src="{% static 'js/container_validation.js' %}"></script>
{% endblock extra_js %}
{% block crud_js %}
{% endblock crud_js %}
</body>
</html>

@ -10,7 +10,7 @@
{% url 'register' as register_url %}
{% url 'client_company' as client_company_url %}
{% url 'client_line' as client_line_url %}
{% url 'user_list' as user_list_url %}
<nav class="nav-menu">
<div class="section-title">Main</div>
<a href="{{ dashboard_url }}" class="nav-item {% if request.path == dashboard_url %}active{% endif %}">
@ -43,7 +43,7 @@
</a>
{% if request.user.UserType == 'CA' or 1 == 1 %}
<div class="section-title account">Nomenclatures</div>
<a href="{{ register_url }}" class="nav-item {% if request.path == register_url %}active{% endif %}">
<a href="{{ user_list_url }}" class="nav-item {% if request.path == user_list_url %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />

@ -13,6 +13,7 @@
<th>Vehicles left</th>
<th>Created on</th>
<th>Created by</th>
<th>Status</th>
{% endblock table_header %}
{% block table_data %}
@ -26,6 +27,7 @@
<td>{{ object.vehicles_left }}</td>
<td>{{ object.created_on }}</td>
<td>{{ object.created_by.username }}</td>
<td>{{ object.status }}</td>
{% endblock %}
{% block buttons %}
@ -33,11 +35,3 @@
<a href="#" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
{% endblock buttons %}
{% block create_modal_header %}
<h2>Create Booking</h2>
{% endblock %}
{% block modal_header %}
<h2>Edit Booking</h2>
{% endblock modal_header %}

@ -9,15 +9,17 @@
<th>Line</th>
<th>Created on</th>
<th>Created by</th>
<th>Status</th>
{% endblock table_header %}
{% block table_data %}
<td>{{ object.id }}</td>
<td>{{ object.container_number }}</td>
<td>{{ object.container_number|upper }}</td>
<td>{{ object.container_type }}</td>
<td>{{ object.line.short_name }}</td>
<td>{{ object.created_on }}</td>
<td>{{ object.created_on|date:"d.m.Y h:m" }}</td>
<td>{{ object.created_by.username }}</td>
<td>{{ object.received|yesno:"Received,Pending" }}</td>
{% endblock %}
{% block buttons %}

@ -1,20 +1,62 @@
{% extends 'barrier/barrier-base.html' %}
{% load static %}
{% block content %}
<form method="post">
<h2 style="color: #a57d52; margin-bottom: 20px; text-align: center;">Container Expedition</h2>
<div class="barrier-container">
{% if show_search %}
<form method="get" class="w-full">
<div class="p-6">
<label for="id_number" class="block text-2xl mb-4">
Enter Booking Number
</label>
<div class="form-group">
<input type="text"
id="id_number"
name="number"
value="{{ search_number }}"
class="px-4 py-3 text-xl border-2 rounded-lg"
required
autocomplete="off"
autofocus>
<div class="form-group">
<button type="submit" class="btn-primary">
Search
</button>
</div>
{% if error %}
<div class="text-red-600 text-xl text-center">
{{ error }}
</div>
{% endif %}
</div>
</div>
</form>
{% elif form %}
<form method="post" class="w-full">
{% csrf_token %}
<input type="hidden" name="booking_id" value="{{ booking.id }}">
<input type="hidden" name="container_id" value="{{ container.id }}">
<input type="hidden" name="search_number" value="{{ search_number }}">
<div class="p-6">
<div class="flex flex-col gap-4">
<div class="form-group">
<p>
<label for="id_booking_number">Booking number:</label>
<input type="text" name="booking_number" value="{{ booking.number }}" maxlength="11" required id="id_booking_number" readonly disabled>
</p>
{{ form.as_p }}
<div >
<button type="submit" >
</div>
<div class="form-group">
<button type="submit" name="expedite" class="btn-primary">
Expedite container
</button>
<button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
Back
</button>
</div>
</div>
</div>
</form>
{% endif %}
</div>
{% endblock content %}

@ -1,10 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
{# templates/container-photos.html #}
{% extends "barrier/barrier-base.html" %}
{% load static %}
{% block content %}
<div class="barrier-container">
<h2 class="text-2xl mb-4">Container Photos - {{ container.number }}</h2>
{% csrf_token %} {# Added CSRF token here #}
<div class="photo-upload-section mb-4">
<video id="video" class="mb-4 w-full" autoplay></video>
<canvas id="canvas" style="display:none;"></canvas>
<div class="flex justify-between mb-4">
<button id="capture" class="btn-primary" {% if photos_count >= 3 %}disabled{% endif %}>
Take Photo ({{ photos_count }}/3)
</button>
<a href="{% url 'barrier_dashboard' %}" class="btn-secondary">Back to Dashboard</a>
</div>
</div>
<div id="photos-preview" class="grid grid-cols-3 gap-4">
{% for photo in photos %}
<div class="photo-preview">
<img src="{{ photo.photo.url }}" alt="Container photo" class="w-full">
</div>
{% endfor %}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const captureButton = document.getElementById('capture');
let photoCount = {{ photos_count }};
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
video.srcObject = stream;
})
.catch(err => {
console.error('Camera error:', err);
alert('Could not access camera. Please check permissions.');
});
captureButton.addEventListener('click', () => {
if (photoCount >= 3) return;
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
const photo = canvas.toDataURL('image/jpeg').split(',')[1];
fetch(`/api/damages/{{ container.pk }}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({
depot_id: {{ container.pk }},
photo_extension: 'jpg',
photo: photo
})
})
.then(async response => {
if (response.status === 201) {
photoCount++;
if (photoCount >= 3) {
captureButton.disabled = true;
}
location.reload();
} else {
const error = await response.json();
throw new Error(error.error || 'Upload failed');
}
})
.catch(error => {
console.error('Error:', error);
alert(error.message);
});
});
});
</script>
</div>
{% endblock %}

@ -1,593 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Barrier Staff Interface | Container Depot</title>
{# <script src="https://cdn.tailwindcss.com"></script>#}
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
{#background-color: #f5f7fa;#}
}
.sidebar {
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%);
transition: all 0.3s;
}
.content-area {
transition: all 0.3s;
}
.nav-item {
transition: all 0.2s;
}
.nav-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.nav-item.active {
background-color: rgba(255, 255, 255, 0.2);
border-left: 4px solid white;
}
.card {
transition: all 0.3s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.tab {
transition: all 0.2s;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.form-section {
animation: slideIn 0.5s ease-in-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.camera-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.9);
z-index: 50;
display: none;
animation: fadeIn 0.3s ease-in-out;
}
.camera-frame {
border: 2px solid #fff;
box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.5);
}
.image-preview {
width: 120px;
height: 120px;
background-color: #f3f4f6;
border: 2px dashed #d1d5db;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.image-preview svg {
color: #9ca3af;
}
.image-preview.has-image {
border: none;
}
.image-preview.has-image svg {
display: none;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(255, 0, 0, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
}
}
</style>
</head>
<body class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<!-- Main Content -->
<main class="content-area flex-1 overflow-y-auto">
<!-- Top Navigation -->
<header class="bg-white shadow-sm">
<div class="flex items-center justify-between px-6 py-4">
<div class="flex items-center">
<h2 class="text-xl font-semibold text-gray-800">Receive Container</h2>
</div>
<div class="flex items-center space-x-4">
<div class="relative">
<span class="absolute top-0 right-0 -mt-1 -mr-1 flex h-4 w-4">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-4 w-4 bg-red-500 text-xs text-white justify-center items-center">3</span>
</span>
<button class="text-gray-500 hover:text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
{% extends "barrier/barrier-base.html" %}
{% block content %}
<div class="barrier-container">
{% if show_search %}
<form method="get" class="w-full">
<div class="p-6">
<label for="id_number" class="block text-2xl mb-4">
Enter Container Number
</label>
<div>
<input type="text"
id="id_number"
name="number"
value="{{ search_number }}"
class="px-4 py-3 text-xl border-2 rounded-lg"
required
autocomplete="off"
autofocus>
<div class="form-group">
<button type="submit" class="btn-primary">
Search
</button>
</div>
<span class="text-gray-700 font-medium">Gate North</span>
</div>
{% if error %}
<div class="text-red-600 text-xl text-center">
{{ error }}
</div>
</header>
<!-- Container Reception Content -->
<div id="receiveContent" class="tab-content active p-6">
<div class="bg-white rounded-lg shadow mb-6">
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
<div>
<h3 class="text-lg font-semibold text-gray-800">Container Reception</h3>
<p class="text-sm text-gray-600 mt-1">Process incoming containers</p>
</div>
<div class="flex items-center">
<span class="bg-green-100 text-green-800 text-xs font-semibold px-3 py-1 rounded-full">Gate Open</span>
{% endif %}
</div>
</div>
<form id="receiveForm" class="space-y-6" method="POST">
</form>
{% elif form %}
<form method="post" class="w-full">
{% csrf_token %}
<input type="hidden" name="search_number" value="{{ search_number }}">
<div class="p-6">
<label for="id_number" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label>
{# <label for="id_number" >Container Number</label>#}
{# <input type="text" id="id_number" name="number" placeholder="Enter Container Number" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4">#}
{# <button type="button" id="check-preinfo-btn">Check Preinfo</button>#}
{# <label for="containerNumber" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label>#}
{# <div class="flex">#}
{# <input type="text" id="id_number" name="number" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>#}
{# <button type="button" id="check-preinfo-btn" class="px-4 py-2 bg-blue-600 text-white rounded-r-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">#}
{# Search#}
{# </button>#}
{# </div>#}
</div>
<div id="preinfoData">
<div class="bg-blue-50 border border-blue-200 rounded-md p-4 mb-6">
<div class="flex items-start">
<div class="flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3 flex-1">
<h3 class="text-sm font-medium text-blue-800">Preinfo Found</h3>
<div class="mt-2 text-sm text-blue-700">
<div class="grid grid-cols-2 gap-4">
<div>
<div>
<span class="font-medium">Container Number:</span>
<input type="text" id="id_number" name="number" value="{{ preinfo.container_number }}" readonly>
</div>
<div>
<span class="font-medium">Container Type:</span>
<input type="text" id="id_container_type" name="id_container_type" value="{{ preinfo.container_type }}" readonly>
</div>
<div>
<span class="font-medium">Line Operator:</span>
<input type="text" id="id_line" name="id_line" value="{{ preinfo.line.name }}" readonly>
</div>
</div>
</div>
{# <p class="mt-2"><span class="font-medium">Notes:</span> <span id="preinfoNotes">Container reported with minor damage on right side panel.</span></p>#}
<div class="flex flex-col gap-4">
<div class="form-group">
{{ form.as_p }}
</div>
</div>
</div>
</div>
</div>
<div>
<label for="id_receive_vehicle">Receive vehicle:</label>
<input type="text" name="receive_vehicle" maxlength="100" id="id_receive_vehicle">
</div>
<div>
<label for="id_damages">Damages:</label>
<textarea name="damages" cols="40" rows="10" id="id_damages"></textarea>
</div>
<div>
<label for="id_heavy_damaged">Heavy damaged:</label>
<input type="checkbox" name="heavy_damaged" id="id_heavy_damaged">
</div>
<div>
<label for="id_position">Position:</label>
<input type="text" name="position" maxlength="100" id="id_position">
</div>
<div class="form-section flex justify-end">
<button type="submit" class="px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Complete Reception
<div class="form-group">
<button type="submit" class="btn-primary">
Receive Container
</button>
</div>
<input type="hidden" name="preinfo_id" id="preinfo_id" value="{{ preinfo.pk }}">
</form>
</div>
<div class="bg-white rounded-lg shadow">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-semibold text-gray-800">Recent Container Movements</h3>
</div>
<div class="p-6">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Container</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Line</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Direction</th>#}
{# <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>#}
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{% for container in containers %}
<tr>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">
{{ container.number }}</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{{ container.container_type }}</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{{ container.line.name }}</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{{ container.received_on }}</td>
{# <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">IN</td>#}
{# <td class="px-4 py-3 whitespace-nowrap">#}
{# <span class="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">Complete</span>#}
{# </td>#}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Container Expedition Content -->
</main>
<!-- Camera Overlay -->
<div id="cameraOverlay" class="camera-overlay">
<div class="h-full flex flex-col">
<div class="p-4 flex justify-between items-center">
<h3 class="text-lg font-semibold text-white">Take Container Photo</h3>
<button id="closeCamera" class="text-white hover:text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
<button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
Back
</button>
</div>
<div class="flex-grow flex items-center justify-center p-4">
<div class="relative">
<div class="camera-frame w-full max-w-2xl aspect-[4/3] bg-gray-800 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<!-- Guide lines for container positioning -->
<div class="absolute inset-0 border-2 border-dashed border-white opacity-50 m-8"></div>
<!-- Positioning guides -->
<div class="absolute top-8 left-8 border-t-2 border-l-2 border-white w-8 h-8"></div>
<div class="absolute top-8 right-8 border-t-2 border-r-2 border-white w-8 h-8"></div>
<div class="absolute bottom-8 left-8 border-b-2 border-l-2 border-white w-8 h-8"></div>
<div class="absolute bottom-8 right-8 border-b-2 border-r-2 border-white w-8 h-8"></div>
</div>
</div>
</div>
<div class="p-4 flex justify-center">
<button id="capturePhoto" class="w-16 h-16 rounded-full bg-red-600 border-4 border-white flex items-center justify-center pulse">
<div class="w-12 h-12 rounded-full bg-red-600"></div>
</button>
</div>
</div>
</form>
{% endif %}
</div>
{# <script>#}
{# // Set current date as default#}
{# const today = new Date().toISOString().split('T')[0];#}
{# document.getElementById('arrivalDate').value = today;#}
{# document.getElementById('expeditionDate').value = today;#}
{# #}
{# // Tab Navigation#}
{# const receiveContent = document.getElementById('receiveContent');#}
{# const expediteContent = document.getElementById('expediteContent');#}
{# #}
{# const receiveNav = document.getElementById('receiveNav');#}
{# const expediteNav = document.getElementById('expediteNav');#}
{# #}
{# receiveNav.addEventListener('click', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // Update content visibility#}
{# receiveContent.classList.add('active');#}
{# expediteContent.classList.remove('active');#}
{# #}
{# // Update navigation styling#}
{# document.querySelector('.nav-item.active').classList.remove('active');#}
{# receiveNav.classList.add('active');#}
{# #}
{# // Update header title#}
{# document.querySelector('header h2').textContent = 'Receive Container';#}
{# });#}
{# #}
{# expediteNav.addEventListener('click', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // Update content visibility#}
{# receiveContent.classList.remove('active');#}
{# expediteContent.classList.add('active');#}
{# #}
{# // Update navigation styling#}
{# document.querySelector('.nav-item.active').classList.remove('active');#}
{# expediteNav.classList.add('active');#}
{# #}
{# // Update header title#}
{# document.querySelector('header h2').textContent = 'Expedite Container';#}
{# });#}
{# #}
{# // Container search functionality#}
{# document.getElementById('searchContainer').addEventListener('click', function() {#}
{# const containerNumber = document.getElementById('containerNumber').value;#}
{# if (containerNumber) {#}
{# document.getElementById('preinfoData').classList.remove('hidden');#}
{# } else {#}
{# alert('Please enter a container number');#}
{# }#}
{# });#}
{# #}
{# document.getElementById('searchExpediteContainer').addEventListener('click', function() {#}
{# const containerNumber = document.getElementById('expediteContainerNumber').value;#}
{# if (containerNumber) {#}
{# document.getElementById('orderData').classList.remove('hidden');#}
{# } else {#}
{# alert('Please enter a container number');#}
{# }#}
{# });#}
{# #}
{# // Camera functionality#}
{# const cameraOverlay = document.getElementById('cameraOverlay');#}
{# const takePhotoBtn = document.getElementById('takePhoto');#}
{# const expediteTakePhotoBtn = document.getElementById('expediteTakePhoto');#}
{# const closeCamera = document.getElementById('closeCamera');#}
{# const capturePhoto = document.getElementById('capturePhoto');#}
{# #}
{# let currentImageTarget = null;#}
{# #}
{# function openCamera(imageTarget) {#}
{# cameraOverlay.style.display = 'block';#}
{# currentImageTarget = imageTarget;#}
{# }#}
{# #}
{# takePhotoBtn.addEventListener('click', function() {#}
{# // Find the first empty preview slot#}
{# for (let i = 1; i <= 4; i++) {#}
{# const previewEl = document.getElementById(`preview${i}`);#}
{# if (!previewEl.classList.contains('has-image')) {#}
{# openCamera(`preview${i}`);#}
{# break;#}
{# }#}
{# }#}
{# });#}
{# #}
{# expediteTakePhotoBtn.addEventListener('click', function() {#}
{# // Find the first empty preview slot#}
{# for (let i = 1; i <= 2; i++) {#}
{# const previewEl = document.getElementById(`expeditePreview${i}`);#}
{# if (!previewEl.classList.contains('has-image')) {#}
{# openCamera(`expeditePreview${i}`);#}
{# break;#}
{# }#}
{# }#}
{# });#}
{# #}
{# closeCamera.addEventListener('click', function() {#}
{# cameraOverlay.style.display = 'none';#}
{# });#}
{# #}
{# capturePhoto.addEventListener('click', function() {#}
{# if (currentImageTarget) {#}
{# const previewEl = document.getElementById(currentImageTarget);#}
{# #}
{# // Create a container SVG image#}
{# const containerSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");#}
{# containerSvg.setAttribute("class", "w-full h-full");#}
{# containerSvg.setAttribute("viewBox", "0 0 120 120");#}
{# #}
{# // Random container color#}
{# const colors = ['#2563eb', '#059669', '#d97706', '#7c3aed'];#}
{# const randomColor = colors[Math.floor(Math.random() * colors.length)];#}
{# #}
{# // Create container body#}
{# const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");#}
{# rect.setAttribute("x", "10");#}
{# rect.setAttribute("y", "20");#}
{# rect.setAttribute("width", "100");#}
{# rect.setAttribute("height", "80");#}
{# rect.setAttribute("fill", randomColor);#}
{# containerSvg.appendChild(rect);#}
{# #}
{# // Add some container details#}
{# const detail1 = document.createElementNS("http://www.w3.org/2000/svg", "rect");#}
{# detail1.setAttribute("x", "20");#}
{# detail1.setAttribute("y", "30");#}
{# detail1.setAttribute("width", "80");#}
{# detail1.setAttribute("height", "20");#}
{# detail1.setAttribute("fill", shadeColor(randomColor, -20));#}
{# containerSvg.appendChild(detail1);#}
{# #}
{# const detail2 = document.createElementNS("http://www.w3.org/2000/svg", "rect");#}
{# detail2.setAttribute("x", "20");#}
{# detail2.setAttribute("y", "60");#}
{# detail2.setAttribute("width", "80");#}
{# detail2.setAttribute("height", "30");#}
{# detail2.setAttribute("fill", shadeColor(randomColor, -20));#}
{# containerSvg.appendChild(detail2);#}
{# #}
{# // Clear the preview and add the new SVG#}
{# previewEl.innerHTML = '';#}
{# previewEl.appendChild(containerSvg);#}
{# previewEl.classList.add('has-image');#}
{# #}
{# // Close the camera#}
{# cameraOverlay.style.display = 'none';#}
{# }#}
{# });#}
{# #}
{# // Helper function to darken/lighten colors#}
{# function shadeColor(color, percent) {#}
{# let R = parseInt(color.substring(1,3), 16);#}
{# let G = parseInt(color.substring(3,5), 16);#}
{# let B = parseInt(color.substring(5,7), 16);#}
{# #}
{# R = parseInt(R * (100 + percent) / 100);#}
{# G = parseInt(G * (100 + percent) / 100);#}
{# B = parseInt(B * (100 + percent) / 100);#}
{# #}
{# R = (R<255)?R:255; #}
{# G = (G<255)?G:255; #}
{# B = (B<255)?B:255; #}
{# #}
{# R = Math.round(R);#}
{# G = Math.round(G);#}
{# B = Math.round(B);#}
{# #}
{# const RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));#}
{# const GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));#}
{# const BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));#}
{# #}
{# return "#"+RR+GG+BB;#}
{# }#}
{# #}
{# // Form submissions#}
{# document.getElementById('receiveForm').addEventListener('submit', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // Check if container data is loaded#}
{# if (document.getElementById('preinfoData').classList.contains('hidden')) {#}
{# alert('Please search for a container first');#}
{# return;#}
{# }#}
{# #}
{# // In a real app, this would send data to the server#}
{# alert('Container reception completed successfully!');#}
{# this.reset();#}
{# document.getElementById('preinfoData').classList.add('hidden');#}
{# #}
{# // Reset image previews#}
{# for (let i = 1; i <= 4; i++) {#}
{# const previewEl = document.getElementById(`preview${i}`);#}
{# previewEl.classList.remove('has-image');#}
{# previewEl.innerHTML = `#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />#}
{# </svg>#}
{# `;#}
{# }#}
{# #}
{# // Add the first two images back#}
{# document.getElementById('preview1').classList.add('has-image');#}
{# document.getElementById('preview1').innerHTML = `#}
{# <svg class="w-full h-full" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">#}
{# <rect x="10" y="20" width="100" height="80" fill="#2563eb" />#}
{# <rect x="20" y="30" width="30" height="20" fill="#1e40af" />#}
{# <rect x="70" y="30" width="30" height="20" fill="#1e40af" />#}
{# <rect x="20" y="60" width="80" height="30" fill="#1e40af" />#}
{# <circle cx="85" cy="45" r="8" fill="#ef4444" />#}
{# </svg>#}
{# `;#}
{# #}
{# document.getElementById('preview2').classList.add('has-image');#}
{# document.getElementById('preview2').innerHTML = `#}
{# <svg class="w-full h-full" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">#}
{# <rect x="10" y="20" width="100" height="80" fill="#2563eb" />#}
{# <rect x="20" y="30" width="80" height="20" fill="#1e40af" />#}
{# <rect x="20" y="60" width="80" height="30" fill="#1e40af" />#}
{# <path d="M85,40 Q95,50 85,60 Q75,50 85,40" fill="#ef4444" />#}
{# </svg>#}
{# `;#}
{# });#}
{# #}
{# document.getElementById('expediteForm').addEventListener('submit', function(e) {#}
{# e.preventDefault();#}
{# #}
{# // Check if order data is loaded#}
{# if (document.getElementById('orderData').classList.contains('hidden')) {#}
{# alert('Please search for a container first');#}
{# return;#}
{# }#}
{# #}
{# // In a real app, this would send data to the server#}
{# alert('Container expedition completed successfully!');#}
{# this.reset();#}
{# document.getElementById('orderData').classList.add('hidden');#}
{# #}
{# // Reset image previews#}
{# for (let i = 1; i <= 2; i++) {#}
{# const previewEl = document.getElementById(`expeditePreview${i}`);#}
{# previewEl.classList.remove('has-image');#}
{# previewEl.innerHTML = `#}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />#}
{# </svg>#}
{# `;#}
{# }#}
{# });#}
{# </script>#}
{#<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9498ddc2d702313f',t:'MTc0ODg4NzM5My4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>#}
{##}
{#<script>#}
{#document.getElementById('check-preinfo-btn').onclick = function() {#}
{# const number = document.getElementById('id_number').value;#}
{# fetch(`/preinfo/check-preinfo/?number=${encodeURIComponent(number)}`)#}
{# .then(response => response.json())#}
{# .then(data => {#}
{# if (data.found) {#}
{# alert('Preinfo found ' + data.container_type);#}
{# document.getElementById('id_line').value = data.line;#}
{# document.getElementById('id_container_type').value = data.container_type;#}
{# document.getElementById('preinfo_id').value = data.preinfo_id;#}
{# } else {#}
{# alert('No preinfo found or already received.');#}
{# }#}
{# });#}
{#;#}
{#</script>#}
</body>
</html>
{% endblock content %}

@ -2,9 +2,16 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
<title>Payment Request</title>
</head>
<body>
$END$
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<h1>Welcome to {{ site_name }}!</h1>
<p>Hello {{ user_email }},</p>
<p>There is a new payment request, see details in the attachments. You can pay via ePay:</p>
<a href="{{ link1 }}" style="background-color: #211FA6; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 10px;">ePay account</a>
<p>or credit card:</p>
<a href="{{ link2 }}" style="background-color: #211FA6; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 10px;">Credit card</a>
</div>
</body>
</html>

@ -24,6 +24,15 @@
{% endblock content %}
</div>
</main>
{% block extra_js %}
<script>
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
</script>
<script src="{% static 'js/container_validation.js' %}"></script>
{% endblock %}
</body>
</html>

@ -1,10 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
{# templates/barrier/container-search.html #}
{% extends "barrier/barrier-base.html" %}
{% block content %}
<div class="barrier-container">
<form method="post" class="w-full">
{% csrf_token %}
<input type="hidden" name="search_type" value="{{ search_type }}">
<div class="p-6">
<label for="id_number" class="block text-2xl mb-4">
{% if search_type == 'container_receive' %}
Enter Booking Number
{% else %}
Enter Container Number
{% endif %}
</label>
<div class="flex flex-col gap-4">
<input type="text"
id="id_number"
name="number"
class="px-4 py-3 text-xl border-2 rounded-lg"
required
autocomplete="off"
autofocus>
<button type="submit"
class="barrier-button">
Search
</button>
{% if error %}
<div class="text-red-600 text-xl text-center">
{{ error }}
</div>
{% endif %}
<a href="{% url 'barrier_dashboard' %}"
class="barrier-button"
style="background-color: #f5f5f5;">
Back
</a>
</div>
</div>
</form>
</div>
{% endblock %}

@ -16,10 +16,10 @@
<td>{{ object.number }}</td>
<td>{{ object.container_type }}</td>
<td>{{ object.line.short_name }}</td>
<td>{{ object.received_on }}</td>
<td>{{ object.received_on|date:"y.m.d h:m" }}</td>
<td>{{ object.position }}</td>
<td>{{ object.swept }}</td>
<td>{{ object.washed }}</td>
<td>{{ object.swept|yesno:"Yes," }}</td>
<td>{{ object.washed|yesno:"Yes," }}</td>
<td>{{ object.booking.number }}</td>
{% endblock %}

@ -1,9 +1,18 @@
{% extends "employee-base.html" %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
{% for container in containers %}
<input type="hidden" name="containers" value="{{ container.id }}">
{% endfor %}
<div>
<button id="createPaymentBtn" class="btn btn-primary" type="submit">Create Payment</button>
</div>
<div class="selected-containers">
<h3>Selected Containers</h3>
@ -30,5 +39,6 @@
</tbody>
</table>
</div>
</form>
{% endblock %}

@ -2,7 +2,7 @@
{% load static %}
{% block filter %}
<div class="filter-form">
{#<div class="filter-form">#}
<form method="GET">
<span style="display: flex; align-items: center; width: 100%;">
<label for="date" style="margin-right: 8px;">Date:</label>
@ -21,7 +21,7 @@
<button type="submit">Search</button>
</span>
</form>
</div>
{#</div>#}
{% endblock %}
{% block selection %}

@ -13,7 +13,7 @@
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
background-color: #ead9cb;
}
header {
position: relative;

@ -1,7 +1,6 @@
{% extends base_template %}
{% load static %}
{% block content %}
{% block filter %}
@ -24,6 +23,10 @@
{% block table_data %}
{% endblock table_data %}
</tr>
{% empty %}
<tr>
<td colspan="100%">No data to display</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
@ -60,6 +63,7 @@
{#footerCell.style.textAlign = 'center';#}
</script>
{% block custom_styles %}
<style>
.selectable-row {
@ -75,10 +79,10 @@
</style>
{% endblock custom_styles %}
{% block extra_js %}
{% block crud_js %}
<script src="{% static 'js/crud-list.js' %}"></script>
{% endblock extra_js %}
{% block custom_js %}
{% endblock crud_js %}
{% endblock custom_js %}
{% endblock content %}

@ -8,7 +8,6 @@
<link rel="stylesheet" href="{% static '/styles/styles.css' %}"/>
</head>
<body>
{# {{ title|default:"Container Depot Management System" }} {{ description|default:"Login to the Container Depot Management System" }}#}
<div class="background-image"></div>
<div class="wave"></div>

Loading…
Cancel
Save