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$/.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/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$/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$/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$/booking/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/forms.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/templatetags/filters.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/templatetags/filters.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$/common/utils/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/utils.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/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/barrier_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_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/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/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$/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/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/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/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$/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$/populate_database.txt" beforeDir="false" afterPath="$PROJECT_DIR$/populate_database.txt" 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$/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/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/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$/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-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-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/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-list.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/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/preinfo-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/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-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/container-search.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/container-search.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/containers-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/containers-list.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-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/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/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> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -63,8 +70,8 @@
<component name="FileTemplateManagerImpl"> <component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="JavaScript File" />
<option value="CSS File" /> <option value="CSS File" />
<option value="JavaScript File" />
<option value="HTML File" /> <option value="HTML File" />
<option value="Python Script" /> <option value="Python Script" />
</list> </list>
@ -81,32 +88,32 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"DefaultHtmlFileTemplate": "HTML File", &quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;,
"Django Server.DepoT.executor": "Debug", &quot;Django Server.DepoT.executor&quot;: &quot;Debug&quot;,
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true", &quot;RunOnceActivity.OpenDjangoStructureViewOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.git.unshallow": "true", &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
"RunOnceActivity.pycharm.django.structure.promotion.once.per.project": "true", &quot;RunOnceActivity.pycharm.django.structure.promotion.once.per.project&quot;: &quot;true&quot;,
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW", &quot;django.template.preview.state&quot;: &quot;SHOW_EDITOR_AND_PREVIEW&quot;,
"git-widget-placeholder": "master", &quot;git-widget-placeholder&quot;: &quot;master&quot;,
"ignore.virus.scanning.warn.message": "true", &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/employee", &quot;last_opened_file_path&quot;: &quot;C:/dev_projects/python/Django/DepoT/templates/employee&quot;,
"list.type.of.created.stylesheet": "CSS", &quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"vue.rearranger.settings.migration": "true" &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}, },
"keyToStringList": { &quot;keyToStringList&quot;: {
"DatabaseDriversLRU": [ &quot;DatabaseDriversLRU&quot;: [
"postgresql" &quot;postgresql&quot;
] ]
} }
}]]></component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" /> <recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
@ -116,11 +123,11 @@
<recent name="C:\dev_projects\python\Django\DepoT\static\styles" /> <recent name="C:\dev_projects\python\Django\DepoT\static\styles" />
</key> </key>
<key name="MoveFile.RECENT_KEYS"> <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\employee" />
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" /> <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\common\views" />
<recent name="C:\dev_projects\python\Django\DepoT\booking\views" /> <recent name="C:\dev_projects\python\Django\DepoT\booking\views" />
<recent name="C:\dev_projects\python\Django\DepoT\containers\views" />
</key> </key>
</component> </component>
<component name="RunManager"> <component name="RunManager">
@ -168,7 +175,11 @@
<updated>1750784740296</updated> <updated>1750784740296</updated>
<workItem from="1750784741367" duration="240087000" /> <workItem from="1750784741367" duration="240087000" />
<workItem from="1752078951079" duration="106000" /> <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>
<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."> <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" /> <option name="closed" value="true" />
@ -293,16 +304,6 @@
<line>118</line> <line>118</line>
<option name="timeStamp" value="61" /> <option name="timeStamp" value="61" />
</line-breakpoint> </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"> <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/common/utils/owncloud_utls.py</url> <url>file://$PROJECT_DIR$/common/utils/owncloud_utls.py</url>
<line>29</line> <line>29</line>
@ -310,8 +311,18 @@
</line-breakpoint> </line-breakpoint>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line"> <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/payments/views.py</url> <url>file://$PROJECT_DIR$/payments/views.py</url>
<line>40</line> <line>41</line>
<option name="timeStamp" value="65" /> <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> </line-breakpoint>
</breakpoints> </breakpoints>
<default-breakpoints> <default-breakpoints>

@ -11,25 +11,21 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
""" """
from pathlib import Path 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 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#" 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 DEBUG = True
ALLOWED_HOSTS = ['192.168.24.43', '127.0.0.1', 'localhost', ] ALLOWED_HOSTS = ['192.168.24.43', '127.0.0.1', 'localhost', ]
# Application definition
PROJECT_APPS = [ PROJECT_APPS = [
'accounts', 'accounts',
"booking", "booking",
@ -95,11 +91,11 @@ WSGI_APPLICATION = "DepoT.wsgi.application"
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.postgresql", "ENGINE": "django.db.backends.postgresql",
"NAME": "depot", "NAME": os.environ.get("DB_NAME", env("DB_NAME")),
"USER": "postgres", "USER": os.environ.get("DB_USER", env("DB_USER")),
"PASSWORD": "admin", "PASSWORD": os.environ.get("DB_PASSWORD", env("DB_PASSWORD")),
"HOST": "127.0.0.1", "HOST": os.environ.get("DB_HOST", env("DB_HOST")),
"PORT": "5432", "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" 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_URL = env('OWNCLOUD_URL')
OWNCLOUD_USER = env('OWNCLOUD_USER') OWNCLOUD_USER = env('OWNCLOUD_USER')
OWNCLOUD_PASSWORD = env('OWNCLOUD_PASSWORD') OWNCLOUD_PASSWORD = env('OWNCLOUD_PASSWORD')
OWNCLOUD_DAMAGES_FOLDER = env('OWNCLOUD_DAMAGES_FOLDER') 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('container/', include('containers.urls')),
path('booking/', include('booking.urls')), path('booking/', include('booking.urls')),
path('payment/', include('payments.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) super().__init__(*args, **kwargs)
self.fields['password1'].widget.attrs.update({'autocomplete': 'new-password'}) self.fields['password1'].widget.attrs.update({'autocomplete': 'new-password'})
self.fields['password2'].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['company_permissions'].widget.attrs['disabled'] = True
self.fields['employee_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 context['base_template'] = self.base_template
return context 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): class UserUpdateView(UpdateView):
template_name = 'registration/register.html' template_name = 'registration/register.html'

@ -17,9 +17,20 @@ class BookingCreateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta): class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number'] 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 BookingUpdateForm(BookingBaseForm):
class Meta(BookingBaseForm.Meta): class Meta(BookingBaseForm.Meta):
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number'] 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 containers.models import Container
from preinfo.models import Preinfo 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): 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) return Container.objects.get(number=number, expedited=False)
except Container.DoesNotExist: except Container.DoesNotExist:
return None 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.shortcuts import render
from django.views.generic import TemplateView from django.views.generic import TemplateView
from containers.models import Container
class BarrierDashboardView(TemplateView): class BarrierDashboardView(TemplateView):
template_name = 'barrier/barrier-dashboard.html' 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): 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 = [ urlpatterns = [
# ... your other urls # ... 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 from containers.models import Container
@ -7,7 +7,12 @@ class ContainerBaseForm(ModelForm):
class Meta: class Meta:
model = Container model = Container
fields = '__all__' fields = '__all__'
widgets = {
'number': TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}
class ContainerReceiveForm(ContainerBaseForm): class ContainerReceiveForm(ContainerBaseForm):
""" """
Form for creating a new Container instance. Form for creating a new Container instance.
@ -16,14 +21,28 @@ class ContainerReceiveForm(ContainerBaseForm):
class Meta(ContainerBaseForm.Meta): class Meta(ContainerBaseForm.Meta):
fields = ['number', 'receive_vehicle', 'damages', 'heavy_damaged', 'position',] 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): class ContainerExpeditionForm(ContainerBaseForm):
"""
Form for updating an existing Container instance.
Inherits from ContainerBaseForm.
"""
class Meta(ContainerBaseForm.Meta): class Meta(ContainerBaseForm.Meta):
fields = ['number', 'expedition_vehicle', 'position', 'line', 'container_type', 'damages', 'heavy_damaged'] 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'] readonly_fields = ['number', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
for field in readonly_fields: for field in readonly_fields:
self.fields[field].widget.attrs['readonly'] = True 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 django.db import models
from common.models import LinesModel, ContainerTypeModel from common.models import LinesModel, ContainerTypeModel
# from payments.models import ContainerTariffPeriod, AdditionalFees
# Create your models here. # Create your models here.
class Container(models.Model): class Container(models.Model):
@ -42,6 +44,11 @@ class Container(models.Model):
blank=True, blank=True,
null=True null=True
) )
preinfo = models.ForeignKey(
'preinfo.Preinfo',
on_delete=models.CASCADE,
related_name='container_preinfo',
)
booking = models.ForeignKey( booking = models.ForeignKey(
'booking.Booking', 'booking.Booking',
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -61,6 +68,7 @@ class Container(models.Model):
expedition_vehicle = models.CharField(max_length=100, blank=True, null=True) expedition_vehicle = models.CharField(max_length=100, blank=True, null=True)
class ContainerHistory(Container): class ContainerHistory(Container):
operation = models.ForeignKey( operation = models.ForeignKey(
'common.OperationModel', 'common.OperationModel',

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

@ -1,14 +1,18 @@
from django.urls import include, path from django.urls import include, path
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView 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 = [ 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('employee/', ContainersListView.as_view(), name='employee_containers'),
path('not-paid', ReportContainersUnpaidListView.as_view(), name='not_paid'), 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('barrier/<int:pk>/', include([
path('receive/', ContainerReceive.as_view(), name='container_receive'), path('photos/', ContainerPhotosView.as_view(), name='container_photos'),
path('expedition/', ContainerExpedition.as_view(), name='container_expedition'),
])), ])),
] ]

@ -7,53 +7,69 @@ from django.views.generic import CreateView, UpdateView, FormView, ListView
from booking.models import Booking from booking.models import Booking
from common.utils.utils import get_container_by_number from common.utils.utils import get_container_by_number
from containers.forms import ContainerReceiveForm, ContainerExpeditionForm 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 containers.services import get_container_for_booking
from preinfo.models import Preinfo from preinfo.models import Preinfo
# Create your views here. # Create your views here.
class ContainerReceive(CreateView): class ContainerReceiveView(FormView):
template_name = 'container-receive.html' template_name = 'container-receive.html'
model = Container
form_class = ContainerReceiveForm form_class = ContainerReceiveForm
success_url = reverse_lazy('dashboard') success_url = reverse_lazy('container_photos')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk') context['show_search'] = True
try:
preinfo =Preinfo.objects.filter(pk=pk, received=False).first()
except Preinfo.DoesNotExist:
preinfo = None
search_number = self.request.GET.get('number')
if search_number:
preinfo = Preinfo.objects.filter(container_number=search_number).first()
if preinfo: if preinfo:
context['preinfo'] = preinfo form = self.form_class(initial={'number': preinfo.container_number})
context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers 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 context
return redirect(reverse_lazy('container_search'))
def form_valid(self, form): def form_valid(self, form):
container = form.save(commit=False)
# Get the preinfo_id from the POST data preinfo = Preinfo.objects.filter(container_number=self.request.POST.get('number')).first()
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:
preinfo.received = True preinfo.received = True
preinfo.save() 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 return redirect('container_photos', pk=container.pk)
form.instance.line = preinfo.line
form.instance.container_type = preinfo.container_type
else:
form.add_error('number', 'Invalid data')
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): class ContainerSearchView(View):
@ -83,30 +99,131 @@ class ContainerSearchView(View):
return redirect(next_url, pk=booking.pk) 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' template_name = 'container-expedition.html'
model = Container
form_class = ContainerExpeditionForm form_class = ContainerExpeditionForm
success_url = reverse_lazy('dashboard') success_url = reverse_lazy('barrier_dashboard')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk') context['show_search'] = True
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() search_number = self.request.GET.get('number')
context['container'] = container if search_number:
context['booking'] = booking 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 return context
def form_valid(self, form): def form_valid(self, form):
booking_id = self.request.POST.get('booking_id') booking_id = self.request.POST.get('booking_id')
container_id = self.request.POST.get('container_id') container_id = self.request.POST.get('container_id')
@ -114,51 +231,74 @@ class ContainerExpedition(UpdateView):
try: try:
booking = Booking.objects.get(id=booking_id) booking = Booking.objects.get(id=booking_id)
container = Container.objects.get(id=container_id) container = Container.objects.get(id=container_id)
vehicle = form.cleaned_data['expedition_vehicle']
# Update container # Update container
container.expedited = True container.expedited = True
container.expedited_on = timezone.now() container.expedited_on = timezone.now()
container.expedited_by = self.request.user container.expedited_by = self.request.user
container.expedition_vehicle = vehicle container.expedition_vehicle = form.cleaned_data['expedition_vehicle']
container.booking = booking container.booking = booking
container.save() container.save()
# Update booking status if needed # Update booking
booking.container_expedited_count += 1 booking.container_expedited_count += 1
if booking.vehicles: if booking.vehicles:
vehicles_list = booking.vehicles.split(',') vehicles_list = booking.vehicles.split(',')
if vehicle in vehicles_list: if container.expedition_vehicle in vehicles_list:
vehicles_list.remove(vehicle) vehicles_list.remove(container.expedition_vehicle)
booking.vehicles = ','.join(vehicles_list) booking.vehicles = ','.join(vehicles_list)
if booking.container_expedited_count >= booking.container_count: if booking.container_expedited_count >= booking.container_count:
booking.status = 'completed' # or any other status you want to set booking.status = 'completed'
booking.save() booking.save()
return redirect(self.success_url) return redirect(self.success_url)
except (Booking.DoesNotExist, Container.DoesNotExist): except (Booking.DoesNotExist, Container.DoesNotExist):
form.add_error(None, 'Invalid booking or container') form.add_error(None, 'Invalid booking or container')
return self.form_invalid(form) 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: def get_form(self, form_class=None):
# context['preinfo'] = preinfo form_class = self.get_form_class()
# context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
# return context if self.request.method == 'POST':
# return redirect(reverse_lazy('container_search')) 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 django.views.generic import ListView
from common.models import CompanyModel from common.models import CompanyModel
from common.utils.utils import get_preinfo_by_number, get_container_by_number
from containers.models import Container from containers.models import Container
@ -50,3 +53,29 @@ class ReportContainersUnpaidListView(ListView):
queryset = queryset.filter(payment_containers__isnull=True) queryset = queryset.filter(payment_containers__isnull=True)
return queryset.order_by('-expedited_on') 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 from damages_api.views import Damages
urlpatterns = [ 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 rest_framework.views import APIView
from common.utils.owncloud_utls import Owncloud from common.utils.owncloud_utls import Owncloud
from containers.models import ContainerPhotos from containers.models import ContainerPhotos, Container
class Damages(APIView): class Damages(APIView):
def post(self, request, depot_id): def post(self, request, depot_id):
data = request.get_json() photo = request.data.get("photo")
photo = data.pop("photo") extension = request.data.get("photo_extension")
extension = data.pop("photo_extension")
with tempfile.NamedTemporaryFile(suffix=f'.{extension}', delete=False) as temp_file: with tempfile.NamedTemporaryFile(suffix=f'.{extension}', delete=False) as temp_file:
try: try:
temp_file.write(base64.b64decode(photo.encode("utf-8"))) temp_file.write(base64.b64decode(photo.encode("utf-8")))
temp_file.flush() temp_file.flush()
temp_file.close() # Close the file before uploading
own_filename = Owncloud.upload_damage_photo(temp_file.name, depot_id) own_filename = Owncloud.upload_damage_photo(temp_file.name, depot_id)
container_photo = ContainerPhotos() 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.photo = own_filename
container_photo.uploaded_on = datetime.now() container_photo.uploaded_on = datetime.now()
container_photo.uploaded_by = request.user container_photo.uploaded_by = request.user
container_photo.save() container_photo.save()
return Response(status=status.HTTP_201_CREATED)
except Exception as ex: except Exception as ex:
raise BadRequest("Invalid photo encoding") return Response(
{"error": "Failed to process photo"},
status=status.HTTP_400_BAD_REQUEST
)
finally: finally:
if temp_file:
try:
os.unlink(temp_file.name) os.unlink(temp_file.name)
return Response(status=status.HTTP_201_CREATED) except OSError:
pass # Ignore deletion errors
def get(self, request, depot_id): def get(self, request, depot_id):
return Owncloud.get_damages(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 PaymentCreateForm(ModelForm):
class Meta: class Meta:
model = Payment model = Payment
fields = ['total_amount', 'company', 'description'] fields = ['company', 'total_amount', 'description']
widgets = { widgets = {
'containers': CheckboxSelectMultiple(), '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 django.urls import path
from payments.views import PaymentCreateView from payments.views import PaymentCreateView, some_view
urlpatterns = [ urlpatterns = [
path("create/", PaymentCreateView.as_view(), name="payments_create"), 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.shortcuts import render
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic import ListView, CreateView 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.models import CompanyModel
from common.utils.utils import send_test_email
from containers.models import Container from containers.models import Container
from payments.forms import PaymentCreateForm 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. # Create your views here.
class PaymentCreateView(CreateView): class PaymentCreateView(CreateView):
model = Payment model = Payment
form_class = PaymentCreateForm form_class = PaymentCreateForm
template_name = 'employee/payment-create.html' template_name = 'employee/payment-create.html'
success_url = reverse_lazy('payment-list') success_url = reverse_lazy('not_paid')
def form_valid(self, form): 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 = 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.save()
payment.containers.set(container_ids) send_test_email('kikimor@gmail.com', urls) # invoice and
# 0000000000000000000000000000000000#
return super().form_valid(form) return super().form_valid(form)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -42,6 +89,145 @@ class PaymentCreateView(CreateView):
company_pk = self.request.GET.get('company', '') company_pk = self.request.GET.get('company', '')
form.fields['company'].initial = company_pk form.fields['company'].initial = company_pk
# container_ids = [int(id) for id in container_ids if id.strip().isdigit()] container_ids = self.request.GET.get('containers', '').split(',')
# form.fields['containers'].initial = container_ids 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 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 (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 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 django.urls import reverse_lazy
from preinfo.models import Preinfo from preinfo.models import Preinfo
@ -8,7 +8,12 @@ class PreinfoBaseForm(ModelForm):
class Meta: class Meta:
model = Preinfo model = Preinfo
fields = '__all__' fields = '__all__'
widgets = {
'container_number': TextInput(attrs={
'oninput': 'validateContainerNumber(this)',
'class': 'form-control'
})
}
# success_url = reverse_lazy('client_preinfo') # success_url = reverse_lazy('client_preinfo')
class PreinfoCreateForm(PreinfoBaseForm): class PreinfoCreateForm(PreinfoBaseForm):
""" """
@ -24,7 +29,6 @@ class PreinfoCreateForm(PreinfoBaseForm):
'received'] # Exclude fields that should not be set by the user 'received'] # Exclude fields that should not be set by the user
class PreinfoEditForm(ModelForm): class PreinfoEditForm(PreinfoBaseForm):
class Meta: class Meta:
model = Preinfo
fields = ['container_number', 'container_type', 'line'] fields = ['container_number', 'container_type', 'line']

@ -6,7 +6,8 @@ from django.views import View
from django.views.generic import CreateView, ListView, UpdateView from django.views.generic import CreateView, ListView, UpdateView
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin 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.forms import PreinfoCreateForm, PreinfoEditForm
from preinfo.models import Preinfo 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> <text x="100" y="200" font-family="monospace" font-size="20" text-anchor="middle" fill="#5B2C06" font-weight="bold">K-DepoT</text>
<!-- Slogan --> <!-- 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> </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) { table.addEventListener('click', function(e) {
const row = e.target.closest('.selectable-row'); const row = e.target.closest('.selectable-row');
if (!row) return; if (!row) return;
const checkbox = row.querySelector('input[type="checkbox"]'); const checkbox = row.querySelector('input[type="checkbox"]');
if (!checkbox) return; if (!checkbox) return;
if (selectionMode === 'single') { if (selectionMode === 'single') {
// Deselect all other rows
table.querySelectorAll('.selected-row').forEach(selectedRow => { table.querySelectorAll('.selected-row').forEach(selectedRow => {
if (selectedRow !== row) { if (selectedRow !== row) {
selectedRow.classList.remove('selected-row'); selectedRow.classList.remove('selected-row');
@ -25,22 +23,15 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
} }
// Toggle current row
row.classList.toggle('selected-row'); row.classList.toggle('selected-row');
checkbox.checked = !checkbox.checked; checkbox.checked = !checkbox.checked;
// Update buttons state
const selectedRows = table.querySelectorAll('.selected-row'); const selectedRows = table.querySelectorAll('.selected-row');
document.querySelectorAll('[data-requires-selection]').forEach(button => { document.querySelectorAll('[data-requires-selection]').forEach(button => {
button.disabled = selectedRows.length === 0; button.disabled = selectedRows.length === 0;
}); });
// Handle edit/delete buttons from original crud-list.js
const objectId = row.dataset.id; const objectId = row.dataset.id;
const editBtn = document.getElementById('editBtn'); const editBtn = document.getElementById('editBtn');
const deleteBtn = document.getElementById('deleteBtn'); const deleteBtn = document.getElementById('deleteBtn');
if (editBtn) { if (editBtn) {
editBtn.removeAttribute('disabled'); editBtn.removeAttribute('disabled');
editBtn.href = editBtn.dataset.url?.replace('0', objectId); editBtn.href = editBtn.dataset.url?.replace('0', objectId);
@ -54,19 +45,14 @@ document.addEventListener('DOMContentLoaded', function() {
toggleSelectAllBtn.addEventListener('click', function() { toggleSelectAllBtn.addEventListener('click', function() {
const rows = table.querySelectorAll('.selectable-row'); const rows = table.querySelectorAll('.selectable-row');
allSelected = !allSelected; allSelected = !allSelected;
rows.forEach(row => { rows.forEach(row => {
const checkbox = row.querySelector('input[type="checkbox"]'); const checkbox = row.querySelector('input[type="checkbox"]');
checkbox.checked = allSelected; checkbox.checked = allSelected;
row.classList.toggle('selected-row', allSelected); row.classList.toggle('selected-row', allSelected);
}); });
// Update other buttons state
document.querySelectorAll('[data-requires-selection]').forEach(button => { document.querySelectorAll('[data-requires-selection]').forEach(button => {
button.disabled = !allSelected; button.disabled = !allSelected;
}); });
// Update button text
this.textContent = allSelected ? 'Unselect All' : 'Select All'; this.textContent = allSelected ? 'Unselect All' : 'Select All';
}); });

@ -36,7 +36,23 @@ input[type="email"],
input[type="password"], input[type="password"],
input[type="date"], input[type="date"],
input[type="number"], 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 { textarea {
width: 100%; width: 100%;
padding: 8px; padding: 8px;
@ -52,21 +68,28 @@ textarea {
font-size: 1.1rem; font-size: 1.1rem;
font-variation-settings: font-variation-settings:
"wdth" 100; "wdth" 100;
height: 100px; /* Adjust height as needed */
min-height: 100px; /* Minimum height */
resize: vertical; /* Allow vertical resizing */
} }
/* Submit button styling */ /* Submit button styling */
button[type="submit"] { button[type="submit"],
background-color: #a57d52; button.btn-primary,
color: white; button.btn-secondary {
background-color: #F2E8DB;
padding: 10px 20px; padding: 10px 20px;
border: none; border: 1px solid #a57d52;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
/*margin-top: 20px;*/
} }
button[type="submit"]:hover { button[type="submit"]:hover,
background-color: #a67744; button.btn-primary:hover,
button.btn-secondary:hover{
background-color: #EDDECB;
} }
/* Error messages */ /* Error messages */
@ -89,7 +112,7 @@ button[type="submit"]:hover {
.card { .card {
margin-top: 24px; margin-top: 24px;
background-color: white; background-color: #EDDECB;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
} }
@ -279,3 +302,33 @@ button[type="submit"]:hover {
align-items: center; align-items: center;
width: 100%; 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; align-items: center;
justify-content: center; justify-content: center;
padding: 1rem; padding: 1rem;
background-color: #ead9cb;
} }
.wave { .wave {
@ -22,7 +23,7 @@ body {
} }
.login-container { .login-container {
background-color: white; background-color: #ead9cb;
border-radius: 0.75rem; border-radius: 0.75rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
overflow: hidden; overflow: hidden;
@ -31,6 +32,8 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
animation: fadeIn 0.8s ease-in-out; animation: fadeIn 0.8s ease-in-out;
margin: 2rem; /* Add margin to see the background */
} }
@media (min-width: 768px) { @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 { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@ -208,4 +135,33 @@ table tr:last-child td {
color: #990000; 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> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Line Operator Dashboard | Container Depot</title> <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:ital,wght@0,100..900;1,100..900&family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
{# <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/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> </head>
<body> <body>
{% block content %} {% block content %}
{% endblock 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> </body>
</html> </html>

@ -1,73 +1,42 @@
{% extends 'barrier/barrier-base.html' %} {% extends "barrier/barrier-base.html" %}
{% load static %}
{% block content %} {% block content %}
<div class="sidebar p-5 text-white border-b border-blue-700"> <div class="barrier-container">
<h1 class="text-xl font-bold">Container Depot</h1> <a href="{% url 'container_receive' %}" class="barrier-button">
<p class="text-sm text-blue-200">Line Operator Portal</p> Receive Container
</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
</a> </a>
<a href="{% url 'container_search' %}?param=container_expedition" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white"> <a href="{% url 'container_expedition' %}" class="barrier-button">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"> Expedite Container
<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> </a>
<a href="#" class="nav-item flex items-center px-6 py-3 text-white"> <a href="{% url 'barrier_photos' %}" class="barrier-button">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"> Take Photos
<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> </a>
{# <div class="px-4 py-2 mt-6 text-xs text-blue-300 uppercase tracking-wider">Account</div>#} <h2 class="text-2xl font-bold mb-4">Recent History</h2>
{# <a href="#" class="nav-item flex items-center px-6 py-3 text-white">#} <table>
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">#} <thead>
{# <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" />#} <tr>
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />#} <th>Date</th>
{# </svg>#} <th>Container Number</th>
{# Settings#} <th>Status</th>
{# </a>#} </tr>
</nav> </thead>
<tbody>
{# <div class="p-4 border-t border-blue-700">#} {% for container in recent_containers %}
{# <div class="flex items-center">#} <tr>
{# <div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold">#} <td>{{ container.number }}</td>
{# LO#} {% if container.expedited %}
{# </div>#} <td>{{ container.expedited_on }}</td>
{# <div class="ml-3">#} <td>Expedited</td>
{# <p class="text-sm font-medium">Maersk Line</p>#} {% else %}
{# <p class="text-xs text-blue-300">Line Operator</p>#} <td>{{ container.received_on }}</td>
{# </div>#} <td>Received</td>
{# <button class="ml-auto text-white">#} {% endif %}
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">#} </tr>
{# <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" />#} {% endfor %}
{# </svg>#} </tbody>
{# </button>#} </table>
{# </div>#}
{# </div>#}
</div>
</div>
</div> </div>
{% endblock content %} {% endblock content %}

@ -24,6 +24,23 @@
{% endblock content %} {% endblock content %}
</div> </div>
</main> </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> </body>
</html> </html>

@ -10,7 +10,7 @@
{% url 'register' as register_url %} {% url 'register' as register_url %}
{% url 'client_company' as client_company_url %} {% url 'client_company' as client_company_url %}
{% url 'client_line' as client_line_url %} {% url 'client_line' as client_line_url %}
{% url 'user_list' as user_list_url %}
<nav class="nav-menu"> <nav class="nav-menu">
<div class="section-title">Main</div> <div class="section-title">Main</div>
<a href="{{ dashboard_url }}" class="nav-item {% if request.path == dashboard_url %}active{% endif %}"> <a href="{{ dashboard_url }}" class="nav-item {% if request.path == dashboard_url %}active{% endif %}">
@ -43,7 +43,7 @@
</a> </a>
{% if request.user.UserType == 'CA' or 1 == 1 %} {% if request.user.UserType == 'CA' or 1 == 1 %}
<div class="section-title account">Nomenclatures</div> <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"> <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="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" /> <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>Vehicles left</th>
<th>Created on</th> <th>Created on</th>
<th>Created by</th> <th>Created by</th>
<th>Status</th>
{% endblock table_header %} {% endblock table_header %}
{% block table_data %} {% block table_data %}
@ -26,6 +27,7 @@
<td>{{ object.vehicles_left }}</td> <td>{{ object.vehicles_left }}</td>
<td>{{ object.created_on }}</td> <td>{{ object.created_on }}</td>
<td>{{ object.created_by.username }}</td> <td>{{ object.created_by.username }}</td>
<td>{{ object.status }}</td>
{% endblock %} {% endblock %}
{% block buttons %} {% 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> <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> <button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
{% endblock buttons %} {% 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>Line</th>
<th>Created on</th> <th>Created on</th>
<th>Created by</th> <th>Created by</th>
<th>Status</th>
{% endblock table_header %} {% endblock table_header %}
{% block table_data %} {% block table_data %}
<td>{{ object.id }}</td> <td>{{ object.id }}</td>
<td>{{ object.container_number }}</td> <td>{{ object.container_number|upper }}</td>
<td>{{ object.container_type }}</td> <td>{{ object.container_type }}</td>
<td>{{ object.line.short_name }}</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.created_by.username }}</td>
<td>{{ object.received|yesno:"Received,Pending" }}</td>
{% endblock %} {% endblock %}
{% block buttons %} {% block buttons %}

@ -1,20 +1,62 @@
{% extends 'barrier/barrier-base.html' %} {% extends 'barrier/barrier-base.html' %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<form method="post"> <div class="barrier-container">
<h2 style="color: #a57d52; margin-bottom: 20px; text-align: center;">Container Expedition</h2> {% 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 %} {% csrf_token %}
<input type="hidden" name="booking_id" value="{{ booking.id }}"> <input type="hidden" name="booking_id" value="{{ booking.id }}">
<input type="hidden" name="container_id" value="{{ container.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> <p>
<label for="id_booking_number">Booking number:</label> <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> <input type="text" name="booking_number" value="{{ booking.number }}" maxlength="11" required id="id_booking_number" readonly disabled>
</p> </p>
{{ form.as_p }} {{ form.as_p }}
<div > </div>
<button type="submit" > <div class="form-group">
<button type="submit" name="expedite" class="btn-primary">
Expedite container Expedite container
</button> </button>
<button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
Back
</button>
</div>
</div>
</div> </div>
</form> </form>
{% endif %}
</div>
{% endblock content %} {% endblock content %}

@ -1,10 +1,90 @@
<!DOCTYPE html> {# templates/container-photos.html #}
<html lang="en"> {% extends "barrier/barrier-base.html" %}
<head> {% load static %}
<meta charset="UTF-8">
<title>$Title$</title> {% block content %}
</head> <div class="barrier-container">
<body> <h2 class="text-2xl mb-4">Container Photos - {{ container.number }}</h2>
$END$
</body> {% csrf_token %} {# Added CSRF token here #}
</html>
<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 @@
{% extends "barrier/barrier-base.html" %}
{% block content %}
<!DOCTYPE html> <div class="barrier-container">
<html lang="en"> {% if show_search %}
<head> <form method="get" class="w-full">
<meta charset="UTF-8"> <div class="p-6">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <label for="id_number" class="block text-2xl mb-4">
<title>Barrier Staff Interface | Container Depot</title> Enter Container Number
{# <script src="https://cdn.tailwindcss.com"></script>#} </label>
<style> <div>
body { <input type="text"
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; id="id_number"
{#background-color: #f5f7fa;#} name="number"
} value="{{ search_number }}"
.sidebar { class="px-4 py-3 text-xl border-2 rounded-lg"
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%); required
transition: all 0.3s; autocomplete="off"
} autofocus>
.content-area { <div class="form-group">
transition: all 0.3s; <button type="submit" class="btn-primary">
} Search
.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>
</button> </button>
</div> </div>
<span class="text-gray-700 font-medium">Gate North</span> {% if error %}
</div> <div class="text-red-600 text-xl text-center">
{{ error }}
</div> </div>
</header> {% endif %}
<!-- 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>
</div> </div>
</div> </div>
<form id="receiveForm" class="space-y-6" method="POST"> </form>
{% elif form %}
<form method="post" class="w-full">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="search_number" value="{{ search_number }}">
<div class="p-6"> <div class="p-6">
<label for="id_number" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label> <div class="flex flex-col gap-4">
{# <label for="id_number" >Container Number</label>#} <div class="form-group">
{# <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">#} {{ form.as_p }}
{# <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> </div>
</div> <div class="form-group">
</div> <button type="submit" class="btn-primary">
</div> Receive Container
</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
</button> </button>
</div> <button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
Back
<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> </button>
</div> </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>
</div> </div>
</form>
{% endif %}
</div> </div>
{% endblock content %}
{# <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>

@ -2,9 +2,16 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>$Title$</title> <title>Payment Request</title>
</head> </head>
<body> <body style="margin: 0; padding: 0; font-family: Arial, sans-serif;">
$END$ <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> </body>
</html> </html>

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

@ -1,10 +1,47 @@
<!DOCTYPE html> {# templates/barrier/container-search.html #}
<html lang="en"> {% extends "barrier/barrier-base.html" %}
<head> {% block content %}
<meta charset="UTF-8"> <div class="barrier-container">
<title>$Title$</title> <form method="post" class="w-full">
</head> {% csrf_token %}
<body> <input type="hidden" name="search_type" value="{{ search_type }}">
$END$
</body> <div class="p-6">
</html> <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.number }}</td>
<td>{{ object.container_type }}</td> <td>{{ object.container_type }}</td>
<td>{{ object.line.short_name }}</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.position }}</td>
<td>{{ object.swept }}</td> <td>{{ object.swept|yesno:"Yes," }}</td>
<td>{{ object.washed }}</td> <td>{{ object.washed|yesno:"Yes," }}</td>
<td>{{ object.booking.number }}</td> <td>{{ object.booking.number }}</td>
{% endblock %} {% endblock %}

@ -1,9 +1,18 @@
{% extends "employee-base.html" %} {% extends "employee-base.html" %}
{% block content %} {% block content %}
<form method="POST">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ 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"> <div class="selected-containers">
<h3>Selected Containers</h3> <h3>Selected Containers</h3>
@ -30,5 +39,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</form>
{% endblock %} {% endblock %}

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

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

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

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

Loading…
Cancel
Save