fixed crud list
fixed payments upload -a
This commit is contained in:
Generated
+72
-61
@@ -8,48 +8,55 @@
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/DepoT/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/settings.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/DepoT/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/DepoT/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/accounts/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/accounts/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/forms.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/accounts/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/accounts/views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/booking/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/views/employee_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/forms/company.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/forms/company.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/forms/line.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/forms/line.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/booking/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/booking/forms.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/templatetags/filters.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/templatetags/filters.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/utils/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/utils/utils.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/client_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/employee_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/views/barrier_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/common_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/common_api/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/containers/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/forms.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/containers/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/models.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/containers/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/services.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/containers/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/containers/views/barrier_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/barrier_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/containers/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/containers/views/employee_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/damages_api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/damages_api/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/damages_api/views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/docker-compose.yml" beforeDir="false" afterPath="$PROJECT_DIR$/docker-compose.yml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dockerfile" beforeDir="false" afterPath="$PROJECT_DIR$/dockerfile" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/payments/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/forms.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/payments/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/models.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/payments/services.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/services.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/payments/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/payments/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/utils.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/payments/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/payments/views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/preinfo/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/preinfo/views/employee_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/employee_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/populate_database.txt" beforeDir="false" afterPath="$PROJECT_DIR$/populate_database.txt" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/preinfo/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/forms.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/preinfo/views/client_views.py" beforeDir="false" afterPath="$PROJECT_DIR$/preinfo/views/client_views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/static/images/k-depot-logo.svg" beforeDir="false" afterPath="$PROJECT_DIR$/static/images/k-depot-logo.svg" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/static/js/container_validation.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/container_validation.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/static/js/crud-list.js" beforeDir="false" afterPath="$PROJECT_DIR$/static/js/crud-list.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/static/styles/forms.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/forms.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/static/styles/sidebar.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/sidebar.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/static/styles/styles.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/styles.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/static/styles/tables.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/styles/tables.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-base.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/barrier/barrier-dashboard.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/client-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-base.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/client-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client-sidebar.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/client/line-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-create.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/client/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/client/line-update.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/line-update.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/client/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/booking-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/client/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/client/preinfo-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/container-expedition.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-expedition.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/container-photos.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-photos.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/container-receive.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/container-receive.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/email.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/email.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee-base.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-base.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee-dashboard-content.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-dashboard-content.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee-sidebar.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee-sidebar.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/booking-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/booking-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/company-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/company-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/container-search.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/container-search.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/containers-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/containers-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/line-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-create.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/line-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/line-update.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/line-update.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/payment-create.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-create.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/payment-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/payment-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/employee/preinfo-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/employee/preinfo-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/landing-page.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/landing-page.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/list-crud.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/list-crud.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/registration/user-list.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/user-list.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/templates/registration/login.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/registration/login.html" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -63,8 +70,8 @@
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="JavaScript File" />
|
||||
<option value="CSS File" />
|
||||
<option value="JavaScript File" />
|
||||
<option value="HTML File" />
|
||||
<option value="Python Script" />
|
||||
</list>
|
||||
@@ -81,32 +88,32 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"DefaultHtmlFileTemplate": "HTML File",
|
||||
"Django Server.DepoT.executor": "Debug",
|
||||
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.pycharm.django.structure.promotion.once.per.project": "true",
|
||||
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW",
|
||||
"git-widget-placeholder": "master",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/employee",
|
||||
"list.type.of.created.stylesheet": "CSS",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"DefaultHtmlFileTemplate": "HTML File",
|
||||
"Django Server.DepoT.executor": "Debug",
|
||||
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.pycharm.django.structure.promotion.once.per.project": "true",
|
||||
"django.template.preview.state": "SHOW_EDITOR_AND_PREVIEW",
|
||||
"git-widget-placeholder": "master",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"last_opened_file_path": "C:/dev_projects/python/Django/DepoT/templates/employee",
|
||||
"list.type.of.created.stylesheet": "CSS",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
]
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
|
||||
@@ -116,11 +123,11 @@
|
||||
<recent name="C:\dev_projects\python\Django\DepoT\static\styles" />
|
||||
</key>
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="C:\dev_projects\python\Django\DepoT" />
|
||||
<recent name="C:\dev_projects\python\Django\DepoT\templates\employee" />
|
||||
<recent name="C:\dev_projects\python\Django\DepoT\templates\client" />
|
||||
<recent name="C:\dev_projects\python\Django\DepoT\common\views" />
|
||||
<recent name="C:\dev_projects\python\Django\DepoT\booking\views" />
|
||||
<recent name="C:\dev_projects\python\Django\DepoT\containers\views" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
@@ -168,7 +175,11 @@
|
||||
<updated>1750784740296</updated>
|
||||
<workItem from="1750784741367" duration="240087000" />
|
||||
<workItem from="1752078951079" duration="106000" />
|
||||
<workItem from="1752079161329" duration="97669000" />
|
||||
<workItem from="1752079161329" duration="189445000" />
|
||||
<workItem from="1753174764499" duration="271000" />
|
||||
<workItem from="1753175068058" duration="4635000" />
|
||||
<workItem from="1753179863298" duration="8357000" />
|
||||
<workItem from="1753197869497" duration="15872000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Add IntelliJ IDEA project configuration files This commit adds IntelliJ IDEA-specific configuration files for the project, including module setup, version control integration, inspection profiles, and workspace settings. These files facilitate development environment configuration for contributors using IntelliJ IDEA.">
|
||||
<option name="closed" value="true" />
|
||||
@@ -293,16 +304,6 @@
|
||||
<line>118</line>
|
||||
<option name="timeStamp" value="61" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
||||
<url>file://$PROJECT_DIR$/damages_api/views.py</url>
|
||||
<line>15</line>
|
||||
<option name="timeStamp" value="62" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
||||
<url>file://$PROJECT_DIR$/damages_api/views.py</url>
|
||||
<line>39</line>
|
||||
<option name="timeStamp" value="63" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
||||
<url>file://$PROJECT_DIR$/common/utils/owncloud_utls.py</url>
|
||||
<line>29</line>
|
||||
@@ -310,8 +311,18 @@
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
||||
<url>file://$PROJECT_DIR$/payments/views.py</url>
|
||||
<line>40</line>
|
||||
<option name="timeStamp" value="65" />
|
||||
<line>41</line>
|
||||
<option name="timeStamp" value="98" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
|
||||
<url>file://$PROJECT_DIR$/payments/views.py</url>
|
||||
<line>56</line>
|
||||
<option name="timeStamp" value="99" />
|
||||
</line-breakpoint>
|
||||
<line-breakpoint enabled="true" type="javascript">
|
||||
<url>file://$PROJECT_DIR$/static/js/container_validation.js</url>
|
||||
<line>4</line>
|
||||
<option name="timeStamp" value="95" />
|
||||
</line-breakpoint>
|
||||
</breakpoints>
|
||||
<default-breakpoints>
|
||||
|
||||
+28
-17
@@ -11,25 +11,21 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import environ
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
env = environ.Env()
|
||||
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = "django-insecure-g%187p84o9^rr)3#9@r3n^o2v1i%@6=+puxm7hlodg+kbsk%n#"
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['192.168.24.43', '127.0.0.1', 'localhost', ]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
PROJECT_APPS = [
|
||||
'accounts',
|
||||
"booking",
|
||||
@@ -95,11 +91,11 @@ WSGI_APPLICATION = "DepoT.wsgi.application"
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": "depot",
|
||||
"USER": "postgres",
|
||||
"PASSWORD": "admin",
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": "5432",
|
||||
"NAME": os.environ.get("DB_NAME", env("DB_NAME")),
|
||||
"USER": os.environ.get("DB_USER", env("DB_USER")),
|
||||
"PASSWORD": os.environ.get("DB_PASSWORD", env("DB_PASSWORD")),
|
||||
"HOST": os.environ.get("DB_HOST", env("DB_HOST")),
|
||||
"PORT": os.environ.get("DB_PORT", env("DB_PORT")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,12 +153,27 @@ TEMP_FILE_FOLDER = "/tmp/damages_photos"
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
import environ
|
||||
env = environ.Env()
|
||||
import os
|
||||
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
|
||||
|
||||
OWNCLOUD_URL = env('OWNCLOUD_URL')
|
||||
OWNCLOUD_USER = env('OWNCLOUD_USER')
|
||||
OWNCLOUD_PASSWORD = env('OWNCLOUD_PASSWORD')
|
||||
OWNCLOUD_DAMAGES_FOLDER = env('OWNCLOUD_DAMAGES_FOLDER')
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = env("EMAIL_HOST", cast=str, default=None)
|
||||
EMAIL_PORT = env("EMAIL_PORT", cast=str, default='587') # Recommended
|
||||
EMAIL_HOST_USER = env("EMAIL_HOST_USER", cast=str, default=None)
|
||||
EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD", cast=str, default=None)
|
||||
EMAIL_USE_TLS = env("EMAIL_USE_TLS", cast=bool, default=True) # Use EMAIL_PORT 587 for TLS
|
||||
EMAIL_USE_SSL = env("EMAIL_USE_SSL", cast=bool, default=False) # EUse MAIL_PORT 465 for SSL
|
||||
|
||||
ADMIN_USER_NAME=env("ADMIN_USER_NAME", default="Admin user")
|
||||
ADMIN_USER_EMAIL=env("ADMIN_USER_EMAIL", default=None)
|
||||
|
||||
MANAGERS=[]
|
||||
ADMINS=[]
|
||||
if all([ADMIN_USER_NAME, ADMIN_USER_EMAIL]):
|
||||
ADMINS +=[
|
||||
(f'{ADMIN_USER_NAME}', f'{ADMIN_USER_EMAIL}')
|
||||
]
|
||||
MANAGERS=ADMINS
|
||||
+2
-1
@@ -26,5 +26,6 @@ urlpatterns = [
|
||||
path('container/', include('containers.urls')),
|
||||
path('booking/', include('booking.urls')),
|
||||
path('payment/', include('payments.urls')),
|
||||
path('damages/', include('damages_api.urls')),
|
||||
path('api/damages/', include('damages_api.urls')),
|
||||
path('api/common/', include('common_api.urls')),
|
||||
]
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ class RegisterForm(ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['password1'].widget.attrs.update({'autocomplete': 'new-password'})
|
||||
self.fields['password2'].widget.attrs.update({'autocomplete': 'new-password'})
|
||||
self.fields['user_type'].widget.attrs['disabled'] = True
|
||||
self.fields['user_type'].widget.attrs['readonly'] = True
|
||||
self.fields['company_permissions'].widget.attrs['disabled'] = True
|
||||
self.fields['employee_permissions'].widget.attrs['disabled'] = True
|
||||
|
||||
|
||||
@@ -97,6 +97,17 @@ class UserListView(ListView):
|
||||
context['base_template'] = self.base_template
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
user = self.request.user
|
||||
|
||||
# Filter users based on permissions
|
||||
if user.is_superuser:
|
||||
return queryset.all()
|
||||
elif user.user_type == DepotUser.UserType.COMPANY_ADMIN:
|
||||
return queryset.filter(company=user.company)
|
||||
else:
|
||||
return queryset.none()
|
||||
|
||||
class UserUpdateView(UpdateView):
|
||||
template_name = 'registration/register.html'
|
||||
|
||||
+12
-1
@@ -17,9 +17,20 @@ class BookingCreateForm(BookingBaseForm):
|
||||
|
||||
class Meta(BookingBaseForm.Meta):
|
||||
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']
|
||||
|
||||
widgets = {
|
||||
'container_number': forms.TextInput(attrs={
|
||||
'oninput': 'validateContainerNumber(this)',
|
||||
'class': 'form-control'
|
||||
})
|
||||
}
|
||||
|
||||
class BookingUpdateForm(BookingBaseForm):
|
||||
|
||||
class Meta(BookingBaseForm.Meta):
|
||||
fields = ['number', 'vehicles', 'container_type', 'container_count', 'carrier', 'line', 'container_number']
|
||||
widgets = {
|
||||
'container_number': forms.TextInput(attrs={
|
||||
'oninput': 'validateContainerNumber(this)',
|
||||
'class': 'form-control'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
from django import template
|
||||
from django.template.defaultfilters import date
|
||||
|
||||
from common.utils.utils import check_container_number_validity
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def bg_date(value):
|
||||
if value:
|
||||
return date(value, "y.m.d h:m")
|
||||
return ""
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def container_validity_class(container_number):
|
||||
if not check_container_number_validity(container_number):
|
||||
return 'invalid-container'
|
||||
return ''
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
import owncloud
|
||||
from owncloud import (HTTPResponseError)
|
||||
|
||||
|
||||
class Owncloud:
|
||||
@staticmethod
|
||||
def upload_damage_photo(filename, depot_id):
|
||||
_, ext = os.path.splitext(filename)
|
||||
oc = owncloud.Client(settings.OWNCLOUD_URL)
|
||||
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
|
||||
path = f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}"
|
||||
try:
|
||||
oc.file_info(path)
|
||||
except HTTPResponseError:
|
||||
_ = oc.mkdir(path)
|
||||
owncloud_filename = f"{datetime.today().strftime('%Y%m%d')}_{uuid.uuid4()}{ext}"
|
||||
owncloud_fullpath = path + "/" + owncloud_filename
|
||||
oc.put_file(owncloud_fullpath, filename)
|
||||
file_info = oc.share_file_with_link(owncloud_fullpath)
|
||||
return file_info.get_link()
|
||||
|
||||
@staticmethod
|
||||
def get_damages(depot_id):
|
||||
oc = owncloud.Client(settings.OWNCLOUD_URL)
|
||||
oc.login(settings.OWNCLOUD_USER, settings.OWNCLOUD_PASSWORD)
|
||||
files = oc.list(f"{settings.OWNCLOUD_DAMAGES_FOLDER}{str(depot_id)}")
|
||||
damages = []
|
||||
for file_info in files:
|
||||
if file_info.is_dir():
|
||||
continue # Пропускаме поддиректории
|
||||
# Създаване на публичен линк за всеки файл
|
||||
link_info = oc.share_file_with_link(file_info.path)
|
||||
damages.append(link_info.get_link())
|
||||
return Response(damages, status=status.HTTP_200_OK)
|
||||
@@ -1,5 +1,12 @@
|
||||
import re
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
from containers.models import Container
|
||||
from preinfo.models import Preinfo
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail, EmailMessage
|
||||
|
||||
|
||||
def filter_queryset_by_user(queryset, user):
|
||||
@@ -41,3 +48,42 @@ def get_container_by_number(number):
|
||||
return Container.objects.get(number=number, expedited=False)
|
||||
except Container.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
CALC_STRING = "0123456789A.BCDEFGHIJK.LMNOPQRSTU.VWXYZ"
|
||||
|
||||
def check_container_number_validity(container_number):
|
||||
if not re.search(r"[A-Z]{4}[0-9]{7}", container_number):
|
||||
return False
|
||||
subtotal = 0
|
||||
delta = 1
|
||||
for i in range(0, 10):
|
||||
subtotal = subtotal + CALC_STRING.find(container_number[i]) * delta
|
||||
delta = delta * 2
|
||||
subtotal = (subtotal % 11) % 10
|
||||
if not subtotal == CALC_STRING.find(container_number[10]):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
ADMIN_USER_EMAIL= settings.ADMIN_USER_EMAIL
|
||||
EMAIL_HOST_USER = settings.EMAIL_HOST_USER
|
||||
|
||||
def send_test_email(user_email, urls):
|
||||
subject='Welcome to Our Platform'
|
||||
html_message = render_to_string('email.html', {
|
||||
'site_name': 'Our Platform',
|
||||
'user_email': user_email,
|
||||
'link1': urls['pay_epay'],
|
||||
'link2': urls['pay_credit_card'],
|
||||
})
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
email = EmailMessage(
|
||||
subject=subject,
|
||||
body=html_message,
|
||||
from_email=EMAIL_HOST_USER,
|
||||
to=[user_email],
|
||||
)
|
||||
email.content_subtype = 'html' # This tells Django to send HTML email
|
||||
email.send()
|
||||
@@ -1,14 +1,17 @@
|
||||
from django.shortcuts import render
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from containers.models import Container
|
||||
|
||||
|
||||
class BarrierDashboardView(TemplateView):
|
||||
template_name = 'barrier/barrier-dashboard.html'
|
||||
extra_context = {
|
||||
'title': 'Client Dashboard',
|
||||
'description': 'This is the client dashboard page.',
|
||||
}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
recent_containers = Container.objects.select_related('line', 'booking').order_by('-expedited_on', '-received_on')[:10]
|
||||
context['recent_containers'] = recent_containers
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return render(request, self.template_name, self.extra_context)
|
||||
return render(request, self.template_name, self.get_context_data())
|
||||
+1
-1
@@ -3,5 +3,5 @@ from .views import validate_container
|
||||
|
||||
urlpatterns = [
|
||||
# ... your other urls
|
||||
path('api/validate-container/<str:number>/', validate_container, name='validate_container'),
|
||||
path('validate-container/<str:number>/', validate_container, name='validate_container'),
|
||||
]
|
||||
+26
-7
@@ -1,4 +1,4 @@
|
||||
from django.forms import ModelForm
|
||||
from django.forms import ModelForm, TextInput
|
||||
|
||||
from containers.models import Container
|
||||
|
||||
@@ -7,7 +7,12 @@ class ContainerBaseForm(ModelForm):
|
||||
class Meta:
|
||||
model = Container
|
||||
fields = '__all__'
|
||||
|
||||
widgets = {
|
||||
'number': TextInput(attrs={
|
||||
'oninput': 'validateContainerNumber(this)',
|
||||
'class': 'form-control'
|
||||
})
|
||||
}
|
||||
class ContainerReceiveForm(ContainerBaseForm):
|
||||
"""
|
||||
Form for creating a new Container instance.
|
||||
@@ -16,14 +21,28 @@ class ContainerReceiveForm(ContainerBaseForm):
|
||||
class Meta(ContainerBaseForm.Meta):
|
||||
fields = ['number', 'receive_vehicle', 'damages', 'heavy_damaged', 'position',]
|
||||
|
||||
def __init__(self, *args, preinfo=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Add form-control class to all fields
|
||||
for field in self.fields.values():
|
||||
field.widget.attrs.update({'class': 'form-control'})
|
||||
|
||||
# Checkbox specific styling
|
||||
self.fields['heavy_damaged'].widget.attrs.update({'class': 'form-check-input'})
|
||||
|
||||
# If preinfo is provided, set initial values and make fields readonly
|
||||
if preinfo:
|
||||
...
|
||||
# self.fields['preinfo'].initial = preinfo.container_number
|
||||
# self.fields['number'].initial = preinfo.number
|
||||
# self.fields['container_number'].widget.attrs['readonly'] = True
|
||||
# self.fields['number'].widget.attrs['readonly'] = True
|
||||
# self.fields['booking'].disabled = True
|
||||
# self.fields['number'].disabled = True
|
||||
|
||||
|
||||
class ContainerExpeditionForm(ContainerBaseForm):
|
||||
"""
|
||||
Form for updating an existing Container instance.
|
||||
Inherits from ContainerBaseForm.
|
||||
"""
|
||||
class Meta(ContainerBaseForm.Meta):
|
||||
fields = ['number', 'expedition_vehicle', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
|
||||
|
||||
@@ -32,4 +51,4 @@ class ContainerExpeditionForm(ContainerBaseForm):
|
||||
readonly_fields = ['number', 'position', 'line', 'container_type', 'damages', 'heavy_damaged']
|
||||
for field in readonly_fields:
|
||||
self.fields[field].widget.attrs['readonly'] = True
|
||||
self.fields[field].disabled = True
|
||||
# self.fields[field].disabled = True
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
from django.db import models
|
||||
from common.models import LinesModel, ContainerTypeModel
|
||||
# from payments.models import ContainerTariffPeriod, AdditionalFees
|
||||
|
||||
# Create your models here.
|
||||
class Container(models.Model):
|
||||
@@ -42,6 +44,11 @@ class Container(models.Model):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
preinfo = models.ForeignKey(
|
||||
'preinfo.Preinfo',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='container_preinfo',
|
||||
)
|
||||
booking = models.ForeignKey(
|
||||
'booking.Booking',
|
||||
on_delete=models.CASCADE,
|
||||
@@ -61,6 +68,7 @@ class Container(models.Model):
|
||||
expedition_vehicle = models.CharField(max_length=100, blank=True, null=True)
|
||||
|
||||
|
||||
|
||||
class ContainerHistory(Container):
|
||||
operation = models.ForeignKey(
|
||||
'common.OperationModel',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from booking.models import Booking
|
||||
from containers.models import Container
|
||||
|
||||
def get_container_for_booking(booking_number):
|
||||
def get_container_for_booking(booking):
|
||||
filters = {
|
||||
'expedited': False,
|
||||
}
|
||||
booking = Booking.objects.get(number=booking_number)
|
||||
# booking = Booking.objects.get(number=booking_number)
|
||||
if not booking:
|
||||
return None
|
||||
if booking.container_number:
|
||||
|
||||
+8
-4
@@ -1,14 +1,18 @@
|
||||
from django.urls import include, path
|
||||
|
||||
from containers.views.employee_views import ContainersListView, ReportContainersUnpaidListView
|
||||
from containers.views.barrier_views import ContainerReceive, ContainerExpedition, ContainerSearchView
|
||||
from containers.views.barrier_views import ContainerReceiveView, ContainerExpedition, ContainerSearchView, \
|
||||
ContainerPhotosView
|
||||
|
||||
urlpatterns = [
|
||||
path('search/', ContainerSearchView.as_view(), name='container_search'),
|
||||
path('container-search/', ContainerSearchView.as_view(), name='container_search'),
|
||||
path('container-search/', ContainerSearchView.as_view(), name='barrier_photos'),
|
||||
# path('search/', ContainerSearchView.as_view(), name='container_search'),
|
||||
path('employee/', ContainersListView.as_view(), name='employee_containers'),
|
||||
path('not-paid', ReportContainersUnpaidListView.as_view(), name='not_paid'),
|
||||
path('barrier/receive/', ContainerReceiveView.as_view(), name='container_receive'),
|
||||
path('barrier/expedition/', ContainerExpedition.as_view(), name='container_expedition'),
|
||||
path('barrier/<int:pk>/', include([
|
||||
path('receive/', ContainerReceive.as_view(), name='container_receive'),
|
||||
path('expedition/', ContainerExpedition.as_view(), name='container_expedition'),
|
||||
path('photos/', ContainerPhotosView.as_view(), name='container_photos'),
|
||||
])),
|
||||
]
|
||||
@@ -7,53 +7,69 @@ from django.views.generic import CreateView, UpdateView, FormView, ListView
|
||||
from booking.models import Booking
|
||||
from common.utils.utils import get_container_by_number
|
||||
from containers.forms import ContainerReceiveForm, ContainerExpeditionForm
|
||||
from containers.models import Container
|
||||
from containers.models import Container, ContainerPhotos
|
||||
from containers.services import get_container_for_booking
|
||||
from preinfo.models import Preinfo
|
||||
|
||||
|
||||
# Create your views here.
|
||||
class ContainerReceive(CreateView):
|
||||
class ContainerReceiveView(FormView):
|
||||
template_name = 'container-receive.html'
|
||||
model = Container
|
||||
form_class = ContainerReceiveForm
|
||||
success_url = reverse_lazy('dashboard')
|
||||
success_url = reverse_lazy('container_photos')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pk = self.kwargs.get('pk')
|
||||
try:
|
||||
preinfo =Preinfo.objects.filter(pk=pk, received=False).first()
|
||||
except Preinfo.DoesNotExist:
|
||||
preinfo = None
|
||||
context['show_search'] = True
|
||||
|
||||
search_number = self.request.GET.get('number')
|
||||
if search_number:
|
||||
preinfo = Preinfo.objects.filter(container_number=search_number).first()
|
||||
if preinfo:
|
||||
context['preinfo'] = preinfo
|
||||
context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
|
||||
form = self.form_class(initial={'number': preinfo.container_number})
|
||||
context.update({
|
||||
'form': form,
|
||||
'preinfo': preinfo,
|
||||
'search_number': search_number,
|
||||
'show_search': False
|
||||
})
|
||||
else:
|
||||
context.update({
|
||||
'error': 'Preinfo for this container not found',
|
||||
'search_number': search_number
|
||||
})
|
||||
return context
|
||||
return redirect(reverse_lazy('container_search'))
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
# Get the preinfo_id from the POST data
|
||||
preinfo_id = self.request.POST.get('preinfo_id')
|
||||
try:
|
||||
preinfo = Preinfo.objects.get(id=preinfo_id)
|
||||
except Preinfo.DoesNotExist:
|
||||
preinfo = None
|
||||
|
||||
# validate if data is correct, comparing user data with preinfo data
|
||||
if preinfo and preinfo.container_number == form.cleaned_data.get('number') and not preinfo.received:
|
||||
container = form.save(commit=False)
|
||||
preinfo = Preinfo.objects.filter(container_number=self.request.POST.get('number')).first()
|
||||
preinfo.received = True
|
||||
preinfo.save()
|
||||
# Set required fields from preinfo
|
||||
container.container_type = preinfo.container_type
|
||||
container.line = preinfo.line
|
||||
container.received_by = self.request.user
|
||||
container.preinfo = preinfo
|
||||
container.save()
|
||||
|
||||
form.instance.received_by = self.request.user
|
||||
form.instance.line = preinfo.line
|
||||
form.instance.container_type = preinfo.container_type
|
||||
else:
|
||||
form.add_error('number', 'Invalid data')
|
||||
return redirect('container_photos', pk=container.pk)
|
||||
|
||||
return super().form_valid(form)
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
search_number = self.request.GET.get('number') # Changed from search_number
|
||||
if search_number:
|
||||
preinfo = Preinfo.objects.filter(container_number=search_number).first()
|
||||
if preinfo:
|
||||
form.initial['number'] = preinfo.container_number
|
||||
form.fields['number'].widget.readonly = True
|
||||
return form
|
||||
|
||||
def form_invalid(self, form):
|
||||
return self.render_to_response(self.get_context_data(
|
||||
form=form,
|
||||
search_number=self.request.POST.get('number'),
|
||||
show_search=False
|
||||
))
|
||||
|
||||
|
||||
class ContainerSearchView(View):
|
||||
@@ -83,30 +99,131 @@ class ContainerSearchView(View):
|
||||
return redirect(next_url, pk=booking.pk)
|
||||
|
||||
|
||||
class ContainerExpedition(UpdateView):
|
||||
# class ContainerExpedition(UpdateView):
|
||||
# template_name = 'container-expedition.html'
|
||||
# model = Container
|
||||
# form_class = ContainerExpeditionForm
|
||||
# success_url = reverse_lazy('dashboard')
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# pk = self.kwargs.get('pk')
|
||||
# booking = Booking.objects.get(pk=pk)
|
||||
# filters = {
|
||||
# 'expedited': False,
|
||||
# }
|
||||
# if booking.container_number:
|
||||
# filters['container_number'] = booking.container_number
|
||||
# else:
|
||||
# filters['container_type'] = booking.container_type
|
||||
# filters['line'] = booking.line
|
||||
#
|
||||
# container = Container.objects.filter(**filters).order_by('received_on').first()
|
||||
# context['container'] = container
|
||||
# context['booking'] = booking
|
||||
# return context
|
||||
#
|
||||
# def form_valid(self, form):
|
||||
# booking_id = self.request.POST.get('booking_id')
|
||||
# container_id = self.request.POST.get('container_id')
|
||||
#
|
||||
# try:
|
||||
# booking = Booking.objects.get(id=booking_id)
|
||||
# container = Container.objects.get(id=container_id)
|
||||
# vehicle = form.cleaned_data['expedition_vehicle']
|
||||
# # Update container
|
||||
# container.expedited = True
|
||||
# container.expedited_on = timezone.now()
|
||||
# container.expedited_by = self.request.user
|
||||
# container.expedition_vehicle = vehicle
|
||||
# container.booking = booking
|
||||
# container.save()
|
||||
#
|
||||
# # Update booking status if needed
|
||||
# booking.container_expedited_count += 1
|
||||
# if booking.vehicles:
|
||||
# vehicles_list = booking.vehicles.split(',')
|
||||
# if vehicle in vehicles_list:
|
||||
# vehicles_list.remove(vehicle)
|
||||
# booking.vehicles = ','.join(vehicles_list)
|
||||
# if booking.container_expedited_count >= booking.container_count:
|
||||
# booking.status = 'completed' # or any other status you want to set
|
||||
# booking.save()
|
||||
#
|
||||
# return redirect(self.success_url)
|
||||
#
|
||||
# except (Booking.DoesNotExist, Container.DoesNotExist):
|
||||
# form.add_error(None, 'Invalid booking or container')
|
||||
# return self.form_invalid(form)
|
||||
#
|
||||
#
|
||||
# # try:
|
||||
# # booking = Booking.objects.get(id=booking_id)
|
||||
# # except Booking.DoesNotExist:
|
||||
# # booking = None
|
||||
# # # validate if data is correct, comparing user data with booking data
|
||||
# # if booking and booking.container_number == form.cleaned_data.get('container_number') and not booking.expedited:
|
||||
# # booking.expedited = True
|
||||
# # booking.save()
|
||||
# #
|
||||
# # form.instance.expedited_by = self.request.user
|
||||
# # form.instance.line = booking.line
|
||||
# # form.instance.container_type = booking.container_type
|
||||
# # else:
|
||||
# # form.add_error('container_number', 'Invalid data')
|
||||
#
|
||||
# # if preinfo:
|
||||
# # context['preinfo'] = preinfo
|
||||
# # context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
|
||||
# # return context
|
||||
# # return redirect(reverse_lazy('container_search'))
|
||||
|
||||
|
||||
class ContainerExpedition(FormView):
|
||||
template_name = 'container-expedition.html'
|
||||
model = Container
|
||||
form_class = ContainerExpeditionForm
|
||||
success_url = reverse_lazy('dashboard')
|
||||
success_url = reverse_lazy('barrier_dashboard')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pk = self.kwargs.get('pk')
|
||||
booking = Booking.objects.get(pk=pk)
|
||||
filters = {
|
||||
'expedited': False,
|
||||
}
|
||||
if booking.container_number:
|
||||
filters['container_number'] = booking.container_number
|
||||
else:
|
||||
filters['container_type'] = booking.container_type
|
||||
filters['line'] = booking.line
|
||||
context['show_search'] = True
|
||||
|
||||
container = Container.objects.filter(**filters).order_by('received_on').first()
|
||||
context['container'] = container
|
||||
context['booking'] = booking
|
||||
search_number = self.request.GET.get('number')
|
||||
if search_number:
|
||||
booking = Booking.objects.filter(number=search_number).first()
|
||||
if booking:
|
||||
container = get_container_for_booking(booking)
|
||||
if container:
|
||||
# Initialize form with container data
|
||||
form = self.form_class(initial={
|
||||
'number': container.number,
|
||||
'position': container.position,
|
||||
'line': container.line.id,
|
||||
'container_type': container.container_type.id,
|
||||
'damages': container.damages,
|
||||
'heavy_damaged': container.heavy_damaged,
|
||||
'expedition_vehicle': container.expedition_vehicle
|
||||
})
|
||||
context.update({
|
||||
'form': form,
|
||||
'booking': booking,
|
||||
'container': container,
|
||||
'search_number': search_number,
|
||||
'show_search': False
|
||||
})
|
||||
else:
|
||||
context.update({
|
||||
'error': 'No available container found for this booking',
|
||||
'search_number': search_number
|
||||
})
|
||||
else:
|
||||
context.update({
|
||||
'error': 'Booking not found',
|
||||
'search_number': search_number
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
booking_id = self.request.POST.get('booking_id')
|
||||
container_id = self.request.POST.get('container_id')
|
||||
@@ -114,51 +231,74 @@ class ContainerExpedition(UpdateView):
|
||||
try:
|
||||
booking = Booking.objects.get(id=booking_id)
|
||||
container = Container.objects.get(id=container_id)
|
||||
vehicle = form.cleaned_data['expedition_vehicle']
|
||||
|
||||
# Update container
|
||||
container.expedited = True
|
||||
container.expedited_on = timezone.now()
|
||||
container.expedited_by = self.request.user
|
||||
container.expedition_vehicle = vehicle
|
||||
container.expedition_vehicle = form.cleaned_data['expedition_vehicle']
|
||||
container.booking = booking
|
||||
container.save()
|
||||
|
||||
# Update booking status if needed
|
||||
# Update booking
|
||||
booking.container_expedited_count += 1
|
||||
if booking.vehicles:
|
||||
vehicles_list = booking.vehicles.split(',')
|
||||
if vehicle in vehicles_list:
|
||||
vehicles_list.remove(vehicle)
|
||||
if container.expedition_vehicle in vehicles_list:
|
||||
vehicles_list.remove(container.expedition_vehicle)
|
||||
booking.vehicles = ','.join(vehicles_list)
|
||||
if booking.container_expedited_count >= booking.container_count:
|
||||
booking.status = 'completed' # or any other status you want to set
|
||||
booking.status = 'completed'
|
||||
booking.save()
|
||||
|
||||
return redirect(self.success_url)
|
||||
|
||||
except (Booking.DoesNotExist, Container.DoesNotExist):
|
||||
form.add_error(None, 'Invalid booking or container')
|
||||
return self.form_invalid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
print("Form errors:", form.errors) # Debug print
|
||||
print("Form data:", form.data) # Debug print
|
||||
return self.render_to_response(self.get_context_data(
|
||||
form=form,
|
||||
search_number=self.request.POST.get('number'),
|
||||
show_search=False
|
||||
))
|
||||
|
||||
# try:
|
||||
# booking = Booking.objects.get(id=booking_id)
|
||||
# except Booking.DoesNotExist:
|
||||
# booking = None
|
||||
# # validate if data is correct, comparing user data with booking data
|
||||
# if booking and booking.container_number == form.cleaned_data.get('container_number') and not booking.expedited:
|
||||
# booking.expedited = True
|
||||
# booking.save()
|
||||
#
|
||||
# form.instance.expedited_by = self.request.user
|
||||
# form.instance.line = booking.line
|
||||
# form.instance.container_type = booking.container_type
|
||||
# else:
|
||||
# form.add_error('container_number', 'Invalid data')
|
||||
|
||||
# if preinfo:
|
||||
# context['preinfo'] = preinfo
|
||||
# context['containers'] = Container.objects.order_by('-received_on').all()[:10] # Fetch the last 10 containers
|
||||
# return context
|
||||
# return redirect(reverse_lazy('container_search'))
|
||||
def get_form(self, form_class=None):
|
||||
form_class = self.get_form_class()
|
||||
|
||||
if self.request.method == 'POST':
|
||||
search_number = self.request.POST.get('search_number')
|
||||
booking = Booking.objects.filter(number=search_number).first()
|
||||
if booking:
|
||||
container = get_container_for_booking(booking)
|
||||
if container:
|
||||
data = self.request.POST.copy()
|
||||
data.update({
|
||||
'number': container.number,
|
||||
'position': container.position,
|
||||
'line': container.line,
|
||||
'container_type': container.container_type,
|
||||
'damages': container.damages,
|
||||
'heavy_damaged': container.heavy_damaged,
|
||||
})
|
||||
return form_class(data)
|
||||
return form_class()
|
||||
|
||||
|
||||
|
||||
|
||||
# containers/views/barrier_views.py
|
||||
class ContainerPhotosView(View):
|
||||
template_name = 'container-photos.html'
|
||||
|
||||
def get(self, request, pk):
|
||||
container = Container.objects.get(pk=pk)
|
||||
photos = ContainerPhotos.objects.filter(container=container)
|
||||
return render(request, self.template_name, {
|
||||
'container': container,
|
||||
'photos': photos,
|
||||
'photos_count': photos.count()
|
||||
})
|
||||
@@ -1,6 +1,9 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.views import View
|
||||
from django.views.generic import ListView
|
||||
|
||||
from common.models import CompanyModel
|
||||
from common.utils.utils import get_preinfo_by_number, get_container_by_number
|
||||
from containers.models import Container
|
||||
|
||||
|
||||
@@ -50,3 +53,29 @@ class ReportContainersUnpaidListView(ListView):
|
||||
queryset = queryset.filter(payment_containers__isnull=True)
|
||||
|
||||
return queryset.order_by('-expedited_on')
|
||||
|
||||
|
||||
class ContainerSearchView(View):
|
||||
template_name = 'barrier/container-search.html' # Single template for all searches
|
||||
|
||||
def get(self, request):
|
||||
search_type = request.GET.get('param') # container_receive or container_expedition
|
||||
return render(request, self.template_name, {'search_type': search_type})
|
||||
|
||||
def post(self, request):
|
||||
number = request.POST.get('number')
|
||||
search_type = request.POST.get('search_type')
|
||||
|
||||
if search_type == 'container_receive':
|
||||
preinfo = get_preinfo_by_number(number)
|
||||
if preinfo:
|
||||
return redirect('receive_container', pk=preinfo.pk)
|
||||
else: # container_expedition
|
||||
container = get_container_by_number(number)
|
||||
if container:
|
||||
return redirect('expedite_container', pk=container.pk)
|
||||
|
||||
return render(request, self.template_name, {
|
||||
'error': 'Not found',
|
||||
'search_type': search_type
|
||||
})
|
||||
+1
-1
@@ -3,5 +3,5 @@ from django.urls import path
|
||||
from damages_api.views import Damages
|
||||
|
||||
urlpatterns = [
|
||||
path('<int:depot_id>', Damages.as_view(), name='damages_list'),
|
||||
path('<int:depot_id>/', Damages.as_view(), name='damages_list'),
|
||||
]
|
||||
+18
-9
@@ -9,34 +9,43 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from common.utils.owncloud_utls import Owncloud
|
||||
from containers.models import ContainerPhotos
|
||||
from containers.models import ContainerPhotos, Container
|
||||
|
||||
|
||||
class Damages(APIView):
|
||||
|
||||
def post(self, request, depot_id):
|
||||
data = request.get_json()
|
||||
photo = data.pop("photo")
|
||||
extension = data.pop("photo_extension")
|
||||
photo = request.data.get("photo")
|
||||
extension = request.data.get("photo_extension")
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=f'.{extension}', delete=False) as temp_file:
|
||||
try:
|
||||
temp_file.write(base64.b64decode(photo.encode("utf-8")))
|
||||
temp_file.flush()
|
||||
temp_file.close() # Close the file before uploading
|
||||
|
||||
own_filename = Owncloud.upload_damage_photo(temp_file.name, depot_id)
|
||||
|
||||
container_photo = ContainerPhotos()
|
||||
container_photo.container.pk = depot_id
|
||||
container_photo.container = Container.objects.get(pk=depot_id)
|
||||
container_photo.photo = own_filename
|
||||
container_photo.uploaded_on = datetime.now()
|
||||
container_photo.uploaded_by = request.user
|
||||
container_photo.save()
|
||||
except Exception as ex:
|
||||
raise BadRequest("Invalid photo encoding")
|
||||
finally:
|
||||
os.unlink(temp_file.name)
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
except Exception as ex:
|
||||
return Response(
|
||||
{"error": "Failed to process photo"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
finally:
|
||||
if temp_file:
|
||||
try:
|
||||
os.unlink(temp_file.name)
|
||||
except OSError:
|
||||
pass # Ignore deletion errors
|
||||
def get(self, request, depot_id):
|
||||
return Owncloud.get_damages(depot_id)
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file:
|
||||
- development.env
|
||||
volumes:
|
||||
- .:/DepoT
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM python:3.11-slim
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
WORKDIR /DepoT
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . .
|
||||
CMD python manage.py migrate && python manage.py runserver 0.0.0.0:8000
|
||||
+1
-1
@@ -7,7 +7,7 @@ from payments.models import Payment
|
||||
class PaymentCreateForm(ModelForm):
|
||||
class Meta:
|
||||
model = Payment
|
||||
fields = ['total_amount', 'company', 'description']
|
||||
fields = ['company', 'total_amount', 'description']
|
||||
widgets = {
|
||||
'containers': CheckboxSelectMultiple(),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import urllib.parse
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import requests
|
||||
|
||||
from DepoT.settings import env
|
||||
|
||||
|
||||
class EPay:
|
||||
payment_types = {
|
||||
"pay_credit_card": "credit_paydirect",
|
||||
"pay_epay": "paylogin",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def encode_message(message):
|
||||
encoded_data = base64.b64encode(message.encode("utf-8")).decode("utf-8")
|
||||
secret_key = env("EPAY_SECURITY_KEY")
|
||||
checksum = hmac.new(
|
||||
secret_key.encode("utf-8"), encoded_data.encode("utf-8"), hashlib.sha1
|
||||
).hexdigest()
|
||||
return encoded_data, checksum
|
||||
|
||||
@classmethod
|
||||
def generate_urls(self, invoice, amount):
|
||||
client_id = env("EPAY_CLIENT_ID")
|
||||
description = f"Invoice number: {invoice} with amount of {amount}"
|
||||
currency = "BGN"
|
||||
expiry = (
|
||||
datetime.today() + timedelta(days=int(env("INVOICE_EXPIRE_PERIOD")))
|
||||
).strftime("%d.%m.%Y")
|
||||
|
||||
if amount == 0:
|
||||
amount = 1000
|
||||
|
||||
message = f"MIN={client_id}\nINVOICE={invoice}\nAMOUNT={amount}\nCURRENCY={currency}\nEXP_TIME={expiry}\nDESCR={description}"
|
||||
encoded_data, checksum = EPay.encode_message(message)
|
||||
result = {}
|
||||
for pay_type, keyword in EPay.payment_types.items():
|
||||
url = f"https://demo.epay.bg/?PAGE={keyword}&ENCODED={urllib.parse.quote(encoded_data)}&CHECKSUM={urllib.parse.quote(checksum)}"
|
||||
result[pay_type] = url
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def payment_result(encoded_data, checksum_received):
|
||||
secret_key = env("EPAY_SECURITY_KEY")
|
||||
decoded_data = base64.b64decode(encoded_data).decode("utf-8")
|
||||
checksum_calculated = hmac.new(
|
||||
secret_key.encode("utf-8"), encoded_data.encode("utf-8"), hashlib.sha1
|
||||
).hexdigest()
|
||||
checksum_result = checksum_calculated == checksum_received
|
||||
|
||||
fields = decoded_data.split(":")
|
||||
fields_dict = {}
|
||||
for field in fields:
|
||||
try:
|
||||
name, value = field.split("=")
|
||||
except ValueError:
|
||||
print(fields)
|
||||
print(field)
|
||||
|
||||
fields_dict[name] = value
|
||||
|
||||
return fields_dict, checksum_result
|
||||
|
||||
@staticmethod
|
||||
def send_response(encoded_data, checksum):
|
||||
url = f"https://demo.epay.bg/?ENCODED={urllib.parse.quote(encoded_data)}&CHECKSUM={urllib.parse.quote(checksum)}"
|
||||
print(url)
|
||||
res = requests.post(url)
|
||||
|
||||
@staticmethod
|
||||
def send_ok(invoice):
|
||||
message = f"INVOICE={invoice}:STATUS=OK"
|
||||
encoded_data, checksum = EPay.encode_message(message)
|
||||
print(f"OK {invoice}")
|
||||
EPay.send_response(encoded_data, checksum)
|
||||
|
||||
@staticmethod
|
||||
def send_no(invoice):
|
||||
message = f"INVOICE={invoice}:STATUS=NO"
|
||||
encoded_data, checksum = EPay.encode_message(message)
|
||||
print(f"NO {invoice}")
|
||||
EPay.send_response(encoded_data, checksum)
|
||||
|
||||
@staticmethod
|
||||
def send_err(invoice):
|
||||
message = f"INVOICE={invoice}:STATUS=ERR:ERR=BAD_CHECKSUM"
|
||||
encoded_data, checksum = EPay.encode_message(message)
|
||||
print(f"ERR {invoice}")
|
||||
EPay.send_response(encoded_data, checksum)
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
from django.urls import path
|
||||
|
||||
from payments.views import PaymentCreateView
|
||||
from payments.views import PaymentCreateView, some_view
|
||||
|
||||
urlpatterns = [
|
||||
path("create/", PaymentCreateView.as_view(), name="payments_create"),
|
||||
path('invoice/', some_view, name='payments_invoice'),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from payments.models import ContainerTariffPeriod, AdditionalFees
|
||||
|
||||
|
||||
def calculate_charges(container):
|
||||
days = (container.expedited_on.date() - container.received_on.date()).days + 1
|
||||
total = 0
|
||||
|
||||
size = '20' if container.container_type.length == 20 else '40'
|
||||
|
||||
tariff = ContainerTariffPeriod.objects.get(
|
||||
Q(to_days__gt=days) | Q(to_days__isnull=True),
|
||||
container_size=size,
|
||||
from_days__lte=days,
|
||||
)
|
||||
|
||||
fees = AdditionalFees.objects.first()
|
||||
storage_charge = days * float(tariff.daily_rate)
|
||||
|
||||
if container.container_type.container_type == 'reefer':
|
||||
storage_charge += days * float(fees.reefer_daily_supplement)
|
||||
|
||||
if container.swept:
|
||||
total += float(fees.sweeping_fee)
|
||||
if container.washed:
|
||||
total += float(fees.fumigation_fee)
|
||||
|
||||
total += storage_charge
|
||||
|
||||
return {
|
||||
'days': days,
|
||||
'storage_charge': storage_charge,
|
||||
'services_charge': total - storage_charge,
|
||||
'total': total,
|
||||
}
|
||||
|
||||
+192
-6
@@ -1,25 +1,72 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.db.models import Sum
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import ListView, CreateView
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.platypus import Table, TableStyle, Spacer, Paragraph, SimpleDocTemplate
|
||||
|
||||
from common.models import CompanyModel
|
||||
from common.utils.utils import send_test_email
|
||||
from containers.models import Container
|
||||
from payments.forms import PaymentCreateForm
|
||||
from payments.models import Payment
|
||||
from payments.models import Payment, PaymentItem
|
||||
from payments.services import EPay
|
||||
from payments.utils import calculate_charges
|
||||
|
||||
import io
|
||||
from django.http import FileResponse, HttpResponse, response
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
# Create your views here.
|
||||
class PaymentCreateView(CreateView):
|
||||
model = Payment
|
||||
form_class = PaymentCreateForm
|
||||
template_name = 'employee/payment-create.html'
|
||||
success_url = reverse_lazy('payment-list')
|
||||
success_url = reverse_lazy('not_paid')
|
||||
|
||||
def form_valid(self, form):
|
||||
container_ids = self.request.POST.getlist('containers')
|
||||
|
||||
last_payment = Payment.objects.order_by('-invoice_number').first()
|
||||
today = datetime.now().strftime('%Y%m%d')
|
||||
|
||||
if last_payment and last_payment.invoice_number.startswith(f'INV-{today}'):
|
||||
last_number = int(last_payment.invoice_number.split('-')[-1])
|
||||
new_number = str(last_number + 1).zfill(4)
|
||||
else:
|
||||
new_number = '0001'
|
||||
|
||||
invoice_number = f'INV-{today}-{new_number}'
|
||||
|
||||
payment = form.save(commit=False)
|
||||
payment.invoice_number = invoice_number
|
||||
payment.created_by = self.request.user
|
||||
payment.updated_by = self.request.user
|
||||
payment.total_amount = 0
|
||||
payment.save()
|
||||
payment.containers.set(container_ids)
|
||||
|
||||
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()
|
||||
send_test_email('kikimor@gmail.com', urls) # invoice and
|
||||
# 0000000000000000000000000000000000#
|
||||
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -42,6 +89,145 @@ class PaymentCreateView(CreateView):
|
||||
company_pk = self.request.GET.get('company', '')
|
||||
form.fields['company'].initial = company_pk
|
||||
|
||||
# container_ids = [int(id) for id in container_ids if id.strip().isdigit()]
|
||||
# form.fields['containers'].initial = container_ids
|
||||
container_ids = self.request.GET.get('containers', '').split(',')
|
||||
container_ids = [int(_id) for _id in container_ids if _id.strip().isdigit()]
|
||||
|
||||
total_charges = {}
|
||||
if container_ids:
|
||||
containers = Container.objects.filter(
|
||||
id__in=container_ids,
|
||||
expedited=True,
|
||||
payment_containers__isnull=True
|
||||
)
|
||||
for container in containers:
|
||||
charges = calculate_charges(container)
|
||||
total_charges['days'] = total_charges.get('days', 0) + charges.get('days', 0)
|
||||
total_charges['storage_charge'] = total_charges.get('storage_charge', 0) + charges.get('storage_charge', 0)
|
||||
total_charges['services_charge'] = total_charges.get('services_charge', 0) + charges.get('services_charge', 0)
|
||||
total_charges['total'] = total_charges.get('total', 0) + charges.get('total', 0)
|
||||
form.fields['total_amount'].initial = total_charges['total']
|
||||
return form
|
||||
|
||||
def some_view(request):
|
||||
buffer = io.BytesIO()
|
||||
doc = SimpleDocTemplate(buffer, pagesize=A4)
|
||||
story = []
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Custom styles
|
||||
styles.add(ParagraphStyle(
|
||||
name='RightAlign',
|
||||
parent=styles['Normal'],
|
||||
alignment=2 # right alignment
|
||||
))
|
||||
|
||||
# Header data
|
||||
depot_data = [
|
||||
['ABC Depot Ltd.'],
|
||||
['123 Depot Street'],
|
||||
['Sofia, Bulgaria'],
|
||||
['VAT: BG123456789'],
|
||||
['Phone: +359 2 123 4567']
|
||||
]
|
||||
|
||||
customer_data = [
|
||||
['Customer Company Ltd.'],
|
||||
['456 Customer Ave.'],
|
||||
['London, UK'],
|
||||
['VAT: GB987654321'],
|
||||
['Contact: John Doe']
|
||||
]
|
||||
|
||||
# Create header table
|
||||
header_data = [[
|
||||
Table(depot_data, colWidths=[200]),
|
||||
Table(customer_data, colWidths=[200])
|
||||
]]
|
||||
header_table = Table(header_data, colWidths=[250, 250])
|
||||
header_table.setStyle(TableStyle([
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
]))
|
||||
story.append(header_table)
|
||||
story.append(Spacer(1, 30))
|
||||
|
||||
# Invoice title and number
|
||||
story.append(Paragraph('INVOICE', styles['Heading1']))
|
||||
story.append(Paragraph('Invoice No: INV-20240220-0001', styles['Normal']))
|
||||
story.append(Paragraph('Date: 20.02.2024', styles['Normal']))
|
||||
story.append(Spacer(1, 20))
|
||||
|
||||
# Invoice table data
|
||||
table_data = [
|
||||
['Container Type', 'Days', 'Swept', 'Washed', 'Amount'],
|
||||
['20\' Container', '15', 'Yes', 'No', '€450.00'],
|
||||
['40\' Container', '10', 'Yes', 'Yes', '€750.00'],
|
||||
]
|
||||
|
||||
# Create invoice table
|
||||
invoice_table = Table(table_data, colWidths=[120, 80, 80, 80, 100])
|
||||
invoice_table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 12),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||
]))
|
||||
story.append(invoice_table)
|
||||
story.append(Spacer(1, 20))
|
||||
|
||||
# Total amount
|
||||
total_text = f'Total Amount: €1,200.00'
|
||||
story.append(Paragraph(total_text, styles['RightAlign']))
|
||||
|
||||
# Build PDF
|
||||
doc.build(story, onFirstPage=add_watermark)
|
||||
pdf = buffer.getvalue()
|
||||
buffer.close()
|
||||
|
||||
# Create response
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
response['Content-Disposition'] = 'inline; filename="invoice.pdf"'
|
||||
response.write(pdf)
|
||||
return response
|
||||
|
||||
|
||||
def add_watermark(canvas, doc):
|
||||
canvas.saveState()
|
||||
canvas.setFillColorRGB(0.9, 0.9, 0.9)
|
||||
canvas.setFont('Helvetica', 80)
|
||||
canvas.rotate(45)
|
||||
canvas.drawString(200, 000, 'ORIGINAL')
|
||||
canvas.restoreState()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def on_payment_result(encoded, checksum):
|
||||
fields_dict, checksum_result = EPay.payment_result(encoded, checksum)
|
||||
status = fields_dict["STATUS"]
|
||||
invoice = fields_dict["INVOICE"]
|
||||
if not checksum_result:
|
||||
return f"INVOICE={invoice}:STATUS=ERR"
|
||||
|
||||
payment = Payment.objects.get(invoice_number=invoice)
|
||||
if not payment:
|
||||
return f"INVOICE={invoice}:STATUS=NO"
|
||||
|
||||
match status:
|
||||
case "PAID":
|
||||
payment.status = "paid"
|
||||
payment.paid_on = datetime.now()
|
||||
payment.pay_time = fields_dict["PAY_TIME"]
|
||||
payment.pay_stan = fields_dict["STAN"]
|
||||
payment.pay_bcode = fields_dict["BCODE"]
|
||||
|
||||
case "DENIED":
|
||||
payment.status = "denied"
|
||||
case "EXPIRED":
|
||||
payment.status = "expired"
|
||||
payment.save()
|
||||
return f"INVOICE={invoice}:STATUS=OK"
|
||||
@@ -37,4 +37,24 @@ insert into accounts_clientpermission (id, codename, name) values (5, 'can_view_
|
||||
insert into accounts_clientpermission (id, codename, name) values (6, 'can_manage_payment', 'Can manage payment');
|
||||
insert into accounts_clientpermission (id, codename, name) values (7, 'can_manage_company_users', 'Can manage company users');
|
||||
|
||||
INSERT INTO payments_containertariffperiod
|
||||
(container_size, from_days, to_days, daily_rate)
|
||||
VALUES
|
||||
('20', 1, 3, 5.00),
|
||||
('20', 4, 7, 10.00),
|
||||
('20', 8, 15, 15.00),
|
||||
('20', 16, NULL, 20.00);
|
||||
|
||||
-- For 40/45 feet containers
|
||||
INSERT INTO payments_containertariffperiod
|
||||
(container_size, from_days, to_days, daily_rate)
|
||||
VALUES
|
||||
('40', 1, 3, 10.00),
|
||||
('40', 4, 7, 15.00),
|
||||
('40', 8, 15, 20.00),
|
||||
('40', 16, NULL, 25.00);
|
||||
|
||||
INSERT INTO payments_additionalfees
|
||||
(reefer_daily_supplement, sweeping_fee, fumigation_fee)
|
||||
VALUES
|
||||
(3.00, 35.00, 75.00);
|
||||
|
||||
+8
-4
@@ -1,4 +1,4 @@
|
||||
from django.forms import ModelForm
|
||||
from django.forms import ModelForm, TextInput
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from preinfo.models import Preinfo
|
||||
@@ -8,7 +8,12 @@ class PreinfoBaseForm(ModelForm):
|
||||
class Meta:
|
||||
model = Preinfo
|
||||
fields = '__all__'
|
||||
|
||||
widgets = {
|
||||
'container_number': TextInput(attrs={
|
||||
'oninput': 'validateContainerNumber(this)',
|
||||
'class': 'form-control'
|
||||
})
|
||||
}
|
||||
# success_url = reverse_lazy('client_preinfo')
|
||||
class PreinfoCreateForm(PreinfoBaseForm):
|
||||
"""
|
||||
@@ -24,7 +29,6 @@ class PreinfoCreateForm(PreinfoBaseForm):
|
||||
'received'] # Exclude fields that should not be set by the user
|
||||
|
||||
|
||||
class PreinfoEditForm(ModelForm):
|
||||
class PreinfoEditForm(PreinfoBaseForm):
|
||||
class Meta:
|
||||
model = Preinfo
|
||||
fields = ['container_number', 'container_type', 'line']
|
||||
@@ -6,7 +6,8 @@ from django.views import View
|
||||
from django.views.generic import CreateView, ListView, UpdateView
|
||||
|
||||
from DepoT.mixins.LineFiltweFormMixin import LineFilterFormMixin
|
||||
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number
|
||||
from common.utils.utils import filter_queryset_by_user, get_preinfo_by_number, send_test_email
|
||||
from payments.services import EPay
|
||||
from preinfo.forms import PreinfoCreateForm, PreinfoEditForm
|
||||
from preinfo.models import Preinfo
|
||||
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
<text x="100" y="200" font-family="monospace" font-size="20" text-anchor="middle" fill="#5B2C06" font-weight="bold">K-DepoT</text>
|
||||
|
||||
<!-- Slogan -->
|
||||
<text x="100" y="220" font-family="monospace" font-size="12" text-anchor="middle" fill="#5B2C06" letter-spacing="2">SAFE AND READY</text>
|
||||
<text x="100" y="220" font-family="monospace" font-size="20" text-anchor="middle" fill="#5B2C06" letter-spacing="2">SAFE AND READY</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,35 @@
|
||||
function validateContainerNumber(input) {
|
||||
const containerNumber = input.value.trim();
|
||||
const feedbackElement = input.nextElementSibling;
|
||||
|
||||
if (!containerNumber) {
|
||||
input.classList.remove('invalid-container', 'incomplete-container');
|
||||
if (feedbackElement) feedbackElement.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (containerNumber.length > 0 && containerNumber.length < 11) {
|
||||
input.classList.remove('invalid-container');
|
||||
input.classList.add('incomplete-container');
|
||||
if (feedbackElement) feedbackElement.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`${validateContainerUrl}${containerNumber}/`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
input.classList.remove('incomplete-container');
|
||||
if (!data.valid) {
|
||||
input.classList.add('invalid-container');
|
||||
if (!feedbackElement) {
|
||||
const feedback = document.createElement('div');
|
||||
feedback.className = 'validation-feedback';
|
||||
feedback.textContent = 'Invalid container number';
|
||||
input.parentNode.insertBefore(feedback, input.nextSibling);
|
||||
}
|
||||
} else {
|
||||
input.classList.remove('invalid-container');
|
||||
if (feedbackElement) feedbackElement.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -12,12 +12,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
table.addEventListener('click', function(e) {
|
||||
const row = e.target.closest('.selectable-row');
|
||||
if (!row) return;
|
||||
|
||||
const checkbox = row.querySelector('input[type="checkbox"]');
|
||||
if (!checkbox) return;
|
||||
|
||||
if (selectionMode === 'single') {
|
||||
// Deselect all other rows
|
||||
table.querySelectorAll('.selected-row').forEach(selectedRow => {
|
||||
if (selectedRow !== row) {
|
||||
selectedRow.classList.remove('selected-row');
|
||||
@@ -25,22 +23,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle current row
|
||||
row.classList.toggle('selected-row');
|
||||
checkbox.checked = !checkbox.checked;
|
||||
|
||||
// Update buttons state
|
||||
const selectedRows = table.querySelectorAll('.selected-row');
|
||||
document.querySelectorAll('[data-requires-selection]').forEach(button => {
|
||||
button.disabled = selectedRows.length === 0;
|
||||
});
|
||||
|
||||
// Handle edit/delete buttons from original crud-list.js
|
||||
const objectId = row.dataset.id;
|
||||
const editBtn = document.getElementById('editBtn');
|
||||
const deleteBtn = document.getElementById('deleteBtn');
|
||||
|
||||
if (editBtn) {
|
||||
editBtn.removeAttribute('disabled');
|
||||
editBtn.href = editBtn.dataset.url?.replace('0', objectId);
|
||||
@@ -54,19 +45,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
toggleSelectAllBtn.addEventListener('click', function() {
|
||||
const rows = table.querySelectorAll('.selectable-row');
|
||||
allSelected = !allSelected;
|
||||
|
||||
rows.forEach(row => {
|
||||
const checkbox = row.querySelector('input[type="checkbox"]');
|
||||
checkbox.checked = allSelected;
|
||||
row.classList.toggle('selected-row', allSelected);
|
||||
});
|
||||
|
||||
// Update other buttons state
|
||||
document.querySelectorAll('[data-requires-selection]').forEach(button => {
|
||||
button.disabled = !allSelected;
|
||||
});
|
||||
|
||||
// Update button text
|
||||
this.textContent = allSelected ? 'Unselect All' : 'Select All';
|
||||
});
|
||||
|
||||
|
||||
+66
-13
@@ -36,8 +36,7 @@ input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="date"],
|
||||
input[type="number"],
|
||||
select,
|
||||
textarea {
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 0px;
|
||||
@@ -54,19 +53,43 @@ textarea {
|
||||
"wdth" 100;
|
||||
}
|
||||
|
||||
/* Submit button styling */
|
||||
button[type="submit"] {
|
||||
background-color: #a57d52;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-bottom: 0px;
|
||||
border: 1px solid #a57d52;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
/*margin-top: 20px;*/
|
||||
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;
|
||||
height: 100px; /* Adjust height as needed */
|
||||
min-height: 100px; /* Minimum height */
|
||||
resize: vertical; /* Allow vertical resizing */
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: #a67744;
|
||||
|
||||
|
||||
/* Submit button styling */
|
||||
button[type="submit"],
|
||||
button.btn-primary,
|
||||
button.btn-secondary {
|
||||
background-color: #F2E8DB;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid #a57d52;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover,
|
||||
button.btn-primary:hover,
|
||||
button.btn-secondary:hover{
|
||||
background-color: #EDDECB;
|
||||
}
|
||||
|
||||
/* Error messages */
|
||||
@@ -89,7 +112,7 @@ button[type="submit"]:hover {
|
||||
|
||||
.card {
|
||||
margin-top: 24px;
|
||||
background-color: white;
|
||||
background-color: #EDDECB;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -279,3 +302,33 @@ button[type="submit"]:hover {
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* Hide sidebar */
|
||||
.sidebar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Make main content full width */
|
||||
.main-content {
|
||||
margin-left: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Hide other non-printable elements if needed */
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Ensure white background and black text */
|
||||
body {
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
/* Remove shadows and borders */
|
||||
* {
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
background-color: #ead9cb;
|
||||
}
|
||||
|
||||
.wave {
|
||||
@@ -22,7 +23,7 @@ body {
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background-color: white;
|
||||
background-color: #ead9cb;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
overflow: hidden;
|
||||
@@ -31,6 +32,8 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: fadeIn 0.8s ease-in-out;
|
||||
margin: 2rem; /* Add margin to see the background */
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
+29
-73
@@ -1,76 +1,3 @@
|
||||
/*.table {*/
|
||||
/* width: 100%;*/
|
||||
/* border-collapse: separate;*/
|
||||
/* border-spacing: 0;*/
|
||||
/* margin: 10px auto;*/
|
||||
/* border: 1px solid #a57d52;*/
|
||||
/* border-radius: 10px;*/
|
||||
/* overflow: hidden;*/
|
||||
/*}*/
|
||||
|
||||
/*.table th {*/
|
||||
/* text-align: left;*/
|
||||
/* padding: 0.75rem;*/
|
||||
/* font-size: 0.75rem;*/
|
||||
/* text-transform: uppercase;*/
|
||||
/* font-weight: 600;*/
|
||||
/* border-bottom: 1px solid #a57d52;*/
|
||||
/* background-color: #F2E8DB;*/
|
||||
/*}*/
|
||||
|
||||
/*.table td {*/
|
||||
/* padding: 0.75rem;*/
|
||||
/* font-size: 0.875rem;*/
|
||||
/* border-bottom: 1px solid #a57d52;*/
|
||||
/*}*/
|
||||
|
||||
/*!* Last row shouldn't have bottom border *!*/
|
||||
/*.table tr:last-child td {*/
|
||||
/* border-bottom: none;*/
|
||||
/*}*/
|
||||
|
||||
/*!* Selection styles *!*/
|
||||
/*.selectable-row {*/
|
||||
/* cursor: pointer;*/
|
||||
/*}*/
|
||||
|
||||
/*.selected-row {*/
|
||||
/* background-color: rgba(165, 125, 82, 0.1);*/
|
||||
/*}*/
|
||||
|
||||
/*.status-received {*/
|
||||
/* background-color: #e8f5e9;*/
|
||||
/* color: #2e7d32;*/
|
||||
/*}*/
|
||||
|
||||
/*.status-pending {*/
|
||||
/* background-color: #fff3e0;*/
|
||||
/* color: #ef6c00;*/
|
||||
/*}*/
|
||||
|
||||
/*.status-overdue {*/
|
||||
/* background-color: #ffebee;*/
|
||||
/* color: #c62828;*/
|
||||
/*}*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -208,4 +135,33 @@ table tr:last-child td {
|
||||
color: #990000;
|
||||
}
|
||||
|
||||
.status-ok {
|
||||
color: #afddc0; /* green */
|
||||
font-weight: 600;
|
||||
}
|
||||
.status-deleted {
|
||||
color: #e49486; /* green */
|
||||
font-weight: 600;
|
||||
}
|
||||
.status-inactive {
|
||||
color: #b2b5b3; /* green */
|
||||
font-weight: 600;
|
||||
}
|
||||
.invalid-container {
|
||||
color: #df6161; /* red */
|
||||
font-weight: 600;
|
||||
}
|
||||
.validation-feedback {
|
||||
color: #dc2626;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
input.invalid-container {
|
||||
border-color: #dc2626;
|
||||
background-color: #fee2e2;
|
||||
}
|
||||
input.incomplete-container {
|
||||
background-color: #fbf3a2; /* red */
|
||||
border-color: #dc2626;
|
||||
}
|
||||
|
||||
@@ -5,19 +5,66 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Line Operator Dashboard | Container Depot</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||
<title>Barrier Dashboard | Container Depot</title>
|
||||
|
||||
{# <link rel="preconnect" href="https://fonts.googleapis.com">#}
|
||||
{# <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>#}
|
||||
{# <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;600&display=swap" rel="stylesheet">#}
|
||||
<link rel="stylesheet" href="{% static 'styles/forms.css' %}?v={% now 'U' %}">
|
||||
<link rel="stylesheet" href="{% static 'styles/base.css' %}?v={% now 'U' %}">
|
||||
<link rel="stylesheet" href="{% static 'styles/tables.css' %}?v={% now 'U' %}">
|
||||
|
||||
{# <script src="https://cdn.tailwindcss.com"></script>#}
|
||||
<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;
|
||||
}
|
||||
|
||||
@media (max-height: 600px) {
|
||||
.barrier-button {
|
||||
min-height: 100px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
|
||||
</script>
|
||||
|
||||
<script src="{% static 'js/container_validation.js' %}"></script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,73 +1,42 @@
|
||||
{% extends 'barrier/barrier-base.html' %}
|
||||
|
||||
{% extends "barrier/barrier-base.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="sidebar p-5 text-white border-b border-blue-700">
|
||||
<h1 class="text-xl font-bold">Container Depot</h1>
|
||||
<p class="text-sm text-blue-200">Line Operator Portal</p>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-screen items-center justify-center">
|
||||
<div class="sidebar w-[400px] h-[600px] text-white flex flex-col rounded-lg overflow-hidden">
|
||||
<div class="sidebar w-[100%] h-full text-white flex flex-col">
|
||||
<div class="p-5 border-b border-blue-700">
|
||||
<h1 class="text-xl font-bold">Container Depot</h1>
|
||||
<p class="text-sm text-blue-200">Line Operator Portal</p>
|
||||
</div>
|
||||
|
||||
<nav class="flex-grow py-4">
|
||||
<div class="px-4 py-2 text-xs text-blue-300 uppercase tracking-wider">Main</div>
|
||||
<a href="{% url 'barrier_dashboard' %}" class="nav-item active flex items-center px-6 py-10 text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
|
||||
</svg>
|
||||
Dashboard
|
||||
<div class="barrier-container">
|
||||
<a href="{% url 'container_receive' %}" class="barrier-button">
|
||||
Receive Container
|
||||
</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 href="{% url 'container_expedition' %}" class="barrier-button">
|
||||
Expedite Container
|
||||
</a>
|
||||
<a href="{% url 'container_search' %}?param=container_expedition" id="ordersNav" class="nav-item flex items-center px-6 py-10 text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
|
||||
</svg>
|
||||
Expedite container
|
||||
</a>
|
||||
<a href="#" class="nav-item flex items-center px-6 py-3 text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
Photos
|
||||
<a href="{% url 'barrier_photos' %}" class="barrier-button">
|
||||
Take Photos
|
||||
</a>
|
||||
|
||||
{# <div class="px-4 py-2 mt-6 text-xs text-blue-300 uppercase tracking-wider">Account</div>#}
|
||||
{# <a href="#" class="nav-item flex items-center px-6 py-3 text-white">#}
|
||||
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
|
||||
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />#}
|
||||
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />#}
|
||||
{# </svg>#}
|
||||
{# Settings#}
|
||||
{# </a>#}
|
||||
</nav>
|
||||
<h2 class="text-2xl font-bold mb-4">Recent History</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Container Number</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for container in recent_containers %}
|
||||
<tr>
|
||||
<td>{{ container.number }}</td>
|
||||
{% if container.expedited %}
|
||||
<td>{{ container.expedited_on }}</td>
|
||||
<td>Expedited</td>
|
||||
{% else %}
|
||||
<td>{{ container.received_on }}</td>
|
||||
<td>Received</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
{# <div class="p-4 border-t border-blue-700">#}
|
||||
{# <div class="flex items-center">#}
|
||||
{# <div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold">#}
|
||||
{# LO#}
|
||||
{# </div>#}
|
||||
{# <div class="ml-3">#}
|
||||
{# <p class="text-sm font-medium">Maersk Line</p>#}
|
||||
{# <p class="text-xs text-blue-300">Line Operator</p>#}
|
||||
{# </div>#}
|
||||
{# <button class="ml-auto text-white">#}
|
||||
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
|
||||
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />#}
|
||||
{# </svg>#}
|
||||
{# </button>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -24,6 +24,23 @@
|
||||
{% endblock content %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{% block custom_styles %}
|
||||
{% endblock custom_styles %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
|
||||
</script>
|
||||
<script src="{% static 'js/container_validation.js' %}"></script>
|
||||
|
||||
{% endblock extra_js %}
|
||||
|
||||
{% block crud_js %}
|
||||
{% endblock crud_js %}
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
</html>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{% url 'register' as register_url %}
|
||||
{% url 'client_company' as client_company_url %}
|
||||
{% url 'client_line' as client_line_url %}
|
||||
|
||||
{% url 'user_list' as user_list_url %}
|
||||
<nav class="nav-menu">
|
||||
<div class="section-title">Main</div>
|
||||
<a href="{{ dashboard_url }}" class="nav-item {% if request.path == dashboard_url %}active{% endif %}">
|
||||
@@ -43,7 +43,7 @@
|
||||
</a>
|
||||
{% if request.user.UserType == 'CA' or 1 == 1 %}
|
||||
<div class="section-title account">Nomenclatures</div>
|
||||
<a href="{{ register_url }}" class="nav-item {% if request.path == register_url %}active{% endif %}">
|
||||
<a href="{{ user_list_url }}" class="nav-item {% if request.path == user_list_url %}active{% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<th>Vehicles left</th>
|
||||
<th>Created on</th>
|
||||
<th>Created by</th>
|
||||
<th>Status</th>
|
||||
{% endblock table_header %}
|
||||
|
||||
{% block table_data %}
|
||||
@@ -26,6 +27,7 @@
|
||||
<td>{{ object.vehicles_left }}</td>
|
||||
<td>{{ object.created_on }}</td>
|
||||
<td>{{ object.created_by.username }}</td>
|
||||
<td>{{ object.status }}</td>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
@@ -33,11 +35,3 @@
|
||||
<a href="#" id="editBtn" data-url="{% url 'client_booking_update' pk=0 %}" class="btn btn-primary" disabled>Edit Preinfo</a>
|
||||
<button id="deleteButton" class="btn btn-danger">Delete Preinfo</button>
|
||||
{% endblock buttons %}
|
||||
|
||||
{% block create_modal_header %}
|
||||
<h2>Create Booking</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_header %}
|
||||
<h2>Edit Booking</h2>
|
||||
{% endblock modal_header %}
|
||||
|
||||
@@ -9,15 +9,17 @@
|
||||
<th>Line</th>
|
||||
<th>Created on</th>
|
||||
<th>Created by</th>
|
||||
<th>Status</th>
|
||||
{% endblock table_header %}
|
||||
|
||||
{% block table_data %}
|
||||
<td>{{ object.id }}</td>
|
||||
<td>{{ object.container_number }}</td>
|
||||
<td>{{ object.container_number|upper }}</td>
|
||||
<td>{{ object.container_type }}</td>
|
||||
<td>{{ object.line.short_name }}</td>
|
||||
<td>{{ object.created_on }}</td>
|
||||
<td>{{ object.created_on|date:"d.m.Y h:m" }}</td>
|
||||
<td>{{ object.created_by.username }}</td>
|
||||
<td>{{ object.received|yesno:"Received,Pending" }}</td>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
|
||||
@@ -1,20 +1,62 @@
|
||||
{% extends 'barrier/barrier-base.html' %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<h2 style="color: #a57d52; margin-bottom: 20px; text-align: center;">Container Expedition</h2>
|
||||
<div class="barrier-container">
|
||||
{% if show_search %}
|
||||
<form method="get" class="w-full">
|
||||
<div class="p-6">
|
||||
<label for="id_number" class="block text-2xl mb-4">
|
||||
Enter Booking Number
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<input type="text"
|
||||
id="id_number"
|
||||
name="number"
|
||||
value="{{ search_number }}"
|
||||
class="px-4 py-3 text-xl border-2 rounded-lg"
|
||||
required
|
||||
autocomplete="off"
|
||||
autofocus>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn-primary">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
{% if error %}
|
||||
<div class="text-red-600 text-xl text-center">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% elif form %}
|
||||
<form method="post" class="w-full">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="booking_id" value="{{ booking.id }}">
|
||||
<input type="hidden" name="container_id" value="{{ container.id }}">
|
||||
<input type="hidden" name="search_number" value="{{ search_number }}">
|
||||
<div class="p-6">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<label for="id_booking_number">Booking number:</label>
|
||||
<input type="text" name="booking_number" value="{{ booking.number }}" maxlength="11" required id="id_booking_number" readonly disabled>
|
||||
</p>
|
||||
{{ form.as_p }}
|
||||
<div >
|
||||
<button type="submit" >
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" name="expedite" class="btn-primary">
|
||||
Expedite container
|
||||
</button>
|
||||
<button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
@@ -1,10 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>$Title$</title>
|
||||
</head>
|
||||
<body>
|
||||
$END$
|
||||
</body>
|
||||
</html>
|
||||
{# templates/container-photos.html #}
|
||||
{% extends "barrier/barrier-base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="barrier-container">
|
||||
<h2 class="text-2xl mb-4">Container Photos - {{ container.number }}</h2>
|
||||
|
||||
{% csrf_token %} {# Added CSRF token here #}
|
||||
|
||||
<div class="photo-upload-section mb-4">
|
||||
<video id="video" class="mb-4 w-full" autoplay></video>
|
||||
<canvas id="canvas" style="display:none;"></canvas>
|
||||
|
||||
<div class="flex justify-between mb-4">
|
||||
<button id="capture" class="btn-primary" {% if photos_count >= 3 %}disabled{% endif %}>
|
||||
Take Photo ({{ photos_count }}/3)
|
||||
</button>
|
||||
<a href="{% url 'barrier_dashboard' %}" class="btn-secondary">Back to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="photos-preview" class="grid grid-cols-3 gap-4">
|
||||
{% for photo in photos %}
|
||||
<div class="photo-preview">
|
||||
<img src="{{ photo.photo.url }}" alt="Container photo" class="w-full">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const video = document.getElementById('video');
|
||||
const canvas = document.getElementById('canvas');
|
||||
const captureButton = document.getElementById('capture');
|
||||
let photoCount = {{ photos_count }};
|
||||
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||
|
||||
navigator.mediaDevices.getUserMedia({ video: true })
|
||||
.then(stream => {
|
||||
video.srcObject = stream;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Camera error:', err);
|
||||
alert('Could not access camera. Please check permissions.');
|
||||
});
|
||||
|
||||
|
||||
captureButton.addEventListener('click', () => {
|
||||
if (photoCount >= 3) return;
|
||||
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.getContext('2d').drawImage(video, 0, 0);
|
||||
|
||||
const photo = canvas.toDataURL('image/jpeg').split(',')[1];
|
||||
|
||||
fetch(`/api/damages/{{ container.pk }}/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
depot_id: {{ container.pk }},
|
||||
photo_extension: 'jpg',
|
||||
photo: photo
|
||||
})
|
||||
})
|
||||
.then(async response => {
|
||||
if (response.status === 201) {
|
||||
photoCount++;
|
||||
if (photoCount >= 3) {
|
||||
captureButton.disabled = true;
|
||||
}
|
||||
location.reload();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Upload failed');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert(error.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,593 +1,54 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Barrier Staff Interface | Container Depot</title>
|
||||
{# <script src="https://cdn.tailwindcss.com"></script>#}
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
{#background-color: #f5f7fa;#}
|
||||
}
|
||||
.sidebar {
|
||||
background: linear-gradient(180deg, #0f4c81 0%, #1a6baf 100%);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.content-area {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.nav-item {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.nav-item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.nav-item.active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-left: 4px solid white;
|
||||
}
|
||||
.card {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.tab {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.form-section {
|
||||
animation: slideIn 0.5s ease-in-out;
|
||||
}
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.camera-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
z-index: 50;
|
||||
display: none;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
.camera-frame {
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background-color: #f3f4f6;
|
||||
border: 2px dashed #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.image-preview svg {
|
||||
color: #9ca3af;
|
||||
}
|
||||
.image-preview.has-image {
|
||||
border: none;
|
||||
}
|
||||
.image-preview.has-image svg {
|
||||
display: none;
|
||||
}
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
|
||||
}
|
||||
70% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 10px rgba(255, 0, 0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex h-screen overflow-hidden">
|
||||
<!-- Sidebar -->
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="content-area flex-1 overflow-y-auto">
|
||||
<!-- Top Navigation -->
|
||||
<header class="bg-white shadow-sm">
|
||||
<div class="flex items-center justify-between px-6 py-4">
|
||||
<div class="flex items-center">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Receive Container</h2>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="relative">
|
||||
<span class="absolute top-0 right-0 -mt-1 -mr-1 flex h-4 w-4">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-4 w-4 bg-red-500 text-xs text-white justify-center items-center">3</span>
|
||||
</span>
|
||||
<button class="text-gray-500 hover:text-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-gray-700 font-medium">Gate North</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Container Reception Content -->
|
||||
<div id="receiveContent" class="tab-content active p-6">
|
||||
<div class="bg-white rounded-lg shadow mb-6">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-800">Container Reception</h3>
|
||||
<p class="text-sm text-gray-600 mt-1">Process incoming containers</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="bg-green-100 text-green-800 text-xs font-semibold px-3 py-1 rounded-full">Gate Open</span>
|
||||
</div>
|
||||
</div>
|
||||
<form id="receiveForm" class="space-y-6" method="POST">
|
||||
{% csrf_token %}
|
||||
{% extends "barrier/barrier-base.html" %}
|
||||
{% block content %}
|
||||
<div class="barrier-container">
|
||||
{% if show_search %}
|
||||
<form method="get" class="w-full">
|
||||
<div class="p-6">
|
||||
<label for="id_number" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label>
|
||||
{# <label for="id_number" >Container Number</label>#}
|
||||
{# <input type="text" id="id_number" name="number" placeholder="Enter Container Number" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4">#}
|
||||
{# <button type="button" id="check-preinfo-btn">Check Preinfo</button>#}
|
||||
|
||||
|
||||
|
||||
{# <label for="containerNumber" class="block text-sm font-medium text-gray-700 mb-1">Container Number</label>#}
|
||||
{# <div class="flex">#}
|
||||
{# <input type="text" id="id_number" name="number" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500" required>#}
|
||||
{# <button type="button" id="check-preinfo-btn" class="px-4 py-2 bg-blue-600 text-white rounded-r-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">#}
|
||||
{# Search#}
|
||||
{# </button>#}
|
||||
{# </div>#}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="preinfoData">
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-md p-4 mb-6">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<h3 class="text-sm font-medium text-blue-800">Preinfo Found</h3>
|
||||
<div class="mt-2 text-sm text-blue-700">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<label for="id_number" class="block text-2xl mb-4">
|
||||
Enter Container Number
|
||||
</label>
|
||||
<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>
|
||||
</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
|
||||
<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>
|
||||
|
||||
<input type="hidden" name="preinfo_id" id="preinfo_id" value="{{ preinfo.pk }}">
|
||||
|
||||
{% if error %}
|
||||
<div class="text-red-600 text-xl text-center">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
{% elif form %}
|
||||
<form method="post" class="w-full">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="search_number" value="{{ search_number }}">
|
||||
<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 class="flex flex-col gap-4">
|
||||
<div class="form-group">
|
||||
{{ form.as_p }}
|
||||
</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>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn-primary">
|
||||
Receive Container
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-grow flex items-center justify-center p-4">
|
||||
<div class="relative">
|
||||
<div class="camera-frame w-full max-w-2xl aspect-[4/3] bg-gray-800 flex items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
|
||||
<!-- Guide lines for container positioning -->
|
||||
<div class="absolute inset-0 border-2 border-dashed border-white opacity-50 m-8"></div>
|
||||
|
||||
<!-- Positioning guides -->
|
||||
<div class="absolute top-8 left-8 border-t-2 border-l-2 border-white w-8 h-8"></div>
|
||||
<div class="absolute top-8 right-8 border-t-2 border-r-2 border-white w-8 h-8"></div>
|
||||
<div class="absolute bottom-8 left-8 border-b-2 border-l-2 border-white w-8 h-8"></div>
|
||||
<div class="absolute bottom-8 right-8 border-b-2 border-r-2 border-white w-8 h-8"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 flex justify-center">
|
||||
<button id="capturePhoto" class="w-16 h-16 rounded-full bg-red-600 border-4 border-white flex items-center justify-center pulse">
|
||||
<div class="w-12 h-12 rounded-full bg-red-600"></div>
|
||||
<button type="button" onclick="window.location.href='{% url 'barrier_dashboard' %}'" class="btn-secondary">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# <script>#}
|
||||
{# // Set current date as default#}
|
||||
{# const today = new Date().toISOString().split('T')[0];#}
|
||||
{# document.getElementById('arrivalDate').value = today;#}
|
||||
{# document.getElementById('expeditionDate').value = today;#}
|
||||
{# #}
|
||||
{# // Tab Navigation#}
|
||||
{# const receiveContent = document.getElementById('receiveContent');#}
|
||||
{# const expediteContent = document.getElementById('expediteContent');#}
|
||||
{# #}
|
||||
{# const receiveNav = document.getElementById('receiveNav');#}
|
||||
{# const expediteNav = document.getElementById('expediteNav');#}
|
||||
{# #}
|
||||
{# receiveNav.addEventListener('click', function(e) {#}
|
||||
{# e.preventDefault();#}
|
||||
{# #}
|
||||
{# // Update content visibility#}
|
||||
{# receiveContent.classList.add('active');#}
|
||||
{# expediteContent.classList.remove('active');#}
|
||||
{# #}
|
||||
{# // Update navigation styling#}
|
||||
{# document.querySelector('.nav-item.active').classList.remove('active');#}
|
||||
{# receiveNav.classList.add('active');#}
|
||||
{# #}
|
||||
{# // Update header title#}
|
||||
{# document.querySelector('header h2').textContent = 'Receive Container';#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# expediteNav.addEventListener('click', function(e) {#}
|
||||
{# e.preventDefault();#}
|
||||
{# #}
|
||||
{# // Update content visibility#}
|
||||
{# receiveContent.classList.remove('active');#}
|
||||
{# expediteContent.classList.add('active');#}
|
||||
{# #}
|
||||
{# // Update navigation styling#}
|
||||
{# document.querySelector('.nav-item.active').classList.remove('active');#}
|
||||
{# expediteNav.classList.add('active');#}
|
||||
{# #}
|
||||
{# // Update header title#}
|
||||
{# document.querySelector('header h2').textContent = 'Expedite Container';#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# // Container search functionality#}
|
||||
{# document.getElementById('searchContainer').addEventListener('click', function() {#}
|
||||
{# const containerNumber = document.getElementById('containerNumber').value;#}
|
||||
{# if (containerNumber) {#}
|
||||
{# document.getElementById('preinfoData').classList.remove('hidden');#}
|
||||
{# } else {#}
|
||||
{# alert('Please enter a container number');#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# document.getElementById('searchExpediteContainer').addEventListener('click', function() {#}
|
||||
{# const containerNumber = document.getElementById('expediteContainerNumber').value;#}
|
||||
{# if (containerNumber) {#}
|
||||
{# document.getElementById('orderData').classList.remove('hidden');#}
|
||||
{# } else {#}
|
||||
{# alert('Please enter a container number');#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# // Camera functionality#}
|
||||
{# const cameraOverlay = document.getElementById('cameraOverlay');#}
|
||||
{# const takePhotoBtn = document.getElementById('takePhoto');#}
|
||||
{# const expediteTakePhotoBtn = document.getElementById('expediteTakePhoto');#}
|
||||
{# const closeCamera = document.getElementById('closeCamera');#}
|
||||
{# const capturePhoto = document.getElementById('capturePhoto');#}
|
||||
{# #}
|
||||
{# let currentImageTarget = null;#}
|
||||
{# #}
|
||||
{# function openCamera(imageTarget) {#}
|
||||
{# cameraOverlay.style.display = 'block';#}
|
||||
{# currentImageTarget = imageTarget;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# takePhotoBtn.addEventListener('click', function() {#}
|
||||
{# // Find the first empty preview slot#}
|
||||
{# for (let i = 1; i <= 4; i++) {#}
|
||||
{# const previewEl = document.getElementById(`preview${i}`);#}
|
||||
{# if (!previewEl.classList.contains('has-image')) {#}
|
||||
{# openCamera(`preview${i}`);#}
|
||||
{# break;#}
|
||||
{# }#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# expediteTakePhotoBtn.addEventListener('click', function() {#}
|
||||
{# // Find the first empty preview slot#}
|
||||
{# for (let i = 1; i <= 2; i++) {#}
|
||||
{# const previewEl = document.getElementById(`expeditePreview${i}`);#}
|
||||
{# if (!previewEl.classList.contains('has-image')) {#}
|
||||
{# openCamera(`expeditePreview${i}`);#}
|
||||
{# break;#}
|
||||
{# }#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# closeCamera.addEventListener('click', function() {#}
|
||||
{# cameraOverlay.style.display = 'none';#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# capturePhoto.addEventListener('click', function() {#}
|
||||
{# if (currentImageTarget) {#}
|
||||
{# const previewEl = document.getElementById(currentImageTarget);#}
|
||||
{# #}
|
||||
{# // Create a container SVG image#}
|
||||
{# const containerSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");#}
|
||||
{# containerSvg.setAttribute("class", "w-full h-full");#}
|
||||
{# containerSvg.setAttribute("viewBox", "0 0 120 120");#}
|
||||
{# #}
|
||||
{# // Random container color#}
|
||||
{# const colors = ['#2563eb', '#059669', '#d97706', '#7c3aed'];#}
|
||||
{# const randomColor = colors[Math.floor(Math.random() * colors.length)];#}
|
||||
{# #}
|
||||
{# // Create container body#}
|
||||
{# const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");#}
|
||||
{# rect.setAttribute("x", "10");#}
|
||||
{# rect.setAttribute("y", "20");#}
|
||||
{# rect.setAttribute("width", "100");#}
|
||||
{# rect.setAttribute("height", "80");#}
|
||||
{# rect.setAttribute("fill", randomColor);#}
|
||||
{# containerSvg.appendChild(rect);#}
|
||||
{# #}
|
||||
{# // Add some container details#}
|
||||
{# const detail1 = document.createElementNS("http://www.w3.org/2000/svg", "rect");#}
|
||||
{# detail1.setAttribute("x", "20");#}
|
||||
{# detail1.setAttribute("y", "30");#}
|
||||
{# detail1.setAttribute("width", "80");#}
|
||||
{# detail1.setAttribute("height", "20");#}
|
||||
{# detail1.setAttribute("fill", shadeColor(randomColor, -20));#}
|
||||
{# containerSvg.appendChild(detail1);#}
|
||||
{# #}
|
||||
{# const detail2 = document.createElementNS("http://www.w3.org/2000/svg", "rect");#}
|
||||
{# detail2.setAttribute("x", "20");#}
|
||||
{# detail2.setAttribute("y", "60");#}
|
||||
{# detail2.setAttribute("width", "80");#}
|
||||
{# detail2.setAttribute("height", "30");#}
|
||||
{# detail2.setAttribute("fill", shadeColor(randomColor, -20));#}
|
||||
{# containerSvg.appendChild(detail2);#}
|
||||
{# #}
|
||||
{# // Clear the preview and add the new SVG#}
|
||||
{# previewEl.innerHTML = '';#}
|
||||
{# previewEl.appendChild(containerSvg);#}
|
||||
{# previewEl.classList.add('has-image');#}
|
||||
{# #}
|
||||
{# // Close the camera#}
|
||||
{# cameraOverlay.style.display = 'none';#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# // Helper function to darken/lighten colors#}
|
||||
{# function shadeColor(color, percent) {#}
|
||||
{# let R = parseInt(color.substring(1,3), 16);#}
|
||||
{# let G = parseInt(color.substring(3,5), 16);#}
|
||||
{# let B = parseInt(color.substring(5,7), 16);#}
|
||||
{# #}
|
||||
{# R = parseInt(R * (100 + percent) / 100);#}
|
||||
{# G = parseInt(G * (100 + percent) / 100);#}
|
||||
{# B = parseInt(B * (100 + percent) / 100);#}
|
||||
{# #}
|
||||
{# R = (R<255)?R:255; #}
|
||||
{# G = (G<255)?G:255; #}
|
||||
{# B = (B<255)?B:255; #}
|
||||
{# #}
|
||||
{# R = Math.round(R);#}
|
||||
{# G = Math.round(G);#}
|
||||
{# B = Math.round(B);#}
|
||||
{# #}
|
||||
{# const RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));#}
|
||||
{# const GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));#}
|
||||
{# const BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));#}
|
||||
{# #}
|
||||
{# return "#"+RR+GG+BB;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# // Form submissions#}
|
||||
{# document.getElementById('receiveForm').addEventListener('submit', function(e) {#}
|
||||
{# e.preventDefault();#}
|
||||
{# #}
|
||||
{# // Check if container data is loaded#}
|
||||
{# if (document.getElementById('preinfoData').classList.contains('hidden')) {#}
|
||||
{# alert('Please search for a container first');#}
|
||||
{# return;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# // In a real app, this would send data to the server#}
|
||||
{# alert('Container reception completed successfully!');#}
|
||||
{# this.reset();#}
|
||||
{# document.getElementById('preinfoData').classList.add('hidden');#}
|
||||
{# #}
|
||||
{# // Reset image previews#}
|
||||
{# for (let i = 1; i <= 4; i++) {#}
|
||||
{# const previewEl = document.getElementById(`preview${i}`);#}
|
||||
{# previewEl.classList.remove('has-image');#}
|
||||
{# previewEl.innerHTML = `#}
|
||||
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
|
||||
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />#}
|
||||
{# </svg>#}
|
||||
{# `;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# // Add the first two images back#}
|
||||
{# document.getElementById('preview1').classList.add('has-image');#}
|
||||
{# document.getElementById('preview1').innerHTML = `#}
|
||||
{# <svg class="w-full h-full" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">#}
|
||||
{# <rect x="10" y="20" width="100" height="80" fill="#2563eb" />#}
|
||||
{# <rect x="20" y="30" width="30" height="20" fill="#1e40af" />#}
|
||||
{# <rect x="70" y="30" width="30" height="20" fill="#1e40af" />#}
|
||||
{# <rect x="20" y="60" width="80" height="30" fill="#1e40af" />#}
|
||||
{# <circle cx="85" cy="45" r="8" fill="#ef4444" />#}
|
||||
{# </svg>#}
|
||||
{# `;#}
|
||||
{# #}
|
||||
{# document.getElementById('preview2').classList.add('has-image');#}
|
||||
{# document.getElementById('preview2').innerHTML = `#}
|
||||
{# <svg class="w-full h-full" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">#}
|
||||
{# <rect x="10" y="20" width="100" height="80" fill="#2563eb" />#}
|
||||
{# <rect x="20" y="30" width="80" height="20" fill="#1e40af" />#}
|
||||
{# <rect x="20" y="60" width="80" height="30" fill="#1e40af" />#}
|
||||
{# <path d="M85,40 Q95,50 85,60 Q75,50 85,40" fill="#ef4444" />#}
|
||||
{# </svg>#}
|
||||
{# `;#}
|
||||
{# });#}
|
||||
{# #}
|
||||
{# document.getElementById('expediteForm').addEventListener('submit', function(e) {#}
|
||||
{# e.preventDefault();#}
|
||||
{# #}
|
||||
{# // Check if order data is loaded#}
|
||||
{# if (document.getElementById('orderData').classList.contains('hidden')) {#}
|
||||
{# alert('Please search for a container first');#}
|
||||
{# return;#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# // In a real app, this would send data to the server#}
|
||||
{# alert('Container expedition completed successfully!');#}
|
||||
{# this.reset();#}
|
||||
{# document.getElementById('orderData').classList.add('hidden');#}
|
||||
{# #}
|
||||
{# // Reset image previews#}
|
||||
{# for (let i = 1; i <= 2; i++) {#}
|
||||
{# const previewEl = document.getElementById(`expeditePreview${i}`);#}
|
||||
{# previewEl.classList.remove('has-image');#}
|
||||
{# previewEl.innerHTML = `#}
|
||||
{# <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">#}
|
||||
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />#}
|
||||
{# </svg>#}
|
||||
{# `;#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{# </script>#}
|
||||
{#<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9498ddc2d702313f',t:'MTc0ODg4NzM5My4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>#}
|
||||
{##}
|
||||
{#<script>#}
|
||||
{#document.getElementById('check-preinfo-btn').onclick = function() {#}
|
||||
{# const number = document.getElementById('id_number').value;#}
|
||||
{# fetch(`/preinfo/check-preinfo/?number=${encodeURIComponent(number)}`)#}
|
||||
{# .then(response => response.json())#}
|
||||
{# .then(data => {#}
|
||||
{# if (data.found) {#}
|
||||
{# alert('Preinfo found ' + data.container_type);#}
|
||||
{# document.getElementById('id_line').value = data.line;#}
|
||||
{# document.getElementById('id_container_type').value = data.container_type;#}
|
||||
{# document.getElementById('preinfo_id').value = data.preinfo_id;#}
|
||||
{# } else {#}
|
||||
{# alert('No preinfo found or already received.');#}
|
||||
{# }#}
|
||||
{# });#}
|
||||
{#;#}
|
||||
{#</script>#}
|
||||
</body>
|
||||
</html>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
+10
-3
@@ -2,9 +2,16 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>$Title$</title>
|
||||
<title>Payment Request</title>
|
||||
</head>
|
||||
<body>
|
||||
$END$
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h1>Welcome to {{ site_name }}!</h1>
|
||||
<p>Hello {{ user_email }},</p>
|
||||
<p>There is a new payment request, see details in the attachments. You can pay via ePay:</p>
|
||||
<a href="{{ link1 }}" style="background-color: #211FA6; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 10px;">ePay account</a>
|
||||
<p>or credit card:</p>
|
||||
<a href="{{ link2 }}" style="background-color: #211FA6; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 10px;">Credit card</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -24,6 +24,15 @@
|
||||
{% endblock content %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const validateContainerUrl = "{% url 'validate_container' 'placeholder' %}".replace('placeholder/', '');
|
||||
</script>
|
||||
<script src="{% static 'js/container_validation.js' %}"></script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,10 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>$Title$</title>
|
||||
</head>
|
||||
<body>
|
||||
$END$
|
||||
</body>
|
||||
</html>
|
||||
{# templates/barrier/container-search.html #}
|
||||
{% extends "barrier/barrier-base.html" %}
|
||||
{% block content %}
|
||||
<div class="barrier-container">
|
||||
<form method="post" class="w-full">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="search_type" value="{{ search_type }}">
|
||||
|
||||
<div class="p-6">
|
||||
<label for="id_number" class="block text-2xl mb-4">
|
||||
{% if search_type == 'container_receive' %}
|
||||
Enter Booking Number
|
||||
{% else %}
|
||||
Enter Container Number
|
||||
{% endif %}
|
||||
</label>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<input type="text"
|
||||
id="id_number"
|
||||
name="number"
|
||||
class="px-4 py-3 text-xl border-2 rounded-lg"
|
||||
required
|
||||
autocomplete="off"
|
||||
autofocus>
|
||||
|
||||
<button type="submit"
|
||||
class="barrier-button">
|
||||
Search
|
||||
</button>
|
||||
|
||||
{% if error %}
|
||||
<div class="text-red-600 text-xl text-center">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url 'barrier_dashboard' %}"
|
||||
class="barrier-button"
|
||||
style="background-color: #f5f5f5;">
|
||||
Back
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -16,10 +16,10 @@
|
||||
<td>{{ object.number }}</td>
|
||||
<td>{{ object.container_type }}</td>
|
||||
<td>{{ object.line.short_name }}</td>
|
||||
<td>{{ object.received_on }}</td>
|
||||
<td>{{ object.received_on|date:"y.m.d h:m" }}</td>
|
||||
<td>{{ object.position }}</td>
|
||||
<td>{{ object.swept }}</td>
|
||||
<td>{{ object.washed }}</td>
|
||||
<td>{{ object.swept|yesno:"Yes," }}</td>
|
||||
<td>{{ object.washed|yesno:"Yes," }}</td>
|
||||
<td>{{ object.booking.number }}</td>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
{% extends "employee-base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
{% for container in containers %}
|
||||
<input type="hidden" name="containers" value="{{ container.id }}">
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<div>
|
||||
<button id="createPaymentBtn" class="btn btn-primary" type="submit">Create Payment</button>
|
||||
</div>
|
||||
|
||||
<div class="selected-containers">
|
||||
<h3>Selected Containers</h3>
|
||||
@@ -30,5 +39,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -2,7 +2,7 @@
|
||||
{% load static %}
|
||||
|
||||
{% block filter %}
|
||||
<div class="filter-form">
|
||||
{#<div class="filter-form">#}
|
||||
<form method="GET">
|
||||
<span style="display: flex; align-items: center; width: 100%;">
|
||||
<label for="date" style="margin-right: 8px;">Date:</label>
|
||||
@@ -21,7 +21,7 @@
|
||||
<button type="submit">Search</button>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
{#</div>#}
|
||||
{% endblock %}
|
||||
|
||||
{% block selection %}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
background-color: #ead9cb;
|
||||
}
|
||||
header {
|
||||
position: relative;
|
||||
|
||||
+15
-11
@@ -1,11 +1,10 @@
|
||||
{% extends base_template %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% block filter %}
|
||||
{% endblock filter %}
|
||||
{% block filter %}
|
||||
{% endblock filter %}
|
||||
|
||||
<table id="objectTable" class="table" data-selection-mode=
|
||||
{% block selection %}
|
||||
@@ -24,6 +23,10 @@
|
||||
{% block table_data %}
|
||||
{% endblock table_data %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="100%">No data to display</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
@@ -60,8 +63,9 @@
|
||||
{#footerCell.style.textAlign = 'center';#}
|
||||
</script>
|
||||
|
||||
{% block custom_styles %}
|
||||
<style>
|
||||
|
||||
{% block custom_styles %}
|
||||
<style>
|
||||
.selectable-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -72,13 +76,13 @@
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
{% endblock custom_styles %}
|
||||
</style>
|
||||
{% endblock custom_styles %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% block crud_js %}
|
||||
<script src="{% static 'js/crud-list.js' %}"></script>
|
||||
{% endblock extra_js %}
|
||||
{% block custom_js %}
|
||||
{% endblock crud_js %}
|
||||
|
||||
{% endblock custom_js %}
|
||||
{% endblock content %}
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<link rel="stylesheet" href="{% static '/styles/styles.css' %}"/>
|
||||
</head>
|
||||
<body>
|
||||
{# {{ title|default:"Container Depot Management System" }} {{ description|default:"Login to the Container Depot Management System" }}#}
|
||||
<div class="background-image"></div>
|
||||
<div class="wave"></div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user