a lot of changes
This commit is contained in:
@@ -15,6 +15,7 @@ APPS = [
|
|||||||
"django_core",
|
"django_core",
|
||||||
"core.apps.accounts.apps.AccountsConfig",
|
"core.apps.accounts.apps.AccountsConfig",
|
||||||
'core.apps.tasks.apps.TasksConfig',
|
'core.apps.tasks.apps.TasksConfig',
|
||||||
|
'core.apps.documents.apps.DocumentsConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
if env.bool("SILK_ENABLED", False):
|
if env.bool("SILK_ENABLED", False):
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ urlpatterns = [
|
|||||||
path("api/v1/", include("core.apps.payment.urls")),
|
path("api/v1/", include("core.apps.payment.urls")),
|
||||||
path("api/v1/", include("core.apps.chat.urls")),
|
path("api/v1/", include("core.apps.chat.urls")),
|
||||||
path("api/v1/tasks/", include("core.apps.tasks.urls")),
|
path("api/v1/tasks/", include("core.apps.tasks.urls")),
|
||||||
|
path("api/v1/documents/", include("core.apps.documents.urls")),
|
||||||
]
|
]
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
|
|||||||
0
core/apps/documents/__init__.py
Normal file
0
core/apps/documents/__init__.py
Normal file
5
core/apps/documents/apps.py
Normal file
5
core/apps/documents/apps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentsConfig(AppConfig):
|
||||||
|
name = "core.apps.documents"
|
||||||
0
core/apps/documents/migrations/__init__.py
Normal file
0
core/apps/documents/migrations/__init__.py
Normal file
7
core/apps/documents/urls.py
Normal file
7
core/apps/documents/urls.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from core.apps.documents.views.contract import GenerateApiView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('generate-contract-pdf/', GenerateApiView.as_view(), name='generate_contract_pdf'),
|
||||||
|
]
|
||||||
153
core/apps/documents/views/contract.py
Normal file
153
core/apps/documents/views/contract.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
"""
|
||||||
|
PDF generatsiya view — JSON yuborasiz, PDF olasiz.
|
||||||
|
Yuborilmagan maydonlar uchun bo'sh string ('') qaytariladi —
|
||||||
|
shunda templateda {{ ... }} qoldiqlari ko'rinmaydi.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from django.http import HttpResponse, JsonResponse
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.views.decorators.http import require_http_methods
|
||||||
|
from weasyprint import HTML
|
||||||
|
from rest_framework import permissions, views
|
||||||
|
|
||||||
|
# Kompaniya defaultlari
|
||||||
|
COMPANY_DEFAULTS = {
|
||||||
|
'company_name': 'SIFAT BAHOLASH',
|
||||||
|
'membership_number': '122',
|
||||||
|
'membership_date': '01.06.2023',
|
||||||
|
'phone_1': '(71) 278-85-85',
|
||||||
|
'phone_2': '(91) 585-77-77',
|
||||||
|
'phone_3': '(90) 535-99-99',
|
||||||
|
'email': 'sifat.baholash@gmail.com',
|
||||||
|
'bank_account': '2020 8 000 405 309 735 001',
|
||||||
|
'bank_mfo': '00440',
|
||||||
|
'inn': '307 930 412',
|
||||||
|
'director_name': 'Тураев Т.Р.',
|
||||||
|
'appraiser_name': 'Тураев Т.Р.',
|
||||||
|
'appraiser_certificate': '0988',
|
||||||
|
'appraiser_certificate_date': '17.11.2021',
|
||||||
|
'insurance_policy': '19-01-25/0000368-2025',
|
||||||
|
'insurance_start': '30.05.2025',
|
||||||
|
'insurance_end': '29.05.2026',
|
||||||
|
'market_name': 'Сергели автомобил бозори',
|
||||||
|
'city': 'Ташкент',
|
||||||
|
'year': '2026',
|
||||||
|
'total_pages': 21,
|
||||||
|
'valuation_purpose': 'для обеспечения залогом',
|
||||||
|
'valuation_purpose_full': 'определение рыночной стоимости для обеспечения залогом',
|
||||||
|
'adj_bargaining': '-9,00%',
|
||||||
|
'adj_tech': '2,00%',
|
||||||
|
'adj_equipment': '3,00%',
|
||||||
|
'cost_weight': '20',
|
||||||
|
'comp_weight': '80',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Template ichida ishlatiladigan barcha maydonlar
|
||||||
|
ALL_FIELDS = [
|
||||||
|
'report_number', 'report_date', 'valuation_date',
|
||||||
|
'customer_name', 'customer_short_name', 'customer_passport',
|
||||||
|
'owner_name', 'owner_address',
|
||||||
|
'vehicle_brand', 'vehicle_model', 'vehicle_plate',
|
||||||
|
'vehicle_type', 'vehicle_year', 'vehicle_mileage',
|
||||||
|
'vehicle_engine_number', 'vehicle_body_number', 'vehicle_chassis_number',
|
||||||
|
'vehicle_color', 'vehicle_tech_passport',
|
||||||
|
'spec_type', 'spec_seats', 'spec_length', 'spec_width', 'spec_height',
|
||||||
|
'spec_wheelbase', 'spec_clearance',
|
||||||
|
'engine_type', 'engine_volume', 'engine_cylinders',
|
||||||
|
'engine_power', 'engine_torque', 'engine_max_speed',
|
||||||
|
'engine_acceleration', 'transmission',
|
||||||
|
'fuel_tank', 'trunk', 'curb_weight', 'gross_weight',
|
||||||
|
'brakes_front', 'brakes_rear', 'tires',
|
||||||
|
'cost_c0', 'cost_tf', 'cost_lf', 'cost_type_name',
|
||||||
|
'cost_kt', 'cost_kl', 'cost_omega',
|
||||||
|
'cost_wear_andrianov', 'cost_wear_expert', 'cost_wear_avg',
|
||||||
|
'cost_func_wear', 'cost_total_wear', 'cost_capital',
|
||||||
|
'cost_approach_value', 'cost_approach_value_words',
|
||||||
|
'analog_1_source', 'analog_1_year', 'analog_1_mileage', 'analog_1_price',
|
||||||
|
'analog_1_adj_1', 'analog_1_adj_2', 'analog_1_adj_3', 'analog_1_weight',
|
||||||
|
'analog_2_source', 'analog_2_year', 'analog_2_mileage', 'analog_2_price',
|
||||||
|
'analog_2_adj_1', 'analog_2_adj_2', 'analog_2_adj_3', 'analog_2_weight',
|
||||||
|
'analog_3_source', 'analog_3_year', 'analog_3_mileage', 'analog_3_price',
|
||||||
|
'analog_3_adj_1', 'analog_3_adj_2', 'analog_3_adj_3', 'analog_3_weight',
|
||||||
|
'comp_total_usd', 'comp_total_sum', 'comp_total_words',
|
||||||
|
'cost_specific', 'comp_specific', 'weighted_value',
|
||||||
|
'market_value_number', 'market_value_words',
|
||||||
|
'rate_rur', 'rate_usd', 'rate_eur',
|
||||||
|
'exploitation_period', 'retail_price',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _img_or_placeholder(image_url, placeholder_text, alt='', height='200mm'):
|
||||||
|
"""Rasm URL bo'lsa <img>, bo'lmasa placeholder qaytaradi"""
|
||||||
|
if image_url:
|
||||||
|
return f'<img src="{image_url}" alt="{alt}" style="max-width:100%;max-height:{height};">'
|
||||||
|
return (
|
||||||
|
f'<div class="image-placeholder" style="height: {height};">'
|
||||||
|
f'{placeholder_text}'
|
||||||
|
f'</div>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_context(data: dict) -> dict:
|
||||||
|
"""Templatedagi barcha o'zgaruvchilar uchun qiymat tayyorlaydi"""
|
||||||
|
# 1) Barcha maydonlarni bo'sh string bilan to'ldirish
|
||||||
|
context = {field: '' for field in ALL_FIELDS}
|
||||||
|
|
||||||
|
# 2) Kompaniya defaultlari
|
||||||
|
context.update(COMPANY_DEFAULTS)
|
||||||
|
|
||||||
|
# 3) Foydalanuvchi yuborgan ma'lumotlarni ustiga yozish
|
||||||
|
context.update(data)
|
||||||
|
|
||||||
|
# 4) Rasmlar uchun tayyor HTML
|
||||||
|
context['cover_photo_html'] = _img_or_placeholder(
|
||||||
|
data.get('vehicle_photo'),
|
||||||
|
'[ Фото автомобиля ]',
|
||||||
|
alt='Vehicle',
|
||||||
|
height='50mm',
|
||||||
|
)
|
||||||
|
context['customer_passport_html'] = _img_or_placeholder(
|
||||||
|
data.get('customer_passport_image'),
|
||||||
|
'[ Изображение паспорта заказчика ]',
|
||||||
|
alt='Паспорт заказчика',
|
||||||
|
)
|
||||||
|
context['tech_passport_html'] = _img_or_placeholder(
|
||||||
|
data.get('tech_passport_image'),
|
||||||
|
'[ Изображение технического паспорта ]',
|
||||||
|
alt='Технический паспорт',
|
||||||
|
)
|
||||||
|
context['price_list_html'] = _img_or_placeholder(
|
||||||
|
data.get('price_list_image'),
|
||||||
|
'[ Прайс-лист завода-изготовителя ]',
|
||||||
|
alt='Прайс-лист',
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5) Loop maydonlar
|
||||||
|
if not context.get('analog_screenshots'):
|
||||||
|
context['analog_screenshots'] = [
|
||||||
|
{'page_num': 18, 'image': None},
|
||||||
|
{'page_num': 19, 'image': None},
|
||||||
|
{'page_num': 20, 'image': None},
|
||||||
|
]
|
||||||
|
if not context.get('vehicle_photos'):
|
||||||
|
context['vehicle_photos'] = []
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateApiView(views.APIView):
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
|
||||||
|
try:
|
||||||
|
html_string = render_to_string('report_template.html', context)
|
||||||
|
pdf_bytes = HTML(string=html_string).write_pdf()
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'error': f'PDF yaratishda xato: {str(e)}'}, status=500)
|
||||||
|
|
||||||
|
filename = f'otchet_1.pdf'
|
||||||
|
|
||||||
|
response = HttpResponse(pdf_bytes, content_type='application/pdf')
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||||
|
return response
|
||||||
@@ -5,6 +5,17 @@ ENV SCRIPT=$SCRIPT
|
|||||||
|
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
glib \
|
||||||
|
gdk-pixbuf \
|
||||||
|
pango \
|
||||||
|
cairo \
|
||||||
|
libffi \
|
||||||
|
shared-mime-info \
|
||||||
|
fontconfig \
|
||||||
|
ttf-dejavu \
|
||||||
|
ttf-liberation
|
||||||
|
|
||||||
COPY requirements.txt /code/requirements.txt
|
COPY requirements.txt /code/requirements.txt
|
||||||
|
|
||||||
RUN uv pip install -r requirements.txt
|
RUN uv pip install -r requirements.txt
|
||||||
@@ -16,5 +27,3 @@ COPY ./resources/scripts/$SCRIPT /code/$SCRIPT
|
|||||||
RUN chmod +x /code/resources/scripts/$SCRIPT
|
RUN chmod +x /code/resources/scripts/$SCRIPT
|
||||||
|
|
||||||
CMD sh /code/resources/scripts/$SCRIPT
|
CMD sh /code/resources/scripts/$SCRIPT
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
191
requirements.txt
191
requirements.txt
@@ -1,52 +1,143 @@
|
|||||||
backports.tarfile==1.2.0
|
aiohappyeyeballs
|
||||||
celery==5.4.0
|
aiohttp
|
||||||
django-cors-headers==4.6.0
|
aiosignal
|
||||||
django-environ==0.11.2
|
amqp
|
||||||
django-extensions==3.2.3
|
annotated-doc
|
||||||
django-filter==24.3
|
annotated-types
|
||||||
django-redis==5.4.0
|
arrow
|
||||||
django-unfold==0.65.0
|
asgiref
|
||||||
djangorestframework-simplejwt==5.3.1
|
astor
|
||||||
drf-spectacular==0.28.0
|
attrs
|
||||||
importlib-metadata==8.5.0
|
backports.tarfile
|
||||||
importlib-resources==6.4.5
|
|
||||||
inflect==7.3.1
|
|
||||||
jaraco.collections==5.1.0
|
|
||||||
packaging==24.2
|
|
||||||
pip-chill==1.0.3
|
|
||||||
platformdirs==4.3.6
|
|
||||||
psycopg2-binary==2.9.10
|
|
||||||
tomli==2.2.1
|
|
||||||
uvicorn==0.32.1
|
|
||||||
jst-django-core~=1.2.2
|
|
||||||
rich
|
|
||||||
pydantic
|
|
||||||
bcrypt
|
bcrypt
|
||||||
pytest-django
|
billiard
|
||||||
requests
|
binaryornot
|
||||||
model_bakery
|
black
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
django-modeltranslation~=0.19.11
|
|
||||||
django-ckeditor-5==0.2.15
|
|
||||||
|
|
||||||
django-rosetta==0.10.1
|
|
||||||
django-cacheops~=7.1
|
|
||||||
|
|
||||||
|
|
||||||
# !NOTE: on-server
|
|
||||||
# gunicorn
|
|
||||||
|
|
||||||
|
|
||||||
django-storages
|
|
||||||
boto3
|
boto3
|
||||||
|
botocore
|
||||||
|
brotli
|
||||||
# !NOTE: on-websocket
|
celery
|
||||||
# websockets
|
certifi
|
||||||
# channels-redis
|
cffi
|
||||||
|
chardet
|
||||||
grpcio>=1.62.0
|
charset-normalizer
|
||||||
grpcio-tools>=1.62.0
|
click
|
||||||
protobuf>=4.25.0
|
click-didyoumean
|
||||||
|
click-plugins
|
||||||
|
click-repl
|
||||||
|
colorlog
|
||||||
|
cookiecutter
|
||||||
|
cssselect2
|
||||||
|
Django
|
||||||
|
django-cacheops
|
||||||
|
django-ckeditor-5
|
||||||
|
django-cors-headers
|
||||||
|
django-environ
|
||||||
|
django-extensions
|
||||||
|
django-filter
|
||||||
|
django-modeltranslation
|
||||||
|
django-redis
|
||||||
|
django-rosetta
|
||||||
|
django-storages
|
||||||
|
django-unfold
|
||||||
|
djangorestframework
|
||||||
|
djangorestframework-simplejwt
|
||||||
|
drf-spectacular
|
||||||
|
flake8
|
||||||
|
fonttools
|
||||||
|
frozenlist
|
||||||
|
funcy
|
||||||
|
g4f
|
||||||
|
grpcio
|
||||||
|
grpcio-tools
|
||||||
|
h11
|
||||||
|
idna
|
||||||
|
importlib_metadata
|
||||||
|
importlib_resources
|
||||||
|
inflect
|
||||||
|
inflection
|
||||||
|
iniconfig
|
||||||
|
isort
|
||||||
|
jaraco.collections
|
||||||
|
jaraco.context
|
||||||
|
jaraco.functools
|
||||||
|
jaraco.text
|
||||||
|
Jinja2
|
||||||
|
jmespath
|
||||||
|
jsonschema
|
||||||
|
jsonschema-specifications
|
||||||
|
jst-aicommit
|
||||||
|
jst-django
|
||||||
|
jst-django-core
|
||||||
|
kombu
|
||||||
|
markdown-it-py
|
||||||
|
MarkupSafe
|
||||||
|
mccabe
|
||||||
|
mdurl
|
||||||
|
model-bakery
|
||||||
|
more-itertools
|
||||||
|
multidict
|
||||||
|
mypy-extensions
|
||||||
|
nest-asyncio
|
||||||
|
packaging
|
||||||
|
pathspec
|
||||||
|
pillow
|
||||||
|
pip-chill
|
||||||
|
platformdirs
|
||||||
|
pluggy
|
||||||
|
polib
|
||||||
|
prompt-toolkit
|
||||||
|
propcache
|
||||||
|
protobuf
|
||||||
|
psycopg2-binary
|
||||||
|
pycodestyle
|
||||||
|
pycparser
|
||||||
|
pycryptodome
|
||||||
|
pydantic
|
||||||
|
pydantic_core
|
||||||
|
pydyf
|
||||||
|
pyflakes
|
||||||
|
Pygments
|
||||||
|
PyJWT
|
||||||
|
pyphen
|
||||||
|
pytest
|
||||||
|
pytest-django
|
||||||
|
python-dateutil
|
||||||
|
python-slugify
|
||||||
|
PyYAML
|
||||||
|
questionary
|
||||||
|
redis
|
||||||
|
referencing
|
||||||
|
reportlab
|
||||||
|
requests
|
||||||
|
rich
|
||||||
|
rpds-py
|
||||||
|
s3transfer
|
||||||
|
setuptools
|
||||||
|
shellingham
|
||||||
|
six
|
||||||
|
sqlparse
|
||||||
|
tenacity
|
||||||
|
text-unidecode
|
||||||
|
tinycss2
|
||||||
|
tinyhtml5
|
||||||
|
tomli
|
||||||
|
tqdm
|
||||||
|
typeguard
|
||||||
|
typer
|
||||||
|
typer-slim
|
||||||
|
types-python-dateutil
|
||||||
|
typing-inspection
|
||||||
|
typing_extensions
|
||||||
|
tzdata
|
||||||
|
uritemplate
|
||||||
|
urllib3
|
||||||
|
uvicorn
|
||||||
|
vine
|
||||||
|
wcwidth
|
||||||
|
weasyprint
|
||||||
|
webencodings
|
||||||
|
yarl
|
||||||
|
zipfile36
|
||||||
|
zipp
|
||||||
|
zopfli
|
||||||
|
|||||||
2567
resources/templates/documents/contract.html
Normal file
2567
resources/templates/documents/contract.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user